diff --git a/README.md b/README.md index 758eeb82..d71c9317 100644 --- a/README.md +++ b/README.md @@ -151,12 +151,18 @@ Returns list of Photos libraries found on the system. **Note**: On MacOS 10.15, #### Open the default Photos library ```python -osxphotos.PhotosDB([dbfile="path to database file"]) +osxphotos.PhotosDB() +osxphotos.PhotosDB(path) +osxphotos.PhotosDB(dbfile=path) ``` -Opens the Photos library database and returns a PhotosDB object. Optionally, pass the path to a specific database file. If `dbfile` is not included, will open the default (last opened) Photos database. +Opens the Photos library database and returns a PhotosDB object. -**Note**: this will open the last library that was opened in Photos. This is not necessarily the System Photos Library. If you have more than one Photos library, you can select which to open by holding down Option key while opening Photos. +Optionally, pass the path to a specific database file or a Photos library (e.g. "/Users/smith/Pictures/Photos Library.photoslibrary" or "/Users/smith/Pictures/Photos Library.photoslibrary/database/photos.db"). Path to photos library may be passed **either** as first argument **or** as named argument `dbfile`. If path is not passed, PhotosDB will attempt to open the default Photos library (that is, the last library that was opened in Photos.app which may or may not also be the System Photos Library). **Note**: Users may specify a different library to open by holding down the *option* key while opening Photos.app. + +If an invalid path is passed, PhotosDB will raise `ValueError` exception. + +Open the default (last opened) Photos library. (E.g. this is the library that would open if the user opened Photos.app) ```python import osxphotos @@ -164,7 +170,25 @@ import osxphotos photosdb = osxphotos.PhotosDB() ``` -Returns a PhotosDB object. +#### Open System Photos library + +In Photos 5 (Catalina / MacOS 10.15), you can use `get_system_library_path()` to get the path to the System photo library if you want to ensure PhotosDB opens the system library. This does not work on older versions of MacOS. E.g. + +```python +import osxphotos + +path = osxphotos.get_system_library_path() +photosdb = osxphotos.PhotosDB(path) +``` + +also, + +```python +import osxphotos + +path = osxphotos.get_system_library_path() +photosdb = osxphotos.PhotosDB(dbfile=path) +``` #### Open a specific Photos library ```python @@ -173,7 +197,17 @@ import osxphotos photosdb = osxphotos.PhotosDB(dbfile="/Users/smith/Pictures/Test.photoslibrary/database/photos.db") ``` -Pass the fully qualified path to the specific Photos database you want to open. The database is called photos.db and resides in the database folder in your Photos library +or + +```python +import osxphotos + +photosdb = osxphotos.PhotosDB("/Users/smith/Pictures/Test.photoslibrary") +``` + +Pass the fully qualified path to the Photos library or the actual database file inside the library. The database is called photos.db and resides in the database folder in your Photos library. If you pass only the path to the library, PhotosDB will add the database path automatically. The option to pass the actual database path is provided so database files can be queried even if separated from the actual .photoslibrary file. + +Returns a PhotosDB object. #### ```keywords``` ```python diff --git a/osxphotos/__init__.py b/osxphotos/__init__.py index 13197aab..f160ee95 100644 --- a/osxphotos/__init__.py +++ b/osxphotos/__init__.py @@ -198,10 +198,11 @@ def list_photo_libraries(): # On 10.15, mdfind appears to find all libraries # On older MacOS versions, mdfind appears to ignore some libraries # glob to find libraries in ~/Pictures then mdfind to find all the others + # TODO: make this more robust lib_list = glob.glob(f"{str(Path.home())}/Pictures/*.photoslibrary") # On older OS, may not get all libraries so make sure we get the last one - last_lib = get_last_library_path() + last_lib = get_last_library_path() if last_lib: lib_list.append(last_lib) @@ -216,19 +217,20 @@ def list_photo_libraries(): class PhotosDB: - def __init__(self, dbfile=None): + def __init__(self, *args, dbfile=None): """ create a new PhotosDB object """ - """ optional: dbfile=path to photos.db from the Photos library """ + """ path to photos library or database may be specified EITHER as first argument or as named argument dbfile=path """ + """ optional: specify full path to photos library or photos.db as first argument """ + """ optional: specify path to photos library or photos.db using named argument dbfile=path """ # Check OS version system = platform.system() (_, major, _) = _get_os_version() if system != "Darwin" or (major not in _TESTED_OS_VERSIONS): - print( + logging.warning( f"WARNING: This module has only been tested with MacOS 10." f"[{', '.join(_TESTED_OS_VERSIONS)}]: " - f"you have {system}, OS version: {major}", - file=sys.stderr, + f"you have {system}, OS version: {major}" ) # configure AppleScripts used to manipulate Photos @@ -236,6 +238,8 @@ class PhotosDB: # set up the data structures used to store all the Photo database info + # Path to the Photos library database file + self._dbfile = None # Dict with information about all photos by uuid self._dbphotos = {} # Dict with information about all persons/photos by uuid @@ -259,13 +263,40 @@ class PhotosDB: self._tmp_files = [] logging.debug(f"dbfile = {dbfile}") - if dbfile is None: + + # get the path to photos library database + if args: + # got a library path as argument + if dbfile: + # shouldn't pass via both *args and dbfile= + raise ValueError( + f"photos database path must be specified as argument or named parameter dbfile but not both: args: {args}, dbfile: {dbfile}", + args, + dbfile, + ) + elif len(args) == 1: + dbfile = args[0] + else: + raise ValueError( + f"__init__ takes only a single argument (photos database path): {args}", + args, + ) + elif dbfile is None: + # no args and dbfile not passed, try to get last opened library library_path = get_last_library_path() - # TODO: verify library path not None + if not library_path: + raise ValueError("could not get library path") dbfile = os.path.join(library_path, "database/photos.db") + if os.path.isdir(dbfile): + # passed a directory, assume it's a photoslibrary + dbfile = os.path.join(dbfile, "database/photos.db") + + # if get here, should have a dbfile path; make sure it exists if not _check_file_exists(dbfile): - sys.exit(f"_dbfile {dbfile} does not exist") + raise ValueError(f"dbfile {dbfile} does not exist", dbfile) + + logging.debug(f"dbfile = {dbfile}") self._dbfile = dbfile diff --git a/osxphotos/_version.py b/osxphotos/_version.py index ddc11e6e..e8d64aa3 100644 --- a/osxphotos/_version.py +++ b/osxphotos/_version.py @@ -1,4 +1,4 @@ """ version info """ -__version__ = "0.14.20" +__version__ = "0.14.21" diff --git a/tests/test_catalina_10_15_1.py b/tests/test_catalina_10_15_1.py index 7a99b51d..18ac7245 100644 --- a/tests/test_catalina_10_15_1.py +++ b/tests/test_catalina_10_15_1.py @@ -44,12 +44,62 @@ ALBUM_DICT = { def test_init(): + # test named argument import osxphotos photosdb = osxphotos.PhotosDB(dbfile=PHOTOS_DB) assert isinstance(photosdb, osxphotos.PhotosDB) +def test_init2(): + # test positional argument + import osxphotos + + photosdb = osxphotos.PhotosDB(PHOTOS_DB) + assert isinstance(photosdb, osxphotos.PhotosDB) + + +def test_init3(): + # test positional and named argument (raises exception) + import osxphotos + + with pytest.raises(Exception): + assert osxphotos.PhotosDB(PHOTOS_DB, dbfile=PHOTOS_DB) + + +def test_init4(): + # test invalid db + import os + import tempfile + import osxphotos + + bad_db = tempfile.mkstemp(suffix=".db", prefix="osxphotos-") + + with pytest.raises(Exception): + assert osxphotos.PhotosDB(bad_db) + + with pytest.raises(Exception): + assert osxphotos.PhotosDB(dbfile=bad_db) + + try: + os.remove(bad_db) + except: + pass + + +def test_init5(): + # test failed get_last_library_path + import osxphotos + + def bad_library(): + return None + + osxphotos.get_last_library_path = bad_library + + with pytest.raises(Exception): + assert osxphotos.PhotosDB() + + def test_db_version(): import osxphotos