makers-sdk 0.1.0b1__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.
- makers_sdk-0.1.0b1/PKG-INFO +375 -0
- makers_sdk-0.1.0b1/README.md +364 -0
- makers_sdk-0.1.0b1/pyproject.toml +27 -0
- makers_sdk-0.1.0b1/setup.cfg +4 -0
- makers_sdk-0.1.0b1/src/makers_sdk/__init__.py +25 -0
- makers_sdk-0.1.0b1/src/makers_sdk/_artifacts.py +283 -0
- makers_sdk-0.1.0b1/src/makers_sdk/client.py +1540 -0
- makers_sdk-0.1.0b1/src/makers_sdk/errors.py +54 -0
- makers_sdk-0.1.0b1/src/makers_sdk/py.typed +0 -0
- makers_sdk-0.1.0b1/src/makers_sdk.egg-info/PKG-INFO +375 -0
- makers_sdk-0.1.0b1/src/makers_sdk.egg-info/SOURCES.txt +14 -0
- makers_sdk-0.1.0b1/src/makers_sdk.egg-info/dependency_links.txt +1 -0
- makers_sdk-0.1.0b1/src/makers_sdk.egg-info/requires.txt +4 -0
- makers_sdk-0.1.0b1/src/makers_sdk.egg-info/top_level.txt +1 -0
- makers_sdk-0.1.0b1/tests/test_client.py +1234 -0
- makers_sdk-0.1.0b1/tests/test_smoke_harness.py +255 -0
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: makers-sdk
|
|
3
|
+
Version: 0.1.0b1
|
|
4
|
+
Summary: Synchronous Python client for the EdgeOne Makers SDK
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Requires-Python: >=3.10
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
Requires-Dist: cos-python-sdk-v5==1.9.44
|
|
9
|
+
Provides-Extra: test
|
|
10
|
+
Requires-Dist: jsonschema[format]>=4.18; extra == "test"
|
|
11
|
+
|
|
12
|
+
# EdgeOne Makers Python SDK
|
|
13
|
+
|
|
14
|
+
[English](README.md) | [中文](README.zh-CN.md)
|
|
15
|
+
|
|
16
|
+
Synchronous Python 3.10+ client for EdgeOne Makers projects, environment variables, and artifact deployments. There is no async client.
|
|
17
|
+
|
|
18
|
+
PyPI package: `makers-sdk` · `import makers_sdk` · Version `0.1.0` · Contract `0.1.23` · MIT License
|
|
19
|
+
|
|
20
|
+
## Installation
|
|
21
|
+
|
|
22
|
+
```sh
|
|
23
|
+
pip install makers-sdk
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
```python
|
|
27
|
+
from makers_sdk import Client
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## Client
|
|
33
|
+
|
|
34
|
+
### Constructor
|
|
35
|
+
|
|
36
|
+
```python
|
|
37
|
+
import os
|
|
38
|
+
from makers_sdk import Client
|
|
39
|
+
|
|
40
|
+
client = Client(
|
|
41
|
+
token=os.environ["MAKERS_API_TOKEN"],
|
|
42
|
+
source="cli",
|
|
43
|
+
region="china",
|
|
44
|
+
)
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
| Parameter | Type | Required | Default | Description |
|
|
48
|
+
|-----------|------|:--------:|---------|-------------|
|
|
49
|
+
| `token` | `str` | Yes | — | EdgeOne Makers API Token |
|
|
50
|
+
| `source` | `str` | Yes | — | Only confirmed value is `"cli"` |
|
|
51
|
+
| `region` | `str` | No | auto-detect | `"china"` → `pages-api.cloud.tencent.com/v1`; `"global"` → `pages-api.edgeone.ai/v1` |
|
|
52
|
+
| `base_url` | `str` | No | — | Overrides `region`; must be HTTPS (localhost may use HTTP) |
|
|
53
|
+
| `timeout` | `float` | No | `30` | Per-request timeout in **seconds** |
|
|
54
|
+
| `retries` | `int` | No | `3` | Max retry count for queries; writes do not retry |
|
|
55
|
+
| `logger` | `Logger` | No | — | Must implement `debug`, `info`, `warn`, `error` |
|
|
56
|
+
|
|
57
|
+
Auto-detection probes china then global and caches the result per Client instance.
|
|
58
|
+
|
|
59
|
+
### Public members
|
|
60
|
+
|
|
61
|
+
| Member | Type | Description |
|
|
62
|
+
|--------|------|-------------|
|
|
63
|
+
| `client.projects` | `Projects` | Project and environment variable operations |
|
|
64
|
+
| `client.deployments` | `Deployments` | Deployment operations |
|
|
65
|
+
| `client.region` | `str \| None` | Read-only; configured or detected region |
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## Projects
|
|
70
|
+
|
|
71
|
+
All return values are `dict` with `snake_case` keys. Optional fields may be absent — always use `.get()`.
|
|
72
|
+
|
|
73
|
+
### `projects.create(*, name, area?, initial_env_vars?)`
|
|
74
|
+
|
|
75
|
+
Creates a project. Returns `{"project_id": ...}` only — query `get` for the full model.
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
created = client.projects.create(
|
|
79
|
+
name="docs-site",
|
|
80
|
+
area="overseas",
|
|
81
|
+
initial_env_vars=[{"key": "API_URL", "value": "https://example.com"}],
|
|
82
|
+
)
|
|
83
|
+
project_id = created["project_id"]
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
| Parameter | Type | Required | Description |
|
|
87
|
+
|-----------|------|:--------:|-------------|
|
|
88
|
+
| `name` | `str` | Yes | Unique per account; duplicate raises `ConflictError` |
|
|
89
|
+
| `area` | `str` | No | `"mainland"` / `"overseas"` / `"global"`; omitted = not sent |
|
|
90
|
+
| `initial_env_vars` | `list[dict]` | No | Set env vars at creation time |
|
|
91
|
+
|
|
92
|
+
### `projects.list(**options)`
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
page = client.projects.list(
|
|
96
|
+
name="docs",
|
|
97
|
+
page=0,
|
|
98
|
+
page_size=20,
|
|
99
|
+
order={"field": "created_on", "direction": "desc"},
|
|
100
|
+
)
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
| Parameter | Type | Default | Description |
|
|
104
|
+
|-----------|------|---------|-------------|
|
|
105
|
+
| `project_ids` | `list[str]` | — | Filter by ID list |
|
|
106
|
+
| `name` | `str` | — | Filter by name |
|
|
107
|
+
| `status` | `str` | — | Filter by status |
|
|
108
|
+
| `provider` | `str` | — | Filter by provider |
|
|
109
|
+
| `page` | `int` | `0` | Zero-based page index |
|
|
110
|
+
| `page_size` | `int` | `20` | 1–100 |
|
|
111
|
+
| `order` | `dict` | — | `field`: `"created_on"` or `"modified_on"`; `direction`: `"asc"` or `"desc"` |
|
|
112
|
+
|
|
113
|
+
**Returns** `dict`: `{ "items", "page", "page_size", "total", "has_next" }`.
|
|
114
|
+
|
|
115
|
+
### `projects.list_all(**options)`
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
for project in client.projects.list_all():
|
|
119
|
+
print(project["project_id"], project["name"])
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Same options as `list` except no `page`. Returns `Iterator[dict]`.
|
|
123
|
+
|
|
124
|
+
### `projects.get(*, project_id)`
|
|
125
|
+
|
|
126
|
+
Returns the full project dict. This is the only way to obtain `preset_domain`.
|
|
127
|
+
|
|
128
|
+
```python
|
|
129
|
+
project = client.projects.get(project_id=project_id)
|
|
130
|
+
print(project.get("preset_domain"))
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### `projects.update(*, project_id, **fields)`
|
|
134
|
+
|
|
135
|
+
Updates project settings. Only provided fields are sent; omitted fields are not cleared.
|
|
136
|
+
|
|
137
|
+
```python
|
|
138
|
+
client.projects.update(
|
|
139
|
+
project_id=project_id,
|
|
140
|
+
name="new-name",
|
|
141
|
+
root_dir=".",
|
|
142
|
+
output_dir="dist",
|
|
143
|
+
build_cmd="npm run build",
|
|
144
|
+
install_cmd="npm install",
|
|
145
|
+
framework="other",
|
|
146
|
+
nodejs_version="20",
|
|
147
|
+
)
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
| Parameter | Type | Description |
|
|
151
|
+
|-----------|------|-------------|
|
|
152
|
+
| `project_id` | `str` | Required |
|
|
153
|
+
| `name` | `str` | Project name |
|
|
154
|
+
| `root_dir` | `str` | Root directory |
|
|
155
|
+
| `output_dir` | `str` | Output directory |
|
|
156
|
+
| `build_cmd` | `str` | Build command |
|
|
157
|
+
| `install_cmd` | `str` | Install command |
|
|
158
|
+
| `framework` | `str` | Framework identifier |
|
|
159
|
+
| `nodejs_version` | `str` | Node.js version |
|
|
160
|
+
|
|
161
|
+
### `projects.delete(*, project_id)`
|
|
162
|
+
|
|
163
|
+
Deletes a project. **Irreversible.**
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
## Environment variables
|
|
168
|
+
|
|
169
|
+
Methods live on `client.projects`. Public fields: `key`, `value`, optional `comment`.
|
|
170
|
+
|
|
171
|
+
### `projects.list_envs(*, project_id)`
|
|
172
|
+
|
|
173
|
+
Returns `list[dict]`. Values are automatically added to the redaction list.
|
|
174
|
+
|
|
175
|
+
### `projects.set_envs(*, project_id, env_vars)`
|
|
176
|
+
|
|
177
|
+
Batch upsert. Internally reads existing vars first, then writes. Not atomic. Duplicate keys raise `ValidationError` before any network request.
|
|
178
|
+
|
|
179
|
+
```python
|
|
180
|
+
client.projects.set_envs(
|
|
181
|
+
project_id=project_id,
|
|
182
|
+
env_vars=[
|
|
183
|
+
{"key": "API_URL", "value": "https://example.com", "comment": "origin"},
|
|
184
|
+
],
|
|
185
|
+
)
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### `projects.delete_envs(*, project_id, keys)`
|
|
189
|
+
|
|
190
|
+
Deletes env vars by key. Duplicate keys raise `ValidationError`.
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
|
|
194
|
+
## Deployments
|
|
195
|
+
|
|
196
|
+
### `deployments.deploy(*, project_id, artifact, **options)`
|
|
197
|
+
|
|
198
|
+
Deploys an artifact. Accepts exactly one variant:
|
|
199
|
+
|
|
200
|
+
| Variant | Type | Behavior |
|
|
201
|
+
|---------|------|----------|
|
|
202
|
+
| `"files"` | `dict[str, str \| bytes]` | SDK zips in memory |
|
|
203
|
+
| `"directory"` | `str` (+ optional `"exclude_patterns": list[str]`) | SDK zips the directory |
|
|
204
|
+
| `"archive"` | `str` (zip file path) | Uploaded directly after safety validation |
|
|
205
|
+
|
|
206
|
+
```python
|
|
207
|
+
# Inline files
|
|
208
|
+
client.deployments.deploy(
|
|
209
|
+
project_id=project_id,
|
|
210
|
+
artifact={"files": {"index.html": "<h1>ok</h1>"}},
|
|
211
|
+
wait=True,
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
# Directory
|
|
215
|
+
client.deployments.deploy(
|
|
216
|
+
project_id=project_id,
|
|
217
|
+
artifact={"directory": "./dist", "exclude_patterns": ["**/*.map"]},
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
# Zip archive
|
|
221
|
+
client.deployments.deploy(
|
|
222
|
+
project_id=project_id,
|
|
223
|
+
artifact={"archive": "./site.zip"},
|
|
224
|
+
)
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
| Parameter | Type | Default | Description |
|
|
228
|
+
|-----------|------|---------|-------------|
|
|
229
|
+
| `project_id` | `str` | — | Required |
|
|
230
|
+
| `artifact` | `dict` | — | Required; exactly one variant |
|
|
231
|
+
| `env` | `str` | `"Production"` | `"Preview"` requires an existing production deployment |
|
|
232
|
+
| `wait` | `bool` | `False` | Poll until terminal status |
|
|
233
|
+
| `timeout` | `float` | `900` | Max wait time in **seconds** (15 min) |
|
|
234
|
+
| `poll_interval` | `float` | `5` | Poll interval in **seconds** |
|
|
235
|
+
| `env_vars` | `list[dict]` | — | Deployment-scoped env vars |
|
|
236
|
+
| `upload_progress` | callable | — | Fires once after upload completes |
|
|
237
|
+
| `status_change` | callable | — | Fires on each status transition |
|
|
238
|
+
|
|
239
|
+
**Returns** `dict`. With `wait=False`, only `deployment_id`, `project_id`, and `env` are populated.
|
|
240
|
+
|
|
241
|
+
**Note**: `timeout` here is the overall wait budget (default 900s), distinct from the per-request `Client(timeout=30)`.
|
|
242
|
+
|
|
243
|
+
**Directory safety**: rejects symlinks; ignores `.git`, `node_modules`, `.edgeone`, `.env`, logs, temp and system files. `exclude_patterns` appends POSIX globs; negation (`!`) is rejected.
|
|
244
|
+
|
|
245
|
+
### `deployments.wait(*, project_id, deployment_id, **options)`
|
|
246
|
+
|
|
247
|
+
Polls until a terminal status: `Success`, `Failed`, `Timeout`, `Cancelled`, or `Invalid`.
|
|
248
|
+
|
|
249
|
+
```python
|
|
250
|
+
result = client.deployments.wait(
|
|
251
|
+
project_id=project_id,
|
|
252
|
+
deployment_id=deployment_id,
|
|
253
|
+
timeout=900,
|
|
254
|
+
poll_interval=5,
|
|
255
|
+
status_change=lambda e: print(e["deployment"].get("status")),
|
|
256
|
+
)
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
| Parameter | Type | Default | Description |
|
|
260
|
+
|-----------|------|---------|-------------|
|
|
261
|
+
| `project_id` | `str` | — | Required |
|
|
262
|
+
| `deployment_id` | `str` | — | Required |
|
|
263
|
+
| `timeout` | `float` | `900` | Raises `DeploymentTimeoutError` on expiry; **does not cancel the deployment** |
|
|
264
|
+
| `poll_interval` | `float` | `5` | |
|
|
265
|
+
| `status_change` | callable | — | |
|
|
266
|
+
|
|
267
|
+
### `deployments.list(*, project_id, **options)`
|
|
268
|
+
|
|
269
|
+
| Parameter | Type | Default | Description |
|
|
270
|
+
|-----------|------|---------|-------------|
|
|
271
|
+
| `project_id` | `str` | — | Required |
|
|
272
|
+
| `status` | `list[str]` | — | Filter by status |
|
|
273
|
+
| `time_range` | `dict` | — | `{"start": ..., "end": ...}` ISO 8601 |
|
|
274
|
+
| `repo_branch` | `list[str]` | — | Filter by branch |
|
|
275
|
+
| `page` | `int` | `0` | |
|
|
276
|
+
| `page_size` | `int` | `20` | |
|
|
277
|
+
| `order` | `dict` | — | |
|
|
278
|
+
|
|
279
|
+
### `deployments.list_all(*, project_id, **options)`
|
|
280
|
+
|
|
281
|
+
Same as `list` without `page`. Returns `Iterator[dict]`.
|
|
282
|
+
|
|
283
|
+
### `deployments.get(*, project_id, deployment_id)`
|
|
284
|
+
|
|
285
|
+
Returns a single deployment dict. Raises `NotFoundError` if not found.
|
|
286
|
+
|
|
287
|
+
### `deployments.get_log(*, project_id, deployment_id)`
|
|
288
|
+
|
|
289
|
+
Returns `{"log_url": ...}` — the build log URL.
|
|
290
|
+
|
|
291
|
+
---
|
|
292
|
+
|
|
293
|
+
## Data models
|
|
294
|
+
|
|
295
|
+
All values are plain `dict`. Optional fields may be absent — use `.get()` to access them.
|
|
296
|
+
|
|
297
|
+
### Project
|
|
298
|
+
|
|
299
|
+
| Field | Type | Always present | Description |
|
|
300
|
+
|-------|------|:--------------:|-------------|
|
|
301
|
+
| `project_id` | `str` | Yes | |
|
|
302
|
+
| `name` | `str` | Yes | Unique per account |
|
|
303
|
+
| `status` | `str` | Yes | |
|
|
304
|
+
| `area` | `str` | No | `"mainland"` / `"overseas"` / `"global"` |
|
|
305
|
+
| `preset_domain` | `str` | No | Production domain; may be absent for new projects |
|
|
306
|
+
| `created_on` | `str` | Yes | ISO 8601 |
|
|
307
|
+
| `modified_on` | `str` | Yes | ISO 8601 |
|
|
308
|
+
|
|
309
|
+
### Deployment
|
|
310
|
+
|
|
311
|
+
| Field | Type | Always present | Description |
|
|
312
|
+
|-------|------|:--------------:|-------------|
|
|
313
|
+
| `deployment_id` | `str` | Yes | |
|
|
314
|
+
| `project_id` | `str` | Yes | |
|
|
315
|
+
| `env` | `str` | Yes | `"Production"` or `"Preview"` |
|
|
316
|
+
| `status` | `str` | No | Terminal: `Success` / `Failed` / `Timeout` / `Cancelled` / `Invalid` |
|
|
317
|
+
| `preview_url` | `str` | No | Only for Preview deployments |
|
|
318
|
+
| `code` | `str` | No | |
|
|
319
|
+
| `created_on` | `str` | No | Not returned with `wait=False` |
|
|
320
|
+
| `modified_on` | `str` | No | Not returned with `wait=False` |
|
|
321
|
+
|
|
322
|
+
### EnvVar
|
|
323
|
+
|
|
324
|
+
| Field | Type | Always present |
|
|
325
|
+
|-------|------|:--------------:|
|
|
326
|
+
| `key` | `str` | Yes |
|
|
327
|
+
| `value` | `str` | Yes |
|
|
328
|
+
| `comment` | `str` | No |
|
|
329
|
+
|
|
330
|
+
### Callbacks
|
|
331
|
+
|
|
332
|
+
**upload_progress event**: `{"uploaded_bytes", "total_bytes", "completed_files", "total_files"}`
|
|
333
|
+
|
|
334
|
+
**status_change event**: `{"deployment": dict, "previous_status": str | None}`
|
|
335
|
+
|
|
336
|
+
---
|
|
337
|
+
|
|
338
|
+
## Errors
|
|
339
|
+
|
|
340
|
+
All errors extend `MakersError` with fields: `code`, `message`, `request_id`, `http_status`, `cause`.
|
|
341
|
+
|
|
342
|
+
| Error class | Trigger |
|
|
343
|
+
|-------------|---------|
|
|
344
|
+
| `AuthError` | Code 105 or HTTP 401 |
|
|
345
|
+
| `ValidationError` | Code contains "Invalid" or HTTP 400 |
|
|
346
|
+
| `NotFoundError` | Code contains "NotFound" or HTTP 404 |
|
|
347
|
+
| `ConflictError` | Code contains "Conflict" or HTTP 409 |
|
|
348
|
+
| `RateLimitError` | Code 110 or HTTP 429 |
|
|
349
|
+
| `UploadError` | COS upload failure |
|
|
350
|
+
| `TimeoutError` | Request timeout |
|
|
351
|
+
| `DeploymentTimeoutError` | Wait timeout (extends `TimeoutError`) |
|
|
352
|
+
|
|
353
|
+
```python
|
|
354
|
+
from makers_sdk import MakersError, NotFoundError, DeploymentTimeoutError
|
|
355
|
+
|
|
356
|
+
try:
|
|
357
|
+
client.projects.get(project_id="missing")
|
|
358
|
+
except NotFoundError as error:
|
|
359
|
+
print(error.code, error.request_id, error.http_status)
|
|
360
|
+
except MakersError:
|
|
361
|
+
raise
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
### Retry behavior
|
|
365
|
+
|
|
366
|
+
Queries retry on network errors, HTTP 408/429/5xx, and outer `Code: 110`, up to `retries` times (default 3). Exponential backoff with full jitter, capped at 10 seconds. `Retry-After` header is respected when present. Creates, updates, and COS credential requests do not retry.
|
|
367
|
+
|
|
368
|
+
---
|
|
369
|
+
|
|
370
|
+
## Development
|
|
371
|
+
|
|
372
|
+
```sh
|
|
373
|
+
python -m pip install -e ".[test]"
|
|
374
|
+
python -m unittest discover -s tests -v
|
|
375
|
+
```
|