quadkit-http 0.0.42__tar.gz → 0.0.44__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 (86) hide show
  1. quadkit_http-0.0.44/.gitignore +24 -0
  2. {quadkit_http-0.0.42 → quadkit_http-0.0.44}/PKG-INFO +2 -2
  3. {quadkit_http-0.0.42 → quadkit_http-0.0.44}/README.md +1 -1
  4. {quadkit_http-0.0.42 → quadkit_http-0.0.44}/pyproject.toml +1 -1
  5. {quadkit_http-0.0.42 → quadkit_http-0.0.44}/src/quadkit/http/client/http_client.py +1 -1
  6. {quadkit_http-0.0.42 → quadkit_http-0.0.44}/src/quadkit/http/retry/policy.py +2 -2
  7. {quadkit_http-0.0.42 → quadkit_http-0.0.44}/tests/unit/test_http_client_verbs.py +2 -2
  8. quadkit_http-0.0.42/.gitignore +0 -214
  9. quadkit_http-0.0.42/docs/ARCHITECTURE.md +0 -250
  10. quadkit_http-0.0.42/docs/CONFIGURATION.md +0 -50
  11. quadkit_http-0.0.42/docs/GUIDE.md +0 -125
  12. quadkit_http-0.0.42/docs/HOWTOS.md +0 -155
  13. quadkit_http-0.0.42/docs/QUICKSTART.md +0 -64
  14. quadkit_http-0.0.42/docs/TROUBLESHOOTING.md +0 -143
  15. quadkit_http-0.0.42/tests/unit/test_http_module_client.py +0 -313
  16. {quadkit_http-0.0.42 → quadkit_http-0.0.44}/LICENSE +0 -0
  17. {quadkit_http-0.0.42 → quadkit_http-0.0.44}/NOTICE +0 -0
  18. {quadkit_http-0.0.42 → quadkit_http-0.0.44}/example.yaml +0 -0
  19. {quadkit_http-0.0.42 → quadkit_http-0.0.44}/src/quadkit/http/__init__.py +0 -0
  20. {quadkit_http-0.0.42 → quadkit_http-0.0.44}/src/quadkit/http/cli/__init__.py +0 -0
  21. {quadkit_http-0.0.42 → quadkit_http-0.0.44}/src/quadkit/http/cli/contributor.py +0 -0
  22. {quadkit_http-0.0.42 → quadkit_http-0.0.44}/src/quadkit/http/cli/generators/__init__.py +0 -0
  23. {quadkit_http-0.0.42 → quadkit_http-0.0.44}/src/quadkit/http/cli/generators/api_client.py +0 -0
  24. {quadkit_http-0.0.42 → quadkit_http-0.0.44}/src/quadkit/http/cli/templates/api_client.py.jinja2 +0 -0
  25. {quadkit_http-0.0.42 → quadkit_http-0.0.44}/src/quadkit/http/client/__init__.py +0 -0
  26. {quadkit_http-0.0.42 → quadkit_http-0.0.44}/src/quadkit/http/client/_streaming.py +0 -0
  27. {quadkit_http-0.0.42 → quadkit_http-0.0.44}/src/quadkit/http/client/_verbs.py +0 -0
  28. {quadkit_http-0.0.42 → quadkit_http-0.0.44}/src/quadkit/http/client/base_url_client.py +0 -0
  29. {quadkit_http-0.0.42 → quadkit_http-0.0.44}/src/quadkit/http/config.py +0 -0
  30. {quadkit_http-0.0.42 → quadkit_http-0.0.44}/src/quadkit/http/constants.py +0 -0
  31. {quadkit_http-0.0.42 → quadkit_http-0.0.44}/src/quadkit/http/decorators.py +0 -0
  32. {quadkit_http-0.0.42 → quadkit_http-0.0.44}/src/quadkit/http/di/__init__.py +0 -0
  33. {quadkit_http-0.0.42 → quadkit_http-0.0.44}/src/quadkit/http/di/provider.py +0 -0
  34. {quadkit_http-0.0.42 → quadkit_http-0.0.44}/src/quadkit/http/events.py +0 -0
  35. {quadkit_http-0.0.42 → quadkit_http-0.0.44}/src/quadkit/http/exceptions.py +0 -0
  36. {quadkit_http-0.0.42 → quadkit_http-0.0.44}/src/quadkit/http/hooks.py +0 -0
  37. {quadkit_http-0.0.42 → quadkit_http-0.0.44}/src/quadkit/http/lib/__init__.py +0 -0
  38. {quadkit_http-0.0.42 → quadkit_http-0.0.44}/src/quadkit/http/lib/format.py +0 -0
  39. {quadkit_http-0.0.42 → quadkit_http-0.0.44}/src/quadkit/http/lib/headers.py +0 -0
  40. {quadkit_http-0.0.42 → quadkit_http-0.0.44}/src/quadkit/http/lib/url.py +0 -0
  41. {quadkit_http-0.0.42 → quadkit_http-0.0.44}/src/quadkit/http/module.py +0 -0
  42. {quadkit_http-0.0.42 → quadkit_http-0.0.44}/src/quadkit/http/pool/__init__.py +0 -0
  43. {quadkit_http-0.0.42 → quadkit_http-0.0.44}/src/quadkit/http/pool/connection_pool.py +0 -0
  44. {quadkit_http-0.0.42 → quadkit_http-0.0.44}/src/quadkit/http/protocols.py +0 -0
  45. {quadkit_http-0.0.42 → quadkit_http-0.0.44}/src/quadkit/http/py.typed +0 -0
  46. {quadkit_http-0.0.42 → quadkit_http-0.0.44}/src/quadkit/http/retry/__init__.py +0 -0
  47. {quadkit_http-0.0.42 → quadkit_http-0.0.44}/src/quadkit/http/types.py +0 -0
  48. {quadkit_http-0.0.42 → quadkit_http-0.0.44}/src/quadkit/http/validation/__init__.py +0 -0
  49. {quadkit_http-0.0.42 → quadkit_http-0.0.44}/src/quadkit/http/validation/primitives.py +0 -0
  50. {quadkit_http-0.0.42 → quadkit_http-0.0.44}/src/quadkit/http/validation/url.py +0 -0
  51. {quadkit_http-0.0.42 → quadkit_http-0.0.44}/src/quadkit/http/websocket.py +0 -0
  52. {quadkit_http-0.0.42 → quadkit_http-0.0.44}/src/quadkit/py.typed +0 -0
  53. {quadkit_http-0.0.42 → quadkit_http-0.0.44}/tests/conftest.py +0 -0
  54. {quadkit_http-0.0.42 → quadkit_http-0.0.44}/tests/integration/__init__.py +0 -0
  55. {quadkit_http-0.0.42 → quadkit_http-0.0.44}/tests/integration/test_http_lifecycle.py +0 -0
  56. {quadkit_http-0.0.42 → quadkit_http-0.0.44}/tests/unit/cli/test_api_client_generator.py +0 -0
  57. {quadkit_http-0.0.42 → quadkit_http-0.0.44}/tests/unit/cli/test_generators_contract.py +0 -0
  58. {quadkit_http-0.0.42 → quadkit_http-0.0.44}/tests/unit/http_module_test_support.py +0 -0
  59. {quadkit_http-0.0.42 → quadkit_http-0.0.44}/tests/unit/test_base_url_client_streaming_and_events.py +0 -0
  60. {quadkit_http-0.0.42 → quadkit_http-0.0.44}/tests/unit/test_exceptions.py +0 -0
  61. {quadkit_http-0.0.42 → quadkit_http-0.0.44}/tests/unit/test_http_client.py +0 -0
  62. {quadkit_http-0.0.42 → quadkit_http-0.0.44}/tests/unit/test_http_constants.py +0 -0
  63. {quadkit_http-0.0.42 → quadkit_http-0.0.44}/tests/unit/test_http_exceptions.py +0 -0
  64. {quadkit_http-0.0.42 → quadkit_http-0.0.44}/tests/unit/test_http_module_config.py +0 -0
  65. {quadkit_http-0.0.42 → quadkit_http-0.0.44}/tests/unit/test_http_module_interceptors.py +0 -0
  66. {quadkit_http-0.0.42 → quadkit_http-0.0.44}/tests/unit/test_http_module_validation.py +0 -0
  67. {quadkit_http-0.0.42 → quadkit_http-0.0.44}/tests/unit/test_http_providers.py +0 -0
  68. {quadkit_http-0.0.42 → quadkit_http-0.0.44}/tests/unit/test_http_streaming.py +0 -0
  69. {quadkit_http-0.0.42 → quadkit_http-0.0.44}/tests/unit/test_http_types.py +0 -0
  70. {quadkit_http-0.0.42 → quadkit_http-0.0.44}/tests/unit/test_http_types_extended.py +0 -0
  71. {quadkit_http-0.0.42 → quadkit_http-0.0.44}/tests/unit/test_http_url_safety.py +0 -0
  72. {quadkit_http-0.0.42 → quadkit_http-0.0.44}/tests/unit/test_lib_format.py +0 -0
  73. {quadkit_http-0.0.42 → quadkit_http-0.0.44}/tests/unit/test_lib_headers.py +0 -0
  74. {quadkit_http-0.0.42 → quadkit_http-0.0.44}/tests/unit/test_m17_m18_http.py +0 -0
  75. {quadkit_http-0.0.42 → quadkit_http-0.0.44}/tests/unit/test_package_hooks_p2.py +0 -0
  76. {quadkit_http-0.0.42 → quadkit_http-0.0.44}/tests/unit/test_package_structure_p1.py +0 -0
  77. {quadkit_http-0.0.42 → quadkit_http-0.0.44}/tests/unit/test_protocols.py +0 -0
  78. {quadkit_http-0.0.42 → quadkit_http-0.0.44}/tests/unit/test_redirect_ssrf.py +0 -0
  79. {quadkit_http-0.0.42 → quadkit_http-0.0.44}/tests/unit/test_retry_policy.py +0 -0
  80. {quadkit_http-0.0.42 → quadkit_http-0.0.44}/tests/unit/test_retry_policy_canonical.py +0 -0
  81. {quadkit_http-0.0.42 → quadkit_http-0.0.44}/tests/unit/test_round24_hardening.py +0 -0
  82. {quadkit_http-0.0.42 → quadkit_http-0.0.44}/tests/unit/test_url_utils.py +0 -0
  83. {quadkit_http-0.0.42 → quadkit_http-0.0.44}/tests/unit/test_utils.py +0 -0
  84. {quadkit_http-0.0.42 → quadkit_http-0.0.44}/tests/unit/test_validation.py +0 -0
  85. {quadkit_http-0.0.42 → quadkit_http-0.0.44}/tests/unit/test_validation_primitives.py +0 -0
  86. {quadkit_http-0.0.42 → quadkit_http-0.0.44}/tests/unit/test_validation_url.py +0 -0
