Bug fix for template functions #477
This commit is contained in:
@@ -1,3 +1,3 @@
|
|||||||
""" version info """
|
""" version info """
|
||||||
|
|
||||||
__version__ = "0.42.45"
|
__version__ = "0.42.46"
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
|||||||
@@ -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]
|
||||||
|
|||||||
@@ -63,7 +63,8 @@ SubField:
|
|||||||
;
|
;
|
||||||
|
|
||||||
SUBFIELD_WORD:
|
SUBFIELD_WORD:
|
||||||
/[\.\w:\/]+/
|
/[\.\w:\/\-\~\'\"\%\@\#\^\’]+/
|
||||||
|
/\\\s/?
|
||||||
;
|
;
|
||||||
|
|
||||||
Filter:
|
Filter:
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
3
tests/hyphen-dir/README.md
Normal file
3
tests/hyphen-dir/README.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# Contents
|
||||||
|
|
||||||
|
This directory used by test_template.py for testing {function} templates with hyphenated directory names
|
||||||
20
tests/hyphen-dir/template_function.py
Normal file
20
tests/hyphen-dir/template_function.py
Normal 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"
|
||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user