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.
- quadkit_http-0.0.44/.gitignore +24 -0
- {quadkit_http-0.0.42 → quadkit_http-0.0.44}/PKG-INFO +2 -2
- {quadkit_http-0.0.42 → quadkit_http-0.0.44}/README.md +1 -1
- {quadkit_http-0.0.42 → quadkit_http-0.0.44}/pyproject.toml +1 -1
- {quadkit_http-0.0.42 → quadkit_http-0.0.44}/src/quadkit/http/client/http_client.py +1 -1
- {quadkit_http-0.0.42 → quadkit_http-0.0.44}/src/quadkit/http/retry/policy.py +2 -2
- {quadkit_http-0.0.42 → quadkit_http-0.0.44}/tests/unit/test_http_client_verbs.py +2 -2
- quadkit_http-0.0.42/.gitignore +0 -214
- quadkit_http-0.0.42/docs/ARCHITECTURE.md +0 -250
- quadkit_http-0.0.42/docs/CONFIGURATION.md +0 -50
- quadkit_http-0.0.42/docs/GUIDE.md +0 -125
- quadkit_http-0.0.42/docs/HOWTOS.md +0 -155
- quadkit_http-0.0.42/docs/QUICKSTART.md +0 -64
- quadkit_http-0.0.42/docs/TROUBLESHOOTING.md +0 -143
- quadkit_http-0.0.42/tests/unit/test_http_module_client.py +0 -313
- {quadkit_http-0.0.42 → quadkit_http-0.0.44}/LICENSE +0 -0
- {quadkit_http-0.0.42 → quadkit_http-0.0.44}/NOTICE +0 -0
- {quadkit_http-0.0.42 → quadkit_http-0.0.44}/example.yaml +0 -0
- {quadkit_http-0.0.42 → quadkit_http-0.0.44}/src/quadkit/http/__init__.py +0 -0
- {quadkit_http-0.0.42 → quadkit_http-0.0.44}/src/quadkit/http/cli/__init__.py +0 -0
- {quadkit_http-0.0.42 → quadkit_http-0.0.44}/src/quadkit/http/cli/contributor.py +0 -0
- {quadkit_http-0.0.42 → quadkit_http-0.0.44}/src/quadkit/http/cli/generators/__init__.py +0 -0
- {quadkit_http-0.0.42 → quadkit_http-0.0.44}/src/quadkit/http/cli/generators/api_client.py +0 -0
- {quadkit_http-0.0.42 → quadkit_http-0.0.44}/src/quadkit/http/cli/templates/api_client.py.jinja2 +0 -0
- {quadkit_http-0.0.42 → quadkit_http-0.0.44}/src/quadkit/http/client/__init__.py +0 -0
- {quadkit_http-0.0.42 → quadkit_http-0.0.44}/src/quadkit/http/client/_streaming.py +0 -0
- {quadkit_http-0.0.42 → quadkit_http-0.0.44}/src/quadkit/http/client/_verbs.py +0 -0
- {quadkit_http-0.0.42 → quadkit_http-0.0.44}/src/quadkit/http/client/base_url_client.py +0 -0
- {quadkit_http-0.0.42 → quadkit_http-0.0.44}/src/quadkit/http/config.py +0 -0
- {quadkit_http-0.0.42 → quadkit_http-0.0.44}/src/quadkit/http/constants.py +0 -0
- {quadkit_http-0.0.42 → quadkit_http-0.0.44}/src/quadkit/http/decorators.py +0 -0
- {quadkit_http-0.0.42 → quadkit_http-0.0.44}/src/quadkit/http/di/__init__.py +0 -0
- {quadkit_http-0.0.42 → quadkit_http-0.0.44}/src/quadkit/http/di/provider.py +0 -0
- {quadkit_http-0.0.42 → quadkit_http-0.0.44}/src/quadkit/http/events.py +0 -0
- {quadkit_http-0.0.42 → quadkit_http-0.0.44}/src/quadkit/http/exceptions.py +0 -0
- {quadkit_http-0.0.42 → quadkit_http-0.0.44}/src/quadkit/http/hooks.py +0 -0
- {quadkit_http-0.0.42 → quadkit_http-0.0.44}/src/quadkit/http/lib/__init__.py +0 -0
- {quadkit_http-0.0.42 → quadkit_http-0.0.44}/src/quadkit/http/lib/format.py +0 -0
- {quadkit_http-0.0.42 → quadkit_http-0.0.44}/src/quadkit/http/lib/headers.py +0 -0
- {quadkit_http-0.0.42 → quadkit_http-0.0.44}/src/quadkit/http/lib/url.py +0 -0
- {quadkit_http-0.0.42 → quadkit_http-0.0.44}/src/quadkit/http/module.py +0 -0
- {quadkit_http-0.0.42 → quadkit_http-0.0.44}/src/quadkit/http/pool/__init__.py +0 -0
- {quadkit_http-0.0.42 → quadkit_http-0.0.44}/src/quadkit/http/pool/connection_pool.py +0 -0
- {quadkit_http-0.0.42 → quadkit_http-0.0.44}/src/quadkit/http/protocols.py +0 -0
- {quadkit_http-0.0.42 → quadkit_http-0.0.44}/src/quadkit/http/py.typed +0 -0
- {quadkit_http-0.0.42 → quadkit_http-0.0.44}/src/quadkit/http/retry/__init__.py +0 -0
- {quadkit_http-0.0.42 → quadkit_http-0.0.44}/src/quadkit/http/types.py +0 -0
- {quadkit_http-0.0.42 → quadkit_http-0.0.44}/src/quadkit/http/validation/__init__.py +0 -0
- {quadkit_http-0.0.42 → quadkit_http-0.0.44}/src/quadkit/http/validation/primitives.py +0 -0
- {quadkit_http-0.0.42 → quadkit_http-0.0.44}/src/quadkit/http/validation/url.py +0 -0
- {quadkit_http-0.0.42 → quadkit_http-0.0.44}/src/quadkit/http/websocket.py +0 -0
- {quadkit_http-0.0.42 → quadkit_http-0.0.44}/src/quadkit/py.typed +0 -0
- {quadkit_http-0.0.42 → quadkit_http-0.0.44}/tests/conftest.py +0 -0
- {quadkit_http-0.0.42 → quadkit_http-0.0.44}/tests/integration/__init__.py +0 -0
- {quadkit_http-0.0.42 → quadkit_http-0.0.44}/tests/integration/test_http_lifecycle.py +0 -0
- {quadkit_http-0.0.42 → quadkit_http-0.0.44}/tests/unit/cli/test_api_client_generator.py +0 -0
- {quadkit_http-0.0.42 → quadkit_http-0.0.44}/tests/unit/cli/test_generators_contract.py +0 -0
- {quadkit_http-0.0.42 → quadkit_http-0.0.44}/tests/unit/http_module_test_support.py +0 -0
- {quadkit_http-0.0.42 → quadkit_http-0.0.44}/tests/unit/test_base_url_client_streaming_and_events.py +0 -0
- {quadkit_http-0.0.42 → quadkit_http-0.0.44}/tests/unit/test_exceptions.py +0 -0
- {quadkit_http-0.0.42 → quadkit_http-0.0.44}/tests/unit/test_http_client.py +0 -0
- {quadkit_http-0.0.42 → quadkit_http-0.0.44}/tests/unit/test_http_constants.py +0 -0
- {quadkit_http-0.0.42 → quadkit_http-0.0.44}/tests/unit/test_http_exceptions.py +0 -0
- {quadkit_http-0.0.42 → quadkit_http-0.0.44}/tests/unit/test_http_module_config.py +0 -0
- {quadkit_http-0.0.42 → quadkit_http-0.0.44}/tests/unit/test_http_module_interceptors.py +0 -0
- {quadkit_http-0.0.42 → quadkit_http-0.0.44}/tests/unit/test_http_module_validation.py +0 -0
- {quadkit_http-0.0.42 → quadkit_http-0.0.44}/tests/unit/test_http_providers.py +0 -0
- {quadkit_http-0.0.42 → quadkit_http-0.0.44}/tests/unit/test_http_streaming.py +0 -0
- {quadkit_http-0.0.42 → quadkit_http-0.0.44}/tests/unit/test_http_types.py +0 -0
- {quadkit_http-0.0.42 → quadkit_http-0.0.44}/tests/unit/test_http_types_extended.py +0 -0
- {quadkit_http-0.0.42 → quadkit_http-0.0.44}/tests/unit/test_http_url_safety.py +0 -0
- {quadkit_http-0.0.42 → quadkit_http-0.0.44}/tests/unit/test_lib_format.py +0 -0
- {quadkit_http-0.0.42 → quadkit_http-0.0.44}/tests/unit/test_lib_headers.py +0 -0
- {quadkit_http-0.0.42 → quadkit_http-0.0.44}/tests/unit/test_m17_m18_http.py +0 -0
- {quadkit_http-0.0.42 → quadkit_http-0.0.44}/tests/unit/test_package_hooks_p2.py +0 -0
- {quadkit_http-0.0.42 → quadkit_http-0.0.44}/tests/unit/test_package_structure_p1.py +0 -0
- {quadkit_http-0.0.42 → quadkit_http-0.0.44}/tests/unit/test_protocols.py +0 -0
- {quadkit_http-0.0.42 → quadkit_http-0.0.44}/tests/unit/test_redirect_ssrf.py +0 -0
- {quadkit_http-0.0.42 → quadkit_http-0.0.44}/tests/unit/test_retry_policy.py +0 -0
- {quadkit_http-0.0.42 → quadkit_http-0.0.44}/tests/unit/test_retry_policy_canonical.py +0 -0
- {quadkit_http-0.0.42 → quadkit_http-0.0.44}/tests/unit/test_round24_hardening.py +0 -0
- {quadkit_http-0.0.42 → quadkit_http-0.0.44}/tests/unit/test_url_utils.py +0 -0
- {quadkit_http-0.0.42 → quadkit_http-0.0.44}/tests/unit/test_utils.py +0 -0
- {quadkit_http-0.0.42 → quadkit_http-0.0.44}/tests/unit/test_validation.py +0 -0
- {quadkit_http-0.0.42 → quadkit_http-0.0.44}/tests/unit/test_validation_primitives.py +0 -0
- {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.
|
|
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 `
|
|
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 `
|
|
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)
|
|
@@ -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:`~
|
|
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
|
|
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:`
|
|
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
|
-
|
|
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
|
|
283
|
+
Result — unreachable hosts reach callers that expected `Err`.
|
|
284
284
|
"""
|
|
285
285
|
client = HTTPClientImpl(
|
|
286
286
|
config=HTTPClientConfig(enforce_url_safety=False),
|
quadkit_http-0.0.42/.gitignore
DELETED
|
@@ -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
|
-
```
|