Fixed template function to work with import command (#765)
This commit is contained in:
parent
08b806ff7d
commit
3c98906158
27
examples/template_function_import.py
Normal file
27
examples/template_function_import.py
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
""" Example showing how to use a custom function for osxphotos {function} template with the `osxphotos import` command
|
||||||
|
Use: osxphotos import /path/to/import/*.jpg --album "{function:/path/to/template_function_import.py::example}"
|
||||||
|
|
||||||
|
You may place more than one template function in a single file as each is called by name using the {function:file.py::function_name} format
|
||||||
|
"""
|
||||||
|
|
||||||
|
import pathlib
|
||||||
|
from typing import List, Optional, Union
|
||||||
|
|
||||||
|
|
||||||
|
def example(
|
||||||
|
filepath: pathlib.Path, args: Optional[str] = None, **kwargs
|
||||||
|
) -> Union[List, str]:
|
||||||
|
"""example function for {function} template for use with `osxphotos import`
|
||||||
|
|
||||||
|
This example parses filenames in format album_img_123.jpg and returns the album name
|
||||||
|
|
||||||
|
Args:
|
||||||
|
filepath: pathlib.Path object of file being imported
|
||||||
|
args: optional str of arguments passed to template function
|
||||||
|
**kwargs: not currently used, placeholder to keep functions compatible with possible changes to {function}
|
||||||
|
Returns:
|
||||||
|
str or list of str of values that should be substituted for the {function} template
|
||||||
|
"""
|
||||||
|
filename = filepath.stem
|
||||||
|
fields = filename.split("_")
|
||||||
|
return fields[0] if len(fields) > 1 else ""
|
||||||
@ -119,7 +119,7 @@ class PhotoInfoFromFile:
|
|||||||
Returns:
|
Returns:
|
||||||
([rendered_strings], [unmatched]): tuple of list of rendered strings and list of unmatched template values
|
([rendered_strings], [unmatched]): tuple of list of rendered strings and list of unmatched template values
|
||||||
"""
|
"""
|
||||||
options = options or RenderOptions()
|
options = options or RenderOptions(caller="import")
|
||||||
template = PhotoTemplate(self, exiftool_path=self._exiftool_path)
|
template = PhotoTemplate(self, exiftool_path=self._exiftool_path)
|
||||||
return template.render(template_str, options)
|
return template.render(template_str, options)
|
||||||
|
|
||||||
@ -163,7 +163,7 @@ def render_photo_template(
|
|||||||
|
|
||||||
photoinfo = PhotoInfoFromFile(filepath, exiftool=exiftool_path)
|
photoinfo = PhotoInfoFromFile(filepath, exiftool=exiftool_path)
|
||||||
options = RenderOptions(
|
options = RenderOptions(
|
||||||
none_str=_OSXPHOTOS_NONE_SENTINEL, filepath=relative_filepath
|
none_str=_OSXPHOTOS_NONE_SENTINEL, filepath=relative_filepath, caller="import"
|
||||||
)
|
)
|
||||||
template_values, _ = photoinfo.render_template(template, options=options)
|
template_values, _ = photoinfo.render_template(template, options=options)
|
||||||
# filter out empty strings
|
# filter out empty strings
|
||||||
|
|||||||
@ -329,6 +329,7 @@ class RenderOptions:
|
|||||||
dest_path: set to the destination path of the photo (for use by {function} template), only valid with --filename
|
dest_path: set to the destination path of the photo (for use by {function} template), only valid with --filename
|
||||||
filepath: set to value for filepath of the exported photo if you want to evaluate {filepath} template
|
filepath: set to value for filepath of the exported photo if you want to evaluate {filepath} template
|
||||||
quote: quote path templates for execution in the shell
|
quote: quote path templates for execution in the shell
|
||||||
|
caller: which command is calling the template (e.g. 'export')
|
||||||
"""
|
"""
|
||||||
|
|
||||||
none_str: str = "_"
|
none_str: str = "_"
|
||||||
@ -343,6 +344,7 @@ class RenderOptions:
|
|||||||
dest_path: Optional[str] = None
|
dest_path: Optional[str] = None
|
||||||
filepath: Optional[str] = None
|
filepath: Optional[str] = None
|
||||||
quote: bool = False
|
quote: bool = False
|
||||||
|
caller: str = "export"
|
||||||
|
|
||||||
|
|
||||||
class PhotoTemplateParser:
|
class PhotoTemplateParser:
|
||||||
@ -788,7 +790,9 @@ class PhotoTemplate:
|
|||||||
raise ValueError(
|
raise ValueError(
|
||||||
"SyntaxError: filename and function must not be null with {function::filename.py:function_name}"
|
"SyntaxError: filename and function must not be null with {function::filename.py:function_name}"
|
||||||
)
|
)
|
||||||
vals = self.get_template_value_function(subfield, field_arg)
|
vals = self.get_template_value_function(
|
||||||
|
subfield, field_arg, self.options.caller
|
||||||
|
)
|
||||||
elif field in MULTI_VALUE_SUBSTITUTIONS or field.startswith("photo"):
|
elif field in MULTI_VALUE_SUBSTITUTIONS or field.startswith("photo"):
|
||||||
vals = self.get_template_value_multi(
|
vals = self.get_template_value_multi(
|
||||||
field, subfield, path_sep=field_arg, default=default
|
field, subfield, path_sep=field_arg, default=default
|
||||||
@ -1459,10 +1463,17 @@ class PhotoTemplate:
|
|||||||
|
|
||||||
def get_template_value_function(
|
def get_template_value_function(
|
||||||
self,
|
self,
|
||||||
subfield,
|
subfield: str,
|
||||||
field_arg,
|
field_arg: Optional[str],
|
||||||
|
caller: str,
|
||||||
):
|
):
|
||||||
"""Get template value from external function"""
|
"""Get template value from external function
|
||||||
|
|
||||||
|
Args:
|
||||||
|
subfield: the filename and function name in for filename.py::function
|
||||||
|
field_arg: the argument to pass to the function
|
||||||
|
caller: the calling source of the template ('export' or 'import')
|
||||||
|
"""
|
||||||
|
|
||||||
if "::" not in subfield:
|
if "::" not in subfield:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
@ -1481,8 +1492,17 @@ class PhotoTemplate:
|
|||||||
# if no uuid, then template is being validated but not actually run
|
# if no uuid, then template is being validated but not actually run
|
||||||
# so don't run the function
|
# so don't run the function
|
||||||
values = []
|
values = []
|
||||||
else:
|
elif caller == "export":
|
||||||
|
# function signature is:
|
||||||
|
# def example(photo: PhotoInfo, options: ExportOptions, args: Optional[str] = None, **kwargs) -> Union[List, str]:
|
||||||
values = template_func(self.photo, options=self.options, args=field_arg)
|
values = template_func(self.photo, options=self.options, args=field_arg)
|
||||||
|
elif caller == "import":
|
||||||
|
# function signature is:
|
||||||
|
# def example(filepath: pathlib.Path, args: Optional[str] = None, **kwargs) -> Union[List, str]:
|
||||||
|
# the PhotoInfoFromFile class used by import sets `path` to the path of the file being imported
|
||||||
|
values = template_func(pathlib.Path(self.photo.path), args=field_arg)
|
||||||
|
else:
|
||||||
|
raise ValueError(f"Unhandled caller: {caller}")
|
||||||
|
|
||||||
if not isinstance(values, (str, list)):
|
if not isinstance(values, (str, list)):
|
||||||
raise TypeError(
|
raise TypeError(
|
||||||
|
|||||||
@ -4,7 +4,9 @@ import os
|
|||||||
import os.path
|
import os.path
|
||||||
import pathlib
|
import pathlib
|
||||||
import re
|
import re
|
||||||
|
import shutil
|
||||||
import time
|
import time
|
||||||
|
from tempfile import TemporaryDirectory
|
||||||
from typing import Dict
|
from typing import Dict
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
@ -13,7 +15,7 @@ from photoscript import Photo
|
|||||||
from pytest import approx
|
from pytest import approx
|
||||||
|
|
||||||
from osxphotos.cli.import_cli import import_cli
|
from osxphotos.cli.import_cli import import_cli
|
||||||
from osxphotos.exiftool import ExifTool, get_exiftool_path
|
from osxphotos.exiftool import get_exiftool_path
|
||||||
from tests.conftest import get_os_version
|
from tests.conftest import get_os_version
|
||||||
|
|
||||||
TERMINAL_WIDTH = 250
|
TERMINAL_WIDTH = 250
|
||||||
@ -644,3 +646,37 @@ def test_import_check_templates():
|
|||||||
|
|
||||||
for idx, line in enumerate(output):
|
for idx, line in enumerate(output):
|
||||||
assert line == TEST_DATA[TEST_IMAGE_1]["check_templates"][idx]
|
assert line == TEST_DATA[TEST_IMAGE_1]["check_templates"][idx]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.test_import
|
||||||
|
def test_import_function_template():
|
||||||
|
"""Test import with a function template"""
|
||||||
|
cwd = os.getcwd()
|
||||||
|
test_image_1 = os.path.join(cwd, TEST_IMAGE_1)
|
||||||
|
function = os.path.join(cwd, "examples/template_function_import.py")
|
||||||
|
with TemporaryDirectory() as tempdir:
|
||||||
|
test_image = shutil.copy(
|
||||||
|
test_image_1, os.path.join(tempdir, "MyAlbum_IMG_0001.jpg")
|
||||||
|
)
|
||||||
|
runner = CliRunner()
|
||||||
|
result = runner.invoke(
|
||||||
|
import_cli,
|
||||||
|
[
|
||||||
|
"--verbose",
|
||||||
|
"--album",
|
||||||
|
"{function:" + function + "::example}",
|
||||||
|
test_image,
|
||||||
|
],
|
||||||
|
terminal_width=TERMINAL_WIDTH,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result.exit_code == 0
|
||||||
|
|
||||||
|
import_data = parse_import_output(result.output)
|
||||||
|
file_1 = pathlib.Path(test_image).name
|
||||||
|
uuid_1 = import_data[file_1]
|
||||||
|
photo_1 = Photo(uuid_1)
|
||||||
|
|
||||||
|
assert photo_1.filename == file_1
|
||||||
|
albums = [a.title for a in photo_1.albums]
|
||||||
|
assert albums == ["MyAlbum"]
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user