Skip to content

Commit

Permalink
Merge branch 'Wurstnase-master'
Browse files Browse the repository at this point in the history
  • Loading branch information
aluminum-ice committed Dec 21, 2023
2 parents 0878f9f + 50f4d07 commit d160b78
Show file tree
Hide file tree
Showing 20 changed files with 310 additions and 332 deletions.
8 changes: 0 additions & 8 deletions .coveragerc

This file was deleted.

75 changes: 37 additions & 38 deletions .github/workflows/tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,42 +11,26 @@ on:

jobs:
run_tox:
name: tox -e ${{ matrix.toxenv }} (${{matrix.python-version}} on ${{ matrix.os }})
name: tox run (${{ matrix.python-version }} on ${{ matrix.os }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
python-version:
- "3.8"
os:
- "ubuntu-20.04"
toxenv:
- "blue"
- "pep8"
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "pypy3.9"]
os: ["ubuntu-22.04"]
include:
- python-version: "3.7"
os: ubuntu-22.04
toxenv: py37
- python-version: "3.8"
os: ubuntu-22.04
toxenv: py38
- python-version: "3.9"
os: ubuntu-22.04
toxenv: py39
- python-version: "3.10"
os: ubuntu-22.04
toxenv: py310
- python-version: "3.11"
os: ubuntu-22.04
toxenv: py311
- python-version: "3.12.0-alpha - 3.12"
os: ubuntu-22.04
toxenv: py312
- python-version: "pypy3.9"
os: ubuntu-22.04
# NOTE(vytas): do not trace coverage under PyPy, it seems it is
# extremely expensive on PyPy 3.9 under extensive threading.
toxenv: nocover
os: "ubuntu-22.04"
coverage: true
mypy: true
pep8: true
- python-verson: "3.11"
os: "ubuntu-22.04"
mypy: true

env:
OS: ${{ matrix.os }}
PYTHON: ${{ matrix.python-version }}

# Steps to run in each job.
# Some are GitHub actions, others run shell commands.
Expand All @@ -61,20 +45,35 @@ jobs:

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install coverage tox
pip install tox
python --version
pip --version
tox --version
coverage --version
- name: Run tests
- name: Setup test suite
run: |
tox run -vv --notest
- name: Run test suite
run: |
tox run --skip-pkg-install
- name: Check pep8
if: matrix.pep8
run: |
tox run -e pep8
- name: Check mypy
if: matrix.mypy
run: |
tox -e ${{ matrix.toxenv }}
tox run -e mypy
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v1
if: ${{ matrix.toxenv == 'py38' }}
uses: codecov/codecov-action@v3
if: matrix.coverage
with:
env_vars: PYTHON
env_vars: OS,PYTHON
files: ./coverage.xml
flags: unittests
name: codecov-umbrella
fail_ci_if_error: true
4 changes: 0 additions & 4 deletions MANIFEST.in

This file was deleted.

71 changes: 71 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[project]
name = "token-bucket"
dynamic = ["version"]
description = "Very fast implementation of the token bucket algorithm."
readme = "README.rst"
license = "Apache-2.0"
requires-python = ">=3.7"
authors = [{ name = "kgriffs", email = "[email protected]" }]
keywords = [
"bucket",
"cloud",
"http",
"https",
"limiting",
"rate",
"throttling",
"token",
"web",
]
classifiers = [
"Development Status :: 4 - Beta",
"Environment :: Web Environment",
"Intended Audience :: Developers",
"Intended Audience :: System Administrators",
"License :: OSI Approved :: Apache Software License",
"Natural Language :: English",
"Operating System :: MacOS :: MacOS X",
"Operating System :: Microsoft :: Windows",
"Operating System :: POSIX",
"Programming Language :: Python",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: Python :: Implementation :: PyPy",
"Topic :: Internet :: WWW/HTTP",
"Topic :: Software Development :: Libraries",
]
dependencies = []

[project.urls]
Homepage = "https://github.com/falconry/token-bucket"

[tool.hatch.version]
path = "src/token_bucket/version.py"

[tool.hatch.build]
source = ["src"]

[tool.coverage.run]
branch = true
source = ["src"]
parallel = true

[tool.coverage.report]
show_missing = true
exclude_lines = [
"pragma: no cover",
"if __name__ == .__main__.:",
"@(abc\\.)?abstractmethod",
]

[tool.black]
line-length = 88
target-version = ['py37', 'py38']
3 changes: 2 additions & 1 deletion requirements/tests
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
coverage
pytest
pytest-cov
freezegun
11 changes: 0 additions & 11 deletions setup.cfg

This file was deleted.

55 changes: 0 additions & 55 deletions setup.py

This file was deleted.

10 changes: 6 additions & 4 deletions token_bucket/__init__.py → src/token_bucket/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
# not use this "front-door" module, but rather import using the
# fully-qualified paths.

from .version import __version__ # NOQA
from .storage import MemoryStorage # NOQA
from .storage_base import StorageBase # NOQA
from .limiter import Limiter # NOQA
from .limiter import Limiter
from .storage import MemoryStorage
from .storage_base import StorageBase
from .version import __version__

__all__ = ["Limiter", "MemoryStorage", "StorageBase", "__version__"]
47 changes: 15 additions & 32 deletions token_bucket/limiter.py → src/token_bucket/limiter.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,13 @@
# See the License for the specific language governing permissions and
# limitations under the License.

from typing import Union

from .storage_base import KeyType
from .storage_base import StorageBase


class Limiter(object):
class Limiter:
"""Limits demand for a finite resource via keyed token buckets.
A limiter manages a set of token buckets that have an identical
Expand Down Expand Up @@ -60,72 +63,52 @@ class Limiter(object):
"""

__slots__ = (
'_rate',
'_capacity',
'_storage',
"_rate",
"_capacity",
"_storage",
)

def __init__(self, rate, capacity, storage):
if not isinstance(rate, (float, int)):
raise TypeError('rate must be an int or float')

def __init__(self, rate: Union[float, int], capacity: int, storage: StorageBase):
if rate <= 0:
raise ValueError('rate must be > 0')

if not isinstance(capacity, int):
raise TypeError('capacity must be an int')
raise ValueError("rate must be > 0")

if capacity < 1:
raise ValueError('capacity must be >= 1')

if not isinstance(storage, StorageBase):
raise TypeError('storage must be a subclass of StorageBase')
raise ValueError("capacity must be >= 1")

self._rate = rate
self._capacity = capacity
self._storage = storage

def consume(self, key, num_tokens=1):
def consume(self, key: KeyType, num_tokens: int = 1) -> bool:
"""Attempt to take one or more tokens from a bucket.
If the specified token bucket does not yet exist, it will be
created and initialized to full capacity before proceeding.
Args:
key (bytes): A string or bytes object that specifies the
key: A string or bytes object that specifies the
token bucket to consume from. If a global limit is
desired for all consumers, the same key may be used
for every call to consume(). Otherwise, a key based on
consumer identity may be used to segregate limits.
Keyword Args:
num_tokens (int): The number of tokens to attempt to
num_tokens: The number of tokens to attempt to
consume, defaulting to 1 if not specified. It may
be appropriate to ask for more than one token according
to the proportion of the resource that a given request
will use, relative to other requests for the same
resource.
Returns:
bool: True if the requested number of tokens were removed
True if the requested number of tokens were removed
from the bucket (conforming), otherwise False (non-
conforming). The entire number of tokens requested must
be available in the bucket to be conforming. Otherwise,
no tokens will be removed (it's all or nothing).
"""

if not key:
if key is None:
raise TypeError('key may not be None')

raise ValueError(
'key must not be a non-empty string or bytestring'
)

if num_tokens is None:
raise TypeError('num_tokens may not be None')

if num_tokens < 1:
raise ValueError('num_tokens must be >= 1')
raise ValueError("num_tokens must be >= 1")

self._storage.replenish(key, self._rate, self._capacity)
return self._storage.consume(key, num_tokens)
Empty file added src/token_bucket/py.typed
Empty file.
Loading

0 comments on commit d160b78

Please sign in to comment.