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:
|
||||
([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)
|
||||
return template.render(template_str, options)
|
||||
|
||||
@ -163,7 +163,7 @@ def render_photo_template(
|
||||
|
||||
photoinfo = PhotoInfoFromFile(filepath, exiftool=exiftool_path)
|
||||
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)
|
||||
# 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
|
||||
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
|
||||
caller: which command is calling the template (e.g. 'export')
|
||||
"""
|
||||
|
||||
none_str: str = "_"
|
||||
@ -343,6 +344,7 @@ class RenderOptions:
|
||||
dest_path: Optional[str] = None
|
||||
filepath: Optional[str] = None
|
||||
quote: bool = False
|
||||
caller: str = "export"
|
||||
|
||||
|
||||
class PhotoTemplateParser:
|
||||
@ -788,7 +790,9 @@ class PhotoTemplate:
|
||||
raise ValueError(
|
||||
"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"):
|
||||
vals = self.get_template_value_multi(
|
||||
field, subfield, path_sep=field_arg, default=default
|
||||
@ -1459,10 +1463,17 @@ class PhotoTemplate:
|
||||
|
||||
def get_template_value_function(
|
||||
self,
|
||||
subfield,
|
||||
field_arg,
|
||||
subfield: str,
|
||||
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:
|
||||
raise ValueError(
|
||||
@ -1481,8 +1492,17 @@ class PhotoTemplate:
|
||||
# if no uuid, then template is being validated but not actually run
|
||||
# so don't run the function
|
||||
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)
|
||||
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)):
|
||||
raise TypeError(
|
||||
|
||||
@ -4,7 +4,9 @@ import os
|
||||
import os.path
|
||||
import pathlib
|
||||
import re
|
||||
import shutil
|
||||
import time
|
||||
from tempfile import TemporaryDirectory
|
||||
from typing import Dict
|
||||
|
||||
import pytest
|
||||
@ -13,7 +15,7 @@ from photoscript import Photo
|
||||
from pytest import approx
|
||||
|
||||
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
|
||||
|
||||
TERMINAL_WIDTH = 250
|
||||
@ -644,3 +646,37 @@ def test_import_check_templates():
|
||||
|
||||
for idx, line in enumerate(output):
|
||||
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