diff --git a/.gitignore b/.gitignore index aabdbe1..99cf360 100644 --- a/.gitignore +++ b/.gitignore @@ -144,3 +144,4 @@ dmypy.json /photo_thumb.jpg /gen_cc_badge.py /webtest.py +/list.fth diff --git a/config.cfg b/config.cfg index 8af1903..b8da250 100644 --- a/config.cfg +++ b/config.cfg @@ -23,6 +23,12 @@ pickle_file=token.pickle # Empty means all. Default: empty album_names= +# Refresh rate for data from the Google Photos API. +# Possible values: always = every frame refresh, once = once a day. +# This is to prevent a large number of photos from hitting the daily API limit (10k calls). +# Default: always +refresh_rate=always + # Set 1 to get photos from local storage. ; The rest of the values in this section regarding local should be filled as well when enabled. # Default: 1 (enabled) diff --git a/ePiframe.py b/ePiframe.py index 85d78e8..e0bdb44 100755 --- a/ePiframe.py +++ b/ePiframe.py @@ -6,9 +6,16 @@ import shutil import signal import sys +from datetime import datetime +import pandas +import starlette.status +from pandas import DataFrame +from requests import HTTPError + from misc.connection import Connection from misc.constants import Constants from misc.logs import Logs +from misc.tools import Tools from modules.albummanager import AlbumManager from modules.base.pluginbase import PluginBase from modules.configmanager import ConfigManager @@ -104,11 +111,7 @@ def process_flow(self): ) self.logging.log(f"Photo to show:\n{photo}") - self.index_manager.set_id( - self.photo_manager.get_photo_attribute( - photo, Constants.GOOGLE_PHOTOS_ALBUMS_PHOTO_ID_HEADER - ) - ) + self.index_manager.set_id(self.get_photo_id(photo)) self.remove_old_files() self.logging.log("Getting next photo...") @@ -120,18 +123,26 @@ def process_flow(self): returned_value, ) + def get_photo_id(self, photo): + return self.photo_manager.get_photo_attribute( + photo, Constants.GOOGLE_PHOTOS_ALBUMS_PHOTO_ID_HEADER + ) + def get_next_photo(self, photo) -> tuple: returned_value = None if photo[Constants.PHOTOS_SOURCE_LABEL] == Constants.GOOGLE_PHOTOS_SOURCE: filename = self.get_photo(photo) - download_url = self.get_download_url(photo) - returned_value = self.try_download_file(download_url, filename) + returned_value = self.try_download_file( + self.get_download_url(photo), self.get_photo_id(photo), filename + ) else: plugin_with_source = self.get_plugin_with_source(photo) filename = self.get_filename(photo, plugin_with_source, returned_value) return filename, returned_value - def get_filename(self, photo, plugin_with_source: PluginBase, returned_value) -> str: + def get_filename( + self, photo, plugin_with_source: PluginBase, returned_value + ) -> str: if plugin_with_source and self.plugins_manager.plugin_source_get_file( plugin_with_source ): @@ -172,21 +183,42 @@ def get_plugin_with_source(self, photo) -> PluginBase: None, ) - def try_download_file(self, download_url: str, filename: str) -> str: - try: - returned_value = Connection.download_file( - download_url, - self.config.get("photo_convert_path"), - filename, - Constants.OK_STATUS_ERRORCODE, - Constants.CHECK_CONNECTION_TIMEOUT, - ) - except Exception as exception: - returned_value = str(exception) + def try_download_file(self, download_url: str, photo_id: str, filename: str) -> str: + returned_value = self.download_or_retry(download_url, filename, photo_id) + if returned_value != Constants.OK_STATUS_ERRORCODE: self.logging.log(f"Fail! Server error: {str(returned_value)}") return returned_value + def download_or_retry(self, download_url, filename, photo_id): + returned_value = None + count = 2 + while not returned_value: + try: + returned_value = Connection.download_file( + download_url, + self.config.get("photo_convert_path"), + filename, + Constants.OK_STATUS_ERRORCODE, + Constants.CHECK_CONNECTION_TIMEOUT, + ) + except HTTPError as exception: + if self.is_403_exception(exception) and count: + download_url = self.get_download_url( + self.auth_manager.get_item(photo_id) + ) + count -= 1 + continue + returned_value = str(exception) + return returned_value + + @staticmethod + def is_403_exception(exception): + return ( + hasattr(exception, "response") + and exception.response.status_code == starlette.status.HTTP_403_FORBIDDEN + ) + def get_download_url(self, photo) -> str: return ( self.photo_manager.get_photo_attribute( @@ -346,38 +378,73 @@ def exit_if_no_photos(self, total_number: int): self.logging.log("No photos in albums!") sys.exit(1) - def get_from_sources(self): - photos = None + def get_from_sources(self) -> DataFrame: + photos: DataFrame = DataFrame() if bool(self.config.getint("use_google_photos")): - self.logging.log("Getting data from Google Photos source...") - self.logging.log("Checking connection...") - self.check_connection() - self.logging.log("OK!") + photos = self.get_google_photos() + photos = self.get_local_source(photos) + photos = self.get_plugin_sources(photos) + return photos - self.logging.log("Loading credentials...") - self.create_auth_manager() - self.logging.log("Success!") + @staticmethod + def should_data_be_refreshed(filename: str) -> bool: + if not os.path.exists(filename): + return True - self.logging.log("Trying to build service with given credentials...") - self.build_service() + mod_time = Tools.get_last_date(filename) + if not mod_time: + return True - self.logging.log("Success!") - self.logging.log("Getting all albums...") - self.get_albums_data() - self.logging.log("Success!") + return datetime.now().date() > datetime.fromtimestamp(int(mod_time)).date() - self.logging.log("Getting desired album(s)...") - self.album_manager = AlbumManager( - self.auth_manager.get_response(), - self.config.get("album_names"), - Constants.GOOGLE_PHOTOS_ALBUMS_TITLE_HEADER, - ) + def read_stored_photos(self): + photos = DataFrame() + filename: str = self.config.get("photo_list_file") + ".fth" + should_refresh: bool = self.should_data_be_refreshed(filename) + if ( + self.config.get("refresh_rate") == Constants.REFRESH_ONCE + and not self.check_arguments("--refresh") + and not should_refresh + ): + try: + self.logging.log( + "Trying to read saved Google Photos data (according to refresh_rate setting set to " + "'once' a day)..." + ) + photos = pandas.read_feather(filename) + self.logging.log("Success!") + except Exception: + pass - photos = self.get_albums() - photos = self.get_local_source(photos) - photos = self.get_plugin_sources(photos) return photos + def get_google_photos(self) -> DataFrame: + self.logging.log("Getting data from Google Photos source...") + self.logging.log("Checking connection...") + self.check_connection() + self.logging.log("OK!") + self.logging.log("Loading credentials...") + self.create_auth_manager() + self.logging.log("Success!") + self.logging.log("Trying to build service with given credentials...") + self.build_service() + self.logging.log("Success!") + + photos = self.read_stored_photos() + if not photos.empty: + return photos + + self.logging.log("Getting all albums...") + self.get_albums_data() + self.logging.log("Success!") + self.logging.log("Getting desired album(s)...") + self.album_manager = AlbumManager( + self.auth_manager.get_response(), + self.config.get("album_names"), + Constants.GOOGLE_PHOTOS_ALBUMS_TITLE_HEADER, + ) + return self.get_albums() + def process_test_convert(self): if self.check_arguments("--test-convert"): self.test_convert() @@ -445,7 +512,7 @@ def check_all_sources(self): self.logging.log("No photo sources picked! Check the configuration!") raise Exception("No photo sources picked! Check the configuration!") - def get_albums(self): + def get_albums(self) -> DataFrame: if self.album_manager.get_albums().empty: self.logging.log( "Fail! Can't find album {}".format(self.config.get("album_names")) @@ -476,11 +543,11 @@ def get_albums(self): raise return self.get_google_source() - def get_google_source(self): + def get_google_source(self) -> DataFrame: if self.album_manager.get_data().empty: self.logging.log("Fail! Couldn't retrieve albums!") raise - photos = self.photo_manager.set_photos( + photos: DataFrame = self.photo_manager.set_photos( self.album_manager, Constants.GOOGLE_PHOTOS_ALBUMS_MEDIAMETADATA_HEADER, Constants.GOOGLE_PHOTOS_ALBUMS_PHOTO_HEADER, @@ -490,9 +557,11 @@ def get_google_source(self): Constants.GOOGLE_PHOTOS_SOURCE, ) self.logging.log("Success!") + if not photos.empty: + photos.to_feather(self.config.get("photo_list_file") + ".fth") return photos - def get_local_source(self, photos): + def get_local_source(self, photos: DataFrame) -> DataFrame: if bool(self.config.getint("use_local")): self.logging.log("Getting data from local source...") self.local_source_manager = LocalSourceManager( @@ -510,7 +579,7 @@ def get_local_source(self, photos): self.logging.log("Success!") return photos - def get_plugin_sources(self, photos): + def get_plugin_sources(self, photos: DataFrame) -> DataFrame: for plugin in self.plugins_manager.plugin_source(): try: self.logging.log(f"Getting data from plugin '{plugin.name}' source...") @@ -631,8 +700,11 @@ def show_help(self): print("--test-display [file] displays the photo file on attached display") print(" with current ePiframe configuration") print("--test-convert [file] converts the photo file to configured") - print(" photo_convert_filename current ePiframe configuration") + print(" photo_convert_filename with current ePiframe configuration") print("--no-skip like --test but is not skipping to another photo") + print( + "--refresh force Google API data refresh even if refresh_rate flag is set to 'once'" + ) print("--users manage users") print("--help this help") diff --git a/install.sh b/install.sh index fa1b10a..24915f6 100755 --- a/install.sh +++ b/install.sh @@ -46,7 +46,7 @@ function install_pips { echo -e '\n\033[0;30mInstalling Python components\033[0m' declare -A pips=( ["Requests"]="requests>=2.26.0" ["Pillow"]="pillow==9.3.0" ["Telebot"]="pyTelegramBotAPI" ["Dateutil"]="python-dateutil" ["ConfigParser"]="configparser>=5.0.0"\ ["Google components"]="google-api-python-client google-auth-httplib2 google-auth-oauthlib"\ - ["SPI Libs"]="spidev==3.5" ["Pandas"]="pandas==1.2.0 numpy==1.20" ["Flask"]="flask<2.2.0" ["Flask-WTF"]="flask-wtf==1.0.0" \ + ["SPI Libs"]="spidev==3.5" ["Pandas"]="pandas==1.2.0 numpy==1.20 pyarrow" ["Flask"]="flask<2.2.0" ["Flask-WTF"]="flask-wtf==1.0.0" \ ["Flask-Login"]="flask-login==0.5.0" ["WTForms"]="wtforms>=3.0.0" ["SMBus"]="smbus2") declare -a order; order+=( "Requests" ) diff --git a/misc/config.default b/misc/config.default index 8af1903..b8da250 100644 --- a/misc/config.default +++ b/misc/config.default @@ -23,6 +23,12 @@ pickle_file=token.pickle # Empty means all. Default: empty album_names= +# Refresh rate for data from the Google Photos API. +# Possible values: always = every frame refresh, once = once a day. +# This is to prevent a large number of photos from hitting the daily API limit (10k calls). +# Default: always +refresh_rate=always + # Set 1 to get photos from local storage. ; The rest of the values in this section regarding local should be filled as well when enabled. # Default: 1 (enabled) diff --git a/misc/connection.py b/misc/connection.py index 1a27e4d..1cc4245 100644 --- a/misc/connection.py +++ b/misc/connection.py @@ -1,7 +1,6 @@ import os import requests import re -import socket class Connection: diff --git a/misc/constants.py b/misc/constants.py index 81e1671..875a65d 100644 --- a/misc/constants.py +++ b/misc/constants.py @@ -3,7 +3,7 @@ class Constants: - EPIFRAME_VERSION = "v1.9.3" + EPIFRAME_VERSION = "v1.9.4" EPIFRAME_SECRET = "ePiframeSecretlyLovesYourPhotos" # minimal needed python version @@ -39,6 +39,7 @@ class Constants: GOOGLE_PHOTOS_ALBUMS_PHOTO_DESCRIPTION_HEADER = "description" GOOGLE_PHOTOS_ALBUMS_PHOTO_BASEURL_HEADER = "baseUrl" GOOGLE_PHOTOS_ALBUMS_PHOTO_ID_HEADER = "id" + GOOGLE_PHOTOS_ALBUMS_MEDIA_ITEM_ID_HEADER = "mediaItemId" GOOGLE_PHOTOS_ALBUMS_PHOTO_HEIGHT_HEADER = "height" GOOGLE_PHOTOS_ALBUMS_PHOTO_WIDTH_HEADER = "width" GOOGLE_PHOTOS_ALBUMS_PHOTO_GET_DETAILS = "=d" @@ -146,3 +147,6 @@ class Constants: BACK_BLACK = "black" BACK_PHOTO = "photo" BACK_CROP = "crop" + + REFRESH_ALWAYS = "always" + REFRESH_ONCE = "once" diff --git a/misc/pimoronidisplay.py b/misc/pimoronidisplay.py index 9d55817..948eec4 100644 --- a/misc/pimoronidisplay.py +++ b/misc/pimoronidisplay.py @@ -65,7 +65,7 @@ def __init_inky_what(self, color_schema: str, module: ModuleType): ) self.__palette_filter = self.__get_palette(module) - def __init_inky_uc(self, color_schema: str, module: ModuleType): + def __init_inky_uc(self, _color_schema: str, module: ModuleType): self.__inky = module.Inky() self.__palette_filter = module.DESATURATED_PALETTE diff --git a/misc/tools.py b/misc/tools.py index d67d9d9..588b9e3 100644 --- a/misc/tools.py +++ b/misc/tools.py @@ -1,4 +1,5 @@ import itertools +import os from typing import Any @@ -9,7 +10,9 @@ def check_if_list(key: str, value: str) -> Any: @staticmethod def get_type_to_extension(extensions: dict) -> dict: - return dict(Tools.check_if_list(key, value) for key, value in extensions.items()) + return dict( + Tools.check_if_list(key, value) for key, value in extensions.items() + ) @staticmethod def get_product(key: str, value: str): @@ -39,3 +42,12 @@ def get_extensions(extensions: list) -> list: [Tools.get_extension(extension) for extension in list(extensions)], [], ) + + @staticmethod + def get_last_date(file: str) -> float: + return_value = None + try: + return_value = os.stat(file).st_mtime + except Exception: + pass + return return_value diff --git a/modules/backendmanager.py b/modules/backendmanager.py index e414109..d93f76a 100644 --- a/modules/backendmanager.py +++ b/modules/backendmanager.py @@ -9,6 +9,7 @@ import modules.timermanager as timer_manager from misc.constants import Constants from misc.logs import Logs +from misc.tools import Tools from modules.displaymanager import DisplayManager from modules.weathermanager import WeatherManager @@ -65,12 +66,7 @@ def __init__(self, event, path: str): ) def get_last_date(self, file: str) -> float: - return_value = None - try: - return_value = os.stat(os.path.join(self.__path, file)).st_mtime - except Exception: - pass - return return_value + return Tools.get_last_date(os.path.join(self.__path, file)) def get_path(self) -> str: return self.__path diff --git a/modules/configmanager.py b/modules/configmanager.py index 3a43eac..b612cc1 100644 --- a/modules/configmanager.py +++ b/modules/configmanager.py @@ -8,6 +8,7 @@ from modules.displaymanager import DisplayManager from modules.filteringmanager import FilteringManager from modules.localsourcemanager import LocalSourceManager +from modules.photomanager import PhotoManager from modules.telebotmanager import TelebotManager from modules.timermanager import TimerManager from modules.weathermanager import WeatherManager @@ -38,6 +39,12 @@ def load_settings(self): ConfigProperty( "album_names", self, not_empty=False, dependency="use_google_photos" ), + ConfigProperty( + "refresh_rate", + self, + possible=PhotoManager.get_refresh_rates(), + dependency="use_google_photos", + ), ConfigProperty("use_local", self, prop_type=ConfigProperty.BOOLEAN_TYPE), ConfigProperty( "local_path", diff --git a/modules/indexmanager.py b/modules/indexmanager.py index bece41f..76caa69 100644 --- a/modules/indexmanager.py +++ b/modules/indexmanager.py @@ -1,4 +1,6 @@ import os +import json +from json import JSONDecodeError class IndexManager: @@ -12,19 +14,23 @@ def __init__(self, path: str): # read index from file to change after each run if os.path.exists(path): - with open(path, "r") as file_lines: - lines = file_lines.readlines() - if len(lines) == 2: - self.__id = str(lines[0]).rstrip() - self.__index = int(lines[1]) - file_lines.close() + try: + with open(path, "r") as index_file: + index_data = json.load(index_file) + self.__index = index_data.get("index") + self.__id = index_data.get("id").rstrip() + except JSONDecodeError: + # legacy + with open(path, "r") as file_lines: + lines = file_lines.readlines() + if len(lines) == 2: + self.__id = str(lines[0]).rstrip() + self.__index = int(lines[1]) + file_lines.close() def save(self): with open(self.__path, "w") as file_data: - file_data.write(str(self.__id)) - file_data.write("\n") - file_data.write(str(self.__index)) - file_data.close() + json.dump({"id": self.__id, "index": self.__index}, file_data) def get_index(self) -> int: return self.__index diff --git a/modules/oauthmanager.py b/modules/oauthmanager.py index 2c15604..c865fc6 100644 --- a/modules/oauthmanager.py +++ b/modules/oauthmanager.py @@ -109,6 +109,12 @@ def get_items( return items + def get_item( + self, + identifier: str, + ) -> list: + return self.__service.mediaItems().get(mediaItemId=identifier).execute() + def get_service(self): return self.__service diff --git a/modules/photomanager.py b/modules/photomanager.py index 44bd87d..2df1ac5 100644 --- a/modules/photomanager.py +++ b/modules/photomanager.py @@ -1,4 +1,5 @@ import pandas as pd +from misc.constants import Constants class PhotoManager: @@ -71,3 +72,7 @@ def append_photos(self, target_dataframe, source_dataframe): target_dataframe = pd.DataFrame() merged = target_dataframe.append([source_dataframe]) return self.reset_index(merged) + + @staticmethod + def get_refresh_rates(): + return [Constants.REFRESH_ALWAYS, Constants.REFRESH_ONCE] diff --git a/modules/pidmanager.py b/modules/pidmanager.py index fa0697b..12648c0 100644 --- a/modules/pidmanager.py +++ b/modules/pidmanager.py @@ -19,7 +19,7 @@ def read(self) -> int: if os.path.exists(self.__path): with open(self.__path, "r") as file_data: lines = file_data.readlines() - pid = str(lines[0]).rstrip() + pid = str("".join(lines)).rstrip() file_data.close() except Exception: self.remove() diff --git a/modules/timermanager.py b/modules/timermanager.py index 918ea22..2e0b51a 100644 --- a/modules/timermanager.py +++ b/modules/timermanager.py @@ -45,11 +45,11 @@ def __get_value( def __check_no_time_mark( self, - now: datetime, + _now: datetime, start_tab: str, - end_tab: str, - day_of_week: int, - yesterday: int, + _end_tab: str, + _day_of_week: int, + _yesterday: int, ) -> bool: return False if start_tab == self.__NO_TIME_MARK else None @@ -57,8 +57,8 @@ def __check_yesterday( self, now: datetime, start_tab: str, - end_tab: str, - day_of_week: int, + _end_tab: str, + _day_of_week: int, yesterday: int, ) -> bool: return ( @@ -76,7 +76,7 @@ def __check_day_of_week( start_tab: str, end_tab: str, day_of_week: int, - yesterday: int, + _yesterday: int, ) -> bool: return ( ( @@ -95,9 +95,9 @@ def __check_now( self, now: datetime, start_tab: str, - end_tab: str, + _end_tab: str, day_of_week: int, - yesterday: int, + _yesterday: int, ) -> bool: return ( True diff --git a/modules/usersmanager.py b/modules/usersmanager.py index 3078a36..fb2f607 100644 --- a/modules/usersmanager.py +++ b/modules/usersmanager.py @@ -268,18 +268,18 @@ def __show_help(self): print("6. Exit") print(len(title) * "-") - def _test_password(self, log: Logs, valid: dict): + def _test_password(self, _log: Logs, _valid: dict): username = self.__user_check() self.__password(username, "Password: ") self.__result("YOU HAVE LOGGED IN") - def _show_api_key(self, log: Logs, valid: dict): + def _show_api_key(self, _log: Logs, _valid: dict): username = self.__user_check() self.__password(username, "Password: ") user_obj = self.get_by_username(username)[0] self.__result(f"USER {username} API KEY: {user_obj.api}", False) - def _change_password(self, log: Logs, valid: dict): + def _change_password(self, log: Logs, _valid: dict): username = self.__user_check() current_password = self.__password(username, "Current password: ") new_password = self.__new_password( @@ -307,7 +307,7 @@ def _delete_user(self, log: Logs, valid: dict): else: print("Please respond with 'yes' or 'no' (or 'y' or 'n')") - def _add_user(self, log: Logs, valid: dict): + def _add_user(self, log: Logs, _valid: dict): username = self.__user_check(False) password = self.__new_password( "Password [empty possible]: ", "Confirm password [empty possible]: " diff --git a/modules/webuimanager.py b/modules/webuimanager.py index c4f445e..9d7062a 100644 --- a/modules/webuimanager.py +++ b/modules/webuimanager.py @@ -80,7 +80,9 @@ class WebUIManager: __ORDER_LABEL = "Execution Order" class SiteBind: - def __init__(self, url: str, func, methods: List[str] = ["GET"], defaults=None): + def __init__(self, url: str, func, methods: List[str] = None, defaults=None): + if not methods: + methods = ["GET"] self.url = url self.func = func self.methods = methods @@ -292,12 +294,12 @@ def load_user(self, username: str) -> Optional[User]: return return_value - def load_user_from_request(self, request) -> Optional[User]: + def load_user_from_request(self, request_data) -> Optional[User]: result = None - api_key = request.args.get("api_key") + api_key = request_data.args.get("api_key") if not api_key: - api_key = request.headers.get("Authorization") + api_key = request_data.headers.get("Authorization") if api_key: api_key = self.__get_api_from_header(api_key) @@ -634,7 +636,7 @@ def __get_bool_field( ) def __get_list_field( - self, config: config_manager.ConfigManager, prop, render: dict, property_name + self, config: config_manager.ConfigManager, _prop, render: dict, property_name ): return StringField( self.adapt_name(config, property_name), @@ -644,7 +646,7 @@ def __get_list_field( ) def __get_password_field( - self, config: config_manager.ConfigManager, prop, render: dict, property_name + self, config: config_manager.ConfigManager, _prop, render: dict, property_name ): return StringField( self.adapt_name(config, property_name), diff --git a/requirements.txt b/requirements.txt index d435655..d4e7c01 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,6 @@ requests==2.28.1 python-dateutil==2.8.2 configparser==5.3.0 pandas==1.2.0 -#numpy==1.20 spidev==3.5 pillow==9.3.0 pyTelegramBotAPI==4.1.1 @@ -11,6 +10,7 @@ werkzeug==2.0.3 flask-wtf==1.0.0 flask-login==0.5.0 wtforms==3.0.1 +pyarrow==14.0.2 pytest starlette google-api-python-client diff --git a/tests/helpers/oauth_service.py b/tests/helpers/oauth_service.py index 933f796..1366090 100644 --- a/tests/helpers/oauth_service.py +++ b/tests/helpers/oauth_service.py @@ -33,6 +33,11 @@ def search(cls, body): print(f"Search: {body=}") return cls + @classmethod + def get(cls, mediaItemId): + print(f"Get: {mediaItemId=}") + return cls + @classmethod def execute(cls): if cls.data: diff --git a/tests/test_indexmanager.py b/tests/test_indexmanager.py index a89c7e0..991e868 100644 --- a/tests/test_indexmanager.py +++ b/tests/test_indexmanager.py @@ -47,3 +47,15 @@ def test_index_manager_max(): assert index_manager.get_index() == index_test remove_file(index_filename) + + +def test_legacy_index_manager_init(): + remove_file(index_filename) + with open(index_filename, "w") as file_data: + file_data.write(str(id_test)) + file_data.write("\n") + file_data.write(str(index_test)) + index_manager = IndexManager(index_filename) + assert index_manager.get_index() == index_test + assert index_manager.get_id() == id_test + remove_file(index_filename) diff --git a/tests/test_oauthmanager.py b/tests/test_oauthmanager.py index 82fdcd2..8bcf010 100644 --- a/tests/test_oauthmanager.py +++ b/tests/test_oauthmanager.py @@ -79,6 +79,26 @@ def test_get_items(): assert items == ["media_items_data"] +def test_get_item(): + manager = get_manager() + OauthService.set_data( + { + Constants.GOOGLE_PHOTOS_ALBUMS_MEDIAITEMS_HEADER: { + "media_item_data": "data" + }, + } + ) + with Capturing() as output: + with not_raises(Exception): + items = manager.get_item("photo_id") + assert output == [ + "MediaItems", + "Get: mediaItemId='photo_id'", + ] + + assert items == {'mediaItems': {'media_item_data': 'data'}} + + def test_create_credentials(): remove_file(pickle_file) manager = get_manager_flow() diff --git a/tests/test_photomanager.py b/tests/test_photomanager.py index 040ad55..7e974fa 100644 --- a/tests/test_photomanager.py +++ b/tests/test_photomanager.py @@ -21,6 +21,10 @@ album: AlbumManager +def test_get_frame_rates(): + assert PhotoManager.get_refresh_rates() == ["always", "once"] + + def test_set_photos(): global album album = AlbumManager(albums, names, header)