Compare commits

..

7 Commits

Author SHA1 Message Date
Rhet Turnbull
1ebf995833 Bug fix for issue #172 2020-06-21 08:42:19 -07:00
Rhet Turnbull
538bac7ade More PhotoInfo.albums refactoring, closes #169 2020-06-21 08:18:11 -07:00
Rhet Turnbull
32806c8459 Updated CHANGELOG.md 2020-06-20 17:44:18 -07:00
Rhet Turnbull
cfabd0dbea Refactored album code in photosdb to fix issue #169 2020-06-20 17:31:33 -07:00
Rhet Turnbull
a23259948c Updated CHANGELOG.md 2020-06-20 08:43:42 -07:00
Rhet Turnbull
1212fad4ad Fixed PhotoInfo.albums, album_info for issue #169 2020-06-20 08:36:03 -07:00
Rhet Turnbull
567abe3311 Updated CHANGELOG.md 2020-06-18 22:52:21 -07:00
12 changed files with 258 additions and 109 deletions

View File

@@ -4,6 +4,29 @@ All notable changes to this project will be documented in this file. Dates are d
Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog). Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
#### [v0.29.24](https://github.com/RhetTbull/osxphotos/compare/v0.29.23...v0.29.24)
> 21 June 2020
- Refactored album code in photosdb to fix issue #169 [`cfabd0d`](https://github.com/RhetTbull/osxphotos/commit/cfabd0dbead62c8ab6a774899239e5da5bfe1203)
#### [v0.29.23](https://github.com/RhetTbull/osxphotos/compare/v0.29.22...v0.29.23)
> 20 June 2020
- Fixed PhotoInfo.albums, album_info for issue #169 [`1212fad`](https://github.com/RhetTbull/osxphotos/commit/1212fad4adde0b4c6b2887392eed829d8d96d61d)
#### [v0.29.22](https://github.com/RhetTbull/osxphotos/compare/v0.29.19...v0.29.22)
> 19 June 2020
- Don't raise KeyError when SystemLibraryPath is absent [`#168`](https://github.com/RhetTbull/osxphotos/pull/168)
- Added check for export db in directory branch, closes #164 [`#164`](https://github.com/RhetTbull/osxphotos/issues/164)
- Added OSXPhotosDB.get_db_connection() [`43d28e7`](https://github.com/RhetTbull/osxphotos/commit/43d28e78f394fa33f8d88f64b56b7dc7258cd454)
- Added show() to photos_repl.py [`e98c3fe`](https://github.com/RhetTbull/osxphotos/commit/e98c3fe42912ac16d13675bf14154981089d41ea)
- Fixed get_last_library_path and get_system_library_path to not raise KeyError [`5a83218`](https://github.com/RhetTbull/osxphotos/commit/5a832181f73e082927c80864f2063e554906b06b)
- Don't raise KeyError when SystemLibraryPath is absent [`1fd0f96`](https://github.com/RhetTbull/osxphotos/commit/1fd0f96b14f0bc38e47bddb4cae12e19406324fb)
#### [v0.29.19](https://github.com/RhetTbull/osxphotos/compare/v0.29.18...v0.29.19) #### [v0.29.19](https://github.com/RhetTbull/osxphotos/compare/v0.29.18...v0.29.19)
> 14 June 2020 > 14 June 2020

View File

@@ -1,3 +1,3 @@
""" version info """ """ version info """
__version__ = "0.29.22" __version__ = "0.29.26"

View File

@@ -21,12 +21,16 @@ import yaml
from .._constants import ( from .._constants import (
_MOVIE_TYPE, _MOVIE_TYPE,
_PHOTO_TYPE, _PHOTO_TYPE,
_PHOTOS_4_ALBUM_KIND,
_PHOTOS_4_ROOT_FOLDER,
_PHOTOS_4_VERSION, _PHOTOS_4_VERSION,
_PHOTOS_5_ALBUM_KIND,
_PHOTOS_5_SHARED_ALBUM_KIND,
_PHOTOS_5_SHARED_PHOTO_PATH, _PHOTOS_5_SHARED_PHOTO_PATH,
) )
from ..albuminfo import AlbumInfo from ..albuminfo import AlbumInfo
from ..placeinfo import PlaceInfo4, PlaceInfo5
from ..phototemplate import PhotoTemplate from ..phototemplate import PhotoTemplate
from ..placeinfo import PlaceInfo4, PlaceInfo5
from ..utils import _debug, _get_resource_loc, findfiles, get_preferred_uti_extension from ..utils import _debug, _get_resource_loc, findfiles, get_preferred_uti_extension
@@ -341,21 +345,26 @@ class PhotoInfo:
@property @property
def albums(self): def albums(self):
""" list of albums picture is contained in """ """ list of albums picture is contained in """
albums = [] try:
for album in self._info["albums"]: return self._albums
if not self._db._dbalbum_details[album]["intrash"]: except AttributeError:
albums.append(self._db._dbalbum_details[album]["title"]) album_uuids = self._get_album_uuids()
return albums self._albums = list(
{self._db._dbalbum_details[album]["title"] for album in album_uuids}
)
return self._albums
@property @property
def album_info(self): def album_info(self):
""" list of AlbumInfo objects representing albums the photos is contained in """ """ list of AlbumInfo objects representing albums the photos is contained in """
albums = [] try:
for album in self._info["albums"]: return self._album_info
if not self._db._dbalbum_details[album]["intrash"]: except AttributeError:
albums.append(AlbumInfo(db=self._db, uuid=album)) album_uuids = self._get_album_uuids()
self._album_info = [
return albums AlbumInfo(db=self._db, uuid=album) for album in album_uuids
]
return self._album_info
@property @property
def keywords(self): def keywords(self):
@@ -655,6 +664,37 @@ class PhotoInfo:
""" Returns latitude, in degrees """ """ Returns latitude, in degrees """
return self._info["latitude"] return self._info["latitude"]
def _get_album_uuids(self):
""" Return list of album UUIDs this photo is found in
Filters out albums in the trash and any special album types
Returns: list of album UUIDs
"""
if self._db._db_version <= _PHOTOS_4_VERSION:
version4 = True
album_kind = [_PHOTOS_4_ALBUM_KIND]
else:
version4 = False
album_kind = [_PHOTOS_5_SHARED_ALBUM_KIND, _PHOTOS_5_ALBUM_KIND]
album_list = []
for album in self._info["albums"]:
detail = self._db._dbalbum_details[album]
if (
detail["kind"] in album_kind
and not detail["intrash"]
and (
not version4
# in Photos <= 4, special albums like "printAlbum" have kind _PHOTOS_4_ALBUM_KIND
# but should not be listed here; they can be distinguished by looking
# for folderUuid of _PHOTOS_4_ROOT_FOLDER as opposed to _PHOTOS_4_TOP_LEVEL_ALBUM
or (version4 and detail["folderUuid"] != _PHOTOS_4_ROOT_FOLDER)
)
):
album_list.append(album)
return album_list
def __repr__(self): def __repr__(self):
return f"osxphotos.{self.__class__.__name__}(db={self._db}, uuid='{self._uuid}', info={self._info})" return f"osxphotos.{self.__class__.__name__}(db={self._db}, uuid='{self._uuid}', info={self._info})"
@@ -772,12 +812,18 @@ class PhotoInfo:
} }
return json.dumps(pic) return json.dumps(pic)
# compare two PhotoInfo objects for equality
def __eq__(self, other): def __eq__(self, other):
""" Compare two PhotoInfo objects for equality """
# Can't just compare the two __dicts__ because some methods (like albums)
# memoize their value once called in an instance variable (e.g. self._albums)
if isinstance(other, self.__class__): if isinstance(other, self.__class__):
return self.__dict__ == other.__dict__ return (
self._db.db_path == other._db.db_path
and self.uuid == other.uuid
and self._info == other._info
)
return False return False
def __ne__(self, other): def __ne__(self, other):
""" Compare two PhotoInfo objects for inequality """
return not self.__eq__(other) return not self.__eq__(other)

View File

@@ -311,18 +311,16 @@ class PhotosDB:
def albums_as_dict(self): def albums_as_dict(self):
""" return albums as dict of albums, count in reverse sorted order (descending) """ """ return albums as dict of albums, count in reverse sorted order (descending) """
albums = {} albums = {}
album_keys = [ album_keys = self._get_album_uuids(shared=False)
k for album in album_keys:
for k in self._dbalbums_album.keys() title = self._dbalbum_details[album]["title"]
if self._dbalbum_details[k]["cloudownerhashedpersonid"] is None if album in self._dbalbums_album:
and not self._dbalbum_details[k]["intrash"] try:
] albums[title] += len(self._dbalbums_album[album])
for k in album_keys: except KeyError:
title = self._dbalbum_details[k]["title"] albums[title] = len(self._dbalbums_album[album])
if title in albums:
albums[title] += len(self._dbalbums_album[k])
else: else:
albums[title] = len(self._dbalbums_album[k]) albums[title] = 0 # empty album
albums = dict(sorted(albums.items(), key=lambda kv: kv[1], reverse=True)) albums = dict(sorted(albums.items(), key=lambda kv: kv[1], reverse=True))
return albums return albums
@@ -331,25 +329,17 @@ class PhotosDB:
""" returns shared albums as dict of albums, count in reverse sorted order (descending) """ returns shared albums as dict of albums, count in reverse sorted order (descending)
valid only on Photos 5; on Photos <= 4, prints warning and returns empty dict """ valid only on Photos 5; on Photos <= 4, prints warning and returns empty dict """
# if _dbalbum_details[key]["cloudownerhashedpersonid"] is not None, then it's a shared album
if self._db_version <= _PHOTOS_4_VERSION:
logging.warning(
f"albums_shared not implemented for Photos versions < {_PHOTOS_5_VERSION}"
)
return {}
albums = {} albums = {}
album_keys = [ album_keys = self._get_album_uuids(shared=True)
k for album in album_keys:
for k in self._dbalbums_album.keys() title = self._dbalbum_details[album]["title"]
if self._dbalbum_details[k]["cloudownerhashedpersonid"] is not None if album in self._dbalbums_album:
] try:
for k in album_keys: albums[title] += len(self._dbalbums_album[album])
title = self._dbalbum_details[k]["title"] except KeyError:
if title in albums: albums[title] = len(self._dbalbums_album[album])
albums[title] += len(self._dbalbums_album[k])
else: else:
albums[title] = len(self._dbalbums_album[k]) albums[title] = 0 # empty album
albums = dict(sorted(albums.items(), key=lambda kv: kv[1], reverse=True)) albums = dict(sorted(albums.items(), key=lambda kv: kv[1], reverse=True))
return albums return albums
@@ -410,32 +400,28 @@ class PhotosDB:
@property @property
def album_info(self): def album_info(self):
""" return list of AlbumInfo objects for each album in the photos database """ """ return list of AlbumInfo objects for each album in the photos database """
try:
return [ return self._album_info
AlbumInfo(db=self, uuid=album) except AttributeError:
for album in self._dbalbums_album.keys() self._album_info = [
if self._dbalbum_details[album]["cloudownerhashedpersonid"] is None AlbumInfo(db=self, uuid=album)
and not self._dbalbum_details[album]["intrash"] for album in self._get_album_uuids(shared=False)
] ]
return self._album_info
@property @property
def album_info_shared(self): def album_info_shared(self):
""" return list of AlbumInfo objects for each shared album in the photos database """ return list of AlbumInfo objects for each shared album in the photos database
only valid for Photos 5; on Photos <= 4, prints warning and returns empty list """ only valid for Photos 5; on Photos <= 4, prints warning and returns empty list """
# if _dbalbum_details[key]["cloudownerhashedpersonid"] is not None, then it's a shared album # if _dbalbum_details[key]["cloudownerhashedpersonid"] is not None, then it's a shared album
try:
if self._db_version <= _PHOTOS_4_VERSION: return self._album_info_shared
logging.warning( except AttributeError:
f"albums_shared not implemented for Photos versions < {_PHOTOS_5_VERSION}" self._album_info_shared = [
) AlbumInfo(db=self, uuid=album)
return [] for album in self._get_album_uuids(shared=True)
]
return [ return self._album_info_shared
AlbumInfo(db=self, uuid=album)
for album in self._dbalbums_album.keys()
if self._dbalbum_details[album]["cloudownerhashedpersonid"] is not None
and not self._dbalbum_details[album]["intrash"]
]
@property @property
def albums(self): def albums(self):
@@ -444,13 +430,11 @@ class PhotosDB:
# Could be more than one album with same name # Could be more than one album with same name
# Right now, they are treated as same album and photos are combined from albums with same name # Right now, they are treated as same album and photos are combined from albums with same name
albums = { try:
self._dbalbum_details[album]["title"] return self._albums
for album in self._dbalbums_album.keys() except AttributeError:
if self._dbalbum_details[album]["cloudownerhashedpersonid"] is None self._albums = self._get_albums(shared=False)
and not self._dbalbum_details[album]["intrash"] return self._albums
}
return list(albums)
@property @property
def albums_shared(self): def albums_shared(self):
@@ -462,19 +446,11 @@ class PhotosDB:
# if _dbalbum_details[key]["cloudownerhashedpersonid"] is not None, then it's a shared album # if _dbalbum_details[key]["cloudownerhashedpersonid"] is not None, then it's a shared album
if self._db_version <= _PHOTOS_4_VERSION: try:
logging.warning( return self._albums_shared
f"album_names_shared not implemented for Photos versions < {_PHOTOS_5_VERSION}" except AttributeError:
) self._albums_shared = self._get_albums(shared=True)
return [] return self._albums_shared
albums = {
self._dbalbum_details[album]["title"]
for album in self._dbalbums_album.keys()
if self._dbalbum_details[album]["cloudownerhashedpersonid"] is not None
and not self._dbalbum_details[album]["intrash"]
}
return list(albums)
@property @property
def db_version(self): def db_version(self):
@@ -634,6 +610,8 @@ class PhotosDB:
"folderUuid": album[5], "folderUuid": album[5],
"albumType": album[6], "albumType": album[6],
"albumSubclass": album[7], "albumSubclass": album[7],
# for compatability with Photos 5 where album kind is ZKIND
"kind": album[7],
} }
# get details about folders # get details about folders
@@ -2098,6 +2076,65 @@ class PhotosDB:
hierarchy = _recurse_folder_hierarchy(folders) hierarchy = _recurse_folder_hierarchy(folders)
return hierarchy return hierarchy
def _get_album_uuids(self, shared=False):
""" Return list of album UUIDs found in photos database
Filters out albums in the trash and any special album types
Args:
shared: boolean; if True, returns shared albums, else normal albums
Returns: list of album UUIDs
"""
if self._db_version <= _PHOTOS_4_VERSION:
version4 = True
if shared:
logging.warning(
f"Shared albums not implemented for Photos library version {self._db_version}"
)
return [] # not implemented for _PHOTOS_4_VERSION
else:
album_kind = _PHOTOS_4_ALBUM_KIND
else:
version4 = False
album_kind = _PHOTOS_5_SHARED_ALBUM_KIND if shared else _PHOTOS_5_ALBUM_KIND
album_list = []
# look through _dbalbum_details because _dbalbums_album won't have empty albums it
for album, detail in self._dbalbum_details.items():
if (
detail["kind"] == album_kind
and not detail["intrash"]
and (
(shared and detail["cloudownerhashedpersonid"] is not None)
or (not shared and detail["cloudownerhashedpersonid"] is None)
)
and (
not version4
# in Photos 4, special albums like "printAlbum" have kind _PHOTOS_4_ALBUM_KIND
# but should not be listed here; they can be distinguished by looking
# for folderUuid of _PHOTOS_4_ROOT_FOLDER as opposed to _PHOTOS_4_TOP_LEVEL_ALBUM
or (version4 and detail["folderUuid"] != _PHOTOS_4_ROOT_FOLDER)
)
):
album_list.append(album)
return album_list
def _get_albums(self, shared=False):
""" Return list of album titles found in photos database
Albums may have duplicate titles -- these will be treated as a single album.
Filters out albums in the trash and any special album types
Args:
shared: boolean; if True, returns shared albums, else normal albums
Returns: list of album names
"""
album_uuids = self._get_album_uuids(shared=shared)
return list({self._dbalbum_details[album]["title"] for album in album_uuids})
def photos( def photos(
self, self,
keywords=None, keywords=None,

View File

@@ -503,7 +503,6 @@ class PlaceInfo5(PlaceInfo):
""" revgeoloc_bplist: a binary plist blob containing """ revgeoloc_bplist: a binary plist blob containing
a serialized PLRevGeoLocationInfo object """ a serialized PLRevGeoLocationInfo object """
self._bplist = revgeoloc_bplist self._bplist = revgeoloc_bplist
# todo: check for None?
self._plrevgeoloc = archiver.unarchive(revgeoloc_bplist) self._plrevgeoloc = archiver.unarchive(revgeoloc_bplist)
self._process_place_info() self._process_place_info()
@@ -535,16 +534,21 @@ class PlaceInfo5(PlaceInfo):
@property @property
def address(self): def address(self):
addr = self._plrevgeoloc.postalAddress addr = self._plrevgeoloc.postalAddress
return PostalAddress( if addr is not None:
street=addr._street, postal_address = PostalAddress(
sub_locality=addr._subLocality, street=addr._street,
city=addr._city, sub_locality=addr._subLocality,
sub_administrative_area=addr._subAdministrativeArea, city=addr._city,
state_province=addr._state, sub_administrative_area=addr._subAdministrativeArea,
postal_code=addr._postalCode, state_province=addr._state,
country=addr._country, postal_code=addr._postalCode,
iso_country_code=addr._ISOCountryCode, country=addr._country,
) iso_country_code=addr._ISOCountryCode,
)
else:
postal_address = None
return postal_address
def _process_place_info(self): def _process_place_info(self):
""" Process sortedPlaceInfos to set self._name and self._names """ """ Process sortedPlaceInfos to set self._name and self._names """
@@ -632,5 +636,5 @@ class PlaceInfo5(PlaceInfo):
"country_code": self.country_code, "country_code": self.country_code,
"ishome": self.ishome, "ishome": self.ishome,
"address_str": self.address_str, "address_str": self.address_str,
"address": self.address._asdict(), "address": self.address._asdict() if self.address is not None else None,
} }

