Skip to content

Commit

Permalink
Retry on 500 (#168)
Browse files Browse the repository at this point in the history
* workaround: retry manifest upload on quay
* decorator: get rid of inheritance
* decorator: retry on 500

Signed-off-by: Isabella do Amaral <idoamara@redhat.com>
  • Loading branch information
isinyaaa authored Nov 13, 2024
1 parent ff9c2e2 commit 71599b9
Show file tree
Hide file tree
Showing 4 changed files with 22 additions and 47 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
64 changes: 19 additions & 45 deletions oras/decorator.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion oras/provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion oras/version.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down

0 comments on commit 71599b9

Please sign in to comment.