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,32 @@
|
|
|
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 — Test flow templates
|
|
16
|
+
#
|
|
17
|
+
# Licence: Proprietary — Cloud-Dog AI Platform
|
|
18
|
+
# Owner: Cloud-Dog AI
|
|
19
|
+
# Description: Reusable test flow templates for auth, CRUD, jobs, and streaming.
|
|
20
|
+
# Related requirements: FR17.1
|
|
21
|
+
# Related architecture: SA1
|
|
22
|
+
|
|
23
|
+
"""Reusable flow templates for cloud_dog_api_kit testing."""
|
|
24
|
+
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
from cloud_dog_api_kit.testing.flows.auth_flow import AuthFlow
|
|
28
|
+
from cloud_dog_api_kit.testing.flows.crud_flow import CRUDFlow
|
|
29
|
+
from cloud_dog_api_kit.testing.flows.job_flow import JobFlow
|
|
30
|
+
from cloud_dog_api_kit.testing.flows.streaming_flow import StreamingFlow
|
|
31
|
+
|
|
32
|
+
__all__ = ["AuthFlow", "CRUDFlow", "JobFlow", "StreamingFlow"]
|
|
@@ -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 — Auth flow template
|
|
16
|
+
#
|
|
17
|
+
# Licence: Proprietary — Cloud-Dog AI Platform
|
|
18
|
+
# Owner: Cloud-Dog AI
|
|
19
|
+
# Description: Reusable auth flow checks for services using cloud_dog_api_kit.
|
|
20
|
+
# Related requirements: FR3.1, FR3.4
|
|
21
|
+
# Related architecture: SA1
|
|
22
|
+
|
|
23
|
+
"""Auth flow template for tests."""
|
|
24
|
+
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
from dataclasses import dataclass
|
|
28
|
+
|
|
29
|
+
import httpx
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass(frozen=True, slots=True)
|
|
33
|
+
class AuthFlow:
|
|
34
|
+
"""Reusable auth flow assertions."""
|
|
35
|
+
|
|
36
|
+
protected_path: str
|
|
37
|
+
|
|
38
|
+
async def assert_unauthenticated(self, client: httpx.AsyncClient) -> None:
|
|
39
|
+
"""Handle assert unauthenticated."""
|
|
40
|
+
r = await client.get(self.protected_path)
|
|
41
|
+
assert r.status_code == 401
|
|
@@ -0,0 +1,50 @@
|
|
|
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 — CRUD flow template
|
|
16
|
+
#
|
|
17
|
+
# Licence: Proprietary — Cloud-Dog AI Platform
|
|
18
|
+
# Owner: Cloud-Dog AI
|
|
19
|
+
# Description: Reusable CRUD flow checks for services using cloud_dog_api_kit.
|
|
20
|
+
# Related requirements: FR4.2
|
|
21
|
+
# Related architecture: SA1
|
|
22
|
+
|
|
23
|
+
"""CRUD flow template for tests."""
|
|
24
|
+
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
from dataclasses import dataclass
|
|
28
|
+
|
|
29
|
+
import httpx
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass(frozen=True, slots=True)
|
|
33
|
+
class CRUDFlow:
|
|
34
|
+
"""Reusable CRUD flow assertions.
|
|
35
|
+
|
|
36
|
+
This template is intentionally minimal; services can extend it for resource-specific behaviour.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
collection_path: str
|
|
40
|
+
|
|
41
|
+
async def create_and_get(self, client: httpx.AsyncClient, payload: dict) -> dict:
|
|
42
|
+
"""Create and get."""
|
|
43
|
+
created = await client.post(self.collection_path, json=payload)
|
|
44
|
+
created.raise_for_status()
|
|
45
|
+
created_data = created.json()["data"]
|
|
46
|
+
resource_id = created_data["id"]
|
|
47
|
+
|
|
48
|
+
fetched = await client.get(f"{self.collection_path}/{resource_id}")
|
|
49
|
+
fetched.raise_for_status()
|
|
50
|
+
return fetched.json()["data"]
|
|
@@ -0,0 +1,42 @@
|
|
|
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 — Job flow template
|
|
16
|
+
#
|
|
17
|
+
# Licence: Proprietary — Cloud-Dog AI Platform
|
|
18
|
+
# Owner: Cloud-Dog AI
|
|
19
|
+
# Description: Reusable job submission flow checks.
|
|
20
|
+
# Related requirements: FR7.1
|
|
21
|
+
# Related architecture: SA1
|
|
22
|
+
|
|
23
|
+
"""Job flow template for tests."""
|
|
24
|
+
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
from dataclasses import dataclass
|
|
28
|
+
|
|
29
|
+
import httpx
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass(frozen=True, slots=True)
|
|
33
|
+
class JobFlow:
|
|
34
|
+
"""Reusable job submission assertions."""
|
|
35
|
+
|
|
36
|
+
submit_path: str
|
|
37
|
+
|
|
38
|
+
async def submit(self, client: httpx.AsyncClient, payload: dict) -> str:
|
|
39
|
+
"""Handle submit."""
|
|
40
|
+
r = await client.post(self.submit_path, json=payload)
|
|
41
|
+
r.raise_for_status()
|
|
42
|
+
return r.json()["data"]["job_id"]
|
|
@@ -0,0 +1,42 @@
|
|
|
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 — Streaming flow template
|
|
16
|
+
#
|
|
17
|
+
# Licence: Proprietary — Cloud-Dog AI Platform
|
|
18
|
+
# Owner: Cloud-Dog AI
|
|
19
|
+
# Description: Reusable streaming flow checks for SSE/JSONL endpoints.
|
|
20
|
+
# Related requirements: FR8.1
|
|
21
|
+
# Related architecture: SA1
|
|
22
|
+
|
|
23
|
+
"""Streaming flow template for tests."""
|
|
24
|
+
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
from dataclasses import dataclass
|
|
28
|
+
|
|
29
|
+
import httpx
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass(frozen=True, slots=True)
|
|
33
|
+
class StreamingFlow:
|
|
34
|
+
"""Reusable streaming flow assertions."""
|
|
35
|
+
|
|
36
|
+
path: str
|
|
37
|
+
|
|
38
|
+
async def fetch_stream(self, client: httpx.AsyncClient) -> str:
|
|
39
|
+
"""Handle fetch stream."""
|
|
40
|
+
r = await client.get(self.path)
|
|
41
|
+
r.raise_for_status()
|
|
42
|
+
return r.text
|
|
@@ -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
|
+
"""Requirement traceability annotations for implemented cloud_dog_api_kit behaviors."""
|
|
16
|
+
|
|
17
|
+
# Implements: CS1.1, CS1.2, CS1.3, CS1.4, CS1.5, CS1.6, CS1.7
|
|
18
|
+
# Implements: FR1.1, FR1.2, FR1.3, FR2.1, FR2.2, FR3.1, FR3.2, FR3.3, FR3.4, FR3.5
|
|
19
|
+
# Implements: FR4.1, FR4.2, FR4.3, FR4.4, FR5.1, FR6.1, FR7.1, FR8.1, FR8.2, FR8.3
|
|
20
|
+
# Implements: FR9.1, FR9.2, FR10.1, FR10.2, FR10.3, FR10.4, FR11.1, FR12.1, FR12.2
|
|
21
|
+
# Implements: FR13.1, FR13.2, FR14.1, FR15.1, FR16.1, FR16.2, FR16.3, FR17.1, FR18.1
|
|
22
|
+
# Implements: FR18.2, FR18.3, FR18.4, FR18.5, FR18.6, FR18.7, FR18.8, FR18.9
|
|
23
|
+
# Implements: NF1.1, NF1.2, NF1.3, NF1.4, NF1.5
|
|
24
|
+
|
|
25
|
+
TRACEABILITY_IDS = (
|
|
26
|
+
"CS1.1",
|
|
27
|
+
"CS1.2",
|
|
28
|
+
"CS1.3",
|
|
29
|
+
"CS1.4",
|
|
30
|
+
"CS1.5",
|
|
31
|
+
"CS1.6",
|
|
32
|
+
"CS1.7",
|
|
33
|
+
"FR1.1",
|
|
34
|
+
"FR1.2",
|
|
35
|
+
"FR1.3",
|
|
36
|
+
"FR2.1",
|
|
37
|
+
"FR2.2",
|
|
38
|
+
"FR3.1",
|
|
39
|
+
"FR3.2",
|
|
40
|
+
"FR3.3",
|
|
41
|
+
"FR3.4",
|
|
42
|
+
"FR3.5",
|
|
43
|
+
"FR4.1",
|
|
44
|
+
"FR4.2",
|
|
45
|
+
"FR4.3",
|
|
46
|
+
"FR4.4",
|
|
47
|
+
"FR5.1",
|
|
48
|
+
"FR6.1",
|
|
49
|
+
"FR7.1",
|
|
50
|
+
"FR8.1",
|
|
51
|
+
"FR8.2",
|
|
52
|
+
"FR8.3",
|
|
53
|
+
"FR9.1",
|
|
54
|
+
"FR9.2",
|
|
55
|
+
"FR10.1",
|
|
56
|
+
"FR10.2",
|
|
57
|
+
"FR10.3",
|
|
58
|
+
"FR10.4",
|
|
59
|
+
"FR11.1",
|
|
60
|
+
"FR12.1",
|
|
61
|
+
"FR12.2",
|
|
62
|
+
"FR13.1",
|
|
63
|
+
"FR13.2",
|
|
64
|
+
"FR14.1",
|
|
65
|
+
"FR15.1",
|
|
66
|
+
"FR16.1",
|
|
67
|
+
"FR16.2",
|
|
68
|
+
"FR16.3",
|
|
69
|
+
"FR17.1",
|
|
70
|
+
"FR18.1",
|
|
71
|
+
"FR18.2",
|
|
72
|
+
"FR18.3",
|
|
73
|
+
"FR18.4",
|
|
74
|
+
"FR18.5",
|
|
75
|
+
"FR18.6",
|
|
76
|
+
"FR18.7",
|
|
77
|
+
"FR18.8",
|
|
78
|
+
"FR18.9",
|
|
79
|
+
"NF1.1",
|
|
80
|
+
"NF1.2",
|
|
81
|
+
"NF1.3",
|
|
82
|
+
"NF1.4",
|
|
83
|
+
"NF1.5",
|
|
84
|
+
)
|
|
@@ -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 — Versioning helpers
|
|
16
|
+
#
|
|
17
|
+
# Licence: Proprietary — Cloud-Dog AI Platform
|
|
18
|
+
# Owner: Cloud-Dog AI
|
|
19
|
+
# Description: API versioning helpers, including X-API-Version response header
|
|
20
|
+
# middleware.
|
|
21
|
+
# Related requirements: FR10.4
|
|
22
|
+
# Related architecture: SA1, CC1.17
|
|
23
|
+
|
|
24
|
+
"""Versioning helpers for cloud_dog_api_kit."""
|
|
25
|
+
|
|
26
|
+
from __future__ import annotations
|
|
27
|
+
|
|
28
|
+
from cloud_dog_api_kit.versioning.header import VersionHeaderMiddleware
|
|
29
|
+
|
|
30
|
+
__all__ = ["VersionHeaderMiddleware"]
|
|
@@ -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 — X-API-Version header middleware
|
|
16
|
+
#
|
|
17
|
+
# Licence: Proprietary — Cloud-Dog AI Platform
|
|
18
|
+
# Owner: Cloud-Dog AI
|
|
19
|
+
# Description: Adds an `X-API-Version` header to all responses.
|
|
20
|
+
# Related requirements: FR10.4
|
|
21
|
+
# Related architecture: SA1
|
|
22
|
+
|
|
23
|
+
"""API version header middleware."""
|
|
24
|
+
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
from typing import Any, Callable
|
|
28
|
+
|
|
29
|
+
from starlette.middleware.base import BaseHTTPMiddleware
|
|
30
|
+
from starlette.requests import Request
|
|
31
|
+
from starlette.responses import Response
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class VersionHeaderMiddleware(BaseHTTPMiddleware):
|
|
35
|
+
"""Middleware that adds X-API-Version header to all responses.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
app: The ASGI application.
|
|
39
|
+
version: The API version string. Defaults to ``v1``.
|
|
40
|
+
|
|
41
|
+
Related tests: UT1.30_VersionHeader
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
def __init__(self, app: Any, version: str = "v1") -> None:
|
|
45
|
+
super().__init__(app)
|
|
46
|
+
self._version = version
|
|
47
|
+
|
|
48
|
+
async def dispatch(self, request: Request, call_next: Callable) -> Response:
|
|
49
|
+
"""Add X-API-Version header to the response."""
|
|
50
|
+
response = await call_next(request)
|
|
51
|
+
response.headers["X-API-Version"] = self._version
|
|
52
|
+
return response
|
|
@@ -0,0 +1,222 @@
|
|
|
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
|
+
"""
|
|
16
|
+
WebApiProxy — Standard web→API proxy for Cloud-Dog services.
|
|
17
|
+
|
|
18
|
+
Licence: Apache 2.0
|
|
19
|
+
Ownership: Cloud-Dog, Viewdeck Engineering Limited
|
|
20
|
+
Description: Shared proxy class that all web servers use to forward
|
|
21
|
+
requests to their API server. Replaces 9 bespoke implementations.
|
|
22
|
+
Related Requirements: FR9.1, FR9.2
|
|
23
|
+
Related Architecture: CC1.15
|
|
24
|
+
Related Tests: UT1.XX_WebApiProxy
|
|
25
|
+
|
|
26
|
+
Recent Changes (max 10):
|
|
27
|
+
- 2026-04-08: W28A-849 — Initial implementation.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
from __future__ import annotations
|
|
31
|
+
|
|
32
|
+
import logging
|
|
33
|
+
from dataclasses import dataclass, field
|
|
34
|
+
from typing import Any, Mapping
|
|
35
|
+
|
|
36
|
+
import httpx
|
|
37
|
+
|
|
38
|
+
logger = logging.getLogger(__name__)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass
|
|
42
|
+
class ProxyResponse:
|
|
43
|
+
"""Structured response from the proxy."""
|
|
44
|
+
|
|
45
|
+
status_code: int
|
|
46
|
+
data: Any = None
|
|
47
|
+
error: str | None = None
|
|
48
|
+
headers: dict[str, str] = field(default_factory=dict)
|
|
49
|
+
|
|
50
|
+
@property
|
|
51
|
+
def ok(self) -> bool:
|
|
52
|
+
"""True when status_code is in the 2xx–3xx range."""
|
|
53
|
+
return 200 <= self.status_code < 400
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class WebApiProxy:
|
|
57
|
+
"""Standard web→API proxy backed by httpx.
|
|
58
|
+
|
|
59
|
+
Provides async methods (``get()``, ``post()``, etc.) and sync methods
|
|
60
|
+
(``get_sync()``, ``post_sync()``, etc.) that auto-inject the API key
|
|
61
|
+
and return structured ``ProxyResponse`` objects.
|
|
62
|
+
|
|
63
|
+
Async usage::
|
|
64
|
+
|
|
65
|
+
proxy = WebApiProxy(api_base_url="http://localhost:8083", api_key="abc")
|
|
66
|
+
result = await proxy.get("/health")
|
|
67
|
+
|
|
68
|
+
Sync usage (for mixed sync/async web servers)::
|
|
69
|
+
|
|
70
|
+
result = proxy.get_sync("/health")
|
|
71
|
+
|
|
72
|
+
From cloud_dog_config::
|
|
73
|
+
|
|
74
|
+
proxy = WebApiProxy.from_config(config)
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
def __init__(
|
|
78
|
+
self,
|
|
79
|
+
api_base_url: str,
|
|
80
|
+
api_key: str = "",
|
|
81
|
+
api_key_header: str = "X-API-Key",
|
|
82
|
+
verify_tls: bool = True,
|
|
83
|
+
timeout: float = 60.0,
|
|
84
|
+
) -> None:
|
|
85
|
+
self._base_url = api_base_url.rstrip("/")
|
|
86
|
+
self._api_key = api_key
|
|
87
|
+
self._api_key_header = api_key_header
|
|
88
|
+
self._verify_tls = verify_tls
|
|
89
|
+
self._timeout = timeout
|
|
90
|
+
|
|
91
|
+
@classmethod
|
|
92
|
+
def from_config(cls, config: Any) -> "WebApiProxy":
|
|
93
|
+
"""Create a proxy from a cloud_dog_config Config object.
|
|
94
|
+
|
|
95
|
+
Reads:
|
|
96
|
+
- ``web_server.api_base_url`` or ``api_server.base_url``
|
|
97
|
+
- ``api_server.api_key`` or ``auth.admin_token``
|
|
98
|
+
- ``api_server.api_key_header`` (default ``X-API-Key``)
|
|
99
|
+
- ``web_server.verify_tls`` (default True)
|
|
100
|
+
- ``web_server.proxy_timeout`` (default 60.0)
|
|
101
|
+
"""
|
|
102
|
+
get = getattr(config, "get", lambda k, d=None: d)
|
|
103
|
+
api_base_url = str(
|
|
104
|
+
get("web_server.api_base_url")
|
|
105
|
+
or get("api_server.base_url")
|
|
106
|
+
or "http://localhost:8083"
|
|
107
|
+
)
|
|
108
|
+
api_key = str(get("api_server.api_key") or get("auth.admin_token") or "")
|
|
109
|
+
api_key_header = str(get("api_server.api_key_header") or "X-API-Key")
|
|
110
|
+
verify_tls = bool(get("web_server.verify_tls") if get("web_server.verify_tls") is not None else True)
|
|
111
|
+
timeout = float(get("web_server.proxy_timeout") or 60.0)
|
|
112
|
+
return cls(
|
|
113
|
+
api_base_url=api_base_url,
|
|
114
|
+
api_key=api_key,
|
|
115
|
+
api_key_header=api_key_header,
|
|
116
|
+
verify_tls=verify_tls,
|
|
117
|
+
timeout=timeout,
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
def _build_headers(
|
|
121
|
+
self, extra_headers: Mapping[str, str] | None = None
|
|
122
|
+
) -> dict[str, str]:
|
|
123
|
+
"""Build request headers with API key and optional extras.
|
|
124
|
+
|
|
125
|
+
Caller-supplied headers take precedence on case-insensitive name match,
|
|
126
|
+
so a forwarded X-API-Key from an authenticated user overrides the proxy's
|
|
127
|
+
service key (preserves caller identity for downstream RBAC).
|
|
128
|
+
"""
|
|
129
|
+
headers: dict[str, str] = {}
|
|
130
|
+
if self._api_key:
|
|
131
|
+
headers[self._api_key_header] = self._api_key
|
|
132
|
+
if extra_headers:
|
|
133
|
+
for incoming_key, incoming_value in extra_headers.items():
|
|
134
|
+
normalised = incoming_key.lower()
|
|
135
|
+
for existing in [k for k in headers if k.lower() == normalised]:
|
|
136
|
+
del headers[existing]
|
|
137
|
+
headers[incoming_key] = incoming_value
|
|
138
|
+
return headers
|
|
139
|
+
|
|
140
|
+
async def request(
|
|
141
|
+
self,
|
|
142
|
+
method: str,
|
|
143
|
+
path: str,
|
|
144
|
+
*,
|
|
145
|
+
json: Any = None,
|
|
146
|
+
params: dict[str, Any] | None = None,
|
|
147
|
+
headers: Mapping[str, str] | None = None,
|
|
148
|
+
cookies: dict[str, str] | None = None,
|
|
149
|
+
) -> ProxyResponse:
|
|
150
|
+
"""Send a proxied request to the API server.
|
|
151
|
+
|
|
152
|
+
Args:
|
|
153
|
+
method: HTTP method (GET, POST, PUT, DELETE, PATCH).
|
|
154
|
+
path: API path (e.g. ``/users/123``).
|
|
155
|
+
json: JSON body for POST/PUT/PATCH.
|
|
156
|
+
params: Query parameters.
|
|
157
|
+
headers: Additional headers to forward.
|
|
158
|
+
cookies: Cookies to forward (for session-based auth proxying).
|
|
159
|
+
|
|
160
|
+
Returns:
|
|
161
|
+
ProxyResponse with status_code, data, and optional error.
|
|
162
|
+
"""
|
|
163
|
+
url = f"{self._base_url}{path}"
|
|
164
|
+
merged_headers = self._build_headers(headers)
|
|
165
|
+
try:
|
|
166
|
+
async with httpx.AsyncClient(
|
|
167
|
+
verify=self._verify_tls,
|
|
168
|
+
timeout=httpx.Timeout(self._timeout),
|
|
169
|
+
cookies=cookies,
|
|
170
|
+
) as client:
|
|
171
|
+
response = await client.request(
|
|
172
|
+
method=method,
|
|
173
|
+
url=url,
|
|
174
|
+
json=json,
|
|
175
|
+
params=params,
|
|
176
|
+
headers=merged_headers,
|
|
177
|
+
)
|
|
178
|
+
try:
|
|
179
|
+
data = response.json()
|
|
180
|
+
except Exception:
|
|
181
|
+
data = response.text
|
|
182
|
+
if response.status_code >= 400:
|
|
183
|
+
return ProxyResponse(
|
|
184
|
+
status_code=response.status_code,
|
|
185
|
+
data=data,
|
|
186
|
+
error=f"API {method} {path} returned {response.status_code}",
|
|
187
|
+
headers=dict(response.headers),
|
|
188
|
+
)
|
|
189
|
+
return ProxyResponse(
|
|
190
|
+
status_code=response.status_code,
|
|
191
|
+
data=data,
|
|
192
|
+
headers=dict(response.headers),
|
|
193
|
+
)
|
|
194
|
+
except httpx.TimeoutException:
|
|
195
|
+
logger.warning("Proxy timeout: %s %s", method, url)
|
|
196
|
+
return ProxyResponse(status_code=504, error=f"Proxy timeout: {method} {path}")
|
|
197
|
+
except httpx.ConnectError as exc:
|
|
198
|
+
logger.warning("Proxy connect error: %s %s — %s", method, url, exc)
|
|
199
|
+
return ProxyResponse(status_code=502, error=f"API unreachable: {method} {path}")
|
|
200
|
+
except Exception as exc:
|
|
201
|
+
logger.error("Proxy error: %s %s — %s", method, url, exc, exc_info=True)
|
|
202
|
+
return ProxyResponse(status_code=500, error=f"Proxy error: {exc}")
|
|
203
|
+
|
|
204
|
+
async def get(self, path: str, **kwargs: Any) -> ProxyResponse:
|
|
205
|
+
"""GET request to the API server."""
|
|
206
|
+
return await self.request("GET", path, **kwargs)
|
|
207
|
+
|
|
208
|
+
async def post(self, path: str, **kwargs: Any) -> ProxyResponse:
|
|
209
|
+
"""POST request to the API server."""
|
|
210
|
+
return await self.request("POST", path, **kwargs)
|
|
211
|
+
|
|
212
|
+
async def put(self, path: str, **kwargs: Any) -> ProxyResponse:
|
|
213
|
+
"""PUT request to the API server."""
|
|
214
|
+
return await self.request("PUT", path, **kwargs)
|
|
215
|
+
|
|
216
|
+
async def delete(self, path: str, **kwargs: Any) -> ProxyResponse:
|
|
217
|
+
"""DELETE request to the API server."""
|
|
218
|
+
return await self.request("DELETE", path, **kwargs)
|
|
219
|
+
|
|
220
|
+
async def patch(self, path: str, **kwargs: Any) -> ProxyResponse:
|
|
221
|
+
"""PATCH request to the API server."""
|
|
222
|
+
return await self.request("PATCH", path, **kwargs)
|
|
@@ -0,0 +1,29 @@
|
|
|
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 — Webhook exports
|
|
16
|
+
#
|
|
17
|
+
# Licence: Proprietary — Cloud-Dog AI Platform
|
|
18
|
+
# Owner: Cloud-Dog AI
|
|
19
|
+
# Description: Webhook verification middleware exports.
|
|
20
|
+
# Related requirements: FR18.7
|
|
21
|
+
# Related architecture: SA1
|
|
22
|
+
|
|
23
|
+
"""Webhook middleware exports."""
|
|
24
|
+
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
from cloud_dog_api_kit.webhook.signature import WebhookSignatureMiddleware, compute_webhook_signature
|
|
28
|
+
|
|
29
|
+
__all__ = ["WebhookSignatureMiddleware", "compute_webhook_signature"]
|