Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
120 additions
and
21 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,3 +18,4 @@ docsrc/_build/ | |
venv/ | ||
.python-version | ||
cov.xml | ||
pyrightconfig.json |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
"""Create a PhotoInfo compatible object from a PhotoInfo dictionary created with PhotoInfo.asdict()""" | ||
|
||
from __future__ import annotations | ||
|
||
import json | ||
from typing import Any | ||
|
||
from .exiftool import ExifToolCaching, get_exiftool_path | ||
from .rehydrate import rehydrate_class | ||
|
||
try: | ||
EXIFTOOL_PATH = get_exiftool_path() | ||
except FileNotFoundError: | ||
EXIFTOOL_PATH = None | ||
|
||
__all__ = ["PhotoInfoFromDict", "photoinfo_from_dict"] | ||
|
||
|
||
class PhotoInfoFromDict: | ||
"""Create a PhotoInfo compatible object from a PhotoInfo dictionary created with PhotoInfo.asdict() or deserialized from JSON""" | ||
|
||
def asdict(self) -> dict[str, Any]: | ||
"""Return the PhotoInfo dictionary""" | ||
return self._data | ||
|
||
def json(self) -> str: | ||
"""Return the PhotoInfo dictionary as a JSON string""" | ||
return json.dumps(self._data) | ||
|
||
|
||
def photoinfo_from_dict( | ||
data: dict[str, Any], exiftool: str | None = None | ||
) -> PhotoInfoFromDict: | ||
"""Create a PhotoInfoFromDict object from a dictionary""" | ||
photoinfo = rehydrate_class(data, PhotoInfoFromDict) | ||
photoinfo._exiftool_path = exiftool or EXIFTOOL_PATH | ||
photoinfo._data = data | ||
return photoinfo |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
"""Rehydrate a class from a dictionary""" | ||
|
||
from __future__ import annotations | ||
|
||
import datetime | ||
from typing import Any, Type | ||
|
||
|
||
def rehydrate_class(data: dict[Any, Any], cls: Type) -> object: | ||
"""Rehydrate a class that's been deserialized from JSON created from asdict() | ||
Args: | ||
data: dictionary of class data; datetimes should be in ISO formatted strings | ||
cls: class to rehydrate into | ||
Returns: | ||
Rehydrated class instance | ||
Note: | ||
This function is not a complete solution for all classes, but it's a good starting point. | ||
It doesn't handle all edge cases, such as classes with required arguments in __init__. | ||
The only special data types are datetimes, which are parsed from ISO formatted strings. | ||
Lists of dictionaries and nested dictionary are also supported. | ||
""" | ||
if isinstance(data, list): | ||
# If the data is a list, create a list of rehydrated class instances | ||
return [rehydrate_class(item, cls) for item in data] | ||
|
||
instance = cls() | ||
|
||
for key, value in data.items(): | ||
if isinstance(value, dict): | ||
# If the value is a dictionary, create a new class instance recursively | ||
setattr(instance, key, rehydrate_class(value, type(key, (object,), {}))) | ||
elif isinstance(value, list): | ||
# If the value is a list, check if it contains dictionaries | ||
if all(isinstance(item, dict) for item in value): | ||
# If all items in the list are dictionaries, create a list of rehydrated class instances | ||
setattr( | ||
instance, | ||
key, | ||
[rehydrate_class(item, type(key, (object,), {})) for item in value], | ||
) | ||
else: | ||
setattr(instance, key, value) | ||
elif "date" in key.lower() and value is not None: | ||
# If the key contains "date" and the value is not None, try to parse it as a datetime | ||
try: | ||
setattr(instance, key, datetime.datetime.fromisoformat(value)) | ||
except (ValueError, TypeError): | ||
setattr(instance, key, value) | ||
else: | ||
setattr(instance, key, value) | ||
|
||
return instance |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
"""Test PhotoInfoFromDict class""" | ||
|
||
from __future__ import annotations | ||
|
||
import pytest | ||
|
||
from osxphotos.photoinfo_dict import PhotoInfoFromDict, photoinfo_from_dict | ||
from osxphotos.photosdb import PhotosDB | ||
|
||
PHOTOSDB = "tests/Test-13.0.0.photoslibrary" | ||
|
||
|
||
def test_rehydrate_dict(): | ||
"""Test rehydrating a dictionary""" | ||
photosdb = PhotosDB(dbfile=PHOTOSDB) | ||
photo = [p for p in photosdb.photos() if p.original_filename == "wedding.jpg"][0] | ||
photo_dict = photo.asdict() | ||
photo2 = photoinfo_from_dict(photo_dict) | ||
assert isinstance(photo2, PhotoInfoFromDict) | ||
assert photo.uuid == photo2.uuid | ||
photo2_dict = photo2.asdict() | ||
assert photo_dict == photo2_dict |