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__ = "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 .phototemplate import PhotoTemplate, RenderOptions
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
# set via --verbose/-V
@ -172,13 +172,14 @@ class FunctionCall(click.ParamType):
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")
try:
function = load_function(filename, funcname)
function = load_function(filename_validated, funcname)
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)

View File

@ -4,8 +4,10 @@ import datetime
import locale
import os
import pathlib
import sys
import shlex
import sys
from dataclasses import dataclass
from typing import Optional
from textx import TextXSyntaxError, metamodel_from_file
@ -14,9 +16,7 @@ from ._version import __version__
from .datetime_formatter import DateTimeFormatter
from .exiftool import ExifToolCaching
from .path_utils import sanitize_dirname, sanitize_filename, sanitize_pathpart
from .utils import load_function
from dataclasses import dataclass
from typing import Optional
from .utils import expand_and_validate_filepath, load_function
# 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("::")
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")
template_func = load_function(filename, funcname)
template_func = load_function(filename_validated, funcname)
values = template_func(self.photo)
if not isinstance(values, (str, list)):
@ -1211,10 +1212,11 @@ class PhotoTemplate:
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")
template_func = load_function(filename, funcname)
template_func = load_function(filename_validated, funcname)
if not isinstance(values, (list, tuple)):
values = [values]

View File

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

View File

@ -419,6 +419,20 @@ def increment_filename(filepath):
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:
"""Load function_name from python file 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|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|capitalize}": ["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)
if subst in ["{exiftool}", "{photo}", "{function}"]:
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)
@ -975,6 +979,15 @@ def test_conditional(photosdb):
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):
"""Test {function}"""
photo = photosdb.get_photo(UUID_MULTI_KEYWORDS)