ucii-sdk 1.0.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.
ucii/__init__.py ADDED
@@ -0,0 +1,55 @@
1
+ """Public UCII Python SDK.
2
+
3
+ Only publication-approved interoperability contracts are exported here.
4
+ Private UCII trust, authority, enforcement, fleet, adoption, and provenance
5
+ implementation details are intentionally outside this package surface.
6
+ """
7
+
8
+ from .client import AsyncUCIIClient, UCIIClient
9
+ from .errors import (
10
+ UCIIAPIError,
11
+ UCIIAuthenticationError,
12
+ UCIIAuthorizationError,
13
+ UCIIConfigurationError,
14
+ UCIIConflictError,
15
+ UCIIError,
16
+ UCIIPaymentRequiredError,
17
+ UCIIResourceNotFoundError,
18
+ UCIIServiceError,
19
+ UCIITransportError,
20
+ UCIIValidationError,
21
+ )
22
+ from .participant import ParticipantContext
23
+ from .service_entitlement import (
24
+ ENTITLEMENT_PROOF_VERSION,
25
+ EntitlementProof,
26
+ EntitlementProofChallenge,
27
+ create_entitlement_proof,
28
+ entitlement_proof_header_value,
29
+ generate_entitlement_nonce,
30
+ )
31
+ from .signing import SigningProvider
32
+
33
+ __all__ = [
34
+ "AsyncUCIIClient",
35
+ "ENTITLEMENT_PROOF_VERSION",
36
+ "EntitlementProof",
37
+ "EntitlementProofChallenge",
38
+ "ParticipantContext",
39
+ "SigningProvider",
40
+ "UCIIAPIError",
41
+ "UCIIClient",
42
+ "UCIIAuthenticationError",
43
+ "UCIIAuthorizationError",
44
+ "UCIIConfigurationError",
45
+ "UCIIConflictError",
46
+ "UCIIError",
47
+ "UCIIPaymentRequiredError",
48
+ "UCIIResourceNotFoundError",
49
+ "UCIIServiceError",
50
+ "UCIITransportError",
51
+ "UCIIValidationError",
52
+ "create_entitlement_proof",
53
+ "entitlement_proof_header_value",
54
+ "generate_entitlement_nonce",
55
+ ]
ucii/_transport.py ADDED
@@ -0,0 +1,208 @@
1
+ """Internal UCII SDK HTTP transport implementation."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ import httpx
8
+
9
+ from .errors import (
10
+ UCIIAPIError,
11
+ UCIIAuthenticationError,
12
+ UCIIAuthorizationError,
13
+ UCIIPaymentRequiredError,
14
+ UCIIConflictError,
15
+ UCIIResourceNotFoundError,
16
+ UCIIServiceError,
17
+ UCIITransportError,
18
+ UCIIValidationError,
19
+ )
20
+
21
+
22
+ def _error_from_response(response: httpx.Response) -> UCIIAPIError:
23
+ """Convert an HTTP error response into the public SDK error model."""
24
+
25
+ status_code = response.status_code
26
+ request_id = response.headers.get("x-request-id")
27
+ details: dict[str, Any] | None = None
28
+ message = f"UCII API request failed with status {status_code}"
29
+ error_code: str | None = None
30
+
31
+ try:
32
+ payload = response.json()
33
+ except ValueError:
34
+ payload = None
35
+
36
+ if isinstance(payload, dict):
37
+ detail = payload.get("detail")
38
+
39
+ if isinstance(detail, str):
40
+ message = detail
41
+ elif isinstance(detail, dict):
42
+ details = dict(detail)
43
+ message = str(
44
+ detail.get("message")
45
+ or detail.get("detail")
46
+ or message
47
+ )
48
+ error_code = detail.get("code") or detail.get("error_code")
49
+ elif detail is not None:
50
+ details = {"detail": detail}
51
+
52
+ error_code = error_code or payload.get("code") or payload.get("error_code")
53
+
54
+ if details is None:
55
+ extra = payload.get("details")
56
+ if isinstance(extra, dict):
57
+ details = dict(extra)
58
+
59
+ request_id = request_id or payload.get("request_id")
60
+
61
+ error_kwargs = {
62
+ "message": message,
63
+ "error_code": error_code,
64
+ "request_id": request_id,
65
+ "status_code": status_code,
66
+ "details": details,
67
+ }
68
+
69
+ if status_code == 402:
70
+ return UCIIPaymentRequiredError(**error_kwargs)
71
+ if status_code == 401:
72
+ return UCIIAuthenticationError(**error_kwargs)
73
+ if status_code == 403:
74
+ return UCIIAuthorizationError(**error_kwargs)
75
+ if status_code == 404:
76
+ return UCIIResourceNotFoundError(**error_kwargs)
77
+ if status_code == 409:
78
+ return UCIIConflictError(**error_kwargs)
79
+ if status_code == 422:
80
+ return UCIIValidationError(**error_kwargs)
81
+ if status_code >= 500:
82
+ return UCIIServiceError(**error_kwargs)
83
+
84
+ return UCIIAPIError(**error_kwargs)
85
+
86
+
87
+ class HTTPTransport:
88
+ """Internal synchronous HTTP transport."""
89
+
90
+ def __init__(
91
+ self,
92
+ *,
93
+ base_url: str,
94
+ api_key: str | None = None,
95
+ timeout: float = 30.0,
96
+ ) -> None:
97
+ self.base_url = base_url.rstrip("/")
98
+ self.api_key = api_key
99
+ self.timeout = timeout
100
+ self._client = httpx.Client(
101
+ base_url=self.base_url,
102
+ timeout=self.timeout,
103
+ )
104
+
105
+ def request(
106
+ self,
107
+ method: str,
108
+ path: str,
109
+ *,
110
+ json: Any | None = None,
111
+ params: Any | None = None,
112
+ headers: dict[str, str] | None = None,
113
+ ) -> httpx.Response:
114
+ request_headers = self._headers()
115
+ if headers:
116
+ request_headers.update(headers)
117
+
118
+ try:
119
+ response = self._client.request(
120
+ method,
121
+ path,
122
+ json=json,
123
+ params=params,
124
+ headers=request_headers,
125
+ )
126
+ except httpx.HTTPError as exc:
127
+ raise UCIITransportError(str(exc)) from exc
128
+
129
+ if response.is_error:
130
+ raise _error_from_response(response)
131
+
132
+ return response
133
+
134
+ def close(self) -> None:
135
+ self._client.close()
136
+
137
+ def _headers(self) -> dict[str, str]:
138
+ headers = {
139
+ "Accept": "application/json",
140
+ "Content-Type": "application/json",
141
+ }
142
+
143
+ if self.api_key:
144
+ headers["Authorization"] = f"Bearer {self.api_key}"
145
+
146
+ return headers
147
+
148
+
149
+ class AsyncHTTPTransport:
150
+ """Internal asynchronous HTTP transport."""
151
+
152
+ def __init__(
153
+ self,
154
+ *,
155
+ base_url: str,
156
+ api_key: str | None = None,
157
+ timeout: float = 30.0,
158
+ ) -> None:
159
+ self.base_url = base_url.rstrip("/")
160
+ self.api_key = api_key
161
+ self.timeout = timeout
162
+ self._client = httpx.AsyncClient(
163
+ base_url=self.base_url,
164
+ timeout=self.timeout,
165
+ )
166
+
167
+ async def request(
168
+ self,
169
+ method: str,
170
+ path: str,
171
+ *,
172
+ json: Any | None = None,
173
+ params: Any | None = None,
174
+ headers: dict[str, str] | None = None,
175
+ ) -> httpx.Response:
176
+ request_headers = self._headers()
177
+ if headers:
178
+ request_headers.update(headers)
179
+
180
+ try:
181
+ response = await self._client.request(
182
+ method,
183
+ path,
184
+ json=json,
185
+ params=params,
186
+ headers=request_headers,
187
+ )
188
+ except httpx.HTTPError as exc:
189
+ raise UCIITransportError(str(exc)) from exc
190
+
191
+ if response.is_error:
192
+ raise _error_from_response(response)
193
+
194
+ return response
195
+
196
+ async def close(self) -> None:
197
+ await self._client.aclose()
198
+
199
+ def _headers(self) -> dict[str, str]:
200
+ headers = {
201
+ "Accept": "application/json",
202
+ "Content-Type": "application/json",
203
+ }
204
+
205
+ if self.api_key:
206
+ headers["Authorization"] = f"Bearer {self.api_key}"
207
+
208
+ return headers
ucii/authorization.py ADDED
@@ -0,0 +1,94 @@
1
+ """Public UCII authorization-check boundary."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+
8
+ def _payload(
9
+ *,
10
+ token: str,
11
+ identity_id: str,
12
+ credential_fingerprint: str,
13
+ operation: str,
14
+ message: str,
15
+ signature: str,
16
+ ) -> dict[str, Any]:
17
+ """Construct an authorization-check request payload."""
18
+
19
+ return {
20
+ "token": token,
21
+ "identity_id": identity_id,
22
+ "credential_fingerprint": credential_fingerprint,
23
+ "operation": operation,
24
+ "message": message,
25
+ "signature": signature,
26
+ }
27
+
28
+
29
+ class AuthorizationNamespace:
30
+ """Thin synchronous non-executing authorization boundary."""
31
+
32
+ def __init__(self, transport: Any) -> None:
33
+ self._transport = transport
34
+
35
+ def check(
36
+ self,
37
+ *,
38
+ token: str,
39
+ identity_id: str,
40
+ credential_fingerprint: str,
41
+ operation: str,
42
+ message: str,
43
+ signature: str,
44
+ ) -> dict[str, Any]:
45
+ """Evaluate authorization without executing the operation."""
46
+
47
+ response = self._transport.request(
48
+ "POST",
49
+ "/v1/authorization/check",
50
+ json=_payload(
51
+ token=token,
52
+ identity_id=identity_id,
53
+ credential_fingerprint=credential_fingerprint,
54
+ operation=operation,
55
+ message=message,
56
+ signature=signature,
57
+ ),
58
+ )
59
+
60
+ return response.json()
61
+
62
+
63
+ class AsyncAuthorizationNamespace:
64
+ """Thin asynchronous non-executing authorization boundary."""
65
+
66
+ def __init__(self, transport: Any) -> None:
67
+ self._transport = transport
68
+
69
+ async def check(
70
+ self,
71
+ *,
72
+ token: str,
73
+ identity_id: str,
74
+ credential_fingerprint: str,
75
+ operation: str,
76
+ message: str,
77
+ signature: str,
78
+ ) -> dict[str, Any]:
79
+ """Evaluate authorization without executing the operation."""
80
+
81
+ response = await self._transport.request(
82
+ "POST",
83
+ "/v1/authorization/check",
84
+ json=_payload(
85
+ token=token,
86
+ identity_id=identity_id,
87
+ credential_fingerprint=credential_fingerprint,
88
+ operation=operation,
89
+ message=message,
90
+ signature=signature,
91
+ ),
92
+ )
93
+
94
+ return response.json()
ucii/client.py ADDED
@@ -0,0 +1,92 @@
1
+ """Minimal public UCII SDK client.
2
+
3
+ This client intentionally exposes only publication-approved interoperability
4
+ namespaces. Private UCII trust, authority, enforcement, economic, fleet,
5
+ adoption, and provenance machinery is not part of this client surface.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from .authorization import (
11
+ AsyncAuthorizationNamespace,
12
+ AuthorizationNamespace,
13
+ )
14
+ from ._transport import AsyncHTTPTransport, HTTPTransport
15
+ from .credentials import AsyncCredentialsNamespace, CredentialsNamespace
16
+ from .identity import AsyncIdentityNamespace, IdentityNamespace
17
+ from .verification import AsyncVerificationNamespace, VerificationNamespace
18
+ from .x402 import AsyncX402Namespace, X402Namespace
19
+
20
+
21
+ class UCIIClient:
22
+ """Synchronous client for publication-approved UCII namespaces."""
23
+
24
+ def __init__(
25
+ self,
26
+ *,
27
+ base_url: str,
28
+ api_key: str | None = None,
29
+ timeout: float = 30.0,
30
+ ) -> None:
31
+ self.base_url = base_url.rstrip("/")
32
+ self.api_key = api_key
33
+ self.timeout = timeout
34
+
35
+ self._transport = HTTPTransport(
36
+ base_url=self.base_url,
37
+ api_key=self.api_key,
38
+ timeout=self.timeout,
39
+ )
40
+
41
+ self.identity = IdentityNamespace(self._transport)
42
+ self.authorization = AuthorizationNamespace(self._transport)
43
+ self.credentials = CredentialsNamespace(self._transport)
44
+ self.verification = VerificationNamespace(self._transport)
45
+ self.x402 = X402Namespace(self._transport)
46
+
47
+ def close(self) -> None:
48
+ """Close the underlying HTTP transport."""
49
+ self._transport.close()
50
+
51
+ def __enter__(self) -> "UCIIClient":
52
+ return self
53
+
54
+ def __exit__(self, exc_type, exc_value, traceback) -> None:
55
+ self.close()
56
+
57
+
58
+ class AsyncUCIIClient:
59
+ """Asynchronous client for publication-approved UCII namespaces."""
60
+
61
+ def __init__(
62
+ self,
63
+ *,
64
+ base_url: str,
65
+ api_key: str | None = None,
66
+ timeout: float = 30.0,
67
+ ) -> None:
68
+ self.base_url = base_url.rstrip("/")
69
+ self.api_key = api_key
70
+ self.timeout = timeout
71
+
72
+ self._transport = AsyncHTTPTransport(
73
+ base_url=self.base_url,
74
+ api_key=self.api_key,
75
+ timeout=self.timeout,
76
+ )
77
+
78
+ self.identity = AsyncIdentityNamespace(self._transport)
79
+ self.authorization = AsyncAuthorizationNamespace(self._transport)
80
+ self.credentials = AsyncCredentialsNamespace(self._transport)
81
+ self.verification = AsyncVerificationNamespace(self._transport)
82
+ self.x402 = AsyncX402Namespace(self._transport)
83
+
84
+ async def close(self) -> None:
85
+ """Close the underlying HTTP transport."""
86
+ await self._transport.close()
87
+
88
+ async def __aenter__(self) -> "AsyncUCIIClient":
89
+ return self
90
+
91
+ async def __aexit__(self, exc_type, exc_value, traceback) -> None:
92
+ await self.close()