httpx-limiter 0.1.0__tar.gz → 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {httpx_limiter-0.1.0 → httpx_limiter-0.2.0}/PKG-INFO +19 -16
- {httpx_limiter-0.1.0 → httpx_limiter-0.2.0}/README.md +13 -11
- {httpx_limiter-0.1.0 → httpx_limiter-0.2.0}/pyproject.toml +40 -23
- {httpx_limiter-0.1.0 → httpx_limiter-0.2.0}/src/httpx_limiter/__init__.py +1 -0
- {httpx_limiter-0.1.0 → httpx_limiter-0.2.0}/src/httpx_limiter/_version.py +9 -4
- {httpx_limiter-0.1.0 → httpx_limiter-0.2.0}/src/httpx_limiter/async_rate_limited_transport.py +16 -9
- httpx_limiter-0.2.0/src/httpx_limiter/rate.py +45 -0
- {httpx_limiter-0.1.0 → httpx_limiter-0.2.0}/.gitignore +0 -0
- {httpx_limiter-0.1.0 → httpx_limiter-0.2.0}/LICENSE +0 -0
- {httpx_limiter-0.1.0 → httpx_limiter-0.2.0}/src/httpx_limiter/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: httpx-limiter
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: A lightweight package that provides rate-limited httpx transports.
|
|
5
5
|
Project-URL: Homepage, https://github.com/Midnighter/httpx-limiter
|
|
6
6
|
Project-URL: Documentation, https://httpx-limiter.readthedocs.io
|
|
@@ -8,8 +8,9 @@ Project-URL: Source Code, https://github.com/Midnighter/httpx-limiter
|
|
|
8
8
|
Project-URL: Bug Tracker, https://github.com/Midnighter/httpx-limiter/issues
|
|
9
9
|
Project-URL: Download, https://pypi.org/project/httpx-limiter/#files
|
|
10
10
|
Author-email: "Moritz E. Beber" <midnighter@posteo.net>
|
|
11
|
-
License: Apache-2.0
|
|
12
|
-
|
|
11
|
+
License-Expression: Apache-2.0
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Keywords: httpx,leaky bucket,limiter,rate-limit
|
|
13
14
|
Classifier: Development Status :: 2 - Pre-Alpha
|
|
14
15
|
Classifier: Environment :: Web Environment
|
|
15
16
|
Classifier: Framework :: AsyncIO
|
|
@@ -25,8 +26,8 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
25
26
|
Classifier: Topic :: Internet :: WWW/HTTP
|
|
26
27
|
Classifier: Typing :: Typed
|
|
27
28
|
Requires-Python: >=3.10
|
|
29
|
+
Requires-Dist: aiolimiter~=1.2
|
|
28
30
|
Requires-Dist: httpx~=0.25
|
|
29
|
-
Requires-Dist: limiter~=0.5
|
|
30
31
|
Description-Content-Type: text/markdown
|
|
31
32
|
|
|
32
33
|
# HTTPX Limiter
|
|
@@ -41,7 +42,8 @@ _A lightweight package that provides rate-limited httpx transports._
|
|
|
41
42
|
|
|
42
43
|
## Installation
|
|
43
44
|
|
|
44
|
-
The package is published on [PyPI]().
|
|
45
|
+
The package is published on [PyPI](https://pypi.org/project/httpx-limiter/).
|
|
46
|
+
Install it, for example, with
|
|
45
47
|
|
|
46
48
|
```sh
|
|
47
49
|
pip install httpx-limiter
|
|
@@ -49,22 +51,23 @@ pip install httpx-limiter
|
|
|
49
51
|
|
|
50
52
|
## Usage
|
|
51
53
|
|
|
52
|
-
For situations when you need to make a large number of asynchronous request with
|
|
53
|
-
controlled number of requests per time, you can apply rate limiting to an
|
|
54
|
-
using the provided transport. If you want to be able to make ten
|
|
55
|
-
for example, use the following:
|
|
54
|
+
For situations when you need to make a large number of asynchronous request with
|
|
55
|
+
a controlled number of requests per unit time, you can apply rate limiting to an
|
|
56
|
+
HTTPX client using the provided transport. If you want to be able to make ten
|
|
57
|
+
requests per second, for example, use the following:
|
|
56
58
|
|
|
57
59
|
```python
|
|
58
60
|
import httpx
|
|
59
|
-
from httpx_limiter import AsyncRateLimitedTransport
|
|
61
|
+
from httpx_limiter import AsyncRateLimitedTransport, Rate
|
|
60
62
|
|
|
61
|
-
async
|
|
62
|
-
|
|
63
|
-
)
|
|
64
|
-
|
|
63
|
+
async def main():
|
|
64
|
+
async with httpx.AsyncClient(
|
|
65
|
+
transport=AsyncRateLimitedTransport.create(rate=Rate.create(magnitude=10)),
|
|
66
|
+
) as client:
|
|
67
|
+
response = await client.get("https://httpbin.org")
|
|
65
68
|
```
|
|
66
69
|
|
|
67
70
|
## Copyright
|
|
68
71
|
|
|
69
|
-
- Copyright © 2024 Moritz E. Beber.
|
|
72
|
+
- Copyright © 2024,2025 Moritz E. Beber.
|
|
70
73
|
- Free software distributed under the [Apache Software License 2.0](./LICENSE).
|
|
@@ -10,7 +10,8 @@ _A lightweight package that provides rate-limited httpx transports._
|
|
|
10
10
|
|
|
11
11
|
## Installation
|
|
12
12
|
|
|
13
|
-
The package is published on [PyPI]().
|
|
13
|
+
The package is published on [PyPI](https://pypi.org/project/httpx-limiter/).
|
|
14
|
+
Install it, for example, with
|
|
14
15
|
|
|
15
16
|
```sh
|
|
16
17
|
pip install httpx-limiter
|
|
@@ -18,22 +19,23 @@ pip install httpx-limiter
|
|
|
18
19
|
|
|
19
20
|
## Usage
|
|
20
21
|
|
|
21
|
-
For situations when you need to make a large number of asynchronous request with
|
|
22
|
-
controlled number of requests per time, you can apply rate limiting to an
|
|
23
|
-
using the provided transport. If you want to be able to make ten
|
|
24
|
-
for example, use the following:
|
|
22
|
+
For situations when you need to make a large number of asynchronous request with
|
|
23
|
+
a controlled number of requests per unit time, you can apply rate limiting to an
|
|
24
|
+
HTTPX client using the provided transport. If you want to be able to make ten
|
|
25
|
+
requests per second, for example, use the following:
|
|
25
26
|
|
|
26
27
|
```python
|
|
27
28
|
import httpx
|
|
28
|
-
from httpx_limiter import AsyncRateLimitedTransport
|
|
29
|
+
from httpx_limiter import AsyncRateLimitedTransport, Rate
|
|
29
30
|
|
|
30
|
-
async
|
|
31
|
-
|
|
32
|
-
)
|
|
33
|
-
|
|
31
|
+
async def main():
|
|
32
|
+
async with httpx.AsyncClient(
|
|
33
|
+
transport=AsyncRateLimitedTransport.create(rate=Rate.create(magnitude=10)),
|
|
34
|
+
) as client:
|
|
35
|
+
response = await client.get("https://httpbin.org")
|
|
34
36
|
```
|
|
35
37
|
|
|
36
38
|
## Copyright
|
|
37
39
|
|
|
38
|
-
- Copyright © 2024 Moritz E. Beber.
|
|
40
|
+
- Copyright © 2024,2025 Moritz E. Beber.
|
|
39
41
|
- Free software distributed under the [Apache Software License 2.0](./LICENSE).
|
|
@@ -41,10 +41,11 @@ keywords = [
|
|
|
41
41
|
"httpx",
|
|
42
42
|
"limiter",
|
|
43
43
|
"rate-limit",
|
|
44
|
+
"leaky bucket",
|
|
44
45
|
]
|
|
45
46
|
dependencies = [
|
|
46
47
|
"httpx ~=0.25",
|
|
47
|
-
"
|
|
48
|
+
"aiolimiter ~=1.2",
|
|
48
49
|
]
|
|
49
50
|
|
|
50
51
|
[project.urls]
|
|
@@ -103,8 +104,6 @@ ignore = [
|
|
|
103
104
|
"D407", # 'Missing dashed underline after section' ignored because Google style docstrings don't underline.
|
|
104
105
|
"ANN002", # 'Missing type annotation for {*args} in method'.
|
|
105
106
|
"ANN003", # 'Missing type annotation for {*kwargs} in method'.
|
|
106
|
-
"ANN101", # 'Missing type annotation for {self} in method'.
|
|
107
|
-
"ANN102", # 'Missing type annotation for {cls} in classmethod'.
|
|
108
107
|
]
|
|
109
108
|
exclude = [
|
|
110
109
|
"src/httpx_limiter/_version.py",
|
|
@@ -130,7 +129,7 @@ known-first-party = ["src", "httpx_limiter"]
|
|
|
130
129
|
lines-after-imports = 2
|
|
131
130
|
|
|
132
131
|
[tool.pydoclint]
|
|
133
|
-
style = "google"
|
|
132
|
+
style = "google"
|
|
134
133
|
arg-type-hints-in-docstring = false
|
|
135
134
|
check-return-types = false
|
|
136
135
|
check-yield-types = false
|
|
@@ -173,20 +172,21 @@ extra-dependencies = [
|
|
|
173
172
|
"pytest",
|
|
174
173
|
"pytest-httpx",
|
|
175
174
|
"pytest-watcher",
|
|
175
|
+
"ruff",
|
|
176
176
|
]
|
|
177
177
|
path = ".venv"
|
|
178
178
|
|
|
179
179
|
[tool.hatch.envs.style]
|
|
180
180
|
description = """Check the style of the codebase."""
|
|
181
181
|
dependencies = [
|
|
182
|
-
"pydoclint",
|
|
182
|
+
"pydoclint ~=0.6",
|
|
183
183
|
"ruff",
|
|
184
184
|
]
|
|
185
185
|
detached = true
|
|
186
186
|
installer = "uv"
|
|
187
187
|
|
|
188
188
|
[tool.hatch.envs.style.scripts]
|
|
189
|
-
docstrings = "pydoclint"
|
|
189
|
+
docstrings = ["pydoclint src", "pydoclint tests"]
|
|
190
190
|
code = "ruff check {args}"
|
|
191
191
|
format = "ruff format {args}"
|
|
192
192
|
check = ["docstrings", "code"]
|
|
@@ -221,33 +221,50 @@ extra-dependencies = [
|
|
|
221
221
|
build = "mkdocs build {args:--clean --strict}"
|
|
222
222
|
serve = "mkdocs serve {args}"
|
|
223
223
|
|
|
224
|
-
[tool.hatch.envs.install]
|
|
225
|
-
description = """Test the installation the package."""
|
|
226
|
-
dependencies = [
|
|
227
|
-
"pip",
|
|
228
|
-
"twine",
|
|
229
|
-
]
|
|
230
|
-
detached = true
|
|
224
|
+
#[tool.hatch.envs.install]
|
|
225
|
+
#description = """Test the installation the package."""
|
|
226
|
+
#dependencies = [
|
|
227
|
+
# "pip",
|
|
228
|
+
# "twine",
|
|
229
|
+
#]
|
|
230
|
+
#detached = true
|
|
231
|
+
|
|
232
|
+
#[tool.hatch.envs.install.scripts]
|
|
233
|
+
#check = [
|
|
234
|
+
# "pip check",
|
|
235
|
+
# "hatch build {args:--clean}",
|
|
236
|
+
# "twine check dist/*",
|
|
237
|
+
#]
|
|
231
238
|
|
|
232
|
-
[tool.hatch.envs.
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
"
|
|
236
|
-
"
|
|
239
|
+
[tool.hatch.envs.unit-tests]
|
|
240
|
+
description = """Run the unit tests."""
|
|
241
|
+
extra-dependencies = [
|
|
242
|
+
"anyio",
|
|
243
|
+
"pytest",
|
|
244
|
+
"pytest-cov",
|
|
245
|
+
"pytest-httpx",
|
|
246
|
+
"pytest-raises",
|
|
237
247
|
]
|
|
238
248
|
|
|
239
|
-
[tool.hatch.envs.
|
|
240
|
-
|
|
249
|
+
[[tool.hatch.envs.unit-tests.matrix]]
|
|
250
|
+
python = ["3.10", "3.11", "3.12", "3.13"]
|
|
251
|
+
|
|
252
|
+
[tool.hatch.envs.unit-tests.scripts]
|
|
253
|
+
run = "pytest {args:--cov=httpx_limiter --cov-report=term-missing} {root}/tests/unit"
|
|
254
|
+
|
|
255
|
+
[tool.hatch.envs.acceptance-tests]
|
|
256
|
+
description = """Run the acceptance tests."""
|
|
241
257
|
extra-dependencies = [
|
|
242
258
|
"anyio",
|
|
259
|
+
# TODO: Use behave in future.
|
|
243
260
|
"pytest",
|
|
244
261
|
"pytest-cov",
|
|
245
262
|
"pytest-httpx",
|
|
246
263
|
"pytest-raises",
|
|
247
264
|
]
|
|
248
265
|
|
|
249
|
-
[[tool.hatch.envs.
|
|
266
|
+
[[tool.hatch.envs.acceptance-tests.matrix]]
|
|
250
267
|
python = ["3.10", "3.11", "3.12", "3.13"]
|
|
251
268
|
|
|
252
|
-
[tool.hatch.envs.
|
|
253
|
-
run = "pytest {args:--cov=httpx_limiter --cov-report=term-missing}"
|
|
269
|
+
[tool.hatch.envs.acceptance-tests.scripts]
|
|
270
|
+
run = "pytest {args:--cov=httpx_limiter --cov-report=term-missing} {root}/tests/acceptance"
|
|
@@ -1,8 +1,13 @@
|
|
|
1
|
-
# file generated by
|
|
1
|
+
# file generated by setuptools-scm
|
|
2
2
|
# don't change, don't track in version control
|
|
3
|
+
|
|
4
|
+
__all__ = ["__version__", "__version_tuple__", "version", "version_tuple"]
|
|
5
|
+
|
|
3
6
|
TYPE_CHECKING = False
|
|
4
7
|
if TYPE_CHECKING:
|
|
5
|
-
from typing import Tuple
|
|
8
|
+
from typing import Tuple
|
|
9
|
+
from typing import Union
|
|
10
|
+
|
|
6
11
|
VERSION_TUPLE = Tuple[Union[int, str], ...]
|
|
7
12
|
else:
|
|
8
13
|
VERSION_TUPLE = object
|
|
@@ -12,5 +17,5 @@ __version__: str
|
|
|
12
17
|
__version_tuple__: VERSION_TUPLE
|
|
13
18
|
version_tuple: VERSION_TUPLE
|
|
14
19
|
|
|
15
|
-
__version__ = version = '0.
|
|
16
|
-
__version_tuple__ = version_tuple = (0,
|
|
20
|
+
__version__ = version = '0.2.0'
|
|
21
|
+
__version_tuple__ = version_tuple = (0, 2, 0)
|
{httpx_limiter-0.1.0 → httpx_limiter-0.2.0}/src/httpx_limiter/async_rate_limited_transport.py
RENAMED
|
@@ -17,8 +17,14 @@
|
|
|
17
17
|
|
|
18
18
|
from __future__ import annotations
|
|
19
19
|
|
|
20
|
+
from typing import TYPE_CHECKING
|
|
21
|
+
|
|
20
22
|
import httpx
|
|
21
|
-
from
|
|
23
|
+
from aiolimiter import AsyncLimiter
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
if TYPE_CHECKING:
|
|
27
|
+
from .rate import Rate
|
|
22
28
|
|
|
23
29
|
|
|
24
30
|
class AsyncRateLimitedTransport(httpx.AsyncBaseTransport):
|
|
@@ -26,7 +32,7 @@ class AsyncRateLimitedTransport(httpx.AsyncBaseTransport):
|
|
|
26
32
|
Define the asynchronous rate-limited transport.
|
|
27
33
|
|
|
28
34
|
This transport consists of a composed transport for handling requests and an
|
|
29
|
-
implementation of a
|
|
35
|
+
implementation of a leaky bucket algorithm in order to rate-limit the number of
|
|
30
36
|
requests.
|
|
31
37
|
|
|
32
38
|
"""
|
|
@@ -34,7 +40,7 @@ class AsyncRateLimitedTransport(httpx.AsyncBaseTransport):
|
|
|
34
40
|
def __init__(
|
|
35
41
|
self,
|
|
36
42
|
*,
|
|
37
|
-
limiter:
|
|
43
|
+
limiter: AsyncLimiter,
|
|
38
44
|
transport: httpx.AsyncBaseTransport,
|
|
39
45
|
**kwargs,
|
|
40
46
|
) -> None:
|
|
@@ -46,20 +52,18 @@ class AsyncRateLimitedTransport(httpx.AsyncBaseTransport):
|
|
|
46
52
|
def create(
|
|
47
53
|
cls,
|
|
48
54
|
*,
|
|
49
|
-
rate:
|
|
50
|
-
capacity: Tokens,
|
|
55
|
+
rate: Rate,
|
|
51
56
|
**kwargs: dict,
|
|
52
57
|
) -> AsyncRateLimitedTransport:
|
|
53
58
|
"""
|
|
54
|
-
Create an instance of asynchronous limited transport.
|
|
59
|
+
Create an instance of asynchronous rate-limited transport.
|
|
55
60
|
|
|
56
61
|
This factory method constructs the instance with an underlying
|
|
57
62
|
`httpx.AsyncHTTPTransport`.
|
|
58
63
|
That transport is passed any additional keyword arguments.
|
|
59
64
|
|
|
60
65
|
Args:
|
|
61
|
-
rate: The
|
|
62
|
-
capacity: The maximum number of tokens the bucket can contain.
|
|
66
|
+
rate: The maximum rate per interval at which bucket capacity is restored.
|
|
63
67
|
**kwargs: Additional keyword arguments are used in the construction of an
|
|
64
68
|
`httpx.AsyncHTTPTransport`.
|
|
65
69
|
|
|
@@ -68,7 +72,10 @@ class AsyncRateLimitedTransport(httpx.AsyncBaseTransport):
|
|
|
68
72
|
|
|
69
73
|
"""
|
|
70
74
|
return cls(
|
|
71
|
-
limiter=
|
|
75
|
+
limiter=AsyncLimiter(
|
|
76
|
+
max_rate=rate.magnitude,
|
|
77
|
+
time_period=rate.in_seconds(),
|
|
78
|
+
),
|
|
72
79
|
transport=httpx.AsyncHTTPTransport(**kwargs), # type: ignore[arg-type]
|
|
73
80
|
)
|
|
74
81
|
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Copyright (c) 2025 Moritz E. Beber
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
|
4
|
+
# use this file except in compliance with the License. You may obtain a copy of
|
|
5
|
+
# the License at
|
|
6
|
+
#
|
|
7
|
+
# https://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
11
|
+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
12
|
+
# License for the specific language governing permissions and limitations under
|
|
13
|
+
# the License.
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
"""Provide a definition of a rate."""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
from datetime import timedelta
|
|
21
|
+
from typing import NamedTuple
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
Number = float | int
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class Rate(NamedTuple):
|
|
28
|
+
"""Define the rate."""
|
|
29
|
+
|
|
30
|
+
magnitude: float
|
|
31
|
+
duration: timedelta
|
|
32
|
+
|
|
33
|
+
@classmethod
|
|
34
|
+
def create(cls, magnitude: Number = 1, duration: timedelta | Number = 1) -> Rate:
|
|
35
|
+
"""Create a rate."""
|
|
36
|
+
magnitude = float(magnitude)
|
|
37
|
+
|
|
38
|
+
if not isinstance(duration, timedelta):
|
|
39
|
+
duration = timedelta(seconds=float(duration))
|
|
40
|
+
|
|
41
|
+
return cls(magnitude=magnitude, duration=duration)
|
|
42
|
+
|
|
43
|
+
def in_seconds(self) -> float:
|
|
44
|
+
"""Return the duration in unit seconds."""
|
|
45
|
+
return self.duration.total_seconds()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|