httpware 0.16.0__tar.gz → 0.16.1__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.
- {httpware-0.16.0 → httpware-0.16.1}/PKG-INFO +2 -2
- {httpware-0.16.0 → httpware-0.16.1}/README.md +1 -1
- httpware-0.16.1/pyproject.toml +126 -0
- httpware-0.16.0/pyproject.toml → httpware-0.16.1/pyproject.toml.orig +12 -11
- {httpware-0.16.0 → httpware-0.16.1}/src/httpware/_internal/observability.py +0 -2
- {httpware-0.16.0 → httpware-0.16.1}/src/httpware/client.py +2 -1
- {httpware-0.16.0 → httpware-0.16.1}/src/httpware/decoders/_resolver.py +1 -1
- {httpware-0.16.0 → httpware-0.16.1}/src/httpware/middleware/resilience/budget.py +0 -2
- {httpware-0.16.0 → httpware-0.16.1}/src/httpware/middleware/resilience/bulkhead.py +0 -2
- {httpware-0.16.0 → httpware-0.16.1}/src/httpware/middleware/resilience/circuit_breaker.py +0 -2
- {httpware-0.16.0 → httpware-0.16.1}/src/httpware/middleware/resilience/retry.py +0 -2
- {httpware-0.16.0 → httpware-0.16.1}/src/httpware/__init__.py +0 -0
- {httpware-0.16.0 → httpware-0.16.1}/src/httpware/_internal/__init__.py +0 -0
- {httpware-0.16.0 → httpware-0.16.1}/src/httpware/_internal/body_cap.py +0 -0
- {httpware-0.16.0 → httpware-0.16.1}/src/httpware/_internal/exception_mapping.py +0 -0
- {httpware-0.16.0 → httpware-0.16.1}/src/httpware/_internal/import_checker.py +0 -0
- {httpware-0.16.0 → httpware-0.16.1}/src/httpware/_internal/redaction.py +0 -0
- {httpware-0.16.0 → httpware-0.16.1}/src/httpware/_internal/status.py +0 -0
- {httpware-0.16.0 → httpware-0.16.1}/src/httpware/decoders/__init__.py +0 -0
- {httpware-0.16.0 → httpware-0.16.1}/src/httpware/decoders/_caching.py +0 -0
- {httpware-0.16.0 → httpware-0.16.1}/src/httpware/decoders/msgspec.py +0 -0
- {httpware-0.16.0 → httpware-0.16.1}/src/httpware/decoders/pydantic.py +0 -0
- {httpware-0.16.0 → httpware-0.16.1}/src/httpware/errors.py +0 -0
- {httpware-0.16.0 → httpware-0.16.1}/src/httpware/middleware/__init__.py +0 -0
- {httpware-0.16.0 → httpware-0.16.1}/src/httpware/middleware/chain.py +0 -0
- {httpware-0.16.0 → httpware-0.16.1}/src/httpware/middleware/resilience/__init__.py +0 -0
- {httpware-0.16.0 → httpware-0.16.1}/src/httpware/middleware/resilience/_backoff.py +0 -0
- {httpware-0.16.0 → httpware-0.16.1}/src/httpware/middleware/resilience/_event_loop_guard.py +0 -0
- {httpware-0.16.0 → httpware-0.16.1}/src/httpware/middleware/resilience/timeout.py +0 -0
- {httpware-0.16.0 → httpware-0.16.1}/src/httpware/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: httpware
|
|
3
|
-
Version: 0.16.
|
|
3
|
+
Version: 0.16.1
|
|
4
4
|
Summary: Python HTTP client framework with sync & async clients and built-in resilience
|
|
5
5
|
Keywords: http-client,httpx,async,asyncio,resilience,retry,circuit-breaker,bulkhead,middleware,pydantic,msgspec,python
|
|
6
6
|
Author: Artur Shiriev
|
|
@@ -120,7 +120,7 @@ Full guides live at **[httpware.modern-python.org](https://httpware.modern-pytho
|
|
|
120
120
|
- **[Testing](https://httpware.modern-python.org/testing/)** — `httpx2.MockTransport` injection.
|
|
121
121
|
- **[Recipes](https://httpware.modern-python.org/recipes/modern-di/)** — DI wiring, phase-decorator patterns, link-header pagination.
|
|
122
122
|
|
|
123
|
-
## 🗒️ [Release notes](https://github.com/modern-python/httpware/releases) · 📦 [PyPI](https://pypi.org/project/httpware) · 📝 [License](LICENSE)
|
|
123
|
+
## 🗒️ [Release notes](https://github.com/modern-python/httpware/releases) · 📦 [PyPI](https://pypi.org/project/httpware) · 📝 [License](https://github.com/modern-python/httpware/blob/main/LICENSE)
|
|
124
124
|
|
|
125
125
|
## Part of `modern-python`
|
|
126
126
|
|
|
@@ -84,7 +84,7 @@ Full guides live at **[httpware.modern-python.org](https://httpware.modern-pytho
|
|
|
84
84
|
- **[Testing](https://httpware.modern-python.org/testing/)** — `httpx2.MockTransport` injection.
|
|
85
85
|
- **[Recipes](https://httpware.modern-python.org/recipes/modern-di/)** — DI wiring, phase-decorator patterns, link-header pagination.
|
|
86
86
|
|
|
87
|
-
## 🗒️ [Release notes](https://github.com/modern-python/httpware/releases) · 📦 [PyPI](https://pypi.org/project/httpware) · 📝 [License](LICENSE)
|
|
87
|
+
## 🗒️ [Release notes](https://github.com/modern-python/httpware/releases) · 📦 [PyPI](https://pypi.org/project/httpware) · 📝 [License](https://github.com/modern-python/httpware/blob/main/LICENSE)
|
|
88
88
|
|
|
89
89
|
## Part of `modern-python`
|
|
90
90
|
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "httpware"
|
|
3
|
+
description = "Python HTTP client framework with sync & async clients and built-in resilience"
|
|
4
|
+
requires-python = ">=3.11,<4"
|
|
5
|
+
license = "MIT"
|
|
6
|
+
readme = "README.md"
|
|
7
|
+
keywords = [
|
|
8
|
+
"http-client",
|
|
9
|
+
"httpx",
|
|
10
|
+
"async",
|
|
11
|
+
"asyncio",
|
|
12
|
+
"resilience",
|
|
13
|
+
"retry",
|
|
14
|
+
"circuit-breaker",
|
|
15
|
+
"bulkhead",
|
|
16
|
+
"middleware",
|
|
17
|
+
"pydantic",
|
|
18
|
+
"msgspec",
|
|
19
|
+
"python",
|
|
20
|
+
]
|
|
21
|
+
classifiers = [
|
|
22
|
+
"Development Status :: 4 - Beta",
|
|
23
|
+
"Intended Audience :: Developers",
|
|
24
|
+
"Programming Language :: Python :: 3.11",
|
|
25
|
+
"Programming Language :: Python :: 3.12",
|
|
26
|
+
"Programming Language :: Python :: 3.13",
|
|
27
|
+
"Programming Language :: Python :: 3.14",
|
|
28
|
+
"Programming Language :: Python :: Free Threading :: 2 - Beta",
|
|
29
|
+
"Typing :: Typed",
|
|
30
|
+
"Topic :: Software Development :: Libraries",
|
|
31
|
+
"Topic :: Internet :: WWW/HTTP",
|
|
32
|
+
"Framework :: AsyncIO",
|
|
33
|
+
]
|
|
34
|
+
version = "0.16.1"
|
|
35
|
+
dependencies = ["httpx2>=2.0.0,<3.0"]
|
|
36
|
+
|
|
37
|
+
[[project.authors]]
|
|
38
|
+
name = "Artur Shiriev"
|
|
39
|
+
email = "me@shiriev.ru"
|
|
40
|
+
|
|
41
|
+
[project.optional-dependencies]
|
|
42
|
+
pydantic = ["pydantic>=2.0,<3.0"]
|
|
43
|
+
msgspec = ["msgspec>=0.18"]
|
|
44
|
+
otel = ["opentelemetry-api>=1.20"]
|
|
45
|
+
all = ["httpware[pydantic,msgspec,otel]"]
|
|
46
|
+
|
|
47
|
+
[project.urls]
|
|
48
|
+
Homepage = "https://httpware.modern-python.org"
|
|
49
|
+
Documentation = "https://httpware.modern-python.org"
|
|
50
|
+
Repository = "https://github.com/modern-python/httpware"
|
|
51
|
+
Issues = "https://github.com/modern-python/httpware/issues"
|
|
52
|
+
Changelog = "https://github.com/modern-python/httpware/releases"
|
|
53
|
+
|
|
54
|
+
[build-system]
|
|
55
|
+
requires = ["uv_build>=0.11,<1.0"]
|
|
56
|
+
build-backend = "uv_build"
|
|
57
|
+
|
|
58
|
+
[tool.uv.build-backend]
|
|
59
|
+
module-name = "httpware"
|
|
60
|
+
module-root = "src"
|
|
61
|
+
|
|
62
|
+
[tool.ruff]
|
|
63
|
+
fix = true
|
|
64
|
+
unsafe-fixes = true
|
|
65
|
+
line-length = 120
|
|
66
|
+
|
|
67
|
+
[tool.ruff.lint]
|
|
68
|
+
select = ["ALL"]
|
|
69
|
+
ignore = [
|
|
70
|
+
"D1",
|
|
71
|
+
"D203",
|
|
72
|
+
"D213",
|
|
73
|
+
"COM812",
|
|
74
|
+
"ISC001",
|
|
75
|
+
"CPY001",
|
|
76
|
+
"FBT",
|
|
77
|
+
"TCH",
|
|
78
|
+
]
|
|
79
|
+
|
|
80
|
+
[tool.ruff.lint.isort]
|
|
81
|
+
lines-after-imports = 2
|
|
82
|
+
no-lines-before = [
|
|
83
|
+
"standard-library",
|
|
84
|
+
"local-folder",
|
|
85
|
+
]
|
|
86
|
+
|
|
87
|
+
[tool.ruff.lint.per-file-ignores]
|
|
88
|
+
"src/httpware/client.py" = [
|
|
89
|
+
"ASYNC109",
|
|
90
|
+
"ANN401",
|
|
91
|
+
]
|
|
92
|
+
"tests/**" = ["S101"]
|
|
93
|
+
|
|
94
|
+
[tool.pytest.ini_options]
|
|
95
|
+
addopts = ""
|
|
96
|
+
asyncio_mode = "auto"
|
|
97
|
+
pythonpath = ["src"]
|
|
98
|
+
asyncio_default_fixture_loop_scope = "function"
|
|
99
|
+
markers = ["stress: concurrency stress test; proves thread-safety under free-threaded CPython (3.14t). Runs under the GIL too, so it counts toward coverage."]
|
|
100
|
+
|
|
101
|
+
[tool.coverage.run]
|
|
102
|
+
concurrency = ["thread"]
|
|
103
|
+
omit = ["benchmarks/*"]
|
|
104
|
+
|
|
105
|
+
[tool.coverage.report]
|
|
106
|
+
exclude_also = [
|
|
107
|
+
"if typing.TYPE_CHECKING:",
|
|
108
|
+
'pytest\.fail\(',
|
|
109
|
+
]
|
|
110
|
+
fail_under = 100
|
|
111
|
+
|
|
112
|
+
[dependency-groups]
|
|
113
|
+
dev = [
|
|
114
|
+
"pytest",
|
|
115
|
+
"pytest-cov",
|
|
116
|
+
"pytest-asyncio",
|
|
117
|
+
"pytest-repeat",
|
|
118
|
+
"pytest-benchmark",
|
|
119
|
+
"hypothesis",
|
|
120
|
+
]
|
|
121
|
+
lint = [
|
|
122
|
+
"ruff",
|
|
123
|
+
"ty",
|
|
124
|
+
"eof-fixer",
|
|
125
|
+
"typing-extensions",
|
|
126
|
+
]
|
|
@@ -32,7 +32,7 @@ classifiers = [
|
|
|
32
32
|
"Topic :: Internet :: WWW/HTTP",
|
|
33
33
|
"Framework :: AsyncIO",
|
|
34
34
|
]
|
|
35
|
-
version = "0.16.
|
|
35
|
+
version = "0.16.1"
|
|
36
36
|
dependencies = [
|
|
37
37
|
"httpx2>=2.0.0,<3.0",
|
|
38
38
|
]
|
|
@@ -78,28 +78,28 @@ lint = [
|
|
|
78
78
|
fix = true
|
|
79
79
|
unsafe-fixes = true
|
|
80
80
|
line-length = 120
|
|
81
|
-
target-version = "py311"
|
|
82
81
|
|
|
83
82
|
[tool.ruff.lint]
|
|
84
83
|
select = ["ALL"]
|
|
85
84
|
ignore = [
|
|
86
|
-
"D1", #
|
|
87
|
-
"
|
|
88
|
-
"
|
|
89
|
-
"
|
|
90
|
-
"
|
|
91
|
-
"
|
|
92
|
-
"
|
|
93
|
-
"
|
|
85
|
+
"D1", # docstrings are not forced; a docstring exists when it says something
|
|
86
|
+
"D203", # conflicts with D211
|
|
87
|
+
"D213", # conflicts with D212
|
|
88
|
+
"COM812", # conflicts with the formatter
|
|
89
|
+
"ISC001", # conflicts with the formatter
|
|
90
|
+
"CPY001", # no per-file copyright header
|
|
91
|
+
"FBT", # boolean positional arguments are fine
|
|
92
|
+
"TCH", # imports stay real; TYPE_CHECKING-only imports break runtime introspection
|
|
94
93
|
]
|
|
95
94
|
isort.lines-after-imports = 2
|
|
96
95
|
isort.no-lines-before = ["standard-library", "local-folder"]
|
|
97
96
|
|
|
98
97
|
[tool.ruff.lint.per-file-ignores]
|
|
99
98
|
"src/httpware/client.py" = ["ASYNC109", "ANN401"]
|
|
99
|
+
"tests/**" = ["S101"]
|
|
100
100
|
|
|
101
101
|
[tool.pytest.ini_options]
|
|
102
|
-
addopts = "
|
|
102
|
+
addopts = ""
|
|
103
103
|
asyncio_mode = "auto"
|
|
104
104
|
pythonpath = ["src"]
|
|
105
105
|
asyncio_default_fixture_loop_scope = "function"
|
|
@@ -111,3 +111,4 @@ markers = [
|
|
|
111
111
|
run.concurrency = ["thread"]
|
|
112
112
|
run.omit = ["benchmarks/*"]
|
|
113
113
|
report.exclude_also = ["if typing.TYPE_CHECKING:", 'pytest\.fail\(']
|
|
114
|
+
report.fail_under = 100
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
"""Observability emission helper — structured logging + opt-in OpenTelemetry span events.
|
|
2
2
|
|
|
3
|
-
See architecture/resilience.md (Observability section) for the contract.
|
|
4
|
-
|
|
5
3
|
Logger names (``httpware.retry``, ``httpware.bulkhead``, ``httpware.circuit_breaker``,
|
|
6
4
|
``httpware.timeout``) and event names (``retry.giving_up``, ``bulkhead.rejected``,
|
|
7
5
|
``circuit.opened``, ``timeout.exceeded``, etc.) are the public observability
|
|
@@ -1035,7 +1035,8 @@ class AsyncClient:
|
|
|
1035
1035
|
is closed when the context exits.
|
|
1036
1036
|
|
|
1037
1037
|
Bypasses the middleware chain (no AsyncRetry, no AsyncBulkhead, no user-installed
|
|
1038
|
-
middleware)
|
|
1038
|
+
middleware): the protocol is typed on a fully-buffered response, and reading it
|
|
1039
|
+
would consume the stream.
|
|
1039
1040
|
|
|
1040
1041
|
Auto-raises StatusError subclasses on 4xx/5xx (NotFoundError,
|
|
1041
1042
|
ServiceUnavailableError, etc.) — consistent with client.get()/post()/etc.
|
|
@@ -8,7 +8,7 @@ client calls `resolve` before `_dispatch` — so a missing decoder fails before
|
|
|
8
8
|
the request goes out — and `_BoundDecoder.decode` after the response arrives.
|
|
9
9
|
|
|
10
10
|
Both clients hold one `_DecoderResolver`; it is fully synchronous, so there is
|
|
11
|
-
no sync/async split.
|
|
11
|
+
no sync/async split.
|
|
12
12
|
"""
|
|
13
13
|
|
|
14
14
|
from typing import Generic, TypeVar
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
"""Finagle-style token-bucket retry budget.
|
|
2
2
|
|
|
3
|
-
See architecture/resilience.md (Retry + RetryBudget section) for the contract.
|
|
4
|
-
|
|
5
3
|
Thread-safe and asyncio-safe: all mutations go through a threading.Lock,
|
|
6
4
|
which ensures no torn state across concurrent accesses. When a RetryBudget
|
|
7
5
|
is shared between a sync Client (pool thread) and an AsyncClient (event-loop
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
"""AsyncBulkhead middleware — concurrency limiter via asyncio.Semaphore.
|
|
2
2
|
|
|
3
|
-
See architecture/resilience.md (Bulkhead section) for the contract.
|
|
4
|
-
|
|
5
3
|
The middleware owns an asyncio.Semaphore(max_concurrent). On each request,
|
|
6
4
|
it acquires a slot (bounded by acquire_timeout via asyncio.timeout) and
|
|
7
5
|
releases the slot in a try/finally so success, exceptions, and cancellation
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
"""CircuitBreaker + AsyncCircuitBreaker — consecutive-failure and failure-rate circuit breakers.
|
|
2
2
|
|
|
3
|
-
See architecture/resilience.md (CircuitBreaker + AsyncTimeout section) for the contract.
|
|
4
|
-
|
|
5
3
|
A counted failure is a NetworkError, an httpware TimeoutError, or a StatusError whose
|
|
6
4
|
status_code is in the effective failure set (default: all 5xx). 4xx — including 429 —
|
|
7
5
|
count as successes: 429 means healthy-but-throttling, and tripping on it amplifies
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
"""AsyncRetry + Retry middleware — automatic retry of transient failures with budget control.
|
|
2
2
|
|
|
3
|
-
See architecture/resilience.md (Retry + RetryBudget section) for the full contract.
|
|
4
|
-
|
|
5
3
|
Status-code retry: the client terminal raises StatusError subclasses on 4xx/5xx,
|
|
6
4
|
so the retry middleware catches StatusError and inspects exc.response.status_code. The
|
|
7
5
|
original StatusError subclass is re-raised unwrapped on exhaustion, with a PEP 678 note
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|