{
"info": {
"author": "Nicholas Brochu",
"author_email": "info@nicholasbrochu.com",
"bugtrack_url": null,
"classifiers": [
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Developers",
"License :: OSI Approved :: Apache Software License",
"Natural Language :: English",
"Programming Language :: Python",
"Programming Language :: Python :: 2.7",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.2",
"Programming Language :: Python :: 3.3",
"Programming Language :: Python :: 3.4",
"Programming Language :: Python :: 3.5"
],
"description": "requests-respectful\n===================\n\nIf you know Python, you know\n*`Requests `__*. *Requests* is love.\n*Requests* is life. Depending on your use cases, you may come across\nscenarios where you need to use *Requests* a lot. Services you consume\nmay have rate-limiting policies in place or you may just happen to be in\na good mood and feel like being a good Netizen. This is where\n*requests-respectful* can come in handy.\n\n***requests-respectful***:\n\n- Is a minimalist wrapper on top of *Requests* to work within rate\n limits of any amount of services simultaneously\n- Can scale out of a single thread, single process or even a single\n machine\n- Enables maximizing your allowed requests without ever going over set\n limits and having to handle the fallout\n- Proxies *Requests* HTTP verb methods (for minimal code changes)\n- Works with both Python 2 and 3 and is fully tested\n- Is cool (hopefully?)\n\n**Typical *requests* call**\n\n.. code:: python\n\n import requests\n response = requests.get(\"http://github.com\", params={\"foo\": \"bar\"})\n\n**Magic *requests-respectful* call** - *requests* verb methods are\nproxied!\n\n.. code:: python\n\n from requests_respectful import RespectfulRequester\n\n rr = RespectfulRequester()\n\n # This can be done elsewhere but the realm needs to be registered!\n rr.register_realm(\"Github\", max_requests=100, timespan=60)\n\n response = rr.get(\"http://github.com\", params={\"foo\": \"bar\"}, realms=[\"Github\"], wait=True)\n\n**Conservative *requests-respectful* call** - pass a lambda with a\n*requests* method call\n\n.. code:: python\n\n import requests\n from requests_respectful import RespectfulRequester\n\n rr = RespectfulRequester()\n\n # This can be done elsewhere but the realm needs to be registered!\n rr.register_realm(\"Github\", max_requests=100, timespan=60)\n\n request_func = lambda: requests.get(\"http://github.com\", params={\"foo\": \"bar\"})\n response = rr.request(request_func, realms=[\"Github\"], wait=True)\n\nRequirements\n------------\n\n- `Redis `__ > 2.8.0 (See FAQ if you are rolling your\n eyes)\n\nInstallation\n------------\n\n.. code:: shell\n\n pip install requests-respectful\n\nConfiguration\n-------------\n\nDefault Configuration Values\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n.. code:: python\n\n {\n \"redis\": {\n \"host\": \"localhost\",\n \"port\": 6379,\n \"database\": 0\n },\n \"safety_threshold\": 10,\n \"requests_module_name\": \"requests\"\n }\n\nConfiguration Keys\n~~~~~~~~~~~~~~~~~~\n\n- **redis**: Provides the ``host``, ``port``\\ and ``database`` of the\n Redis instance\n- **safety\\_threshold**: A rate-limited exception will be raised at\n *(realm\\_max\\_requests - safety\\_threshold)*. Prevents going over the\n limit of services in scenarios where a large amount of requests are\n issued in parallel\n- **requests\\_module\\_name**: Provides the name of the *Requests*\n module used in the request lambdas. Should not need to be changed\n unless you import *Requests* as another name.\n\nOverriding Configuration Values\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nWith *requests-respectful.config.yml*\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nThe library auto-detects the presence of a YAML file named\n*requests-respectful.config.yml* at the root of your project and will\nattempt to load configuration values from it.\n\n**Example**:\n\nrequests-respectful.config.yml\n\n.. code:: yaml\n\n redis:\n host: 0.0.0.0\n port: 6379\n database: 5\n\n safety_threshold: 25\n\nWith the *configure()* class method\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nIf you don't like having an extra file lying around, the library can\nalso be configured at runtime using the *configure()* class method.\n\n.. code:: python\n\n RespectfulRequester.configure(\n redis={\"host\": \"0.0.0.0\", \"port\": 6379, \"database\": 5},\n safety_threshold=25\n )\n\n**In both cases, the resulting active configuration would be:**\n\n.. code:: python\n\n RespectfulRequester._config()\n\n Out[1]: {\n \"redis\": {\n \"host\": \"0.0.0.0\",\n \"port\": 6379,\n \"database\": 5\n },\n \"safety_threshold\": 25,\n \"requests_module_name\": \"requests\"\n }\n\nUsage\n-----\n\nIn your quest to use *requests-respectful*, you should only ever have to\nbother with one class: *RespectfulRequester*. Instance this class and\nyou can perform all important operations.\n\nBefore each example, it is assumed that the following code has already\nbeen executed.\n\n.. code:: python\n\n from requests_respectful import RespectfulRequester\n rr = RespectfulRequester()\n\nRealms\n~~~~~~\n\nRealms are simply named containers that are provided with a maximum\nrequesting rate. You are responsible of the management (i.e. CRUD) of\nyour realms.\n\nRealms track the HTTP requests that are performed under them and will\nraise a catchable rate limit exception if you are over their allowed\nrequesting rate.\n\nFetching the list of Realms\n^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n.. code:: python\n\n rr.fetch_registered_realms()\n\nThis returns a list of currently registered realm names.\n\nRegistering a Realm\n^^^^^^^^^^^^^^^^^^^\n\n.. code:: python\n\n rr.register_realm(\"Google\", max_requests=10, timespan=1)\n rr.register_realm(\"Github\", max_requests=100, timespan=60)\n rr.register_realm(\"Twitter\", max_requests=150, timespan=300)\n\n # OR\n realm_tuples = [\n [\"Google\", 10, 1],\n [\"Github\", 100, 60],\n [\"Twitter\", 150, 300]\n ]\n\n rr.register_realms(realm_tuples)\n\nEither of these registers 3 realms: \\* *Google* at a maximum requesting\nrate of 10 requests per second \\* *Github* at a maximum requesting rate\nof 100 requests per minute \\* *Twitter* at a maximum requesting rate of\n150 requests per 5 minutes\n\nUpdating a Realm\n^^^^^^^^^^^^^^^^\n\n.. code:: python\n\n rr.update_realm(\"Google\", max_requests=25, timespan=5)\n\nThis updates the maximum requesting rate of *Google* to 25 requests per\n5 seconds.\n\nGetting the maximum requests value of a Realm\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n.. code:: python\n\n rr.realm_max_requests(\"Google\")\n\nThis would return 25.\n\nGetting the timespan value of a Realm\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n.. code:: python\n\n rr.realm_timespan(\"Google\")\n\nThis would return 5.\n\nUnregistering a Realm\n^^^^^^^^^^^^^^^^^^^^^\n\n.. code:: python\n\n rr.unregister_realm(\"Google\")\n\nThis would unregister the *Google* realm, preventing further queries\nfrom executing on it.\n\nUnregistering multiple Realms\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n.. code:: python\n\n rr.unregister_realms([\"Google\", \"Github\", \"Twitter\"])\n\nThis would unregister all 3 realms in one operation, preventing further\nqueries from executing on them.\n\nRequesting\n~~~~~~~~~~\n\nUsing *Requests* HTTP verb methods\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nThe library supports proxying calls to the 7 *Requests* HTTP verb\nmethods (DELETE, GET, HEAD, OPTIONS, PATCH, POST, PUT). This is\nliterally a *Requests* method so go crazy with your *params*, *body*,\n*headers*, *auth* etc. kwargs. The only major difference is that a\n*realm* kwarg is expected. A *wait* boolean kwargs can also be provided\n(the behavior is explained later).\n\nThese are all valid calls:\n\n.. code:: python\n\n rr.get(\"http://httpbin.org\", realms=[\"HTTPBin\"])\n rr.post('http://httpbin.org/post', data = {'key':'value'}, realms=[\"HTTPBin\"], wait=True)\n rr.put('http://httpbin.org/put', data = {'key':'value'}, realms=[\"HTTPBin\"])\n rr.delete('http://httpbin.org/delete', realms=[\"HTTPBin\"])\n\nIf not rate-limited, these would return your usual *requests.Response*\nobject.\n\nUsing a request lamba\n^^^^^^^^^^^^^^^^^^^^^\n\nIf you are a purist and prefer not using fancy proxying, you are also\nallowed to create a lambda of your *Requests* call and pass it to the\n*request()* instance method.\n\n.. code:: python\n\n request_func = lambda: requests.post('http://httpbin.org/post', data = {'key':'value'})\n rr.request(request_func, realms=[\"HTTPBin\"], wait=True)\n\nIf not rate-limited, this would return your usual *requests.Response*\nobject.\n\nMultiple realms per request\n^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nStarting in 0.2.0, you can have a single request count against multiple\nrealms. The kwarg has been changed from ``realm`` to ``realms`` and\nworks as you would expect it to.\n\n.. code:: python\n\n rr.get(\"http://httpbin.org\", realms=[\"HTTPBin\", \"HTTPBinUser123\", \"HTTPBinServer3\"])\n\nThe kwarg ``realm`` has been deprecated on requesting instance methods.\nIt will still work with a warning until 0.3.0\n\nHandling exceptions\n^^^^^^^^^^^^^^^^^^^\n\nExecuting these calls will either return a *requests.Response* object\nwith the results of the HTTP call or raise a\nRequestsRespectfulRateLimitedError exception. This means that you'll\nlikely want to catch and handle that exception.\n\n.. code:: python\n\n from requests_respectful import RequestsRespectfulRateLimitedError\n\n try:\n response = rr.get(\"http://httpbin.org\", realm=\"HTTPBin\")\n except RequestsRespectfulRateLimitedError:\n pass # Possibly requeue that call or wait.\n\nThe *wait* kwarg\n^^^^^^^^^^^^^^^^\n\nBoth ways of requesting accept a *wait* kwarg that defaults to False. If\nswitched on and the realm is currently rate-limited, the process will\nblock, wait until it is safe to send requests again and perform the\nrequests then. Waiting is perfectly fine for scripts or smaller\noperations but is discouraged for large, multi-realm, parallel tasks\n(i.e. Background Tasks like Celery workers).\n\nTests\n-----\n\n- Exist? ``Yes``\n- Exhaustive? ``Yes``\n- Facepalm tactics?\n ``Yes - Redis calls aren't mocked and google.com gets a few friendly calls``\n\nRun them with ``python -m pytest tests --spec``\n\nFAQ\n---\n\nWhoa, whoa, whoa! Redis?!\n~~~~~~~~~~~~~~~~~~~~~~~~~\n\nYes. The use of Redis allows for *requests-respectful* to go\nmulti-thread, multi-process and even multi-machine while still\nrespecting the maximum requesting rates of registered realms. Operations\nlike Redis' SETEX are key in designing and working with rate-limiting\nsystems. If you are doing Python development, there is a decent chance\nyou already work with Redis as it is one of the two options to use as\nCelery's backend and one of the 2 major caching options in Web\ndevelopment. If not, you can always keep things clean and use a `Docker\nContainer `__ or even `build it from\nsource `__. Redis has kept a\nconsistent record over the years of being lightweight, solid software.\n\nHow is this different than other throttling libraries?\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n- Most other libraries will ask you to specify an interval at which to\n send requests and will literally loop over\n ``request()...time.sleep(interval)``. This one will allow to send as\n many as you want, as fast as you want, as long as you are under the\n maximum requesting rate of your realm.\n- Other libraries don't have the concept of realms and separate\n requesting rate rules.\n- Other libraries don't scale outside of the process.\n- Most other libraries don't integrate this neatly with *Requests*\n\nRoadmap / Contribution Ideas\n----------------------------\n\n- Provide some introspection methods to get live realm stats\n- Create a curses realm stats monitor\n- Provide real-life use cases\n- Read the Docs RST Documentation\n- Mock out the Redis calls in the tests\n- Mock out the Requests calls in the tests",
"description_content_type": null,
"docs_url": null,
"download_url": "UNKNOWN",
"downloads": {
"last_day": -1,
"last_month": -1,
"last_week": -1
},
"home_page": "https://github.com/nbrochu/requests-respectful",
"keywords": null,
"license": "Apache License v2",
"maintainer": null,
"maintainer_email": null,
"name": "requests-respectful",
"package_url": "https://pypi.org/project/requests-respectful/",
"platform": "UNKNOWN",
"project_url": "https://pypi.org/project/requests-respectful/",
"project_urls": {
"Download": "UNKNOWN",
"Homepage": "https://github.com/nbrochu/requests-respectful"
},
"release_url": "https://pypi.org/project/requests-respectful/0.2.0/",
"requires_dist": null,
"requires_python": null,
"summary": "Minimalist wrapper on top of Requests to work within rate limits of any amount of services simultaneously. Parallel processing friendly.",
"version": "0.2.0"
},
"last_serial": 2349662,
"releases": {
"0.1.0": [
{
"comment_text": "",
"digests": {
"md5": "ec11cb6603c2504b9c7de6e6dc771a61",
"sha256": "0f80cf43c0e94197ee5a0d63a3f0edbc2ff924944ca20dc0ff6bcb5302a97d1d"
},
"downloads": -1,
"filename": "requests-respectful-0.1.0.tar.gz",
"has_sig": false,
"md5_digest": "ec11cb6603c2504b9c7de6e6dc771a61",
"packagetype": "sdist",
"python_version": "source",
"requires_python": null,
"size": 7631,
"upload_time": "2016-06-12T21:54:27",
"url": "https://files.pythonhosted.org/packages/fc/5f/9c156d153a2bf11d968643c9ff3019c8f6f2acc4c87acc91a078f5f6b5e6/requests-respectful-0.1.0.tar.gz"
}
],
"0.1.1": [
{
"comment_text": "",
"digests": {
"md5": "f1e517360a2204fa42f36341ecb59ed6",
"sha256": "3f581896215d3da76b372a268c18de941d4c40cc2421fff2ab97ddd188676521"
},
"downloads": -1,
"filename": "requests-respectful-0.1.1.tar.gz",
"has_sig": false,
"md5_digest": "f1e517360a2204fa42f36341ecb59ed6",
"packagetype": "sdist",
"python_version": "source",
"requires_python": null,
"size": 7636,
"upload_time": "2016-06-21T15:47:02",
"url": "https://files.pythonhosted.org/packages/50/84/f3666aa32c0c5eab8c5daa1a01d2fa65b8c31737f625876eff0b424254c0/requests-respectful-0.1.1.tar.gz"
}
],
"0.1.2": [
{
"comment_text": "",
"digests": {
"md5": "347f1a60f3dd35eab3deedca252c7eaf",
"sha256": "96cec0e83f33348fb1a084670228593470b0bf7439e570db994b3bb701b929db"
},
"downloads": -1,
"filename": "requests-respectful-0.1.2.tar.gz",
"has_sig": false,
"md5_digest": "347f1a60f3dd35eab3deedca252c7eaf",
"packagetype": "sdist",
"python_version": "source",
"requires_python": null,
"size": 7669,
"upload_time": "2016-06-21T16:49:26",
"url": "https://files.pythonhosted.org/packages/9d/23/6a5384ecb8534b26733e71e69817a68943db6bacf054bebda397df54d3d3/requests-respectful-0.1.2.tar.gz"
}
],
"0.2.0": [
{
"comment_text": "",
"digests": {
"md5": "897178687b2d7e03f3d64758d4c91615",
"sha256": "3b8e41c17795e92939eecb8fb62dad8650850131069bafff0dd1aa4c071bebca"
},
"downloads": -1,
"filename": "requests-respectful-0.2.0.tar.gz",
"has_sig": false,
"md5_digest": "897178687b2d7e03f3d64758d4c91615",
"packagetype": "sdist",
"python_version": "source",
"requires_python": null,
"size": 11966,
"upload_time": "2016-09-18T22:06:15",
"url": "https://files.pythonhosted.org/packages/57/f6/447364f226f121607c41a7a34d0a3b4b3bce74d9e125e2019e0961e2d0f3/requests-respectful-0.2.0.tar.gz"
}
]
},
"urls": [
{
"comment_text": "",
"digests": {
"md5": "897178687b2d7e03f3d64758d4c91615",
"sha256": "3b8e41c17795e92939eecb8fb62dad8650850131069bafff0dd1aa4c071bebca"
},
"downloads": -1,
"filename": "requests-respectful-0.2.0.tar.gz",
"has_sig": false,
"md5_digest": "897178687b2d7e03f3d64758d4c91615",
"packagetype": "sdist",
"python_version": "source",
"requires_python": null,
"size": 11966,
"upload_time": "2016-09-18T22:06:15",
"url": "https://files.pythonhosted.org/packages/57/f6/447364f226f121607c41a7a34d0a3b4b3bce74d9e125e2019e0961e2d0f3/requests-respectful-0.2.0.tar.gz"
}
]
}