Template system now supports default values

This commit is contained in:
Rhet Turnbull 2020-03-28 09:57:48 -07:00
parent 427c4c0bc4
commit 67a9a9e21b
9 changed files with 357 additions and 119 deletions

214
README.md
View File

@ -214,19 +214,20 @@ Options:
exiftool may be installed from exiftool may be installed from
https://exiftool.org/ https://exiftool.org/
--directory DIRECTORY Optional template for specifying name of --directory DIRECTORY Optional template for specifying name of
output directory. See below for additional output directory in the form
details on templating system '{name,DEFAULT}'. See below for additional
details on templating system.
-h, --help Show this message and exit. -h, --help Show this message and exit.
**Templating System** **Templating System**
With the --directory option, you may specify a template for the export With the --directory option, you may specify a template for the export
directory. This directory will be appended to the export path specified in directory. This directory will be appended to the export path specified in
the export DEST argument to export. For example, if template is the export DEST argument to export. For example, if template is
'{created.year}/{created.month}', and export desitnation DEST is '{created.year}/{created.month}', and export desitnation DEST is
'/Users/maria/Pictures/export', the actual export directory for a photo would '/Users/maria/Pictures/export', the actual export directory for a photo would
be '/Users/maria/Pictures/export/2020/March' if the photo was created in be '/Users/maria/Pictures/export/2020/March' if the photo was created in March
March 2020. 2020.
In the template, valid template substitutions will be replaced by the In the template, valid template substitutions will be replaced by the
corresponding value from the table below. Invalid substitutions will result corresponding value from the table below. Invalid substitutions will result
@ -239,54 +240,77 @@ rendered name, escape the curly braces with \, for example, using
'{created.year}/\{name\}' for --directory would result in output of '{created.year}/\{name\}' for --directory would result in output of
2020/{name}/photoname.jpg 2020/{name}/photoname.jpg
In the current implementation, substitutions which have no value will be You may specify an optional default value to use if the substitution does not
replaced by '_', for example, your template looked like contain a value (e.g. the value is null) by specifying the default value after
'{created.year}/{place.address}' but there was no address associated with the a ',' in the template string: for example, if template is
photo, the resulting output would be: '2020/_/photoname.jpg' '{created.year}/{place.address,'NO_ADDRESS'}' but there was no address
associated with the photo, the resulting output would be:
'2020/NO_ADDRESS/photoname.jpg'. If specified, the default value may not
contain a brace symbol ('{' or '}').
I plan to extend the templating system to the exported filename so you can specify the filename using a template. If you do not specify a default value and the template substitution has no
value, '_' (underscore) will be used as the default value. For example, in the
above example, this would result in '2020/_/photoname.jpg' if address was null
I plan to eventually extend the templating system to the exported filename so
you can specify the filename using a template.
Substitution Description Substitution Description
{name} Filename of the photo {name} Filename of the photo
{original_name} Photo's original filename when imported to Photos {original_name} Photo's original filename when imported to
{title} Title of the photo Photos
{descr} Description of the photo {title} Title of the photo
{created.date} Photo's creation date in ISO format, e.g. '2020-03-22' {descr} Description of the photo
{created.year} 4-digit year of file creation time {created.date} Photo's creation date in ISO format, e.g.
{created.yy} 2-digit year of file creation time '2020-03-22'
{created.mm} 2-digit month of the file creation time (zero padded) {created.year} 4-digit year of file creation time
{created.month} Month name in user's locale of the file creation time {created.yy} 2-digit year of file creation time
{created.mon} Month abbreviation in the user's locale of the file {created.mm} 2-digit month of the file creation time
creation time (zero padded)
{created.doy} 3-digit day of year (e.g Julian day) of file creation {created.month} Month name in user's locale of the file
time, starting from 1 (zero padded) creation time
{modified.date} Photo's modification date in ISO format, e.g. {created.mon} Month abbreviation in the user's locale of
'2020-03-22' the file creation time
{modified.year} 4-digit year of file modification time {created.doy} 3-digit day of year (e.g Julian day) of file
{modified.yy} 2-digit year of file modification time creation time, starting from 1 (zero padded)
{modified.mm} 2-digit month of the file modification time (zero {modified.date} Photo's modification date in ISO format,
padded) e.g. '2020-03-22'
{modified.month} Month name in user's locale of the file modification {modified.year} 4-digit year of file modification time
time {modified.yy} 2-digit year of file modification time
{modified.mon} Month abbreviation in the user's locale of the file {modified.mm} 2-digit month of the file modification time
modification time (zero padded)
{modified.doy} 3-digit day of year (e.g Julian day) of file {modified.month} Month name in user's locale of the file
modification time, starting from 1 (zero padded) modification time
{place.name} Place name from the photo's reverse geolocation data; {modified.mon} Month abbreviation in the user's locale of
this is the place name shown in the Photos Info window the file modification time
{place.names} list of place names from the photo's reverse {modified.doy} 3-digit day of year (e.g Julian day) of file
geolocation data, joined with '_', for example, '18th modification time, starting from 1 (zero
St NW_Washington_DC_United States' padded)
{place.address} Postal address from the photo's reverse geolocation {place.name} Place name from the photo's reverse
data, e.g. '2007 18th St NW, Washington, DC 20009, geolocation data, as displayed in Photos
United States' {place.name.country} Country name from the photo's reverse
{place.street} Street part of the postal address, e.g. '2007 18th St geolocation data
NW' {place.name.state_province} State or province name from the photo's
{place.city} City part of the postal address, e.g. 'Washington' reverse geolocation data
{place.state} State part of the postal address, e.g. 'DC' {place.name.city} City or locality name from the photo's
{place.postal_code} Postal code part of the postal address, e.g. '20009' reverse geolocation data
{place.country} Country name of the postal address, e.g. 'United States' {place.name.area_of_interest} Area of interest name (e.g. landmark or
{place.country_code} ISO country code of the postal address, e.g. 'US' public place) from the photo's reverse
geolocation data
{place.address} Postal address from the photo's reverse
geolocation data, e.g. '2007 18th St NW,
Washington, DC 20009, United States'
{place.address.street} Street part of the postal address, e.g.
'2007 18th St NW'
{place.address.city} City part of the postal address, e.g.
'Washington'
{place.address.state_province} State/province part of the postal address,
e.g. 'DC'
{place.address.postal_code} Postal code part of the postal address, e.g.
'20009'
{place.address.country} Country name of the postal address, e.g.
'United States'
{place.address.country_code} ISO country code of the postal address, e.g.
'US'
``` ```
Example: export all photos to ~/Desktop/export, including edited versions and live photo movies, group in folders by date created Example: export all photos to ~/Desktop/export, including edited versions and live photo movies, group in folders by date created
@ -930,14 +954,14 @@ For example: "2038 18th St NW, Washington, DC 20009, United States"
#### `address`: #### `address`:
Returns a `PostalAddress` namedtuple with details of the postal address containing the following fields: Returns a `PostalAddress` namedtuple with details of the postal address containing the following fields:
- city - `city`
- country - `country`
- postal_code - `postal_code`
- state - `state`
- street - `street`
- sub_administrative_area - `sub_administrative_area`
- sub_locality - `sub_locality`
- iso_country_code - `iso_country_code`
For example: For example:
```python ```python
@ -947,6 +971,68 @@ PostalAddress(street='3700 Wailea Alanui Dr', sub_locality=None, city='Kihei', s
'96753' '96753'
``` ```
### Template Functions
There is a simple template system used by the command line client to specify the output directory using a template. The following are available in `osxphotos.template`.
#### `render_filepath_template(template, photo, none_str="_")`
Render template string for photo. none_str is used if template substitution results in None value and no default specified.
- `template`: str in form "{name,DEFAULT}" where name is one of the values in table below. The "," and default value that follows are optional. If specified, "DEFAULT" will be used if "name" is None. This is useful for values which are not always present, for example reverse geolocation data.
- `photo`: a [PhotoInfo](#photoinfo) object
- `none_str`: optional str to use as substitution when template value is None and no default specified in the template string. default is "_".
Returns a tuple of (rendered, unmatched) where rendered is the rendered template string with all substitutions made and unmatched is a list of any strings that resembled a template substitution but did not match a known substitution. E.g. strings in the form "{foo}".
e.g. `render_filepath_template("{created.year}/{foo}", photo)` would return `("2020/{foo}",["{foo}"])`
| Substitution | Description |
|--------------|-------------|
|{name}|Filename of the photo|
|{original_name}|Photo's original filename when imported to Photos|
|{title}|Title of the photo|
|{descr}|Description of the photo|
|{created.date}|Photo's creation date in ISO format, e.g. '2020-03-22'|
|{created.year}|4-digit year of file creation time|
|{created.yy}|2-digit year of file creation time|
|{created.mm}|2-digit month of the file creation time (zero padded)|
|{created.month}|Month name in user's locale of the file creation time|
|{created.mon}|Month abbreviation in the user's locale of the file creation time|
|{created.doy}|3-digit day of year (e.g Julian day) of file creation time, starting from 1 (zero padded)|
|{modified.date}|Photo's modification date in ISO format, e.g. '2020-03-22'|
|{modified.year}|4-digit year of file modification time|
|{modified.yy}|2-digit year of file modification time|
|{modified.mm}|2-digit month of the file modification time (zero padded)|
|{modified.month}|Month name in user's locale of the file modification time|
|{modified.mon}|Month abbreviation in the user's locale of the file modification time|
|{modified.doy}|3-digit day of year (e.g Julian day) of file modification time, starting from 1 (zero padded)|
|{place.name}|Place name from the photo's reverse geolocation data, as displayed in Photos|
|{place.name.country}|Country name from the photo's reverse geolocation data|
|{place.name.state_province}|State or province name from the photo's reverse geolocation data|
|{place.name.city}|City or locality name from the photo's reverse geolocation data|
|{place.name.area_of_interest}|Area of interest name (e.g. landmark or public place) from the photo's reverse geolocation data|
|{place.address}|Postal address from the photo's reverse geolocation data, e.g. '2007 18th St NW, Washington, DC 20009, United States'|
|{place.address.street}|Street part of the postal address, e.g. '2007 18th St NW'|
|{place.address.city}|City part of the postal address, e.g. 'Washington'|
|{place.address.state_province}|State/province part of the postal address, e.g. 'DC'|
|{place.address.postal_code}|Postal code part of the postal address, e.g. '20009'|
|{place.address.country}|Country name of the postal address, e.g. 'United States'|
|{place.address.country_code}|ISO country code of the postal address, e.g. 'US'|
#### `DateTimeFormatter(dt)`
Class that provides easy access to formatted datetime values.
- `dt`: a datetime.datetime object
Returnes `DateTimeFormater` class.
Has the following properties:
- `date`: Date in ISO format without timezone, e.g. "2020-03-04"
- `year`: 4-digit year
- `yy`: 2-digit year
- `month`: month name in user's locale
- `mon`: month abbreviation in user's locale
- `mm`: 2-digit month
- `doy`: 3-digit day of year (e.g. Julian day)
### Utility Functions ### Utility Functions
The following functions are located in osxphotos.utils The following functions are located in osxphotos.utils
@ -965,15 +1051,15 @@ Returns list of Photos libraries found on the system. **Note**: On MacOS 10.15,
#### `dd_to_dms_str(lat, lon)` #### `dd_to_dms_str(lat, lon)`
Convert latitude, longitude in degrees to degrees, minutes, seconds as string. Convert latitude, longitude in degrees to degrees, minutes, seconds as string.
lat: latitude in degrees - `lat`: latitude in degrees
lon: longitude in degrees - `lon`: longitude in degrees
returns: string tuple in format ("51 deg 30' 12.86\\" N", "0 deg 7' 54.50\\" W") returns: string tuple in format ("51 deg 30' 12.86\\" N", "0 deg 7' 54.50\\" W")
This is the same format used by exiftool's json format. This is the same format used by exiftool's json format.
#### `create_path_by_date(dest, dt)` #### `create_path_by_date(dest, dt)`
Creates a path in dest folder in form dest/YYYY/MM/DD/ Creates a path in dest folder in form dest/YYYY/MM/DD/
dest: valid path as str - `dest`: valid path as str
dt: datetime.timetuple() object - `dt`: datetime.timetuple() object
Checks to see if path exists, if it does, do nothing and return path. If path does not exist, creates it and returns path. Useful for exporting photos to a date-based folder structure. Checks to see if path exists, if it does, do nothing and return path. If path does not exist, creates it and returns path. Useful for exporting photos to a date-based folder structure.
## Examples ## Examples

View File

@ -81,11 +81,11 @@ class ExportCommand(click.Command):
formatter.write_text( formatter.write_text(
"With the --directory option, you may specify a template for the " "With the --directory option, you may specify a template for the "
+ "export directory. This directory will be appended to the export path specified " + "export directory. This directory will be appended to the export path specified "
+ " in the export DEST argument to export. For example, if template is " + "in the export DEST argument to export. For example, if template is "
+ "'{created.year}/{created.month}', and export desitnation DEST is " + "'{created.year}/{created.month}', and export desitnation DEST is "
+ "'/Users/maria/Pictures/export', " + "'/Users/maria/Pictures/export', "
+ " the actual export directory for a photo would be '/Users/maria/Pictures/export/2020/March' " + "the actual export directory for a photo would be '/Users/maria/Pictures/export/2020/March' "
+ " if the photo was created in March 2020. " + "if the photo was created in March 2020. "
) )
formatter.write("\n") formatter.write("\n")
formatter.write_text( formatter.write_text(
@ -104,16 +104,22 @@ class ExportCommand(click.Command):
) )
formatter.write("\n") formatter.write("\n")
formatter.write_text( formatter.write_text(
"In the current implementation, substitutions which have no value " "You may specify an optional default value to use if the substitution does not contain a value "
+ "will be replaced by '_', " + "(e.g. the value is null) "
+ "for example, your template looked like '{created.year}/{place.address}' " + "by specifying the default value after a ',' in the template string: "
+ "for example, if template is '{created.year}/{place.address,'NO_ADDRESS'}' "
+ "but there was no address associated with the photo, the resulting output would be: " + "but there was no address associated with the photo, the resulting output would be: "
+ "'2020/_/photoname.jpg' " + "'2020/NO_ADDRESS/photoname.jpg'. "
+ "If specified, the default value may not contain a brace symbol ('{' or '}')."
) )
formatter.write("\n") formatter.write("\n")
formatter.write_text( formatter.write_text(
"I plan to add the option to specify the value to be used for missing " "If you do not specify a default value and the template substitution "
+ "subsitutions in a future version. I also plan to extend the templating system " + "has no value, '_' (underscore) will be used as the default value. For example, in the "
+ "above example, this would result in '2020/_/photoname.jpg' if address was null"
)
formatter.write_text(
"I plan to eventually extend the templating system "
+ "to the exported filename so you can specify the filename using a template." + "to the exported filename so you can specify the filename using a template."
) )
@ -817,8 +823,8 @@ def query(
"--directory", "--directory",
metavar="DIRECTORY", metavar="DIRECTORY",
default=None, default=None,
help="Optional template for specifying name of output directory. " help="Optional template for specifying name of output directory in the form '{name,DEFAULT}'. "
"See below for additional details on templating system", "See below for additional details on templating system.",
) )
@DB_ARGUMENT @DB_ARGUMENT
@click.argument("dest", nargs=1, type=click.Path(exists=True)) @click.argument("dest", nargs=1, type=click.Path(exists=True))

View File

@ -1,3 +1,3 @@
""" version info """ """ version info """
__version__ = "0.24.0" __version__ = "0.24.1"

View File

@ -25,17 +25,17 @@ TEMPLATE_SUBSTITUTIONS = {
"{modified.mon}": "Month abbreviation in the user's locale of the file modification time", "{modified.mon}": "Month abbreviation in the user's locale of the file modification time",
"{modified.doy}": "3-digit day of year (e.g Julian day) of file modification time, starting from 1 (zero padded)", "{modified.doy}": "3-digit day of year (e.g Julian day) of file modification time, starting from 1 (zero padded)",
"{place.name}": "Place name from the photo's reverse geolocation data, as displayed in Photos", "{place.name}": "Place name from the photo's reverse geolocation data, as displayed in Photos",
"{place.names.country}": "Country name from the photo's reverse geolocation data", "{place.name.country}": "Country name from the photo's reverse geolocation data",
"{place.names.state_province}": "State or province name from the photo's reverse geolocation data", "{place.name.state_province}": "State or province name from the photo's reverse geolocation data",
"{place.names.city}": "City or locality name from the photo's reverse geolocation data", "{place.name.city}": "City or locality name from the photo's reverse geolocation data",
"{place.names.area_of_interest}": "Area of interest name (e.g. landmark or public place) from the photo's reverse geolocation data", "{place.name.area_of_interest}": "Area of interest name (e.g. landmark or public place) from the photo's reverse geolocation data",
"{place.address}": "Postal address from the photo's reverse geolocation data, e.g. '2007 18th St NW, Washington, DC 20009, United States'", "{place.address}": "Postal address from the photo's reverse geolocation data, e.g. '2007 18th St NW, Washington, DC 20009, United States'",
"{place.street}": "Street part of the postal address, e.g. '2007 18th St NW'", "{place.address.street}": "Street part of the postal address, e.g. '2007 18th St NW'",
"{place.city}": "City part of the postal address, e.g. 'Washington'", "{place.address.city}": "City part of the postal address, e.g. 'Washington'",
"{place.state_province}": "State/province part of the postal address, e.g. 'DC'", "{place.address.state_province}": "State/province part of the postal address, e.g. 'DC'",
"{place.postal_code}": "Postal code part of the postal address, e.g. '20009'", "{place.address.postal_code}": "Postal code part of the postal address, e.g. '20009'",
"{place.country}": "Country name of the postal address, e.g. 'United States'", "{place.address.country}": "Country name of the postal address, e.g. 'United States'",
"{place.country_code}": "ISO country code of the postal address, e.g. 'US'", "{place.address.country_code}": "ISO country code of the postal address, e.g. 'US'",
} }
@ -119,34 +119,81 @@ def get_template_value(lookup, photo):
if lookup == "place.name": if lookup == "place.name":
return photo.place.name if photo.place else None return photo.place.name if photo.place else None
if lookup == "place.names.country": if lookup == "place.name.country":
return ( return (
photo.place.names.country[0] photo.place.names.country[0]
if photo.place and photo.place.names.country if photo.place and photo.place.names.country
else None else None
) )
if lookup == "place.names.state_province": if lookup == "place.name.state_province":
return ( return (
photo.place.names.state_province[0] photo.place.names.state_province[0]
if photo.place and photo.place.names.state_province if photo.place and photo.place.names.state_province
else None else None
) )
if lookup == "place.names.city": if lookup == "place.name.city":
return ( return (
photo.place.names.city[0] photo.place.names.city[0]
if photo.place and photo.place.names.city if photo.place and photo.place.names.city
else None else None
) )
if lookup == "place.names.area_of_interest": if lookup == "place.name.area_of_interest":
return ( return (
photo.place.names.area_of_interest[0] photo.place.names.area_of_interest[0]
if photo.place and photo.place.names.area_of_interest if photo.place and photo.place.names.area_of_interest
else None else None
) )
if lookup == "place.address":
return (
photo.place.address_str if photo.place and photo.place.address_str else None
)
if lookup == "place.address.street":
return (
photo.place.address.street
if photo.place and photo.place.address.street
else None
)
if lookup == "place.address.city":
return (
photo.place.address.city
if photo.place and photo.place.address.city
else None
)
if lookup == "place.address.state_province":
return (
photo.place.address.state_province
if photo.place and photo.place.address.state_province
else None
)
if lookup == "place.address.postal_code":
return (
photo.place.address.postal_code
if photo.place and photo.place.address.postal_code
else None
)
if lookup == "place.address.country":
return (
photo.place.address.country
if photo.place and photo.place.address.country
else None
)
if lookup == "place.address.country_code":
return (
photo.place.address.iso_country_code
if photo.place and photo.place.address.iso_country_code
else None
)
# if here, didn't get a match # if here, didn't get a match
raise KeyError(f"No rule for processing {lookup}") raise KeyError(f"No rule for processing {lookup}")

View File

@ -109,11 +109,7 @@ def test_PlaceInfo5():
assert place.name == "Washington, District of Columbia, United States" assert place.name == "Washington, District of Columbia, United States"
assert place.names.street_address == ["2038 18th St NW"] assert place.names.street_address == ["2038 18th St NW"]
assert place.names.additional_city_info == ["Adams Morgan"] assert place.names.additional_city_info == ["Adams Morgan"]
assert place.names.city == [ assert place.names.city == ["Washington", "Washington", "Washington"]
"Washington",
"Washington",
"Washington",
]
assert place.names.state_province == ["District of Columbia"] assert place.names.state_province == ["District of Columbia"]
assert place.names.country == ["United States"] assert place.names.country == ["United States"]
assert place.country_code == "US" assert place.country_code == "US"

View File

@ -1,8 +1,6 @@
""" Test PlaceInfo """ """ Test PlaceInfo """
import pytest import pytest
from osxphotos._constants import _UNKNOWN_PERSON
PHOTOS_DB = "./tests/Test-Places-Catalina-10_15_1.photoslibrary/database/photos.db" PHOTOS_DB = "./tests/Test-Places-Catalina-10_15_1.photoslibrary/database/photos.db"
@ -62,23 +60,11 @@ def test_place_place_info_2():
assert not photo.place.ishome assert not photo.place.ishome
assert photo.place.name == "Maui, Wailea, Hawai'i, United States" assert photo.place.name == "Maui, Wailea, Hawai'i, United States"
assert photo.place.names.street_address == ["3700 Wailea Alanui Dr"] assert photo.place.names.street_address == ["3700 Wailea Alanui Dr"]
assert photo.place.names.city == [ assert photo.place.names.city == ["Wailea", "Kihei", "Kihei"]
"Wailea", assert photo.place.names.region == ["Maui"]
"Kihei", assert photo.place.names.sub_administrative_area == ["Maui"]
"Kihei", assert photo.place.names.state_province == ["Hawai'i"]
] assert photo.place.names.country == ["United States"]
assert photo.place.names.region == [
"Maui",
]
assert photo.place.names.sub_administrative_area == [
"Maui",
]
assert photo.place.names.state_province == [
"Hawai'i",
]
assert photo.place.names.country == [
"United States",
]
assert photo.place.country_code == "US" assert photo.place.country_code == "US"
assert ( assert (

View File

@ -1,8 +1,6 @@
""" Test PlaceInfo """ """ Test PlaceInfo """
import pytest import pytest
from osxphotos._constants import _UNKNOWN_PERSON
PHOTOS_DB = "./tests/Test-Places-High-Sierra-10.13.6.photoslibrary/database/photos.db" PHOTOS_DB = "./tests/Test-Places-High-Sierra-10.13.6.photoslibrary/database/photos.db"
UUID_DICT = { UUID_DICT = {
@ -144,6 +142,7 @@ def test_place_place_info_4():
assert photo.place.names.sub_throughfare == [] assert photo.place.names.sub_throughfare == []
assert photo.place.names.body_of_water == ["River Torrens"] assert photo.place.names.body_of_water == ["River Torrens"]
def test_place_no_place_info(): def test_place_no_place_info():
# test valid place info # test valid place info
import osxphotos import osxphotos

View File

@ -1,8 +1,6 @@
""" Test PlaceInfo """ """ Test PlaceInfo """
import pytest import pytest
from osxphotos._constants import _UNKNOWN_PERSON
PHOTOS_DB = "./tests/Test-10.14.6.photoslibrary/database/photos.db" PHOTOS_DB = "./tests/Test-10.14.6.photoslibrary/database/photos.db"
UUID_DICT = {"place_uk": "3Jn73XpSQQCluzRBMWRsMA", "no_place": "15uNd7%8RguTEgNPKHfTWw"} UUID_DICT = {"place_uk": "3Jn73XpSQQCluzRBMWRsMA", "no_place": "15uNd7%8RguTEgNPKHfTWw"}
@ -58,7 +56,7 @@ def test_place_str():
"names='PlaceNames(field0=[], country=['United Kingdom'], " "names='PlaceNames(field0=[], country=['United Kingdom'], "
"state_province=['England'], sub_administrative_area=['London'], " "state_province=['England'], sub_administrative_area=['London'], "
"city=['Westminster'], field5=[], additional_city_info=[], ocean=[], " "city=['Westminster'], field5=[], additional_city_info=[], ocean=[], "
"area_of_interest=[\"St James's Park\"], inland_water=[], field10=[], " 'area_of_interest=["St James\'s Park"], inland_water=[], field10=[], '
"region=[], sub_throughfare=[], field13=[], postal_code=[], field15=[], " "region=[], sub_throughfare=[], field13=[], postal_code=[], field15=[], "
"field16=[], street_address=[], body_of_water=[])', country_code='GB')" "field16=[], street_address=[], body_of_water=[])', country_code='GB')"
) )

120
tests/test_template.py Normal file
View File

@ -0,0 +1,120 @@
""" Test template.py """
import pytest
PHOTOS_DB = "./tests/Test-Places-Catalina-10_15_1.photoslibrary/database/photos.db"
UUID_DICT = {"place_dc": "128FB4C6-0B16-4E7D-9108-FB2E90DA1546"}
TEMPLATE_VALUES = {
"{name}": "128FB4C6-0B16-4E7D-9108-FB2E90DA1546",
"{original_name}": "IMG_1064",
"{title}": "Glen Ord",
"{descr}": "Jack Rose Dining Saloon",
"{created.date}": "2020-02-04",
"{created.year}": "2020",
"{created.yy}": "20",
"{created.mm}": "02",
"{created.month}": "February",
"{created.mon}": "Feb",
"{created.doy}": "035",
"{modified.date}": "2020-03-21",
"{modified.year}": "2020",
"{modified.yy}": "20",
"{modified.mm}": "03",
"{modified.month}": "March",
"{modified.mon}": "Mar",
"{modified.doy}": "081",
"{place.name}": "Washington, District of Columbia, United States",
"{place.name.country}": "United States",
"{place.name.state_province}": "District of Columbia",
"{place.name.city}": "Washington",
"{place.name.area_of_interest}": "_",
"{place.address}": "2038 18th St NW, Washington, DC 20009, United States",
"{place.address.street}": "2038 18th St NW",
"{place.address.city}": "Washington",
"{place.address.state_province}": "DC",
"{place.address.postal_code}": "20009",
"{place.address.country}": "United States",
"{place.address.country_code}": "US",
}
def test_lookup():
""" Test that a lookup is returned for every possible value """
import re
import osxphotos
from osxphotos.template import (
get_template_value,
render_filepath_template,
TEMPLATE_SUBSTITUTIONS,
)
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
photo = photosdb.photos(uuid=[UUID_DICT["place_dc"]])[0]
for subst in TEMPLATE_SUBSTITUTIONS:
lookup_str = re.match(r"\{([^\\,}]+)\}", subst).group(1)
lookup = get_template_value(lookup_str, photo)
assert lookup or lookup is None
def test_subst():
""" Test that substitutions are correct """
import locale
import osxphotos
from osxphotos.template import render_filepath_template
locale.setlocale(locale.LC_ALL, "en_US")
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
photo = photosdb.photos(uuid=[UUID_DICT["place_dc"]])[0]
for template in TEMPLATE_VALUES:
rendered, _ = render_filepath_template(template, photo)
assert rendered == TEMPLATE_VALUES[template]
def test_subst_default_val():
""" Test substitution with default value specified """
import locale
import osxphotos
from osxphotos.template import render_filepath_template
locale.setlocale(locale.LC_ALL, "en_US")
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
photo = photosdb.photos(uuid=[UUID_DICT["place_dc"]])[0]
template = "{place.name.area_of_interest,UNKNOWN}"
rendered, _ = render_filepath_template(template, photo)
assert rendered == "UNKNOWN"
def test_subst_default_val_2():
""" Test substitution with ',' but no default value """
import locale
import osxphotos
from osxphotos.template import render_filepath_template
locale.setlocale(locale.LC_ALL, "en_US")
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
photo = photosdb.photos(uuid=[UUID_DICT["place_dc"]])[0]
template = "{place.name.area_of_interest,}"
rendered, _ = render_filepath_template(template, photo)
assert rendered == "_"
def test_subst_unknown_val():
""" Test substitution with unknown value specified """
import locale
import osxphotos
from osxphotos.template import render_filepath_template
locale.setlocale(locale.LC_ALL, "en_US")
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
photo = photosdb.photos(uuid=[UUID_DICT["place_dc"]])[0]
template = "{created.year}/{foo}"
rendered, unknown = render_filepath_template(template, photo)
assert rendered == "2020/{foo}"
assert unknown == ["{foo}"]