Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add retry decorator #88

Merged
merged 10 commits into from
Apr 7, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Inspired by [this](http://pastebin.com/8fBVpjaj) wrapper written by 'oipminer'
- The `poloniex.Poloniex(timeout=3)` attribute/arg adjusts the number of seconds to wait for a response from poloniex, else `requests.exceptions.Timeout` is raised.
- A call restrictor (`coach`) is enabled by default, limiting the api wrapper to 6 calls per second. If you wish, you can deactivate the coach using `Poloniex(coach=False)` or use an 'external' coach.
- By default, json floats are parsed as strings (ints are ints), you can define `Poloniex(jsonNums=float)` to have _all numbers_ (floats _and_ ints) parsed as floats (or import and use `decimal.Decimal`).
- `poloniex.coach` and `poloniex.Poloniex` have self named loggers. You can define the log level of the `requests` module by defining `Poloniex(loglevel=logging.DEBUG)` (this also changes the log level of the `Poloniex` object).
- `poloniex.coach`, 'poloniex.retry', and `poloniex` have self named loggers.

## Install:
Python 2:
Expand Down
38 changes: 20 additions & 18 deletions poloniex/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,21 +23,27 @@
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
# python 3 voodoo
try:
from urllib.parse import urlencode as _urlencode
except:
from urllib import urlencode as _urlencode
from json import loads as _loads
from hmac import new as _new
from hashlib import sha512 as _sha512
from time import time
import logging
# pip
# 3rd party
from requests.exceptions import RequestException
from requests import post as _post
from requests import get as _get
# local
from .coach import Coach
# python 3 voodoo
try:
from urllib.parse import urlencode as _urlencode
except ImportError:
from urllib import urlencode as _urlencode
from .retry import retry
# logger
logger = logging.getLogger(__name__)

retryDelays = (0, 2, 5, 30)

# Possible Commands
PUBLIC_COMMANDS = [
Expand Down Expand Up @@ -85,30 +91,24 @@ class Poloniex(object):

def __init__(
self, Key=False, Secret=False,
timeout=3, coach=True, loglevel=False, jsonNums=False):
timeout=1, coach=True, jsonNums=False):
"""
Key = str api key supplied by Poloniex
Secret = str secret hash supplied by Poloniex
timeout = int time in sec to wait for an api response
(otherwise 'requests.exceptions.Timeout' is raised)
coach = bool to indicate if the api coach should be used
loglevel = logging level object to set the module at
(changes the requests module as well)

self.apiCoach = object that regulates spacing between api calls
jsonNums = datatype to use when parsing json ints and floats

# Time Placeholders # (MONTH == 30*DAYS)

self.MINUTE, self.HOUR, self.DAY, self.WEEK, self.MONTH, self.YEAR
"""
self.logger = logging.getLogger(__name__)
if loglevel:
logging.getLogger("requests").setLevel(loglevel)
logging.getLogger("urllib3").setLevel(loglevel)
self.logger.setLevel(loglevel)
# Call coach, set nonce
if coach is True:
coach = Coach()
self.logger = logger
self.retryDelays = retryDelays
self.coach, self._nonce = coach, int(time() * 1000)
# json number datatypes
self.jsonNums = jsonNums
Expand All @@ -126,6 +126,7 @@ def nonce(self):
return self._nonce

# -----------------Meat and Potatos---------------------------------------
@retry(delays=retryDelays, exception=RequestException)
def __call__(self, command, args={}):
"""
Main Api Function
Expand Down Expand Up @@ -251,7 +252,8 @@ def marketTradeHist(self, pair, start=False, end=False):
"""
if self.coach:
self.coach.wait()
args = {'command': 'returnTradeHistory', 'currencyPair': str(pair).upper()}
args = {'command': 'returnTradeHistory',
'currencyPair': str(pair).upper()}
if start:
args['start'] = start
if end:
Expand All @@ -275,7 +277,7 @@ def generateNewAddress(self, coin):
""" Creates a new deposit address for <coin> """
return self.__call__('generateNewAddress', {
'currency': coin})

def returnTradeHistory(self, pair='all', start=False, end=False):
""" Returns private trade history for <pair> """
args = {'currencyPair': str(pair).upper()}
Expand Down
6 changes: 2 additions & 4 deletions poloniex/coach.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
from time import time, sleep
from threading import Semaphore, Timer
from collections import deque

logger = logging.getLogger(__name__)


Expand All @@ -41,7 +40,7 @@ def __init__(self, timeFrame=1.0, callLimit=6):

def wait(self):
""" Makes sure our api calls don't go past the api call limit """
self.semaphore.acquire() # blocking call
self.semaphore.acquire() # blocking call
# delayed release
timer = Timer(self.timeFrame, self.semaphore.release)
# allows the timer to be canceled on exit
Expand Down Expand Up @@ -88,8 +87,7 @@ def wait(self):

if __name__ == '__main__':
import random
logging.basicConfig()
logger.setLevel(logging.DEBUG)
logging.basicConfig(level=logging.DEBUG)
coach = Coach()
for i in range(50):
logger.debug(i)
Expand Down
26 changes: 26 additions & 0 deletions poloniex/retry.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# http://code.activestate.com/recipes/580745-retry-decorator-in-python/
from itertools import chain
from time import sleep
import logging
logger = logging.getLogger(__name__)


def retry(delays=(0, 1, 5, 30),
exception=Exception):
def wrapper(function):
def wrapped(*args, **kwargs):
problems = []
for delay in chain(delays, [None]):
try:
return function(*args, **kwargs)
except exception as problem:
problems.append(problem)
if delay is None:
logger.error(problems)
raise
else:
logger.debug(problem)
logger.info("-- delaying for %ds", delay)
sleep(delay)
return wrapped
return wrapper