-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 2bde538
Showing
9 changed files
with
243 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
|
||
# Minimum sleep time in seconds. Integer. Defaults to 10. | ||
MIN_SLEEP_TIME=10 | ||
|
||
# Maximum sleep time in seconds. Integer. Defaults to 20. | ||
MAX_SLEEP_TIME=20 | ||
|
||
# Maximum number of times to retry. Integer. Defaults to 10. | ||
MAX_RETRIES=10 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
name: MyPy | ||
|
||
on: pull_request | ||
|
||
jobs: | ||
deploy: | ||
runs-on: ubuntu-20.04 | ||
|
||
steps: | ||
- uses: actions/checkout@v2 | ||
with: | ||
fetch-depth: 0 | ||
|
||
- name: Setup Python (faster than using Python container) | ||
uses: actions/setup-python@v2 | ||
with: | ||
python-version: "3.9" | ||
|
||
- name: Install MyPy | ||
run: | | ||
python -m pip install --upgrade pip | ||
pip install mypy | ||
- name: Run MyPy | ||
run: mypy ./eth_retry --strict --pretty |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
name: Upload Python Package | ||
|
||
on: | ||
push: | ||
branches: | ||
- dev | ||
release: | ||
branches: | ||
- master | ||
|
||
jobs: | ||
deploy: | ||
runs-on: ubuntu-20.04 | ||
|
||
steps: | ||
- uses: actions/checkout@v2 | ||
with: | ||
fetch-depth: 0 | ||
- uses: actions/setup-python@v2 | ||
- name: Install dependencies | ||
run: | | ||
python -m pip install --upgrade pip | ||
pip install setuptools wheel twine | ||
- name: Build and publish | ||
env: | ||
TWINE_USERNAME: __token__ | ||
TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} | ||
run: | | ||
python setup.py sdist bdist_wheel | ||
twine upload dist/* |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
build | ||
dist | ||
.eggs | ||
eth_retry.egg-info | ||
__pycache__ | ||
env | ||
.mypy_cache |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
|
||
# eth_retry | ||
> Stop transient errors from wasting your time! | ||
`eth_retry` is a Python library that provides one decorator, `eth_retry.auto_retry`. | ||
|
||
`auto_retry` will automatically catch known transient exceptions that are common in the Ethereum/EVM ecosystem and will reattempt to evaluate your decorated function up to `os.environ['MAX_RETRIES']` (default: 10) times. | ||
|
||
------------ | ||
|
||
Covers many common transient errors in the EVM ecosystem, including: | ||
- RPC timeouts | ||
- Block explorer API rate-limiting | ||
- Generic exceptions: | ||
- ConnectionError | ||
- HTTPError | ||
- ReadTimeout | ||
- TimeoutError | ||
- eth-brownie specific errors: | ||
- sqlite3.OperationalError: database is locked | ||
|
||
## Installation: | ||
`pip install eth_retry` | ||
or | ||
`pip install git+https://github.com/BobTheBuidler/eth_retry.git` | ||
|
||
## Usage: | ||
``` | ||
import eth_retry | ||
@eth_retry.auto_retry | ||
def some_function_that_errors_sometimes(): | ||
i = 0 | ||
am = 1 | ||
doing = 2 | ||
stuff = 3 | ||
print("Woohoo it worked!") | ||
return stuff | ||
error_free_result = some_function_that_errors_sometimes() | ||
``` | ||
|
||
Between attempts, eth_retry will `time.sleep` for a random period between `os.environ['MIN_SLEEP_TIME']` (default: 10) and `os.environ['MAX_SLEEP_TIME']` (default: 20) seconds. The period is randomized to help prevent repetitive rate-limiting issues with parallelism by staggering the retries. | ||
|
||
On the `n`th retry, the sleep period is multiplied by `n` so that the target endpoint can cool off in case of rate-limiting. | ||
|
||
After `os.environ['MAX_RETRIES']` failures, eth_retry will raise the exception. | ||
|
||
## Environment: | ||
``` | ||
# Minimum sleep time in seconds. Integer. Defaults to 10. | ||
MIN_SLEEP_TIME=10 | ||
# Maximum sleep time in seconds. Integer. Defaults to 20. | ||
MAX_SLEEP_TIME=20 | ||
# Maximum number of times to retry. Integer. Defaults to 10. | ||
MAX_RETRIES=10 | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
import functools | ||
import logging | ||
import os | ||
from random import randrange | ||
from time import sleep | ||
from typing import Any, Callable | ||
|
||
from eth_retry.conditional_imports import (HTTPError, # type: ignore | ||
OperationalError, ReadTimeout) | ||
|
||
logger = logging.getLogger('eth_retry') | ||
|
||
MIN_SLEEP_TIME = int(os.environ.get("MIN_SLEEP_TIME", 10)) | ||
MAX_SLEEP_TIME = int(os.environ.get("MAX_SLEEP_TIME", 20)) | ||
MAX_RETRIES = int(os.environ.get("MAX_RETRIES", 10)) | ||
|
||
|
||
def auto_retry(func: Callable[...,Any]) -> Callable[...,Any]: | ||
''' | ||
Decorator that will retry the function on: | ||
- ConnectionError | ||
- HTTPError | ||
- TimeoutError | ||
- ReadTimeout | ||
It will also retry on specific ValueError exceptions: | ||
- Max rate limit reached | ||
- please use API Key for higher rate limit | ||
- execution aborted (timeout = 5s) | ||
On repeat errors, will retry in increasing intervals. | ||
''' | ||
|
||
@functools.wraps(func) | ||
def auto_retry_wrap(*args: Any, **kwargs: Any) -> Any: | ||
failures = 0 | ||
sleep_time = randrange(MIN_SLEEP_TIME,MAX_SLEEP_TIME) | ||
while True: | ||
|
||
# Attempt to execute `func` and return response | ||
try: | ||
return func(*args, **kwargs) | ||
|
||
# Generic exceptions | ||
except (ConnectionError, HTTPError, TimeoutError, ReadTimeout) as e: | ||
# This happens when we pass too large of a request to the node. Do not retry. | ||
if failures > MAX_RETRIES or 'Too Large' in str(e) or '404' in str(e): | ||
raise | ||
logger.warning(f'{str(e)} [{failures}]') | ||
|
||
# Specific exceptions | ||
except OperationalError as e: | ||
# This happens when brownie's deployments.db gets locked. Just retry. | ||
if failures > MAX_RETRIES or 'database is locked' not in str(e): | ||
raise | ||
logger.warning(f'{str(e)} [{failures}]') | ||
except ValueError as e: | ||
retry_on_errs = ( | ||
# Occurs on any chain when making computationally intensive calls. Just retry. | ||
# Sometimes works, sometimes doesn't. Worth a shot. | ||
'execution aborted (timeout = 5s)', | ||
|
||
# From block explorer while interacting with api. Just retry. | ||
'Max rate limit reached', | ||
'please use API Key for higher rate limit', | ||
|
||
# Occurs occasionally on AVAX when node is slow to sync. Just retry. | ||
'after last accepted block', | ||
) | ||
if failures > MAX_RETRIES or not any(err in str(e) for err in retry_on_errs): | ||
raise | ||
logger.warning(f'{str(e)} [{failures}]') | ||
|
||
# Attempt failed, sleep time | ||
failures += 1 | ||
sleep(failures * sleep_time) | ||
|
||
return auto_retry_wrap |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
|
||
class DummyException(Exception): | ||
pass | ||
|
||
try: | ||
from sqlite3 import OperationalError | ||
except ModuleNotFoundError: | ||
OperationalError = DummyException # type: ignore | ||
|
||
try: | ||
from requests.exceptions import HTTPError, ReadTimeout | ||
except ModuleNotFoundError: | ||
HTTPError, ReadTimeout = DummyException, DummyException # type: ignore |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
[metadata] | ||
description-file = README.md |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
from setuptools import find_packages, setup | ||
|
||
setup( | ||
name='eth_retry', | ||
packages=find_packages(), | ||
use_scm_version={ | ||
"root": ".", | ||
"relative_to": __file__, | ||
"local_scheme": "no-local-version", | ||
"version_scheme": "python-simplified-semver", | ||
}, | ||
description='Provides a decorator that automatically catches known transient exceptions that are common in the Ethereum/EVM ecosystem and reattempts to evaluate your decorated function', | ||
author='BobTheBuidler', | ||
author_email='bobthebuidlerdefi@gmail.com', | ||
url='https://github.com/BobTheBuidler/eth_retry', | ||
license='MIT', | ||
setup_requires=[ | ||
'setuptools_scm', | ||
], | ||
) |