View File

@@ -16,7 +16,7 @@ KEYWORDS = [
"United Kingdom", "United Kingdom",
] ]
PERSONS = ["Katie", "Suzy", "Maria"] PERSONS = ["Katie", "Suzy", "Maria"]
ALBUMS = ["Pumpkin Farm", "Last Import", "AlbumInFolder"] ALBUMS = ["Pumpkin Farm", "AlbumInFolder"]
KEYWORDS_DICT = { KEYWORDS_DICT = {
"Kids": 4, "Kids": 4,
"wedding": 2, "wedding": 2,
@@ -29,7 +29,7 @@ KEYWORDS_DICT = {
"United Kingdom": 1, "United Kingdom": 1,
} }
PERSONS_DICT = {"Katie": 3, "Suzy": 2, "Maria": 1} PERSONS_DICT = {"Katie": 3, "Suzy": 2, "Maria": 1}
ALBUM_DICT = {"Pumpkin Farm": 3, "Last Import": 1, "AlbumInFolder": 1} ALBUM_DICT = {"Pumpkin Farm": 3, "AlbumInFolder": 1}
def test_init(): def test_init():
@@ -124,7 +124,7 @@ def test_attributes():
) )
assert p.description == "Girl holding pumpkin" assert p.description == "Girl holding pumpkin"
assert p.title == "I found one!" assert p.title == "I found one!"
assert p.albums == ["Pumpkin Farm", "AlbumInFolder"] assert sorted(p.albums) == ["AlbumInFolder", "Pumpkin Farm"]
assert p.persons == ["Katie"] assert p.persons == ["Katie"]
assert p.path.endswith( assert p.path.endswith(
"/tests/Test-10.12.6.photoslibrary/Masters/2019/08/24/20190824-030824/Pumkins2.jpg" "/tests/Test-10.12.6.photoslibrary/Masters/2019/08/24/20190824-030824/Pumkins2.jpg"

View File

@@ -229,6 +229,7 @@ def test_albums_photos():
def test_photoinfo_albums(): def test_photoinfo_albums():
""" Test PhotoInfo.albums """
import osxphotos import osxphotos
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB) photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
@@ -238,7 +239,20 @@ def test_photoinfo_albums():
assert "Pumpkin Farm" in albums assert "Pumpkin Farm" in albums
def test_photoinfo_albums_2():
""" Test that PhotoInfo.albums returns only number albums expected """
import osxphotos
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
photos = photosdb.photos(uuid=[UUID_DICT["two_albums"]])
albums = photos[0].albums
assert len(albums) == 2
def test_photoinfo_album_info(): def test_photoinfo_album_info():
""" test PhotoInfo.album_info """
import osxphotos import osxphotos
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB) photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
@@ -249,4 +263,4 @@ def test_photoinfo_album_info():
assert album_info[0].title in ["Pumpkin Farm", "Test Album"] assert album_info[0].title in ["Pumpkin Farm", "Test Album"]
assert album_info[1].title in ["Pumpkin Farm", "Test Album"] assert album_info[1].title in ["Pumpkin Farm", "Test Album"]
assert photos[0] in album_info[0].photos assert photos[0].uuid in [photo.uuid for photo in album_info[0].photos]

