treedx 0.1.1__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.
- treedx-0.1.1/.gitignore +8 -0
- treedx-0.1.1/PKG-INFO +188 -0
- treedx-0.1.1/README.md +175 -0
- treedx-0.1.1/pyproject.toml +28 -0
- treedx-0.1.1/scripts/check_treedx_generated_types.py +19 -0
- treedx-0.1.1/scripts/generate_treedx_openapi_types.py +57 -0
- treedx-0.1.1/sdk-manifest.yaml +64 -0
- treedx-0.1.1/src/treedx/__init__.py +17 -0
- treedx-0.1.1/src/treedx/adapters/__init__.py +43 -0
- treedx-0.1.1/src/treedx/adapters/admin.py +27 -0
- treedx-0.1.1/src/treedx/adapters/artifacts.py +23 -0
- treedx-0.1.1/src/treedx/adapters/audit.py +14 -0
- treedx-0.1.1/src/treedx/adapters/blobs.py +39 -0
- treedx-0.1.1/src/treedx/adapters/common.py +31 -0
- treedx-0.1.1/src/treedx/adapters/context.py +17 -0
- treedx-0.1.1/src/treedx/adapters/exec.py +14 -0
- treedx-0.1.1/src/treedx/adapters/federation.py +53 -0
- treedx-0.1.1/src/treedx/adapters/federation_internal.py +16 -0
- treedx-0.1.1/src/treedx/adapters/files.py +38 -0
- treedx-0.1.1/src/treedx/adapters/graph.py +38 -0
- treedx-0.1.1/src/treedx/adapters/migrations.py +17 -0
- treedx-0.1.1/src/treedx/adapters/mirrors.py +26 -0
- treedx-0.1.1/src/treedx/adapters/observability.py +23 -0
- treedx-0.1.1/src/treedx/adapters/policy.py +16 -0
- treedx-0.1.1/src/treedx/adapters/query.py +23 -0
- treedx-0.1.1/src/treedx/adapters/registry.py +23 -0
- treedx-0.1.1/src/treedx/adapters/repositories.py +38 -0
- treedx-0.1.1/src/treedx/adapters/search_index.py +20 -0
- treedx-0.1.1/src/treedx/adapters/snapshots.py +17 -0
- treedx-0.1.1/src/treedx/adapters/workspaces.py +20 -0
- treedx-0.1.1/src/treedx/auth.py +35 -0
- treedx-0.1.1/src/treedx/binary.py +35 -0
- treedx-0.1.1/src/treedx/client.py +123 -0
- treedx-0.1.1/src/treedx/config.py +17 -0
- treedx-0.1.1/src/treedx/conformance/__init__.py +7 -0
- treedx-0.1.1/src/treedx/conformance/adapter.py +67 -0
- treedx-0.1.1/src/treedx/errors.py +42 -0
- treedx-0.1.1/src/treedx/generated/__init__.py +13 -0
- treedx-0.1.1/src/treedx/generated/openapi_types.py +130 -0
- treedx-0.1.1/src/treedx/pagination.py +36 -0
- treedx-0.1.1/src/treedx/ports/__init__.py +40 -0
- treedx-0.1.1/src/treedx/ports/admin_port.py +3 -0
- treedx-0.1.1/src/treedx/ports/artifact_port.py +8 -0
- treedx-0.1.1/src/treedx/ports/audit_port.py +3 -0
- treedx-0.1.1/src/treedx/ports/auth_provider.py +3 -0
- treedx-0.1.1/src/treedx/ports/blob_port.py +8 -0
- treedx-0.1.1/src/treedx/ports/context_port.py +8 -0
- treedx-0.1.1/src/treedx/ports/exec_port.py +7 -0
- treedx-0.1.1/src/treedx/ports/federation_internal_port.py +3 -0
- treedx-0.1.1/src/treedx/ports/federation_port.py +8 -0
- treedx-0.1.1/src/treedx/ports/file_port.py +8 -0
- treedx-0.1.1/src/treedx/ports/graph_port.py +8 -0
- treedx-0.1.1/src/treedx/ports/migration_port.py +8 -0
- treedx-0.1.1/src/treedx/ports/mirror_port.py +8 -0
- treedx-0.1.1/src/treedx/ports/policy_port.py +3 -0
- treedx-0.1.1/src/treedx/ports/query_port.py +8 -0
- treedx-0.1.1/src/treedx/ports/registry_port.py +9 -0
- treedx-0.1.1/src/treedx/ports/repository_port.py +8 -0
- treedx-0.1.1/src/treedx/ports/search_index_port.py +3 -0
- treedx-0.1.1/src/treedx/ports/snapshot_port.py +8 -0
- treedx-0.1.1/src/treedx/ports/transport.py +3 -0
- treedx-0.1.1/src/treedx/ports/workspace_port.py +8 -0
- treedx-0.1.1/src/treedx/transport.py +83 -0
- treedx-0.1.1/tests/adapters/conftest.py +26 -0
- treedx-0.1.1/tests/adapters/test_artifacts.py +16 -0
- treedx-0.1.1/tests/adapters/test_blobs.py +24 -0
- treedx-0.1.1/tests/adapters/test_context.py +12 -0
- treedx-0.1.1/tests/adapters/test_exec.py +10 -0
- treedx-0.1.1/tests/adapters/test_federation.py +18 -0
- treedx-0.1.1/tests/adapters/test_files.py +20 -0
- treedx-0.1.1/tests/adapters/test_graph.py +12 -0
- treedx-0.1.1/tests/adapters/test_migrations.py +12 -0
- treedx-0.1.1/tests/adapters/test_mirrors.py +18 -0
- treedx-0.1.1/tests/adapters/test_observability.py +16 -0
- treedx-0.1.1/tests/adapters/test_query.py +16 -0
- treedx-0.1.1/tests/adapters/test_registry.py +16 -0
- treedx-0.1.1/tests/adapters/test_repositories.py +14 -0
- treedx-0.1.1/tests/adapters/test_snapshots.py +12 -0
- treedx-0.1.1/tests/adapters/test_workspaces.py +14 -0
- treedx-0.1.1/tests/conformance/test_sdk_conformance.py +44 -0
- treedx-0.1.1/tests/generated/test_exports.py +22 -0
- treedx-0.1.1/tests/generated/test_openapi_freshness.py +15 -0
- treedx-0.1.1/tests/generated/test_openapi_types.py +6 -0
- treedx-0.1.1/tests/integration/test_live_api.py +13 -0
- treedx-0.1.1/tests/unit/test_auth.py +11 -0
- treedx-0.1.1/tests/unit/test_binary.py +15 -0
- treedx-0.1.1/tests/unit/test_client.py +57 -0
- treedx-0.1.1/tests/unit/test_errors.py +17 -0
- treedx-0.1.1/tests/unit/test_pagination.py +10 -0
- treedx-0.1.1/tests/unit/test_transport.py +17 -0
treedx-0.1.1/.gitignore
ADDED
treedx-0.1.1/PKG-INFO
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: treedx
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: Generic Python SDK for TreeDX.
|
|
5
|
+
License-Expression: Apache-2.0
|
|
6
|
+
Requires-Python: >=3.11
|
|
7
|
+
Requires-Dist: httpx<1,>=0.28
|
|
8
|
+
Requires-Dist: pyyaml<7,>=6.0
|
|
9
|
+
Provides-Extra: dev
|
|
10
|
+
Requires-Dist: build<2,>=1.2; extra == 'dev'
|
|
11
|
+
Requires-Dist: pytest<9,>=8.0; extra == 'dev'
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
|
|
14
|
+
# TreeDX Python SDK
|
|
15
|
+
|
|
16
|
+
`treedx` is the generic Python SDK for TreeDX. It implements the shared
|
|
17
|
+
`packages/sdk-spec` architecture, follows `docs/api/openapi.yaml`, and does not
|
|
18
|
+
encode TreeSeed product semantics. `packages/trsd-sdk` is a downstream
|
|
19
|
+
TreeSeed consumer/reference only.
|
|
20
|
+
|
|
21
|
+
The current `sdk-manifest.yaml` reports modules, capabilities, and test roots as
|
|
22
|
+
`implemented`. The SDK exposes all 113 `/api/v1` OpenAPI operations through
|
|
23
|
+
first-class module methods and a validated raw operation fallback.
|
|
24
|
+
|
|
25
|
+
## Install
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
python -m pip install treedx
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Configure Client
|
|
32
|
+
|
|
33
|
+
```python
|
|
34
|
+
from treedx import TreeDxClient, TreeDxApiError
|
|
35
|
+
|
|
36
|
+
client = TreeDxClient(
|
|
37
|
+
base_url="http://localhost:4000",
|
|
38
|
+
token="...",
|
|
39
|
+
)
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
The client also accepts an auth provider, custom transport, default headers, and
|
|
43
|
+
timeout settings.
|
|
44
|
+
|
|
45
|
+
## Authenticate
|
|
46
|
+
|
|
47
|
+
Bearer authentication uses the `Authorization: Bearer <token>` header. Tokens
|
|
48
|
+
may come from `token` or an auth provider. The SDK must not place production
|
|
49
|
+
identity in request JSON and must not log bearer tokens.
|
|
50
|
+
|
|
51
|
+
## Basic Health Call
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
health = client.health()
|
|
55
|
+
version = client.version()
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Repository Query
|
|
59
|
+
|
|
60
|
+
Repository-scoped query helpers live under `client.query`:
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
results = client.query.search_files(
|
|
64
|
+
"repo_demo",
|
|
65
|
+
{"query": "release provenance", "paths": ["docs/**"]},
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
file = client.query.read_file(
|
|
69
|
+
"repo_demo",
|
|
70
|
+
{"ref": "refs/heads/main", "path": "docs/index.md"},
|
|
71
|
+
)
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Workspace File Lifecycle
|
|
75
|
+
|
|
76
|
+
Workspace-scoped file helpers live under `client.workspaces` and `client.files`:
|
|
77
|
+
|
|
78
|
+
```python
|
|
79
|
+
workspace = client.workspaces.create("repo_demo", {"ref": "refs/heads/main"})
|
|
80
|
+
|
|
81
|
+
client.files.write("workspace_123", {"path": "docs/new.md", "content": "# New"})
|
|
82
|
+
client.files.patch("workspace_123", {"path": "docs/new.md", "patch": "..."})
|
|
83
|
+
client.files.commit("workspace_123", {"message": "Update docs"})
|
|
84
|
+
client.workspaces.close("workspace_123")
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Blob Upload And Download
|
|
88
|
+
|
|
89
|
+
Binary helpers preserve byte payloads and reject strings as binary input.
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
client.blobs.upload("workspace_123", b"\x01\x02\x03")
|
|
93
|
+
blob = client.blobs.download("workspace_123", {"path": "asset.bin"})
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Multipart helpers expose create, part upload, complete, and abort:
|
|
97
|
+
|
|
98
|
+
```python
|
|
99
|
+
upload = client.blobs.create_multipart_upload("workspace_123", {"path": "large.bin"})
|
|
100
|
+
client.blobs.upload_part("workspace_123", upload["uploadId"], 1, b"\x01")
|
|
101
|
+
client.blobs.complete_multipart_upload(
|
|
102
|
+
"workspace_123",
|
|
103
|
+
upload["uploadId"],
|
|
104
|
+
{"parts": [{"partNumber": 1}]},
|
|
105
|
+
)
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Graph And Context Query
|
|
109
|
+
|
|
110
|
+
```python
|
|
111
|
+
client.graph.refresh("repo_demo")
|
|
112
|
+
graph = client.graph.query("repo_demo", {"query": "MATCH ..."})
|
|
113
|
+
context = client.context.build("repo_demo", {"query": "ctx docs"})
|
|
114
|
+
parsed = client.context.parse("repo_demo", {"source": "ctx docs"})
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Federated Query
|
|
118
|
+
|
|
119
|
+
Federation helpers use portfolio/global TreeDX routes rather than a single
|
|
120
|
+
configured repository:
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
plan = client.federation.plan({"query": "release provenance"})
|
|
124
|
+
results = client.federation.search({"query": "release provenance"})
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Scoped Admin And Internal Modules
|
|
128
|
+
|
|
129
|
+
Full OpenAPI coverage includes sensitive scoped modules: Admin, Audit, Policy,
|
|
130
|
+
SearchIndex, and FederationInternal. These APIs require appropriate TreeDX
|
|
131
|
+
credentials and should be used carefully against production systems. They remain
|
|
132
|
+
generic TreeDX APIs and do not encode TreeSeed product semantics.
|
|
133
|
+
|
|
134
|
+
The raw operation fallback validates method/path pairs against generated OpenAPI
|
|
135
|
+
metadata before dispatch.
|
|
136
|
+
|
|
137
|
+
## Error Handling
|
|
138
|
+
|
|
139
|
+
Non-2xx responses and network failures raise `TreeDxApiError`. The error keeps
|
|
140
|
+
`status`, `code`, `message`, `details`, and `payload` available. Network
|
|
141
|
+
failures use `status=0` and `code="network_error"`.
|
|
142
|
+
|
|
143
|
+
```python
|
|
144
|
+
try:
|
|
145
|
+
client.whoami()
|
|
146
|
+
except TreeDxApiError as error:
|
|
147
|
+
print(error.status, error.code, error.message)
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## Pagination
|
|
151
|
+
|
|
152
|
+
`TreeDxPage` and `TreeDxCursor` model opaque server-owned cursor pagination.
|
|
153
|
+
Helpers preserve cursor metadata and do not decode cursor internals.
|
|
154
|
+
|
|
155
|
+
## Binary And Multipart
|
|
156
|
+
|
|
157
|
+
Binary helpers accept `bytes`, `bytearray`, `memoryview`, and binary streams.
|
|
158
|
+
Multipart helper metadata is represented by `MultipartUpload`.
|
|
159
|
+
|
|
160
|
+
## Conformance
|
|
161
|
+
|
|
162
|
+
The conformance adapter loads Phase 7 scenario records and reports
|
|
163
|
+
`live or configured` until executable live dispatch is wired. It must not fake
|
|
164
|
+
conformance success.
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
python -m pytest tests/conformance
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## Integration
|
|
171
|
+
|
|
172
|
+
Integration tests call a live TreeDX server only when `TREEDX_BASE_URL` is set.
|
|
173
|
+
Without that environment variable, they skip cleanly.
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
python -m pytest tests/integration
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## Development Commands
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
python -m pip install -e ".[dev]"
|
|
183
|
+
python scripts/check_treedx_generated_types.py
|
|
184
|
+
python -m build
|
|
185
|
+
python -m pytest tests/conformance
|
|
186
|
+
python -m pytest tests/integration
|
|
187
|
+
python -m pytest
|
|
188
|
+
```
|
treedx-0.1.1/README.md
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
# TreeDX Python SDK
|
|
2
|
+
|
|
3
|
+
`treedx` is the generic Python SDK for TreeDX. It implements the shared
|
|
4
|
+
`packages/sdk-spec` architecture, follows `docs/api/openapi.yaml`, and does not
|
|
5
|
+
encode TreeSeed product semantics. `packages/trsd-sdk` is a downstream
|
|
6
|
+
TreeSeed consumer/reference only.
|
|
7
|
+
|
|
8
|
+
The current `sdk-manifest.yaml` reports modules, capabilities, and test roots as
|
|
9
|
+
`implemented`. The SDK exposes all 113 `/api/v1` OpenAPI operations through
|
|
10
|
+
first-class module methods and a validated raw operation fallback.
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
python -m pip install treedx
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Configure Client
|
|
19
|
+
|
|
20
|
+
```python
|
|
21
|
+
from treedx import TreeDxClient, TreeDxApiError
|
|
22
|
+
|
|
23
|
+
client = TreeDxClient(
|
|
24
|
+
base_url="http://localhost:4000",
|
|
25
|
+
token="...",
|
|
26
|
+
)
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
The client also accepts an auth provider, custom transport, default headers, and
|
|
30
|
+
timeout settings.
|
|
31
|
+
|
|
32
|
+
## Authenticate
|
|
33
|
+
|
|
34
|
+
Bearer authentication uses the `Authorization: Bearer <token>` header. Tokens
|
|
35
|
+
may come from `token` or an auth provider. The SDK must not place production
|
|
36
|
+
identity in request JSON and must not log bearer tokens.
|
|
37
|
+
|
|
38
|
+
## Basic Health Call
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
health = client.health()
|
|
42
|
+
version = client.version()
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Repository Query
|
|
46
|
+
|
|
47
|
+
Repository-scoped query helpers live under `client.query`:
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
results = client.query.search_files(
|
|
51
|
+
"repo_demo",
|
|
52
|
+
{"query": "release provenance", "paths": ["docs/**"]},
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
file = client.query.read_file(
|
|
56
|
+
"repo_demo",
|
|
57
|
+
{"ref": "refs/heads/main", "path": "docs/index.md"},
|
|
58
|
+
)
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Workspace File Lifecycle
|
|
62
|
+
|
|
63
|
+
Workspace-scoped file helpers live under `client.workspaces` and `client.files`:
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
workspace = client.workspaces.create("repo_demo", {"ref": "refs/heads/main"})
|
|
67
|
+
|
|
68
|
+
client.files.write("workspace_123", {"path": "docs/new.md", "content": "# New"})
|
|
69
|
+
client.files.patch("workspace_123", {"path": "docs/new.md", "patch": "..."})
|
|
70
|
+
client.files.commit("workspace_123", {"message": "Update docs"})
|
|
71
|
+
client.workspaces.close("workspace_123")
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Blob Upload And Download
|
|
75
|
+
|
|
76
|
+
Binary helpers preserve byte payloads and reject strings as binary input.
|
|
77
|
+
|
|
78
|
+
```python
|
|
79
|
+
client.blobs.upload("workspace_123", b"\x01\x02\x03")
|
|
80
|
+
blob = client.blobs.download("workspace_123", {"path": "asset.bin"})
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Multipart helpers expose create, part upload, complete, and abort:
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
upload = client.blobs.create_multipart_upload("workspace_123", {"path": "large.bin"})
|
|
87
|
+
client.blobs.upload_part("workspace_123", upload["uploadId"], 1, b"\x01")
|
|
88
|
+
client.blobs.complete_multipart_upload(
|
|
89
|
+
"workspace_123",
|
|
90
|
+
upload["uploadId"],
|
|
91
|
+
{"parts": [{"partNumber": 1}]},
|
|
92
|
+
)
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Graph And Context Query
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
client.graph.refresh("repo_demo")
|
|
99
|
+
graph = client.graph.query("repo_demo", {"query": "MATCH ..."})
|
|
100
|
+
context = client.context.build("repo_demo", {"query": "ctx docs"})
|
|
101
|
+
parsed = client.context.parse("repo_demo", {"source": "ctx docs"})
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Federated Query
|
|
105
|
+
|
|
106
|
+
Federation helpers use portfolio/global TreeDX routes rather than a single
|
|
107
|
+
configured repository:
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
plan = client.federation.plan({"query": "release provenance"})
|
|
111
|
+
results = client.federation.search({"query": "release provenance"})
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Scoped Admin And Internal Modules
|
|
115
|
+
|
|
116
|
+
Full OpenAPI coverage includes sensitive scoped modules: Admin, Audit, Policy,
|
|
117
|
+
SearchIndex, and FederationInternal. These APIs require appropriate TreeDX
|
|
118
|
+
credentials and should be used carefully against production systems. They remain
|
|
119
|
+
generic TreeDX APIs and do not encode TreeSeed product semantics.
|
|
120
|
+
|
|
121
|
+
The raw operation fallback validates method/path pairs against generated OpenAPI
|
|
122
|
+
metadata before dispatch.
|
|
123
|
+
|
|
124
|
+
## Error Handling
|
|
125
|
+
|
|
126
|
+
Non-2xx responses and network failures raise `TreeDxApiError`. The error keeps
|
|
127
|
+
`status`, `code`, `message`, `details`, and `payload` available. Network
|
|
128
|
+
failures use `status=0` and `code="network_error"`.
|
|
129
|
+
|
|
130
|
+
```python
|
|
131
|
+
try:
|
|
132
|
+
client.whoami()
|
|
133
|
+
except TreeDxApiError as error:
|
|
134
|
+
print(error.status, error.code, error.message)
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## Pagination
|
|
138
|
+
|
|
139
|
+
`TreeDxPage` and `TreeDxCursor` model opaque server-owned cursor pagination.
|
|
140
|
+
Helpers preserve cursor metadata and do not decode cursor internals.
|
|
141
|
+
|
|
142
|
+
## Binary And Multipart
|
|
143
|
+
|
|
144
|
+
Binary helpers accept `bytes`, `bytearray`, `memoryview`, and binary streams.
|
|
145
|
+
Multipart helper metadata is represented by `MultipartUpload`.
|
|
146
|
+
|
|
147
|
+
## Conformance
|
|
148
|
+
|
|
149
|
+
The conformance adapter loads Phase 7 scenario records and reports
|
|
150
|
+
`live or configured` until executable live dispatch is wired. It must not fake
|
|
151
|
+
conformance success.
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
python -m pytest tests/conformance
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## Integration
|
|
158
|
+
|
|
159
|
+
Integration tests call a live TreeDX server only when `TREEDX_BASE_URL` is set.
|
|
160
|
+
Without that environment variable, they skip cleanly.
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
python -m pytest tests/integration
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## Development Commands
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
python -m pip install -e ".[dev]"
|
|
170
|
+
python scripts/check_treedx_generated_types.py
|
|
171
|
+
python -m build
|
|
172
|
+
python -m pytest tests/conformance
|
|
173
|
+
python -m pytest tests/integration
|
|
174
|
+
python -m pytest
|
|
175
|
+
```
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling>=1.27"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "treedx"
|
|
7
|
+
version = "0.1.1"
|
|
8
|
+
description = "Generic Python SDK for TreeDX."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
|
+
license = "Apache-2.0"
|
|
12
|
+
dependencies = [
|
|
13
|
+
"httpx>=0.28,<1",
|
|
14
|
+
"PyYAML>=6.0,<7"
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
[project.optional-dependencies]
|
|
18
|
+
dev = [
|
|
19
|
+
"pytest>=8.0,<9",
|
|
20
|
+
"build>=1.2,<2"
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
[tool.hatch.build.targets.wheel]
|
|
24
|
+
packages = ["src/treedx"]
|
|
25
|
+
|
|
26
|
+
[tool.pytest.ini_options]
|
|
27
|
+
testpaths = ["tests"]
|
|
28
|
+
pythonpath = ["src"]
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from generate_treedx_openapi_types import OUTPUT_PATH, render_openapi_types
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def main() -> None:
|
|
10
|
+
expected = render_openapi_types()
|
|
11
|
+
actual = OUTPUT_PATH.read_text(encoding="utf8") if OUTPUT_PATH.exists() else ""
|
|
12
|
+
if actual != expected:
|
|
13
|
+
print("TreeDX generated OpenAPI metadata is stale. Run scripts/generate_treedx_openapi_types.py.", file=sys.stderr)
|
|
14
|
+
raise SystemExit(1)
|
|
15
|
+
print("TreeDX generated OpenAPI metadata is fresh")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
if __name__ == "__main__":
|
|
19
|
+
main()
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
import re
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
PACKAGE_ROOT = Path(__file__).resolve().parents[1]
|
|
8
|
+
REPO_ROOT = PACKAGE_ROOT.parents[1]
|
|
9
|
+
OPENAPI_PATH = REPO_ROOT / "docs" / "api" / "openapi.yaml"
|
|
10
|
+
OUTPUT_PATH = PACKAGE_ROOT / "src" / "treedx" / "generated" / "openapi_types.py"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def render_openapi_types() -> str:
|
|
14
|
+
operation_keys: set[tuple[str, str]] = set()
|
|
15
|
+
current_path: str | None = None
|
|
16
|
+
in_paths = False
|
|
17
|
+
for line in OPENAPI_PATH.read_text(encoding="utf8").splitlines():
|
|
18
|
+
if line == "paths:":
|
|
19
|
+
in_paths = True
|
|
20
|
+
continue
|
|
21
|
+
if in_paths and line and not line.startswith(" "):
|
|
22
|
+
break
|
|
23
|
+
if not in_paths:
|
|
24
|
+
continue
|
|
25
|
+
path_match = re.match(r"^ (/api/v1/[^:]+):\s*$", line)
|
|
26
|
+
if path_match:
|
|
27
|
+
current_path = path_match.group(1)
|
|
28
|
+
continue
|
|
29
|
+
method_match = re.match(r"^ (get|post|put|patch|delete):\s*$", line)
|
|
30
|
+
if current_path and method_match:
|
|
31
|
+
operation_keys.add((method_match.group(1).upper(), current_path))
|
|
32
|
+
operations = [{"method": method, "path": path} for method, path in operation_keys]
|
|
33
|
+
operations.sort(key=lambda operation: f"{operation['method']} {operation['path']}")
|
|
34
|
+
rendered_operations = ",\n".join(
|
|
35
|
+
f" {{\"method\": \"{operation['method']}\", \"path\": \"{operation['path']}\"}}"
|
|
36
|
+
for operation in operations
|
|
37
|
+
)
|
|
38
|
+
return (
|
|
39
|
+
"# Generated by scripts/generate_treedx_openapi_types.py. Do not edit by hand.\n\n"
|
|
40
|
+
"from typing import Final, Literal, TypedDict\n\n\n"
|
|
41
|
+
"TreeDxOpenApiMethod = Literal[\"GET\", \"POST\", \"PUT\", \"PATCH\", \"DELETE\"]\n\n\n"
|
|
42
|
+
"class TreeDxOpenApiOperation(TypedDict):\n"
|
|
43
|
+
" method: TreeDxOpenApiMethod\n"
|
|
44
|
+
" path: str\n\n\n"
|
|
45
|
+
f"TREEDX_OPENAPI_OPERATION_COUNT: Final[int] = {len(operations)}\n\n"
|
|
46
|
+
"TREEDX_OPENAPI_OPERATIONS: Final[tuple[TreeDxOpenApiOperation, ...]] = (\n"
|
|
47
|
+
f"{rendered_operations}\n"
|
|
48
|
+
")\n"
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def main() -> None:
|
|
53
|
+
OUTPUT_PATH.write_text(render_openapi_types(), encoding="utf8")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
if __name__ == "__main__":
|
|
57
|
+
main()
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
sdk: treedx-python
|
|
2
|
+
language: python
|
|
3
|
+
version: 0.1.1
|
|
4
|
+
sdkSpecVersion: 0.1.0
|
|
5
|
+
openapiVersion: 0.10.0
|
|
6
|
+
testLayout:
|
|
7
|
+
unit: implemented
|
|
8
|
+
adapters: implemented
|
|
9
|
+
generated: implemented
|
|
10
|
+
conformance: implemented
|
|
11
|
+
integration: implemented
|
|
12
|
+
modules:
|
|
13
|
+
Client: implemented
|
|
14
|
+
Auth: implemented
|
|
15
|
+
Repositories: implemented
|
|
16
|
+
Workspaces: implemented
|
|
17
|
+
Files: implemented
|
|
18
|
+
Blobs: implemented
|
|
19
|
+
Query: implemented
|
|
20
|
+
Graph: implemented
|
|
21
|
+
Context: implemented
|
|
22
|
+
Federation: implemented
|
|
23
|
+
FederationInternal: implemented
|
|
24
|
+
Registry: implemented
|
|
25
|
+
Snapshots: implemented
|
|
26
|
+
Artifacts: implemented
|
|
27
|
+
Mirrors: implemented
|
|
28
|
+
Migrations: implemented
|
|
29
|
+
Exec: implemented
|
|
30
|
+
Observability: implemented
|
|
31
|
+
Admin: implemented
|
|
32
|
+
Audit: implemented
|
|
33
|
+
Policy: implemented
|
|
34
|
+
SearchIndex: implemented
|
|
35
|
+
capabilities:
|
|
36
|
+
health.basic: implemented
|
|
37
|
+
auth.whoami: implemented
|
|
38
|
+
auth.mode_dev_token: implemented
|
|
39
|
+
repositories.lifecycle: implemented
|
|
40
|
+
repositories.push_sync: implemented
|
|
41
|
+
workspaces.lifecycle: implemented
|
|
42
|
+
files.lifecycle: implemented
|
|
43
|
+
blobs.binary: implemented
|
|
44
|
+
blobs.multipart: implemented
|
|
45
|
+
query.repository: implemented
|
|
46
|
+
graph.repository: implemented
|
|
47
|
+
graph.expanded_repository: implemented
|
|
48
|
+
context.repository: implemented
|
|
49
|
+
federation.global_query: implemented
|
|
50
|
+
federation.catalog_peers_routes: implemented
|
|
51
|
+
federation.internal_node_operations: implemented
|
|
52
|
+
registry.routing: implemented
|
|
53
|
+
snapshots.lifecycle: implemented
|
|
54
|
+
artifacts.lifecycle: implemented
|
|
55
|
+
mirrors.lifecycle: implemented
|
|
56
|
+
migrations.lifecycle: implemented
|
|
57
|
+
exec.workspace: implemented
|
|
58
|
+
observability.health_metrics: implemented
|
|
59
|
+
admin.storage_operations: implemented
|
|
60
|
+
admin.repository_operations: implemented
|
|
61
|
+
admin.workspace_artifact_operations: implemented
|
|
62
|
+
audit.events: implemented
|
|
63
|
+
policy.capabilities_grants: implemented
|
|
64
|
+
search_index.lifecycle: implemented
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from .binary import BinaryBody, MultipartUpload
|
|
2
|
+
from .client import TreeDxClient, TreeDxFederatedClient, TreeDxRegistryClient
|
|
3
|
+
from .config import TreeDxClientConfig
|
|
4
|
+
from .errors import TreeDxApiError
|
|
5
|
+
from .pagination import TreeDxCursor, TreeDxPage
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"BinaryBody",
|
|
9
|
+
"MultipartUpload",
|
|
10
|
+
"TreeDxApiError",
|
|
11
|
+
"TreeDxClient",
|
|
12
|
+
"TreeDxClientConfig",
|
|
13
|
+
"TreeDxCursor",
|
|
14
|
+
"TreeDxFederatedClient",
|
|
15
|
+
"TreeDxPage",
|
|
16
|
+
"TreeDxRegistryClient",
|
|
17
|
+
]
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
from .federation_internal import FederationInternalAdapter
|
|
2
|
+
from .search_index import SearchIndexAdapter
|
|
3
|
+
from .policy import PolicyAdapter
|
|
4
|
+
from .audit import AuditAdapter
|
|
5
|
+
from .admin import AdminAdapter
|
|
6
|
+
from .artifacts import ArtifactsAdapter
|
|
7
|
+
from .blobs import BlobsAdapter
|
|
8
|
+
from .context import ContextAdapter
|
|
9
|
+
from .exec import ExecAdapter
|
|
10
|
+
from .federation import FederationAdapter
|
|
11
|
+
from .files import FilesAdapter
|
|
12
|
+
from .graph import GraphAdapter
|
|
13
|
+
from .migrations import MigrationsAdapter
|
|
14
|
+
from .mirrors import MirrorsAdapter
|
|
15
|
+
from .observability import ObservabilityAdapter
|
|
16
|
+
from .query import QueryAdapter
|
|
17
|
+
from .registry import RegistryAdapter
|
|
18
|
+
from .repositories import RepositoriesAdapter
|
|
19
|
+
from .snapshots import SnapshotsAdapter
|
|
20
|
+
from .workspaces import WorkspacesAdapter
|
|
21
|
+
|
|
22
|
+
__all__ = [
|
|
23
|
+
"FederationInternalAdapter",
|
|
24
|
+
"SearchIndexAdapter",
|
|
25
|
+
"PolicyAdapter",
|
|
26
|
+
"AuditAdapter",
|
|
27
|
+
"AdminAdapter",
|
|
28
|
+
"ArtifactsAdapter",
|
|
29
|
+
"BlobsAdapter",
|
|
30
|
+
"ContextAdapter",
|
|
31
|
+
"ExecAdapter",
|
|
32
|
+
"FederationAdapter",
|
|
33
|
+
"FilesAdapter",
|
|
34
|
+
"GraphAdapter",
|
|
35
|
+
"MigrationsAdapter",
|
|
36
|
+
"MirrorsAdapter",
|
|
37
|
+
"ObservabilityAdapter",
|
|
38
|
+
"QueryAdapter",
|
|
39
|
+
"RegistryAdapter",
|
|
40
|
+
"RepositoriesAdapter",
|
|
41
|
+
"SnapshotsAdapter",
|
|
42
|
+
"WorkspacesAdapter",
|
|
43
|
+
]
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from .common import json_request
|
|
6
|
+
from treedx.transport import Transport
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class AdminAdapter:
|
|
10
|
+
def __init__(self, transport: Transport) -> None:
|
|
11
|
+
self.transport = transport
|
|
12
|
+
|
|
13
|
+
def deep_health(self) -> Any: return json_request(self.transport, "GET", "/api/v1/admin/health/deep")
|
|
14
|
+
def storage_health(self) -> Any: return json_request(self.transport, "GET", "/api/v1/admin/storage/health")
|
|
15
|
+
def storage_check(self, body: Any | None = None) -> Any: return json_request(self.transport, "POST", "/api/v1/admin/storage/check", body)
|
|
16
|
+
def storage_recover(self, body: Any | None = None) -> Any: return json_request(self.transport, "POST", "/api/v1/admin/storage/recover", body)
|
|
17
|
+
def storage_compact(self, body: Any | None = None) -> Any: return json_request(self.transport, "POST", "/api/v1/admin/storage/compact", body)
|
|
18
|
+
def storage_backup(self, body: Any | None = None) -> Any: return json_request(self.transport, "POST", "/api/v1/admin/storage/backup", body)
|
|
19
|
+
def storage_migrations(self) -> Any: return json_request(self.transport, "GET", "/api/v1/admin/storage/migrations")
|
|
20
|
+
def storage_migration_plan(self, body: Any) -> Any: return json_request(self.transport, "POST", "/api/v1/admin/storage/migrations/plan", body)
|
|
21
|
+
def storage_migration_apply(self, body: Any) -> Any: return json_request(self.transport, "POST", "/api/v1/admin/storage/migrations/apply", body)
|
|
22
|
+
def storage_migration_rollback(self, body: Any) -> Any: return json_request(self.transport, "POST", "/api/v1/admin/storage/migrations/rollback", body)
|
|
23
|
+
def storage_restore_verify(self, body: Any) -> Any: return json_request(self.transport, "POST", "/api/v1/admin/storage/restore/verify", body)
|
|
24
|
+
def storage_restore(self, body: Any) -> Any: return json_request(self.transport, "POST", "/api/v1/admin/storage/restore", body)
|
|
25
|
+
def quarantined_workspaces(self) -> Any: return json_request(self.transport, "GET", "/api/v1/admin/workspaces/quarantined")
|
|
26
|
+
def cleanup_artifacts(self, body: Any | None = None) -> Any: return json_request(self.transport, "POST", "/api/v1/admin/artifacts/cleanup", body)
|
|
27
|
+
def import_local_repo(self, body: Any) -> Any: return json_request(self.transport, "POST", "/api/v1/admin/repos/import-local", body)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from .common import json_request, segment
|
|
6
|
+
from treedx.transport import Transport
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ArtifactsAdapter:
|
|
10
|
+
def __init__(self, transport: Transport) -> None:
|
|
11
|
+
self.transport = transport
|
|
12
|
+
|
|
13
|
+
def export(self, repo_id: str, body: Any) -> Any:
|
|
14
|
+
return json_request(self.transport, "POST", f"/api/v1/repos/{segment(repo_id)}/artifacts/export", body)
|
|
15
|
+
|
|
16
|
+
def list(self, repo_id: str) -> Any:
|
|
17
|
+
return json_request(self.transport, "GET", f"/api/v1/repos/{segment(repo_id)}/artifacts")
|
|
18
|
+
|
|
19
|
+
def get(self, repo_id: str, artifact_id: str) -> Any:
|
|
20
|
+
return json_request(self.transport, "GET", f"/api/v1/repos/{segment(repo_id)}/artifacts/{segment(artifact_id)}")
|
|
21
|
+
|
|
22
|
+
def delete(self, repo_id: str, artifact_id: str) -> Any:
|
|
23
|
+
return json_request(self.transport, "DELETE", f"/api/v1/repos/{segment(repo_id)}/artifacts/{segment(artifact_id)}")
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, Mapping
|
|
4
|
+
|
|
5
|
+
from .common import json_request
|
|
6
|
+
from treedx.transport import Transport
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class AuditAdapter:
|
|
10
|
+
def __init__(self, transport: Transport) -> None:
|
|
11
|
+
self.transport = transport
|
|
12
|
+
|
|
13
|
+
def events(self, query: Mapping[str, str | int | float | bool | None] | None = None) -> Any:
|
|
14
|
+
return json_request(self.transport, "GET", "/api/v1/audit/events", query=query)
|