hexastack-fastapi 0.2.0__tar.gz → 0.4.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.
- {hexastack_fastapi-0.2.0 → hexastack_fastapi-0.4.0}/PKG-INFO +14 -11
- {hexastack_fastapi-0.2.0 → hexastack_fastapi-0.4.0}/README.md +11 -6
- {hexastack_fastapi-0.2.0 → hexastack_fastapi-0.4.0}/pyproject.toml +15 -5
- {hexastack_fastapi-0.2.0 → hexastack_fastapi-0.4.0}/pyproject.toml.orig +15 -8
- {hexastack_fastapi-0.2.0 → hexastack_fastapi-0.4.0}/src/hexastack_fastapi/adapters/__init__.py +13 -12
- {hexastack_fastapi-0.2.0 → hexastack_fastapi-0.4.0}/src/hexastack_fastapi/adapters/health.py +20 -7
- hexastack_fastapi-0.4.0/src/hexastack_fastapi/adapters/metrics.py +44 -0
- {hexastack_fastapi-0.2.0 → hexastack_fastapi-0.4.0}/src/hexastack_fastapi/adapters/routing.py +107 -1
- hexastack_fastapi-0.4.0/src/hexastack_fastapi/adapters/sse.py +188 -0
- hexastack_fastapi-0.4.0/src/hexastack_fastapi/adapters/websockets.py +212 -0
- {hexastack_fastapi-0.2.0/src/hexastack_fastapi/infra → hexastack_fastapi-0.4.0/src/hexastack_fastapi/domain}/config.py +1 -21
- {hexastack_fastapi-0.2.0 → hexastack_fastapi-0.4.0}/src/hexastack_fastapi/infra/__init__.py +2 -0
- {hexastack_fastapi-0.2.0/src/hexastack_fastapi/adapters → hexastack_fastapi-0.4.0/src/hexastack_fastapi/infra}/app.py +15 -0
- {hexastack_fastapi-0.2.0 → hexastack_fastapi-0.4.0}/src/hexastack_fastapi/infra/bootstrap.py +1 -1
- hexastack_fastapi-0.4.0/src/hexastack_fastapi/infra/config.py +31 -0
- {hexastack_fastapi-0.2.0 → hexastack_fastapi-0.4.0}/src/hexastack_fastapi/infra/middleware/__init__.py +4 -0
- hexastack_fastapi-0.4.0/src/hexastack_fastapi/infra/middleware/metrics.py +122 -0
- hexastack_fastapi-0.4.0/src/hexastack_fastapi/py.typed +0 -0
- hexastack_fastapi-0.2.0/src/hexastack_fastapi/adapters/ui.py +0 -502
- {hexastack_fastapi-0.2.0 → hexastack_fastapi-0.4.0}/src/hexastack_fastapi/__init__.py +0 -0
- {hexastack_fastapi-0.2.0 → hexastack_fastapi-0.4.0}/src/hexastack_fastapi/adapters/db_session.py +0 -0
- {hexastack_fastapi-0.2.0 → hexastack_fastapi-0.4.0}/src/hexastack_fastapi/adapters/dependencies.py +0 -0
- {hexastack_fastapi-0.2.0 → hexastack_fastapi-0.4.0}/src/hexastack_fastapi/adapters/docs.py +0 -0
- {hexastack_fastapi-0.2.0 → hexastack_fastapi-0.4.0}/src/hexastack_fastapi/adapters/ratelimit.py +0 -0
- /hexastack_fastapi-0.2.0/src/hexastack_fastapi/py.typed → /hexastack_fastapi-0.4.0/src/hexastack_fastapi/domain/__init__.py +0 -0
- {hexastack_fastapi-0.2.0 → hexastack_fastapi-0.4.0}/src/hexastack_fastapi/infra/autodiscovery.py +0 -0
- {hexastack_fastapi-0.2.0 → hexastack_fastapi-0.4.0}/src/hexastack_fastapi/infra/decorators.py +0 -0
- {hexastack_fastapi-0.2.0 → hexastack_fastapi-0.4.0}/src/hexastack_fastapi/infra/exception_handlers.py +0 -0
- {hexastack_fastapi-0.2.0 → hexastack_fastapi-0.4.0}/src/hexastack_fastapi/infra/middleware/correlation.py +0 -0
- {hexastack_fastapi-0.2.0 → hexastack_fastapi-0.4.0}/src/hexastack_fastapi/infra/middleware/logging.py +0 -0
- {hexastack_fastapi-0.2.0 → hexastack_fastapi-0.4.0}/src/hexastack_fastapi/testing/__init__.py +0 -0
- {hexastack_fastapi-0.2.0 → hexastack_fastapi-0.4.0}/src/hexastack_fastapi/testing/cursor.py +0 -0
- {hexastack_fastapi-0.2.0 → hexastack_fastapi-0.4.0}/src/hexastack_fastapi/testing/recorder.py +0 -0
- {hexastack_fastapi-0.2.0 → hexastack_fastapi-0.4.0}/src/hexastack_fastapi/testing/server.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: hexastack-fastapi
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: FastAPI presentation adapter for Hexastack
|
|
5
5
|
Author: Richard West
|
|
6
6
|
Author-email: Richard West <dopplereffect.us@gmail.com>
|
|
@@ -10,17 +10,15 @@ Requires-Dist: hexastack-core
|
|
|
10
10
|
Requires-Dist: hexastack-cqrs
|
|
11
11
|
Requires-Dist: hexastack-auth ; extra == 'auth'
|
|
12
12
|
Requires-Dist: zensical>=0.0.56 ; extra == 'docs'
|
|
13
|
-
Requires-Dist: limits>=
|
|
14
|
-
Requires-Dist: slowapi>=0.1.
|
|
13
|
+
Requires-Dist: limits>=5.8.0 ; extra == 'ratelimit'
|
|
14
|
+
Requires-Dist: slowapi>=0.1.10 ; extra == 'ratelimit'
|
|
15
15
|
Requires-Dist: playwright>=1.49.0 ; extra == 'testing'
|
|
16
16
|
Requires-Dist: schemathesis>=3.39.0 ; extra == 'testing'
|
|
17
|
-
Requires-Dist: nicegui>=2.0.0 ; extra == 'ui'
|
|
18
17
|
Requires-Python: >=3.13
|
|
19
18
|
Provides-Extra: auth
|
|
20
19
|
Provides-Extra: docs
|
|
21
20
|
Provides-Extra: ratelimit
|
|
22
21
|
Provides-Extra: testing
|
|
23
|
-
Provides-Extra: ui
|
|
24
22
|
Description-Content-Type: text/markdown
|
|
25
23
|
|
|
26
24
|

