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.
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.3
1
+ Metadata-Version: 2.4
2
2
  Name: httpx-limiter
3
- Version: 0.1.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
- Keywords: httpx,limiter,rate-limit
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](). Install it, for example, with
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 a
53
- controlled number of requests per time, you can apply rate limiting to an HTTPX client
54
- using the provided transport. If you want to be able to make ten requests per second,
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 with httpx.AsyncClient(
62
- transport=AsyncRateLimitedTransport.create(rate=10, capacity=10),
63
- ) as client:
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](). Install it, for example, with
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 a
22
- controlled number of requests per time, you can apply rate limiting to an HTTPX client
23
- using the provided transport. If you want to be able to make ten requests per second,
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 with httpx.AsyncClient(
31
- transport=AsyncRateLimitedTransport.create(rate=10, capacity=10),
32
- ) as client:
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
- "limiter ~=0.5",
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" # TODO: Other styles are possible here, like 'numpy'
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.install.scripts]
233
- check = [
234
- "pip check",
235
- "hatch build {args:--clean}",
236
- "twine check dist/*",
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.test]
240
- description = """Run the test suite."""
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.test.matrix]]
266
+ [[tool.hatch.envs.acceptance-tests.matrix]]
250
267
  python = ["3.10", "3.11", "3.12", "3.13"]
251
268
 
252
- [tool.hatch.envs.test.scripts]
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"
@@ -15,4 +15,5 @@
15
15
 
16
16
  """Provide top level symbols."""
17
17
 
18
+ from .rate import Number, Rate
18
19
  from .async_rate_limited_transport import AsyncRateLimitedTransport
@@ -1,8 +1,13 @@
1
- # file generated by setuptools_scm
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, Union
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.1.0'
16
- __version_tuple__ = version_tuple = (0, 1, 0)
20
+ __version__ = version = '0.2.0'
21
+ __version_tuple__ = version_tuple = (0, 2, 0)
@@ -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 limiter import Limiter, Tokens
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 token bucket algorithm in order to rate-limit the number of
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: 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: Tokens,
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 refresh rate per second for bucket tokens.
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=Limiter(rate=rate, capacity=capacity),
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