requests-ratelimiter


Namerequests-ratelimiter JSON
Version 0.6.0 PyPI version JSON
download
home_pagehttps://github.com/JWCook/requests-ratelimiter
SummaryRate-limiting for the requests library
upload_time2024-02-29 20:40:17
maintainer
docs_urlNone
authorJordan Cook
requires_python>=3.7,<4.0
licenseMIT
keywords requests rate-limiting leaky-bucket
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # Requests-Ratelimiter
[![Build
status](https://github.com/JWCook/requests-ratelimiter/workflows/Build/badge.svg)](https://github.com/JWCook/requests-ratelimiter/actions)
[![Codecov](https://codecov.io/gh/JWCook/requests-ratelimiter/branch/main/graph/badge.svg)](https://codecov.io/gh/JWCook/requests-ratelimiter)
[![Documentation Status](https://img.shields.io/readthedocs/requests-ratelimiter/stable?label=docs)](https://requests-ratelimiter.readthedocs.io)
[![PyPI](https://img.shields.io/pypi/v/requests-ratelimiter?color=blue)](https://pypi.org/project/requests-ratelimiter)
[![Conda](https://img.shields.io/conda/vn/conda-forge/requests-ratelimiter?color=blue)](https://anaconda.org/conda-forge/requests-ratelimiter)
[![PyPI - Python Versions](https://img.shields.io/pypi/pyversions/requests-ratelimiter)](https://pypi.org/project/requests-ratelimiter)
[![PyPI - Format](https://img.shields.io/pypi/format/requests-ratelimiter?color=blue)](https://pypi.org/project/requests-ratelimiter)

This package is a simple wrapper around [pyrate-limiter v2](https://github.com/vutran1710/PyrateLimiter/tree/v2.10.0)
that adds convenient integration with the [requests](https://requests.readthedocs.io) library.

Full project documentation can be found at [requests-ratelimiter.readthedocs.io](https://requests-ratelimiter.readthedocs.io).


# Features
* `pyrate-limiter` is a general-purpose rate-limiting library that implements the leaky bucket
  algorithm, supports multiple rate limits, and has optional persistence with SQLite and Redis
  backends
* `requests-ratelimiter` adds some conveniences for sending rate-limited HTTP requests with the
  `requests` library
* It can be used as either a
  [session](https://requests.readthedocs.io/en/latest/user/advanced/#session-objects) or a
  [transport adapter](https://requests.readthedocs.io/en/latest/user/advanced/#transport-adapters)
* It can also be used as a mixin, for compatibility with other `requests`-based libraries
* Rate limits are tracked separately per host
* Different rate limits can optionally be applied to different hosts

# Installation
```
pip install requests-ratelimiter
```

# Usage

## Usage Options
There are three ways to use `requests-ratelimiter`:

### Session
The simplest option is
[`LimiterSession`](https://requests-ratelimiter.readthedocs.io/en/stable/reference.html#requests_ratelimiter.LimiterSession),
which can be used as a drop-in replacement for
[`requests.Session`](https://requests.readthedocs.io/en/latest/api/#requests.Session).

Example:
```python
from requests_ratelimiter import LimiterSession
from time import time

# Apply a rate limit of 5 requests per second to all requests
session = LimiterSession(per_second=5)
start = time()

# Send requests that stay within the defined rate limit
for i in range(20):
    response = session.get('https://httpbin.org/get')
    print(f'[t+{time()-start:.2f}] Sent request {i+1}')
```

Example output:
```bash
[t+0.22] Sent request 1
[t+0.26] Sent request 2
[t+0.30] Sent request 3
[t+0.34] Sent request 4
[t+0.39] Sent request 5
[t+1.24] Sent request 6
[t+1.28] Sent request 7
[t+1.32] Sent request 8
[t+1.37] Sent request 9
[t+1.41] Sent request 10
[t+2.04] Sent request 11
...
```

### Adapter
For more advanced usage,
[`LimiterAdapter`](https://requests-ratelimiter.readthedocs.io/en/stable/reference.html#requests_ratelimiter.LimiterAdapter)
is available to be used as a
[transport adapter](https://requests.readthedocs.io/en/latest/user/advanced/#transport-adapters).

Example:
```python
from requests import Session
from requests_ratelimiter import LimiterAdapter

session = Session()

# Apply a rate-limit (5 requests per second) to all requests
adapter = LimiterAdapter(per_second=5)
session.mount('http://', adapter)
session.mount('https://', adapter)

# Send rate-limited requests
for user_id in range(100):
    response = session.get(f'https://api.some_site.com/v1/users/{user_id}')
    print(response.json())
```

### Mixin
Finally,
[`LimiterMixin`](https://requests-ratelimiter.readthedocs.io/en/stable/reference.html#requests_ratelimiter.LimiterMixin)
is available for advanced use cases in which you want add rate-limiting features to a custom session
or adapter class. See
[Custom Session Example](#custom-session-example-requests-cache) below for an example.

## Rate Limit Settings
### Basic Settings
The following parameters are available for the most common rate limit intervals:
* `per_second`: Max requests per second
* `per_minute`: Max requests per minute
* `per_hour`: Max requests per hour
* `per_day`: Max requests per day
* `per_month`: Max requests per month
* `burst`: Max number of consecutive requests allowed before applying per-second rate-limiting

<!-- TODO: Section explaining burst rate limit -->

### Advanced Settings
If you need to define more complex rate limits, you can create a `Limiter` object instead:
```python
from pyrate_limiter import Duration, RequestRate, Limiter
from requests_ratelimiter import LimiterSession

nanocentury_rate = RequestRate(10, Duration.SECOND * 3.156)
fortnight_rate = RequestRate(1000, Duration.DAY * 14)
trimonthly_rate = RequestRate(10000, Duration.MONTH * 3)
limiter = Limiter(nanocentury_rate, fortnight_rate, trimonthly_rate)

session = LimiterSession(limiter=limiter)
```

See [pyrate-limiter docs](https://pyratelimiter.readthedocs.io/en/latest/#basic-usage) for more `Limiter` usage details.

## Backends
By default, rate limits are tracked in memory and are not persistent. You can optionally use either
SQLite or Redis to persist rate limits across threads, processes, and/or application restarts.
You can specify which backend to use with the `bucket_class` argument. For example, to use SQLite:
```python
from pyrate_limiter import SQLiteBucket
from requests_ratelimiter import LimiterSession

session = LimiterSession(per_second=5, bucket_class=SQLiteBucket)
```

See [pyrate-limiter docs](https://pyratelimiter.readthedocs.io/en/latest/#backends) for more details.

## Other Features
### Per-Host Rate Limit Tracking
With either `LimiterSession` or `LimiterAdapter`, rate limits are tracked separately for each host.
In other words, requests sent to one host will not count against the rate limit for any other hosts:

```python
session = LimiterSession(per_second=5)

# Make requests for two different hosts
for _ in range(10):
    response = session.get(f'https://httpbin.org/get')
    print(response.json())
    session.get(f'https://httpbingo.org/get')
    print(response.json())
```

If you have a case where multiple hosts share the same rate limit, you can disable this behavior
with the `per_host` option:
```python
session = LimiterSession(per_second=5, per_host=False)
```

### Per-Host Rate Limit Definitions
With `LimiterAdapter`, you can apply different rate limits to different hosts or URLs:
```python
# Apply a different set of rate limits (2/second and 100/minute) to a specific host
adapter_2 = LimiterAdapter(per_second=2, per_minute=100)
session.mount('https://api.some_site.com', adapter_2)
```

Behavior for matching requests is the same as other transport adapters: `requests` will use the
adapter with the most specific (i.e., longest) URL prefix that matches a given request. For example:
```python
session.mount('https://api.some_site.com/v1', adapter_3)
session.mount('https://api.some_site.com/v1/users', adapter_4)

# This request will use adapter_3
session.get('https://api.some_site.com/v1/')

# This request will use adapter_4
session.get('https://api.some_site.com/v1/users/1234')
```

### Custom Tracking
For advanced use cases, you can define your own custom tracking behavior with the `bucket` option.
For example, an API that enforces rate limits based on a tenant ID, this feature can be used to track
rate limits per tenant. If `bucket` is specified, host tracking is disabled.

Note: It is advisable to use SQLite or Redis backends when using custom tracking because using the default backend
each session will track rate limits independently, even if both sessions call the same URL.
```python
sessionA = LimiterSession(per_second=5, bucket='tenant1')
sessionB = LimiterSession(per_second=5, bucket='tenant2')
```

### Rate Limit Error Handling
Sometimes, server-side rate limiting may not behave exactly as documented (or may not be documented
at all). Or you might encounter other scenarios where your client-side limit gets out of sync with
the server-side limit. Typically, a server will send a `429: Too Many Requests` response for an
exceeded rate limit.

When this happens, `requests-ratelimiter` will adjust its request log in an attempt to catch up to
the server-side limit. If a server sends a different status code other than 429 to indicate an
exceeded limit, you can set this with `limit_statuses`:
```python
session = LimiterSession(per_second=5, limit_statuses=[429, 500])
```

Or if you would prefer to disable this behavior and handle it yourself:
```python
session = LimiterSession(per_second=5, limit_statuses=[])
```

# Compatibility
There are many other useful libraries out there that add features to `requests`, most commonly by
extending or modifying
[requests.Session](https://requests.readthedocs.io/en/latest/api/#requests.Session) or
[requests.HTTPAdapter](https://requests.readthedocs.io/en/latest/api/#requests.adapters.HTTPAdapter).

To use `requests-ratelimiter` with one of these libraries, you have a few different options:
1. If the library provides a custom `Session` class, mount a `LimiterAdapter` on it
2. Or use `LimiterMixin` to create a custom `Session` class with features from both libraries
3. If the library provides a custom `Adapter` class, use `LimiterMixin` to create a custom `Adapter`
   class with features from both libraries

## Custom Session Example: Requests-Cache
For example, to combine with [requests-cache](https://github.com/requests-cache/requests-cache), which
also includes a separate mixin class:
```python
from requests import Session
from requests_cache import CacheMixin, RedisCache
from requests_ratelimiter import LimiterMixin, RedisBucket


class CachedLimiterSession(CacheMixin, LimiterMixin, Session):
    """Session class with caching and rate-limiting behavior. Accepts arguments for both
    LimiterSession and CachedSession.
    """


# Optionally use Redis as both the bucket backend and the cache backend
session = CachedLimiterSession(
    per_second=5,
    bucket_class=RedisBucket,
    backend=RedisCache(),
)
```

This example has an extra benefit: cache hits won't count against your rate limit!

            

Raw data

            {
    "_id": null,
    "home_page": "https://github.com/JWCook/requests-ratelimiter",
    "name": "requests-ratelimiter",
    "maintainer": "",
    "docs_url": null,
    "requires_python": ">=3.7,<4.0",
    "maintainer_email": "",
    "keywords": "requests,rate-limiting,leaky-bucket",
    "author": "Jordan Cook",
    "author_email": "",
    "download_url": "https://files.pythonhosted.org/packages/6d/90/82c076f226828ebdbacc5c9e265f47d3fa51ca4b674eb7e3d9ee0b38f305/requests_ratelimiter-0.6.0.tar.gz",
    "platform": null,
    "description": "# Requests-Ratelimiter\n[![Build\nstatus](https://github.com/JWCook/requests-ratelimiter/workflows/Build/badge.svg)](https://github.com/JWCook/requests-ratelimiter/actions)\n[![Codecov](https://codecov.io/gh/JWCook/requests-ratelimiter/branch/main/graph/badge.svg)](https://codecov.io/gh/JWCook/requests-ratelimiter)\n[![Documentation Status](https://img.shields.io/readthedocs/requests-ratelimiter/stable?label=docs)](https://requests-ratelimiter.readthedocs.io)\n[![PyPI](https://img.shields.io/pypi/v/requests-ratelimiter?color=blue)](https://pypi.org/project/requests-ratelimiter)\n[![Conda](https://img.shields.io/conda/vn/conda-forge/requests-ratelimiter?color=blue)](https://anaconda.org/conda-forge/requests-ratelimiter)\n[![PyPI - Python Versions](https://img.shields.io/pypi/pyversions/requests-ratelimiter)](https://pypi.org/project/requests-ratelimiter)\n[![PyPI - Format](https://img.shields.io/pypi/format/requests-ratelimiter?color=blue)](https://pypi.org/project/requests-ratelimiter)\n\nThis package is a simple wrapper around [pyrate-limiter v2](https://github.com/vutran1710/PyrateLimiter/tree/v2.10.0)\nthat adds convenient integration with the [requests](https://requests.readthedocs.io) library.\n\nFull project documentation can be found at [requests-ratelimiter.readthedocs.io](https://requests-ratelimiter.readthedocs.io).\n\n\n# Features\n* `pyrate-limiter` is a general-purpose rate-limiting library that implements the leaky bucket\n  algorithm, supports multiple rate limits, and has optional persistence with SQLite and Redis\n  backends\n* `requests-ratelimiter` adds some conveniences for sending rate-limited HTTP requests with the\n  `requests` library\n* It can be used as either a\n  [session](https://requests.readthedocs.io/en/latest/user/advanced/#session-objects) or a\n  [transport adapter](https://requests.readthedocs.io/en/latest/user/advanced/#transport-adapters)\n* It can also be used as a mixin, for compatibility with other `requests`-based libraries\n* Rate limits are tracked separately per host\n* Different rate limits can optionally be applied to different hosts\n\n# Installation\n```\npip install requests-ratelimiter\n```\n\n# Usage\n\n## Usage Options\nThere are three ways to use `requests-ratelimiter`:\n\n### Session\nThe simplest option is\n[`LimiterSession`](https://requests-ratelimiter.readthedocs.io/en/stable/reference.html#requests_ratelimiter.LimiterSession),\nwhich can be used as a drop-in replacement for\n[`requests.Session`](https://requests.readthedocs.io/en/latest/api/#requests.Session).\n\nExample:\n```python\nfrom requests_ratelimiter import LimiterSession\nfrom time import time\n\n# Apply a rate limit of 5 requests per second to all requests\nsession = LimiterSession(per_second=5)\nstart = time()\n\n# Send requests that stay within the defined rate limit\nfor i in range(20):\n    response = session.get('https://httpbin.org/get')\n    print(f'[t+{time()-start:.2f}] Sent request {i+1}')\n```\n\nExample output:\n```bash\n[t+0.22] Sent request 1\n[t+0.26] Sent request 2\n[t+0.30] Sent request 3\n[t+0.34] Sent request 4\n[t+0.39] Sent request 5\n[t+1.24] Sent request 6\n[t+1.28] Sent request 7\n[t+1.32] Sent request 8\n[t+1.37] Sent request 9\n[t+1.41] Sent request 10\n[t+2.04] Sent request 11\n...\n```\n\n### Adapter\nFor more advanced usage,\n[`LimiterAdapter`](https://requests-ratelimiter.readthedocs.io/en/stable/reference.html#requests_ratelimiter.LimiterAdapter)\nis available to be used as a\n[transport adapter](https://requests.readthedocs.io/en/latest/user/advanced/#transport-adapters).\n\nExample:\n```python\nfrom requests import Session\nfrom requests_ratelimiter import LimiterAdapter\n\nsession = Session()\n\n# Apply a rate-limit (5 requests per second) to all requests\nadapter = LimiterAdapter(per_second=5)\nsession.mount('http://', adapter)\nsession.mount('https://', adapter)\n\n# Send rate-limited requests\nfor user_id in range(100):\n    response = session.get(f'https://api.some_site.com/v1/users/{user_id}')\n    print(response.json())\n```\n\n### Mixin\nFinally,\n[`LimiterMixin`](https://requests-ratelimiter.readthedocs.io/en/stable/reference.html#requests_ratelimiter.LimiterMixin)\nis available for advanced use cases in which you want add rate-limiting features to a custom session\nor adapter class. See\n[Custom Session Example](#custom-session-example-requests-cache) below for an example.\n\n## Rate Limit Settings\n### Basic Settings\nThe following parameters are available for the most common rate limit intervals:\n* `per_second`: Max requests per second\n* `per_minute`: Max requests per minute\n* `per_hour`: Max requests per hour\n* `per_day`: Max requests per day\n* `per_month`: Max requests per month\n* `burst`: Max number of consecutive requests allowed before applying per-second rate-limiting\n\n<!-- TODO: Section explaining burst rate limit -->\n\n### Advanced Settings\nIf you need to define more complex rate limits, you can create a `Limiter` object instead:\n```python\nfrom pyrate_limiter import Duration, RequestRate, Limiter\nfrom requests_ratelimiter import LimiterSession\n\nnanocentury_rate = RequestRate(10, Duration.SECOND * 3.156)\nfortnight_rate = RequestRate(1000, Duration.DAY * 14)\ntrimonthly_rate = RequestRate(10000, Duration.MONTH * 3)\nlimiter = Limiter(nanocentury_rate, fortnight_rate, trimonthly_rate)\n\nsession = LimiterSession(limiter=limiter)\n```\n\nSee [pyrate-limiter docs](https://pyratelimiter.readthedocs.io/en/latest/#basic-usage) for more `Limiter` usage details.\n\n## Backends\nBy default, rate limits are tracked in memory and are not persistent. You can optionally use either\nSQLite or Redis to persist rate limits across threads, processes, and/or application restarts.\nYou can specify which backend to use with the `bucket_class` argument. For example, to use SQLite:\n```python\nfrom pyrate_limiter import SQLiteBucket\nfrom requests_ratelimiter import LimiterSession\n\nsession = LimiterSession(per_second=5, bucket_class=SQLiteBucket)\n```\n\nSee [pyrate-limiter docs](https://pyratelimiter.readthedocs.io/en/latest/#backends) for more details.\n\n## Other Features\n### Per-Host Rate Limit Tracking\nWith either `LimiterSession` or `LimiterAdapter`, rate limits are tracked separately for each host.\nIn other words, requests sent to one host will not count against the rate limit for any other hosts:\n\n```python\nsession = LimiterSession(per_second=5)\n\n# Make requests for two different hosts\nfor _ in range(10):\n    response = session.get(f'https://httpbin.org/get')\n    print(response.json())\n    session.get(f'https://httpbingo.org/get')\n    print(response.json())\n```\n\nIf you have a case where multiple hosts share the same rate limit, you can disable this behavior\nwith the `per_host` option:\n```python\nsession = LimiterSession(per_second=5, per_host=False)\n```\n\n### Per-Host Rate Limit Definitions\nWith `LimiterAdapter`, you can apply different rate limits to different hosts or URLs:\n```python\n# Apply a different set of rate limits (2/second and 100/minute) to a specific host\nadapter_2 = LimiterAdapter(per_second=2, per_minute=100)\nsession.mount('https://api.some_site.com', adapter_2)\n```\n\nBehavior for matching requests is the same as other transport adapters: `requests` will use the\nadapter with the most specific (i.e., longest) URL prefix that matches a given request. For example:\n```python\nsession.mount('https://api.some_site.com/v1', adapter_3)\nsession.mount('https://api.some_site.com/v1/users', adapter_4)\n\n# This request will use adapter_3\nsession.get('https://api.some_site.com/v1/')\n\n# This request will use adapter_4\nsession.get('https://api.some_site.com/v1/users/1234')\n```\n\n### Custom Tracking\nFor advanced use cases, you can define your own custom tracking behavior with the `bucket` option.\nFor example, an API that enforces rate limits based on a tenant ID, this feature can be used to track\nrate limits per tenant. If `bucket` is specified, host tracking is disabled.\n\nNote: It is advisable to use SQLite or Redis backends when using custom tracking because using the default backend\neach session will track rate limits independently, even if both sessions call the same URL.\n```python\nsessionA = LimiterSession(per_second=5, bucket='tenant1')\nsessionB = LimiterSession(per_second=5, bucket='tenant2')\n```\n\n### Rate Limit Error Handling\nSometimes, server-side rate limiting may not behave exactly as documented (or may not be documented\nat all). Or you might encounter other scenarios where your client-side limit gets out of sync with\nthe server-side limit. Typically, a server will send a `429: Too Many Requests` response for an\nexceeded rate limit.\n\nWhen this happens, `requests-ratelimiter` will adjust its request log in an attempt to catch up to\nthe server-side limit. If a server sends a different status code other than 429 to indicate an\nexceeded limit, you can set this with `limit_statuses`:\n```python\nsession = LimiterSession(per_second=5, limit_statuses=[429, 500])\n```\n\nOr if you would prefer to disable this behavior and handle it yourself:\n```python\nsession = LimiterSession(per_second=5, limit_statuses=[])\n```\n\n# Compatibility\nThere are many other useful libraries out there that add features to `requests`, most commonly by\nextending or modifying\n[requests.Session](https://requests.readthedocs.io/en/latest/api/#requests.Session) or\n[requests.HTTPAdapter](https://requests.readthedocs.io/en/latest/api/#requests.adapters.HTTPAdapter).\n\nTo use `requests-ratelimiter` with one of these libraries, you have a few different options:\n1. If the library provides a custom `Session` class, mount a `LimiterAdapter` on it\n2. Or use `LimiterMixin` to create a custom `Session` class with features from both libraries\n3. If the library provides a custom `Adapter` class, use `LimiterMixin` to create a custom `Adapter`\n   class with features from both libraries\n\n## Custom Session Example: Requests-Cache\nFor example, to combine with [requests-cache](https://github.com/requests-cache/requests-cache), which\nalso includes a separate mixin class:\n```python\nfrom requests import Session\nfrom requests_cache import CacheMixin, RedisCache\nfrom requests_ratelimiter import LimiterMixin, RedisBucket\n\n\nclass CachedLimiterSession(CacheMixin, LimiterMixin, Session):\n    \"\"\"Session class with caching and rate-limiting behavior. Accepts arguments for both\n    LimiterSession and CachedSession.\n    \"\"\"\n\n\n# Optionally use Redis as both the bucket backend and the cache backend\nsession = CachedLimiterSession(\n    per_second=5,\n    bucket_class=RedisBucket,\n    backend=RedisCache(),\n)\n```\n\nThis example has an extra benefit: cache hits won't count against your rate limit!\n",
    "bugtrack_url": null,
    "license": "MIT",
    "summary": "Rate-limiting for the requests library",
    "version": "0.6.0",
    "project_urls": {
        "Documentation": "https://requests-ratelimiter.readthedocs.io",
        "Homepage": "https://github.com/JWCook/requests-ratelimiter",
        "Repository": "https://github.com/JWCook/requests-ratelimiter"
    },
    "split_keywords": [
        "requests",
        "rate-limiting",
        "leaky-bucket"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "abc2f8ac48cf378d4da920b103544ddb748ac5c230574d35962eb865d3338e02",
                "md5": "f1611aed86c641c6f45e12e49f8e7c3d",
                "sha256": "0641ec7b3dd919a64a8e390358d416c7369f89eb61bec0cf6113ea8d2ec4f072"
            },
            "downloads": -1,
            "filename": "requests_ratelimiter-0.6.0-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "f1611aed86c641c6f45e12e49f8e7c3d",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.7,<4.0",
            "size": 9061,
            "upload_time": "2024-02-29T20:40:16",
            "upload_time_iso_8601": "2024-02-29T20:40:16.087952Z",
            "url": "https://files.pythonhosted.org/packages/ab/c2/f8ac48cf378d4da920b103544ddb748ac5c230574d35962eb865d3338e02/requests_ratelimiter-0.6.0-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "6d9082c076f226828ebdbacc5c9e265f47d3fa51ca4b674eb7e3d9ee0b38f305",
                "md5": "6ec35ea4a384e1dc34c4b085a896cce0",
                "sha256": "ad72f033323df3c1012a3ac4bb91be36606741f58ec9faede62b89b475ea99f6"
            },
            "downloads": -1,
            "filename": "requests_ratelimiter-0.6.0.tar.gz",
            "has_sig": false,
            "md5_digest": "6ec35ea4a384e1dc34c4b085a896cce0",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.7,<4.0",
            "size": 13215,
            "upload_time": "2024-02-29T20:40:17",
            "upload_time_iso_8601": "2024-02-29T20:40:17.654020Z",
            "url": "https://files.pythonhosted.org/packages/6d/90/82c076f226828ebdbacc5c9e265f47d3fa51ca4b674eb7e3d9ee0b38f305/requests_ratelimiter-0.6.0.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2024-02-29 20:40:17",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "JWCook",
    "github_project": "requests-ratelimiter",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "lcname": "requests-ratelimiter"
}
        
Elapsed time: 0.24719s