Implemented --query-function, #430
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
""" example function for osxphotos --query-function """
|
||||
|
||||
|
||||
from typing import List
|
||||
|
||||
from osxphotos import PhotoInfo
|
||||
@@ -13,9 +12,20 @@ def best_selfies(photos: List[PhotoInfo]) -> List[PhotoInfo]:
|
||||
|
||||
# get list of selfies sorted by date
|
||||
photos = sorted([p for p in photos if p.selfie], key=lambda p: p.date)
|
||||
if not photos:
|
||||
return []
|
||||
|
||||
start_year = photos[0].date.year
|
||||
stop_year = photos[-1].date.year
|
||||
print(start_year, stop_year)
|
||||
best_selfies = []
|
||||
for year in range(start_year, stop_year + 1):
|
||||
# find best selfie each year as determined by overall aesthetic score
|
||||
selfies = sorted(
|
||||
[p for p in photos if p.date.year == year],
|
||||
key=lambda p: p.score.overall,
|
||||
reverse=True,
|
||||
)
|
||||
if selfies:
|
||||
best_selfies.append(selfies[0])
|
||||
|
||||
return photos
|
||||
return best_selfies
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
""" version info """
|
||||
|
||||
__version__ = "0.42.44"
|
||||
__version__ = "0.42.45"
|
||||
|
||||
@@ -543,6 +543,18 @@ def QUERY_OPTIONS(f):
|
||||
"CRITERIA must be a valid python expression. "
|
||||
"See https://rhettbull.github.io/osxphotos/ for additional documentation on the PhotoInfo class.",
|
||||
),
|
||||
o(
|
||||
"--query-function",
|
||||
metavar="filename.py::function",
|
||||
multiple=True,
|
||||
type=FunctionCall(),
|
||||
help="Run function to filter photos. Use this in format: --query-function filename.py::function where filename.py is a python "
|
||||
+ "file you've created and function is the name of the function in the python file you want to call. "
|
||||
+ "Your function will be passed a list of PhotoInfo objects and is expected to return a filtered list of PhotoInfo objects. "
|
||||
+ "You may use more than one function by repeating the --query-function option with a different value. "
|
||||
+ "Your query function will be called after all other query options have been evaluated. "
|
||||
+ "See https://github.com/RhetTbull/osxphotos/blob/master/examples/query_function.py for example of how to use this option.",
|
||||
),
|
||||
]
|
||||
for o in options[::-1]:
|
||||
f = o(f)
|
||||
@@ -1141,6 +1153,7 @@ def export(
|
||||
max_size,
|
||||
regex,
|
||||
query_eval,
|
||||
query_function,
|
||||
duplicate,
|
||||
post_command,
|
||||
post_function,
|
||||
@@ -1300,6 +1313,7 @@ def export(
|
||||
max_size = cfg.max_size
|
||||
regex = cfg.regex
|
||||
query_eval = cfg.query_eval
|
||||
query_function = cfg.query_function
|
||||
duplicate = cfg.duplicate
|
||||
post_command = cfg.post_command
|
||||
post_function = cfg.post_function
|
||||
@@ -1611,6 +1625,7 @@ def export(
|
||||
max_size=max_size,
|
||||
regex=regex,
|
||||
query_eval=query_eval,
|
||||
function=query_function,
|
||||
duplicate=duplicate,
|
||||
)
|
||||
|
||||
@@ -2013,6 +2028,7 @@ def query(
|
||||
max_size,
|
||||
regex,
|
||||
query_eval,
|
||||
query_function,
|
||||
add_to_album,
|
||||
):
|
||||
"""Query the Photos database using 1 or more search options;
|
||||
@@ -2041,6 +2057,7 @@ def query(
|
||||
label,
|
||||
is_reference,
|
||||
query_eval,
|
||||
query_function,
|
||||
min_size,
|
||||
max_size,
|
||||
regex,
|
||||
@@ -2172,6 +2189,7 @@ def query(
|
||||
min_size=min_size,
|
||||
max_size=max_size,
|
||||
query_eval=query_eval,
|
||||
function=query_function,
|
||||
regex=regex,
|
||||
duplicate=duplicate,
|
||||
)
|
||||
|
||||
@@ -3259,6 +3259,10 @@ class PhotosDB:
|
||||
elif options.no_location:
|
||||
photos = [p for p in photos if p.location == (None, None)]
|
||||
|
||||
if options.function:
|
||||
for function in options.function:
|
||||
photos = function[0](photos)
|
||||
|
||||
return photos
|
||||
|
||||
def _duplicate_signature(self, uuid):
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
""" QueryOptions class for PhotosDB.query """
|
||||
|
||||
from dataclasses import dataclass, asdict
|
||||
from typing import Optional, Iterable, Tuple
|
||||
import datetime
|
||||
from dataclasses import asdict, dataclass
|
||||
from typing import Iterable, List, Optional, Tuple
|
||||
|
||||
import bitmath
|
||||
|
||||
|
||||
@@ -81,6 +82,7 @@ class QueryOptions:
|
||||
duplicate: Optional[bool] = None
|
||||
location: Optional[bool] = None
|
||||
no_location: Optional[bool] = None
|
||||
function: Optional[List[Tuple[callable, str]]] = None
|
||||
|
||||
def asdict(self):
|
||||
return asdict(self)
|
||||
|
||||
@@ -6530,6 +6530,39 @@ def test_query_regex_4():
|
||||
assert len(json_got) == 2
|
||||
|
||||
|
||||
def test_query_function():
|
||||
"""test query --query-function"""
|
||||
import json
|
||||
|
||||
from osxphotos.cli import query
|
||||
|
||||
runner = CliRunner()
|
||||
cwd = os.getcwd()
|
||||
# pylint: disable=not-context-manager
|
||||
with runner.isolated_filesystem():
|
||||
with open("query1.py", "w") as f:
|
||||
f.writelines(
|
||||
[
|
||||
"def query(photos):\n",
|
||||
" return [p for p in photos if 'DSC03584' in p.original_filename]",
|
||||
]
|
||||
)
|
||||
tmpdir = os.getcwd()
|
||||
result = runner.invoke(
|
||||
query,
|
||||
[
|
||||
os.path.join(cwd, PHOTOS_DB_15_7),
|
||||
"--query-function",
|
||||
f"{tmpdir}/query1.py::query",
|
||||
"--json",
|
||||
],
|
||||
)
|
||||
assert result.exit_code == 0
|
||||
json_got = json.loads(result.output)
|
||||
assert len(json_got) == 1
|
||||
assert json_got[0]["original_filename"] == "DSC03584.dng"
|
||||
|
||||
|
||||
def test_export_export_dir_template():
|
||||
"""Test {export_dir} template"""
|
||||
import json
|
||||
@@ -6830,3 +6863,39 @@ def test_export_directory_template_function():
|
||||
)
|
||||
assert result.exit_code == 0
|
||||
assert pathlib.Path(f"foo/bar/{CLI_EXPORT_UUID_FILENAME}").is_file()
|
||||
|
||||
|
||||
def test_export_query_function():
|
||||
"""Test --query-function"""
|
||||
import os.path
|
||||
|
||||
from osxphotos.cli import cli
|
||||
|
||||
runner = CliRunner()
|
||||
cwd = os.getcwd()
|
||||
# pylint: disable=not-context-manager
|
||||
with runner.isolated_filesystem():
|
||||
with open("query2.py", "w") as f:
|
||||
f.writelines(
|
||||
[
|
||||
"def query(photos):\n",
|
||||
" return [p for p in photos if p.title and 'Tulips' in p.title]\n",
|
||||
]
|
||||
)
|
||||
|
||||
tempdir = os.getcwd()
|
||||
result = runner.invoke(
|
||||
cli,
|
||||
[
|
||||
"export",
|
||||
"--db",
|
||||
os.path.join(cwd, PHOTOS_DB_15_7),
|
||||
".",
|
||||
"--query-function",
|
||||
f"{tempdir}/query2.py::query",
|
||||
"-V",
|
||||
"--skip-edited",
|
||||
],
|
||||
)
|
||||
assert result.exit_code == 0
|
||||
assert "exported: 1" in result.output
|
||||
|
||||
Reference in New Issue
Block a user