Bug fix for template functions #477

This commit is contained in:
Rhet Turnbull
2021-06-23 22:36:58 -07:00
parent 5ea01df69b
commit 49317582c4
8 changed files with 99 additions and 45 deletions

View File

@@ -1,3 +1,3 @@
""" version info """ """ version info """
__version__ = "0.42.45" __version__ = "0.42.46"

View File

@@ -57,7 +57,7 @@ from .photokit import check_photokit_authorization, request_photokit_authorizati
from .photosalbum import PhotosAlbum from .photosalbum import PhotosAlbum
from .phototemplate import PhotoTemplate, RenderOptions from .phototemplate import PhotoTemplate, RenderOptions
from .queryoptions import QueryOptions from .queryoptions import QueryOptions
from .utils import get_preferred_uti_extension, load_function from .utils import get_preferred_uti_extension, load_function, expand_and_validate_filepath
# global variable to control verbose output # global variable to control verbose output
# set via --verbose/-V # set via --verbose/-V
@@ -172,13 +172,14 @@ class FunctionCall(click.ParamType):
filename, funcname = value.split("::") filename, funcname = value.split("::")
if not pathlib.Path(filename).is_file(): filename_validated = expand_and_validate_filepath(filename)
if not filename_validated:
self.fail(f"'{filename}' does not appear to be a file") self.fail(f"'{filename}' does not appear to be a file")
try: try:
function = load_function(filename, funcname) function = load_function(filename_validated, funcname)
except Exception as e: except Exception as e:
self.fail(f"Could not load function {funcname} from {filename}") self.fail(f"Could not load function {funcname} from {filename_validated}")
return (function, value) return (function, value)

View File

@@ -4,8 +4,10 @@ import datetime
import locale import locale
import os import os
import pathlib import pathlib
import sys
import shlex import shlex
import sys
from dataclasses import dataclass
from typing import Optional
from textx import TextXSyntaxError, metamodel_from_file from textx import TextXSyntaxError, metamodel_from_file
@@ -14,9 +16,7 @@ from ._version import __version__
from .datetime_formatter import DateTimeFormatter from .datetime_formatter import DateTimeFormatter
from .exiftool import ExifToolCaching from .exiftool import ExifToolCaching
from .path_utils import sanitize_dirname, sanitize_filename, sanitize_pathpart from .path_utils import sanitize_dirname, sanitize_filename, sanitize_pathpart
from .utils import load_function from .utils import expand_and_validate_filepath, load_function
from dataclasses import dataclass
from typing import Optional
# TODO: a lot of values are passed from function to function like path_sep--make these all class properties # TODO: a lot of values are passed from function to function like path_sep--make these all class properties
@@ -1177,10 +1177,11 @@ class PhotoTemplate:
filename, funcname = subfield.split("::") filename, funcname = subfield.split("::")
if not pathlib.Path(filename).is_file(): filename_validated = expand_and_validate_filepath(filename)
if not filename_validated:
raise ValueError(f"'{filename}' does not appear to be a file") raise ValueError(f"'{filename}' does not appear to be a file")
template_func = load_function(filename, funcname) template_func = load_function(filename_validated, funcname)
values = template_func(self.photo) values = template_func(self.photo)
if not isinstance(values, (str, list)): if not isinstance(values, (str, list)):
@@ -1211,10 +1212,11 @@ class PhotoTemplate:
filename, funcname = filter_.split("::") filename, funcname = filter_.split("::")
if not pathlib.Path(filename).is_file(): filename_validated = expand_and_validate_filepath(filename)
if not filename_validated:
raise ValueError(f"'{filename}' does not appear to be a file") raise ValueError(f"'{filename}' does not appear to be a file")
template_func = load_function(filename, funcname) template_func = load_function(filename_validated, funcname)
if not isinstance(values, (list, tuple)): if not isinstance(values, (list, tuple)):
values = [values] values = [values]

View File

