diff --git a/README.md b/README.md index e8be083..88077bb 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,10 @@ Authentication keys are currently tied to an individual user's account. To issue To get your organization ID, look at the URL path when you go to the Manager app while logged in. +## Rate Limits + +This client sleeps for .5 seconds after every request. Thus, in a single thread, requests are limited to 120 per minute. This is done to avoid rate limiting. Staffjoy's API currently rate limits to 300 requests per second across keys and IPs. Thus, by using this library, you should never encounter a rate limit (assuming one executing thread per IP address). + ## Updates If you use this library, please subscribe to the [Staffjoy API Updates Google Group](https://groups.google.com/forum/#!forum/staffjoy-api-updates) for important notifications about changes and deprecations. @@ -62,4 +66,3 @@ loc.delete() ``` - diff --git a/setup.py b/setup.py index d896206..2d44b61 100644 --- a/setup.py +++ b/setup.py @@ -1,6 +1,6 @@ from setuptools import setup, find_packages -version = "0.13" +version = "0.14" setup(name="staffjoy", packages=find_packages(), version=version, diff --git a/staffjoy/config.py b/staffjoy/config.py index 4b09018..514058f 100644 --- a/staffjoy/config.py +++ b/staffjoy/config.py @@ -5,6 +5,7 @@ class DefaultConfig: ENV = "prod" LOG_LEVEL = logging.INFO BASE = "https://www.staffjoy.com/api/v2/" + REQUEST_SLEEP = 0.5 class StageConfig(DefaultConfig): diff --git a/staffjoy/resource.py b/staffjoy/resource.py index 87438c5..d66ac6e 100644 --- a/staffjoy/resource.py +++ b/staffjoy/resource.py @@ -1,4 +1,5 @@ import requests +import time from copy import copy from .config import config_from_env @@ -6,6 +7,9 @@ class Resource: + # Seconds to sleep between requests (bc of rate limits) + REQUEST_SLEEP = 0.5 + PATH = "" # URL path added to base, including route variables ID_NAME = None # What is this ID called in the route of children? META_ENVELOPES = [] # Metadata keys for what to unpack from response @@ -70,6 +74,7 @@ def get_all(cls, parent=None, **params): r = requests.get(base_obj._url(), auth=(base_obj.key, ""), params=params) + time.sleep(cls.REQUEST_SLEEP) if r.status_code not in cls.TRUTHY_CODES: return base_obj._handle_request_exception(r) @@ -117,6 +122,8 @@ def _handle_request_exception(request): def fetch(self): """Perform a read request against the resource""" r = requests.get(self._url(), auth=(self.key, "")) + time.sleep(self.REQUEST_SLEEP) + if r.status_code not in self.TRUTHY_CODES: return self._handle_request_exception(r) @@ -138,12 +145,16 @@ def delete(self): """Delete the object""" r = requests.delete(self._url(), auth=(self.key, "")) + time.sleep(self.REQUEST_SLEEP) + if r.status_code not in self.TRUTHY_CODES: return self._handle_request_exception(r) def patch(self, **kwargs): """Change attributes of the item""" r = requests.patch(self._url(), auth=(self.key, ""), data=kwargs) + time.sleep(self.REQUEST_SLEEP) + if r.status_code not in self.TRUTHY_CODES: return self._handle_request_exception(r) @@ -165,6 +176,7 @@ def create(cls, parent=None, **kwargs): obj = cls(key=parent.key, route=route, config=parent.config) response = requests.post(obj._url(), auth=(obj.key, ""), data=kwargs) + time.sleep(cls.REQUEST_SLEEP) if response.status_code not in cls.TRUTHY_CODES: return cls._handle_request_exception(response)