blitz-api-py 0.4.0__tar.gz → 0.5.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.
Files changed (43) hide show
  1. {blitz_api_py-0.4.0 → blitz_api_py-0.5.0}/CHANGELOG.md +7 -0
  2. {blitz_api_py-0.4.0 → blitz_api_py-0.5.0}/PKG-INFO +8 -5
  3. {blitz_api_py-0.4.0 → blitz_api_py-0.5.0}/README.md +7 -4
  4. {blitz_api_py-0.4.0 → blitz_api_py-0.5.0}/src/blitz_api/_client_async.py +23 -2
  5. {blitz_api_py-0.4.0 → blitz_api_py-0.5.0}/src/blitz_api/_client_sync.py +23 -2
  6. {blitz_api_py-0.4.0 → blitz_api_py-0.5.0}/src/blitz_api/_rate_limit_async.py +6 -4
  7. {blitz_api_py-0.4.0 → blitz_api_py-0.5.0}/src/blitz_api/_rate_limit_sync.py +6 -4
  8. blitz_api_py-0.5.0/src/blitz_api/_version.py +1 -0
  9. blitz_api_py-0.4.0/src/blitz_api/_version.py +0 -1
  10. {blitz_api_py-0.4.0 → blitz_api_py-0.5.0}/.gitignore +0 -0
  11. {blitz_api_py-0.4.0 → blitz_api_py-0.5.0}/LICENSE +0 -0
  12. {blitz_api_py-0.4.0 → blitz_api_py-0.5.0}/pyproject.toml +0 -0
  13. {blitz_api_py-0.4.0 → blitz_api_py-0.5.0}/src/blitz_api/__init__.py +0 -0
  14. {blitz_api_py-0.4.0 → blitz_api_py-0.5.0}/src/blitz_api/_base_client.py +0 -0
  15. {blitz_api_py-0.4.0 → blitz_api_py-0.5.0}/src/blitz_api/_client.py +0 -0
  16. {blitz_api_py-0.4.0 → blitz_api_py-0.5.0}/src/blitz_api/_compat.py +0 -0
  17. {blitz_api_py-0.4.0 → blitz_api_py-0.5.0}/src/blitz_api/_constants.py +0 -0
  18. {blitz_api_py-0.4.0 → blitz_api_py-0.5.0}/src/blitz_api/_exceptions.py +0 -0
  19. {blitz_api_py-0.4.0 → blitz_api_py-0.5.0}/src/blitz_api/_pagination_async.py +0 -0
  20. {blitz_api_py-0.4.0 → blitz_api_py-0.5.0}/src/blitz_api/_pagination_base.py +0 -0
  21. {blitz_api_py-0.4.0 → blitz_api_py-0.5.0}/src/blitz_api/_pagination_sync.py +0 -0
  22. {blitz_api_py-0.4.0 → blitz_api_py-0.5.0}/src/blitz_api/_rate_limit.py +0 -0
  23. {blitz_api_py-0.4.0 → blitz_api_py-0.5.0}/src/blitz_api/py.typed +0 -0
  24. {blitz_api_py-0.4.0 → blitz_api_py-0.5.0}/src/blitz_api/resources/__init__.py +0 -0
  25. {blitz_api_py-0.4.0 → blitz_api_py-0.5.0}/src/blitz_api/resources/_async/__init__.py +0 -0
  26. {blitz_api_py-0.4.0 → blitz_api_py-0.5.0}/src/blitz_api/resources/_async/account.py +0 -0
  27. {blitz_api_py-0.4.0 → blitz_api_py-0.5.0}/src/blitz_api/resources/_async/enrichment.py +0 -0
  28. {blitz_api_py-0.4.0 → blitz_api_py-0.5.0}/src/blitz_api/resources/_async/search.py +0 -0
  29. {blitz_api_py-0.4.0 → blitz_api_py-0.5.0}/src/blitz_api/resources/_async/utils.py +0 -0
  30. {blitz_api_py-0.4.0 → blitz_api_py-0.5.0}/src/blitz_api/resources/_sync/__init__.py +0 -0
  31. {blitz_api_py-0.4.0 → blitz_api_py-0.5.0}/src/blitz_api/resources/_sync/account.py +0 -0
  32. {blitz_api_py-0.4.0 → blitz_api_py-0.5.0}/src/blitz_api/resources/_sync/enrichment.py +0 -0
  33. {blitz_api_py-0.4.0 → blitz_api_py-0.5.0}/src/blitz_api/resources/_sync/search.py +0 -0
  34. {blitz_api_py-0.4.0 → blitz_api_py-0.5.0}/src/blitz_api/resources/_sync/utils.py +0 -0
  35. {blitz_api_py-0.4.0 → blitz_api_py-0.5.0}/src/blitz_api/types/__init__.py +0 -0
  36. {blitz_api_py-0.4.0 → blitz_api_py-0.5.0}/src/blitz_api/types/_models.py +0 -0
  37. {blitz_api_py-0.4.0 → blitz_api_py-0.5.0}/src/blitz_api/types/account.py +0 -0
  38. {blitz_api_py-0.4.0 → blitz_api_py-0.5.0}/src/blitz_api/types/enrichment.py +0 -0
  39. {blitz_api_py-0.4.0 → blitz_api_py-0.5.0}/src/blitz_api/types/enums.py +0 -0
  40. {blitz_api_py-0.4.0 → blitz_api_py-0.5.0}/src/blitz_api/types/filters.py +0 -0
  41. {blitz_api_py-0.4.0 → blitz_api_py-0.5.0}/src/blitz_api/types/search.py +0 -0
  42. {blitz_api_py-0.4.0 → blitz_api_py-0.5.0}/src/blitz_api/types/shared.py +0 -0
  43. {blitz_api_py-0.4.0 → blitz_api_py-0.5.0}/src/blitz_api/types/utils.py +0 -0
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.5.0](https://github.com/api-blitz/blitz-api-py/compare/v0.4.0...v0.5.0) (2026-06-18)
4
+
5
+
6
+ ### Features
7
+
8
+ * scope client-side rate limiting per endpoint ([#13](https://github.com/api-blitz/blitz-api-py/issues/13)) ([de5308b](https://github.com/api-blitz/blitz-api-py/commit/de5308b6995edc68dd1e43a8554296b7de095df2))
9
+
3
10
  ## [0.4.0](https://github.com/api-blitz/blitz-api-py/compare/v0.3.0...v0.4.0) (2026-06-17)
4
11
 
5
12
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: blitz-api-py
3
- Version: 0.4.0
3
+ Version: 0.5.0
4
4
  Summary: Typed Python SDK for the Blitz API — B2B data, search, and enrichment.
5
5
  Project-URL: Homepage, https://blitz-api.ai
6
6
  Project-URL: Documentation, https://docs.blitz-api.ai
@@ -258,10 +258,13 @@ client = BlitzAPI(
258
258
  ```
259
259
 
260
260
  The client-side rate limiter is a sliding window — at most `rate_limit_rps` requests
261
- in any rolling second — so a single client instance stays under the API's limit (5 req/s
262
- by default; check your key's limit via
263
- `client.account.key_info().max_requests_per_seconds`). Across multiple processes you may
264
- still hit `429` the retry path handles that.
261
+ in any rolling second — applied **per endpoint**: each endpoint (e.g. `.email` vs
262
+ `.phone`) is throttled independently, mirroring the API's own limit, which is also per
263
+ endpoint (5 req/s by default; check yours via
264
+ `client.account.key_info().max_requests_per_seconds`). A single client instance therefore
265
+ stays under the limit on every endpoint, so a burst on one never blocks another. Across
266
+ multiple processes — which share an endpoint's budget — you may still hit `429`; the retry
267
+ path handles that.
265
268
 
266
269
  Every method also accepts a per-call `timeout` (seconds or an `httpx.Timeout`) when one
267
270
  endpoint needs longer than the client default:
@@ -227,10 +227,13 @@ client = BlitzAPI(
227
227
  ```
228
228
 
229
229
  The client-side rate limiter is a sliding window — at most `rate_limit_rps` requests
230
- in any rolling second — so a single client instance stays under the API's limit (5 req/s
231
- by default; check your key's limit via
232
- `client.account.key_info().max_requests_per_seconds`). Across multiple processes you may
233
- still hit `429` the retry path handles that.
230
+ in any rolling second — applied **per endpoint**: each endpoint (e.g. `.email` vs
231
+ `.phone`) is throttled independently, mirroring the API's own limit, which is also per
232
+ endpoint (5 req/s by default; check yours via
233
+ `client.account.key_info().max_requests_per_seconds`). A single client instance therefore
234
+ stays under the limit on every endpoint, so a burst on one never blocks another. Across
235
+ multiple processes — which share an endpoint's budget — you may still hit `429`; the retry
236
+ path handles that.
234
237
 
235
238
  Every method also accepts a per-call `timeout` (seconds or an `httpx.Timeout`) when one
236
239
  endpoint needs longer than the client default:
@@ -50,13 +50,34 @@ class AsyncBlitzAPI(BaseClient):
50
50
  )
51
51
  self._http_client = http_client or httpx.AsyncClient(timeout=timeout)
52
52
  self._owns_http_client = http_client is None
53
- self._rate_limiter = AsyncRateLimiter(rate_limit_rps)
53
+ self._rate_limit_rps = rate_limit_rps
54
+ # One limiter per endpoint path so each endpoint's rate limit is tracked
55
+ # independently (e.g. ``.email`` and ``.phone`` do not share a budget). Built
56
+ # lazily in ``_limiter_for`` on first use of each path.
57
+ self._rate_limiters: dict[str, AsyncRateLimiter] = {}
54
58
  if sleep is None:
55
59
  import asyncio
56
60
 
57
61
  sleep = asyncio.sleep
58
62
  self._sleep = sleep
59
63
 
64
+ def _limiter_for(self, path: str) -> AsyncRateLimiter:
65
+ """Return the per-endpoint limiter for ``path``, creating it on first use.
66
+
67
+ Each endpoint path gets its own sliding window so its rate limit is tracked
68
+ independently of every other endpoint.
69
+ """
70
+ limiter = self._rate_limiters.get(path)
71
+ if limiter is None:
72
+ # ``setdefault`` keeps concurrent first-callers on the same instance; any
73
+ # extra limiter built in a race is harmlessly discarded. Thread the client's
74
+ # ``sleep`` through so a custom/fake sleep injected for tests also drives the
75
+ # limiter's throttle wait, not just the retry backoff.
76
+ limiter = self._rate_limiters.setdefault(
77
+ path, AsyncRateLimiter(self._rate_limit_rps, sleep=self._sleep)
78
+ )
79
+ return limiter
80
+
60
81
  async def _request(
61
82
  self,
62
83
  method: str,
@@ -72,7 +93,7 @@ class AsyncBlitzAPI(BaseClient):
72
93
 
73
94
  attempt = 0
74
95
  while True:
75
- await self._rate_limiter.acquire()
96
+ await self._limiter_for(path).acquire()
76
97
  try:
77
98
  if timeout is None:
78
99
  response = await self._http_client.request(
@@ -52,13 +52,34 @@ class BlitzAPI(BaseClient):
52
52
  )
53
53
  self._http_client = http_client or httpx.Client(timeout=timeout)
54
54
  self._owns_http_client = http_client is None
55
- self._rate_limiter = RateLimiter(rate_limit_rps)
55
+ self._rate_limit_rps = rate_limit_rps
56
+ # One limiter per endpoint path so each endpoint's rate limit is tracked
57
+ # independently (e.g. ``.email`` and ``.phone`` do not share a budget). Built
58
+ # lazily in ``_limiter_for`` on first use of each path.
59
+ self._rate_limiters: dict[str, RateLimiter] = {}
56
60
  if sleep is None:
57
61
  import time
58
62
 
59
63
  sleep = time.sleep
60
64
  self._sleep = sleep
61
65
 
66
+ def _limiter_for(self, path: str) -> RateLimiter:
67
+ """Return the per-endpoint limiter for ``path``, creating it on first use.
68
+
69
+ Each endpoint path gets its own sliding window so its rate limit is tracked
70
+ independently of every other endpoint.
71
+ """
72
+ limiter = self._rate_limiters.get(path)
73
+ if limiter is None:
74
+ # ``setdefault`` keeps concurrent first-callers on the same instance; any
75
+ # extra limiter built in a race is harmlessly discarded. Thread the client's
76
+ # ``sleep`` through so a custom/fake sleep injected for tests also drives the
77
+ # limiter's throttle wait, not just the retry backoff.
78
+ limiter = self._rate_limiters.setdefault(
79
+ path, RateLimiter(self._rate_limit_rps, sleep=self._sleep)
80
+ )
81
+ return limiter
82
+
62
83
  def _request(
63
84
  self,
64
85
  method: str,
@@ -74,7 +95,7 @@ class BlitzAPI(BaseClient):
74
95
 
75
96
  attempt = 0
76
97
  while True:
77
- self._rate_limiter.acquire()
98
+ self._limiter_for(path).acquire()
78
99
  try:
79
100
  if timeout is None:
80
101
  response = self._http_client.request(
@@ -1,9 +1,11 @@
1
1
  """Client-side sliding-window rate limiter (async source; sync twin generated).
2
2
 
3
- The API enforces a per-key request rate (5 req/s by default). This limiter throttles
4
- outgoing requests *before* they are sent so a single client instance stays under the
5
- limit proactively; the server-side 429 retry path is the backstop for bursts across
6
- processes.
3
+ The API enforces a per-endpoint request rate (5 req/s by default). This limiter throttles
4
+ outgoing requests *before* they are sent. The client holds one limiter **per endpoint
5
+ path** (see ``_client_async.py``), so each endpoint is throttled to ``rps`` independently,
6
+ mirroring the server's per-endpoint budget; a single client instance therefore stays under
7
+ the limit on every endpoint on its own. The server-side 429 retry path is the backstop for
8
+ bursts across processes, which share the same per-endpoint budget.
7
9
 
8
10
  The algorithm is a sliding window: at most ``rps`` requests may begin in any rolling
9
11
  one-second window. This matches the Blitz docs ("max 5 requests per 1000 ms") and the
@@ -2,10 +2,12 @@
2
2
  # Do not edit by hand — edit the async source and run `python scripts/gen_sync.py`.
3
3
  """Client-side sliding-window rate limiter (async source; sync twin generated).
4
4
 
5
- The API enforces a per-key request rate (5 req/s by default). This limiter throttles
6
- outgoing requests *before* they are sent so a single client instance stays under the
7
- limit proactively; the server-side 429 retry path is the backstop for bursts across
8
- processes.
5
+ The API enforces a per-endpoint request rate (5 req/s by default). This limiter throttles
6
+ outgoing requests *before* they are sent. The client holds one limiter **per endpoint
7
+ path** (see ``_client_async.py``), so each endpoint is throttled to ``rps`` independently,
8
+ mirroring the server's per-endpoint budget; a single client instance therefore stays under
9
+ the limit on every endpoint on its own. The server-side 429 retry path is the backstop for
10
+ bursts across processes, which share the same per-endpoint budget.
9
11
 
10
12
  The algorithm is a sliding window: at most ``rps`` requests may begin in any rolling
11
13
  one-second window. This matches the Blitz docs ("max 5 requests per 1000 ms") and the
@@ -0,0 +1 @@
1
+ __version__ = "0.5.0" # x-release-please-version
@@ -1 +0,0 @@
1
- __version__ = "0.4.0" # x-release-please-version
File without changes
File without changes