{"id":19732240,"url":"https://github.com/lifeomic/rate-limiter-py","last_synced_at":"2025-04-30T02:31:58.469Z","repository":{"id":49041205,"uuid":"141655872","full_name":"lifeomic/rate-limiter-py","owner":"lifeomic","description":"Rate-limiter module which leverages DynamoDB to enforce resource limits","archived":false,"fork":false,"pushed_at":"2023-10-11T20:39:59.000Z","size":73,"stargazers_count":11,"open_issues_count":1,"forks_count":1,"subscribers_count":22,"default_branch":"master","last_synced_at":"2025-04-28T01:05:55.171Z","etag":null,"topics":["team-platform"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/lifeomic.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"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}},"created_at":"2018-07-20T02:54:44.000Z","updated_at":"2024-03-29T04:12:32.000Z","dependencies_parsed_at":"2024-11-12T00:27:38.039Z","dependency_job_id":"2f4448e3-717d-4cc9-b6e1-86c7bc8180ce","html_url":"https://github.com/lifeomic/rate-limiter-py","commit_stats":{"total_commits":36,"total_committers":3,"mean_commits":12.0,"dds":"0.13888888888888884","last_synced_commit":"b5587dcdf97b27f495e78ea07ef3cfffed303cc0"},"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lifeomic%2Frate-limiter-py","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lifeomic%2Frate-limiter-py/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lifeomic%2Frate-limiter-py/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lifeomic%2Frate-limiter-py/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/lifeomic","download_url":"https://codeload.github.com/lifeomic/rate-limiter-py/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":251629191,"owners_count":21618118,"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":["team-platform"],"created_at":"2024-11-12T00:25:24.351Z","updated_at":"2025-04-30T02:31:58.207Z","avatar_url":"https://github.com/lifeomic.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# rate-limiter-py\n\nThis is a rate-limiter module which leverages DynamoDB to enforce resource limits.\n\nThis module offers two different methods for consuming and replenishing resource capacity.\nThe typical use calls for a time driven method for distributing and replenishing tokens, e.g. 10 requests per second.\nHowever, there are cases which require both an acquisition and return of a specific token,\ne.g. wait for an EMR cluster to complete.\n\nBearing this in mind, resource capacity is represented by either fungible\nor non-fungible tokens. The former are interchangeable and thus can\nmake use of time-based expiration to restore capacity.\nThe latter are unique and require explicit removal to restore capacity.\n\nFor fungible tokens, the limiter leverages the token bucket algorithm,\ntracking resource usage by acquiring and replenishing tokens for each unit of capacity.\n\nFor non-fungible tokens, the limiter creates a new token for each resource.\nLimiting is enforced by disallowing token creation beyond the specified capacity.\n\n## Fungible Token Requirements and Usage\n\nFungible token rate-limiting requirements and usage is detailed below.\n\n### DynamoDB Tables\n\nThe expected usage is each rate limiter will use the same multi-tenant token and limit tables, created and\nmanaged by a separate service. However, a private token and/or limit table can be used when instantiating the\nmiddleware.\n\n#### Token Table\n\nThe tokens for a single resource are stored in a single DynamoDB row, representing the \"bucket\".\nThe expected table schema is detailed below.\n\n##### Attributes\n\nThese are all the expected table attributes, including the keys.\n\n| Attribute Name | Data Type | Description                                                  |\n|----------------|-----------|--------------------------------------------------------------|\n| resourceName   | String    | User-defined name of the rate limited resource               |\n| accountId      | String    | Id of the entity which created the resource                  |\n| tokens         | Number    | Number of tokens available                                   |\n| lastRefill     | Number    | Timestamp, in milliseconds, when the tokens were replenished |\n| lastToken      | Number    | Timestamp, in milliseconds, when the last token was taken    |\n\n##### Keys\n\nThe key data type and description can be found in the above, attributes table.\n\n| Attribute Name | Key Type |\n|----------------|----------|\n| resourceName   | HASH     |\n| accountId      | RANGE    |\n\n#### Limit Table\n\nThe limit and window for a specific account on a specific resource are stored in a single DynamoDB row.\nThe expected table schema is detailed below.\n\n##### Attributes\n\nThese are all the expected table attributes, including the keys.\n\n| Attribute Name | Data Type | Description                                                                                    |\n|----------------|-----------|------------------------------------------------------------------------------------------------|\n| resourceName   | String    | User-defined name of the rate limited resource                                                 |\n| accountId      | String    | Id of the entity which created the resource                                                    |\n| limit          | Number    | The maximum number of tokens the account may acquire on the resource                           |\n| windowSec      | Number    | Sliding window of time, in seconds, wherein only the limit number of tokens will be available. |\n| serviceName    | String    | Name of the service that created this limit.                                                   |\n\n##### Keys\n\nThe key data type and description can be found in the above, attributes table.\n\n| Attribute Name | Key Type |\n|----------------|----------|\n| resourceName   | HASH     |\n| accountId      | RANGE    |\n\n##### Service Limits Index\n\nThe service limits global secondary index is used when updating/loading service limits.\n\n| Attribute Name | Key Type |\n|----------------|----------|\n| serviceName    | HASH     |\n\n### Usage\n\nEach of the fungible token limiter implementations require the names of the token and limit tables. These values can be\npassed directly to the limiter or set via environment variables.\n\n| Name        | Environment Variable | Description                                          |\n|-------------|----------------------|------------------------------------------------------|\n| token_table | FUNGIBLE_TABLE       | Name of the DynamoDB table containing tokens.        |\n| limit_table | LIMIT_TABLE          | Name of the DynamoDB table containing account limit. |\n\nThe only other value required by each implementation is `account id`. Each implementation handles specifying this value\ndifferently.\n\n#### Decorator\n\nThe function decorator has a minimum of four arguments:\n\n1.  The name of the resource being rate-limited.\n\n1.  How to access the account id from the arguments of the function being decorated. This can be either a positional\n    argument numeric index or a keyword argument key.\n\n1.  The default limit, used when no limit is found in the limits table.\n\n1.  The default window, used when no window is found in the limits table.\n\n##### Examples\n\nThe examples below assume the table name, limit and window have been set via environment variables.\n\n###### Positional Argument\n\n```python\nfrom limiter import rate_limit\n\n@rate_limit('my-resource', 1, 10, account_id_pos=1)\ndef invoke_my_resource(arg_1, account_id):\n  # If I am here, I was not rate limited\n```\n\n###### Keyword Argument\n\n```python\nfrom limiter import rate_limit\n\n@rate_limit('my-resource', 1, 10, account_id_key='foo')\ndef invoke_my_resource(arg_1, foo='account-1234'):\n  # If I am here, I was not rate limited\n\n# The default keyword argument is \"account_id\", to make the decorator more succinct:\n@rate_limit('my-resource', 1, 10)\ndef invoke_my_resource(arg_1, account_id='account-1234'):\n  # If I am here, I was not rate limited\n```\n\n#### Context Manager\n\nThe context manager has a minimum of four arguments:\n\n1.  The name of the resource being rate-limited.\n\n1.  The account id.\n\n1.  The default limit, used when no limit is found in the limits table.\n\n1.  The default window, used when no window is found in the limits table.\n\n##### Example\n\nThe example below assume the table name, limit and window have been set via environment variables.\n\n```python\nfrom limiter import fungible_limiter\n\ndef invoke_my_resource(account_id):\n  with fungible_limiter('my-resource', account_id, 1, 10):\n    # If I am here, I was not rate limited\n```\n\n#### Direct\n\nDirectly creating an instance of the fungible limiter has a minimum of four arguments:\n\n1.  The name of the resource being rate-limited.\n\n1.  The account id.\n\n1.  The default limit, used when no limit is found in the limits table.\n\n1.  The default window, used when no window is found in the limits table.\n\n##### Example\n\nThe example below assume the table name, limit and window have been set via environment variables.\n\n```python\nfrom limiter import fungible_limiter\n\ndef invoke_my_resource(account_id):\n  limiter = fungible_limiter('my-resource', account_id, 1, 10)\n  limiter.get_token()\n  # If I am here, I was not rate limited\n```\n\n## Non-Fungible Token Requirements and Usage\n\nNon-fungible token rate-limiting requirements and usage is detailed below.\n\n### DynamoDB Table\n\nEach token is represented as a single row in DynamoDB. The expected table schema is detailed below.\n\n#### Attributes\n\nThese are all the expected table attributes, including the keys.\n\n| Attribute Name     | Data Type | Description                                                   |\n|--------------------|-----------|---------------------------------------------------------------|\n| resourceCoordinate | String    | Composed of the resource name and account id                  |\n| reservationId      | String    | Identifies the token reservation                              |\n| resourceId         | String    | Identifies the instance of a resource, e.g. EMR cluster id    |\n| resourceName       | String    | User-defined name of the rate limited resource                |\n| expirationTime     | Number    | Timestamp, in sec, when the token will be expired by DynamoDB |\n| accountId          | String    | Id of the entity which created the resource                   |\n\n\n#### Keys\n\nThe key data type and description can be found in the above, attributes table.\n\n| Attribute Name     | Key Type |\n|--------------------|----------|\n| resourceCoordinate | HASH     |\n| reservationId      | RANGE    |\n\n#### Global Secondary Index\n\nA global secondary index is used to locate tokens using only `resourceId`. This will be needed to locate tokens\nbased on the resource id provided in CloudWatch events.\n\n| Attribute Name | Key Type |\n|----------------|----------|\n| resourceId     | HASH     |\n\n### Creating Tokens\n\nEach of the fungible token limiter implementations require the names of the token and limit tables. These values can be\npassed directly to the limiter or set via environment variables.\n\n| Name        | Environment Variable | Description                                         |\n|-------------|----------------------|-----------------------------------------------------|\n| token_table | FUNGIBLE_TABLE       | Name of the DynamoDB table containing tokens.       |\n| limit_table | LIMIT_TABLE          | Name of the DynamoDB table containing account limit |\n\nThe only other value required by each implementation is `account id`. Each implementation handles specifying this value\ndifferently.\n\nEach implementation example assumes the table name and limit have been set via environment variables.\n\n#### Context Manager\n\nThe context manager has a minimum of three arguments:\n\n1.  The name of the resource being rate-limited.\n\n1.  The account id.\n\n1.  The default limit, used when no limit is found in the limits table.\n\n##### Example\n\n```python\nfrom limiter import non_fungible_limiter\n\ndef invoke_my_resource(account_id):\n  with non_fungible_limiter('my-resource', account_id, 10) as reservation:\n    emr_cluster_id = create_emr_cluster() # Create an instance of the resource\n    reservation.create_token(emr_cluster_id) # Create a token for this unique resource\n```\n\n#### Directly\n\nDirectly creating an instance of the non-fungible limiter has a minimum of three arguments:\n\n1.  The name of the resource being rate-limited.\n\n1.  The account id.\n\n1.  The default limit, used when no limit is found in the limits table.\n\n##### Example\n\n```python\nfrom limiter import non_fungible_limiter\n\ndef invoke_my_resource(account_id):\n  limiter = non_fungible_limiter('my-resource', account_id, 10)\n  reservation = limiter.get_reservation()\n\n  emr_cluster_id = create_emr_cluster() # Create an instance of the resource\n  reservation.create_token(emr_cluster_id) # Create a token for this unique resource\n```\n\n### Removing Tokens\n\nThe recommended approach for detecting the termination of and removing tokenized\nresources is via a Lambda triggered by CloudWatch. The CloudWatch rules should be as precise as\npractical to avoid unnecessarily executing the lambda.\n\nThe `event_processors` module contains all the logic necessary to consume, test and remove tokens from\nCloudWatch events.\n\n#### Usage\n\nThe `EventProcessorManager` class removes non-fungible tokens from DynamoDB represented by CloudWatch events.\nThe manager is composed of multiple `EventProcessors`, one for specific event \"source\", e.g. 'aws.emr'.\nThe processors are responsible for determining if an event references a tokenized resource and if so, extracting its\nresource id. Each processor is composed of zero to many predicates, which determine if an event references a tokenized\nresource. If a processor is not configured with any predicates it will just extract the resource id.\n\n##### Example\n\n```python\nfrom limiter.event_processors import EventProcessorManager, EventProcessor, ProcessorPredicate\n\npredicate = ProcessorPredicate('detail.name', lambda name: 'debugging' not in name)\nprocessor = EventProcessor('aws.emr', 'detail.clusterId', predicate=predicate)\nmanager = EventProcessorManager(table_name='table', index_name='idx', processors=[processor])\n\ndef handler(event, context):\n  manager.process_event(event)\n```\n\nEventProcessors can also be configured with the event `detail-type`. The EventProcessor in the above example\ncan be tweaked to process EMR step changes.\n\n```python\n...\nprocessor = EventProcessor('aws.emr', 'detail.stepId', type='EMR Step Status Change', predicate=predicate)\n...\n```\n\n## Development\n\n### Dependencies\nThe dependencies needed for local development (running unit tests, etc.) are contained in `dev_requirements.txt` and\ncan be installed via pip: `pip install -r dev_requirements.txt`.\n\n### Unit Tests\nRunning the unit tests is done via a recipe in the `makefile`, the command: `make test`.\nThe unit tests are run with [nose](http://nose.readthedocs.io/en/latest/) inside a virtual environment managed\nby [tox](https://pypi.python.org/pypi/tox). The `test_requirements.txt` contains all the testing dependencies and\nis used to pip install everything needed by the tests in the tox environments (tox installs these dependencies).\n\n### Code Hygiene\nThe `make check` command will run [pylint](https://www.pylint.org/) with standards defined in `pylintrc`.\nThis is a measurable way to enforce style and standards.\n\n### Cleanup\nRun `make clean` to remove artifacts leftover by tox and pylint.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flifeomic%2Frate-limiter-py","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flifeomic%2Frate-limiter-py","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flifeomic%2Frate-limiter-py/lists"}