Source code for arrapi.objs.reload

from abc import abstractmethod
from typing import Union, Optional, List, TYPE_CHECKING

from arrapi import NotFound, Invalid, Exists, Excluded
from arrapi.objs.base import BaseObj

if TYPE_CHECKING:
    from arrapi.objs.simple import RootFolder

class ReloadObj(BaseObj):
    def __init__(self, arr, data, load=False):
        super().__init__(arr, data)
        if load:
            self._load(None)

    @abstractmethod
    def _load(self, data):
        self._partial = data is not None
        super()._load(self._full_load() if data is None else data)

    @abstractmethod
    def _full_load(self):
        pass

    def reload(self):
        """ Reloads the Object """
        self._load(None)


[docs] class QualityProfile(ReloadObj): """ Represents a single Quality Profile. Attributes: id (int): ID of the Quality Profile. name (str): Name of the Quality Profile. """ def _load(self, data): super()._load(data) self.id = self._parse(attrs="id", value_type="int") self.name = self._parse(attrs="name") self._finish(self.name) def _full_load(self): return self._raw.get_qualityProfileId(self.id)
[docs] class LanguageProfile(ReloadObj): """ Represents a single Language Profile. Attributes: id (int): ID of the Language Profile. name (str): Name of the Language Profile. """ def _load(self, data): super()._load(data) self.id = self._parse(attrs="id", value_type="int") self.name = self._parse(attrs="name") self._finish(self.name) def _full_load(self): return self._raw.get_languageProfileId(self.id)
[docs] class SystemStatus(ReloadObj): """ Represents the System Status. Attributes: version (str): Version of the Arr Instance. Check dir(SystemStatus) for all attribute as the rest are auto built. """ def __init__(self, arr): super().__init__(arr, None) def _load(self, data): super()._load(None) self.version = "" for key, value in self._data.items(): if key.startswith("is") or key == "migrationVersion": setattr(self, key, self._parse(data=value, value_type="int")) elif key.endswith("Time"): setattr(self, key, self._parse(data=value, value_type="date")) elif key == "version": self.version = value else: setattr(self, key, value) self._finish(self.version) def _full_load(self): return self._raw.get_system_status()
[docs] class Tag(ReloadObj): """ Represents a single Tag. Attributes: id (int): ID of the Tag. label (str): Label of the Tag. detail (bool): If the Tag was loaded with details. delayProfileIds (List[int]): Delay Profile IDs. (Only when loaded with details) notificationIds (List[int]): Notification IDs. (Only when loaded with details) restrictionIds (List[int]): Restriction IDs. (Only when loaded with details) importListIds (List[int]): Import List IDs. (Only when loaded with details) movieIds (List[int]): Radarr Movie IDs. (Only when loaded with details using :class:`~arrapi.apis.radarr.RadarrAPI`) seriesIds (List[int]): Sonarr Series IDs. (Only when loaded with details using :class:`~arrapi.apis.radarr.SonarrAPI`) artistIds (List[int]): Lidarr Artist IDs. (Only when loaded with details using :class:`~arrapi.apis.lidarr.LidarrAPI`) authorIds (List[int]): Readarr Author IDs. (Only when loaded with details using :class:`~arrapi.apis.readarr.ReadarrAPI`) """ def _load(self, data): super()._load(data) self.label = self._parse(attrs="label") self.id = self._parse(attrs="id", value_type="int", default_is_none=True) self.detail = False if "delayProfileIds" in self._data: self.detail = True self.delayProfileIds = self._parse(attrs="delayProfileIds", value_type="int", is_list=True) self.notificationIds = self._parse(attrs="notificationIds", value_type="int", is_list=True) self.restrictionIds = self._parse(attrs="restrictionIds", value_type="int", is_list=True) self.importListIds = self._parse(attrs="importListIds", value_type="int", is_list=True) if "movieIds" in self._data: self.movieIds = self._parse(attrs="movieIds", value_type="int", is_list=True) if "seriesIds" in self._data: self.seriesIds = self._parse(attrs="seriesIds", value_type="int", is_list=True) if "artistIds" in self._data: self.artistIds = self._parse(attrs="artistIds", value_type="int", is_list=True) if "authorIds" in self._data: self.authorIds = self._parse(attrs="authorIds", value_type="int", is_list=True) self._finish(self.label) def __str__(self): return f"[{self.id}:{self._name}]" if self.id and self._name else f"[Tag:{self.id}]" def _full_load(self): return self._raw.get_tag_id(self.id, detail=self.detail)
[docs] def edit(self, label: str) -> None: """ Edit the :class:`~arrapi.objs.reload.Tag`. Parameters: label (str): Label to change tag to. """ self._load(self._raw.put_tag_id(self.id, label))
[docs] def delete(self) -> None: """ Delete the :class:`~arrapi.objs.reload.Tag`.""" self._raw.delete_tag_id(self.id)
[docs] class Command(ReloadObj): """ Represents a single Command. Attributes: name (str): Name. commandName (str): Full named. message (str): Status Message. body (dict): Body of dict sent with the command. priority (str): Priority level. status (str): Status state. queued (datetime): Datetime queued. started (datetime): Datetime started. ended (datetime): Datetime ended. duration (str): Duration. exception (str): Exception message if there is a failure. trigger (str): Command trigger. stateChangeTime (datetime): Datetime the sate was cahnged. sendUpdatesToClient (bool): Send Updates To Client. updateScheduledTask (bool):Update Scheduled Task. id (int): ID of the Command. """ def _load(self, data): super()._load(data) self.name = self._parse(attrs="name") self.commandName = self._parse(attrs="commandName") self.message = self._parse(attrs="message") self.body = self._parse(attrs="body", value_type="dict") self.priority = self._parse(attrs="priority") self.status = self._parse(attrs="status") self.queued = self._parse(attrs="queued", value_type="date") self.started = self._parse(attrs="started", value_type="date") self.ended = self._parse(attrs="ended", value_type="date") self.duration = self._parse(attrs="duration") self.exception = self._parse(attrs="exception") self.trigger = self._parse(attrs="trigger") self.sendUpdatesToClient = self._parse(attrs="sendUpdatesToClient", value_type="bool") self.updateScheduledTask = self._parse(attrs="updateScheduledTask", value_type="bool") self.stateChangeTime = self._parse(attrs="stateChangeTime", value_type="date") self.lastExecutionTime = self._parse(attrs="lastExecutionTime", value_type="date") self.id = self._parse(attrs="id", value_type="int") self._finish(self.name) def _full_load(self): return self._raw.get_command_id(self.id)
[docs] class Movie(ReloadObj): """ Represents a single Movie. Attributes: id (int): ID of the Movie. title (str): Title of the Movie. sortTitle (str): Sort Title of the Movie. sizeOnDisk (int): Movie Size On Disk. status (str): Status of the Movie. overview (str): Overview of the Movie. inCinemas (datetime): Date the Movie was in Cinemas. physicalRelease (datetime): Date the Movie was Physically Released. digitalRelease (datetime): Date the Movie was Digitally Released. images (List[:class:`~arrapi.objs.simple.Image`]): List of Images for the Movie. website (str): Website of the Movie. year (int): Year of the Movie. hasFile (bool): If the Movie has a file. youTubeTrailerId (str): YouTube Trailer ID for the Movie. studio (str): Studio of the Movie. path (str): Path of the Movie. monitored (bool): If the Movie is monitored. minimumAvailability (str): Minimum Availability of the Movie. isAvailable (bool): If the Movie is Available. folderName (str): Folder Name of the Movie. folder (str): Folder name. runtime (int): Runtime of the Movie. cleanTitle (str): Clean Title of the Movie. imdbId (str): IMDb ID of the Movie. tmdbId (int): TMDb ID of the Movie. titleSlug (str): Title Slug of the Movie. certification (str): Certification of the Movie. genres (List[str]): List of genres for the Movie. tags (List[:class:`~arrapi.objs.reload.Tag`]): List of tags for the Movie. added (datetime): Date the Movie was added to Radarr. tagsIds (List[int]): List of tag ids for the Movie. rating_votes (int): Number of votes for the Movie. rating_value (float): Rating of the Movie. originalTitle (str): Original Title of the Movie. (Radarr v3 Only) digitalRelease (datetime): Date the Movie was Released Digitally. (Radarr v3 Only) qualityProfileId (int): Quality Profile ID of the Movie. (Radarr v3 Only) qualityProfile (:class:`~arrapi.objs.reload.QualityProfile`): Quality Profile of the Movie. (Radarr v3 Only) collection_name (str): Collection Name of the Movie. (Radarr v3 Only) collection_tmdbId (int): TMDb Collection ID for the Movie. (Radarr v3 Only) downloaded (bool): If the Movie has been Downloaded. (Radarr v2 Only) profileId (int): Quality Profile ID of the Movie. (Radarr v2 Only) profile (:class:`~arrapi.objs.reload.QualityProfile`): Quality Profile of the Movie. (Radarr v2 Only) """ def __init__(self, radarr, data=None, movie_id=None, tmdb_id=None, imdb_id=None): self._loading = True self.id = movie_id self.tmdbId = tmdb_id self.imdbId = imdb_id super().__init__(radarr, data, load=movie_id or tmdb_id or imdb_id) def _load(self, data): super()._load(data) self.title = self._parse(attrs="title") self.sortTitle = self._parse(attrs="sortTitle") self.sizeOnDisk = self._parse(attrs="sizeOnDisk", value_type="int") self.status = self._parse(attrs="status") self.overview = self._parse(attrs="overview") self.inCinemas = self._parse(attrs="inCinemas", value_type="date") self.physicalRelease = self._parse(attrs="physicalRelease", value_type="date") self.digitalRelease = self._parse(attrs="digitalRelease", value_type="date") self.images = self._parse(attrs="images", value_type="image", is_list=True) self.website = self._parse(attrs="website") self.year = self._parse(attrs="year", value_type="int") self.hasFile = self._parse(attrs="hasFile", value_type="bool") self.youTubeTrailerId = self._parse(attrs="youTubeTrailerId") self.studio = self._parse(attrs="studio") self.path = self._parse(attrs="path") self.monitored = self._parse(attrs="monitored", value_type="bool") self.minimumAvailability = self._parse(attrs="minimumAvailability") self.isAvailable = self._parse(attrs="isAvailable", value_type="bool") self.folderName = self._parse(attrs="folderName") self.folder = self._parse(attrs="folder") self.runtime = self._parse(attrs="runtime", value_type="int") self.cleanTitle = self._parse(attrs="cleanTitle") self.imdbId = self._parse(attrs="imdbId") self.tmdbId = self._parse(attrs="tmdbId", value_type="int", default_is_none=True) self.titleSlug = self._parse(attrs="titleSlug") self.certification = self._parse(attrs="certification") self.genres = self._parse(attrs="genres", is_list=True) self.tagsIds = self._parse(attrs="tags", value_type="int", is_list=True) self.tags = self._parse(attrs="tags", value_type="intTag", is_list=True) self.added = self._parse(attrs="added", value_type="date") self.rating_votes = self._parse(attrs=["rating", "votes"], value_type="int") self.rating_value = self._parse(attrs=["rating", "value"], value_type="float") self.collection = self._parse(attrs="collection", value_type="collection") self.id = self._parse(attrs="id", value_type="int", default_is_none=True) if self._raw.new_codebase: self.originalTitle = self._parse(attrs="originalTitle") self.digitalRelease = self._parse(attrs="digitalRelease", value_type="date") self.qualityProfileId = self._parse(attrs="qualityProfileId", value_type="int") self.qualityProfile = self._parse(attrs="qualityProfileId", value_type="intQualityProfile") self.collection_name = self._parse(attrs=["collection", "name"]) self.collection_tmdbId = self._parse(attrs=["collection", "tmdbId"], value_type="int", default_is_none=True) else: self.downloaded = self._parse(attrs="downloaded", value_type="bool") self.profileId = self._parse(attrs="profileId", value_type="int") self.profile = self._parse(attrs="profileId", value_type="intQualityProfile") self._finish(self.title) def _full_load(self): if self.id: return self._raw.get_movie_id(self.id) elif self.tmdbId or self.imdbId: items = self._raw.get_movie_lookup(f"tmdb:{self.tmdbId}" if self.tmdbId else f"imdb:{self.imdbId}") if items: return items[0] else: raise NotFound("Item Not Found") else: raise Invalid("Load Failed: No Load Input") def _get_add_data(self, options, path=None): if self.id: raise Exists(f"{self.title} is already in Radarr") self._data.pop("Id", None) self._data["monitored"] = options["monitor"] if path: if not path.startswith(options["root_folder"]): raise Invalid(f"Individual Path: {path} must start with the Root Folder: {options['root_folder']} ") self._data["path"] = path else: self._data["rootFolderPath"] = options["root_folder"] self._data["rootFolderPath"] = options["root_folder"] self._data["qualityProfileId" if self._raw.new_codebase else "profileId"] = options["quality_profile"] self._data["minimumAvailability"] = options["minimum_availability"] self._data["addOptions"] = {"searchForMovie": options["search"]} if "tags" in options: self._data["tags"] = options["tags"] return self._data
[docs] def add(self, root_folder: Union[str, int, "RootFolder"], quality_profile: Union[str, int, "QualityProfile"], monitor: bool = True, search: bool = True, minimum_availability: str = "announced", tags: Optional[List[Union[str, int, Tag]]] = None ) -> None: """ Add this Movie to Radarr. Parameters: root_folder (Union[str, int, RootFolder]): Root Folder for the Movie. quality_profile (Union[str, int, QualityProfile]): Quality Profile for the Movie. monitor (bool): Monitor the Movie. search (bool): Search for the Movie after adding. minimum_availability (str): Minimum Availability for the Movie. Valid options are announced, inCinemas, released, or preDB. tags (Optional[List[Union[str, int, Tag]]]): Tags to be added to the Movie. Raises: :class:`~arrapi.exceptions.Invalid`: When one of the options given is invalid. :class:`~arrapi.exceptions.Exists`: When the Movie already Exists in Radarr. """ if self._arr.exclusions and self.tmdbId in self._arr.exclusions: raise Excluded(f"TMDb ID: {self.tmdbId} is excluded from being added.") self._load(self._raw.post_movie(self._get_add_data(self._arr._validate_add_options( root_folder, quality_profile, monitor=monitor, search=search, minimum_availability=minimum_availability, tags=tags ))))
[docs] def edit(self, path: Optional[str] = None, move_files: bool = False, quality_profile: Optional[Union[str, int, "QualityProfile"]] = None, monitored: Optional[bool] = None, minimum_availability: Optional[str] = None, tags: Optional[List[Union[str, int, Tag]]] = None, apply_tags: str = "add" ) -> None: """ Edit this Movie in Radarr. Parameters: path (Optional[str]): Path to change the Movie to. move_files (bool): When changing the path do you want to move the files to the new path. quality_profile (Optional[Union[str, int, QualityProfile]]): Quality Profile to change the Movie to. monitored (Optional[bool]): Monitor the Movie. minimum_availability (Optional[str]): Minimum Availability to change the Movie to. Valid options are announced, inCinemas, released, or preDB. tags (Optional[List[Union[str, int, Tag]]]): Tags to be added, replaced, or removed from the Movie. apply_tags (str): How you want to edit the Tags. Valid options are add, replace, or remove. Raises: :class:`ValueError`: When theres no options given. :class:`~arrapi.exceptions.Invalid`: When one of the options given is invalid. :class:`~arrapi.exceptions.NotFound`: When the Movie hasn't been added to Radarr. """ if not self.id: raise NotFound(f"{self.title} not found Radarr, it must be added before editing") options = self._arr._validate_edit_options(path=path, move_files=move_files, quality_profile=quality_profile, monitored=monitored, minimum_availability=minimum_availability, tags=tags, apply_tags=apply_tags) valid_move_files = options["path"] if "path" in options else False for key, value in options.items(): if key == "tags": tag_type = options["applyTags"] if tag_type == "add": self._data[key].extend([t for t in value if t not in self._data["tags"]]) elif tag_type == "remove": self._data[key] = [t for t in self._data["tags"] if t not in value] elif tag_type == "replace": self._data[key] = value else: raise Invalid(f"Invalid apply_tags: '{tag_type}' Options: {self._arr.apply_tags_options}") elif key != ["applyTags", "moveFiles"]: self._data[key] = value self._load(self._raw.put_movie_id(self.id, self._data, moveFiles=valid_move_files))
[docs] def delete(self, addImportExclusion: bool = False, deleteFiles: bool = False) -> None: """ Delete this Movie from Radarr. Parameters: addImportExclusion (bool): Add Import Exclusion for this Movie. deleteFiles (bool): Delete Files for this Movie. Raises: :class:`~arrapi.exceptions.NotFound`: When the Movie hasn't been added to Radarr. """ if not self.id: raise NotFound(f"{self.title} not found Radarr, it must be added before deleting") self._raw.delete_movie_id(self.id, addImportExclusion=addImportExclusion, deleteFiles=deleteFiles) self._loading = True self.id = None self._loading = False
[docs] class Series(ReloadObj): """ Represents a single Series. Attributes: id (int): ID of the Series. title (str): Title of the Series. sortTitle (str): Sort Title of the Series. status (str): Status of the Series. overview (str): Overview of the Series. seasons (List[:class:`~arrapi.objs.simple.Season`]): List of Seasons in the Series. nextAiring (datetime): Date the next Episode in the Series Airs. previousAiring (datetime): Date the latest Episode in the Series Aired. network (str): Network the Series Airs on. airTime (str): Time Series Airs. images (List[:class:`~arrapi.objs.simple.Image`]): List of Images for the Series. year (int): Year of the Series. path (str): Path of the Series. languageProfileId (int): Language Profile ID of the Series. languageProfile (:class:`~arrapi.objs.reload.LanguageProfile`)): Language Profile of the Series. seasonFolder (bool): If the Series has Season Folders. monitored (bool): If the Series is monitored. useSceneNumbering (bool): If the Series uses Scene Numbering. folder (str): Folder name. runtime (int): Runtime of the Series. cleanTitle (str): Clean Title of the Series. imdbId (str): IMDb ID of the Series. tvdbId (int): TVDb ID of the Series. tvRageId (int): TV Rage ID of the Series. tvMazeId (int): TV Maze ID of the Series. titleSlug (str): Title Slug of the Series. firstAired (datetime): Date the Series First Aired. seriesType (str): Series Type of the Series. certification (str): Certification of the Series. genres (List[str]): List of genres for the Series. tags (List[:class:`~arrapi.objs.reload.Tag`]): List of tags for the Movie. added (datetime): Date the Movie was added to Radarr. tagsIds (List[int]): List of tag ids for the Movie. rating_votes (int): Number of votes for the Series. rating_value (float): Rating of the Series. seasonCount (int): Number of Seasons in the Series. totalEpisodeCount (int): Total Episodes in the Series. episodeCount (int): Episodes in the Series. episodeFileCount (int): Episode File Count for the Series. sizeOnDisk (int): Size on Disk for the Series. ended (bool): If the Series has ended. (Sonarr v3 Only) rootFolderPath (str): Root Folder Path for the Series. (Sonarr v3 Only) qualityProfileId (int): Quality Profile ID of the Series. (Sonarr v3 Only) qualityProfile (:class:`~arrapi.objs.reload.QualityProfile`): Quality Profile of the Series. (Sonarr v3 Only) percentOfEpisodes (float): Percent of Episodes obtained. (Sonarr v3 Only) profileId (int): Quality Profile ID of the Series. (Sonarr v2 Only) profile (:class:`~arrapi.objs.reload.QualityProfile`): Quality Profile of the Series. (Sonarr v2 Only) """ def __init__(self, sonarr, data=None, series_id=None, tvdb_id=None): self._loading = True self.id = series_id self.tvdbId = tvdb_id super().__init__(sonarr, data, load=series_id or tvdb_id) def _load(self, data): super()._load(data) self.title = self._parse(attrs="title") self.sortTitle = self._parse(attrs="sortTitle") self.status = self._parse(attrs="status") self.overview = self._parse(attrs="overview") self.nextAiring = self._parse(attrs="nextAiring", value_type="date") self.previousAiring = self._parse(attrs="previousAiring", value_type="date") self.network = self._parse(attrs="network") self.airTime = self._parse(attrs="airTime") self.images = self._parse(attrs="images", value_type="image", is_list=True) self.year = self._parse(attrs="year", value_type="int") self.path = self._parse(attrs="path") self.languageProfileId = self._parse(attrs="languageProfileId", value_type="int") self.languageProfile = self._parse(attrs="languageProfileId", value_type="intLanguageProfile") self.seasonFolder = self._parse(attrs="seasonFolder", value_type="bool") self.monitored = self._parse(attrs="monitored", value_type="bool") self.useSceneNumbering = self._parse(attrs="useSceneNumbering", value_type="bool") self.folder = self._parse(attrs="folder") self.runtime = self._parse(attrs="runtime", value_type="int") self.cleanTitle = self._parse(attrs="cleanTitle") self.imdbId = self._parse(attrs="imdbId") self.tvdbId = self._parse(attrs="tvdbId", value_type="int") self.tvRageId = self._parse(attrs="tvRageId", value_type="int") self.tvMazeId = self._parse(attrs="tvMazeId", value_type="int") self.titleSlug = self._parse(attrs="titleSlug") self.firstAired = self._parse(attrs="firstAired", value_type="date") self.seriesType = self._parse(attrs="seriesType") self.certification = self._parse(attrs="certification") self.genres = self._parse(attrs="genres", is_list=True) self.tagsIds = self._parse(attrs="tags", value_type="int", is_list=True) self.tags = self._parse(attrs="tags", value_type="intTag", is_list=True) self.added = self._parse(attrs="added", value_type="date") if "rating" in self._data: self.rating_votes = self._parse(attrs=["rating", "votes"], value_type="int") self.rating_value = self._parse(attrs=["rating", "value"], value_type="float") self.id = self._parse(attrs="id", value_type="int", default_is_none=True) self.seasons = self._parse(attrs="seasons", value_type="season", is_list=True) if self._raw.new_codebase: self.ended = self._parse(attrs="ended", value_type="bool") self.rootFolderPath = self._parse(attrs="rootFolderPath") self.qualityProfileId = self._parse(attrs="qualityProfileId", value_type="int") self.qualityProfile = self._parse(attrs="qualityProfileId", value_type="intQualityProfile") self.seasonCount = self._parse(attrs=["statistics", "seasonCount"], value_type="int") self.totalEpisodeCount = self._parse(attrs=["statistics", "totalEpisodeCount"], value_type="int") self.episodeCount = self._parse(attrs=["statistics", "episodeCount"], value_type="int") self.episodeFileCount = self._parse(attrs=["statistics", "episodeFileCount"], value_type="int") self.sizeOnDisk = self._parse(attrs=["statistics", "sizeOnDisk"], value_type="int") self.percentOfEpisodes = self._parse(attrs=["statistics", "percentOfEpisodes"], value_type="float") else: self.profileId = self._parse(attrs="profileId", value_type="int") self.profile = self._parse(attrs="profileId", value_type="intQualityProfile") self.seasonCount = self._parse(attrs="seasonCount", value_type="int") self.totalEpisodeCount = self._parse(attrs="totalEpisodeCount", value_type="int") self.episodeCount = self._parse(attrs="episodeCount", value_type="int") self.episodeFileCount = self._parse(attrs="episodeFileCount", value_type="int") self.sizeOnDisk = self._parse(attrs="sizeOnDisk", value_type="int") self._finish(self.title) def _full_load(self): if self.id: return self._raw.get_series_id(self.id) elif self.tvdbId: items = self._raw.get_series_lookup(f"tvdb:{self.tvdbId}") if items: return items[0] else: raise NotFound("Item Not Found") else: raise Invalid("Load Failed: No Load Input") def _get_add_data(self, options, path=None): if self.id: raise Exists(f"{self.title} is already in Sonarr") self._data.pop("Id", None) self._data["rootFolderPath"] = options["root_folder"] if path: if not path.startswith(options["root_folder"]): raise Invalid(f"Individual Path: {path} must start with the Root Folder: {options['root_folder']} ") self._data["path"] = path else: self._data["rootFolderPath"] = options["root_folder"] self._data["monitored"] = options["monitored"] self._data["qualityProfileId" if self._raw.new_codebase else "profileId"] = options["quality_profile"] if "language_profile" in options: self._data["languageProfileId"] = options["language_profile"] self._data["seriesType"] = options["series_type"] self._data["seasonFolder"] = options["season_folder"] self._data["addOptions"] = { "searchForMissingEpisodes": options["search"], "searchForCutoffUnmetEpisodes": options["unmet_search"], "monitor": options["monitor"] } if "tags" in options: self._data["tags"] = options["tags"] return self._data
[docs] def add(self, root_folder: Union[str, int, "RootFolder"], quality_profile: Union[str, int, "QualityProfile"], language_profile: Optional[Union[str, int, "LanguageProfile"]] = None, monitor: str = "all", season_folder: bool = True, search: bool = True, unmet_search: bool = True, series_type: str = "standard", tags: Optional[List[Union[str, int, Tag]]] = None) -> None: """ Add this Series to Sonarr. Parameters: root_folder (Union[str, int, RootFolder]): Root Folder for the Series. quality_profile (Union[str, int, QualityProfile]): Quality Profile for the Series. language_profile (Optional[Union[str, int, LanguageProfile]]): Language Profile for the Series. Required for older versions. monitor (bool): How to monitor the Series. Valid options are all, future, missing, existing, pilot, firstSeason, latestSeason, or none. season_folder (bool): Use Season Folders for the Series. search (bool): Start search for missing episodes of the Series after adding. unmet_search (bool): Start search for cutoff unmet episodes of the Series after adding. series_type (str): Series Type for the Series. Valid options are standard, daily, or anime. tags (Optional[List[Union[str, int, Tag]]]): Tags to be added to the Series. Raises: :class:`~arrapi.exceptions.Invalid`: When one of the options given is invalid. :class:`~arrapi.exceptions.Exists`: When the Series already Exists in Sonarr. """ if self._arr.exclusions and self.tvdbId in self._arr.exclusions: raise Excluded(f"TVDb ID: {self.tvdbId} is excluded from being added.") self._load(self._raw.post_series(self._get_add_data(self._arr._validate_add_options( root_folder, quality_profile, language_profile=language_profile, monitor=monitor, season_folder=season_folder, search=search, unmet_search=unmet_search, series_type=series_type, tags=tags ))))
[docs] def edit(self, path: Optional[str] = None, move_files: bool = False, quality_profile: Optional[Union[str, int, "QualityProfile"]] = None, language_profile: Optional[Union[str, int, "LanguageProfile"]] = None, monitor: Optional[str] = None, monitored: Optional[bool] = None, season_folder: Optional[bool] = None, series_type: Optional[str] = None, tags: Optional[List[Union[str, int, Tag]]] = None, apply_tags: str = "add" ) -> None: """ Edit this Series in Sonarr. Parameters: path (Optional[str]): Path to change the Series to. move_files (bool): When changing the path do you want to move the files to the new path. quality_profile (Optional[Union[str, int, QualityProfile]]): Quality Profile to change the Series to. language_profile (Optional[Union[str, int, LanguageProfile]]): Language Profile to change the Series to. monitor (Optional[str]): How you want the Series monitored. Valid options are all, future, missing, existing, pilot, firstSeason, latestSeason, or none. monitored (Optional[bool]): Monitor the Series. season_folder (Optional[bool]): Use Season Folders for the Series. series_type (Optional[str]): Series Type to change the Series to. Valid options are standard, daily, or anime. tags (Optional[List[Union[str, int, Tag]]]): Tags to be added, replaced, or removed from the Series. apply_tags (str): How you want to edit the Tags. Valid options are add, replace, or remove. Raises: :class:`ValueError`: When theres no options given. :class:`~arrapi.exceptions.Invalid`: When one of the options given is invalid. :class:`~arrapi.exceptions.NotFound`: When the Series hasn't been added to Sonarr. """ if not self.id: raise NotFound(f"{self.title} not found in Sonarr, it must be added before editing") options = self._arr._validate_edit_options(path=path, move_files=move_files, quality_profile=quality_profile, language_profile=language_profile, monitor=monitor, monitored=monitored, season_folder=season_folder, series_type=series_type, tags=tags, apply_tags=apply_tags) if "monitor" in options: self._raw.edit_series_monitoring([self.id], options.pop("monitor")) valid_move_files = options["path"] if "path" in options else False for key, value in options.items(): if key == "tags": tag_type = options["applyTags"] if tag_type == "add": self._data[key].extend([t for t in value if t not in self._data["tags"]]) elif tag_type == "remove": self._data[key] = [t for t in self._data["tags"] if t not in value] elif tag_type == "replace": self._data[key] = value else: raise Invalid(f"Invalid apply_tags: '{tag_type}' Options: {self._arr.apply_tags_options}") elif key != ["applyTags", "moveFiles"]: self._data[key] = value self._load(self._raw.put_series_id(self.id, self._data, moveFiles=valid_move_files))
[docs] def delete(self, addImportListExclusion: bool = False, deleteFiles: bool = False) -> None: """ Delete this Series from Sonarr. Parameters: addImportListExclusion (bool): Add Import Exclusion for this Series. deleteFiles (bool): Delete Files for this Series. Raises: :class:`~arrapi.exceptions.NotFound`: When the Series hasn't been added to Sonarr. """ if not self.id: raise NotFound(f"{self.title} not found in Sonarr, it must be added before deleting") self._raw.delete_series_id(self.id, addImportListExclusion=addImportListExclusion, deleteFiles=deleteFiles) self._loading = True self.id = None self._loading = False