From 71599b903a05ac09e921af1762dfea9fa6fa8c7c Mon Sep 17 00:00:00 2001 From: Isabella Basso Date: Wed, 13 Nov 2024 15:02:29 -0300 Subject: [PATCH] Retry on 500 (#168) * workaround: retry manifest upload on quay * decorator: get rid of inheritance * decorator: retry on 500 Signed-off-by: Isabella do Amaral --- CHANGELOG.md | 1 + oras/decorator.py | 64 ++++++++++++++--------------------------------- oras/provider.py | 2 +- oras/version.py | 2 +- 4 files changed, 22 insertions(+), 47 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f35ed91..f7f7f9d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ and **Merged pull requests**. Critical items to know are: The versions coincide with releases on pip. Only major versions will be released as tags on Github. ## [0.0.x](https://github.com/oras-project/oras-py/tree/main) (0.0.x) + - retry on 500 (0.2.25) - align provider config_path type annotations (0.2.24) - add missing prefix property to auth backend (0.2.23) - allow for filepaths to include `:` (0.2.22) diff --git a/oras/decorator.py b/oras/decorator.py index 7730438..db2b2e0 100644 --- a/oras/decorator.py +++ b/oras/decorator.py @@ -3,77 +3,51 @@ __license__ = "Apache-2.0" import time -from functools import partial, update_wrapper +from functools import wraps import oras.auth from oras.logger import logger -class Decorator: - """ - Shared parent decorator class - """ - - def __init__(self, func): - update_wrapper(self, func) - self.func = func - - def __get__(self, obj, objtype): - return partial(self.__call__, obj) - - -class ensure_container(Decorator): +def ensure_container(func): """ Ensure the first argument is a container, and not a string. """ - def __call__(self, cls, *args, **kwargs): + @wraps(func) + def wrapper(cls, *args, **kwargs): if "container" in kwargs: kwargs["container"] = cls.get_container(kwargs["container"]) elif args: container = cls.get_container(args[0]) args = (container, *args[1:]) - return self.func(cls, *args, **kwargs) - - -class classretry(Decorator): - """ - Retry a function that is part of a class - """ - - def __init__(self, func, attempts=5, timeout=2): - super().__init__(func) - self.attempts = attempts - self.timeout = timeout + return func(cls, *args, **kwargs) - def __call__(self, cls, *args, **kwargs): - attempt = 0 - attempts = self.attempts - timeout = self.timeout - while attempt < attempts: - try: - return self.func(cls, *args, **kwargs) - except oras.auth.AuthenticationException as e: - raise e - except Exception as e: - sleep = timeout + 3**attempt - logger.info(f"Retrying in {sleep} seconds - error: {e}") - time.sleep(sleep) - attempt += 1 - return self.func(cls, *args, **kwargs) + return wrapper -def retry(attempts, timeout=2): +def retry(attempts=5, timeout=2): """ A simple retry decorator """ def decorator(func): + @wraps(func) def inner(*args, **kwargs): attempt = 0 while attempt < attempts: try: - return func(*args, **kwargs) + res = func(*args, **kwargs) + if res.status_code == 500: + try: + msg = res.json() + for error in msg.get("errors", []): + if isinstance(error, dict) and "message" in error: + logger.error(error["message"]) + except Exception: + pass + raise ValueError(f"Issue with {res.request.url}: {res.reason}") + return res except oras.auth.AuthenticationException as e: raise e except Exception as e: diff --git a/oras/provider.py b/oras/provider.py index 809ee12..1bb94a7 100644 --- a/oras/provider.py +++ b/oras/provider.py @@ -935,7 +935,7 @@ def get_manifest( jsonschema.validate(manifest, schema=oras.schemas.manifest) return manifest - @decorator.classretry + @decorator.retry() def do_request( self, url: str, diff --git a/oras/version.py b/oras/version.py index 2501083..ba91a0e 100644 --- a/oras/version.py +++ b/oras/version.py @@ -2,7 +2,7 @@ __copyright__ = "Copyright The ORAS Authors." __license__ = "Apache-2.0" -__version__ = "0.2.24" +__version__ = "0.2.25" AUTHOR = "Vanessa Sochat" EMAIL = "vsoch@users.noreply.github.com" NAME = "oras"