Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

modernize project #23

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 0 additions & 8 deletions .coveragerc

This file was deleted.

83 changes: 38 additions & 45 deletions .github/workflows/tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,39 +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:
- "pep8"
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "pypy3.9"]
os: ["ubuntu-22.04"]
include:
- python-version: "3.5"
os: ubuntu-20.04
toxenv: py35
- python-version: "3.6"
os: ubuntu-20.04
toxenv: py36
- python-version: "3.7"
os: ubuntu-20.04
toxenv: py37
- python-version: "3.8"
os: ubuntu-20.04
toxenv: py38
- python-version: "3.9"
os: ubuntu-20.04
toxenv: py39
- python-version: "3.10"
os: ubuntu-20.04
toxenv: py310
- python-version: pypy3
os: ubuntu-20.04
toxenv: pypy3
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 @@ -52,35 +39,41 @@ jobs:
uses: actions/checkout@v2

- name: Set up Python
uses: actions/setup-python@v2
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}

- name: Set up Python 3.8 env
if: ${{ matrix.toxenv == 'py35' }}
run: |
sudo apt-get update
sudo apt-get install -y build-essential python3.8 python3.8-dev python3.8-venv
python3.8 -m venv py38-env

- name: Install dependencies
run: |
if [ -f py38-env/bin/activate ]; then source py38-env/bin/activate; fi
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: |
if [ -f py38-env/bin/activate ]; then source py38-env/bin/activate; fi
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.

48 changes: 0 additions & 48 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__"]
45 changes: 15 additions & 30 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,70 +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