mini-oss-osdk 0.1.0a3__tar.gz

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 (26) hide show
  1. mini_oss_osdk-0.1.0a3/LICENSE +13 -0
  2. mini_oss_osdk-0.1.0a3/MANIFEST.in +1 -0
  3. mini_oss_osdk-0.1.0a3/PKG-INFO +71 -0
  4. mini_oss_osdk-0.1.0a3/README.md +49 -0
  5. mini_oss_osdk-0.1.0a3/pyproject.toml +39 -0
  6. mini_oss_osdk-0.1.0a3/setup.cfg +4 -0
  7. mini_oss_osdk-0.1.0a3/src/mini_oss_osdk/__init__.py +45 -0
  8. mini_oss_osdk-0.1.0a3/src/mini_oss_osdk/client.py +285 -0
  9. mini_oss_osdk-0.1.0a3/src/mini_oss_osdk/facade.py +295 -0
  10. mini_oss_osdk-0.1.0a3/src/mini_oss_osdk/operation.py +94 -0
  11. mini_oss_osdk-0.1.0a3/src/mini_oss_osdk/py.typed +0 -0
  12. mini_oss_osdk-0.1.0a3/src/mini_oss_osdk/query.py +160 -0
  13. mini_oss_osdk-0.1.0a3/src/mini_oss_osdk/result.py +211 -0
  14. mini_oss_osdk-0.1.0a3/src/mini_oss_osdk.egg-info/PKG-INFO +71 -0
  15. mini_oss_osdk-0.1.0a3/src/mini_oss_osdk.egg-info/SOURCES.txt +24 -0
  16. mini_oss_osdk-0.1.0a3/src/mini_oss_osdk.egg-info/dependency_links.txt +1 -0
  17. mini_oss_osdk-0.1.0a3/src/mini_oss_osdk.egg-info/requires.txt +1 -0
  18. mini_oss_osdk-0.1.0a3/src/mini_oss_osdk.egg-info/top_level.txt +1 -0
  19. mini_oss_osdk-0.1.0a3/tests/fixtures/flight-passenger-order/README.md +15 -0
  20. mini_oss_osdk-0.1.0a3/tests/fixtures/flight-passenger-order/adult-passenger-order-aggregate.query.json +48 -0
  21. mini_oss_osdk-0.1.0a3/tests/fixtures/flight-passenger-order/operation.succeeded.json +13 -0
  22. mini_oss_osdk-0.1.0a3/tests/fixtures/flight-passenger-order/result-ref.json +30 -0
  23. mini_oss_osdk-0.1.0a3/tests/test_contract_fixture.py +32 -0
  24. mini_oss_osdk-0.1.0a3/tests/test_facade.py +272 -0
  25. mini_oss_osdk-0.1.0a3/tests/test_operation_client.py +175 -0
  26. mini_oss_osdk-0.1.0a3/tests/test_result_client.py +140 -0