@@ -0,0 +1,24 @@
1
+ # Generated public-repository ignore rules.
2
+ # Private monorepo paths are intentionally not referenced here.
3
+
4
+ __pycache__/
5
+ *.py[cod]
6
+ *.so
7
+ *.egg-info/
8
+ .venv/
9
+ venv/
10
+ .env
11
+ .env.*
12
+ !.env.example
13
+ .pytest_cache/
14
+ .ruff_cache/
15
+ .mypy_cache/
16
+ .coverage
17
+ coverage.xml
18
+ htmlcov/
19
+ build/
20
+ dist/
21
+ *.log
22
+ .DS_Store
23
+ .idea/
24
+ .vscode/
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: quadkit-http
3
- Version: 0.0.42
3
+ Version: 0.0.44
4
4
  Summary: Outbound HTTP client for the Quadkit Framework
5
5
  Project-URL: Homepage, https://dbtinoy-.github.io/quadkit
6
6
  Project-URL: Repository, https://github.com/dbtinoy-/quadkit
@@ -57,7 +57,7 @@ DI integration.
57
57
 
58
58
  This package focuses on making **outbound** HTTP requests — for inbound web servers,
59
59
  use `quadkit-web`. Resilience patterns (retry, circuit breaker) are layered in
60
- through `quadkit-resilience`, not built in by default.
60
+ through `the optional provider`, not built in by default.
61
61
 