@@ -63,7 +63,8 @@ SubField:
; ;
SUBFIELD_WORD: SUBFIELD_WORD:
/[\.\w:\/]+/ /[\.\w:\/\-\~\'\"\%\@\#\^\]+/
/\\\s/?
; ;
Filter: Filter:

View File

@@ -419,6 +419,20 @@ def increment_filename(filepath):
return str(dest) return str(dest)
def expand_and_validate_filepath(path: str) -> str:
"""validate and expand ~ in filepath, also un-escapes "\ "
Returns:
expanded path if path is valid file, else None
"""
path = re.sub(r"\\ ", " ", path)
path = pathlib.Path(path).expanduser()
if path.is_file():
return str(path)
return None
def load_function(pyfile: str, function_name: str) -> Callable: def load_function(pyfile: str, function_name: str) -> Callable:
"""Load function_name from python file pyfile""" """Load function_name from python file pyfile"""
module_file = pathlib.Path(pyfile) module_file = pathlib.Path(pyfile)

View File

@@ -0,0 +1,3 @@
# Contents
This directory used by test_template.py for testing {function} templates with hyphenated directory names

View File

@@ -0,0 +1,20 @@
""" Example showing how to use a custom function for osxphotos {function} template """
import pathlib
from typing import List, Union
import osxphotos
def foo(photo: osxphotos.PhotoInfo, **kwargs) -> Union[List, str]:
""" example function for {function} template
Args:
photo: osxphotos.PhotoInfo object
**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
"""
return photo.original_filename + "-FOO"

View File

@@ -82,7 +82,9 @@ TEMPLATE_VALUES_TITLE = {
"{title|titlecase}": ["Tulips Tied Together At A Flower Shop"], "{title|titlecase}": ["Tulips Tied Together At A Flower Shop"],
"{title|upper}": ["TULIPS TIED TOGETHER AT A FLOWER SHOP"], "{title|upper}": ["TULIPS TIED TOGETHER AT A FLOWER SHOP"],
"{title|titlecase|lower|upper}": ["TULIPS TIED TOGETHER AT A FLOWER SHOP"], "{title|titlecase|lower|upper}": ["TULIPS TIED TOGETHER AT A FLOWER SHOP"],
"{title|titlecase|lower|upper|shell_quote}": ["'TULIPS TIED TOGETHER AT A FLOWER SHOP'"], "{title|titlecase|lower|upper|shell_quote}": [
"'TULIPS TIED TOGETHER AT A FLOWER SHOP'"
],
"{title|upper|titlecase}": ["Tulips Tied Together At A Flower Shop"], "{title|upper|titlecase}": ["Tulips Tied Together At A Flower Shop"],
"{title|capitalize}": ["Tulips tied together at a flower shop"], "{title|capitalize}": ["Tulips tied together at a flower shop"],
"{title[ ,_]}": ["Tulips_tied_together_at_a_flower_shop"], "{title[ ,_]}": ["Tulips_tied_together_at_a_flower_shop"],
@@ -388,7 +390,9 @@ def test_lookup_multi(photosdb_places):
lookup_str = re.match(r"\{([^\\,}]+)\}", subst).group(1) lookup_str = re.match(r"\{([^\\,}]+)\}", subst).group(1)
if subst in ["{exiftool}", "{photo}", "{function}"]: if subst in ["{exiftool}", "{photo}", "{function}"]:
continue continue
lookup = template.get_template_value_multi(lookup_str, path_sep=os.path.sep, default=[]) lookup = template.get_template_value_multi(
lookup_str, path_sep=os.path.sep, default=[]
)
assert isinstance(lookup, list) assert isinstance(lookup, list)
@@ -975,6 +979,15 @@ def test_conditional(photosdb):
assert sorted(rendered) == sorted(UUID_CONDITIONAL[uuid][template]) assert sorted(rendered) == sorted(UUID_CONDITIONAL[uuid][template])
def test_function_hyphen_dir(photosdb):
"""Test {function} with a hyphenated directory (#477)"""
photo = photosdb.get_photo(UUID_MULTI_KEYWORDS)
rendered, _ = photo.render_template(
"{function:tests/hyphen-dir/template_function.py::foo}"
)
assert rendered == [f"{photo.original_filename}-FOO"]
def test_function(photosdb): def test_function(photosdb):
"""Test {function}""" """Test {function}"""
photo = photosdb.get_photo(UUID_MULTI_KEYWORDS) photo = photosdb.get_photo(UUID_MULTI_KEYWORDS)