@@ -0,0 +1,13 @@
1
+ Copyright (c) 2026 ITEM. All rights reserved.
2
+
3
+ This software and its associated documentation are proprietary to ITEM.
4
+ No permission is granted to use, copy, modify, merge, publish, distribute,
5
+ sublicense, sell, or create derivative works from this software except as
6
+ expressly authorized under a separate written agreement with ITEM.
7
+
8
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
9
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
10
+ FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. IN NO EVENT SHALL ITEM
11
+ BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY ARISING FROM, OUT OF, OR
12
+ IN CONNECTION WITH THE SOFTWARE OR THE USE OF OR OTHER DEALINGS IN THE
13
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ recursive-include tests/fixtures *.json *.md
@@ -0,0 +1,71 @@
1
+ Metadata-Version: 2.4
2
+ Name: mini-oss-osdk
3
+ Version: 0.1.0a3
4
+ Summary: Async Python client and object facade for the ITEM Mini-OSS runtime
5
+ License-Expression: LicenseRef-Proprietary
6
+ Keywords: item,ontology,osdk,object-query
7
+ Classifier: Development Status :: 3 - Alpha
8
+ Classifier: Intended Audience :: Developers
9
+ Classifier: Operating System :: OS Independent
10
+ Classifier: Programming Language :: Python :: 3 :: Only
11
+ Classifier: Programming Language :: Python :: 3.9
12
+ Classifier: Programming Language :: Python :: 3.10
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Programming Language :: Python :: 3.13
16
+ Classifier: Typing :: Typed
17
+ Requires-Python: >=3.9
18
+ Description-Content-Type: text/markdown
19
+ License-File: LICENSE
20
+ Requires-Dist: httpx<1,>=0.27
21
+ Dynamic: license-file
22
+
23
+ # Mini-OSS Python OSDK
24
+
25
+ Async Python builder/runtime for canonical Object Query, Operation polling and bounded Result Gateway access. `0.1.0a3` adds a unified high-level facade while retaining the low-level `ObjectEngineClient` and `ResultGatewayClient` APIs. Production and isolated Preview Staging API Key lanes have passed page, lookup iterator and high-level `get()` verification.
26
+
27
+ The public PyPI release lane is being prepared as distribution `mini-oss-osdk` with import `mini_oss_osdk`, but it is not published yet. A package name remains unreserved until the first successful registry upload.
28
+
29
+ ## High-level object lookup
30
+
31
+ ```python
32
+ from mini_oss_osdk import OSDKClient
33
+
34
+ async with OSDKClient(
35
+ tenant_id="01jabcdefgh1",
36
+ object_engine_url="https://object-engine.example.com",
37
+ result_gateway_url="https://object-results.example.com",
38
+ api_key=tenant_api_key,
39
+ ) as client:
40
+ restaurant = await client.ontology.objects.object(
41
+ "01jabcdefgh2", "Restaurant"
42
+ ).get(
43
+ "restaurant-001",
44
+ select=("name", "city"),
45
+ )
46
+
47
+ if restaurant is not None:
48
+ print(restaurant.rid, restaurant.primary_key, restaurant["name"])
49
+ ```
50
+
51
+ `get()` only orchestrates canonical lookup → execute → wait → bounded Result page. It never reads Dataset/Redash/Athena metadata or creates SQL. Generic runtime has no Catalog property list, so `select=()` requests identity only; callers list Property API names explicitly. A future generated ontology package may provide that static list.
52
+
53
+ Both service endpoints are required. HTTPS is mandatory except loopback HTTP used by tests. A local wait timeout does not cancel the server Operation.
54
+
55
+ Generated ontology-specific classes are not implemented.
56
+
57
+ ## Distribution verification
58
+
59
+ ```bash
60
+ python -m unittest discover -s tests
61
+ python -m ruff check src tests scripts
62
+ python -m build
63
+ python -m twine check dist/*
64
+ python scripts/verify_distribution.py dist
65
+ ```
66
+
67
+ Branch and `main` pipelines only test and build. A `v<version>` tag is the sole public PyPI upload authority, and the tag must exactly match wheel metadata before upload.
68
+
69
+ ## License
70
+
71
+ Proprietary. Copyright (c) 2026 ITEM. All rights reserved. See [`LICENSE`](LICENSE); public package availability does not grant permission to use, modify, or redistribute the software.
@@ -0,0 +1,49 @@
1
+ # Mini-OSS Python OSDK
2
+
3
+ Async Python builder/runtime for canonical Object Query, Operation polling and bounded Result Gateway access. `0.1.0a3` adds a unified high-level facade while retaining the low-level `ObjectEngineClient` and `ResultGatewayClient` APIs. Production and isolated Preview Staging API Key lanes have passed page, lookup iterator and high-level `get()` verification.
4
+
5
+ The public PyPI release lane is being prepared as distribution `mini-oss-osdk` with import `mini_oss_osdk`, but it is not published yet. A package name remains unreserved until the first successful registry upload.
6
+
7
+ ## High-level object lookup
8
+
9
+ ```python
10
+ from mini_oss_osdk import OSDKClient
11
+
12
+ async with OSDKClient(
13
+ tenant_id="01jabcdefgh1",
14
+ object_engine_url="https://object-engine.example.com",
15
+ result_gateway_url="https://object-results.example.com",
16
+ api_key=tenant_api_key,
17
+ ) as client:
18
+ restaurant = await client.ontology.objects.object(
19
+ "01jabcdefgh2", "Restaurant"
20
+ ).get(
21
+ "restaurant-001",
22
+ select=("name", "city"),
23
+ )
24
+
25
+ if restaurant is not None:
26
+ print(restaurant.rid, restaurant.primary_key, restaurant["name"])
27
+ ```
28
+
29
+ `get()` only orchestrates canonical lookup → execute → wait → bounded Result page. It never reads Dataset/Redash/Athena metadata or creates SQL. Generic runtime has no Catalog property list, so `select=()` requests identity only; callers list Property API names explicitly. A future generated ontology package may provide that static list.
30
+
31
+ Both service endpoints are required. HTTPS is mandatory except loopback HTTP used by tests. A local wait timeout does not cancel the server Operation.
32
+
33
+ Generated ontology-specific classes are not implemented.
34
+
35
+ ## Distribution verification
36
+
37
+ ```bash
38
+ python -m unittest discover -s tests
39
+ python -m ruff check src tests scripts
40
+ python -m build
41
+ python -m twine check dist/*
42
+ python scripts/verify_distribution.py dist
43
+ ```
44
+
45
+ Branch and `main` pipelines only test and build. A `v<version>` tag is the sole public PyPI upload authority, and the tag must exactly match wheel metadata before upload.
46
+
47
+ ## License
48
+
49
+ Proprietary. Copyright (c) 2026 ITEM. All rights reserved. See [`LICENSE`](LICENSE); public package availability does not grant permission to use, modify, or redistribute the software.
@@ -0,0 +1,39 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "mini-oss-osdk"
7
+ version = "0.1.0a3"
8
+ requires-python = ">=3.9"
9
+ description = "Async Python client and object facade for the ITEM Mini-OSS runtime"
10
+ readme = "README.md"
11
+ license = "LicenseRef-Proprietary"
12
+ license-files = ["LICENSE"]
13
+ keywords = ["item", "ontology", "osdk", "object-query"]
14
+ classifiers = [
15
+ "Development Status :: 3 - Alpha",
16
+ "Intended Audience :: Developers",
17
+ "Operating System :: OS Independent",
18
+ "Programming Language :: Python :: 3 :: Only",
19
+ "Programming Language :: Python :: 3.9",
20
+ "Programming Language :: Python :: 3.10",
21
+ "Programming Language :: Python :: 3.11",
22
+ "Programming Language :: Python :: 3.12",
23
+ "Programming Language :: Python :: 3.13",
24
+ "Typing :: Typed",
25
+ ]
26
+ dependencies = ["httpx>=0.27,<1"]
27
+
28
+ [tool.setuptools.packages.find]
29
+ where = ["src"]
30
+
31
+ [tool.setuptools.package-data]
32
+ mini_oss_osdk = ["py.typed"]
33
+
34
+ [tool.ruff]
35
+ target-version = "py39"
36
+ line-length = 100
37
+
38
+ [tool.ruff.lint]
39
+ select = ["E4", "E7", "E9", "F", "I"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,45 @@
1
+ from .client import (
2
+ EngineApiError,
3
+ HttpxTransport,
4
+ ObjectEngineClient,
5
+ OperationWaitTimeout,
6
+ TransportResponse,
7
+ )
8
+ from .facade import (
9
+ ObjectTypeClient,
10
+ OntologyClient,
11
+ OntologyObject,
12
+ OntologyObjects,
13
+ OperationExecutionError,
14
+ OSDKClient,
15
+ OSDKInvariantError,
16
+ )
17
+ from .operation import Operation, OperationError, OperationStatus
18
+ from .query import Field, Metric, ObjectSet, Query
19
+ from .result import ResultApiError, ResultGatewayClient, ResultPage, ResultRef
20
+
21
+ __all__ = [
22
+ "EngineApiError",
23
+ "Field",
24
+ "HttpxTransport",
25
+ "Metric",
26
+ "OSDKClient",
27
+ "OSDKInvariantError",
28
+ "ObjectEngineClient",
29
+ "ObjectTypeClient",
30
+ "OntologyClient",
31
+ "OntologyObject",
32
+ "OntologyObjects",
33
+ "ObjectSet",
34
+ "Operation",
35
+ "OperationError",
36
+ "OperationExecutionError",
37
+ "OperationStatus",
38
+ "OperationWaitTimeout",
39
+ "Query",
40
+ "ResultApiError",
41
+ "ResultGatewayClient",
42
+ "ResultPage",
43
+ "ResultRef",
44
+ "TransportResponse",
45
+ ]
@@ -0,0 +1,285 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import ipaddress
5
+ import time
6
+ from dataclasses import dataclass, field
7
+ from typing import Any, Awaitable, Callable, Mapping, Optional, Protocol, Union
8
+ from urllib.parse import quote, urlsplit
9
+
10
+ import httpx
11
+
12
+ from .operation import Operation
13
+ from .query import Query
14
+
15
+
16
+ @dataclass(frozen=True)
17
+ class TransportResponse:
18
+ status_code: int
19
+ body: Mapping[str, Any]
20
+ headers: Mapping[str, str] = field(default_factory=dict)
21
+
22
+
23
+ class AsyncTransport(Protocol):
24
+ async def request(
25
+ self,
26
+ method: str,
27
+ url: str,
28
+ *,
29
+ headers: Mapping[str, str],
30
+ json_body: Optional[Mapping[str, Any]] = None,
31
+ ) -> TransportResponse:
32
+ ...
33
+
34
+
35
+ class HttpxTransport:
36
+ def __init__(self, client: Optional[httpx.AsyncClient] = None) -> None:
37
+ self._client = client or httpx.AsyncClient()
38
+ self._owns_client = client is None
39
+
40
+ async def request(
41
+ self,
42
+ method: str,
43
+ url: str,
44
+ *,
45
+ headers: Mapping[str, str],
46
+ json_body: Optional[Mapping[str, Any]] = None,
47
+ ) -> TransportResponse:
48
+ response = await self._client.request(
49
+ method,
50
+ url,
51
+ headers=dict(headers),
52
+ json=dict(json_body) if json_body is not None else None,
53
+ )
54
+ payload = response.json()
55
+ if not isinstance(payload, Mapping):
56
+ raise ValueError("Object Engine response must be a JSON object")
57
+ return TransportResponse(
58
+ status_code=response.status_code,
59
+ body=dict(payload),
60
+ headers=dict(response.headers),
61
+ )
62
+
63
+ async def aclose(self) -> None:
64
+ if self._owns_client:
65
+ await self._client.aclose()
66
+
67
+
68
+ class EngineApiError(RuntimeError):
69
+ def __init__(
70
+ self,
71
+ *,
72
+ status_code: int,
73
+ code: str,
74
+ message: str,
75
+ retryable: bool,
76
+ request_id: Optional[str],
77
+ details: Mapping[str, Any],
78
+ ) -> None:
79
+ super().__init__(f"{code}: {message}")
80
+ self.status_code = status_code
81
+ self.code = code
82
+ self.message = message
83
+ self.retryable = retryable
84
+ self.request_id = request_id
85
+ self.details = dict(details)
86
+
87
+
88
+ class OperationWaitTimeout(TimeoutError):
89
+ def __init__(self, operation: Operation) -> None:
90
+ super().__init__(f"timed out waiting for Operation {operation.operation_id}")
91
+ self.operation = operation
92
+
93
+
94
+ QueryInput = Union[Query, Mapping[str, Any]]
95
+ Sleep = Callable[[float], Awaitable[None]]
96
+ Clock = Callable[[], float]
97
+
98
+
99
+ class ObjectEngineClient:
100
+ def __init__(
101
+ self,
102
+ *,
103
+ base_url: str,
104
+ api_key: str,
105
+ transport: Optional[AsyncTransport] = None,
106
+ ) -> None:
107
+ if not api_key:
108
+ raise ValueError("api_key must not be empty")
109
+ self._base_url = validate_service_base_url(base_url)
110
+ self._api_key = api_key
111
+ self._transport = transport or HttpxTransport()
112
+
113
+ async def aclose(self) -> None:
114
+ close = getattr(self._transport, "aclose", None)
115
+ if close is not None:
116
+ await close()
117
+
118
+ async def execute(
119
+ self,
120
+ *,
121
+ tenant_id: str,
122
+ namespace_id: str,
123
+ query: QueryInput,
124
+ idempotency_key: str,
125
+ ) -> Operation:
126
+ if not idempotency_key:
127
+ raise ValueError("idempotency_key must not be empty")
128
+ response = await self._request(
129
+ "POST",
130
+ self._query_path(tenant_id, namespace_id, ":execute"),
131
+ json_body=_query_wire(query),
132
+ extra_headers={"Idempotency-Key": idempotency_key},
133
+ )
134
+ return Operation.from_wire(response.body)
135
+
136
+ async def get_operation(
137
+ self,
138
+ *,
139
+ tenant_id: str,
140
+ namespace_id: str,
141
+ operation_id: str,
142
+ ) -> Operation:
143
+ response = await self._request(
144
+ "GET",
145
+ self._operation_path(tenant_id, namespace_id, operation_id),
146
+ )
147
+ return Operation.from_wire(response.body)
148
+
149
+ async def cancel(
150
+ self,
151
+ *,
152
+ tenant_id: str,
153
+ namespace_id: str,
154
+ operation_id: str,
155
+ ) -> Operation:
156
+ response = await self._request(
157
+ "POST",
158
+ self._operation_path(tenant_id, namespace_id, operation_id) + ":cancel",
159
+ )
160
+ return Operation.from_wire(response.body)
161
+
162
+ async def wait(
163
+ self,
164
+ operation: Operation,
165
+ *,
166
+ timeout: Optional[float] = None,
167
+ sleep: Sleep = asyncio.sleep,
168
+ monotonic: Clock = time.monotonic,
169
+ ) -> Operation:
170
+ if operation.is_terminal:
171
+ return operation
172
+ deadline = monotonic() + timeout if timeout is not None else None
173
+ current = operation
174
+ while not current.is_terminal:
175
+ if deadline is not None and monotonic() >= deadline:
176
+ raise OperationWaitTimeout(current)
177
+ delay = (current.next_poll_after_ms or 1000) / 1000.0
178
+ if deadline is not None:
179
+ delay = min(delay, max(0.0, deadline - monotonic()))
180
+ await sleep(delay)
181
+ if deadline is not None and monotonic() >= deadline:
182
+ raise OperationWaitTimeout(current)
183
+ current = await self.get_operation(
184
+ tenant_id=current.tenant_id,
185
+ namespace_id=current.namespace_id,
186
+ operation_id=current.operation_id,
187
+ )
188
+ return current
189
+
190
+ async def _request(
191
+ self,
192
+ method: str,
193
+ path: str,
194
+ *,
195
+ json_body: Optional[Mapping[str, Any]] = None,
196
+ extra_headers: Optional[Mapping[str, str]] = None,
197
+ ) -> TransportResponse:
198
+ headers = {
199
+ "Accept": "application/json",
200
+ "X-API-Key": self._api_key,
201
+ }
202
+ if extra_headers:
203
+ headers.update(extra_headers)
204
+ response = await self._transport.request(
205
+ method,
206
+ self._base_url + path,
207
+ headers=headers,
208
+ json_body=json_body,
209
+ )
210
+ if response.status_code >= 400:
211
+ payload = response.body
212
+ raise EngineApiError(
213
+ status_code=response.status_code,
214
+ code=str(payload.get("code", "ENGINE_HTTP_ERROR")),
215
+ message=str(payload.get("message", "Object Engine request failed")),
216
+ retryable=bool(payload.get("retryable", False)),
217
+ request_id=(
218
+ str(payload["requestId"])
219
+ if payload.get("requestId") is not None
220
+ else None
221
+ ),
222
+ details=(
223
+ dict(payload["details"])
224
+ if isinstance(payload.get("details"), Mapping)
225
+ else {}
226
+ ),
227
+ )
228
+ return response
229
+
230
+ @staticmethod
231
+ def _query_path(tenant_id: str, namespace_id: str, action: str) -> str:
232
+ return (
233
+ f"/v1/tenants/{quote(tenant_id, safe='')}/namespaces/"
234
+ f"{quote(namespace_id, safe='')}/queries{action}"
235
+ )
236
+
237
+ @staticmethod
238
+ def _operation_path(tenant_id: str, namespace_id: str, operation_id: str) -> str:
239
+ return (
240
+ f"/v1/tenants/{quote(tenant_id, safe='')}/namespaces/"
241
+ f"{quote(namespace_id, safe='')}/operations/"
242
+ f"{quote(operation_id, safe='')}"
243
+ )
244
+
245
+
246
+ def validate_service_base_url(base_url: str) -> str:
247
+ if not base_url:
248
+ raise ValueError("service base URL must not be empty")
249
+ parsed = urlsplit(base_url)
250
+ try:
251
+ parsed.port
252
+ except ValueError as error:
253
+ raise ValueError("service base URL has an invalid port") from error
254
+ if (
255
+ not parsed.hostname
256
+ or parsed.username is not None
257
+ or parsed.password is not None
258
+ or parsed.query
259
+ or parsed.fragment
260
+ ):
261
+ raise ValueError(
262
+ "service base URL must be absolute and omit credentials, query and fragment"
263
+ )
264
+ if parsed.scheme != "https" and not (
265
+ parsed.scheme == "http" and _is_loopback_host(parsed.hostname)
266
+ ):
267
+ raise ValueError(
268
+ "service base URL must use HTTPS; HTTP is only allowed for loopback tests"
269
+ )
270
+ return base_url.rstrip("/")
271
+
272
+
273
+ def _is_loopback_host(host: str) -> bool:
274
+ if host.lower() == "localhost":
275
+ return True
276
+ try:
277
+ return ipaddress.ip_address(host).is_loopback
278
+ except ValueError:
279
+ return False
280
+
281
+
282
+ def _query_wire(query: QueryInput) -> Mapping[str, Any]:
283
+ if isinstance(query, Query):
284
+ return query.to_wire()
285
+ return dict(query)