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,118 @@
|
|
|
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 — Correlation context using contextvars
|
|
16
|
+
#
|
|
17
|
+
# Licence: Proprietary — Cloud-Dog AI Platform
|
|
18
|
+
# Owner: Cloud-Dog AI
|
|
19
|
+
# Description: Context-local storage for request ID, correlation ID, app ID,
|
|
20
|
+
# and host ID. Uses contextvars for async-safe propagation.
|
|
21
|
+
# Related requirements: FR5.1, FR3.2, NF1.3
|
|
22
|
+
# Related architecture: CC1.8
|
|
23
|
+
|
|
24
|
+
"""Correlation context management using contextvars."""
|
|
25
|
+
|
|
26
|
+
from __future__ import annotations
|
|
27
|
+
|
|
28
|
+
import uuid
|
|
29
|
+
from contextvars import ContextVar
|
|
30
|
+
|
|
31
|
+
_request_id_var: ContextVar[str | None] = ContextVar("request_id", default=None)
|
|
32
|
+
_correlation_id_var: ContextVar[str | None] = ContextVar("correlation_id", default=None)
|
|
33
|
+
_app_id_var: ContextVar[str | None] = ContextVar("app_id", default=None)
|
|
34
|
+
_host_id_var: ContextVar[str | None] = ContextVar("host_id", default=None)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def get_request_id() -> str:
|
|
38
|
+
"""Get the current request ID, generating one if not set.
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
The request ID string.
|
|
42
|
+
"""
|
|
43
|
+
rid = _request_id_var.get()
|
|
44
|
+
if rid is None:
|
|
45
|
+
rid = uuid.uuid4().hex
|
|
46
|
+
_request_id_var.set(rid)
|
|
47
|
+
return rid
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def set_request_id(rid: str) -> None:
|
|
51
|
+
"""Set the request ID for the current context.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
rid: The request ID to set.
|
|
55
|
+
"""
|
|
56
|
+
_request_id_var.set(rid)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def get_correlation_id() -> str | None:
|
|
60
|
+
"""Get the cross-service correlation ID, if set.
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
The correlation ID string or None.
|
|
64
|
+
"""
|
|
65
|
+
return _correlation_id_var.get()
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def set_correlation_id(cid: str) -> None:
|
|
69
|
+
"""Set the cross-service correlation ID.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
cid: The correlation ID to set.
|
|
73
|
+
"""
|
|
74
|
+
_correlation_id_var.set(cid)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def get_app_id() -> str | None:
|
|
78
|
+
"""Get the calling application ID, if set.
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
The app ID string or None.
|
|
82
|
+
"""
|
|
83
|
+
return _app_id_var.get()
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def set_app_id(app_id: str) -> None:
|
|
87
|
+
"""Set the calling application ID.
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
app_id: The app ID to set.
|
|
91
|
+
"""
|
|
92
|
+
_app_id_var.set(app_id)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def get_host_id() -> str | None:
|
|
96
|
+
"""Get the host ID, if set.
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
The host ID string or None.
|
|
100
|
+
"""
|
|
101
|
+
return _host_id_var.get()
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def set_host_id(host_id: str) -> None:
|
|
105
|
+
"""Set the host ID.
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
host_id: The host ID to set.
|
|
109
|
+
"""
|
|
110
|
+
_host_id_var.set(host_id)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def clear_context() -> None:
|
|
114
|
+
"""Clear all correlation context variables."""
|
|
115
|
+
_request_id_var.set(None)
|
|
116
|
+
_correlation_id_var.set(None)
|
|
117
|
+
_app_id_var.set(None)
|
|
118
|
+
_host_id_var.set(None)
|
|
@@ -0,0 +1,133 @@
|
|
|
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 — Correlation ID ASGI middleware
|
|
16
|
+
#
|
|
17
|
+
# Licence: Proprietary — Cloud-Dog AI Platform
|
|
18
|
+
# Owner: Cloud-Dog AI
|
|
19
|
+
# Description: ASGI middleware that extracts X-Request-Id, X-Correlation-Id,
|
|
20
|
+
# X-App-Id, and X-Host-Id from request headers, stores them in contextvars,
|
|
21
|
+
# and attaches X-Request-Id to response headers.
|
|
22
|
+
# Related requirements: FR5.1, FR3.2
|
|
23
|
+
# Related architecture: CC1.8
|
|
24
|
+
|
|
25
|
+
"""Correlation ID ASGI middleware for cloud_dog_api_kit."""
|
|
26
|
+
|
|
27
|
+
from __future__ import annotations
|
|
28
|
+
|
|
29
|
+
import logging
|
|
30
|
+
import uuid
|
|
31
|
+
from typing import Callable
|
|
32
|
+
|
|
33
|
+
from starlette.middleware.base import BaseHTTPMiddleware
|
|
34
|
+
from starlette.requests import Request
|
|
35
|
+
from starlette.responses import JSONResponse, Response
|
|
36
|
+
|
|
37
|
+
from cloud_dog_api_kit.correlation.context import (
|
|
38
|
+
clear_context,
|
|
39
|
+
set_app_id,
|
|
40
|
+
set_correlation_id,
|
|
41
|
+
set_host_id,
|
|
42
|
+
set_request_id,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
_logger = logging.getLogger("cloud_dog_api_kit.correlation")
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class CorrelationMiddleware(BaseHTTPMiddleware):
|
|
49
|
+
"""ASGI middleware for correlation ID extraction and propagation.
|
|
50
|
+
|
|
51
|
+
On each request:
|
|
52
|
+
1. Extracts ``X-Request-Id`` (generates UUID if absent).
|
|
53
|
+
2. Extracts ``X-Correlation-Id`` if provided, propagates unchanged.
|
|
54
|
+
3. Extracts ``X-App-Id`` and ``X-Host-Id`` if provided.
|
|
55
|
+
4. Stores all in contextvars for the duration of the request.
|
|
56
|
+
5. Attaches ``X-Request-Id`` to response headers.
|
|
57
|
+
6. Sets ``request.state.request_id`` and ``request.state.correlation_id``.
|
|
58
|
+
|
|
59
|
+
Related tests: UT1.11_CorrelationContext, UT1.12_CorrelationMiddleware
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
async def dispatch(self, request: Request, call_next: Callable) -> Response:
|
|
63
|
+
"""Process the request and propagate correlation context.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
request: The incoming HTTP request.
|
|
67
|
+
call_next: The next middleware or handler.
|
|
68
|
+
|
|
69
|
+
Returns:
|
|
70
|
+
The HTTP response with X-Request-Id header attached.
|
|
71
|
+
"""
|
|
72
|
+
# Extract or generate request ID
|
|
73
|
+
request_id = request.headers.get("x-request-id", "").strip()
|
|
74
|
+
if not request_id:
|
|
75
|
+
request_id = uuid.uuid4().hex
|
|
76
|
+
set_request_id(request_id)
|
|
77
|
+
|
|
78
|
+
# Extract optional correlation headers
|
|
79
|
+
correlation_id = request.headers.get("x-correlation-id", "").strip() or None
|
|
80
|
+
if correlation_id:
|
|
81
|
+
set_correlation_id(correlation_id)
|
|
82
|
+
|
|
83
|
+
app_id = request.headers.get("x-app-id", "").strip() or None
|
|
84
|
+
if app_id:
|
|
85
|
+
set_app_id(app_id)
|
|
86
|
+
|
|
87
|
+
host_id = request.headers.get("x-host-id", "").strip() or None
|
|
88
|
+
if host_id:
|
|
89
|
+
set_host_id(host_id)
|
|
90
|
+
|
|
91
|
+
# Store on request.state for downstream handlers
|
|
92
|
+
request.state.request_id = request_id
|
|
93
|
+
request.state.correlation_id = correlation_id
|
|
94
|
+
request.state.app_id = app_id
|
|
95
|
+
request.state.host_id = host_id
|
|
96
|
+
|
|
97
|
+
try:
|
|
98
|
+
response = await call_next(request)
|
|
99
|
+
response.headers["X-Request-Id"] = request_id
|
|
100
|
+
if correlation_id:
|
|
101
|
+
response.headers["X-Correlation-Id"] = correlation_id
|
|
102
|
+
return response
|
|
103
|
+
except Exception as exc:
|
|
104
|
+
# Catch unhandled exceptions that escape FastAPI's exception handlers
|
|
105
|
+
# when BaseHTTPMiddleware is in the stack. Return a safe 500 envelope.
|
|
106
|
+
from cloud_dog_api_kit.errors.exceptions import APIError
|
|
107
|
+
|
|
108
|
+
if isinstance(exc, APIError):
|
|
109
|
+
body = {
|
|
110
|
+
"ok": False,
|
|
111
|
+
"error": {
|
|
112
|
+
"code": exc.code,
|
|
113
|
+
"message": exc.message,
|
|
114
|
+
"details": exc.details,
|
|
115
|
+
"retryable": exc.retryable,
|
|
116
|
+
},
|
|
117
|
+
"meta": {"request_id": request_id, "correlation_id": correlation_id},
|
|
118
|
+
}
|
|
119
|
+
return JSONResponse(status_code=exc.status_code, content=body)
|
|
120
|
+
_logger.exception("Unhandled exception", extra={"request_id": request_id})
|
|
121
|
+
body = {
|
|
122
|
+
"ok": False,
|
|
123
|
+
"error": {
|
|
124
|
+
"code": "INTERNAL_ERROR",
|
|
125
|
+
"message": "An internal error occurred",
|
|
126
|
+
"details": None,
|
|
127
|
+
"retryable": False,
|
|
128
|
+
},
|
|
129
|
+
"meta": {"request_id": request_id, "correlation_id": correlation_id},
|
|
130
|
+
}
|
|
131
|
+
return JSONResponse(status_code=500, content=body)
|
|
132
|
+
finally:
|
|
133
|
+
clear_context()
|
|
@@ -0,0 +1,37 @@
|
|
|
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 — Response envelopes
|
|
16
|
+
#
|
|
17
|
+
# Licence: Proprietary — Cloud-Dog AI Platform
|
|
18
|
+
# Owner: Cloud-Dog AI
|
|
19
|
+
# Description: Standard success and error response envelopes per PS-20.
|
|
20
|
+
# Related requirements: FR1.1, FR1.2
|
|
21
|
+
# Related architecture: CC1.1
|
|
22
|
+
|
|
23
|
+
"""Standard response envelopes for cloud_dog_api_kit."""
|
|
24
|
+
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
from cloud_dog_api_kit.envelopes.error import ErrorDetail, ErrorResponse, error_envelope
|
|
28
|
+
from cloud_dog_api_kit.envelopes.success import Meta, SuccessResponse, success_envelope
|
|
29
|
+
|
|
30
|
+
__all__ = [
|
|
31
|
+
"ErrorDetail",
|
|
32
|
+
"ErrorResponse",
|
|
33
|
+
"Meta",
|
|
34
|
+
"SuccessResponse",
|
|
35
|
+
"success_envelope",
|
|
36
|
+
"error_envelope",
|
|
37
|
+
]
|
|
@@ -0,0 +1,87 @@
|
|
|
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 — Error response envelopes
|
|
16
|
+
#
|
|
17
|
+
# Licence: Proprietary — Cloud-Dog AI Platform
|
|
18
|
+
# Owner: Cloud-Dog AI
|
|
19
|
+
# Description: Standard error envelope models and helpers.
|
|
20
|
+
# Related requirements: FR1.2
|
|
21
|
+
# Related architecture: SA1, CC1.1
|
|
22
|
+
|
|
23
|
+
"""Error response envelope models for cloud_dog_api_kit."""
|
|
24
|
+
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
from typing import Any
|
|
28
|
+
|
|
29
|
+
from pydantic import BaseModel, Field
|
|
30
|
+
|
|
31
|
+
from cloud_dog_api_kit.envelopes.success import Meta
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class ErrorDetail(BaseModel):
|
|
35
|
+
"""Standard error detail within the error envelope.
|
|
36
|
+
|
|
37
|
+
Attributes:
|
|
38
|
+
code: Stable, machine-readable error code from the taxonomy.
|
|
39
|
+
message: Human-readable summary (no secrets, no stack traces).
|
|
40
|
+
details: Optional field-level error details.
|
|
41
|
+
retryable: Whether the client can retry the request.
|
|
42
|
+
|
|
43
|
+
Related tests: UT1.2_ErrorEnvelope, UT1.3_ErrorTaxonomy
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
code: str
|
|
47
|
+
message: str
|
|
48
|
+
details: dict[str, Any] | None = None
|
|
49
|
+
retryable: bool = False
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class ErrorResponse(BaseModel):
|
|
53
|
+
"""Standard error response envelope.
|
|
54
|
+
|
|
55
|
+
Related tests: UT1.2_ErrorEnvelope
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
ok: bool = False
|
|
59
|
+
error: ErrorDetail
|
|
60
|
+
meta: Meta = Field(default_factory=Meta)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def error_envelope(
|
|
64
|
+
code: str,
|
|
65
|
+
message: str,
|
|
66
|
+
details: dict[str, Any] | None = None,
|
|
67
|
+
retryable: bool = False,
|
|
68
|
+
request_id: str = "",
|
|
69
|
+
correlation_id: str | None = None,
|
|
70
|
+
) -> dict[str, Any]:
|
|
71
|
+
"""Build an error response envelope dictionary.
|
|
72
|
+
|
|
73
|
+
Related tests: UT1.2_ErrorEnvelope
|
|
74
|
+
"""
|
|
75
|
+
return {
|
|
76
|
+
"ok": False,
|
|
77
|
+
"error": {
|
|
78
|
+
"code": code,
|
|
79
|
+
"message": message,
|
|
80
|
+
"details": details,
|
|
81
|
+
"retryable": retryable,
|
|
82
|
+
},
|
|
83
|
+
"meta": {
|
|
84
|
+
"request_id": request_id,
|
|
85
|
+
"correlation_id": correlation_id,
|
|
86
|
+
},
|
|
87
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
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 — Success response envelopes
|
|
16
|
+
#
|
|
17
|
+
# Licence: Proprietary — Cloud-Dog AI Platform
|
|
18
|
+
# Owner: Cloud-Dog AI
|
|
19
|
+
# Description: Standard success envelope models and helpers.
|
|
20
|
+
# Related requirements: FR1.1
|
|
21
|
+
# Related architecture: SA1, CC1.1
|
|
22
|
+
|
|
23
|
+
"""Success response envelope models for cloud_dog_api_kit."""
|
|
24
|
+
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
from typing import Any, Generic, TypeVar
|
|
28
|
+
|
|
29
|
+
from pydantic import BaseModel, Field
|
|
30
|
+
|
|
31
|
+
T = TypeVar("T")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class Meta(BaseModel):
|
|
35
|
+
"""Response metadata included in every envelope.
|
|
36
|
+
|
|
37
|
+
Attributes:
|
|
38
|
+
request_id: The unique request identifier (correlation ID).
|
|
39
|
+
correlation_id: Optional cross-service correlation identifier.
|
|
40
|
+
version: API version string.
|
|
41
|
+
|
|
42
|
+
Related tests: UT1.1_SuccessEnvelope
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
request_id: str = ""
|
|
46
|
+
correlation_id: str | None = None
|
|
47
|
+
version: str = "v1"
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class SuccessResponse(BaseModel, Generic[T]):
|
|
51
|
+
"""Standard success response envelope.
|
|
52
|
+
|
|
53
|
+
Attributes:
|
|
54
|
+
ok: Always True for success responses.
|
|
55
|
+
data: The response payload.
|
|
56
|
+
meta: Response metadata.
|
|
57
|
+
|
|
58
|
+
Related tests: UT1.1_SuccessEnvelope
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
ok: bool = True
|
|
62
|
+
data: T
|
|
63
|
+
meta: Meta = Field(default_factory=Meta)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def success_envelope(
|
|
67
|
+
data: Any,
|
|
68
|
+
request_id: str = "",
|
|
69
|
+
correlation_id: str | None = None,
|
|
70
|
+
version: str = "v1",
|
|
71
|
+
) -> dict[str, Any]:
|
|
72
|
+
"""Build a success response envelope dictionary.
|
|
73
|
+
|
|
74
|
+
Related tests: UT1.1_SuccessEnvelope
|
|
75
|
+
"""
|
|
76
|
+
return {
|
|
77
|
+
"ok": True,
|
|
78
|
+
"data": data,
|
|
79
|
+
"meta": {
|
|
80
|
+
"request_id": request_id,
|
|
81
|
+
"correlation_id": correlation_id,
|
|
82
|
+
"version": version,
|
|
83
|
+
},
|
|
84
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
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 — Error taxonomy and exception hierarchy
|
|
16
|
+
#
|
|
17
|
+
# Licence: Proprietary — Cloud-Dog AI Platform
|
|
18
|
+
# Owner: Cloud-Dog AI
|
|
19
|
+
# Description: Typed exception classes mapping to the PS-20 error taxonomy.
|
|
20
|
+
# Related requirements: FR1.3, FR2.1, FR2.2
|
|
21
|
+
# Related architecture: CC1.3
|
|
22
|
+
|
|
23
|
+
"""Error taxonomy and exception hierarchy for cloud_dog_api_kit."""
|
|
24
|
+
|
|
25
|
+
from cloud_dog_api_kit.errors.exceptions import (
|
|
26
|
+
APIError,
|
|
27
|
+
ConflictError,
|
|
28
|
+
InternalError,
|
|
29
|
+
NotFoundError,
|
|
30
|
+
RateLimitError,
|
|
31
|
+
TimeoutError,
|
|
32
|
+
UnauthenticatedError,
|
|
33
|
+
UnauthorisedError,
|
|
34
|
+
UpstreamError,
|
|
35
|
+
ValidationError,
|
|
36
|
+
)
|
|
37
|
+
from cloud_dog_api_kit.errors.handler import register_error_handlers
|
|
38
|
+
|
|
39
|
+
__all__ = [
|
|
40
|
+
"APIError",
|
|
41
|
+
"ConflictError",
|
|
42
|
+
"InternalError",
|
|
43
|
+
"NotFoundError",
|
|
44
|
+
"RateLimitError",
|
|
45
|
+
"TimeoutError",
|
|
46
|
+
"UnauthenticatedError",
|
|
47
|
+
"UnauthorisedError",
|
|
48
|
+
"UpstreamError",
|
|
49
|
+
"ValidationError",
|
|
50
|
+
"register_error_handlers",
|
|
51
|
+
]
|
|
@@ -0,0 +1,184 @@
|
|
|
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 — Typed exception classes
|
|
16
|
+
#
|
|
17
|
+
# Licence: Proprietary — Cloud-Dog AI Platform
|
|
18
|
+
# Owner: Cloud-Dog AI
|
|
19
|
+
# Description: Exception hierarchy mapping to the PS-20 error taxonomy.
|
|
20
|
+
# Each exception carries code, message, retryable, and optional details.
|
|
21
|
+
# Related requirements: FR2.1
|
|
22
|
+
# Related architecture: CC1.3
|
|
23
|
+
|
|
24
|
+
"""Typed exception classes for the PS-20 error taxonomy."""
|
|
25
|
+
|
|
26
|
+
from __future__ import annotations
|
|
27
|
+
|
|
28
|
+
from typing import Any
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class APIError(Exception):
|
|
32
|
+
"""Base exception for all API errors.
|
|
33
|
+
|
|
34
|
+
Attributes:
|
|
35
|
+
status_code: HTTP status code.
|
|
36
|
+
code: Machine-readable error code from the taxonomy.
|
|
37
|
+
message: Human-readable error message.
|
|
38
|
+
retryable: Whether the client can retry.
|
|
39
|
+
details: Optional field-level error details.
|
|
40
|
+
|
|
41
|
+
Related tests: UT1.4_ErrorExceptions
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
status_code: int = 500
|
|
45
|
+
code: str = "INTERNAL_ERROR"
|
|
46
|
+
retryable: bool = False
|
|
47
|
+
|
|
48
|
+
def __init__(
|
|
49
|
+
self,
|
|
50
|
+
message: str = "An internal error occurred",
|
|
51
|
+
details: dict[str, Any] | None = None,
|
|
52
|
+
retryable: bool | None = None,
|
|
53
|
+
) -> None:
|
|
54
|
+
super().__init__(message)
|
|
55
|
+
self.message = message
|
|
56
|
+
self.details = details
|
|
57
|
+
if retryable is not None:
|
|
58
|
+
self.retryable = retryable
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class UnauthenticatedError(APIError):
|
|
62
|
+
"""Missing or invalid credentials (401).
|
|
63
|
+
|
|
64
|
+
Related tests: UT1.4_ErrorExceptions
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
status_code = 401
|
|
68
|
+
code = "UNAUTHENTICATED"
|
|
69
|
+
retryable = False
|
|
70
|
+
|
|
71
|
+
def __init__(self, message: str = "Missing or invalid credentials", **kwargs: Any) -> None:
|
|
72
|
+
super().__init__(message=message, **kwargs)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class UnauthorisedError(APIError):
|
|
76
|
+
"""Insufficient permissions (403).
|
|
77
|
+
|
|
78
|
+
Related tests: UT1.4_ErrorExceptions
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
status_code = 403
|
|
82
|
+
code = "UNAUTHORISED"
|
|
83
|
+
retryable = False
|
|
84
|
+
|
|
85
|
+
def __init__(self, message: str = "Insufficient permissions", **kwargs: Any) -> None:
|
|
86
|
+
super().__init__(message=message, **kwargs)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class NotFoundError(APIError):
|
|
90
|
+
"""Resource does not exist (404).
|
|
91
|
+
|
|
92
|
+
Related tests: UT1.4_ErrorExceptions
|
|
93
|
+
"""
|
|
94
|
+
|
|
95
|
+
status_code = 404
|
|
96
|
+
code = "NOT_FOUND"
|
|
97
|
+
retryable = False
|
|
98
|
+
|
|
99
|
+
def __init__(self, message: str = "Resource not found", **kwargs: Any) -> None:
|
|
100
|
+
super().__init__(message=message, **kwargs)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class ConflictError(APIError):
|
|
104
|
+
"""Resource state conflict (409).
|
|
105
|
+
|
|
106
|
+
Related tests: UT1.4_ErrorExceptions
|
|
107
|
+
"""
|
|
108
|
+
|
|
109
|
+
status_code = 409
|
|
110
|
+
code = "CONFLICT"
|
|
111
|
+
retryable = False
|
|
112
|
+
|
|
113
|
+
def __init__(self, message: str = "Resource state conflict", **kwargs: Any) -> None:
|
|
114
|
+
super().__init__(message=message, **kwargs)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
class ValidationError(APIError):
|
|
118
|
+
"""Validation failure (422).
|
|
119
|
+
|
|
120
|
+
Related tests: UT1.4_ErrorExceptions
|
|
121
|
+
"""
|
|
122
|
+
|
|
123
|
+
status_code = 422
|
|
124
|
+
code = "INVALID_REQUEST"
|
|
125
|
+
retryable = False
|
|
126
|
+
|
|
127
|
+
def __init__(self, message: str = "Validation failure", **kwargs: Any) -> None:
|
|
128
|
+
super().__init__(message=message, **kwargs)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
class RateLimitError(APIError):
|
|
132
|
+
"""Too many requests (429).
|
|
133
|
+
|
|
134
|
+
Related tests: UT1.4_ErrorExceptions
|
|
135
|
+
"""
|
|
136
|
+
|
|
137
|
+
status_code = 429
|
|
138
|
+
code = "RATE_LIMITED"
|
|
139
|
+
retryable = True
|
|
140
|
+
|
|
141
|
+
def __init__(self, message: str = "Too many requests", **kwargs: Any) -> None:
|
|
142
|
+
super().__init__(message=message, **kwargs)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
class TimeoutError(APIError):
|
|
146
|
+
"""Operation timed out (504).
|
|
147
|
+
|
|
148
|
+
Related tests: UT1.4_ErrorExceptions
|
|
149
|
+
"""
|
|
150
|
+
|
|
151
|
+
status_code = 504
|
|
152
|
+
code = "TIMEOUT"
|
|
153
|
+
retryable = True
|
|
154
|
+
|
|
155
|
+
def __init__(self, message: str = "Operation timed out", **kwargs: Any) -> None:
|
|
156
|
+
super().__init__(message=message, **kwargs)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
class UpstreamError(APIError):
|
|
160
|
+
"""Downstream service failure (502).
|
|
161
|
+
|
|
162
|
+
Related tests: UT1.4_ErrorExceptions
|
|
163
|
+
"""
|
|
164
|
+
|
|
165
|
+
status_code = 502
|
|
166
|
+
code = "UPSTREAM_ERROR"
|
|
167
|
+
retryable = True
|
|
168
|
+
|
|
169
|
+
def __init__(self, message: str = "Downstream service failure", **kwargs: Any) -> None:
|
|
170
|
+
super().__init__(message=message, **kwargs)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
class InternalError(APIError):
|
|
174
|
+
"""Unhandled server error (500).
|
|
175
|
+
|
|
176
|
+
Related tests: UT1.4_ErrorExceptions
|
|
177
|
+
"""
|
|
178
|
+
|
|
179
|
+
status_code = 500
|
|
180
|
+
code = "INTERNAL_ERROR"
|
|
181
|
+
retryable = False
|
|
182
|
+
|
|
183
|
+
def __init__(self, message: str = "An internal error occurred", **kwargs: Any) -> None:
|
|
184
|
+
super().__init__(message=message, **kwargs)
|