{"id":24961327,"url":"https://github.com/zhuozhuocrayon/throttled-py","last_synced_at":"2025-12-29T19:00:09.345Z","repository":{"id":274798852,"uuid":"912253327","full_name":"ZhuoZhuoCrayon/throttled-py","owner":"ZhuoZhuoCrayon","description":"🔧 High-performance Python rate limiting library with multiple algorithms (Fixed Window, Sliding Window, Token Bucket, Leaky Bucket \u0026 GCRA) and storage backends (Redis, In-Memory).","archived":false,"fork":false,"pushed_at":"2025-04-12T10:13:23.000Z","size":1098,"stargazers_count":97,"open_issues_count":0,"forks_count":4,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-04-12T10:36:43.732Z","etag":null,"topics":["gcra","python","rate-limiter","rate-limiting","redis","throttler","token-bucket"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ZhuoZhuoCrayon.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-01-05T03:17:28.000Z","updated_at":"2025-04-12T10:13:27.000Z","dependencies_parsed_at":"2025-02-22T05:21:27.784Z","dependency_job_id":"1fbe8f0a-c45a-46da-a7f1-0220a9a16e4c","html_url":"https://github.com/ZhuoZhuoCrayon/throttled-py","commit_stats":null,"previous_names":["zhuozhuocrayon/throttled-py"],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ZhuoZhuoCrayon%2Fthrottled-py","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ZhuoZhuoCrayon%2Fthrottled-py/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ZhuoZhuoCrayon%2Fthrottled-py/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ZhuoZhuoCrayon%2Fthrottled-py/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ZhuoZhuoCrayon","download_url":"https://codeload.github.com/ZhuoZhuoCrayon/throttled-py/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248557205,"owners_count":21124156,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["gcra","python","rate-limiter","rate-limiting","redis","throttler","token-bucket"],"created_at":"2025-02-03T08:52:09.026Z","updated_at":"2025-12-29T19:00:09.333Z","avatar_url":"https://github.com/ZhuoZhuoCrayon.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003ch1 align=\"center\"\u003ethrottled-py\u003c/h1\u003e\n\u003cp align=\"center\"\u003e\n    \u003cem\u003e🔧 High-performance Python rate limiting library with multiple algorithms (Fixed Window, Sliding Window, Token Bucket, Leaky Bucket \u0026 GCRA) and storage backends (Redis, In-Memory).\u003c/em\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n    \u003ca href=\"https://github.com/ZhuoZhuoCrayon/throttled-py\"\u003e\n        \u003cimg src=\"https://img.shields.io/badge/python-%3E%3D3.8-green?logo=python\" alt=\"Python\"\u003e\n    \u003c/a\u003e\n     \u003ca href=\"https://github.com/ZhuoZhuoCrayon/throttled-py\"\u003e\n        \u003cimg src=\"https://codecov.io/gh/ZhuoZhuoCrayon/throttled-py/graph/badge.svg\" alt=\"Coverage Status\"\u003e\n    \u003c/a\u003e\n     \u003ca href=\"https://pypi.org/project/throttled-py/\"\u003e\n        \u003cimg src=\"https://img.shields.io/pypi/v/throttled-py.svg\" alt=\"Coverage Status\"\u003e\n    \u003c/a\u003e\n    \u003ca href=\"https://github.com/ZhuoZhuoCrayon/throttled-py/issues\"\u003e\n        \u003cimg src=\"https://img.shields.io/badge/issue-welcome-blue.svg?logo=github\" alt=\"Welcome Issue\"\u003e\n    \u003c/a\u003e\n    \u003ca href=\"https://hellogithub.com/repository/fb094234bf744e108f4ce7d3326a5cb1\" target=\"_blank\"\u003e\n        \u003cimg src=\"https://api.hellogithub.com/v1/widgets/recommend.svg?rid=fb094234bf744e108f4ce7d3326a5cb1\u0026claim_uid=RzCXpndJ3LrMbUH\u0026theme=small\" alt=\"Featured｜HelloGitHub\" /\u003e\n    \u003c/a\u003e\n\u003c/p\u003e\n\n[简体中文](https://github.com/ZhuoZhuoCrayon/throttled-py/blob/main/README_ZH.md) | English\n\n[🔰 Installation](https://github.com/ZhuoZhuoCrayon/throttled-py?tab=readme-ov-file#-installation) | [🎨 Quick Start](https://github.com/ZhuoZhuoCrayon/throttled-py?tab=readme-ov-file#-quick-start) | [📝 Usage](https://github.com/ZhuoZhuoCrayon/throttled-py?tab=readme-ov-file#-usage) | [⚙️ Data Models](https://github.com/ZhuoZhuoCrayon/throttled-py?tab=readme-ov-file#%EF%B8%8F-data-models--configuration) | [📊 Benchmarks](https://github.com/ZhuoZhuoCrayon/throttled-py?tab=readme-ov-file#-benchmarks) | [🍃 Inspiration](https://github.com/ZhuoZhuoCrayon/throttled-py?tab=readme-ov-file#-inspiration) | [📚 Version History](https://github.com/ZhuoZhuoCrayon/throttled-py?tab=readme-ov-file#-version-history) | [📄 License](https://github.com/ZhuoZhuoCrayon/throttled-py?tab=readme-ov-file#-license)\n\n\n## ✨ Features\n\n* Supports both synchronous and [asynchronous](https://github.com/ZhuoZhuoCrayon/throttled-py?tab=readme-ov-file#3-asynchronous) (`async / await`).\n* Provides thread-safe storage backends: [Redis(Standalone/Sentinel/Cluster)](https://github.com/ZhuoZhuoCrayon/throttled-py?tab=readme-ov-file#redis), [In-Memory (with support for key expiration and eviction)](https://github.com/ZhuoZhuoCrayon/throttled-py?tab=readme-ov-file#in-memory).\n* Supports multiple rate limiting algorithms: [Fixed Window](https://github.com/ZhuoZhuoCrayon/throttled-py/tree/main/docs/basic#21-%E5%9B%BA%E5%AE%9A%E7%AA%97%E5%8F%A3%E8%AE%A1%E6%95%B0%E5%99%A8), [Sliding Window](https://github.com/ZhuoZhuoCrayon/throttled-py/blob/main/docs/basic/readme.md#22-%E6%BB%91%E5%8A%A8%E7%AA%97%E5%8F%A3), [Token Bucket](https://github.com/ZhuoZhuoCrayon/throttled-py/blob/main/docs/basic/readme.md#23-%E4%BB%A4%E7%89%8C%E6%A1%B6), [Leaky Bucket](https://github.com/ZhuoZhuoCrayon/throttled-py/blob/main/docs/basic/readme.md#24-%E6%BC%8F%E6%A1%B6) \u0026 [Generic Cell Rate Algorithm (GCRA)](https://github.com/ZhuoZhuoCrayon/throttled-py/blob/main/docs/basic/readme.md#25-gcra).\n* Supports [configuration of rate limiting algorithms](https://github.com/ZhuoZhuoCrayon/throttled-py?tab=readme-ov-file#3-algorithms) and provides flexible [quota configuration](https://github.com/ZhuoZhuoCrayon/throttled-py?tab=readme-ov-file#4-quota-configuration).\n* Supports immediate response and [wait-retry](https://github.com/ZhuoZhuoCrayon/throttled-py?tab=readme-ov-file#wait--retry) modes, and provides [function call](https://github.com/ZhuoZhuoCrayon/throttled-py?tab=readme-ov-file#function-call), [decorator](https://github.com/ZhuoZhuoCrayon/throttled-py?tab=readme-ov-file#decorator), and [context manager](https://github.com/ZhuoZhuoCrayon/throttled-py?tab=readme-ov-file#context-manager) modes.\n* Supports integration with the [MCP](https://modelcontextprotocol.io/introduction) [Python SDK](https://github.com/modelcontextprotocol/python-sdk) to provide rate limiting support for model dialog processes.\n* Excellent performance,  The execution time for a single rate limiting API call is equivalent to(see [Benchmarks](https://github.com/ZhuoZhuoCrayon/throttled-py?tab=readme-ov-file#-benchmarks) for details):\n  * In-Memory: ~2.5-4.5x `dict[key] += 1` operations.\n  * Redis: ~1.06-1.37x `INCRBY key increment` operations.\n\n\n## 🔰 Installation\n\n```shell\n$ pip install throttled-py\n```\n\n### 1) Optional Dependencies\n\nStarting from [v2.0.0](https://github.com/ZhuoZhuoCrayon/throttled-py/releases/tag/v2.0.0), only core dependencies are installed by default.\n\nTo enable additional features, install optional dependencies as follows (multiple extras can be comma-separated):\n\n```shell\n$ pip install \"throttled-py[redis]\"\n\n$ pip install \"throttled-py[redis,in-memory]\"\n```\n\n| Extra       | Description                       |\n|-------------|-----------------------------------|\n| `all`       | Install all extras.               |\n| `in-memory` | Use In-Memory as storage backend. |\n| `redis`     | Use Redis as storage backend.     |\n\n\n## 🎨 Quick Start\n\n### 1) Core API\n\n* `limit`: Deduct requests and return [**RateLimitResult**](https://github.com/ZhuoZhuoCrayon/throttled-py?tab=readme-ov-file#1-ratelimitresult).\n* `peek`: Check current rate limit state for a key (returns [**RateLimitState**](https://github.com/ZhuoZhuoCrayon/throttled-py?tab=readme-ov-file#2-ratelimitstate)).\n\n### 2) Example\n\n```python\nfrom throttled import RateLimiterType, Throttled, rate_limiter, utils\n\nthrottle = Throttled(\n    # 📈 Use Token Bucket algorithm\n    using=RateLimiterType.TOKEN_BUCKET.value,\n    # 🪣 Set quota: 1,000 tokens per second (limit), bucket size 1,000 (burst)\n    quota=rate_limiter.per_sec(1_000, burst=1_000),\n    # 📁 By default, global MemoryStore is used as the storage backend.\n)\n\ndef call_api() -\u003e bool:\n    # 💧 Deduct 1 token for key=\"/ping\"\n    result = throttle.limit(\"/ping\", cost=1)\n    return result.limited\n\nif __name__ == \"__main__\":\n    # 💻 Python 3.12.10, Linux 5.4.119-1-tlinux4-0009.1, Arch: x86_64, Specs: 2C4G.\n    # ✅ Total: 100000, 🕒 Latency: 0.0068 ms/op, 🚀 Throughput: 122513 req/s (--)\n    # ❌ Denied: 98000 requests\n    benchmark: utils.Benchmark = utils.Benchmark()\n    denied_num: int = sum(benchmark.serial(call_api, 100_000))\n    print(f\"❌ Denied: {denied_num} requests\")\n```\n\n### 3) Asynchronous\n\nThe core API is the same for synchronous and asynchronous code. Just replace `from throttled import ...` with `from throttled.asyncio import ...` in your code.\n\nFor example, rewrite `2) Example` to asynchronous:\n\n```python\nimport asyncio\nfrom throttled.asyncio import RateLimiterType, Throttled, rate_limiter, utils\n\nthrottle = Throttled(\n    using=RateLimiterType.TOKEN_BUCKET.value,\n    quota=rate_limiter.per_sec(1_000, burst=1_000)\n)\n\n\nasync def call_api() -\u003e bool:\n    result = await throttle.limit(\"/ping\", cost=1)\n    return result.limited\n\n\nasync def main():\n    benchmark: utils.Benchmark = utils.Benchmark()\n    denied_num: int = sum(await benchmark.async_serial(call_api, 100_000))\n    print(f\"❌ Denied: {denied_num} requests\")\n\nif __name__ == \"__main__\":\n    asyncio.run(main())\n```\n\n\n## 📝 Usage\n\n### 1) Basic Usage\n\n#### Function Call\n\n```python\nfrom throttled import Throttled\n\n# Default: In-Memory storage, Token Bucket algorithm, 60 reqs / min.\nthrottle = Throttled()\n\n# Deduct 1 request -\u003e RateLimitResult(limited=False,\n# state=RateLimitState(limit=60, remaining=59, reset_after=1, retry_after=0))\nprint(throttle.limit(\"key\", 1))\n# Check state -\u003e RateLimitState(limit=60, remaining=59, reset_after=1, retry_after=0)\nprint(throttle.peek(\"key\"))\n\n# Deduct 60 requests (limited) -\u003e RateLimitResult(limited=True,\n# state=RateLimitState(limit=60, remaining=59, reset_after=1, retry_after=60))\nprint(throttle.limit(\"key\", 60))\n```\n\n#### Decorator\n\n```python\nfrom throttled import Throttled, rate_limiter, exceptions\n\n@Throttled(key=\"/ping\", quota=rate_limiter.per_min(1))\ndef ping() -\u003e str:\n    return \"ping\"\n\nping()\n\ntry:\n    ping()  # Raises LimitedError\nexcept exceptions.LimitedError as exc:\n    print(exc)  # Rate limit exceeded: remaining=0, reset_after=60, retry_after=60\n```\n\n#### Context Manager\n\nYou can use the context manager to limit the code block. When access is allowed, return [**RateLimitResult**](https://github.com/ZhuoZhuoCrayon/throttled-py?tab=readme-ov-file#1-ratelimitresult).\n\nIf the limit is exceeded or the retry timeout is exceeded, it will raise [**LimitedError**](https://github.com/ZhuoZhuoCrayon/throttled-py?tab=readme-ov-file#limitederror).\n\n```python\nfrom throttled import Throttled, exceptions, rate_limiter\n\ndef call_api():\n    print(\"doing something...\")\n\nthrottle: Throttled = Throttled(key=\"/api/v1/users/\", quota=rate_limiter.per_min(1))\nwith throttle as rate_limit_result:\n    print(f\"limited: {rate_limit_result.limited}\")\n    call_api()\n\ntry:\n    with throttle:\n        call_api()\nexcept exceptions.LimitedError as exc:\n    print(exc)  # Rate limit exceeded: remaining=0, reset_after=60, retry_after=60\n```\n\n#### Wait \u0026 Retry\n\nBy default, rate limiting returns [**RateLimitResult**](https://github.com/ZhuoZhuoCrayon/throttled-py?tab=readme-ov-file#1-ratelimitresult) immediately.\n\nYou can specify a **`timeout`** to enable wait-and-retry behavior. The rate limiter will wait according to the `retry_after` value in [**RateLimitState**](https://github.com/ZhuoZhuoCrayon/throttled-py?tab=readme-ov-file#2-ratelimitstate) and retry automatically.\n\nReturns the final [**RateLimitResult**](https://github.com/ZhuoZhuoCrayon/throttled-py?tab=readme-ov-file#1-ratelimitresult) when the request is allowed or timeout reached.\n\n```python\nfrom throttled import RateLimiterType, Throttled, rate_limiter, utils\n\nthrottle = Throttled(\n    using=RateLimiterType.GCRA.value,\n    quota=rate_limiter.per_sec(100, burst=100),\n    # ⏳ Set timeout=1 to enable wait-and-retry (max wait 1 second)\n    timeout=1,\n)\n\ndef call_api() -\u003e bool:\n    # ⬆️⏳ Function-level timeout overrides global timeout\n    result = throttle.limit(\"/ping\", cost=1, timeout=1)\n    return result.limited\n\nif __name__ == \"__main__\":\n    # 👇 The actual QPS is close to the preset quota (100 req/s):\n    # ✅ Total: 1000, 🕒 Latency: 35.8103 ms/op, 🚀 Throughput: 111 req/s (--)\n    # ❌ Denied: 8 requests\n    benchmark: utils.Benchmark = utils.Benchmark()\n    denied_num: int = sum(benchmark.concurrent(call_api, 1_000, workers=4))\n    print(f\"❌ Denied: {denied_num} requests\")\n```\n\n### 2) Storage Backends\n\n#### Redis\n\nYou only need very simple configuration, and it supports connecting to Redis standalone, sentinel, and cluster modes.\n\nThe following example uses Redis as the storage backend, `options` supports all Redis configuration items, see [RedisStore Options](https://github.com/ZhuoZhuoCrayon/throttled-py?tab=readme-ov-file#redisstore-options).\n\n```python\nfrom throttled import RateLimiterType, Throttled, rate_limiter, store\n\n@Throttled(\n    key=\"/api/products\",\n    using=RateLimiterType.TOKEN_BUCKET.value,\n    quota=rate_limiter.per_min(1),\n    store=store.RedisStore(\n        # Standalone mode\n        server=\"redis://127.0.0.1:6379/0\",\n        # Sentinel mode\n        # server=\"redis+sentinel://:yourpassword@host1:26379,host2:26379/mymaster\"\n        # Cluster mode\n        # server=\"redis+cluster://:yourpassword@host1:6379,host2:6379\",\n        options={}\n    ),\n)\ndef products() -\u003e list:\n    return [{\"name\": \"iPhone\"}, {\"name\": \"MacBook\"}]\n\nproducts()  # Success\nproducts()  # Raises LimitedError\n```\n\n#### In-Memory\n\nBy default, a global `MemoryStore` instance with a maximum capacity of 1024 is used as the storage backend when no storage backend is specified. Therefore, **it is usually not necessary to manually create** a `MemoryStore` instance.\n\nDifferent instances mean different storage spaces, if you want to throttle the same Key at different locations in your program, make sure that Throttled receives the same MemoryStore and uses a consistent [`Quota`](https://github.com/ZhuoZhuoCrayon/throttled-py?tab=readme-ov-file#3-quota).\n\nThe following example uses memory as the storage backend and throttles the same Key on ping and pong:\n\n```python\nfrom throttled import Throttled, rate_limiter, store\n\nmem_store = store.MemoryStore()\n\n@Throttled(key=\"ping-pong\", quota=rate_limiter.per_min(1), store=mem_store)\ndef ping() -\u003e str: return \"ping\"\n\n@Throttled(key=\"ping-pong\", quota=rate_limiter.per_min(1), store=mem_store)\ndef pong() -\u003e str: return \"pong\"\n\nping()  # Success\npong()  # Raises LimitedError\n```\n\n### 3) Algorithms\n\nThe rate limiting algorithm is specified by the **`using`** parameter. The supported algorithms are as follows:\n\n* [Fixed window](https://github.com/ZhuoZhuoCrayon/throttled-py/tree/main/docs/basic#21-%E5%9B%BA%E5%AE%9A%E7%AA%97%E5%8F%A3%E8%AE%A1%E6%95%B0%E5%99%A8): `RateLimiterType.FIXED_WINDOW.value`\n* [Sliding window](https://github.com/ZhuoZhuoCrayon/throttled-py/blob/main/docs/basic/readme.md#22-%E6%BB%91%E5%8A%A8%E7%AA%97%E5%8F%A3): `RateLimiterType.SLIDING_WINDOW.value`\n* [Token Bucket](https://github.com/ZhuoZhuoCrayon/throttled-py/blob/main/docs/basic/readme.md#23-%E4%BB%A4%E7%89%8C%E6%A1%B6): `RateLimiterType.TOKEN_BUCKET.value`\n* [Leaky Bucket](https://github.com/ZhuoZhuoCrayon/throttled-py/blob/main/docs/basic/readme.md#24-%E6%BC%8F%E6%A1%B6): `RateLimiterType.LEAKING_BUCKET.value`\n* [Generic Cell Rate Algorithm, GCRA](https://github.com/ZhuoZhuoCrayon/throttled-py/blob/main/docs/basic/readme.md#25-gcra): `RateLimiterType.GCRA.value`\n\n```python\nfrom throttled import RateLimiterType, Throttled, rate_limiter\n\nthrottle = Throttled(\n    # 🌟Specifying a current limiting algorithm\n    using=RateLimiterType.FIXED_WINDOW.value, \n    quota=rate_limiter.per_min(1)\n)\nassert throttle.limit(\"key\", 2).limited is True\n```\n\n### 4) Quota Configuration\n\n#### Quick Setup\n\n```python\nfrom throttled import rate_limiter\n\nrate_limiter.per_sec(60)    # 60 req/sec\nrate_limiter.per_min(60)    # 60 req/min\nrate_limiter.per_hour(60)   # 60 req/hour\nrate_limiter.per_day(60)    # 60 req/day\nrate_limiter.per_week(60)   # 60 req/week\n```\n\n#### Burst Capacity\n\nThe **`burst`** parameter can be used to adjust the ability of the throttling object to handle burst traffic. This is valid for the following algorithms:\n\n* `TOKEN_BUCKET`\n* `LEAKING_BUCKET`\n* `GCRA`\n\n```python\nfrom throttled import rate_limiter\n\n# Allow 120 burst requests.\n# When burst is not specified, the default setting is the limit passed in.\nrate_limiter.per_min(60, burst=120)\n```\n\n#### Custom Quota\n\n```python\nfrom datetime import timedelta\nfrom throttled import rate_limiter\n\n# A total of 120 requests are allowed in two minutes, and a burst of 150 requests is allowed.\nrate_limiter.per_duration(timedelta(minutes=2), limit=120, burst=150)\n```\n\n\n## ⚙️ Data Models \u0026 Configuration\n\n### 1) RateLimitResult\n\nRateLimitState represents the result after executing the RateLimiter for the given key.\n\n| Field     | Type           | Description                                                                             |\n|-----------|----------------|-----------------------------------------------------------------------------------------|\n| `limited` | bool           | Limited represents whether this request is allowed to pass.                             |\n| `state`   | RateLimitState | RateLimitState represents the result after executing the RateLimiter for the given key. |\n\n### 2) RateLimitState\n\nRateLimitState represents the current state of the rate limiter for the given key.\n\n| Field         | Type  | Description                                                                                                                          |\n|---------------|-------|--------------------------------------------------------------------------------------------------------------------------------------|\n| `limit`       | int   | Limit represents the maximum number of requests allowed to pass in the initial state.                                                |\n| `remaining`   | int   | Remaining represents the maximum number of requests allowed to pass for the given key in the current state.                          |\n| `reset_after` | float | ResetAfter represents the time in seconds for the RateLimiter to return to its initial state. In the initial state, Limit=Remaining. |\n| `retry_after` | float | RetryAfter represents the time in seconds for the request to be retried, 0 if the request is allowed.                                |\n\n### 3) Quota\n\nQuota represents the quota limit configuration.\n\n| Field   | Type | Description                                                                                                    |\n|---------|------|----------------------------------------------------------------------------------------------------------------|\n| `burst` | int  | Optional burst capacity that allows exceeding the rate limit momentarily(supports Token / Leaky Bucket, GCRA). |\n| `rate`  | Rate | The base rate limit configuration.                                                                             |\n\n### 4) Rate\n\nRate represents the rate limit configuration.\n\n| Field    | Type               | Description                                                         |\n|----------|--------------------|---------------------------------------------------------------------|\n| `period` | datetime.timedelta | The time period for which the rate limit applies.                   |\n| `limit`  | int                | The maximum number of requests allowed within the specified period. |\n\n### 5) Store Configuration\n\n#### Common Parameters\n\n| Param     | Description                                                                      | Default                      |\n|-----------|----------------------------------------------------------------------------------|------------------------------|\n| `server`  | Redis connection URL, you can use it to connect to Redis in any deployment mode. | `\"redis://localhost:6379/0\"` |\n| `options` | Storage-specific configurations                                                  | `{}`                         |\n\n#### RedisStore Options\n\nRedisStore is developed based on the Redis API provided by [redis-py](https://github.com/redis/redis-py).\n\nIn terms of Redis connection configuration management, the configuration naming of [django-redis](https://github.com/jazzband/django-redis) is basically used to reduce the learning cost.\n\n| Parameter                  | Description                                                                                                                                                 | Default                                                                                                                                                                                                                                                                                                                                                                       |\n|----------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n| `SOCKET_TIMEOUT`           | ConnectionPool parameters.                                                                                                                                  | `null`                                                                                                                                                                                                                                                                                                                                                                        |\n| `SOCKET_CONNECT_TIMEOUT`   | ConnectionPool parameters.                                                                                                                                  | `null`                                                                                                                                                                                                                                                                                                                                                                        |\n| `CONNECTION_POOL_KWARGS`   | [ConnectionPool construction parameters](https://redis.readthedocs.io/en/stable/connections.html#connectionpool).                                           | `{}`                                                                                                                                                                                                                                                                                                                                                                          |\n| `REDIS_CLIENT_KWARGS`      | [RedisClient construction parameters](https://redis.readthedocs.io/en/stable/connections.html#redis.Redis).                                                 | `{}`                                                                                                                                                                                                                                                                                                                                                                          |\n| `SENTINEL_KWARGS`          | [Sentinel construction parameters](https://redis.readthedocs.io/en/stable/connections.html#id1).                                                            | `{}`                                                                                                                                                                                                                                                                                                                                                                          |\n| `CONNECTION_FACTORY_CLASS` | ConnectionFactory is used to create and maintain [ConnectionPool](https://redis.readthedocs.io/en/stable/connections.html#redis.connection.ConnectionPool). | Automatically select via the `server` scheme by default. \u003cbr /\u003eStandalone: `\"throttled.store.ConnectionFactory\"` \u003cbr /\u003eSentinel:`\"throttled.store.SentinelConnectionFactory\"`\u003cbr /\u003eCluster: `\"throttled.store.ClusterConnectionFactory\"`                                                                                                                                      |\n| `REDIS_CLIENT_CLASS`       | RedisClient import path.                                                                                                                                    | Automatically select sync/async mode by default.\u003cbr /\u003eSync(Standalone/Sentinel): `\"redis.client.Redis\"`\u003cbr /\u003eAsync(Standalone/Sentinel): `\"redis.asyncio.client.Redis\"`\u003cbr /\u003eSync(Cluster): ``\"redis.cluster.RedisCluster\"``\u003cbr /\u003eAsync(Cluster): ``\"redis.asyncio.cluster.RedisCluster\"``                                                                                    |\n| `CONNECTION_POOL_CLASS`    | ConnectionPool import path.                                                                                                                                 | Automatically select via the `server` scheme and sync/async mode by default.\u003cbr /\u003eSync(Standalone): `\"redis.connection.ConnectionPool\"`\u003cbr /\u003eAsync(Standalone): `\"redis.asyncio.connection.ConnectionPool\"`\u003cbr /\u003eSync(Sentinel): `\"redis.sentinel.SentinelConnectionPool\"`\u003cbr /\u003eAsync(Sentinel): `\"redis.asyncio.sentinel.SentinelConnectionPool\"`\u003cbr /\u003eCluster: `\"Disabled\"` |\n| `SENTINEL_CLASS`           | Sentinel import path.                                                                                                                                       | Automatically select sync/async mode by default.\u003cbr /\u003eSync: `\"redis.Sentinel\"`\u003cbr /\u003eAsync: `\"redis.asyncio.Sentinel\"`                                                                                                                                                                                                                                                         |\n\n#### MemoryStore Options\n\nMemoryStore is essentially a [LRU Cache](https://en.wikipedia.org/wiki/Cache_replacement_policies#LRU) based on memory with expiration time.\n\n| Parameter  | Description                                                                                                                          | Default |\n|------------|--------------------------------------------------------------------------------------------------------------------------------------|---------|\n| `MAX_SIZE` | Maximum capacity. When the number of stored key-value pairs exceeds `MAX_SIZE`, they will be eliminated according to the LRU policy. | `1024`  |\n\n### 6) Exception\n\nAll exceptions inherit from `throttled.exceptions.BaseThrottledError`.\n\n#### LimitedError\n\nWhen a request is throttled, an exception is thrown, such as: `Rate limit exceeded: remaining=0, reset_after=60, retry_after=60.`.\n\n| Field               | Type              | Description                                                   |\n|---------------------|-------------------|---------------------------------------------------------------|\n| `rate_limit_result` | `RateLimitResult` | The result after executing the RateLimiter for the given key. |\n\n#### DataError\n\nThrown when the parameter is invalid, such as: `Invalid key: None, must be a non-empty key.`.\n\n\n## 📊 Benchmarks\n\n### 1) Test Environment\n- **Python Version**: 3.13.1 (CPython implementation)\n- **Operating System**: macOS Darwin 23.6.0 (ARM64 architecture)\n- **Redis Version**: 7.x (local connection)\n\n### 2) Performance Metrics\n\u003e Throughput in req/s, Latency in ms/op.\n\n| Algorithm Type     | In-Memory (Single-thread) | In-Memory (16 threads)     | Redis (Single-thread) | Redis (16 threads)  |\n|--------------------|---------------------------|----------------------------|-----------------------|---------------------|\n| **Baseline** *[1]* | **1,692,307 / 0.0002**    | **135,018 / 0.0004** *[2]* | **17,324 / 0.0571**   | **16,803 / 0.9478** |\n| Fixed Window       | 369,635 / 0.0023          | 57,275 / 0.2533            | 16,233 / 0.0610       | 15,835 / 1.0070     |\n| Sliding Window     | 265,215 / 0.0034          | 49,721 / 0.2996            | 12,605 / 0.0786       | 13,371 / 1.1923     |\n| Token Bucket       | 365,678 / 0.0023          | 54,597 / 0.2821            | 13,643 / 0.0727       | 13,219 / 1.2057     |\n| Leaky Bucket       | 364,296 / 0.0023          | 54,136 / 0.2887            | 13,628 / 0.0727       | 12,579 / 1.2667     |\n| GCRA               | 373,906 / 0.0023          | 53,994 / 0.2895            | 12,901 / 0.0769       | 12,861 / 1.2391     |\n\n* *[1] Baseline: In-Memory - `dict[key] += 1`, Redis - `INCRBY key increment`*.\n* *[2] In-Memory concurrent baseline uses `threading.RLock` for thread safety.*\n* *[3] Performance: In-Memory - ~2.5-4.5x `dict[key] += 1` operations, Redis - ~1.06-1.37x `INCRBY key increment` operations.*\n* *[4] Benchmark code: [tests/benchmarks/test_throttled.py](https://github.com/ZhuoZhuoCrayon/throttled-py/blob/main/tests/benchmarks/test_throttled.py).*\n\n\n## 🍃 Inspiration\n\n[Rate Limiting, Cells, and GCRA](https://brandur.org/rate-limiting), by [Brandur Leach](https://github.com/brandur)\n\n\n## 📚 Version History\n\n[See CHANGELOG](https://github.com/ZhuoZhuoCrayon/throttled-py/blob/main/CHANGELOG_EN.rst)\n\n\n## 📄 License\n\n[The MIT License](https://github.com/ZhuoZhuoCrayon/throttled-py/blob/main/LICENSE)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzhuozhuocrayon%2Fthrottled-py","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fzhuozhuocrayon%2Fthrottled-py","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzhuozhuocrayon%2Fthrottled-py/lists"}