From ad6414b99a8d78423505f59904e728f583725190 Mon Sep 17 00:00:00 2001 From: Salvoxia <Salvoxia@blindfish.info> Date: Sun, 18 Aug 2024 10:25:19 +0200 Subject: [PATCH] Chore: Added Docstring to mehtods --- immich_auto_album.py | 171 ++++++++++++++++++++++++++++++++++--------- 1 file changed, 135 insertions(+), 36 deletions(-) diff --git a/immich_auto_album.py b/immich_auto_album.py index 48c1de8..f9b28de 100644 --- a/immich_auto_album.py +++ b/immich_auto_album.py @@ -1,4 +1,5 @@ +from typing import Tuple import requests import argparse import logging @@ -148,19 +149,17 @@ requests_kwargs = { 'verify' : not insecure } -# Yield successive n-sized -# chunks from l. -def divide_chunks(l, n): - +def divide_chunks(l: list, n: int): + """Yield successive n-sized chunks from l. """ # looping till length l for i in range(0, len(l), n): yield l[i:i + n] -def parseSeparatedString(s: str, seprator: str): +def parseSeparatedString(s: str, seprator: str) -> Tuple[str, str]: """ Parse a key, value pair, separated by the provided separator. + That's the reverse of ShellArgs. - On the command line (argparse) a declaration will typically look like: foo=hello or @@ -186,9 +185,15 @@ def parseSeparatedStrings(items: list[str]) -> dict: d[key] = value return d -# Create album names from provided path_chunks string array -# based on supplied album_levels argument (either by level range or absolute album levels) -def create_album_name(path_chunks): +def create_album_name(path_chunks: list[str], album_separator: str) -> str: + """ + Create album names from provided path_chunks string array. + + The method uses global variables album_levels_range_arr or album_levels to + generate ablum names either by level range or absolute album levels. If multiple + album path chunks are used for album names they are separated by album_separator. + """ + album_name_chunks = () logging.debug("path chunks = %s", list(path_chunks)) # Check which path to take: album_levels_range or album_levels @@ -229,11 +234,22 @@ def create_album_name(path_chunks): if album_name_chunk_size < 0: album_name_chunks = path_chunks[album_name_chunk_size:] logging.debug("album_name_chunks = %s", album_name_chunks) - return album_level_separator.join(album_name_chunks) + return album_separator.join(album_name_chunks) -# Fetches assets from the Immich API -# Takes different API versions into account for compatibility -def fetchServerVersion(): +def fetchServerVersion() -> dict: + """ + Fetches the API version from the immich server. + + If the API endpoint does not yet exist, returns the latest version + before that API endpoint was introduced: 1.105.1 + + Returns + ------- + Dictionary with keys + - major + - minor + - patch + """ # This API call was only introduced with version 1.106.1, so it will fail # for older versions. # Initialize the version with the latest version without this API call @@ -247,17 +263,28 @@ def fetchServerVersion(): logging.info("Detected Immich server version %s.%s.%s or older", version['major'], version['minor'], version['patch']) return version -# Fetches assets from the Immich API -# Uses the /search/meta-data call. Much more efficient than the legacy method -# since this call allows to filter for assets that are not in an album only. -def fetchAssets(): + +def fetchAssets(isNotInAlbum: bool) -> list: + """ + Fetches assets from the Immich API. + + Uses the /search/meta-data call. Much more efficient than the legacy method + since this call allows to filter for assets that are not in an album only. + + Parameters + ---------- + isNotInAlbum : bool + Flag indicating whether to fetch only assets that are not part + of an album or not. + Returns + --------- + An array of asset objects + """ + assets = [] # prepare request body body = {} - # only request images that are not in any album if we are running in CREATE mode, - # otherwise we need all images, even if they are part of an album - if mode == SCRIPT_MODE_CREATE: - body['isNotInAlbum'] = 'true' + body['isNotInAlbum'] = isNotInAlbum # This API call allows a maximum page size of 1000 number_of_assets_to_fetch_per_request_search = min(1000, number_of_assets_to_fetch_per_request) @@ -285,17 +312,32 @@ def fetchAssets(): return assets -# Fetches albums from the Immich API def fetchAlbums(): + """Fetches albums from the Immich API""" + apiEndpoint = 'albums' r = requests.get(root_url+apiEndpoint, **requests_kwargs) r.raise_for_status() return r.json() -# Deletes an album identified by album['id'] -# Returns False if the album could not be deleted, otherwise True -def deleteAlbum(album): +def deleteAlbum(album: dict): + """ + Deletes an album identified by album['id'] + + If the album could not be deleted, logs an error. + + Parameters + ---------- + album : dict + Dictionary with the following keys: + - id + - albumName + + Returns + --------- + True if the album was deleted, otherwise False + """ apiEndpoint = 'albums' logging.debug("Album ID = %s, Album Name = %s", album['id'], album['albumName']) @@ -305,9 +347,26 @@ def deleteAlbum(album): return False return True -# Creates an album with the provided name and returns the ID of the -# created album -def createAlbum(albumName): + +def createAlbum(albumName: str) -> str: + """ + Creates an album with the provided name and returns the ID of the created album + + + Parameters + ---------- + albumName : str + Name of the album to create + + Returns + --------- + True if the album was deleted, otherwise False + + Raises + ---------- + Exception if the API call failed + """ + apiEndpoint = 'albums' data = { @@ -318,8 +377,23 @@ def createAlbum(albumName): assert r.status_code in [200, 201] return r.json()['id'] -# Adds the provided assetIds to the provided albumId -def addAssetsToAlbum(albumId, assets): + +def addAssetsToAlbum(albumId: str, assets: list[str]): + """ + Adds the assets IDs provided in assets to the provided albumId. + + If assets if larger than number_of_images_per_request, the list is chunked + and one API call is performed per chunk. + Only logs errors and successes. + + + Parameters + ---------- + albumId : str + The ID of the album to add assets to + assets: list[str] + A list of asset IDs to add to the album + """ apiEndpoint = 'albums' # Divide our assets into chunks of number_of_images_per_request, @@ -346,19 +420,39 @@ def addAssetsToAlbum(albumId, assets): if cpt > 0: logging.info("%d new assets added to %s", cpt, album) -# Queries and returns all users def fetchUsers(): + """Queries and returns all users""" + apiEndpoint = 'users' r = requests.get(root_url+apiEndpoint, **requests_kwargs) assert r.status_code in [200, 201] return r.json() -# Shares the album with the provided album_id with all provided share_user_ids -# using share_role as a role. -def shareAlbumWithUserAndRole(album_id, share_user_ids, share_role): +def shareAlbumWithUserAndRole(album_id: str, share_user_ids: list[str], share_role: str): + """ + Shares the album with the provided album_id with all provided share_user_ids + using share_role as a role. + + Parameters + ---------- + album_id : str + The ID of the album to share + share_user_ids: list[str] + IDs of users to share the album with + share_role: str + The share role to use when sharing the album, valid values are + "viewer" or "editor" + Raises + ---------- + Exception if share_role contains an invalid value + Exception if the API call failed + """ + apiEndpoint = 'albums/'+album_id+'/users' + assert share_role in SHARE_ROLES + # build payload album_users = [] for share_user_id in share_user_ids: @@ -414,7 +508,12 @@ if mode == SCRIPT_MODE_DELETE_ALL: exit(0) logging.info("Requesting all assets") -assets = fetchAssets() +# only request images that are not in any album if we are running in CREATE mode, +# otherwise we need all images, even if they are part of an album +if mode == SCRIPT_MODE_CREATE: + assets = fetchAssets(True) +else: + assets = fetchAssets(False) logging.info("%d photos found", len(assets)) @@ -445,7 +544,7 @@ for asset in assets: # remove last item from path chunks, which is the file name del path_chunks[-1] - album_name = create_album_name(path_chunks) + album_name = create_album_name(path_chunks, album_level_separator) if len(album_name) > 0: album_to_assets[album_name].append(asset['id']) else: