xtr-http-client 4.0.4__tar.gz → 4.1.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.
- {xtr_http_client-4.0.4 → xtr_http_client-4.1.0}/PKG-INFO +25 -15
- {xtr_http_client-4.0.4 → xtr_http_client-4.1.0}/README.md +24 -9
- {xtr_http_client-4.0.4 → xtr_http_client-4.1.0}/pyproject.toml +2 -4
- {xtr_http_client-4.0.4 → xtr_http_client-4.1.0}/pyproject.toml.orig +3 -10
- {xtr_http_client-4.0.4 → xtr_http_client-4.1.0}/src/xtr_http_client/.agents/skills/xtr-http-client/SKILL.md +9 -3
- {xtr_http_client-4.0.4 → xtr_http_client-4.1.0}/src/xtr_http_client/bundle/_clients.py +1 -1
- {xtr_http_client-4.0.4 → xtr_http_client-4.1.0}/src/xtr_http_client/bundle/client_options_config.py +1 -1
- {xtr_http_client-4.0.4 → xtr_http_client-4.1.0}/src/xtr_http_client/bundle/http_client_bundle.py +1 -1
- {xtr_http_client-4.0.4 → xtr_http_client-4.1.0}/src/xtr_http_client/exception/__init__.py +5 -1
- xtr_http_client-4.1.0/src/xtr_http_client/exception/logic_error.py +16 -0
- {xtr_http_client-4.0.4 → xtr_http_client-4.1.0}/src/xtr_http_client/no_private_network_http_client.py +33 -6
- {xtr_http_client-4.0.4 → xtr_http_client-4.1.0}/LICENSE +0 -0
- {xtr_http_client-4.0.4 → xtr_http_client-4.1.0}/src/xtr_http_client/__init__.py +0 -0
- {xtr_http_client-4.0.4 → xtr_http_client-4.1.0}/src/xtr_http_client/bundle/__init__.py +0 -0
- {xtr_http_client-4.0.4 → xtr_http_client-4.1.0}/src/xtr_http_client/bundle/http_client_config.py +0 -0
- {xtr_http_client-4.0.4 → xtr_http_client-4.1.0}/src/xtr_http_client/bundle/retry_failed_config.py +0 -0
- {xtr_http_client-4.0.4 → xtr_http_client-4.1.0}/src/xtr_http_client/bundle/scoped_client_config.py +0 -0
- {xtr_http_client-4.0.4 → xtr_http_client-4.1.0}/src/xtr_http_client/exception/_http_error_message.py +0 -0
- {xtr_http_client-4.0.4 → xtr_http_client-4.1.0}/src/xtr_http_client/http_client.py +0 -0
- {xtr_http_client-4.0.4 → xtr_http_client-4.1.0}/src/xtr_http_client/http_client_mixin.py +0 -0
- {xtr_http_client-4.0.4 → xtr_http_client-4.1.0}/src/xtr_http_client/httpx_http_client.py +0 -0
- {xtr_http_client-4.0.4 → xtr_http_client-4.1.0}/src/xtr_http_client/internal/__init__.py +0 -0
- {xtr_http_client-4.0.4 → xtr_http_client-4.1.0}/src/xtr_http_client/internal/debug_type.py +0 -0
- {xtr_http_client-4.0.4 → xtr_http_client-4.1.0}/src/xtr_http_client/internal/httpx_body.py +0 -0
- {xtr_http_client-4.0.4 → xtr_http_client-4.1.0}/src/xtr_http_client/internal/httpx_clients.py +0 -0
- {xtr_http_client-4.0.4 → xtr_http_client-4.1.0}/src/xtr_http_client/internal/httpx_errors.py +0 -0
- {xtr_http_client-4.0.4 → xtr_http_client-4.1.0}/src/xtr_http_client/internal/httpx_redirect.py +0 -0
- {xtr_http_client-4.0.4 → xtr_http_client-4.1.0}/src/xtr_http_client/internal/httpx_request.py +0 -0
- {xtr_http_client-4.0.4 → xtr_http_client-4.1.0}/src/xtr_http_client/internal/mock_head.py +0 -0
- {xtr_http_client-4.0.4 → xtr_http_client-4.1.0}/src/xtr_http_client/internal/mock_info.py +0 -0
- {xtr_http_client-4.0.4 → xtr_http_client-4.1.0}/src/xtr_http_client/internal/option_merge.py +0 -0
- {xtr_http_client-4.0.4 → xtr_http_client-4.1.0}/src/xtr_http_client/internal/response_body.py +0 -0
- {xtr_http_client-4.0.4 → xtr_http_client-4.1.0}/src/xtr_http_client/internal/retry_count_response.py +0 -0
- {xtr_http_client-4.0.4 → xtr_http_client-4.1.0}/src/xtr_http_client/internal/retry_delay.py +0 -0
- {xtr_http_client-4.0.4 → xtr_http_client-4.1.0}/src/xtr_http_client/internal/retry_options.py +0 -0
- {xtr_http_client-4.0.4 → xtr_http_client-4.1.0}/src/xtr_http_client/mock_http_client.py +0 -0
- {xtr_http_client-4.0.4 → xtr_http_client-4.1.0}/src/xtr_http_client/py.typed +0 -0
- {xtr_http_client-4.0.4 → xtr_http_client-4.1.0}/src/xtr_http_client/recipe/__init__.py +0 -0
- {xtr_http_client-4.0.4 → xtr_http_client-4.1.0}/src/xtr_http_client/recipe/manifest.toml +0 -0
- {xtr_http_client-4.0.4 → xtr_http_client-4.1.0}/src/xtr_http_client/response/__init__.py +0 -0
- {xtr_http_client-4.0.4 → xtr_http_client-4.1.0}/src/xtr_http_client/response/http_response.py +0 -0
- {xtr_http_client-4.0.4 → xtr_http_client-4.1.0}/src/xtr_http_client/response/httpx_response.py +0 -0
- {xtr_http_client-4.0.4 → xtr_http_client-4.1.0}/src/xtr_http_client/response/json_mock_response.py +0 -0
- {xtr_http_client-4.0.4 → xtr_http_client-4.1.0}/src/xtr_http_client/response/mock_response.py +0 -0
- {xtr_http_client-4.0.4 → xtr_http_client-4.1.0}/src/xtr_http_client/retry/__init__.py +0 -0
- {xtr_http_client-4.0.4 → xtr_http_client-4.1.0}/src/xtr_http_client/retry/generic_retry_strategy.py +0 -0
- {xtr_http_client-4.0.4 → xtr_http_client-4.1.0}/src/xtr_http_client/retry/retry_context.py +0 -0
- {xtr_http_client-4.0.4 → xtr_http_client-4.1.0}/src/xtr_http_client/retry/retry_strategy_interface.py +0 -0
- {xtr_http_client-4.0.4 → xtr_http_client-4.1.0}/src/xtr_http_client/retryable_http_client.py +0 -0
- {xtr_http_client-4.0.4 → xtr_http_client-4.1.0}/src/xtr_http_client/scoping_http_client.py +0 -0
- {xtr_http_client-4.0.4 → xtr_http_client-4.1.0}/src/xtr_http_client/throttling_http_client.py +0 -0
- {xtr_http_client-4.0.4 → xtr_http_client-4.1.0}/src/xtr_http_client/traceable_http_client.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: xtr-http-client
|
|
3
|
-
Version: 4.0
|
|
3
|
+
Version: 4.1.0
|
|
4
4
|
Summary: An async HTTP client behind one interface: typed request options, bodies read on demand, scoped clients, a private-network guard and a mock client for tests.
|
|
5
5
|
Keywords: http,http-client,request,response,mock,asyncio
|
|
6
6
|
Author: Xterr
|
|
@@ -20,18 +20,13 @@ Requires-Dist: typing-extensions>=4.4
|
|
|
20
20
|
Requires-Dist: anyio>=4.0
|
|
21
21
|
Requires-Dist: httpx>=0.27
|
|
22
22
|
Requires-Dist: xtr-http-client-contracts>=4.0,<5
|
|
23
|
-
Requires-Dist: xtr-http-foundation>=4.0,<5
|
|
24
23
|
Requires-Dist: xtr-http-message>=4.0,<5
|
|
25
24
|
Requires-Dist: xtr-http-message-contracts>=4.0,<5
|
|
26
25
|
Requires-Dist: xtr-logging-contracts>=4.0,<5
|
|
27
26
|
Requires-Dist: xtr-service-contracts>=4.0,<5
|
|
28
|
-
Requires-Dist: xtr-dependency-injection>=4.0,<5 ; extra == 'di'
|
|
29
27
|
Requires-Dist: httpx[http2]>=0.27 ; extra == 'http2'
|
|
30
|
-
Requires-Dist: xtr-rate-limiter>=4.0,<5 ; extra == 'throttling'
|
|
31
28
|
Requires-Python: >=3.11
|
|
32
|
-
Provides-Extra: di
|
|
33
29
|
Provides-Extra: http2
|
|
34
|
-
Provides-Extra: throttling
|
|
35
30
|
Description-Content-Type: text/markdown
|
|
36
31
|
|
|
37
32
|
<div align="center">
|
|
@@ -64,12 +59,18 @@ calls out sees one type however much is layered under it.
|
|
|
64
59
|
|
|
65
60
|
```sh
|
|
66
61
|
uv add xtr-http-client
|
|
67
|
-
uv add "xtr-http-client[
|
|
68
|
-
uv add
|
|
69
|
-
uv add
|
|
62
|
+
uv add "xtr-http-client[http2]" # + the framing a request asking for version "2" needs
|
|
63
|
+
uv add xtr-http-client xtr-dependency-injection # + the container, for the HttpClientBundle
|
|
64
|
+
uv add xtr-rate-limiter # beside it: the limiter the throttling client paces by
|
|
65
|
+
uv add xtr-http-foundation # beside it: the networks the private-network guard refuses
|
|
70
66
|
```
|
|
71
67
|
|
|
72
|
-
Requires Python 3.11+.
|
|
68
|
+
Requires Python 3.11+. `http2` is the only extra, and the container is not a dependency: the
|
|
69
|
+
bundle is used when [xtr-dependency-injection](../xtr-dependency-injection) is installed beside the
|
|
70
|
+
package, never as an extra, and every client works without it, built by hand as below. Installed
|
|
71
|
+
beside it too, [xtr-rate-limiter](../xtr-rate-limiter) supplies the limiter `ThrottlingHttpClient`
|
|
72
|
+
paces by, and [xtr-http-foundation](../xtr-http-foundation) the networks
|
|
73
|
+
`NoPrivateNetworkHttpClient` refuses.
|
|
73
74
|
|
|
74
75
|
## Quick start
|
|
75
76
|
|
|
@@ -250,9 +251,9 @@ from xtr_http_client import ThrottlingHttpClient
|
|
|
250
251
|
client = ThrottlingHttpClient(inner, limiter)
|
|
251
252
|
```
|
|
252
253
|
|
|
253
|
-
`limiter` is an [xtr-rate-limiter](../xtr-rate-limiter) `LimiterInterface`, from
|
|
254
|
-
|
|
255
|
-
that booking is due; booking first and waiting after is what makes the pace hold under
|
|
254
|
+
`limiter` is an [xtr-rate-limiter](../xtr-rate-limiter) `LimiterInterface`, from that package
|
|
255
|
+
installed beside this one. A token is booked before each request and the request waits for the
|
|
256
|
+
moment that booking is due; booking first and waiting after is what makes the pace hold under
|
|
256
257
|
concurrency, since requests then leave in the order they were asked for rather than all at
|
|
257
258
|
once. Which requests share a pace is decided by which of them are given the same limiter — one
|
|
258
259
|
per host, one per account, one for the whole application — and a copy made by `with_options()`
|
|
@@ -308,6 +309,10 @@ alike. `subnets` replaces that set and `allow_list` opens one receiver without o
|
|
|
308
309
|
network it sits in; both take addresses or CIDR networks, never host names. What a host
|
|
309
310
|
resolved to is remembered for the client's life, and `reset()` forgets it.
|
|
310
311
|
|
|
312
|
+
xtr-http-foundation is not a dependency: install it beside this package (`uv add
|
|
313
|
+
xtr-http-foundation`) to use the guard. Without it, building `NoPrivateNetworkHttpClient`
|
|
314
|
+
raises `LogicError`, so nothing goes out unchecked.
|
|
315
|
+
|
|
311
316
|
### `MockHttpClient` — answer from a script
|
|
312
317
|
|
|
313
318
|
The same client as any other here — the options it refuses, the defaults it starts from and the
|
|
@@ -370,8 +375,9 @@ Everything adding this package to an application on
|
|
|
370
375
|
[xtr-dependency-injection](../xtr-dependency-injection) takes — and, read backwards, what
|
|
371
376
|
removing it undoes.
|
|
372
377
|
|
|
373
|
-
- **Install** — `uv add
|
|
374
|
-
|
|
378
|
+
- **Install** — `uv add xtr-http-client xtr-dependency-injection`: the bundle needs the container
|
|
379
|
+
installed beside it, never as an extra. Add `http2` to ask for HTTP/2, and install
|
|
380
|
+
[xtr-rate-limiter](../xtr-rate-limiter) beside it to pace a client with one of its limiters.
|
|
375
381
|
- **Recipe** — `uv run xtr-recipes recipes:sync` does the *Activate* step below: it lists
|
|
376
382
|
`HttpClientBundle`. There is no config file, environment or ignore line to write; it prints the
|
|
377
383
|
step to name the services the application calls in `config/http_client.py`, which a recipe
|
|
@@ -392,6 +398,10 @@ removing it undoes.
|
|
|
392
398
|
|
|
393
399
|
## Kernel / bundle
|
|
394
400
|
|
|
401
|
+
With [xtr-dependency-injection](../xtr-dependency-injection) installed beside the package — it is
|
|
402
|
+
not a dependency, and nothing outside `xtr_http_client.bundle` needs it — list the bundle, then
|
|
403
|
+
configure the clients:
|
|
404
|
+
|
|
395
405
|
```python
|
|
396
406
|
# app/bundles.py
|
|
397
407
|
from xtr_http_client.bundle import HttpClientBundle
|
|
@@ -28,12 +28,18 @@ calls out sees one type however much is layered under it.
|
|
|
28
28
|
|
|
29
29
|
```sh
|
|
30
30
|
uv add xtr-http-client
|
|
31
|
-
uv add "xtr-http-client[
|
|
32
|
-
uv add
|
|
33
|
-
uv add
|
|
31
|
+
uv add "xtr-http-client[http2]" # + the framing a request asking for version "2" needs
|
|
32
|
+
uv add xtr-http-client xtr-dependency-injection # + the container, for the HttpClientBundle
|
|
33
|
+
uv add xtr-rate-limiter # beside it: the limiter the throttling client paces by
|
|
34
|
+
uv add xtr-http-foundation # beside it: the networks the private-network guard refuses
|
|
34
35
|
```
|
|
35
36
|
|
|
36
|
-
Requires Python 3.11+.
|
|
37
|
+
Requires Python 3.11+. `http2` is the only extra, and the container is not a dependency: the
|
|
38
|
+
bundle is used when [xtr-dependency-injection](../xtr-dependency-injection) is installed beside the
|
|
39
|
+
package, never as an extra, and every client works without it, built by hand as below. Installed
|
|
40
|
+
beside it too, [xtr-rate-limiter](../xtr-rate-limiter) supplies the limiter `ThrottlingHttpClient`
|
|
41
|
+
paces by, and [xtr-http-foundation](../xtr-http-foundation) the networks
|
|
42
|
+
`NoPrivateNetworkHttpClient` refuses.
|
|
37
43
|
|
|
38
44
|
## Quick start
|
|
39
45
|
|
|
@@ -214,9 +220,9 @@ from xtr_http_client import ThrottlingHttpClient
|
|
|
214
220
|
client = ThrottlingHttpClient(inner, limiter)
|
|
215
221
|
```
|
|
216
222
|
|
|
217
|
-
`limiter` is an [xtr-rate-limiter](../xtr-rate-limiter) `LimiterInterface`, from
|
|
218
|
-
|
|
219
|
-
that booking is due; booking first and waiting after is what makes the pace hold under
|
|
223
|
+
`limiter` is an [xtr-rate-limiter](../xtr-rate-limiter) `LimiterInterface`, from that package
|
|
224
|
+
installed beside this one. A token is booked before each request and the request waits for the
|
|
225
|
+
moment that booking is due; booking first and waiting after is what makes the pace hold under
|
|
220
226
|
concurrency, since requests then leave in the order they were asked for rather than all at
|
|
221
227
|
once. Which requests share a pace is decided by which of them are given the same limiter — one
|
|
222
228
|
per host, one per account, one for the whole application — and a copy made by `with_options()`
|
|
@@ -272,6 +278,10 @@ alike. `subnets` replaces that set and `allow_list` opens one receiver without o
|
|
|
272
278
|
network it sits in; both take addresses or CIDR networks, never host names. What a host
|
|
273
279
|
resolved to is remembered for the client's life, and `reset()` forgets it.
|
|
274
280
|
|
|
281
|
+
xtr-http-foundation is not a dependency: install it beside this package (`uv add
|
|
282
|
+
xtr-http-foundation`) to use the guard. Without it, building `NoPrivateNetworkHttpClient`
|
|
283
|
+
raises `LogicError`, so nothing goes out unchecked.
|
|
284
|
+
|
|
275
285
|
### `MockHttpClient` — answer from a script
|
|
276
286
|
|
|
277
287
|
The same client as any other here — the options it refuses, the defaults it starts from and the
|
|
@@ -334,8 +344,9 @@ Everything adding this package to an application on
|
|
|
334
344
|
[xtr-dependency-injection](../xtr-dependency-injection) takes — and, read backwards, what
|
|
335
345
|
removing it undoes.
|
|
336
346
|
|
|
337
|
-
- **Install** — `uv add
|
|
338
|
-
|
|
347
|
+
- **Install** — `uv add xtr-http-client xtr-dependency-injection`: the bundle needs the container
|
|
348
|
+
installed beside it, never as an extra. Add `http2` to ask for HTTP/2, and install
|
|
349
|
+
[xtr-rate-limiter](../xtr-rate-limiter) beside it to pace a client with one of its limiters.
|
|
339
350
|
- **Recipe** — `uv run xtr-recipes recipes:sync` does the *Activate* step below: it lists
|
|
340
351
|
`HttpClientBundle`. There is no config file, environment or ignore line to write; it prints the
|
|
341
352
|
step to name the services the application calls in `config/http_client.py`, which a recipe
|
|
@@ -356,6 +367,10 @@ removing it undoes.
|
|
|
356
367
|
|
|
357
368
|
## Kernel / bundle
|
|
358
369
|
|
|
370
|
+
With [xtr-dependency-injection](../xtr-dependency-injection) installed beside the package — it is
|
|
371
|
+
not a dependency, and nothing outside `xtr_http_client.bundle` needs it — list the bundle, then
|
|
372
|
+
configure the clients:
|
|
373
|
+
|
|
359
374
|
```python
|
|
360
375
|
# app/bundles.py
|
|
361
376
|
from xtr_http_client.bundle import HttpClientBundle
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "xtr-http-client"
|
|
3
|
-
version = "4.0
|
|
3
|
+
version = "4.1.0"
|
|
4
4
|
description = "An async HTTP client behind one interface: typed request options, bodies read on demand, scoped clients, a private-network guard and a mock client for tests."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.11"
|
|
@@ -30,7 +30,6 @@ dependencies = [
|
|
|
30
30
|
"anyio>=4.0",
|
|
31
31
|
"httpx>=0.27",
|
|
32
32
|
"xtr-http-client-contracts>=4.0,<5",
|
|
33
|
-
"xtr-http-foundation>=4.0,<5",
|
|
34
33
|
"xtr-http-message>=4.0,<5",
|
|
35
34
|
"xtr-http-message-contracts>=4.0,<5",
|
|
36
35
|
"xtr-logging-contracts>=4.0,<5",
|
|
@@ -42,9 +41,7 @@ name = "Xterr"
|
|
|
42
41
|
email = "me@xterr.dev"
|
|
43
42
|
|
|
44
43
|
[project.optional-dependencies]
|
|
45
|
-
di = ["xtr-dependency-injection>=4.0,<5"]
|
|
46
44
|
http2 = ["httpx[http2]>=0.27"]
|
|
47
|
-
throttling = ["xtr-rate-limiter>=4.0,<5"]
|
|
48
45
|
|
|
49
46
|
[project.entry-points."xtr_dependency_injection.bundles"]
|
|
50
47
|
http_client = "xtr_http_client.bundle:HttpClientBundle"
|
|
@@ -63,6 +60,7 @@ dev = [
|
|
|
63
60
|
"ty>=0.0.83",
|
|
64
61
|
"xtr-clock>=4.0,<5",
|
|
65
62
|
"xtr-dependency-injection>=4.0,<5",
|
|
63
|
+
"xtr-http-foundation>=4.0,<5",
|
|
66
64
|
"xtr-rate-limiter>=4.0,<5",
|
|
67
65
|
]
|
|
68
66
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "xtr-http-client"
|
|
3
|
-
version = "4.0
|
|
3
|
+
version = "4.1.0"
|
|
4
4
|
description = "An async HTTP client behind one interface: typed request options, bodies read on demand, scoped clients, a private-network guard and a mock client for tests."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
authors = [
|
|
@@ -28,7 +28,6 @@ dependencies = [
|
|
|
28
28
|
"anyio>=4.0",
|
|
29
29
|
"httpx>=0.27",
|
|
30
30
|
"xtr-http-client-contracts>=4.0,<5",
|
|
31
|
-
"xtr-http-foundation>=4.0,<5",
|
|
32
31
|
"xtr-http-message>=4.0,<5",
|
|
33
32
|
"xtr-http-message-contracts>=4.0,<5",
|
|
34
33
|
"xtr-logging-contracts>=4.0,<5",
|
|
@@ -36,18 +35,10 @@ dependencies = [
|
|
|
36
35
|
]
|
|
37
36
|
|
|
38
37
|
[project.optional-dependencies]
|
|
39
|
-
di = [
|
|
40
|
-
"xtr-dependency-injection>=4.0,<5",
|
|
41
|
-
]
|
|
42
38
|
# The HTTP/2 framing, which only a request asking for version "2" needs.
|
|
43
39
|
http2 = [
|
|
44
40
|
"httpx[http2]>=0.27",
|
|
45
41
|
]
|
|
46
|
-
# The limiter the throttling client paces requests by; nothing else needs it,
|
|
47
|
-
# so the package imports and every other client works without it.
|
|
48
|
-
throttling = [
|
|
49
|
-
"xtr-rate-limiter>=4.0,<5",
|
|
50
|
-
]
|
|
51
42
|
|
|
52
43
|
# Advertises the bundle so debug:bundles can name it when installed but not listed.
|
|
53
44
|
# Advertising activates nothing: an application lists the bundles it wants.
|
|
@@ -69,6 +60,8 @@ dev = [
|
|
|
69
60
|
"ty>=0.0.83",
|
|
70
61
|
"xtr-clock>=4.0,<5",
|
|
71
62
|
"xtr-dependency-injection>=4.0,<5",
|
|
63
|
+
# The networks NoPrivateNetworkHttpClient refuses; optional for an application.
|
|
64
|
+
"xtr-http-foundation>=4.0,<5",
|
|
72
65
|
"xtr-rate-limiter>=4.0,<5",
|
|
73
66
|
]
|
|
74
67
|
|
|
@@ -28,6 +28,9 @@ client, so the calling code sees one type however deep the stack is.
|
|
|
28
28
|
`MockHttpClient`.
|
|
29
29
|
- Options are the contract's `RequestOptions`. An option it does not know is refused, never
|
|
30
30
|
ignored.
|
|
31
|
+
- In an application, with xtr-dependency-injection installed beside the package (it is not a
|
|
32
|
+
dependency): activate `HttpClientBundle` and inject `HttpClientInterface`. Without the
|
|
33
|
+
container, build the clients yourself; only `xtr_http_client.bundle` needs it.
|
|
31
34
|
|
|
32
35
|
## Make a request
|
|
33
36
|
|
|
@@ -109,7 +112,7 @@ client = RetryableHttpClient(inner, GenericRetryStrategy([500, 502, 503]), max_r
|
|
|
109
112
|
```python
|
|
110
113
|
from xtr_http_client import NoPrivateNetworkHttpClient, ThrottlingHttpClient, TraceableHttpClient
|
|
111
114
|
|
|
112
|
-
paced = ThrottlingHttpClient(inner, limiter) # xtr-rate-limiter
|
|
115
|
+
paced = ThrottlingHttpClient(inner, limiter) # a LimiterInterface from xtr-rate-limiter
|
|
113
116
|
traced = TraceableHttpClient(inner)
|
|
114
117
|
guarded = NoPrivateNetworkHttpClient(inner, allow_list=("10.1.2.3",))
|
|
115
118
|
```
|
|
@@ -125,6 +128,8 @@ guarded = NoPrivateNetworkHttpClient(inner, allow_list=("10.1.2.3",))
|
|
|
125
128
|
Refusal is `TransportError`: `Host "<host>" is blocked for "<url>".`. Without `subnets` it
|
|
126
129
|
refuses `IpUtils.PRIVATE_SUBNETS`; `allow_list` opens one receiver. Addresses and CIDR
|
|
127
130
|
networks only — never host names.
|
|
131
|
+
It needs xtr-http-foundation installed beside this package (`uv add xtr-http-foundation`);
|
|
132
|
+
without it, building the guard raises `LogicError`.
|
|
128
133
|
|
|
129
134
|
## Use the transport directly
|
|
130
135
|
|
|
@@ -187,8 +192,9 @@ async def test_it_reads_the_created_order() -> None:
|
|
|
187
192
|
`HttpClientBundle`. There is no config file, environment or ignore line to write; it prints the
|
|
188
193
|
step to name the services the application calls, which a recipe cannot make for you.
|
|
189
194
|
|
|
190
|
-
1. **Install** — `uv add
|
|
191
|
-
|
|
195
|
+
1. **Install** — `uv add xtr-http-client xtr-dependency-injection`: the bundle needs the container
|
|
196
|
+
installed beside it, never as an extra. Add `http2` to ask for HTTP/2, and install
|
|
197
|
+
xtr-rate-limiter beside it to pace a client with one of its limiters.
|
|
192
198
|
2. **Activate** — `HttpClientBundle: {"all": True}` in `BUNDLES` in `<app>/bundles.py`
|
|
193
199
|
(`from xtr_http_client.bundle import HttpClientBundle`).
|
|
194
200
|
3. **Brings along** — the rate limiter bundle when xtr-rate-limiter is installed.
|
|
@@ -124,7 +124,7 @@ def _mock_transport() -> MockHttpClient:
|
|
|
124
124
|
|
|
125
125
|
def _limiters(rate_limiter: str) -> object:
|
|
126
126
|
"""Return the annotation asking for the limiter factory ``rate_limiter`` names."""
|
|
127
|
-
#
|
|
127
|
+
# xtr-rate-limiter is optional: a client is only paced while the rate
|
|
128
128
|
# limiter bundle is active, which proves it installed.
|
|
129
129
|
from xtr_rate_limiter import RateLimiterFactoryInterface # noqa: PLC0415
|
|
130
130
|
|
{xtr_http_client-4.0.4 → xtr_http_client-4.1.0}/src/xtr_http_client/bundle/client_options_config.py
RENAMED
|
@@ -44,7 +44,7 @@ class ClientOptionsConfig:
|
|
|
44
44
|
extra: Additional options a client may ignore.
|
|
45
45
|
retry_failed: Whether and how a failed request is made again.
|
|
46
46
|
rate_limiter: The name of the xtr-rate-limiter limiter every request
|
|
47
|
-
waits its turn from; needs
|
|
47
|
+
waits its turn from; needs xtr-rate-limiter installed.
|
|
48
48
|
"""
|
|
49
49
|
|
|
50
50
|
_NOT_OPTIONS: ClassVar[frozenset[str]] = frozenset({"retry_failed", "rate_limiter"})
|
{xtr_http_client-4.0.4 → xtr_http_client-4.1.0}/src/xtr_http_client/bundle/http_client_bundle.py
RENAMED
|
@@ -81,5 +81,5 @@ def _check_pace(owner: str, rate_limiter: str | None, pacing: bool) -> None:
|
|
|
81
81
|
if rate_limiter is not None and not pacing:
|
|
82
82
|
raise InvalidArgumentError(
|
|
83
83
|
f'{owner} is paced by the "{rate_limiter}" rate limiter, but the rate limiter '
|
|
84
|
-
|
|
84
|
+
"bundle is not active: install xtr-rate-limiter."
|
|
85
85
|
)
|
|
@@ -8,7 +8,8 @@ at the root, :class:`TransportError` for "there is no answer" (with
|
|
|
8
8
|
:class:`ClientError`, :class:`ServerError`), :class:`DecodingError` for an
|
|
9
9
|
answer that is not what it was read as (with :class:`JsonError` for a body that
|
|
10
10
|
is not the JSON it was read as), and :class:`InvalidArgumentError` for a request
|
|
11
|
-
this client refuses to make.
|
|
11
|
+
this client refuses to make. :class:`LogicError` is this package's own: a client
|
|
12
|
+
built in a way that cannot work.
|
|
12
13
|
"""
|
|
13
14
|
|
|
14
15
|
from __future__ import annotations
|
|
@@ -26,6 +27,8 @@ from xtr_http_client_contracts import (
|
|
|
26
27
|
TransportTimeoutError,
|
|
27
28
|
)
|
|
28
29
|
|
|
30
|
+
from .logic_error import LogicError
|
|
31
|
+
|
|
29
32
|
__all__ = [
|
|
30
33
|
"ClientError",
|
|
31
34
|
"DecodingError",
|
|
@@ -33,6 +36,7 @@ __all__ = [
|
|
|
33
36
|
"HttpError",
|
|
34
37
|
"InvalidArgumentError",
|
|
35
38
|
"JsonError",
|
|
39
|
+
"LogicError",
|
|
36
40
|
"RedirectionError",
|
|
37
41
|
"ServerError",
|
|
38
42
|
"TransportError",
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""This library was used in a way that cannot work."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from xtr_http_client_contracts import HttpClientError
|
|
6
|
+
|
|
7
|
+
__all__ = ["LogicError"]
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class LogicError(HttpClientError, RuntimeError):
|
|
11
|
+
"""A client was built in a way that cannot work, whatever it is asked to send.
|
|
12
|
+
|
|
13
|
+
Building the private-network guard without xtr-http-foundation installed,
|
|
14
|
+
which holds the networks it refuses, is a mistake in the application's
|
|
15
|
+
setup rather than in a request, so retrying it changes nothing.
|
|
16
|
+
"""
|
|
@@ -12,11 +12,10 @@ from urllib.parse import urljoin
|
|
|
12
12
|
import anyio
|
|
13
13
|
from typing_extensions import Unpack, override
|
|
14
14
|
from xtr_http_client_contracts import HttpClientInterface
|
|
15
|
-
from xtr_http_foundation import IpUtils
|
|
16
15
|
from xtr_http_message import Request, Stream, Uri
|
|
17
16
|
from xtr_service_contracts import ResetInterface
|
|
18
17
|
|
|
19
|
-
from .exception import InvalidArgumentError, TransportError
|
|
18
|
+
from .exception import InvalidArgumentError, LogicError, TransportError
|
|
20
19
|
from .internal.option_merge import as_options
|
|
21
20
|
|
|
22
21
|
if TYPE_CHECKING:
|
|
@@ -24,6 +23,7 @@ if TYPE_CHECKING:
|
|
|
24
23
|
from typing import Self
|
|
25
24
|
|
|
26
25
|
from xtr_http_client_contracts import HttpResponseInterface, RequestOptions
|
|
26
|
+
from xtr_http_foundation import IpUtils
|
|
27
27
|
from xtr_http_message_contracts import RequestInterface
|
|
28
28
|
|
|
29
29
|
__all__ = ["NoPrivateNetworkHttpClient"]
|
|
@@ -64,9 +64,12 @@ class NoPrivateNetworkHttpClient(HttpClientInterface, ResetInterface):
|
|
|
64
64
|
What a host resolves to is remembered for as long as this client lives,
|
|
65
65
|
so a chain of hops back to the same host asks the resolver once;
|
|
66
66
|
:meth:`reset` forgets it.
|
|
67
|
+
|
|
68
|
+
The networks it refuses come from xtr-http-foundation, which is not a
|
|
69
|
+
dependency of this package: install it beside it to use this client.
|
|
67
70
|
"""
|
|
68
71
|
|
|
69
|
-
__slots__ = ("_allow_list", "_client", "_dns_cache", "_resolver", "_subnets")
|
|
72
|
+
__slots__ = ("_allow_list", "_client", "_dns_cache", "_ip_utils", "_resolver", "_subnets")
|
|
70
73
|
|
|
71
74
|
def __init__(
|
|
72
75
|
self,
|
|
@@ -89,9 +92,14 @@ class NoPrivateNetworkHttpClient(HttpClientInterface, ResetInterface):
|
|
|
89
92
|
have to be resolved, and could point anywhere by then.
|
|
90
93
|
resolver: What every address of a host and port is asked of.
|
|
91
94
|
``None`` asks the platform's own resolver.
|
|
95
|
+
|
|
96
|
+
Raises:
|
|
97
|
+
LogicError: When xtr-http-foundation is not installed, since there
|
|
98
|
+
is then no way to tell which addresses to refuse.
|
|
92
99
|
"""
|
|
100
|
+
self._ip_utils = _ip_utils()
|
|
93
101
|
self._client = client
|
|
94
|
-
self._subnets = _entries(subnets) if subnets is not None else
|
|
102
|
+
self._subnets = _entries(subnets) if subnets is not None else self._ip_utils.PRIVATE_SUBNETS
|
|
95
103
|
self._allow_list = _entries(allow_list)
|
|
96
104
|
self._resolver = resolver if resolver is not None else _getaddrinfo
|
|
97
105
|
self._dns_cache: dict[str, Sequence[str]] = {}
|
|
@@ -258,10 +266,29 @@ class NoPrivateNetworkHttpClient(HttpClientInterface, ResetInterface):
|
|
|
258
266
|
The allow list is read first, so one internal receiver is opened
|
|
259
267
|
without opening the network it sits in.
|
|
260
268
|
"""
|
|
261
|
-
if self._allow_list and
|
|
269
|
+
if self._allow_list and self._ip_utils.check_ip(address, self._allow_list):
|
|
262
270
|
return False
|
|
263
271
|
|
|
264
|
-
return
|
|
272
|
+
return self._ip_utils.check_ip(address, self._subnets)
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def _ip_utils() -> type[IpUtils]:
|
|
276
|
+
"""The address checks, from xtr-http-foundation when it is installed.
|
|
277
|
+
|
|
278
|
+
Raises:
|
|
279
|
+
LogicError: When it is not, so the guard fails where it is built
|
|
280
|
+
rather than letting through a request it could not check.
|
|
281
|
+
"""
|
|
282
|
+
try:
|
|
283
|
+
# Optional package, so imported only when a guard is built (hence PLC0415).
|
|
284
|
+
from xtr_http_foundation import IpUtils # noqa: PLC0415
|
|
285
|
+
except ImportError as error:
|
|
286
|
+
raise LogicError(
|
|
287
|
+
'You cannot use "NoPrivateNetworkHttpClient" if xtr-http-foundation is not '
|
|
288
|
+
'installed. Try running "uv add xtr-http-foundation".'
|
|
289
|
+
) from error
|
|
290
|
+
|
|
291
|
+
return IpUtils
|
|
265
292
|
|
|
266
293
|
|
|
267
294
|
async def _getaddrinfo(host: str, port: int) -> Sequence[str]:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{xtr_http_client-4.0.4 → xtr_http_client-4.1.0}/src/xtr_http_client/bundle/http_client_config.py
RENAMED
|
File without changes
|
{xtr_http_client-4.0.4 → xtr_http_client-4.1.0}/src/xtr_http_client/bundle/retry_failed_config.py
RENAMED
|
File without changes
|
{xtr_http_client-4.0.4 → xtr_http_client-4.1.0}/src/xtr_http_client/bundle/scoped_client_config.py
RENAMED
|
File without changes
|
{xtr_http_client-4.0.4 → xtr_http_client-4.1.0}/src/xtr_http_client/exception/_http_error_message.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{xtr_http_client-4.0.4 → xtr_http_client-4.1.0}/src/xtr_http_client/internal/httpx_clients.py
RENAMED
|
File without changes
|
{xtr_http_client-4.0.4 → xtr_http_client-4.1.0}/src/xtr_http_client/internal/httpx_errors.py
RENAMED
|
File without changes
|
{xtr_http_client-4.0.4 → xtr_http_client-4.1.0}/src/xtr_http_client/internal/httpx_redirect.py
RENAMED
|
File without changes
|
{xtr_http_client-4.0.4 → xtr_http_client-4.1.0}/src/xtr_http_client/internal/httpx_request.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{xtr_http_client-4.0.4 → xtr_http_client-4.1.0}/src/xtr_http_client/internal/option_merge.py
RENAMED
|
File without changes
|
{xtr_http_client-4.0.4 → xtr_http_client-4.1.0}/src/xtr_http_client/internal/response_body.py
RENAMED
|
File without changes
|
{xtr_http_client-4.0.4 → xtr_http_client-4.1.0}/src/xtr_http_client/internal/retry_count_response.py
RENAMED
|
File without changes
|
|
File without changes
|
{xtr_http_client-4.0.4 → xtr_http_client-4.1.0}/src/xtr_http_client/internal/retry_options.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{xtr_http_client-4.0.4 → xtr_http_client-4.1.0}/src/xtr_http_client/response/http_response.py
RENAMED
|
File without changes
|
{xtr_http_client-4.0.4 → xtr_http_client-4.1.0}/src/xtr_http_client/response/httpx_response.py
RENAMED
|
File without changes
|
{xtr_http_client-4.0.4 → xtr_http_client-4.1.0}/src/xtr_http_client/response/json_mock_response.py
RENAMED
|
File without changes
|
{xtr_http_client-4.0.4 → xtr_http_client-4.1.0}/src/xtr_http_client/response/mock_response.py
RENAMED
|
File without changes
|
|
File without changes
|
{xtr_http_client-4.0.4 → xtr_http_client-4.1.0}/src/xtr_http_client/retry/generic_retry_strategy.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{xtr_http_client-4.0.4 → xtr_http_client-4.1.0}/src/xtr_http_client/retryable_http_client.py
RENAMED
|
File without changes
|
|
File without changes
|
{xtr_http_client-4.0.4 → xtr_http_client-4.1.0}/src/xtr_http_client/throttling_http_client.py
RENAMED
|
File without changes
|
{xtr_http_client-4.0.4 → xtr_http_client-4.1.0}/src/xtr_http_client/traceable_http_client.py
RENAMED
|
File without changes
|