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.
Files changed (98) hide show
  1. cloud_dog_api_kit/__init__.py +170 -0
  2. cloud_dog_api_kit/a2a/__init__.py +53 -0
  3. cloud_dog_api_kit/a2a/card.py +138 -0
  4. cloud_dog_api_kit/a2a/events.py +1123 -0
  5. cloud_dog_api_kit/a2a/gateway.py +105 -0
  6. cloud_dog_api_kit/a2a/skill_audit.py +107 -0
  7. cloud_dog_api_kit/auth/__init__.py +35 -0
  8. cloud_dog_api_kit/auth/dependency.py +121 -0
  9. cloud_dog_api_kit/auth/rbac.py +107 -0
  10. cloud_dog_api_kit/auth/service_auth.py +54 -0
  11. cloud_dog_api_kit/clients/__init__.py +29 -0
  12. cloud_dog_api_kit/clients/circuit_breaker.py +39 -0
  13. cloud_dog_api_kit/clients/http_client.py +127 -0
  14. cloud_dog_api_kit/clients/retry.py +83 -0
  15. cloud_dog_api_kit/compat/__init__.py +37 -0
  16. cloud_dog_api_kit/compat/envelope.py +120 -0
  17. cloud_dog_api_kit/compat/profile.py +102 -0
  18. cloud_dog_api_kit/compat/routes.py +90 -0
  19. cloud_dog_api_kit/config.py +54 -0
  20. cloud_dog_api_kit/correlation/__init__.py +50 -0
  21. cloud_dog_api_kit/correlation/context.py +118 -0
  22. cloud_dog_api_kit/correlation/middleware.py +133 -0
  23. cloud_dog_api_kit/envelopes/__init__.py +37 -0
  24. cloud_dog_api_kit/envelopes/error.py +87 -0
  25. cloud_dog_api_kit/envelopes/success.py +84 -0
  26. cloud_dog_api_kit/errors/__init__.py +51 -0
  27. cloud_dog_api_kit/errors/exceptions.py +184 -0
  28. cloud_dog_api_kit/errors/handler.py +102 -0
  29. cloud_dog_api_kit/errors/taxonomy.py +62 -0
  30. cloud_dog_api_kit/factory.py +157 -0
  31. cloud_dog_api_kit/idempotency/__init__.py +28 -0
  32. cloud_dog_api_kit/idempotency/middleware.py +118 -0
  33. cloud_dog_api_kit/idempotency/store.py +100 -0
  34. cloud_dog_api_kit/lifecycle/__init__.py +39 -0
  35. cloud_dog_api_kit/lifecycle/hooks.py +75 -0
  36. cloud_dog_api_kit/lifecycle/shutdown.py +178 -0
  37. cloud_dog_api_kit/mcp/__init__.py +122 -0
  38. cloud_dog_api_kit/mcp/async_jobs.py +126 -0
  39. cloud_dog_api_kit/mcp/client_sdk.py +235 -0
  40. cloud_dog_api_kit/mcp/client_transport/__init__.py +47 -0
  41. cloud_dog_api_kit/mcp/client_transport/base.py +98 -0
  42. cloud_dog_api_kit/mcp/client_transport/exceptions.py +37 -0
  43. cloud_dog_api_kit/mcp/client_transport/http_jsonrpc.py +405 -0
  44. cloud_dog_api_kit/mcp/client_transport/legacy_sse.py +320 -0
  45. cloud_dog_api_kit/mcp/client_transport/stdio.py +322 -0
  46. cloud_dog_api_kit/mcp/client_transport/streamable_http.py +748 -0
  47. cloud_dog_api_kit/mcp/contract.py +113 -0
  48. cloud_dog_api_kit/mcp/error_mapper.py +84 -0
  49. cloud_dog_api_kit/mcp/gateway.py +117 -0
  50. cloud_dog_api_kit/mcp/legacy_sse.py +129 -0
  51. cloud_dog_api_kit/mcp/session.py +96 -0
  52. cloud_dog_api_kit/mcp/sync_handler.py +269 -0
  53. cloud_dog_api_kit/mcp/tool_audit.py +136 -0
  54. cloud_dog_api_kit/mcp/tool_router.py +180 -0
  55. cloud_dog_api_kit/mcp/transport.py +1041 -0
  56. cloud_dog_api_kit/middleware/__init__.py +39 -0
  57. cloud_dog_api_kit/middleware/cors.py +74 -0
  58. cloud_dog_api_kit/middleware/logging.py +98 -0
  59. cloud_dog_api_kit/middleware/request_size_limit.py +86 -0
  60. cloud_dog_api_kit/middleware/timeout.py +78 -0
  61. cloud_dog_api_kit/middleware/timing.py +52 -0
  62. cloud_dog_api_kit/openapi/__init__.py +30 -0
  63. cloud_dog_api_kit/openapi/customise.py +69 -0
  64. cloud_dog_api_kit/openapi/route.py +46 -0
  65. cloud_dog_api_kit/routers/__init__.py +41 -0
  66. cloud_dog_api_kit/routers/crud.py +173 -0
  67. cloud_dog_api_kit/routers/health.py +160 -0
  68. cloud_dog_api_kit/routers/jobs.py +69 -0
  69. cloud_dog_api_kit/routers/version.py +46 -0
  70. cloud_dog_api_kit/schemas/__init__.py +36 -0
  71. cloud_dog_api_kit/schemas/envelopes.py +37 -0
  72. cloud_dog_api_kit/schemas/filters.py +103 -0
  73. cloud_dog_api_kit/schemas/pagination.py +148 -0
  74. cloud_dog_api_kit/streaming/__init__.py +28 -0
  75. cloud_dog_api_kit/streaming/events.py +47 -0
  76. cloud_dog_api_kit/streaming/jsonl.py +68 -0
  77. cloud_dog_api_kit/streaming/sse.py +102 -0
  78. cloud_dog_api_kit/testing/__init__.py +46 -0
  79. cloud_dog_api_kit/testing/conformance.py +156 -0
  80. cloud_dog_api_kit/testing/fixtures.py +90 -0
  81. cloud_dog_api_kit/testing/flows/__init__.py +32 -0
  82. cloud_dog_api_kit/testing/flows/auth_flow.py +41 -0
  83. cloud_dog_api_kit/testing/flows/crud_flow.py +50 -0
  84. cloud_dog_api_kit/testing/flows/job_flow.py +42 -0
  85. cloud_dog_api_kit/testing/flows/streaming_flow.py +42 -0
  86. cloud_dog_api_kit/traceability_ids.py +84 -0
  87. cloud_dog_api_kit/versioning/__init__.py +30 -0
  88. cloud_dog_api_kit/versioning/header.py +52 -0
  89. cloud_dog_api_kit/web/__init__.py +7 -0
  90. cloud_dog_api_kit/web/proxy.py +222 -0
  91. cloud_dog_api_kit/webhook/__init__.py +29 -0
  92. cloud_dog_api_kit/webhook/signature.py +149 -0
  93. cloud_dog_api_kit-0.13.0.dist-info/METADATA +27 -0
  94. cloud_dog_api_kit-0.13.0.dist-info/RECORD +98 -0
  95. cloud_dog_api_kit-0.13.0.dist-info/WHEEL +4 -0
  96. cloud_dog_api_kit-0.13.0.dist-info/licenses/LICENCE +190 -0
  97. cloud_dog_api_kit-0.13.0.dist-info/licenses/LICENSE +176 -0
  98. 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)