Added --query-eval, implements #280

This commit is contained in:
Rhet Turnbull
2021-04-08 22:15:58 -07:00
parent 520a15fac6
commit b4bc906b6a
4 changed files with 75 additions and 1 deletions

View File

@@ -315,6 +315,19 @@ Options:
albums. albums.
--not-in-album Search for photos that are not in any albums. --not-in-album Search for photos that are not in any albums.
--query-eval CRITERIA Evaluate CRITERIA to filter photos. CRITERIA
will be evaluated in context of the following
list comprehension: `photos = [photo for photo
in photos if CRITERIA]` where photo represents
a PhotoInfo object. For example: `--query-eval
photo.favorite` returns all photos that have
been favorited and is equivalent to
--favorite. You may specify more than one
CRITERIA by using --query-eval multiple times.
See https://rhettbull.github.io/osxphotos/ for
additional documentation on the PhotoInfo
class.
--missing Export only photos missing from the Photos --missing Export only photos missing from the Photos
library; must be used with --download-missing. library; must be used with --download-missing.

View File

@@ -1,3 +1,3 @@
""" version info """ """ version info """
__version__ = "0.41.9" __version__ = "0.41.10"

View File

@@ -447,6 +447,19 @@ def query_options(f):
is_flag=True, is_flag=True,
help="Search for photos that are not in any albums.", help="Search for photos that are not in any albums.",
), ),
o(
"--query-eval",
metavar="CRITERIA",
multiple=True,
help="Evaluate CRITERIA to filter photos. "
"CRITERIA will be evaluated in context of the following list comprehension: "
"`photos = [photo for photo in photos if CRITERIA]` "
"where photo represents a PhotoInfo object. "
"For example: `--query-eval photo.favorite` returns all photos that have been "
"favorited and is equivalent to --favorite. "
"You may specify more than one CRITERIA by using --query-eval multiple times. "
"See https://rhettbull.github.io/osxphotos/ for additional documentation on the PhotoInfo class.",
),
] ]
for o in options[::-1]: for o in options[::-1]:
f = o(f) f = o(f)
@@ -984,6 +997,7 @@ def export(
beta, beta,
in_album, in_album,
not_in_album, not_in_album,
query_eval,
): ):
"""Export photos from the Photos database. """Export photos from the Photos database.
Export path DEST is required. Export path DEST is required.
@@ -1131,6 +1145,7 @@ def export(
only_new = cfg.only_new only_new = cfg.only_new
in_album = cfg.in_album in_album = cfg.in_album
not_in_album = cfg.not_in_album not_in_album = cfg.not_in_album
query_eval = cfg.query_eval
# config file might have changed verbose # config file might have changed verbose
VERBOSE = bool(verbose) VERBOSE = bool(verbose)
@@ -1433,6 +1448,7 @@ def export(
# skip missing bursts if using --download-missing by itself as AppleScript otherwise causes errors # skip missing bursts if using --download-missing by itself as AppleScript otherwise causes errors
missing_bursts=(download_missing and use_photokit) or not download_missing, missing_bursts=(download_missing and use_photokit) or not download_missing,
name=name, name=name,
query_eval=query_eval,
) )
if photos: if photos:
@@ -1718,6 +1734,7 @@ def query(
is_reference, is_reference,
in_album, in_album,
not_in_album, not_in_album,
query_eval,
): ):
"""Query the Photos database using 1 or more search options; """Query the Photos database using 1 or more search options;
if more than one option is provided, they are treated as "AND" if more than one option is provided, they are treated as "AND"
@@ -1744,6 +1761,7 @@ def query(
to_time, to_time,
label, label,
is_reference, is_reference,
query_eval,
] ]
exclusive = [ exclusive = [
(favorite, not_favorite), (favorite, not_favorite),
@@ -1866,6 +1884,7 @@ def query(
in_album=in_album, in_album=in_album,
not_in_album=not_in_album, not_in_album=not_in_album,
name=name, name=name,
query_eval=query_eval,
) )
# below needed for to make CliRunner work for testing # below needed for to make CliRunner work for testing
@@ -2044,6 +2063,7 @@ def _query(
burst_photos=None, burst_photos=None,
missing_bursts=None, missing_bursts=None,
name=None, name=None,
query_eval=None,
): ):
"""Run a query against PhotosDB to extract the photos based on user supply criteria used by query and export commands """Run a query against PhotosDB to extract the photos based on user supply criteria used by query and export commands
@@ -2338,6 +2358,16 @@ def _query(
) )
photos = photo_list photos = photo_list
if query_eval:
for q in query_eval:
query_string = f"[photo for photo in photos if {q}]"
try:
photos = eval(query_string)
except Exception as e:
raise click.BadOptionUsage(
"query_eval", f"Invalid query-eval CRITERIA: {e}"
)
return photos return photos

View File

@@ -5768,3 +5768,34 @@ def test_export_name():
files = glob.glob("*") files = glob.glob("*")
assert len(files) == 1 assert len(files) == 1
def test_query_eval():
""" test export --query-eval """
import glob
from osxphotos.cli import export
runner = CliRunner()
cwd = os.getcwd()
# pylint: disable=not-context-manager
with runner.isolated_filesystem():
result = runner.invoke(
export, [os.path.join(cwd, PHOTOS_DB_15_7), ".", "-V", "--query-eval", "'DSC03584' in photo.original_filename"]
)
assert result.exit_code == 0
files = glob.glob("*")
assert len(files) == 1
def test_bad_query_eval():
""" test export --query-eval with bad input """
import glob
from osxphotos.cli import export
runner = CliRunner()
cwd = os.getcwd()
# pylint: disable=not-context-manager
with runner.isolated_filesystem():
result = runner.invoke(
export, [os.path.join(cwd, PHOTOS_DB_15_7), ".", "-V", "--query-eval", "'DSC03584' in photo.originalfilename"]
)
assert result.exit_code != 0
assert "Error: Invalid query-eval CRITERIA" in result.output