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.
Files changed (30) hide show
  1. {httpware-0.16.0 → httpware-0.16.1}/PKG-INFO +2 -2
  2. {httpware-0.16.0 → httpware-0.16.1}/README.md +1 -1
  3. httpware-0.16.1/pyproject.toml +126 -0
  4. httpware-0.16.0/pyproject.toml → httpware-0.16.1/pyproject.toml.orig +12 -11
  5. {httpware-0.16.0 → httpware-0.16.1}/src/httpware/_internal/observability.py +0 -2
  6. {httpware-0.16.0 → httpware-0.16.1}/src/httpware/client.py +2 -1
  7. {httpware-0.16.0 → httpware-0.16.1}/src/httpware/decoders/_resolver.py +1 -1
  8. {httpware-0.16.0 → httpware-0.16.1}/src/httpware/middleware/resilience/budget.py +0 -2
  9. {httpware-0.16.0 → httpware-0.16.1}/src/httpware/middleware/resilience/bulkhead.py +0 -2
  10. {httpware-0.16.0 → httpware-0.16.1}/src/httpware/middleware/resilience/circuit_breaker.py +0 -2
  11. {httpware-0.16.0 → httpware-0.16.1}/src/httpware/middleware/resilience/retry.py +0 -2
  12. {httpware-0.16.0 → httpware-0.16.1}/src/httpware/__init__.py +0 -0
  13. {httpware-0.16.0 → httpware-0.16.1}/src/httpware/_internal/__init__.py +0 -0
  14. {httpware-0.16.0 → httpware-0.16.1}/src/httpware/_internal/body_cap.py +0 -0
  15. {httpware-0.16.0 → httpware-0.16.1}/src/httpware/_internal/exception_mapping.py +0 -0
  16. {httpware-0.16.0 → httpware-0.16.1}/src/httpware/_internal/import_checker.py +0 -0
  17. {httpware-0.16.0 → httpware-0.16.1}/src/httpware/_internal/redaction.py +0 -0
  18. {httpware-0.16.0 → httpware-0.16.1}/src/httpware/_internal/status.py +0 -0
  19. {httpware-0.16.0 → httpware-0.16.1}/src/httpware/decoders/__init__.py +0 -0
  20. {httpware-0.16.0 → httpware-0.16.1}/src/httpware/decoders/_caching.py +0 -0
  21. {httpware-0.16.0 → httpware-0.16.1}/src/httpware/decoders/msgspec.py +0 -0
  22. {httpware-0.16.0 → httpware-0.16.1}/src/httpware/decoders/pydantic.py +0 -0
  23. {httpware-0.16.0 → httpware-0.16.1}/src/httpware/errors.py +0 -0
  24. {httpware-0.16.0 → httpware-0.16.1}/src/httpware/middleware/__init__.py +0 -0
  25. {httpware-0.16.0 → httpware-0.16.1}/src/httpware/middleware/chain.py +0 -0
  26. {httpware-0.16.0 → httpware-0.16.1}/src/httpware/middleware/resilience/__init__.py +0 -0
  27. {httpware-0.16.0 → httpware-0.16.1}/src/httpware/middleware/resilience/_backoff.py +0 -0
  28. {httpware-0.16.0 → httpware-0.16.1}/src/httpware/middleware/resilience/_event_loop_guard.py +0 -0
  29. {httpware-0.16.0 → httpware-0.16.1}/src/httpware/middleware/resilience/timeout.py +0 -0
  30. {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.0
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.0"
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", # allow missing docstrings
87
- "S101", # allow asserts
88
- "TCH", # ignore flake8-type-checking
89
- "FBT", # allow boolean args
90
- "D203", # "one-blank-line-before-class" conflicting with D211
91
- "D213", # "multi-line-summary-second-line" conflicting with D212
92
- "COM812", # flake8-commas "Trailing comma missing"
93
- "ISC001", # flake8-implicit-str-concat
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 = "--cov=. --cov-report term-missing --cov-fail-under=100"
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) see architecture/client.md for the contract.
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. See architecture/decoders.md for the full Seam B contract.
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