62
62
 
63
63
  > Full documentation: [docs.quadkit.dev](https://docs.quadkit.dev)
@@ -13,7 +13,7 @@ DI integration.
13
13
 
14
14
  This package focuses on making **outbound** HTTP requests — for inbound web servers,
15
15
  use `quadkit-web`. Resilience patterns (retry, circuit breaker) are layered in
16
- through `quadkit-resilience`, not built in by default.
16
+ through `the optional provider`, not built in by default.
17
17
 
18
18
 
19
19
  > Full documentation: [docs.quadkit.dev](https://docs.quadkit.dev)
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "quadkit-http"
7
- version = "0.0.42"
7
+ version = "0.0.44"
8
8
  description = "Outbound HTTP client for the Quadkit Framework"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -133,7 +133,7 @@ class HTTPClient(
133
133
  pool: Optional pre-configured :class:`ConnectionPool`. Created from
134
134
  *config* when not provided.
135
135
  retry_policy: Retry policy for transient failures. Defaults to a
136
- :class:`~quadkit.resilience.RetryPolicy` with framework defaults.
136
+ :class:`~the optional provider module` with framework defaults.
137
137
  circuit_breaker: Optional circuit breaker; disabled when ``None``.
138
138
  interceptors: Zero or more interceptors applied to every request.
139
139
  resilience: Optional :class:`ResiliencePipelineProtocol` that combines
@@ -1,6 +1,6 @@
1
1
  """Retry policy for HTTP client requests.
2
2
 
3
- Local implementation to avoid cross-package imports from quadkit.resilience.
3
+ Local implementation to avoid cross-package imports from the optional provider module.
4
4
  """
5
5
 
6
6
  from __future__ import annotations
@@ -63,7 +63,7 @@ class RetryPolicy:
63
63
 
64
64
  Executes an async callable with configurable retry logic, exponential
65
65
  backoff, and per-exception filtering. Decision semantics follow the
66
- canonical :mod:`quadkit.resilience` retry implementation for the
66
+ canonical :mod:`the optional provider module` retry implementation for the
67
67
  shared :class:`~quadkit.contracts.infra.resilience.models.RetryConfig`:
68
68
 
69
69
  * ``max_attempts`` is the *total* number of attempts including the
@@ -278,9 +278,9 @@ class TestTimeoutNormalization:
278
278
  async def test_get_normalises_aiohttp_connection_error(self):
279
279
  """aiohttp.ClientError (connect refused/DNS/reset) → Err(HTTPConnectionError).
280
280
 
281
- Regression (QK-USE-3, gatewai probe adoption): raw aiohttp errors used
281
+ Detail (QK-USE-3, probe adoption): raw aiohttp errors used
282
282
  to escape `get()` instead of mapping to the documented connection-error
283
- Result — unreachable hosts crashed callers that expected `Err`.
283
+ Result — unreachable hosts reach callers that expected `Err`.
284
284
  """
285
285
  client = HTTPClientImpl(
286
286
  config=HTTPClientConfig(enforce_url_safety=False),
@@ -1,214 +0,0 @@
1
- *
2
- !tests/
3
- !tests/**
4
- !README.md
5
- !AGENTS.md
6
- !DEVELOPMENT.md
7
- !QUICKGUIDE.md
8
- !MILESTONE*.md
9
- !TESTS*.md
10
- !CHANGELOG.md
11
- !AUDIT*.md
12
- !REF*.md
13
- !AUDIT*.json
14
- !LICENSE
15
- !LICENSE-MIT.txt
16
- !NOTICE
17
- !TRADEMARK.md
18
- !conftest.py
19
- !CONTRIBUTING.md
20
- !CODE_OF_CONDUCT.md
21
- !SECURITY.md
22
- !GOVERNANCE.md
23
- !pyproject.toml
24
- !uv.lock
25
- !docker-compose.yml
26
- !.env.example
27
- !.env.full.example
28
- !.importlinter
29
- !.superpowers/
30
- !.superpowers/**
31
- !.gitattributes
32
- !.editorconfig
33
- !Makefile
34
- !docker-compose.test.yml
35
- !quadkit.yaml.example
36
- !.github/
37
- !.github/workflows/
38
- !.github/workflows/**
39
- !.github/dependabot.yml
40
- !.devcontainer/
41
- !.devcontainer/**
42
- !tools/
43
- !tools/**
44
- !*example.yaml
45
-
46
- # Quadkit Packages
47
- !quadkit*/
48
- !quadkit*/pyproject.toml
49
- !quadkit*/CHANGELOG.md
50
- !quadkit*/README.md
51
- !quadkit*/alembic.ini
52
- !quadkit*/mypy.ini
53
- !quadkit*/*.yaml.example
54
- !quadkit*/src/
55
- !quadkit*/src/**
56
- !quadkit*/docs/
57
- !quadkit*/docs/**
58
- !quadkit*/tests/
59
- !quadkit*/tests/**
60
-
61
- # Keep quadkit docs
62
- !/docs/
63
- !docs/**
64
-
65
- # Session / cutover plans at the repo root
66
- !plans/
67
- !plans/**
68
-
69
- # Keep example files for README examples
70
- !/examples/
71
- !/examples/**
72
-
73
- # Re-ignore these AFTER negations (last match wins)
74
- *.tape
75
- examples/*/runs/
76
- __pycache__/
77
- *.pyc
78
- *.pyo
79
- *.egg-info/
80
- dist/
81
- build/
82
- .eggs/
83
- *.egg
84
- .pytest_cache/
85
- .mypy_cache/
86
- .ruff_cache/
87
- htmlcov/
88
- .coverage
89
- coverage.xml
90
- test.db
91
- !quadkit*/tests/fixtures/test.db
92
- *.log
93
- *.tmp
94
-
95
- # Company-specific files (keep private, do not commit)
96
- .company/
97
- COMPANY_*.md
98
- company-config/
99
- internal-docs/
100
- # Editor swap files
101
- *.swp
102
-
103
- !core/
104
- !packages/
105
- !core/quadkit*/
106
- !packages/quadkit*/
107
- !core/quadkit*/pyproject.toml
108
- !packages/quadkit*/pyproject.toml
109
- !core/quadkit*/CHANGELOG.md
110
- !packages/quadkit*/CHANGELOG.md
111
- !core/quadkit*/README.md
112
- !packages/quadkit*/README.md
113
- !core/quadkit*/alembic.ini
114
- !packages/quadkit*/alembic.ini
115
- !core/quadkit*/mypy.ini
116
- !packages/quadkit*/mypy.ini
117
- !core/quadkit*/*.yaml.example
118
- !packages/quadkit*/*.yaml.example
119
- !core/quadkit*/src/
120
- !core/quadkit*/src/**
121
- !packages/quadkit*/src/
122
- !packages/quadkit*/src/**
123
- !core/quadkit*/tests/
124
- !core/quadkit*/tests/**
125
- !packages/quadkit*/tests/
126
- !packages/quadkit*/tests/**
127
- !core/quadkit*/docs/
128
- !core/quadkit*/docs/**
129
- !packages/quadkit*/docs/
130
- !packages/quadkit*/docs/**
131
-
132
- # dev/
133
- !dev/
134
- !dev/**
135
-
136
- # Admin dev playground (committed dev asset; local SQLite files stay ignored)
137
- !packages/quadkit-admin/playground/
138
- !packages/quadkit-admin/playground/serve.py
139
-
140
- # gatewai apps
141
- !apps/
142
- !apps/**
143
- !apps/gatewai/
144
- !apps/gatewai/**
145
- apps/**/node_modules/
146
- apps/**/dist/
147
- apps/**/__pycache__/
148
- apps/**/*.pyc
149
- apps/**/*.db
150
- apps/**/*.db-wal
151
- apps/**/*.db-shm
152
- apps/**/.env
153
- apps/**/.env.*
154
- !apps/**/.env.example
155
- !apps/**/.env.full.example
156
-
157
- # Piccolina — nested app tracked in this monorepo (standalone .git removed).
158
- # Keep marketing packages and deployment secrets out of the tree.
159
- apps/piccolina/piccolina-marketing/
160
- apps/piccolina/piccolina-marketing-plans/
161
- apps/piccolina/secrets/
162
- apps/piccolina/**/secrets/
163
- apps/piccolina/**/*.pem
164
- apps/piccolina/**/*.key
165
- apps/piccolina/**/.venv/
166
- apps/piccolina/**/node_modules/
167
- apps/piccolina/**/__pycache__/
168
- apps/piccolina/**/*.pyc
169
- apps/piccolina/**/*.pbf
170
- apps/piccolina/**/.open-next/
171
- apps/piccolina/**/storage/
172
- apps/piccolina/**/.deploy-host
173
-
174
- # Re-exclude private content under the tiered paths.
175
- # MUST come after the !...docs/** negations above — last match wins.
176
- core/quadkit*/docs/plans/
177
- packages/quadkit*/docs/plans/
178
- core/quadkit*/docs/**/
179
- packages/quadkit*/docs/**/
180
-
181
- # Re-exclude tooling artifacts under the tiered paths
182
- __pycache__/
183
- *.pyc
184
- *.pyo
185
- *.egg-info/
186
- .pytest_cache/
187
- .mypy_cache/
188
- .ruff_cache/
189
- htmlcov/
190
- .coverage
191
- coverage.xml
192
- test.db
193
- *.tmp
194
- *.log
195
- !core/quadkit*/tests/fixtures/test.db
196
- !packages/quadkit*/tests/fixtures/test.db
197
-
198
- # Allow the generated example gifs to be tracked (README hero images).
199
- !core/quadkit*/docs/gifs/
200
- !core/quadkit*/docs/gifs/**
201
- !packages/*/quadkit*/docs/gifs/
202
- !packages/*/quadkit*/docs/gifs/**
203
- examples/*/screenshot.png
204
- !banner*
205
- apps/quadkit-playground/**/node_modules/
206
- apps/quadkit-playground/**/dist/
207
- apps/quadkit-playground/**/__pycache__/
208
- apps/quadkit-playground/**/*.pyc
209
- apps/quadkit-playground/**/*.db
210
- apps/quadkit-playground/**/*.db-wal
211
- apps/quadkit-playground/**/*.db-shm
212
- apps/quadkit-playground/projects/
213
- apps/quadkit-playground/quadkit/
214
- docs/superpowers/
@@ -1,250 +0,0 @@
1
- # Architecture
2
-
3
- Internal design of the `quadkit-http` package.
4
-
5
- ---
6
-
7
- ## Role in the System
8
-
9
- ```mermaid
10
- flowchart BT
11
- Contracts[quadkit-contracts<br/>HTTPClientProtocol · InterceptorProtocol<br/>HttpResponse · HttpStatusError]
12
- Core[quadkit<br/>DI · Provider · Module · Config · Result]
13
- Http[quadkit-http<br/>HTTPClient · ConnectionPool · Retry · Interceptors]
14
- Resilience[quadkit-resilience<br/>RetryPolicyProtocol · CircuitBreakerProtocol]
15
- Consumers["Application code<br/>(LLM adapters, API clients)"]
16
-
17
- Http --> Core
18
- Http --> Contracts
19
- Http -.->|optional| Resilience
20
- Consumers --> Http
21
- ```
22
-
23
- `quadkit-http` depends on `quadkit` and `quadkit-contracts`. Resilience is optional — resolved from the container at boot. Consumer code communicates through `HTTPClientProtocol`.
24
-
25
- ---
26
-
27
- ## Client Architecture
28
-
29
- ```mermaid
30
- flowchart LR
31
- subgraph Public
32
- HC[HTTPClient<br/>get · post · put · delete · patch · head]
33
- BHC[BaseURLHTTPClient<br/>Base URL · default headers]
34
- end
35
- subgraph Internal
36
- CP[ConnectionPool<br/>aiohttp TCPConnector]
37
- RP[RetryPolicy<br/>Exponential backoff]
38
- INT[Interceptor chain]
39
- end
40
- subgraph Optional
41
- CB[CircuitBreakerProtocol]
42
- RES[ResiliencePipelineProtocol]
43
- end
44
- HC --> CP & RP & INT
45
- HC -.-> CB & RES
46
- BHC --> HC
47
- ```
48
-
49
- `HTTPClient` exposes a `request()` method (raises exceptions) and Result-returning verb methods for domain code. `BaseURLHTTPClient` wraps `HTTPClient` with base-URL resolution and default headers — designed for SDK-style callers.
50
-
51
- ### ConnectionPool
52
-
53
- Wraps `aiohttp.TCPConnector` with configurable limits. Supports `warm_up(urls)` to pre-establish TLS handshakes for critical endpoints.
54
-
55
- ### RetryPolicy
56
-
57
- Local implementation. Exponential backoff with optional jitter:
58
-
59
- ```python
60
- config = RetryConfig(
61
- max_attempts=3, base_delay=1.0,
62
- max_delay=30.0, backoff_factor=2.0,
63
- jitter=0.5, retry_on=(TimeoutError, ConnectionError),
64
- )
65
- ```
66
-
67
- ### Interceptors
68
-
69
- Receive typed `RequestContext` and raw response objects:
70
-
71
- ```python
72
- class LoggingInterceptor:
73
- async def intercept_request(self, ctx: RequestContext) -> RequestContext:
74
- logger.info("http.request", method=ctx.method, url=ctx.url)
75
- return ctx
76
- async def intercept_response(self, response: Any) -> Any:
77
- logger.info("http.response", status=getattr(response, "status", None))
78
- return response
79
- ```
80
-
81
- ---
82
-
83
- ## Request/Response Model
84
-
85
- ```mermaid
86
- sequenceDiagram
87
- actor C as Caller
88
- participant HC as HTTPClient
89
- participant I as Interceptors
90
- participant CB as CircuitBreaker
91
- participant RP as RetryPolicy
92
- participant S as Remote Server
93
-
94
- C->>HC: get(url)
95
- HC->>I: intercept_request(ctx)
96
- HC->>CB: check state
97
- alt circuit open
98
- CB-->>HC: Raise HTTPCircuitOpenError
99
- else
100
- HC->>RP: execute(_execute)
101
- RP->>S: HTTP request
102
- S-->>RP: Response
103
- end
104
- HC->>I: intercept_response(raw)
105
- HC->>HC: to HttpResponse
106
- alt status >= 400
107
- HC-->>C: Err(HTTPStatusError)
108
- else
109
- HC-->>C: Ok(HttpResponse)
110
- end
111
- ```
112
-
113
- All consumers receive `HttpResponse` (from `quadkit-contracts`) — a dataclass decoupling them from `aiohttp`:
114
-
115
- ```python
116
- @dataclass(frozen=True)
117
- class HttpResponse:
118
- status: int
119
- headers: dict[str, str]
120
- body: bytes
121
- text: str # UTF-8 decoded
122
- json: Any | None # Parsed JSON
123
- url: str
124
- method: str
125
- content_length: int | None
126
- ```
127
-
128
- The client also provides `stream()` for byte-level streaming and `sse()` for `text/event-stream` parsing.
129
-
130
- ---
131
-
132
- ## Authentication
133
-
134
- `quadkit-http` has no built-in auth handlers. Authentication uses standard HTTP header mechanisms:
135
-
136
- | Approach | Implementation |
137
- |----------|---------------|
138
- | **Bearer token** | `Authorization: Bearer <token>` header per-request or via `BaseURLHTTPClient` defaults |
139
- | **Basic auth** | `Authorization: Basic <base64>` directly |
140
- | **API key** | Custom header (e.g. `X-API-Key`) per request |
141
- | **OAuth 2.0** | Implement token-refresh interceptor using `InterceptorProtocol` |
142
- | **Custom** | Implement `InterceptorProtocol.intercept_request()` to mutate `RequestContext.headers` |
143
-
144
- ```python
145
- # Bearer token via BaseURLHTTPClient
146
- client = BaseURLHTTPClient(
147
- base_url="https://api.example.com/v1",
148
- headers={"Authorization": "Bearer eyJhbGci..."},
149
- )
150
-
151
- # API key via interceptor
152
- class ApiKeyInterceptor:
153
- async def intercept_request(self, ctx: RequestContext) -> RequestContext:
154
- ctx.headers["X-API-Key"] = self._api_key
155
- return ctx
156
- async def intercept_response(self, response: Any) -> Any:
157
- return response
158
- ```
159
-
160
- ---
161
-
162
- ## Error Handling
163
-
164
- Two-layer error strategy:
165
-
166
- - **Infrastructure** (`request()`) — raises `HTTPCircuitOpenError`, `HTTPRetryExhaustedError`
167
- - **Domain** (verb methods) — returns `Result[HttpResponse, HTTPClientError]`
168
-
169
- ```
170
- HTTPClientError (QK_ERR_HTTP_001)
171
- ├── HTTPConnectionError (002) HTTPTimeoutError (003)
172
- ├── HTTPInterceptorError (004) HTTPCircuitOpenError (005)
173
- ├── HTTPRetryExhaustedError (006)
174
- └── HTTPStatusError (007) # 4xx/5xx
175
- ```
176
-
177
- Verb methods map 4xx/5xx to `Err(HTTPStatusError)` and transport failures to `Err(HTTPConnectionError | HTTPTimeoutError)`. Circuit-breaker open and retry-exhausted always propagate as exceptions.
178
-
179
- ---
180
-
181
- ## Contracts Used
182
-
183
- | Protocol / Type | Source | Purpose |
184
- |----------------|--------|---------|
185
- | `HTTPClientProtocol` | `quadkit.contracts.web.http_protocols` | Outbound HTTP client interface |
186
- | `InterceptorProtocol` | `quadkit.contracts.web.http_protocols` | Request/response interception |
187
- | `InterceptorChainProtocol` | `quadkit.contracts.web.http_protocols` | Interceptor chain management |
188
- | `HttpResponse` | `quadkit.contracts.web.http_models` | Transport-agnostic response |
189
- | `HttpStatusError` | `quadkit.contracts.web.http_models` | 4xx/5xx error with response |
190
- | `HTTPSessionProtocol` | `quadkit.contracts.web.http_protocols` | HTTP session abstraction |
191
- | `ServerSentEvent` | `quadkit.contracts.web.sse` | SSE event dataclass |
192
- | `RetryPolicyProtocol` | `quadkit.contracts.infra.resilience` | Retry policy (optional DI) |
193
- | `CircuitBreakerProtocol` | `quadkit.contracts.infra.resilience` | Circuit breaker (optional DI) |
194
- | `ResiliencePipelineProtocol` | `quadkit.contracts.infra.resilience` | Composable resilience (optional DI) |
195
- | `MetricsRecorderProtocol` | `quadkit.contracts.observability` | Request metrics (optional DI) |
196
-
197
- ---
198
-
199
- ## Extension Points
200
-
201
- | Point | Interface | Default | Customise by |
202
- |-------|-----------|---------|--------------|
203
- | Request interceptor | `InterceptorProtocol.intercept_request()` | None | Implement protocol, inject via constructor |
204
- | Response interceptor | `InterceptorProtocol.intercept_response()` | None | Implement protocol, inject via constructor |
205
- | Retry policy | `RetryPolicyProtocol` | Local `RetryPolicy` with defaults | Inject via constructor or register before boot |
206
- | Circuit breaker | `CircuitBreakerProtocol` | None | Register `CircuitBreakerRegistryProtocol` before boot |
207
- | Resilience pipeline | `ResiliencePipelineProtocol` | None | Register before boot |
208
- | Metrics | `MetricsRecorderProtocol` | None | Inject via constructor |
209
-
210
- All resilience dependencies are **optional** — the client works without them.
211
-
212
- ---
213
-
214
- ## DI Registration
215
-
216
- ```python
217
- class HTTPProvider(Provider):
218
- name = "http"
219
- priority = ProviderPriority.INFRASTRUCTURE
220
-
221
- def __init__(self, config: HTTPClientConfig | None = None) -> None:
222
- self._config = config or HTTPClientConfig()
223
- self._client: HTTPClient | None = None
224
-
225
- async def register(self, container: ContainerRegistrarProtocol) -> None:
226
- container.singleton(HTTPClient, self._get_client)
227
- container.singleton(HTTPClientProtocol, self._get_client)
228
-
229
- async def boot(self, container: ContainerResolverProtocol) -> None:
230
- retry = await _try_resolve(container, RetryPolicyProtocol)
231
- cb = await _try_resolve_circuit_breaker(container)
232
- resilience = await _try_resolve(container, ResiliencePipelineProtocol)
233
- self._client = HTTPClient(config=self._config, retry_policy=retry, circuit_breaker=cb, resilience=resilience)
234
- await self._client.start()
235
-
236
- async def shutdown(self) -> None:
237
- if self._client:
238
- await self._client.stop()
239
- ```
240
-
241
- Module registration:
242
-
243
- ```python
244
- from quadkit.http import HTTPModule
245
-
246
- app.add_module(HTTPModule.configure()) # defaults
247
- app.add_module(HTTPModule.configure( # custom
248
- HTTPClientConfig(pool=ConnectionPoolConfig(timeout=60.0))
249
- ))
250
- ```
@@ -1,50 +0,0 @@
1
- ---
2
- title: quadkit-http Configuration
3
- description: Every configuration key for the HTTP client
4
- ---
5
-
6
- Config section: `http`
7
- Env prefix: `QK_HTTP__`
8
- Config model: `HTTPClientConfig`
9
-
10
- ## Top-level
11
-
12
- | Key | Type | Default | Env var | Description |
13
- |-----|------|---------|---------|-------------|
14
- | `pool` | `ConnectionPoolConfig` | — | `QK_HTTP__POOL__*` | Connection pool settings |
15
- | `proxy` | `str \| None` | `None` | `QK_HTTP__PROXY` | HTTP/HTTPS proxy URL |
16
- | `trust_env` | `bool` | `True` | `QK_HTTP__TRUST_ENV` | Read proxy from env vars |
17
- | `cookie_jar` | `bool` | `True` | `QK_HTTP__COOKIE_JAR` | Enable in-memory cookie jar |
18
-
19
- ## ConnectionPoolConfig
20
-
21
- | Key | Type | Default | Env var | Description |
22
- |-----|------|---------|---------|-------------|
23
- | `max_connections` | `int` | `10` | `QK_HTTP__POOL__MAX_CONNECTIONS` | Total concurrent connections |
24
- | `max_keepalive_connections` | `int` | `5` | `QK_HTTP__POOL__MAX_KEEPALIVE_CONNECTIONS` | Keep-alive per host |
25
- | `max_connections_per_host` | `int` | `10` | `QK_HTTP__POOL__MAX_CONNECTIONS_PER_HOST` | Max per host |
26
- | `timeout` | `float` | `30.0` | `QK_HTTP__POOL__TIMEOUT` | Request timeout (seconds) |
27
- | `ttl_dns_cache` | `int` | `300` | `QK_HTTP__POOL__TTL_DNS_CACHE` | DNS cache TTL (seconds) |
28
- | `force_close` | `bool` | `False` | `QK_HTTP__POOL__FORCE_CLOSE` | Force-close connections after use |
29
-
30
- ## Example YAML
31
-
32
- ```yaml
33
- http:
34
- pool:
35
- max_connections: 50
36
- max_keepalive_connections: 20
37
- timeout: 10.0
38
- ttl_dns_cache: 60
39
- proxy: http://proxy.example.com:8080
40
- trust_env: true
41
- cookie_jar: true
42
- ```
43
-
44
- Env var override form:
45
-
46
- ```bash
47
- export QK_HTTP__POOL__TIMEOUT=5.0
48
- export QK_HTTP__POOL__MAX_CONNECTIONS=100
49
- export QK_HTTP__PROXY=http://proxy.internal:8080
50
- ```