View File

@@ -244,4 +244,4 @@ def test_photoinfo_album_info():
assert album_info[0].title in ["Pumpkin Farm", "Test Album"] assert album_info[0].title in ["Pumpkin Farm", "Test Album"]
assert album_info[1].title in ["Pumpkin Farm", "Test Album"] assert album_info[1].title in ["Pumpkin Farm", "Test Album"]
assert photos[0] in album_info[0].photos assert photos[0].uuid in [photo.uuid for photo in album_info[0].photos]

View File

@@ -106,6 +106,7 @@ def test_init4():
except: except:
pass pass
def test_init5(mocker): def test_init5(mocker):
# test failed get_last_library_path # test failed get_last_library_path
import osxphotos import osxphotos
@@ -116,7 +117,6 @@ def test_init5(mocker):
# get_last_library actually in utils but need to patch it in photosdb because it's imported into photosdb # get_last_library actually in utils but need to patch it in photosdb because it's imported into photosdb
# because of the layout of photosdb/ need to patch it this way...don't really understand why, but it works # because of the layout of photosdb/ need to patch it this way...don't really understand why, but it works
mocker.patch("osxphotos.photosdb.photosdb.get_last_library_path", new=bad_library) mocker.patch("osxphotos.photosdb.photosdb.get_last_library_path", new=bad_library)
with pytest.raises(Exception): with pytest.raises(Exception):
assert osxphotos.PhotosDB() assert osxphotos.PhotosDB()
@@ -207,7 +207,7 @@ def test_attributes():
) )
assert p.description == "Girl holding pumpkin" assert p.description == "Girl holding pumpkin"
assert p.title == "I found one!" assert p.title == "I found one!"
assert p.albums == ["Pumpkin Farm", "Test Album", "Multi Keyword"] assert sorted(p.albums) == ["Multi Keyword", "Pumpkin Farm", "Test Album"]
assert p.persons == ["Katie"] assert p.persons == ["Katie"]
assert p.path.endswith( assert p.path.endswith(
"tests/Test-10.15.1.photoslibrary/originals/D/D79B8D77-BFFC-460B-9312-034F2877D35B.jpeg" "tests/Test-10.15.1.photoslibrary/originals/D/D79B8D77-BFFC-460B-9312-034F2877D35B.jpeg"

View File

@@ -215,7 +215,7 @@ def test_attributes():
) )
assert p.description == "Girl holding pumpkin" assert p.description == "Girl holding pumpkin"
assert p.title == "I found one!" assert p.title == "I found one!"
assert p.albums == ["Pumpkin Farm", "Test Album"] assert sorted(p.albums) == ["Pumpkin Farm", "Test Album"]
assert p.persons == ["Katie"] assert p.persons == ["Katie"]
assert p.path.endswith( assert p.path.endswith(
"tests/Test-10.15.4.photoslibrary/originals/D/D79B8D77-BFFC-460B-9312-034F2877D35B.jpeg" "tests/Test-10.15.4.photoslibrary/originals/D/D79B8D77-BFFC-460B-9312-034F2877D35B.jpeg"

View File

@@ -28,6 +28,7 @@ ALBUMS = [
"AlbumInFolder", "AlbumInFolder",
"Raw", "Raw",
"I have a deleted twin", # there's an empty album with same name that has been deleted "I have a deleted twin", # there's an empty album with same name that has been deleted
"EmptyAlbum",
] ]
KEYWORDS_DICT = { KEYWORDS_DICT = {
"Kids": 4, "Kids": 4,
@@ -47,6 +48,7 @@ ALBUM_DICT = {
"AlbumInFolder": 2, "AlbumInFolder": 2,
"Raw": 4, "Raw": 4,
"I have a deleted twin": 1, "I have a deleted twin": 1,
"EmptyAlbum": 0,
} # Note: there are 2 albums named "Test Album" for testing duplicate album names } # Note: there are 2 albums named "Test Album" for testing duplicate album names
UUID_DICT = { UUID_DICT = {
@@ -63,6 +65,7 @@ UUID_DICT = {
"no_external_edit": "E9BC5C36-7CD1-40A1-A72B-8B8FAC227D51", "no_external_edit": "E9BC5C36-7CD1-40A1-A72B-8B8FAC227D51",
"export": "D79B8D77-BFFC-460B-9312-034F2877D35B", # "Pumkins2.jpg" "export": "D79B8D77-BFFC-460B-9312-034F2877D35B", # "Pumkins2.jpg"
"export_tif": "8846E3E6-8AC8-4857-8448-E3D025784410", "export_tif": "8846E3E6-8AC8-4857-8448-E3D025784410",
"in_album": "D79B8D77-BFFC-460B-9312-034F2877D35B", # "Pumkins2.jpg"
} }
UUID_PUMPKIN_FARM = [ UUID_PUMPKIN_FARM = [
@@ -225,7 +228,7 @@ def test_attributes():
) )
assert p.description == "Girl holding pumpkin" assert p.description == "Girl holding pumpkin"
assert p.title == "I found one!" assert p.title == "I found one!"
assert p.albums == ["Pumpkin Farm", "Test Album"] assert sorted(p.albums) == ["Pumpkin Farm", "Test Album"]
assert p.persons == ["Katie"] assert p.persons == ["Katie"]
assert p.path.endswith( assert p.path.endswith(
"tests/Test-10.15.5.photoslibrary/originals/D/D79B8D77-BFFC-460B-9312-034F2877D35B.jpeg" "tests/Test-10.15.5.photoslibrary/originals/D/D79B8D77-BFFC-460B-9312-034F2877D35B.jpeg"
@@ -797,11 +800,29 @@ def test_export_14(caplog):
def test_eq(): def test_eq():
""" Test equality of two PhotoInfo objects """
import osxphotos import osxphotos
photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB) photosdb1 = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
photos1 = photosdb.photos(uuid=[UUID_DICT["export"]]) photosdb2 = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
photos2 = photosdb.photos(uuid=[UUID_DICT["export"]]) photos1 = photosdb1.photos(uuid=[UUID_DICT["export"]])
photos2 = photosdb2.photos(uuid=[UUID_DICT["export"]])
assert photos1[0] == photos2[0]
def test_eq_2():
""" Test equality of two PhotoInfo objects when one has memoized property """
import osxphotos
photosdb1 = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
photosdb2 = osxphotos.PhotosDB(dbfile=PHOTOS_DB)
photos1 = photosdb1.photos(uuid=[UUID_DICT["in_album"]])
photos2 = photosdb2.photos(uuid=[UUID_DICT["in_album"]])
# memoize a value
albums = photos1[0].albums
assert albums
assert photos1[0] == photos2[0] assert photos1[0] == photos2[0]

View File

@@ -261,6 +261,7 @@ ALBUMS_JSON = {
"AlbumInFolder": 2, "AlbumInFolder": 2,
"Test Album": 2, "Test Album": 2,
"I have a deleted twin": 1, "I have a deleted twin": 1,
"EmptyAlbum": 0,
}, },
"shared albums": {}, "shared albums": {},
} }
@@ -354,7 +355,10 @@ def test_query_uuid():
for key_ in json_expected: for key_ in json_expected:
assert key_ in json_got assert key_ in json_got
if key_ != "path": if key_ != "path":
assert json_expected[key_] == json_got[key_] if isinstance(json_expected[key_], list):
assert sorted(json_expected[key_]) == sorted(json_got[key_])
else:
assert json_expected[key_] == json_got[key_]
else: else:
assert json_expected[key_] in json_got[key_] assert json_expected[key_] in json_got[key_]