{ "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" } ] }