|
|
@@ -40,28 +38,32 @@ Description-Content-Type: text/markdown
|
|
|
40
38
|
## 1. Overview & Capabilities
|
|
41
39
|
|
|
42
40
|
- **Automatic CQRS Routing & Feature Gating**: Map HTTP endpoints directly to `CommandBusPort` or `QueryBusPort` using `@api_command` and `@api_query`, with native feature flag route guards (`@feature_flag_route` and `require_feature(...)`).
|
|
41
|
+
- **Server-Sent Events (SSE) Real-Time Streaming**: Native `ServerSentEvent` formatting and `EventSourceResponse` supporting chunked transfer encoding, keep-alive heartbeat pings, and direct streaming queries on `CqrsRouter.add_streaming_query`.
|
|
42
|
+
- **WebSockets Real-Time Channel Manager & CQRS Bridge**: `WebSocketConnectionManager` managing connection pools, room/channel groupings, and broadcasts, alongside `WebSocketCqrsBridge` mapping incoming socket JSON actions into CQRS Commands and Queries.
|
|
43
43
|
- **Decoupled Database Session Middleware**: `DbSessionMiddleware` (sync) and `AsyncDbSessionMiddleware` (async) manage session-per-request lifecycles by consuming sessionmakers from DI without a hard dependency on `hexastack-db`.
|
|
44
44
|
- **Standardized Exception Handlers**: Automatically translates domain exceptions (`EntityNotFoundError`, `UniqueConstraintViolationError`, `HexastackError`) into appropriate HTTP status codes (404, 409, 500) and structured JSON error envelopes.
|
|
45
|
-
- **Observability & Correlation Middleware**: Injects `X-Correlation-ID` headers
|
|
45
|
+
- **Observability & Correlation Middleware**: Injects `X-Correlation-ID` headers, logs HTTP request lifecycles, and captures HTTP RED metrics (`HttpMetricsMiddleware`) exposing a standard `/metrics` endpoint.
|
|
46
46
|
- **Built-in Health Checks**: Configurable `/health` and `/ready` endpoints verifying container and subsystem readiness.
|
|
47
47
|
- **NiceGUI Reactive UI & DevTools Dashboard**: Optional `[ui]` presentation adapter providing `ui_page`, `dispatch_command`, `dispatch_query`, and a zero-config interactive developer console (`mount_devtools_dashboard`) for inspecting CQRS buses, feature flags, and DI services.
|
|
48
48
|
|
|
49
|
+
|
|
49
50
|
---
|
|
50
51
|
|
|
51
52
|
## 2. Package Anatomy & Key Components
|
|
52
53
|
|
|
53
54
|
```
|
|
54
55
|
hexastack_fastapi/
|
|
55
|
-
├── domain/ # HealthStatus, HTTP error envelope models
|
|
56
|
-
├── adapters/ #
|
|
57
|
-
└── infra/ # FastApiBootstrapper (order=30), exception handlers, correlation/logging middlewares
|
|
56
|
+
├── domain/ # HealthStatus, HTTP error envelope models, HexastackFastApiConfig
|
|
57
|
+
├── adapters/ # routing decorators, SSE streaming, WebSockets, health endpoints, db_session middleware, dependencies, NiceGUI UI adapter
|
|
58
|
+
└── infra/ # create_fastapi_app, FastApiBootstrapper (order=30), exception handlers, correlation/logging middlewares
|
|
58
59
|
```
|
|
59
60
|
|
|
61
|
+
|
|
60
62
|
### Key Exports
|
|
61
63
|
|
|
62
64
|
| Category | Exports |
|
|
63
65
|
|---|---|
|
|
64
|
-
| **Application Factory** | `
|
|
66
|
+
| **Application Factory** | `create_fastapi_app`, `FastApiBootstrapper` (order=30) |
|
|
65
67
|
| **Decorators** | `@api_command`, `@api_query`, `@feature_flag_route`, `@rate_limit`, `RouteMetadata` |
|
|
66
68
|
| **Dependencies** | `get_container`, `get_pipeline`, `get_feature_flags`, `require_feature`, `get_rate_limiter`, `require_rate_limit` |
|
|
67
69
|
| **Documentation Mounting** | `mount_zensical_docs`, `DocumentationNotFoundError`, `ZensicalDocsConfig` |
|
|
@@ -69,12 +71,13 @@ hexastack_fastapi/
|
|
|
69
71
|
| **Middlewares** | `DbSessionMiddleware`, `AsyncDbSessionMiddleware`, `add_db_session_middleware`, `CorrelationMiddleware`, `HttpLoggingMiddleware` |
|
|
70
72
|
| **Rate Limiting** | `SlowapiRateLimiterAdapter`, `get_remote_address`, `get_user_or_ip_key`, `RateLimitConfig` |
|
|
71
73
|
| **Reactive UI (NiceGUI)** | `dispatch_command`, `dispatch_query`, `mount_devtools_dashboard`, `mount_ui_app`, `ui_page` |
|
|
74
|
+
| **Real-Time Streaming (SSE & WebSockets)** | `EventSourceResponse`, `ServerSentEvent`, `WebSocketConnectionManager`, `WebSocketCqrsBridge` |
|
|
72
75
|
| **Routing** | `CqrsRouter`, `autodiscover_routes` |
|
|
73
76
|
| **Testing, E2E & Demo Narration** | `create_test_client`, `check_openapi_conformance`, `EphemeralServer`, `ephemeral_server`, `find_free_port`, `smart_click`, `DemoNarrator`, `VIRTUAL_CURSOR_SCRIPT` |
|
|
74
77
|
|
|
75
|
-
|
|
76
78
|
---
|
|
77
79
|
|
|
80
|
+
|
|
78
81
|
## 3. Monorepo & Sibling Relationships
|
|
79
82
|
|
|
80
83
|
```mermaid
|
|
@@ -15,28 +15,32 @@
|
|
|
15
15
|
## 1. Overview & Capabilities
|
|
16
16
|
|
|
17
17
|
- **Automatic CQRS Routing & Feature Gating**: Map HTTP endpoints directly to `CommandBusPort` or `QueryBusPort` using `@api_command` and `@api_query`, with native feature flag route guards (`@feature_flag_route` and `require_feature(...)`).
|
|
18
|
+
- **Server-Sent Events (SSE) Real-Time Streaming**: Native `ServerSentEvent` formatting and `EventSourceResponse` supporting chunked transfer encoding, keep-alive heartbeat pings, and direct streaming queries on `CqrsRouter.add_streaming_query`.
|
|
19
|
+
- **WebSockets Real-Time Channel Manager & CQRS Bridge**: `WebSocketConnectionManager` managing connection pools, room/channel groupings, and broadcasts, alongside `WebSocketCqrsBridge` mapping incoming socket JSON actions into CQRS Commands and Queries.
|
|
18
20
|
- **Decoupled Database Session Middleware**: `DbSessionMiddleware` (sync) and `AsyncDbSessionMiddleware` (async) manage session-per-request lifecycles by consuming sessionmakers from DI without a hard dependency on `hexastack-db`.
|
|
19
21
|
- **Standardized Exception Handlers**: Automatically translates domain exceptions (`EntityNotFoundError`, `UniqueConstraintViolationError`, `HexastackError`) into appropriate HTTP status codes (404, 409, 500) and structured JSON error envelopes.
|
|
20
|
-
- **Observability & Correlation Middleware**: Injects `X-Correlation-ID` headers
|
|
22
|
+
- **Observability & Correlation Middleware**: Injects `X-Correlation-ID` headers, logs HTTP request lifecycles, and captures HTTP RED metrics (`HttpMetricsMiddleware`) exposing a standard `/metrics` endpoint.
|
|
21
23
|
- **Built-in Health Checks**: Configurable `/health` and `/ready` endpoints verifying container and subsystem readiness.
|
|
22
24
|
- **NiceGUI Reactive UI & DevTools Dashboard**: Optional `[ui]` presentation adapter providing `ui_page`, `dispatch_command`, `dispatch_query`, and a zero-config interactive developer console (`mount_devtools_dashboard`) for inspecting CQRS buses, feature flags, and DI services.
|
|
23
25
|
|
|
26
|
+
|
|
24
27
|
---
|
|
25
28
|
|
|
26
29
|
## 2. Package Anatomy & Key Components
|
|
27
30
|
|
|
28
31
|
```
|
|
29
32
|
hexastack_fastapi/
|
|
30
|
-
├── domain/ # HealthStatus, HTTP error envelope models
|
|
31
|
-
├── adapters/ #
|
|
32
|
-
└── infra/ # FastApiBootstrapper (order=30), exception handlers, correlation/logging middlewares
|
|
33
|
+
├── domain/ # HealthStatus, HTTP error envelope models, HexastackFastApiConfig
|
|
34
|
+
├── adapters/ # routing decorators, SSE streaming, WebSockets, health endpoints, db_session middleware, dependencies, NiceGUI UI adapter
|
|
35
|
+
└── infra/ # create_fastapi_app, FastApiBootstrapper (order=30), exception handlers, correlation/logging middlewares
|
|
33
36
|
```
|
|
34
37
|
|
|
38
|
+
|
|
35
39
|
### Key Exports
|
|
36
40
|
|
|
37
41
|
| Category | Exports |
|
|
38
42
|
|---|---|
|
|
39
|
-
| **Application Factory** | `
|
|
43
|
+
| **Application Factory** | `create_fastapi_app`, `FastApiBootstrapper` (order=30) |
|
|
40
44
|
| **Decorators** | `@api_command`, `@api_query`, `@feature_flag_route`, `@rate_limit`, `RouteMetadata` |
|
|
41
45
|
| **Dependencies** | `get_container`, `get_pipeline`, `get_feature_flags`, `require_feature`, `get_rate_limiter`, `require_rate_limit` |
|
|
42
46
|
| **Documentation Mounting** | `mount_zensical_docs`, `DocumentationNotFoundError`, `ZensicalDocsConfig` |
|
|
@@ -44,12 +48,13 @@ hexastack_fastapi/
|
|
|
44
48
|
| **Middlewares** | `DbSessionMiddleware`, `AsyncDbSessionMiddleware`, `add_db_session_middleware`, `CorrelationMiddleware`, `HttpLoggingMiddleware` |
|
|
45
49
|
| **Rate Limiting** | `SlowapiRateLimiterAdapter`, `get_remote_address`, `get_user_or_ip_key`, `RateLimitConfig` |
|
|
46
50
|
| **Reactive UI (NiceGUI)** | `dispatch_command`, `dispatch_query`, `mount_devtools_dashboard`, `mount_ui_app`, `ui_page` |
|
|
51
|
+
| **Real-Time Streaming (SSE & WebSockets)** | `EventSourceResponse`, `ServerSentEvent`, `WebSocketConnectionManager`, `WebSocketCqrsBridge` |
|
|
47
52
|
| **Routing** | `CqrsRouter`, `autodiscover_routes` |
|
|
48
53
|
| **Testing, E2E & Demo Narration** | `create_test_client`, `check_openapi_conformance`, `EphemeralServer`, `ephemeral_server`, `find_free_port`, `smart_click`, `DemoNarrator`, `VIRTUAL_CURSOR_SCRIPT` |
|
|
49
54
|
|
|
50
|
-
|
|
51
55
|
---
|
|
52
56
|
|
|
57
|
+
|
|
53
58
|
## 3. Monorepo & Sibling Relationships
|
|
54
59
|
|
|
55
60
|
```mermaid
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "hexastack-fastapi"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.4.0"
|
|
4
4
|
description = "FastAPI presentation adapter for Hexastack"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = "Apache-2.0"
|
|
@@ -19,14 +19,13 @@ email = "dopplereffect.us@gmail.com"
|
|
|
19
19
|
auth = ["hexastack-auth"]
|
|
20
20
|
docs = ["zensical>=0.0.56"]
|
|
21
21
|
ratelimit = [
|
|
22
|
-
"limits>=
|
|
23
|
-
"slowapi>=0.1.
|
|
22
|
+
"limits>=5.8.0",
|
|
23
|
+
"slowapi>=0.1.10",
|
|
24
24
|
]
|
|
25
25
|
testing = [
|
|
26
26
|
"playwright>=1.49.0",
|
|
27
27
|
"schemathesis>=3.39.0",
|
|
28
28
|
]
|
|
29
|
-
ui = ["nicegui>=2.0.0"]
|
|
30
29
|
|
|
31
30
|
[project.entry-points."hexastack.bootstrappers"]
|
|
32
31
|
fastapi = "hexastack_fastapi.infra.bootstrap:FastApiBootstrapper"
|
|
@@ -52,8 +51,19 @@ name = "Hexagonal architecture layer hierarchy"
|
|
|
52
51
|
type = "layers"
|
|
53
52
|
containers = ["hexastack_fastapi"]
|
|
54
53
|
layers = [
|
|
55
|
-
"adapters",
|
|
56
54
|
"infra",
|
|
55
|
+
"adapters",
|
|
56
|
+
"domain",
|
|
57
|
+
]
|
|
58
|
+
|
|
59
|
+
[[tool.importlinter.contracts]]
|
|
60
|
+
name = "Forbidden imports for domain"
|
|
61
|
+
type = "forbidden"
|
|
62
|
+
source_modules = ["hexastack_fastapi.domain"]
|
|
63
|
+
forbidden_modules = [
|
|
64
|
+
"hexastack_fastapi.adapters",
|
|
65
|
+
"hexastack_fastapi.infra",
|
|
66
|
+
"hexastack_fastapi.testing",
|
|
57
67
|
]
|
|
58
68
|
|
|
59
69
|
[[tool.importlinter.contracts]]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "hexastack-fastapi"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.4.0"
|
|
4
4
|
description = "FastAPI presentation adapter for Hexastack"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = "Apache-2.0"
|
|
@@ -22,17 +22,13 @@ docs = [
|
|
|
22
22
|
"zensical>=0.0.56",
|
|
23
23
|
]
|
|
24
24
|
ratelimit = [
|
|
25
|
-
"limits>=
|
|
26
|
-
"slowapi>=0.1.
|
|
25
|
+
"limits>=5.8.0",
|
|
26
|
+
"slowapi>=0.1.10",
|
|
27
27
|
]
|
|
28
28
|
testing = [
|
|
29
29
|
"playwright>=1.49.0",
|
|
30
30
|
"schemathesis>=3.39.0",
|
|
31
31
|
]
|
|
32
|
-
ui = [
|
|
33
|
-
"nicegui>=2.0.0",
|
|
34
|
-
]
|
|
35
|
-
|
|
36
32
|
|
|
37
33
|
[project.entry-points."hexastack.bootstrappers"]
|
|
38
34
|
fastapi = "hexastack_fastapi.infra.bootstrap:FastApiBootstrapper"
|
|
@@ -54,8 +50,19 @@ name = "Hexagonal architecture layer hierarchy"
|
|
|
54
50
|
type = "layers"
|
|
55
51
|
containers = ["hexastack_fastapi"]
|
|
56
52
|
layers = [
|
|
57
|
-
"adapters",
|
|
58
53
|
"infra",
|
|
54
|
+
"adapters",
|
|
55
|
+
"domain",
|
|
56
|
+
]
|
|
57
|
+
|
|
58
|
+
[[tool.importlinter.contracts]]
|
|
59
|
+
name = "Forbidden imports for domain"
|
|
60
|
+
type = "forbidden"
|
|
61
|
+
source_modules = ["hexastack_fastapi.domain"]
|
|
62
|
+
forbidden_modules = [
|
|
63
|
+
"hexastack_fastapi.adapters",
|
|
64
|
+
"hexastack_fastapi.infra",
|
|
65
|
+
"hexastack_fastapi.testing",
|
|
59
66
|
]
|
|
60
67
|
|
|
61
68
|
[[tool.importlinter.contracts]]
|
{hexastack_fastapi-0.2.0 → hexastack_fastapi-0.4.0}/src/hexastack_fastapi/adapters/__init__.py
RENAMED
|
@@ -13,6 +13,7 @@ from hexastack_fastapi.adapters.docs import (
|
|
|
13
13
|
mount_zensical_docs,
|
|
14
14
|
)
|
|
15
15
|
from hexastack_fastapi.adapters.health import create_health_router
|
|
16
|
+
from hexastack_fastapi.adapters.metrics import create_metrics_router
|
|
16
17
|
from hexastack_fastapi.adapters.ratelimit import (
|
|
17
18
|
SlowapiRateLimiterAdapter,
|
|
18
19
|
get_remote_address,
|
|
@@ -20,35 +21,35 @@ from hexastack_fastapi.adapters.ratelimit import (
|
|
|
20
21
|
rate_limit,
|
|
21
22
|
)
|
|
22
23
|
from hexastack_fastapi.adapters.routing import CqrsRouter
|
|
23
|
-
from hexastack_fastapi.adapters.
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
24
|
+
from hexastack_fastapi.adapters.sse import (
|
|
25
|
+
EventSourceResponse,
|
|
26
|
+
ServerSentEvent,
|
|
27
|
+
)
|
|
28
|
+
from hexastack_fastapi.adapters.websockets import (
|
|
29
|
+
WebSocketConnectionManager,
|
|
30
|
+
WebSocketCqrsBridge,
|
|
29
31
|
)
|
|
30
32
|
|
|
31
33
|
__all__ = [
|
|
32
34
|
"check_openapi_conformance",
|
|
33
35
|
"CqrsRouter",
|
|
34
|
-
"create_fastapi_app",
|
|
35
36
|
"create_health_router",
|
|
37
|
+
"create_metrics_router",
|
|
36
38
|
"create_test_client",
|
|
37
|
-
"dispatch_command",
|
|
38
|
-
"dispatch_query",
|
|
39
39
|
"DocumentationNotFoundError",
|
|
40
|
+
"EventSourceResponse",
|
|
40
41
|
"get_container",
|
|
41
42
|
"get_feature_flags",
|
|
42
43
|
"get_pipeline",
|
|
43
44
|
"get_rate_limiter",
|
|
44
45
|
"get_remote_address",
|
|
45
46
|
"get_user_or_ip_key",
|
|
46
|
-
"mount_devtools_dashboard",
|
|
47
|
-
"mount_ui_app",
|
|
48
47
|
"mount_zensical_docs",
|
|
49
48
|
"rate_limit",
|
|
50
49
|
"require_feature",
|
|
51
50
|
"require_rate_limit",
|
|
51
|
+
"ServerSentEvent",
|
|
52
52
|
"SlowapiRateLimiterAdapter",
|
|
53
|
-
"
|
|
53
|
+
"WebSocketConnectionManager",
|
|
54
|
+
"WebSocketCqrsBridge",
|
|
54
55
|
]
|
{hexastack_fastapi-0.2.0 → hexastack_fastapi-0.4.0}/src/hexastack_fastapi/adapters/health.py
RENAMED
|
@@ -6,7 +6,6 @@ from fastapi.responses import JSONResponse
|
|
|
6
6
|
from rodi import Container
|
|
7
7
|
|
|
8
8
|
from hexastack_core.utils.context import get_correlation_id
|
|
9
|
-
from hexastack_fastapi.infra.config import HealthConfig
|
|
10
9
|
|
|
11
10
|
__all__ = [
|
|
12
11
|
"create_health_router",
|
|
@@ -15,17 +14,22 @@ __all__ = [
|
|
|
15
14
|
|
|
16
15
|
def create_health_router(
|
|
17
16
|
container: Container | None = None,
|
|
18
|
-
config:
|
|
17
|
+
config: Any | None = None,
|
|
18
|
+
health_path: str = "/health",
|
|
19
|
+
ready_path: str = "/ready",
|
|
19
20
|
) -> APIRouter:
|
|
20
21
|
"""Create APIRouter with standard liveness and readiness probe endpoints.
|
|
21
22
|
|
|
22
23
|
Notes/Architectural Intent:
|
|
23
24
|
Exposes lightweight /health (liveness) and /ready (readiness) endpoints for orchestration
|
|
24
|
-
platforms like Kubernetes and container engines.
|
|
25
|
+
platforms like Kubernetes and container engines. Decoupled from infra configuration schemas
|
|
26
|
+
by accepting primitives or duck-typed config objects.
|
|
25
27
|
|
|
26
28
|
Args:
|
|
27
29
|
container: Optional rodi Container instance to check dependency health.
|
|
28
|
-
config: Optional
|
|
30
|
+
config: Optional configuration object specifying custom URL paths (e.g. HealthConfig).
|
|
31
|
+
health_path: Default liveness probe path (used if config not provided).
|
|
32
|
+
ready_path: Default readiness probe path (used if config not provided).
|
|
29
33
|
|
|
30
34
|
Returns:
|
|
31
35
|
APIRouter mounted with health endpoints.
|
|
@@ -33,10 +37,19 @@ def create_health_router(
|
|
|
33
37
|
Raises:
|
|
34
38
|
None.
|
|
35
39
|
"""
|
|
36
|
-
|
|
40
|
+
resolved_health_path = (
|
|
41
|
+
getattr(config, "health_path", None) or health_path
|
|
42
|
+
if config is not None
|
|
43
|
+
else health_path
|
|
44
|
+
)
|
|
45
|
+
resolved_ready_path = (
|
|
46
|
+
getattr(config, "ready_path", None) or ready_path
|
|
47
|
+
if config is not None
|
|
48
|
+
else ready_path
|
|
49
|
+
)
|
|
37
50
|
router = APIRouter(tags=["Health"])
|
|
38
51
|
|
|
39
|
-
@router.get(
|
|
52
|
+
@router.get(resolved_health_path, summary="Liveness Probe")
|
|
40
53
|
async def liveness() -> dict[str, Any]:
|
|
41
54
|
return {
|
|
42
55
|
"status": "ok",
|
|
@@ -44,7 +57,7 @@ def create_health_router(
|
|
|
44
57
|
"correlation_id": get_correlation_id(),
|
|
45
58
|
}
|
|
46
59
|
|
|
47
|
-
@router.get(
|
|
60
|
+
@router.get(resolved_ready_path, summary="Readiness Probe")
|
|
48
61
|
async def readiness() -> JSONResponse:
|
|
49
62
|
checks: dict[str, str] = {
|
|
50
63
|
"container": "ok" if container is not None else "unconfigured",
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""Prometheus Metrics Endpoint Router for FastAPI."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from fastapi import APIRouter, Response
|
|
6
|
+
from rodi import Container
|
|
7
|
+
|
|
8
|
+
from hexastack_core.ports.metrics import MetricsPort
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def create_metrics_router(container: Container | None = None) -> APIRouter:
|
|
12
|
+
"""Create a FastAPI APIRouter mounting the /metrics Prometheus scraper endpoint.
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
container: Active rodi Container to resolve MetricsPort.
|
|
16
|
+
|
|
17
|
+
Returns:
|
|
18
|
+
APIRouter exposing GET /metrics.
|
|
19
|
+
"""
|
|
20
|
+
router = APIRouter(tags=["Observability"])
|
|
21
|
+
|
|
22
|
+
@router.get("/metrics", include_in_schema=False)
|
|
23
|
+
async def get_metrics() -> Response:
|
|
24
|
+
if container is not None and MetricsPort in container:
|
|
25
|
+
metrics_port = container.resolve(MetricsPort)
|
|
26
|
+
body = metrics_port.generate_metrics_text()
|
|
27
|
+
else:
|
|
28
|
+
try:
|
|
29
|
+
from prometheus_client import generate_latest
|
|
30
|
+
|
|
31
|
+
body = generate_latest()
|
|
32
|
+
except ImportError:
|
|
33
|
+
body = b"# Prometheus metrics not configured"
|
|
34
|
+
|
|
35
|
+
return Response(
|
|
36
|
+
content=body, media_type="text/plain; version=0.0.4; charset=utf-8"
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
return router
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
__all__ = [
|
|
43
|
+
"create_metrics_router",
|
|
44
|
+
]
|
{hexastack_fastapi-0.2.0 → hexastack_fastapi-0.4.0}/src/hexastack_fastapi/adapters/routing.py
RENAMED
|
@@ -8,6 +8,7 @@ from fastapi import APIRouter, Depends
|
|
|
8
8
|
from hexastack_core.domain import Command, Query
|
|
9
9
|
from hexastack_cqrs.infra.pipeline import ExecutionPipeline
|
|
10
10
|
from hexastack_fastapi.adapters.dependencies import get_pipeline
|
|
11
|
+
from hexastack_fastapi.adapters.sse import EventSourceResponse
|
|
11
12
|
|
|
12
13
|
|
|
13
14
|
class CqrsRouter(APIRouter):
|
|
@@ -90,7 +91,7 @@ class CqrsRouter(APIRouter):
|
|
|
90
91
|
Args:
|
|
91
92
|
path: URL path for the route.
|
|
92
93
|
query_cls: Pydantic Query model class.
|
|
93
|
-
method: HTTP method ('GET' or 'POST').
|
|
94
|
+
method: HTTP method (e.g., 'GET' or 'POST').
|
|
94
95
|
status_code: Success HTTP status code.
|
|
95
96
|
output_format: Optional presenter format (e.g., 'json').
|
|
96
97
|
summary: Optional OpenAPI summary string.
|
|
@@ -126,6 +127,55 @@ class CqrsRouter(APIRouter):
|
|
|
126
127
|
response_model=None,
|
|
127
128
|
)
|
|
128
129
|
|
|
130
|
+
def add_streaming_query[TQuery: Query](
|
|
131
|
+
self,
|
|
132
|
+
path: str,
|
|
133
|
+
query_cls: type[TQuery],
|
|
134
|
+
*,
|
|
135
|
+
method: str = "GET",
|
|
136
|
+
summary: str | None = None,
|
|
137
|
+
tags: list[str | Enum] | None = None,
|
|
138
|
+
feature_flag: str | None = None,
|
|
139
|
+
rate_limit: str | None = None,
|
|
140
|
+
ping_interval: float | None = None,
|
|
141
|
+
) -> None:
|
|
142
|
+
"""Register a Server-Sent Events (SSE) streaming endpoint for a Query model.
|
|
143
|
+
|
|
144
|
+
Args:
|
|
145
|
+
path: URL path for the route.
|
|
146
|
+
query_cls: Pydantic Query model class.
|
|
147
|
+
method: HTTP method (e.g. 'GET' or 'POST').
|
|
148
|
+
summary: Optional OpenAPI summary string.
|
|
149
|
+
tags: Optional OpenAPI tags list.
|
|
150
|
+
feature_flag: Optional feature flag key required for endpoint execution.
|
|
151
|
+
rate_limit: Optional rate limit string (e.g. '10/minute').
|
|
152
|
+
ping_interval: Optional keep-alive ping interval in seconds.
|
|
153
|
+
|
|
154
|
+
Returns:
|
|
155
|
+
None.
|
|
156
|
+
"""
|
|
157
|
+
endpoint = _create_streaming_query_endpoint(query_cls, method, ping_interval)
|
|
158
|
+
dependencies = []
|
|
159
|
+
if feature_flag:
|
|
160
|
+
from hexastack_fastapi.adapters.dependencies import require_feature
|
|
161
|
+
|
|
162
|
+
dependencies.append(Depends(require_feature(feature_flag)))
|
|
163
|
+
if rate_limit:
|
|
164
|
+
from hexastack_fastapi.adapters.dependencies import require_rate_limit
|
|
165
|
+
|
|
166
|
+
dependencies.append(Depends(require_rate_limit(rate_limit)))
|
|
167
|
+
|
|
168
|
+
self.add_api_route(
|
|
169
|
+
path=path,
|
|
170
|
+
endpoint=endpoint,
|
|
171
|
+
methods=[method.upper()],
|
|
172
|
+
status_code=200,
|
|
173
|
+
summary=summary or f"Stream {query_cls.__name__}",
|
|
174
|
+
tags=tags,
|
|
175
|
+
dependencies=dependencies if dependencies else None,
|
|
176
|
+
response_class=EventSourceResponse,
|
|
177
|
+
)
|
|
178
|
+
|
|
129
179
|
|
|
130
180
|
__all__ = [
|
|
131
181
|
"CqrsRouter",
|
|
@@ -213,3 +263,59 @@ def _create_query_endpoint(
|
|
|
213
263
|
]
|
|
214
264
|
cast("Any", post_endpoint).__signature__ = sig.replace(parameters=params)
|
|
215
265
|
return post_endpoint
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def _create_streaming_query_endpoint(
|
|
269
|
+
query_cls: type[Query], method: str, ping_interval: float | None
|
|
270
|
+
) -> Callable[..., Any]:
|
|
271
|
+
"""Dynamically construct an SSE streaming FastAPI endpoint for a Query model."""
|
|
272
|
+
if method.upper() == "GET":
|
|
273
|
+
|
|
274
|
+
async def get_stream_endpoint(
|
|
275
|
+
qry: Any,
|
|
276
|
+
pipeline: Annotated[ExecutionPipeline, Depends(get_pipeline)],
|
|
277
|
+
) -> EventSourceResponse:
|
|
278
|
+
gen = pipeline.execute(qry)
|
|
279
|
+
return EventSourceResponse(gen, ping_interval=ping_interval)
|
|
280
|
+
|
|
281
|
+
sig = inspect.signature(get_stream_endpoint)
|
|
282
|
+
params = [
|
|
283
|
+
inspect.Parameter(
|
|
284
|
+
"qry",
|
|
285
|
+
inspect.Parameter.POSITIONAL_OR_KEYWORD,
|
|
286
|
+
annotation=query_cls,
|
|
287
|
+
default=Depends(query_cls),
|
|
288
|
+
),
|
|
289
|
+
inspect.Parameter(
|
|
290
|
+
"pipeline",
|
|
291
|
+
inspect.Parameter.POSITIONAL_OR_KEYWORD,
|
|
292
|
+
annotation=ExecutionPipeline,
|
|
293
|
+
default=Depends(get_pipeline),
|
|
294
|
+
),
|
|
295
|
+
]
|
|
296
|
+
cast("Any", get_stream_endpoint).__signature__ = sig.replace(parameters=params)
|
|
297
|
+
return get_stream_endpoint
|
|
298
|
+
|
|
299
|
+
async def post_stream_endpoint(
|
|
300
|
+
qry: Any,
|
|
301
|
+
pipeline: Annotated[ExecutionPipeline, Depends(get_pipeline)],
|
|
302
|
+
) -> EventSourceResponse:
|
|
303
|
+
gen = pipeline.execute(qry)
|
|
304
|
+
return EventSourceResponse(gen, ping_interval=ping_interval)
|
|
305
|
+
|
|
306
|
+
sig = inspect.signature(post_stream_endpoint)
|
|
307
|
+
params = [
|
|
308
|
+
inspect.Parameter(
|
|
309
|
+
"qry",
|
|
310
|
+
inspect.Parameter.POSITIONAL_OR_KEYWORD,
|
|
311
|
+
annotation=query_cls,
|
|
312
|
+
),
|
|
313
|
+
inspect.Parameter(
|
|
314
|
+
"pipeline",
|
|
315
|
+
inspect.Parameter.POSITIONAL_OR_KEYWORD,
|
|
316
|
+
annotation=ExecutionPipeline,
|
|
317
|
+
default=Depends(get_pipeline),
|
|
318
|
+
),
|
|
319
|
+
]
|
|
320
|
+
cast("Any", post_stream_endpoint).__signature__ = sig.replace(parameters=params)
|
|
321
|
+
return post_stream_endpoint
|