Skip to content

Commit

Permalink
Merge pull request #964 from glensc/logical-decorator-retry
Browse files Browse the repository at this point in the history
Refactor: Add retry decorator
  • Loading branch information
glensc authored Jul 6, 2022
2 parents 735ec7b + 2209378 commit 740017e
Show file tree
Hide file tree
Showing 4 changed files with 81 additions and 21 deletions.
17 changes: 4 additions & 13 deletions plextraktsync/decorators/rate_limit.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
from functools import wraps
from time import sleep

from plexapi.exceptions import BadRequest
from requests import RequestException
from trakt.errors import (LockedUserAccountException, RateLimitException,
TraktInternalException)
from trakt.errors import RateLimitException

from plextraktsync.logging import logger

Expand All @@ -23,17 +20,11 @@ def wrapper(*args, **kwargs):
while True:
try:
return fn(*args, **kwargs)
except (
BadRequest,
RateLimitException,
RequestException,
TraktInternalException,
LockedUserAccountException,
) as e:
except RateLimitException as e:
if retry == retries:
logger.error(f"Error: {e}")
logger.error(f"Trakt Error: {e}")
logger.error(
"API didn't respond properly, script will abort now. Please try again later."
"Trakt API didn't respond properly, script will abort now. Please try again later."
)
logger.error(
f"Last call: {fn.__module__}.{fn.__name__}({args[1:]}, {kwargs})"
Expand Down
51 changes: 51 additions & 0 deletions plextraktsync/decorators/retry.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
from functools import wraps
from time import sleep

from plexapi.exceptions import BadRequest
from requests import ReadTimeout, RequestException
from trakt.errors import TraktInternalException

from plextraktsync.logging import logger


def retry(retries=5):
"""
retry a call retries times
:param retries: number of retries
:return:
"""

def decorator(fn):
@wraps(fn)
def wrapper(*args, **kwargs):
count = 0
while True:
try:
return fn(*args, **kwargs)
except (
BadRequest,
ReadTimeout,
RequestException,
TraktInternalException,
) as e:
if count == retries:
logger.error(f"Error: {e}")
logger.error(
"API didn't respond properly, script will abort now. Please try again later."
)
logger.error(
f"Last call: {fn.__module__}.{fn.__name__}({args[1:]}, {kwargs})"
)
exit(1)

seconds = 1 + count
count += 1
logger.warning(
f"{e} for {fn.__module__}.{fn.__name__}(), retrying after {seconds} seconds (try: {count}/{retries})"
)
sleep(seconds)

return wrapper

return decorator
15 changes: 8 additions & 7 deletions plextraktsync/plex_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
from plextraktsync.decorators.flatten import flatten_dict, flatten_list
from plextraktsync.decorators.memoize import memoize
from plextraktsync.decorators.nocache import nocache
from plextraktsync.decorators.rate_limit import rate_limit
from plextraktsync.decorators.retry import retry
from plextraktsync.factory import factory
from plextraktsync.logging import logger

Expand Down Expand Up @@ -161,7 +161,7 @@ def is_legacy_agent(self):
return not self.item.guid.startswith("plex://")

@nocache
@rate_limit()
@retry()
def get_guids(self):
return self.item.guids

Expand Down Expand Up @@ -198,7 +198,7 @@ def type(self):

@cached_property
@nocache
@rate_limit(retries=1)
@retry(retries=1)
def rating(self):
if self.plex is not None:
ratings = self.plex.ratings[self.item.librarySectionID]
Expand Down Expand Up @@ -359,7 +359,7 @@ def episodes(self):
yield PlexLibraryItem(ep, plex=self.plex)

@nocache
@rate_limit()
@retry()
def _get_episodes(self):
return self.item.episodes()

Expand Down Expand Up @@ -457,7 +457,7 @@ def all(self, max_items: int):
break

@nocache
@rate_limit()
@retry()
def fetch_items(self, key: str, size: int, start: int):
return self.section.fetchItems(key, container_start=start, container_size=size)

Expand Down Expand Up @@ -501,6 +501,7 @@ def show_sections(self, library=None) -> List[PlexLibrarySection]:

@memoize
@nocache
@retry()
def fetch_item(self, key: Union[int, str]) -> Optional[PlexLibraryItem]:
try:
media = self.plex.library.fetchItem(key)
Expand Down Expand Up @@ -566,7 +567,7 @@ def ratings(self):
return PlexRatingCollection(self)

@nocache
@rate_limit()
@retry()
def rate(self, m, rating):
m.rate(rating)

Expand Down Expand Up @@ -599,7 +600,7 @@ def history(self, m, device=False, account=False):
yield h

@nocache
@rate_limit()
@retry()
def mark_watched(self, m):
m.markWatched()

Expand Down
19 changes: 18 additions & 1 deletion plextraktsync/trakt_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from plextraktsync.decorators.flatten import flatten_dict
from plextraktsync.decorators.nocache import nocache
from plextraktsync.decorators.rate_limit import rate_limit
from plextraktsync.decorators.retry import retry
from plextraktsync.decorators.time_limit import time_limit
from plextraktsync.factory import factory
from plextraktsync.logging import logger, logging
Expand Down Expand Up @@ -56,6 +57,7 @@ def stop(self, progress: float):
@nocache
@rate_limit()
@time_limit()
@retry()
@post
def _post(self, method: str, progress: float):
self.scrobbler.progress = progress
Expand Down Expand Up @@ -111,6 +113,7 @@ def batch(self):
@cached_property
@nocache
@rate_limit()
@retry()
def me(self):
try:
return trakt.users.User("me")
Expand All @@ -121,30 +124,35 @@ def me(self):
@cached_property
@nocache
@rate_limit()
@retry()
def liked_lists(self):
return pytrakt_extensions.get_liked_lists()

@cached_property
@nocache
@rate_limit()
@retry()
def watched_movies(self):
return set(map(lambda m: m.trakt, self.me.watched_movies))

@cached_property
@nocache
@rate_limit()
@retry()
def movie_collection(self):
return self.me.movie_collection

@cached_property
@nocache
@rate_limit()
@retry()
def show_collection(self):
return self.me.show_collection

@nocache
@rate_limit()
@time_limit()
@retry()
def remove_from_library(self, media: Union[Movie, TVShow, TVSeason, TVEpisode]):
if not isinstance(media, (Movie, TVShow, TVSeason, TVEpisode)):
raise ValueError("Must be valid media type")
Expand All @@ -157,18 +165,21 @@ def movie_collection_set(self):
@cached_property
@nocache
@rate_limit()
@retry()
def watched_shows(self):
return pytrakt_extensions.allwatched()

@cached_property
@nocache
@rate_limit()
@retry()
def collected_shows(self):
return pytrakt_extensions.allcollected()

@cached_property
@nocache
@rate_limit()
@retry()
def watchlist_movies(self):
return self.me.watchlist_movies

Expand All @@ -178,13 +189,14 @@ def ratings(self):

@nocache
@rate_limit()
@time_limit()
@retry()
def get_ratings(self, media_type: str):
return self.me.get_ratings(media_type)

@nocache
@rate_limit()
@time_limit()
@retry()
def rate(self, m, rating):
m.rate(rating)

Expand All @@ -196,6 +208,7 @@ def scrobbler(media: Union[Movie, TVEpisode], threshold=80) -> ScrobblerProxy:
@nocache
@rate_limit()
@time_limit()
@retry()
def mark_watched(self, m, time, show_trakt_id=None):
m.mark_as_seen(time)
if m.media_type == "movies":
Expand Down Expand Up @@ -225,11 +238,13 @@ def add_to_collection(self, m, pm: PlexLibraryItem, batch=False):

@nocache
@rate_limit()
@retry()
def collected(self, tm: TVShow):
return pytrakt_extensions.collected(tm.trakt)

@nocache
@rate_limit()
@retry()
def lookup(self, tm: TVShow):
"""
This lookup-table is accessible via lookup[season][episode]
Expand All @@ -248,6 +263,7 @@ def find_by_guid(self, guid: PlexGuid):
return self.search_by_id(guid.id, id_type=guid.provider, media_type=guid.type)

@rate_limit()
@retry()
def search_by_id(self, media_id: str, id_type: str, media_type: str):
if id_type == "tvdb" and media_type == "movie":
# Skip invalid search.
Expand Down Expand Up @@ -323,6 +339,7 @@ def __init__(self, trakt: TraktApi, batch_delay=None):
@nocache
@rate_limit()
@time_limit()
@retry()
def submit_collection(self):
if self.queue_size() == 0:
return
Expand Down

0 comments on commit 740017e

Please sign in to comment.