cloud-dog-api-kit 0.13.0__py3-none-any.whl
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.
- cloud_dog_api_kit/__init__.py +170 -0
- cloud_dog_api_kit/a2a/__init__.py +53 -0
- cloud_dog_api_kit/a2a/card.py +138 -0
- cloud_dog_api_kit/a2a/events.py +1123 -0
- cloud_dog_api_kit/a2a/gateway.py +105 -0
- cloud_dog_api_kit/a2a/skill_audit.py +107 -0
- cloud_dog_api_kit/auth/__init__.py +35 -0
- cloud_dog_api_kit/auth/dependency.py +121 -0
- cloud_dog_api_kit/auth/rbac.py +107 -0
- cloud_dog_api_kit/auth/service_auth.py +54 -0
- cloud_dog_api_kit/clients/__init__.py +29 -0
- cloud_dog_api_kit/clients/circuit_breaker.py +39 -0
- cloud_dog_api_kit/clients/http_client.py +127 -0
- cloud_dog_api_kit/clients/retry.py +83 -0
- cloud_dog_api_kit/compat/__init__.py +37 -0
- cloud_dog_api_kit/compat/envelope.py +120 -0
- cloud_dog_api_kit/compat/profile.py +102 -0
- cloud_dog_api_kit/compat/routes.py +90 -0
- cloud_dog_api_kit/config.py +54 -0
- cloud_dog_api_kit/correlation/__init__.py +50 -0
- cloud_dog_api_kit/correlation/context.py +118 -0
- cloud_dog_api_kit/correlation/middleware.py +133 -0
- cloud_dog_api_kit/envelopes/__init__.py +37 -0
- cloud_dog_api_kit/envelopes/error.py +87 -0
- cloud_dog_api_kit/envelopes/success.py +84 -0
- cloud_dog_api_kit/errors/__init__.py +51 -0
- cloud_dog_api_kit/errors/exceptions.py +184 -0
- cloud_dog_api_kit/errors/handler.py +102 -0
- cloud_dog_api_kit/errors/taxonomy.py +62 -0
- cloud_dog_api_kit/factory.py +157 -0
- cloud_dog_api_kit/idempotency/__init__.py +28 -0
- cloud_dog_api_kit/idempotency/middleware.py +118 -0
- cloud_dog_api_kit/idempotency/store.py +100 -0
- cloud_dog_api_kit/lifecycle/__init__.py +39 -0
- cloud_dog_api_kit/lifecycle/hooks.py +75 -0
- cloud_dog_api_kit/lifecycle/shutdown.py +178 -0
- cloud_dog_api_kit/mcp/__init__.py +122 -0
- cloud_dog_api_kit/mcp/async_jobs.py +126 -0
- cloud_dog_api_kit/mcp/client_sdk.py +235 -0
- cloud_dog_api_kit/mcp/client_transport/__init__.py +47 -0
- cloud_dog_api_kit/mcp/client_transport/base.py +98 -0
- cloud_dog_api_kit/mcp/client_transport/exceptions.py +37 -0
- cloud_dog_api_kit/mcp/client_transport/http_jsonrpc.py +405 -0
- cloud_dog_api_kit/mcp/client_transport/legacy_sse.py +320 -0
- cloud_dog_api_kit/mcp/client_transport/stdio.py +322 -0
- cloud_dog_api_kit/mcp/client_transport/streamable_http.py +748 -0
- cloud_dog_api_kit/mcp/contract.py +113 -0
- cloud_dog_api_kit/mcp/error_mapper.py +84 -0
- cloud_dog_api_kit/mcp/gateway.py +117 -0
- cloud_dog_api_kit/mcp/legacy_sse.py +129 -0
- cloud_dog_api_kit/mcp/session.py +96 -0
- cloud_dog_api_kit/mcp/sync_handler.py +269 -0
- cloud_dog_api_kit/mcp/tool_audit.py +136 -0
- cloud_dog_api_kit/mcp/tool_router.py +180 -0
- cloud_dog_api_kit/mcp/transport.py +1041 -0
- cloud_dog_api_kit/middleware/__init__.py +39 -0
- cloud_dog_api_kit/middleware/cors.py +74 -0
- cloud_dog_api_kit/middleware/logging.py +98 -0
- cloud_dog_api_kit/middleware/request_size_limit.py +86 -0
- cloud_dog_api_kit/middleware/timeout.py +78 -0
- cloud_dog_api_kit/middleware/timing.py +52 -0
- cloud_dog_api_kit/openapi/__init__.py +30 -0
- cloud_dog_api_kit/openapi/customise.py +69 -0
- cloud_dog_api_kit/openapi/route.py +46 -0
- cloud_dog_api_kit/routers/__init__.py +41 -0
- cloud_dog_api_kit/routers/crud.py +173 -0
- cloud_dog_api_kit/routers/health.py +160 -0
- cloud_dog_api_kit/routers/jobs.py +69 -0
- cloud_dog_api_kit/routers/version.py +46 -0
- cloud_dog_api_kit/schemas/__init__.py +36 -0
- cloud_dog_api_kit/schemas/envelopes.py +37 -0
- cloud_dog_api_kit/schemas/filters.py +103 -0
- cloud_dog_api_kit/schemas/pagination.py +148 -0
- cloud_dog_api_kit/streaming/__init__.py +28 -0
- cloud_dog_api_kit/streaming/events.py +47 -0
- cloud_dog_api_kit/streaming/jsonl.py +68 -0
- cloud_dog_api_kit/streaming/sse.py +102 -0
- cloud_dog_api_kit/testing/__init__.py +46 -0
- cloud_dog_api_kit/testing/conformance.py +156 -0
- cloud_dog_api_kit/testing/fixtures.py +90 -0
- cloud_dog_api_kit/testing/flows/__init__.py +32 -0
- cloud_dog_api_kit/testing/flows/auth_flow.py +41 -0
- cloud_dog_api_kit/testing/flows/crud_flow.py +50 -0
- cloud_dog_api_kit/testing/flows/job_flow.py +42 -0
- cloud_dog_api_kit/testing/flows/streaming_flow.py +42 -0
- cloud_dog_api_kit/traceability_ids.py +84 -0
- cloud_dog_api_kit/versioning/__init__.py +30 -0
- cloud_dog_api_kit/versioning/header.py +52 -0
- cloud_dog_api_kit/web/__init__.py +7 -0
- cloud_dog_api_kit/web/proxy.py +222 -0
- cloud_dog_api_kit/webhook/__init__.py +29 -0
- cloud_dog_api_kit/webhook/signature.py +149 -0
- cloud_dog_api_kit-0.13.0.dist-info/METADATA +27 -0
- cloud_dog_api_kit-0.13.0.dist-info/RECORD +98 -0
- cloud_dog_api_kit-0.13.0.dist-info/WHEEL +4 -0
- cloud_dog_api_kit-0.13.0.dist-info/licenses/LICENCE +190 -0
- cloud_dog_api_kit-0.13.0.dist-info/licenses/LICENSE +176 -0
- cloud_dog_api_kit-0.13.0.dist-info/licenses/NOTICE +7 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Copyright 2026 Cloud-Dog, Viewdeck Engineering Limited
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
# cloud_dog_api_kit — Middleware components
|
|
16
|
+
#
|
|
17
|
+
# Licence: Proprietary — Cloud-Dog AI Platform
|
|
18
|
+
# Owner: Cloud-Dog AI
|
|
19
|
+
# Description: Standard middleware components (logging, CORS, timing, timeout).
|
|
20
|
+
# Related requirements: FR11.1, FR12.1, FR13.1
|
|
21
|
+
# Related architecture: SA1
|
|
22
|
+
|
|
23
|
+
"""Middleware components for cloud_dog_api_kit."""
|
|
24
|
+
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
from cloud_dog_api_kit.middleware.cors import configure_cors
|
|
28
|
+
from cloud_dog_api_kit.middleware.logging import RequestLoggingMiddleware
|
|
29
|
+
from cloud_dog_api_kit.middleware.request_size_limit import RequestSizeLimitMiddleware
|
|
30
|
+
from cloud_dog_api_kit.middleware.timing import TimingMiddleware
|
|
31
|
+
from cloud_dog_api_kit.middleware.timeout import TimeoutMiddleware
|
|
32
|
+
|
|
33
|
+
__all__ = [
|
|
34
|
+
"RequestLoggingMiddleware",
|
|
35
|
+
"RequestSizeLimitMiddleware",
|
|
36
|
+
"TimingMiddleware",
|
|
37
|
+
"TimeoutMiddleware",
|
|
38
|
+
"configure_cors",
|
|
39
|
+
]
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# Copyright 2026 Cloud-Dog, Viewdeck Engineering Limited
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
# cloud_dog_api_kit — CORS configuration helper
|
|
16
|
+
#
|
|
17
|
+
# Licence: Proprietary — Cloud-Dog AI Platform
|
|
18
|
+
# Owner: Cloud-Dog AI
|
|
19
|
+
# Description: CORS middleware configuration with production-safe defaults.
|
|
20
|
+
# Does not use wildcard origins unless explicitly configured.
|
|
21
|
+
# Related requirements: FR11.1, CS1.4
|
|
22
|
+
# Related architecture: CC1.12
|
|
23
|
+
|
|
24
|
+
"""CORS configuration helper for cloud_dog_api_kit."""
|
|
25
|
+
|
|
26
|
+
from __future__ import annotations
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
from fastapi import FastAPI
|
|
30
|
+
from starlette.middleware.cors import CORSMiddleware
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def configure_cors(
|
|
34
|
+
app: FastAPI,
|
|
35
|
+
allowed_origins: list[str] | None = None,
|
|
36
|
+
allow_credentials: bool = True,
|
|
37
|
+
allow_methods: list[str] | None = None,
|
|
38
|
+
allow_headers: list[str] | None = None,
|
|
39
|
+
) -> None:
|
|
40
|
+
"""Configure CORS middleware on a FastAPI application.
|
|
41
|
+
|
|
42
|
+
Production-safe defaults: explicit origins only, not wildcard ``*``
|
|
43
|
+
unless explicitly provided.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
app: The FastAPI application.
|
|
47
|
+
allowed_origins: List of allowed origins. Defaults to empty (no CORS).
|
|
48
|
+
allow_credentials: Whether to allow credentials. Defaults to True.
|
|
49
|
+
allow_methods: Allowed HTTP methods. Defaults to standard set.
|
|
50
|
+
allow_headers: Allowed headers. Defaults to standard set.
|
|
51
|
+
|
|
52
|
+
Related tests: UT1.25_CORSHelper
|
|
53
|
+
"""
|
|
54
|
+
origins = allowed_origins or []
|
|
55
|
+
methods = allow_methods or ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"]
|
|
56
|
+
headers = allow_headers or [
|
|
57
|
+
"Authorization",
|
|
58
|
+
"X-API-Key",
|
|
59
|
+
"X-Request-Id",
|
|
60
|
+
"X-Correlation-Id",
|
|
61
|
+
"X-App-Id",
|
|
62
|
+
"X-Host-Id",
|
|
63
|
+
"Content-Type",
|
|
64
|
+
"Accept",
|
|
65
|
+
"Idempotency-Key",
|
|
66
|
+
]
|
|
67
|
+
|
|
68
|
+
app.add_middleware(
|
|
69
|
+
CORSMiddleware,
|
|
70
|
+
allow_origins=origins,
|
|
71
|
+
allow_credentials=allow_credentials,
|
|
72
|
+
allow_methods=methods,
|
|
73
|
+
allow_headers=headers,
|
|
74
|
+
)
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# Copyright 2026 Cloud-Dog, Viewdeck Engineering Limited
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
# cloud_dog_api_kit — Request/response logging middleware
|
|
16
|
+
#
|
|
17
|
+
# Licence: Proprietary — Cloud-Dog AI Platform
|
|
18
|
+
# Owner: Cloud-Dog AI
|
|
19
|
+
# Description: Middleware that logs request/response details with header
|
|
20
|
+
# redaction for sensitive values (Authorization, X-API-Key, Cookie).
|
|
21
|
+
# Related requirements: FR12.1, CS1.2, CS1.6
|
|
22
|
+
# Related architecture: CC1.13
|
|
23
|
+
|
|
24
|
+
"""Request/response logging middleware for cloud_dog_api_kit."""
|
|
25
|
+
|
|
26
|
+
from __future__ import annotations
|
|
27
|
+
|
|
28
|
+
import logging
|
|
29
|
+
import time
|
|
30
|
+
from typing import Callable
|
|
31
|
+
|
|
32
|
+
from starlette.middleware.base import BaseHTTPMiddleware
|
|
33
|
+
from starlette.requests import Request
|
|
34
|
+
from starlette.responses import Response
|
|
35
|
+
|
|
36
|
+
REDACTED_HEADERS = frozenset({"authorization", "x-api-key", "cookie"})
|
|
37
|
+
|
|
38
|
+
logger = logging.getLogger("cloud_dog_api_kit.request_logging")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class RequestLoggingMiddleware(BaseHTTPMiddleware):
|
|
42
|
+
"""Request/response logging middleware with header redaction.
|
|
43
|
+
|
|
44
|
+
Logs: request_id, correlation_id, user/system identity, method, path,
|
|
45
|
+
status code, duration (ms), client IP. Redacts sensitive headers.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
app: The ASGI application.
|
|
49
|
+
|
|
50
|
+
Related tests: UT1.26_RequestLogging, SEC1.5_SecretRedaction
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
async def dispatch(self, request: Request, call_next: Callable) -> Response:
|
|
54
|
+
"""Log request and response details.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
request: The incoming HTTP request.
|
|
58
|
+
call_next: The next middleware or handler.
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
The HTTP response.
|
|
62
|
+
"""
|
|
63
|
+
request_id = getattr(request.state, "request_id", "")
|
|
64
|
+
correlation_id = getattr(request.state, "correlation_id", None)
|
|
65
|
+
user = getattr(request.state, "user", None)
|
|
66
|
+
client_ip = request.client.host if request.client else "unknown"
|
|
67
|
+
|
|
68
|
+
logger.info(
|
|
69
|
+
"Request started",
|
|
70
|
+
extra={
|
|
71
|
+
"request_id": request_id,
|
|
72
|
+
"correlation_id": correlation_id,
|
|
73
|
+
"user": user,
|
|
74
|
+
"method": request.method,
|
|
75
|
+
"path": request.url.path,
|
|
76
|
+
"client_ip": client_ip,
|
|
77
|
+
},
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
start_time = time.monotonic()
|
|
81
|
+
response = await call_next(request)
|
|
82
|
+
duration_ms = round((time.monotonic() - start_time) * 1000, 2)
|
|
83
|
+
|
|
84
|
+
logger.info(
|
|
85
|
+
"Request completed",
|
|
86
|
+
extra={
|
|
87
|
+
"request_id": request_id,
|
|
88
|
+
"correlation_id": correlation_id,
|
|
89
|
+
"user": user,
|
|
90
|
+
"method": request.method,
|
|
91
|
+
"path": request.url.path,
|
|
92
|
+
"status_code": response.status_code,
|
|
93
|
+
"duration_ms": duration_ms,
|
|
94
|
+
"client_ip": client_ip,
|
|
95
|
+
},
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
return response
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# Copyright 2026 Cloud-Dog, Viewdeck Engineering Limited
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
# cloud_dog_api_kit — Request size limit middleware
|
|
16
|
+
#
|
|
17
|
+
# Licence: Proprietary — Cloud-Dog AI Platform
|
|
18
|
+
# Owner: Cloud-Dog AI
|
|
19
|
+
# Description: Middleware that enforces a maximum request body size and
|
|
20
|
+
# returns a standard envelope with HTTP 413 when exceeded.
|
|
21
|
+
# Related requirements: FR18.8
|
|
22
|
+
# Related architecture: SA1
|
|
23
|
+
|
|
24
|
+
"""Request body size limit middleware for cloud_dog_api_kit."""
|
|
25
|
+
|
|
26
|
+
from __future__ import annotations
|
|
27
|
+
|
|
28
|
+
from typing import Any, Callable
|
|
29
|
+
|
|
30
|
+
from starlette.middleware.base import BaseHTTPMiddleware
|
|
31
|
+
from starlette.requests import Request
|
|
32
|
+
from starlette.responses import JSONResponse, Response
|
|
33
|
+
|
|
34
|
+
from cloud_dog_api_kit.envelopes.error import error_envelope
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class RequestSizeLimitMiddleware(BaseHTTPMiddleware):
|
|
38
|
+
"""Enforce a maximum request body size.
|
|
39
|
+
|
|
40
|
+
The middleware checks `Content-Length` when available and validates the
|
|
41
|
+
effective byte size by reading the request body when needed.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
app: The ASGI application.
|
|
45
|
+
max_bytes: Maximum allowed body size in bytes.
|
|
46
|
+
|
|
47
|
+
Related tests: UT1.44_RequestSizeLimit
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
def __init__(self, app: Any, max_bytes: int) -> None:
|
|
51
|
+
if max_bytes < 1:
|
|
52
|
+
raise ValueError("max_bytes must be >= 1")
|
|
53
|
+
super().__init__(app)
|
|
54
|
+
self._max_bytes = max_bytes
|
|
55
|
+
|
|
56
|
+
async def dispatch(self, request: Request, call_next: Callable) -> Response:
|
|
57
|
+
"""Reject requests larger than the configured threshold."""
|
|
58
|
+
content_length = request.headers.get("content-length")
|
|
59
|
+
if content_length:
|
|
60
|
+
try:
|
|
61
|
+
if int(content_length) > self._max_bytes:
|
|
62
|
+
return self._too_large_response(request)
|
|
63
|
+
except ValueError:
|
|
64
|
+
# Non-integer content length is treated as invalid request size.
|
|
65
|
+
return self._too_large_response(request)
|
|
66
|
+
|
|
67
|
+
body = await request.body()
|
|
68
|
+
if len(body) > self._max_bytes:
|
|
69
|
+
return self._too_large_response(request)
|
|
70
|
+
return await call_next(request)
|
|
71
|
+
|
|
72
|
+
def _too_large_response(self, request: Request) -> JSONResponse:
|
|
73
|
+
"""Build a standard 413 error response."""
|
|
74
|
+
request_id = getattr(request.state, "request_id", "")
|
|
75
|
+
correlation_id = getattr(request.state, "correlation_id", None)
|
|
76
|
+
return JSONResponse(
|
|
77
|
+
status_code=413,
|
|
78
|
+
content=error_envelope(
|
|
79
|
+
code="INVALID_REQUEST",
|
|
80
|
+
message=f"Request body exceeds maximum size ({self._max_bytes} bytes)",
|
|
81
|
+
details={"max_bytes": self._max_bytes},
|
|
82
|
+
retryable=False,
|
|
83
|
+
request_id=request_id,
|
|
84
|
+
correlation_id=correlation_id,
|
|
85
|
+
),
|
|
86
|
+
)
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# Copyright 2026 Cloud-Dog, Viewdeck Engineering Limited
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
# cloud_dog_api_kit — Request timeout middleware
|
|
16
|
+
#
|
|
17
|
+
# Licence: Proprietary — Cloud-Dog AI Platform
|
|
18
|
+
# Owner: Cloud-Dog AI
|
|
19
|
+
# Description: Configurable request timeout middleware that returns a standard
|
|
20
|
+
# TIMEOUT error envelope when a request exceeds the configured duration.
|
|
21
|
+
# Related requirements: FR13.1
|
|
22
|
+
# Related architecture: CC1.13
|
|
23
|
+
|
|
24
|
+
"""Request timeout middleware for cloud_dog_api_kit."""
|
|
25
|
+
|
|
26
|
+
from __future__ import annotations
|
|
27
|
+
|
|
28
|
+
import asyncio
|
|
29
|
+
from typing import Any, Callable
|
|
30
|
+
|
|
31
|
+
from starlette.middleware.base import BaseHTTPMiddleware
|
|
32
|
+
from starlette.requests import Request
|
|
33
|
+
from starlette.responses import JSONResponse, Response
|
|
34
|
+
|
|
35
|
+
from cloud_dog_api_kit.envelopes.error import error_envelope
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class TimeoutMiddleware(BaseHTTPMiddleware):
|
|
39
|
+
"""Request timeout middleware.
|
|
40
|
+
|
|
41
|
+
Returns a standard TIMEOUT error envelope when a request exceeds
|
|
42
|
+
the configured duration. Streaming endpoints should use jobs instead.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
app: The ASGI application.
|
|
46
|
+
timeout_seconds: Default request timeout in seconds. Defaults to 30.
|
|
47
|
+
|
|
48
|
+
Related tests: UT1.27_TimeoutMiddleware
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
def __init__(self, app: Any, timeout_seconds: float = 30.0) -> None:
|
|
52
|
+
super().__init__(app)
|
|
53
|
+
self._timeout = timeout_seconds
|
|
54
|
+
|
|
55
|
+
async def dispatch(self, request: Request, call_next: Callable) -> Response:
|
|
56
|
+
"""Process request with timeout enforcement.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
request: The incoming HTTP request.
|
|
60
|
+
call_next: The next middleware or handler.
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
The HTTP response or a TIMEOUT error envelope.
|
|
64
|
+
"""
|
|
65
|
+
try:
|
|
66
|
+
return await asyncio.wait_for(
|
|
67
|
+
call_next(request),
|
|
68
|
+
timeout=self._timeout,
|
|
69
|
+
)
|
|
70
|
+
except asyncio.TimeoutError:
|
|
71
|
+
request_id = getattr(request.state, "request_id", "")
|
|
72
|
+
body = error_envelope(
|
|
73
|
+
code="TIMEOUT",
|
|
74
|
+
message=f"Request timed out after {self._timeout}s",
|
|
75
|
+
retryable=True,
|
|
76
|
+
request_id=request_id,
|
|
77
|
+
)
|
|
78
|
+
return JSONResponse(status_code=504, content=body)
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Copyright 2026 Cloud-Dog, Viewdeck Engineering Limited
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
# cloud_dog_api_kit — Timing middleware
|
|
16
|
+
#
|
|
17
|
+
# Licence: Proprietary — Cloud-Dog AI Platform
|
|
18
|
+
# Owner: Cloud-Dog AI
|
|
19
|
+
# Description: Measures request duration and exposes it via response header.
|
|
20
|
+
# Related requirements: FR12.1
|
|
21
|
+
# Related architecture: SA1
|
|
22
|
+
|
|
23
|
+
"""Request timing middleware for cloud_dog_api_kit."""
|
|
24
|
+
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
import time
|
|
28
|
+
from typing import Any, Callable
|
|
29
|
+
|
|
30
|
+
from starlette.middleware.base import BaseHTTPMiddleware
|
|
31
|
+
from starlette.requests import Request
|
|
32
|
+
from starlette.responses import Response
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class TimingMiddleware(BaseHTTPMiddleware):
|
|
36
|
+
"""Measures request duration and sets response header.
|
|
37
|
+
|
|
38
|
+
Adds `X-Response-Time-Ms` header and stores `request.state.duration_ms`.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
def __init__(self, app: Any, header_name: str = "X-Response-Time-Ms") -> None:
|
|
42
|
+
super().__init__(app)
|
|
43
|
+
self._header_name = header_name
|
|
44
|
+
|
|
45
|
+
async def dispatch(self, request: Request, call_next: Callable) -> Response:
|
|
46
|
+
"""Handle dispatch."""
|
|
47
|
+
start = time.monotonic()
|
|
48
|
+
response = await call_next(request)
|
|
49
|
+
duration_ms = round((time.monotonic() - start) * 1000, 2)
|
|
50
|
+
setattr(request.state, "duration_ms", duration_ms)
|
|
51
|
+
response.headers[self._header_name] = str(duration_ms)
|
|
52
|
+
return response
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Copyright 2026 Cloud-Dog, Viewdeck Engineering Limited
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
# cloud_dog_api_kit — OpenAPI customisation helpers
|
|
16
|
+
#
|
|
17
|
+
# Licence: Proprietary — Cloud-Dog AI Platform
|
|
18
|
+
# Owner: Cloud-Dog AI
|
|
19
|
+
# Description: Helpers for OpenAPI schema customisation and versioning.
|
|
20
|
+
# Related requirements: FR10.1, FR10.2, FR10.4
|
|
21
|
+
# Related architecture: CC1.17
|
|
22
|
+
|
|
23
|
+
"""OpenAPI customisation helpers for cloud_dog_api_kit."""
|
|
24
|
+
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
from cloud_dog_api_kit.openapi.customise import configure_openapi
|
|
28
|
+
from cloud_dog_api_kit.openapi.route import create_openapi_router
|
|
29
|
+
|
|
30
|
+
__all__ = ["configure_openapi", "create_openapi_router"]
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# Copyright 2026 Cloud-Dog, Viewdeck Engineering Limited
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
# cloud_dog_api_kit — OpenAPI customisation
|
|
16
|
+
#
|
|
17
|
+
# Licence: Proprietary — Cloud-Dog AI Platform
|
|
18
|
+
# Owner: Cloud-Dog AI
|
|
19
|
+
# Description: Helpers for OpenAPI schema customisation, security schemes,
|
|
20
|
+
# and documentation behaviours.
|
|
21
|
+
# Related requirements: FR10.1, FR10.2
|
|
22
|
+
# Related architecture: SA1, CC1.17
|
|
23
|
+
|
|
24
|
+
"""OpenAPI customisation helpers."""
|
|
25
|
+
|
|
26
|
+
from __future__ import annotations
|
|
27
|
+
|
|
28
|
+
from typing import Any
|
|
29
|
+
|
|
30
|
+
from fastapi import FastAPI
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def configure_openapi(
|
|
34
|
+
app: FastAPI,
|
|
35
|
+
tags: list[dict] | None = None,
|
|
36
|
+
security_schemes: dict | None = None,
|
|
37
|
+
) -> None:
|
|
38
|
+
"""Configure OpenAPI schema customisation on a FastAPI application.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
app: The FastAPI application.
|
|
42
|
+
tags: OpenAPI tag metadata for endpoint grouping.
|
|
43
|
+
security_schemes: Security scheme definitions.
|
|
44
|
+
|
|
45
|
+
Related tests: UT1.31_OpenAPICustomise
|
|
46
|
+
"""
|
|
47
|
+
if tags:
|
|
48
|
+
app.openapi_tags = tags
|
|
49
|
+
|
|
50
|
+
if security_schemes:
|
|
51
|
+
|
|
52
|
+
def _custom_openapi() -> dict[str, Any]:
|
|
53
|
+
if app.openapi_schema:
|
|
54
|
+
return app.openapi_schema
|
|
55
|
+
from fastapi.openapi.utils import get_openapi
|
|
56
|
+
|
|
57
|
+
schema = get_openapi(
|
|
58
|
+
title=app.title,
|
|
59
|
+
version=app.version,
|
|
60
|
+
description=app.description,
|
|
61
|
+
routes=app.routes,
|
|
62
|
+
tags=app.openapi_tags,
|
|
63
|
+
)
|
|
64
|
+
schema.setdefault("components", {})
|
|
65
|
+
schema["components"]["securitySchemes"] = security_schemes
|
|
66
|
+
app.openapi_schema = schema
|
|
67
|
+
return schema
|
|
68
|
+
|
|
69
|
+
app.openapi = _custom_openapi # type: ignore[method-assign]
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Copyright 2026 Cloud-Dog, Viewdeck Engineering Limited
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
# cloud_dog_api_kit — OpenAPI spec route helper
|
|
16
|
+
#
|
|
17
|
+
# Licence: Proprietary — Cloud-Dog AI Platform
|
|
18
|
+
# Owner: Cloud-Dog AI
|
|
19
|
+
# Description: Provides a helper for serving an OpenAPI schema via a stable
|
|
20
|
+
# endpoint.
|
|
21
|
+
# Related requirements: FR10.1
|
|
22
|
+
# Related architecture: SA1
|
|
23
|
+
|
|
24
|
+
"""OpenAPI spec route helper for cloud_dog_api_kit."""
|
|
25
|
+
|
|
26
|
+
from __future__ import annotations
|
|
27
|
+
|
|
28
|
+
from typing import Any
|
|
29
|
+
|
|
30
|
+
from fastapi import APIRouter, FastAPI
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def create_openapi_router(
|
|
34
|
+
app: FastAPI,
|
|
35
|
+
path: str = "/openapi.json",
|
|
36
|
+
tags: list[str] | None = None,
|
|
37
|
+
) -> APIRouter:
|
|
38
|
+
"""Create a router that serves the OpenAPI schema for the given app."""
|
|
39
|
+
router = APIRouter(tags=tags or ["openapi"])
|
|
40
|
+
|
|
41
|
+
@router.get(path)
|
|
42
|
+
async def openapi_schema() -> dict[str, Any]:
|
|
43
|
+
"""Return the OpenAPI schema."""
|
|
44
|
+
return app.openapi()
|
|
45
|
+
|
|
46
|
+
return router
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Copyright 2026 Cloud-Dog, Viewdeck Engineering Limited
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
# cloud_dog_api_kit — Router helpers
|
|
16
|
+
#
|
|
17
|
+
# Licence: Proprietary — Cloud-Dog AI Platform
|
|
18
|
+
# Owner: Cloud-Dog AI
|
|
19
|
+
# Description: Router factories (CRUD, health, jobs, version) for consistent
|
|
20
|
+
# API structure.
|
|
21
|
+
# Related requirements: FR4.1, FR4.2, FR6.1, FR7.1
|
|
22
|
+
# Related architecture: SA1
|
|
23
|
+
|
|
24
|
+
"""Router helpers for cloud_dog_api_kit."""
|
|
25
|
+
|
|
26
|
+
from __future__ import annotations
|
|
27
|
+
|
|
28
|
+
from cloud_dog_api_kit.routers.crud import CRUDService, create_crud_router, create_versioned_router
|
|
29
|
+
from cloud_dog_api_kit.routers.health import HealthCheck, create_health_router
|
|
30
|
+
from cloud_dog_api_kit.routers.jobs import create_job_endpoint
|
|
31
|
+
from cloud_dog_api_kit.routers.version import create_version_router
|
|
32
|
+
|
|
33
|
+
__all__ = [
|
|
34
|
+
"CRUDService",
|
|
35
|
+
"create_crud_router",
|
|
36
|
+
"create_versioned_router",
|
|
37
|
+
"HealthCheck",
|
|
38
|
+
"create_health_router",
|
|
39
|
+
"create_job_endpoint",
|
|
40
|
+
"create_version_router",
|
|
41
|
+
]
|