#341 added validate_builders

This commit is contained in:
meisnate12 2021-07-30 15:19:43 -04:00
parent 711ae08372
commit 91e525e43d
4 changed files with 163 additions and 204 deletions

View file

@ -73,7 +73,7 @@ poster_details = ["url_poster", "tmdb_poster", "tmdb_profile", "tvdb_poster", "f
background_details = ["url_background", "tmdb_background", "tvdb_background", "file_background"] background_details = ["url_background", "tmdb_background", "tvdb_background", "file_background"]
boolean_details = ["visible_library", "visible_home", "visible_shared", "show_filtered", "show_missing", "save_missing", "item_assets"] boolean_details = ["visible_library", "visible_home", "visible_shared", "show_filtered", "show_missing", "save_missing", "item_assets"]
string_details = ["sort_title", "content_rating", "name_mapping"] string_details = ["sort_title", "content_rating", "name_mapping"]
ignored_details = ["smart_filter", "smart_label", "smart_url", "run_again", "schedule", "sync_mode", "template", "test", "tmdb_person", "build_collection", "collection_order"] ignored_details = ["smart_filter", "smart_label", "smart_url", "run_again", "schedule", "sync_mode", "template", "test", "tmdb_person", "build_collection", "collection_order", "validate_builders"]
details = ["collection_mode", "collection_order", "label"] + boolean_details + string_details details = ["collection_mode", "collection_order", "label"] + boolean_details + string_details
collectionless_details = ["collection_order", "plex_collectionless", "label", "label_sync_mode", "test"] + \ collectionless_details = ["collection_order", "plex_collectionless", "label", "label_sync_mode", "test"] + \
poster_details + background_details + summary_details + string_details poster_details + background_details + summary_details + string_details
@ -350,18 +350,28 @@ class CollectionBuilder:
if skip_collection: if skip_collection:
raise Failed(f"{self.schedule}\n\nCollection {self.name} not scheduled to run") raise Failed(f"{self.schedule}\n\nCollection {self.name} not scheduled to run")
self.run_again = "run_again" in methods
self.collectionless = "plex_collectionless" in methods self.collectionless = "plex_collectionless" in methods
self.validate_builders = True
if "validate_builders" in methods:
logger.info("")
logger.info("Validating Method: validate_builders")
logger.info(f"Value: {data[methods['validate_builders']]}")
self.validate_builders = util.parse("validate_builders", self.data, datatype="bool", methods=methods, default=True)
self.run_again = False self.run_again = False
if "run_again" in methods: if "run_again" in methods:
logger.info("") logger.info("")
logger.info("Validating Method: run_again") logger.info("Validating Method: run_again")
if not self.data[methods["run_again"]]: logger.info(f"Value: {data[methods['run_again']]}")
logger.warning(f"Collection Warning: run_again attribute is blank defaulting to false") self.run_again = util.parse("run_again", self.data, datatype="bool", methods=methods, default=False)
else:
logger.debug(f"Value: {self.data[methods['run_again']]}") self.build_collection = True
self.run_again = util.parse_bool("run_again", self.data[methods["run_again"]]) if "build_collection" in methods:
logger.info("")
logger.info("Validating Method: build_collection")
logger.info(f"Value: {data[methods['build_collection']]}")
self.build_collection = util.parse("build_collection", self.data, datatype="bool", methods=methods, default=True)
self.sync = self.library.sync_mode == "sync" self.sync = self.library.sync_mode == "sync"
if "sync_mode" in methods: if "sync_mode" in methods:
@ -376,16 +386,6 @@ class CollectionBuilder:
else: else:
self.sync = self.data[methods["sync_mode"]].lower() == "sync" self.sync = self.data[methods["sync_mode"]].lower() == "sync"
self.build_collection = True
if "build_collection" in methods:
logger.info("")
logger.info("Validating Method: build_collection")
if self.data[methods["build_collection"]] is None:
logger.warning(f"Collection Warning: build_collection attribute is blank defaulting to true")
else:
logger.debug(f"Value: {self.data[methods['build_collection']]}")
self.build_collection = util.parse_bool("build_collection", self.data[methods["build_collection"]])
self.custom_sort = False self.custom_sort = False
if "collection_order" in methods: if "collection_order" in methods:
logger.info("") logger.info("")
@ -477,40 +477,46 @@ class CollectionBuilder:
logger.debug("") logger.debug("")
logger.debug(f"Validating Method: {method_key}") logger.debug(f"Validating Method: {method_key}")
logger.debug(f"Value: {method_data}") logger.debug(f"Value: {method_data}")
if method_data is None and method_name in all_builders + plex.searches: raise Failed(f"Collection Error: {method_final} attribute is blank") try:
elif method_data is None: logger.warning(f"Collection Warning: {method_final} attribute is blank") if method_data is None and method_name in all_builders + plex.searches: raise Failed(f"Collection Error: {method_final} attribute is blank")
elif not self.config.Trakt and "trakt" in method_name: raise Failed(f"Collection Error: {method_final} requires Trakt to be configured") elif method_data is None: logger.warning(f"Collection Warning: {method_final} attribute is blank")
elif not self.library.Radarr and "radarr" in method_name: raise Failed(f"Collection Error: {method_final} requires Radarr to be configured") elif not self.config.Trakt and "trakt" in method_name: raise Failed(f"Collection Error: {method_final} requires Trakt to be configured")
elif not self.library.Sonarr and "sonarr" in method_name: raise Failed(f"Collection Error: {method_final} requires Sonarr to be configured") elif not self.library.Radarr and "radarr" in method_name: raise Failed(f"Collection Error: {method_final} requires Radarr to be configured")
elif not self.library.Tautulli and "tautulli" in method_name: raise Failed(f"Collection Error: {method_final} requires Tautulli to be configured") elif not self.library.Sonarr and "sonarr" in method_name: raise Failed(f"Collection Error: {method_final} requires Sonarr to be configured")
elif not self.config.MyAnimeList and "mal" in method_name: raise Failed(f"Collection Error: {method_final} requires MyAnimeList to be configured") elif not self.library.Tautulli and "tautulli" in method_name: raise Failed(f"Collection Error: {method_final} requires Tautulli to be configured")
elif self.library.is_movie and method_name in show_only_builders: raise Failed(f"Collection Error: {method_final} attribute only works for show libraries") elif not self.config.MyAnimeList and "mal" in method_name: raise Failed(f"Collection Error: {method_final} requires MyAnimeList to be configured")
elif self.library.is_show and method_name in movie_only_builders: raise Failed(f"Collection Error: {method_final} attribute only works for movie libraries") elif self.library.is_movie and method_name in show_only_builders: raise Failed(f"Collection Error: {method_final} attribute only works for show libraries")
elif self.library.is_show and method_name in plex.movie_only_searches: raise Failed(f"Collection Error: {method_final} plex search only works for movie libraries") elif self.library.is_show and method_name in movie_only_builders: raise Failed(f"Collection Error: {method_final} attribute only works for movie libraries")
elif self.library.is_movie and method_name in plex.show_only_searches: raise Failed(f"Collection Error: {method_final} plex search only works for show libraries") elif self.library.is_show and method_name in plex.movie_only_searches: raise Failed(f"Collection Error: {method_final} plex search only works for movie libraries")
elif self.smart and method_name in smart_invalid: raise Failed(f"Collection Error: {method_final} attribute only works with normal collections") elif self.library.is_movie and method_name in plex.show_only_searches: raise Failed(f"Collection Error: {method_final} plex search only works for show libraries")
elif self.collectionless and method_name not in collectionless_details: raise Failed(f"Collection Error: {method_final} attribute does not work for Collectionless collection") elif self.smart and method_name in smart_invalid: raise Failed(f"Collection Error: {method_final} attribute only works with normal collections")
elif self.smart_url and method_name in all_builders + smart_url_invalid: raise Failed(f"Collection Error: {method_final} builder not allowed when using smart_filter") elif self.collectionless and method_name not in collectionless_details: raise Failed(f"Collection Error: {method_final} attribute does not work for Collectionless collection")
elif method_name in summary_details: self._summary(method_name, method_data) elif self.smart_url and method_name in all_builders + smart_url_invalid: raise Failed(f"Collection Error: {method_final} builder not allowed when using smart_filter")
elif method_name in poster_details: self._poster(method_name, method_data) elif method_name in summary_details: self._summary(method_name, method_data)
elif method_name in background_details: self._background(method_name, method_data) elif method_name in poster_details: self._poster(method_name, method_data)
elif method_name in details: self._details(method_name, method_data, method_final, methods) elif method_name in background_details: self._background(method_name, method_data)
elif method_name in item_details: self._item_details(method_name, method_data, method_mod, method_final, methods) elif method_name in details: self._details(method_name, method_data, method_final, methods)
elif method_name in radarr_details: self._radarr(method_name, method_data) elif method_name in item_details: self._item_details(method_name, method_data, method_mod, method_final, methods)
elif method_name in sonarr_details: self._sonarr(method_name, method_data) elif method_name in radarr_details: self._radarr(method_name, method_data)
elif method_name in anidb.builders: self._anidb(method_name, method_data) elif method_name in sonarr_details: self._sonarr(method_name, method_data)
elif method_name in anilist.builders: self._anilist(method_name, method_data) elif method_name in anidb.builders: self._anidb(method_name, method_data)
elif method_name in icheckmovies.builders: self._icheckmovies(method_name, method_data) elif method_name in anilist.builders: self._anilist(method_name, method_data)
elif method_name in letterboxd.builders: self._letterboxd(method_name, method_data) elif method_name in icheckmovies.builders: self._icheckmovies(method_name, method_data)
elif method_name in imdb.builders: self._imdb(method_name, method_data) elif method_name in letterboxd.builders: self._letterboxd(method_name, method_data)
elif method_name in mal.builders: self._mal(method_name, method_data) elif method_name in imdb.builders: self._imdb(method_name, method_data)
elif method_name in plex.builders or method_final in plex.searches: self._plex(method_name, method_data) elif method_name in mal.builders: self._mal(method_name, method_data)
elif method_name in tautulli.builders: self._tautulli(method_name, method_data) elif method_name in plex.builders or method_final in plex.searches: self._plex(method_name, method_data)
elif method_name in tmdb.builders: self._tmdb(method_name, method_data) elif method_name in tautulli.builders: self._tautulli(method_name, method_data)
elif method_name in trakt.builders: self._trakt(method_name, method_data) elif method_name in tmdb.builders: self._tmdb(method_name, method_data)
elif method_name in tvdb.builders: self._tvdb(method_name, method_data) elif method_name in trakt.builders: self._trakt(method_name, method_data)
elif method_name == "filters": self._filters(method_name, method_data) elif method_name in tvdb.builders: self._tvdb(method_name, method_data)
else: raise Failed(f"Collection Error: {method_final} attribute not supported") elif method_name == "filters": self._filters(method_name, method_data)
else: raise Failed(f"Collection Error: {method_final} attribute not supported")
except Failed as e:
if self.validate_builders:
raise
else:
logger.error(e)
if self.custom_sort and len(self.builders) > 1: if self.custom_sort and len(self.builders) > 1:
raise Failed("Collection Error: collection_order: custom can only be used with a single builder per collection") raise Failed("Collection Error: collection_order: custom can only be used with a single builder per collection")
@ -622,7 +628,7 @@ class CollectionBuilder:
else: else:
self.details[method_final] = util.get_list(method_data) self.details[method_final] = util.get_list(method_data)
elif method_name in boolean_details: elif method_name in boolean_details:
self.details[method_name] = util.parse_bool(method_name, method_data) self.details[method_name] = util.parse(method_name, method_data, datatype="bool")
elif method_name in string_details: elif method_name in string_details:
self.details[method_name] = str(method_data) self.details[method_name] = str(method_data)
@ -664,11 +670,11 @@ class CollectionBuilder:
def _radarr(self, method_name, method_data): def _radarr(self, method_name, method_data):
if method_name == "radarr_add": if method_name == "radarr_add":
self.add_to_radarr = util.parse_bool(method_name, method_data) self.add_to_radarr = util.parse(method_name, method_data, datatype="bool")
elif method_name == "radarr_folder": elif method_name == "radarr_folder":
self.radarr_options["folder"] = method_data self.radarr_options["folder"] = method_data
elif method_name in ["radarr_monitor", "radarr_search"]: elif method_name in ["radarr_monitor", "radarr_search"]:
self.radarr_options[method_name[7:]] = util.parse_bool(method_name, method_data) self.radarr_options[method_name[7:]] = util.parse(method_name, method_data, datatype="bool")
elif method_name == "radarr_availability": elif method_name == "radarr_availability":
if str(method_data).lower() in radarr.availability_translation: if str(method_data).lower() in radarr.availability_translation:
self.radarr_options["availability"] = str(method_data).lower() self.radarr_options["availability"] = str(method_data).lower()
@ -682,7 +688,7 @@ class CollectionBuilder:
def _sonarr(self, method_name, method_data): def _sonarr(self, method_name, method_data):
if method_name == "sonarr_add": if method_name == "sonarr_add":
self.add_to_sonarr = util.parse_bool(method_name, method_data) self.add_to_sonarr = util.parse(method_name, method_data, datatype="bool")
elif method_name == "sonarr_folder": elif method_name == "sonarr_folder":
self.sonarr_options["folder"] = method_data self.sonarr_options["folder"] = method_data
elif method_name == "sonarr_monitor": elif method_name == "sonarr_monitor":
@ -702,18 +708,18 @@ class CollectionBuilder:
else: else:
raise Failed(f"Collection Error: {method_name} attribute must be either standard, daily, or anime") raise Failed(f"Collection Error: {method_name} attribute must be either standard, daily, or anime")
elif method_name in ["sonarr_season", "sonarr_search", "sonarr_cutoff_search"]: elif method_name in ["sonarr_season", "sonarr_search", "sonarr_cutoff_search"]:
self.sonarr_options[method_name[7:]] = util.parse_bool(method_name, method_data) self.sonarr_options[method_name[7:]] = util.parse(method_name, method_data, datatype="bool")
elif method_name == "sonarr_tag": elif method_name == "sonarr_tag":
self.sonarr_options["tag"] = util.get_list(method_data) self.sonarr_options["tag"] = util.get_list(method_data)
def _anidb(self, method_name, method_data): def _anidb(self, method_name, method_data):
if method_name == "anidb_popular": if method_name == "anidb_popular":
self.builders.append((method_name, util.parse_int(method_name, method_data, default=30, maximum=30))) self.builders.append((method_name, util.parse(method_name, method_data, datatype="int", default=30, maximum=30)))
elif method_name in ["anidb_id", "anidb_relation"]: elif method_name in ["anidb_id", "anidb_relation"]:
for anidb_id in self.config.AniDB.validate_anidb_ids(method_data, self.language): for anidb_id in self.config.AniDB.validate_anidb_ids(method_data, self.language):
self.builders.append((method_name, anidb_id)) self.builders.append((method_name, anidb_id))
elif method_name == "anidb_tag": elif method_name == "anidb_tag":
for dict_data, dict_methods in util.validate_dict_list(method_name, method_data): for dict_data, dict_methods in util.parse(method_name, method_data, datatype="dictlist"):
new_dictionary = {} new_dictionary = {}
if "tag" not in dict_methods: if "tag" not in dict_methods:
raise Failed("Collection Error: anidb_tag tag attribute is required") raise Failed("Collection Error: anidb_tag tag attribute is required")
@ -721,7 +727,7 @@ class CollectionBuilder:
raise Failed("Collection Error: anidb_tag tag attribute is blank") raise Failed("Collection Error: anidb_tag tag attribute is blank")
else: else:
new_dictionary["tag"] = util.regex_first_int(dict_data[dict_methods["username"]], "AniDB Tag ID") new_dictionary["tag"] = util.regex_first_int(dict_data[dict_methods["username"]], "AniDB Tag ID")
new_dictionary["limit"] = util.parse_int_from_dict(method_name, "limit", dict_data, dict_methods, 0, minimum=0) new_dictionary["limit"] = util.parse("limit", dict_data, datatype="int", methods=dict_methods, default=0, parent=method_name, minimum=0)
self.builders.append((method_name, new_dictionary)) self.builders.append((method_name, new_dictionary))
def _anilist(self, method_name, method_data): def _anilist(self, method_name, method_data):
@ -729,23 +735,23 @@ class CollectionBuilder:
for anilist_id in self.config.AniList.validate_anilist_ids(method_data, studio=method_name == "anilist_studio"): for anilist_id in self.config.AniList.validate_anilist_ids(method_data, studio=method_name == "anilist_studio"):
self.builders.append((method_name, anilist_id)) self.builders.append((method_name, anilist_id))
elif method_name in ["anilist_popular", "anilist_top_rated"]: elif method_name in ["anilist_popular", "anilist_top_rated"]:
self.builders.append((method_name, util.parse_int(method_name, method_data))) self.builders.append((method_name, util.parse(method_name, method_data, datatype="int", default=10)))
elif method_name in ["anilist_season", "anilist_genre", "anilist_tag"]: elif method_name in ["anilist_season", "anilist_genre", "anilist_tag"]:
for dict_data, dict_methods in util.validate_dict_list(method_name, method_data): for dict_data, dict_methods in util.parse(method_name, method_data, datatype="dictlist"):
new_dictionary = {} new_dictionary = {}
if method_name == "anilist_season": if method_name == "anilist_season":
if self.current_time.month in [12, 1, 2]: new_dictionary["season"] = "winter" if self.current_time.month in [12, 1, 2]: new_dictionary["season"] = "winter"
elif self.current_time.month in [3, 4, 5]: new_dictionary["season"] = "spring" elif self.current_time.month in [3, 4, 5]: new_dictionary["season"] = "spring"
elif self.current_time.month in [6, 7, 8]: new_dictionary["season"] = "summer" elif self.current_time.month in [6, 7, 8]: new_dictionary["season"] = "summer"
elif self.current_time.month in [9, 10, 11]: new_dictionary["season"] = "fall" elif self.current_time.month in [9, 10, 11]: new_dictionary["season"] = "fall"
new_dictionary["season"] = util.parse_from_dict(method_name, "season", dict_data, dict_methods, default=new_dictionary["season"], options=["winter", "spring", "summer", "fall"]) new_dictionary["season"] = util.parse("season", dict_data, methods=dict_methods, parent=method_name, default=new_dictionary["season"], options=["winter", "spring", "summer", "fall"])
new_dictionary["year"] = util.parse_int_from_dict(method_name, "year", dict_data, dict_methods, self.current_time.year, minimum=1917, maximum=self.current_time.year + 1) new_dictionary["year"] = util.parse("year", dict_data, datatype="int", methods=dict_methods, default=self.current_time.year, parent=method_name, minimum=1917, maximum=self.current_time.year + 1)
elif method_name == "anilist_genre": elif method_name == "anilist_genre":
new_dictionary["genre"] = self.config.AniList.validate_genre(util.parse_from_dict(method_name, "genre", dict_data, dict_methods)) new_dictionary["genre"] = self.config.AniList.validate_genre(util.parse("genre", dict_data, methods=dict_methods, parent=method_name))
elif method_name == "anilist_tag": elif method_name == "anilist_tag":
new_dictionary["tag"] = self.config.AniList.validate_tag(util.parse_from_dict(method_name, "tag", dict_data, dict_methods)) new_dictionary["tag"] = self.config.AniList.validate_tag(util.parse("tag", dict_data, methods=dict_methods, parent=method_name))
new_dictionary["sort_by"] = util.parse_from_dict(method_name, "sort_by", dict_data, dict_methods, default="score", options=["score", "popular"]) new_dictionary["sort_by"] = util.parse("sort_by", dict_data, methods=dict_methods, parent=method_name, default="score", options=["score", "popular"])
new_dictionary["limit"] = util.parse_int_from_dict(method_name, "limit", dict_data, dict_methods, 0, maximum=500) new_dictionary["limit"] = util.parse("limit", dict_data, datatype="int", methods=dict_methods, default=0, parent=method_name, maximum=500)
self.builders.append((method_name, new_dictionary)) self.builders.append((method_name, new_dictionary))
def _icheckmovies(self, method_name, method_data): def _icheckmovies(self, method_name, method_data):
@ -780,37 +786,37 @@ class CollectionBuilder:
for mal_id in util.get_int_list(method_data, "MyAnimeList ID"): for mal_id in util.get_int_list(method_data, "MyAnimeList ID"):
self.builders.append((method_name, mal_id)) self.builders.append((method_name, mal_id))
elif method_name in ["mal_all", "mal_airing", "mal_upcoming", "mal_tv", "mal_ova", "mal_movie", "mal_special", "mal_popular", "mal_favorite", "mal_suggested"]: elif method_name in ["mal_all", "mal_airing", "mal_upcoming", "mal_tv", "mal_ova", "mal_movie", "mal_special", "mal_popular", "mal_favorite", "mal_suggested"]:
self.builders.append((method_name, util.parse_int(method_name, method_data))) self.builders.append((method_name, util.parse(method_name, method_data, datatype="int", default=10)))
elif method_name in ["mal_season", "mal_userlist"]: elif method_name in ["mal_season", "mal_userlist"]:
for dict_data, dict_methods in util.validate_dict_list(method_name, method_data): for dict_data, dict_methods in util.parse(method_name, method_data, datatype="dictlist"):
new_dictionary = {} new_dictionary = {}
if method_name == "mal_season": if method_name == "mal_season":
if self.current_time.month in [1, 2, 3]: new_dictionary["season"] = "winter" if self.current_time.month in [1, 2, 3]: new_dictionary["season"] = "winter"
elif self.current_time.month in [4, 5, 6]: new_dictionary["season"] = "spring" elif self.current_time.month in [4, 5, 6]: new_dictionary["season"] = "spring"
elif self.current_time.month in [7, 8, 9]: new_dictionary["season"] = "summer" elif self.current_time.month in [7, 8, 9]: new_dictionary["season"] = "summer"
elif self.current_time.month in [10, 11, 12]: new_dictionary["season"] = "fall" elif self.current_time.month in [10, 11, 12]: new_dictionary["season"] = "fall"
new_dictionary["season"] = util.parse_from_dict(method_name, "season", dict_data, dict_methods, default=new_dictionary["season"], options=["winter", "spring", "summer", "fall"]) new_dictionary["season"] = util.parse("season", dict_data, methods=dict_methods, parent=method_name, default=new_dictionary["season"], options=["winter", "spring", "summer", "fall"])
new_dictionary["sort_by"] = util.parse_from_dict(method_name, "sort_by", dict_data, dict_methods, default="members", options=mal.season_sort_options, translation=mal.season_sort_translation) new_dictionary["sort_by"] = util.parse("sort_by", dict_data, methods=dict_methods, parent=method_name, default="members", options=mal.season_sort_options, translation=mal.season_sort_translation)
new_dictionary["year"] = util.parse_int_from_dict(method_name, "year", dict_data, dict_methods, self.current_time.year, minimum=1917, maximum=self.current_time.year + 1) new_dictionary["year"] = util.parse("year", dict_data, datatype="int", methods=dict_methods, default=self.current_time.year, parent=method_name, minimum=1917, maximum=self.current_time.year + 1)
new_dictionary["limit"] = util.parse_int_from_dict(method_name, "limit", dict_data, dict_methods, 100, maximum=500) new_dictionary["limit"] = util.parse("limit", dict_data, datatype="int", methods=dict_methods, default=100, parent=method_name, maximum=500)
elif method_name == "mal_userlist": elif method_name == "mal_userlist":
new_dictionary["username"] = util.parse_from_dict(method_name, "username", dict_data, dict_methods) new_dictionary["username"] = util.parse("username", dict_data, methods=dict_methods, parent=method_name)
new_dictionary["status"] = util.parse_from_dict(method_name, "status", dict_data, dict_methods, default="all", options=mal.userlist_status) new_dictionary["status"] = util.parse("status", dict_data, methods=dict_methods, parent=method_name, default="all", options=mal.userlist_status)
new_dictionary["sort_by"] = util.parse_from_dict(method_name, "sort_by", dict_data, dict_methods, default="score", options=mal.userlist_sort_options, translation=mal.userlist_sort_translation) new_dictionary["sort_by"] = util.parse("sort_by", dict_data, methods=dict_methods, parent=method_name, default="score", options=mal.userlist_sort_options, translation=mal.userlist_sort_translation)
new_dictionary["limit"] = util.parse_int_from_dict(method_name, "limit", dict_data, dict_methods, 100, maximum=1000) new_dictionary["limit"] = util.parse("limit", dict_data, datatype="int", methods=dict_methods, default=100, parent=method_name, maximum=1000)
self.builders.append((method_name, new_dictionary)) self.builders.append((method_name, new_dictionary))
def _plex(self, method_name, method_data): def _plex(self, method_name, method_data):
if method_name == "plex_all": if method_name == "plex_all":
self.builders.append((method_name, True)) self.builders.append((method_name, True))
elif method_name in ["plex_search", "plex_collectionless"]: elif method_name in ["plex_search", "plex_collectionless"]:
for dict_data, dict_methods in util.validate_dict_list(method_name, method_data): for dict_data, dict_methods in util.parse(method_name, method_data, datatype="dictlist"):
new_dictionary = {} new_dictionary = {}
if method_name == "plex_search": if method_name == "plex_search":
new_dictionary = self.build_filter("plex_search", dict_data) new_dictionary = self.build_filter("plex_search", dict_data)
elif method_name == "plex_collectionless": elif method_name == "plex_collectionless":
prefix_list = util.parse_list("exclude_prefix", dict_data, dict_methods) prefix_list = util.parse("exclude_prefix", dict_data, datatype="list", methods=dict_methods)
exact_list = util.parse_list("exclude", dict_data, dict_methods) exact_list = util.parse("exclude", dict_data, datatype="list", methods=dict_methods)
if len(prefix_list) == 0 and len(exact_list) == 0: if len(prefix_list) == 0 and len(exact_list) == 0:
raise Failed("Collection Error: you must have at least one exclusion") raise Failed("Collection Error: you must have at least one exclusion")
exact_list.append(self.name) exact_list.append(self.name)
@ -821,17 +827,17 @@ class CollectionBuilder:
self.builders.append(("plex_search", self.build_filter("plex_search", {"any": {method_name: method_data}}))) self.builders.append(("plex_search", self.build_filter("plex_search", {"any": {method_name: method_data}})))
def _tautulli(self, method_name, method_data): def _tautulli(self, method_name, method_data):
for dict_data, dict_methods in util.validate_dict_list(method_name, method_data): for dict_data, dict_methods in util.parse(method_name, method_data, datatype="dictlist"):
self.builders.append((method_name, { self.builders.append((method_name, {
"list_type": "popular" if method_name == "tautulli_popular" else "watched", "list_type": "popular" if method_name == "tautulli_popular" else "watched",
"list_days": util.parse_int_from_dict(method_name, "list_days", dict_data, dict_methods, 30), "list_days": util.parse("list_days", dict_data, datatype="int", methods=dict_methods, default=30, parent=method_name),
"list_size": util.parse_int_from_dict(method_name, "list_size", dict_data, dict_methods, 10), "list_size": util.parse("list_size", dict_data, datatype="int", methods=dict_methods, default=10, parent=method_name),
"list_buffer": util.parse_int_from_dict(method_name, "list_buffer", dict_data, dict_methods, 20) "list_buffer": util.parse("list_buffer", dict_data, datatype="int", methods=dict_methods, default=20, parent=method_name)
})) }))
def _tmdb(self, method_name, method_data): def _tmdb(self, method_name, method_data):
if method_name == "tmdb_discover": if method_name == "tmdb_discover":
for dict_data, dict_methods in util.validate_dict_list(method_name, method_data): for dict_data, dict_methods in util.parse(method_name, method_data, datatype="dictlist"):
new_dictionary = {"limit": 100} new_dictionary = {"limit": 100}
for discover_name, discover_data in dict_data.items(): for discover_name, discover_data in dict_data.items():
discover_final = discover_name.lower() discover_final = discover_name.lower()
@ -868,9 +874,9 @@ class CollectionBuilder:
elif discover_final in tmdb.discover_dates: elif discover_final in tmdb.discover_dates:
new_dictionary[discover_final] = util.validate_date(discover_data, f"{method_name} attribute {discover_final}", return_as="%m/%d/%Y") new_dictionary[discover_final] = util.validate_date(discover_data, f"{method_name} attribute {discover_final}", return_as="%m/%d/%Y")
elif discover_final in ["primary_release_year", "year", "first_air_date_year"]: elif discover_final in ["primary_release_year", "year", "first_air_date_year"]:
new_dictionary[discover_final] = util.check_number(discover_data, f"{method_name} attribute {discover_final}", minimum=1800, maximum=self.current_year + 1) new_dictionary[discover_final] = util.parse(discover_final, discover_data, datatype="int", parent=method_name, minimum=1800, maximum=self.current_year + 1)
elif discover_final in ["vote_count.gte", "vote_count.lte", "vote_average.gte", "vote_average.lte", "with_runtime.gte", "with_runtime.lte"]: elif discover_final in ["vote_count.gte", "vote_count.lte", "vote_average.gte", "vote_average.lte", "with_runtime.gte", "with_runtime.lte"]:
new_dictionary[discover_final] = util.check_number(discover_data, f"{method_name} attribute {discover_final}", minimum=1) new_dictionary[discover_final] = util.parse(discover_final, discover_data, datatype="int", parent=method_name)
elif discover_final in ["with_cast", "with_crew", "with_people", "with_companies", "with_networks", "with_genres", "without_genres", "with_keywords", "without_keywords", "with_original_language", "timezone"]: elif discover_final in ["with_cast", "with_crew", "with_people", "with_companies", "with_networks", "with_genres", "without_genres", "with_keywords", "without_keywords", "with_original_language", "timezone"]:
new_dictionary[discover_final] = discover_data new_dictionary[discover_final] = discover_data
else: else:
@ -889,7 +895,7 @@ class CollectionBuilder:
else: else:
raise Failed(f"Collection Error: {method_name} had no valid fields") raise Failed(f"Collection Error: {method_name} had no valid fields")
elif method_name in ["tmdb_popular", "tmdb_top_rated", "tmdb_now_playing", "tmdb_trending_daily", "tmdb_trending_weekly"]: elif method_name in ["tmdb_popular", "tmdb_top_rated", "tmdb_now_playing", "tmdb_trending_daily", "tmdb_trending_weekly"]:
self.builders.append((method_name, util.parse_int(method_name, method_data))) self.builders.append((method_name, util.parse(method_name, method_data, datatype="int", default=10)))
else: else:
values = self.config.TMDb.validate_tmdb_ids(method_data, method_name) values = self.config.TMDb.validate_tmdb_ids(method_data, method_name)
if method_name.endswith("_details"): if method_name.endswith("_details"):
@ -922,7 +928,7 @@ class CollectionBuilder:
if method_name.endswith("_details"): if method_name.endswith("_details"):
self.summaries[method_name] = self.config.Trakt.list_description(trakt_lists[0]) self.summaries[method_name] = self.config.Trakt.list_description(trakt_lists[0])
elif method_name in ["trakt_trending", "trakt_popular", "trakt_recommended", "trakt_watched", "trakt_collected"]: elif method_name in ["trakt_trending", "trakt_popular", "trakt_recommended", "trakt_watched", "trakt_collected"]:
self.builders.append((method_name, util.parse_int(method_name, method_data))) self.builders.append((method_name, util.parse(method_name, method_data, datatype="int", default=10)))
elif method_name in ["trakt_watchlist", "trakt_collection"]: elif method_name in ["trakt_watchlist", "trakt_collection"]:
for trakt_list in self.config.Trakt.validate_trakt(method_data, self.library.is_movie, trakt_type=method_name[6:]): for trakt_list in self.config.Trakt.validate_trakt(method_data, self.library.is_movie, trakt_type=method_name[6:]):
self.builders.append((method_name, trakt_list)) self.builders.append((method_name, trakt_list))
@ -944,7 +950,7 @@ class CollectionBuilder:
self.builders.append((method_name[:-8] if method_name.endswith("_details") else method_name, value)) self.builders.append((method_name[:-8] if method_name.endswith("_details") else method_name, value))
def _filters(self, method_name, method_data): def _filters(self, method_name, method_data):
for dict_data, dict_methods in util.validate_dict_list(method_name, method_data): for dict_data, dict_methods in util.parse(method_name, method_data, datatype="dictlist"):
validate = True validate = True
if "validate" in dict_data: if "validate" in dict_data:
if dict_data["validate"] is None: if dict_data["validate"] is None:
@ -1215,7 +1221,7 @@ class CollectionBuilder:
return util.get_list(data) return util.get_list(data)
elif attribute == "history": elif attribute == "history":
try: try:
return util.check_number(data, final, minimum=1, maximum=30) return util.parse(final, data, datatype="int", maximum=30)
except Failed: except Failed:
if str(data).lower() in ["day", "month"]: if str(data).lower() in ["day", "month"]:
return data.lower() return data.lower()
@ -1247,17 +1253,21 @@ class CollectionBuilder:
logger.error(error) logger.error(error)
return valid_list return valid_list
elif attribute in ["year", "episode_year"] and modifier in [".gt", ".gte", ".lt", ".lte"]: elif attribute in ["year", "episode_year"] and modifier in [".gt", ".gte", ".lt", ".lte"]:
return util.check_year(data, self.current_year, final) return util.parse(final, data, datatype="int", minimum=1800, maximum=self.current_year)
elif attribute in plex.date_attributes and modifier in [".before", ".after"]: elif attribute in plex.date_attributes and modifier in [".before", ".after"]:
return util.validate_date(data, final, return_as="%Y-%m-%d") return util.validate_date(data, final, return_as="%Y-%m-%d")
elif attribute in plex.number_attributes and modifier in ["", ".not", ".gt", ".gte", ".lt", ".lte"]: elif attribute in plex.number_attributes and modifier in ["", ".not", ".gt", ".gte", ".lt", ".lte"]:
return util.check_number(data, final, minimum=1) return util.parse(final, data, datatype="int")
elif attribute in plex.float_attributes and modifier in [".gt", ".gte", ".lt", ".lte"]: elif attribute in plex.float_attributes and modifier in [".gt", ".gte", ".lt", ".lte"]:
return util.check_number(data, final, number_type="float", minimum=0, maximum=10) return util.parse(final, data, datatype="float", minimum=0, maximum=10)
elif attribute in ["decade", "year", "episode_year"] and modifier in ["", ".not"]: elif attribute in ["decade", "year", "episode_year"] and modifier in ["", ".not"]:
return smart_pair(util.get_year_list(data, self.current_year, final)) final_years = []
values = util.get_list(data)
for value in values:
final_years.append(util.parse(final, value, datatype="int", minimum=1800, maximum=self.current_year))
return smart_pair(final_years)
elif attribute in plex.boolean_attributes: elif attribute in plex.boolean_attributes:
return util.parse_bool(attribute, data) return util.parse(attribute, data, datatype="bool")
else: else:
raise Failed(f"Collection Error: {final} attribute not supported") raise Failed(f"Collection Error: {final} attribute not supported")
@ -1796,7 +1806,10 @@ class CollectionBuilder:
items = self.library.get_collection_items(self.obj, self.smart_label_collection) items = self.library.get_collection_items(self.obj, self.smart_label_collection)
keys = {item.ratingKey: item for item in items} keys = {item.ratingKey: item for item in items}
previous = None previous = None
logger.debug(keys)
logger.debug(self.rating_keys)
for ki, key in enumerate(self.rating_keys): for ki, key in enumerate(self.rating_keys):
logger.debug(items)
if key != items[ki].ratingKey: if key != items[ki].ratingKey:
logger.info(f"Moving {keys[key].title} {'after {}'.format(keys[previous].title) if previous else 'to the beginning'}") logger.info(f"Moving {keys[key].title} {'after {}'.format(keys[previous].title) if previous else 'to the beginning'}")
self.library.moveItem(self.obj, key, after=previous) self.library.moveItem(self.obj, key, after=previous)

View file

@ -30,11 +30,11 @@ class IMDb:
if not isinstance(imdb_dict, dict): if not isinstance(imdb_dict, dict):
imdb_dict = {"url": imdb_dict} imdb_dict = {"url": imdb_dict}
dict_methods = {dm.lower(): dm for dm in imdb_dict} dict_methods = {dm.lower(): dm for dm in imdb_dict}
imdb_url = util.parse_from_dict("imdb_list", "url", imdb_dict, dict_methods).strip() imdb_url = util.parse("url", imdb_dict, methods=dict_methods, parent="imdb_list").strip()
if not imdb_url.startswith((urls["list"], urls["search"], urls["keyword"])): if not imdb_url.startswith((urls["list"], urls["search"], urls["keyword"])):
raise Failed(f"IMDb Error: {imdb_url} must begin with either:\n{urls['list']} (For Lists)\n{urls['search']} (For Searches)\n{urls['keyword']} (For Keyword Searches)") raise Failed(f"IMDb Error: {imdb_url} must begin with either:\n{urls['list']} (For Lists)\n{urls['search']} (For Searches)\n{urls['keyword']} (For Keyword Searches)")
self._total(imdb_url, language) self._total(imdb_url, language)
list_count = util.parse_int_from_dict("imdb_list", "limit", imdb_dict, dict_methods, 0, minimum=0) if "limit" in dict_methods else 0 list_count = util.parse("limit", imdb_dict, datatype="int", methods=dict_methods, default=0, parent="imdb_list", minimum=0) if "limit" in dict_methods else 0
valid_lists.append({"url": imdb_url, "limit": list_count}) valid_lists.append({"url": imdb_url, "limit": list_count})
return valid_lists return valid_lists

View file

@ -93,7 +93,7 @@ class Metadata:
final_value = util.validate_date(value, name, return_as="%Y-%m-%d") final_value = util.validate_date(value, name, return_as="%Y-%m-%d")
current = current[:-9] current = current[:-9]
elif var_type == "float": elif var_type == "float":
final_value = util.check_number(value, name, number_type="float", minimum=0, maximum=10) final_value = util.parse(name, value, datatype="float", minimum=0, maximum=10)
else: else:
final_value = value final_value = value
if current != str(final_value): if current != str(final_value):
@ -170,7 +170,7 @@ class Metadata:
logger.info("") logger.info("")
year = None year = None
if "year" in methods: if "year" in methods:
year = util.check_number(meta[methods["year"]], "year", minimum=1800, maximum=datetime.now().year + 1) year = util.parse("year", meta, datatype="int", methods=methods, minimum=1800, maximum=datetime.now().year + 1)
title = mapping_name title = mapping_name
if "title" in methods: if "title" in methods:

View file

@ -95,18 +95,6 @@ def tab_new_lines(data):
def make_ordinal(n): def make_ordinal(n):
return f"{n}{'th' if 11 <= (n % 100) <= 13 else ['th', 'st', 'nd', 'rd', 'th'][min(n % 10, 4)]}" return f"{n}{'th' if 11 <= (n % 100) <= 13 else ['th', 'st', 'nd', 'rd', 'th'][min(n % 10, 4)]}"
def parse_bool(method_name, method_data):
if isinstance(method_data, bool):
return method_data
elif isinstance(method_data, int):
return method_data > 0
elif str(method_data).lower() in ["t", "true"]:
return True
elif str(method_data).lower() in ["f", "false"]:
return False
else:
raise Failed(f"Collection Error: {method_name} attribute: {method_data} invalid must be either true or false")
def compile_list(data): def compile_list(data):
if isinstance(data, list): if isinstance(data, list):
text = "" text = ""
@ -126,40 +114,12 @@ def get_list(data, lower=False, split=True, int_list=False):
else: return [d.strip() for d in str(data).split(",")] else: return [d.strip() for d in str(data).split(",")]
def get_int_list(data, id_type): def get_int_list(data, id_type):
values = get_list(data)
int_values = [] int_values = []
for value in values: for value in get_list(data):
try: int_values.append(regex_first_int(value, id_type)) try: int_values.append(regex_first_int(value, id_type))
except Failed as e: logger.error(e) except Failed as e: logger.error(e)
return int_values return int_values
def get_year_list(data, current_year, method):
final_years = []
values = get_list(data)
for value in values:
final_years.append(check_year(value, current_year, method))
return final_years
def check_year(year, current_year, method):
return check_number(year, method, minimum=1800, maximum=current_year)
def check_number(value, method, number_type="int", minimum=None, maximum=None):
if number_type == "int":
try: num_value = int(str(value))
except ValueError: raise Failed(f"Collection Error: {method}: {value} must be an integer")
elif number_type == "float":
try: num_value = float(str(value))
except ValueError: raise Failed(f"Collection Error: {method}: {value} must be a number")
else: raise Failed(f"Number Type: {number_type} invalid")
if minimum is not None and maximum is not None and (num_value < minimum or num_value > maximum):
raise Failed(f"Collection Error: {method}: {num_value} must be between {minimum} and {maximum}")
elif minimum is not None and num_value < minimum:
raise Failed(f"Collection Error: {method}: {num_value} is less then {minimum}")
elif maximum is not None and num_value > maximum:
raise Failed(f"Collection Error: {method}: {num_value} is greater then {maximum}")
else:
return num_value
def validate_date(date_text, method, return_as=None): def validate_date(date_text, method, return_as=None):
try: date_obg = datetime.strptime(str(date_text), "%Y-%m-%d" if "-" in str(date_text) else "%m/%d/%Y") try: date_obg = datetime.strptime(str(date_text), "%Y-%m-%d" if "-" in str(date_text) else "%m/%d/%Y")
except ValueError: raise Failed(f"Collection Error: {method}: {date_text} must match pattern YYYY-MM-DD (e.g. 2020-12-25) or MM/DD/YYYY (e.g. 12/25/2020)") except ValueError: raise Failed(f"Collection Error: {method}: {date_text} must match pattern YYYY-MM-DD (e.g. 2020-12-25) or MM/DD/YYYY (e.g. 12/25/2020)")
@ -184,22 +144,6 @@ def unix_input(prompt, timeout=60):
except EOFError: raise Failed("Input Failed") except EOFError: raise Failed("Input Failed")
finally: signal.alarm(0) finally: signal.alarm(0)
def old_windows_input(prompt, timeout=60, timer=time.monotonic):
prompt = f"| {prompt}: "
sys.stdout.write(prompt)
sys.stdout.flush()
endtime = timer() + timeout
result = []
while timer() < endtime:
if msvcrt.kbhit():
result.append(msvcrt.getwche())
if result[-1] == "\n":
out = "".join(result[:-1])
logger.debug(f"{prompt[2:]}{out}")
return out
time.sleep(0.04)
raise TimeoutExpired
def windows_input(prompt, timeout=5): def windows_input(prompt, timeout=5):
sys.stdout.write(f"| {prompt}: ") sys.stdout.write(f"| {prompt}: ")
sys.stdout.flush() sys.stdout.flush()
@ -322,64 +266,66 @@ def is_locked(filepath):
file_object = open(filepath, 'a', 8) file_object = open(filepath, 'a', 8)
if file_object: if file_object:
locked = False locked = False
except IOError as message: except IOError:
locked = True locked = True
finally: finally:
if file_object: if file_object:
file_object.close() file_object.close()
return locked return locked
def validate_dict_list(method_name, data): def parse(attribute, data, datatype=None, methods=None, parent=None, default=None, options=None, translation=None, minimum=1, maximum=None):
final_list = [] display = f"{parent + ' ' if parent else ''}{attribute} attribute"
for dict_data in get_list(data):
if isinstance(dict_data, dict):
final_list.append((dict_data, {dm.lower(): dm for dm in dict_data}))
else:
raise Failed(f"Collection Error: {method_name} attribute is not a dictionary: {dict_data}")
return final_list
def parse_int(method, data, default=10, minimum=1, maximum=None):
list_count = regex_first_int(data, "List Size", default=default)
if maximum is None and list_count < minimum:
logger.warning(f"Collection Warning: {method} must an integer >= {minimum} using {default} as default")
elif maximum is not None and (list_count < minimum or list_count > maximum):
logger.warning(f"Collection Warning: {method} must an integer between {minimum} and {maximum} using {default} as default")
else:
return list_count
return default
def parse_from_dict(parent, method, data, methods, default=None, options=None, translation=None):
if options is None and translation is not None: if options is None and translation is not None:
options = [o for o in translation] options = [o for o in translation]
if method not in methods: value = data[methods[attribute]] if methods and attribute in methods else data
message = f"{parent} {method} attribute not found"
elif data[methods[method]] is None: if datatype == "list":
message = f"{parent} {method} attribute is blank" if methods and attribute in methods and data[methods[attribute]]:
elif (translation is not None and str(data[methods[method]]).lower() not in translation) or \ return [v for v in value if v] if isinstance(value, list) else [str(value)]
(options is not None and translation is None and str(data[methods[method]]).lower() not in options): return []
message = f"{parent} {method} attribute {data[methods[method]]} must be in {options}" elif datatype == "dictlist":
final_list = []
for dict_data in get_list(value):
if isinstance(dict_data, dict):
final_list.append((dict_data, {dm.lower(): dm for dm in dict_data}))
else:
raise Failed(f"Collection Error: {display} {dict_data} is not a dictionary")
return final_list
elif methods and attribute not in methods:
message = f"{display} not found"
elif value is None:
message = f"{display} is blank"
elif datatype == "bool":
if isinstance(value, bool):
return value
elif isinstance(value, int):
return value > 0
elif str(value).lower() in ["t", "true"]:
return True
elif str(value).lower() in ["f", "false"]:
return False
else:
message = f"{display} must be either true or false"
elif datatype in ["int", "float"]:
try:
value = int(str(value)) if datatype == "int" else float(str(value))
if (maximum is None and minimum <= value) or (maximum is not None and minimum <= value <= maximum):
return value
except ValueError:
pass
pre = f"{display} {value} must {'an integer' if datatype == 'int' else 'a number'}"
if maximum is None:
message = f"{pre} {minimum} or greater"
else:
message = f"{pre} between {minimum} and {maximum}"
elif (translation is not None and str(value).lower() not in translation) or \
(options is not None and translation is None and str(value).lower() not in options):
message = f"{display} {value} must be in {options}"
else: else:
return translation[data[methods[method]]] if translation is not None else data[methods[method]] return translation[value] if translation is not None else value
if default is None: if default is None:
raise Failed(f"Collection Error: {message}") raise Failed(f"Collection Error: {message}")
else: else:
logger.warning(f"Collection Warning: {message} using {default} as default") logger.warning(f"Collection Warning: {message} using {default} as default")
return translation[default] if translation is not None else default return translation[default] if translation is not None else default
def parse_int_from_dict(parent, method, data, methods, default, minimum=1, maximum=None):
if method not in methods:
logger.warning(f"Collection Warning: {parent} {method} attribute not found using {default} as default")
elif not data[methods[method]]:
logger.warning(f"Collection Warning: {parent} {methods[method]} attribute is blank using {default} as default")
elif maximum is None and (not isinstance(data[methods[method]], int) or data[methods[method]] < minimum):
logger.warning(f"Collection Warning: {parent} {methods[method]} attribute {data[methods[method]]} must an integer >= {minimum} using {default} as default")
elif maximum is not None and (not isinstance(data[methods[method]], int) or data[methods[method]] < minimum or data[methods[method]] > maximum):
logger.warning(f"Collection Warning: {parent} {methods[method]} attribute {data[methods[method]]} must an integer between {minimum} and {maximum} using {default} as default")
else:
return data[methods[method]]
return default
def parse_list(method, data, methods):
if method in methods and data[methods[method]]:
return [i for i in data[methods[method]] if i] if isinstance(data[methods[method]], list) else [str(data[methods[method]])]
return []