makers-sdk 0.1.0__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.
@@ -0,0 +1,436 @@
1
+ Metadata-Version: 2.4
2
+ Name: makers-sdk
3
+ Version: 0.1.0
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, artifact deployments, and tenant tokens. There is no async Makers.
17
+
18
+ PyPI package: `makers-sdk` · `import makers_sdk` · Version `0.1.0` · Contract `0.1.39` · MIT License
19
+
20
+ ## Installation
21
+
22
+ ```sh
23
+ pip install makers-sdk
24
+ ```
25
+
26
+ ```python
27
+ from makers_sdk import Makers
28
+ ```
29
+
30
+ ---
31
+
32
+ ## Makers
33
+
34
+ ### Constructor
35
+
36
+ ```python
37
+ import os
38
+ from makers_sdk import Makers
39
+
40
+ makers = Makers(
41
+ token=os.environ["MAKERS_API_TOKEN"],
42
+ region="china",
43
+ )
44
+ ```
45
+
46
+ | Parameter | Type | Required | Default | Description |
47
+ |-----------|------|:--------:|---------|-------------|
48
+ | `token` | `str` | Yes | — | EdgeOne Makers API Token |
49
+ | `region` | `str` | No | auto-detect | `"china"` → `pages-api.cloud.tencent.com/v1`; `"global"` → `pages-api.edgeone.ai/v1` |
50
+ | `timeout` | `float` | No | `30` | Per-request timeout in **seconds** |
51
+ | `retries` | `int` | No | `3` | Max retry count for queries; writes do not retry |
52
+ | `logger` | `Logger` | No | — | Must implement `debug`, `info`, `warn`, `error` |
53
+
54
+ Auto-detection probes china then global and caches the result per Makers instance.
55
+
56
+ ### Public members
57
+
58
+ | Member | Type | Description |
59
+ |--------|------|-------------|
60
+ | `makers.projects` | `Projects` | Project and environment variable operations |
61
+ | `makers.deployments` | `Deployments` | Deployment operations |
62
+ | `makers.tokens` | `Tokens` | Issue a tenant token (`tokens.create`) |
63
+ | `makers.region` | `str \| None` | Read-only; configured or detected region |
64
+
65
+ ---
66
+
67
+ ## Tokens
68
+
69
+ Issue a tenant token with a **master API Token**, then construct a new `Makers` with the returned `token` to manage that tenant's projects. `tokens.create` requires `tenant_id`, `name`, and `expires_in`. It returns `{ "token", "token_id", "expired" }`.
70
+
71
+ `expires_in` is relative seconds, not a unix timestamp. The returned `expired` is the backend expiry time as a unix-seconds number.
72
+
73
+ Call `tokens.create` with a master API Token. The SDK does not inspect whether the current token is a master Token or a tenant token, and will not block a tenant token from calling `tokens.create`. v1 does not provide list, get, or delete token methods.
74
+
75
+ Repeating `tokens.create` with the same `tenant_id` is idempotent: the same `token` and `token_id` are returned, and only `expired` may refresh.
76
+
77
+ ### `tokens.create(*, tenant_id, name, expires_in)`
78
+
79
+ ```python
80
+ platform = Makers(
81
+ token=os.environ["MAKERS_API_TOKEN"],
82
+ )
83
+ created = platform.tokens.create(
84
+ tenant_id="user-open-id",
85
+ name="user-open-id",
86
+ expires_in=86400,
87
+ )
88
+ user = Makers(token=created["token"])
89
+ created_project = user.projects.create(name="my-site")
90
+ project_id = created_project["project_id"]
91
+ user.deployments.deploy(
92
+ project_id=project_id,
93
+ artifact={"files": {"index.html": "<h1>Hello</h1>"}},
94
+ )
95
+ ```
96
+
97
+ | Parameter | Type | Required | Description |
98
+ |-----------|------|:--------:|-------------|
99
+ | `tenant_id` | `str` | Yes | Tenant identifier; non-empty, max 64 characters |
100
+ | `name` | `str` | Yes | Token name; 1–128 characters |
101
+ | `expires_in` | `int` | Yes | Lifetime in relative seconds (integer 10–315360000), not a unix timestamp |
102
+
103
+ **Returns** `dict`: `{ "token", "token_id", "expired" }`. `expired` is the backend expiry time as unix seconds. Invalid input raises `ValidationError` before any network request.
104
+
105
+ ---
106
+
107
+ ## Projects
108
+
109
+ All return values are `dict` with `snake_case` keys. Optional fields may be absent — always use `.get()`.
110
+
111
+ ### `projects.create(*, name, area?, initial_env_vars?)`
112
+
113
+ Creates a project. Returns `{"project_id": ...}` only — query `get` for the full model.
114
+
115
+ ```python
116
+ created = makers.projects.create(
117
+ name="docs-site",
118
+ area="overseas",
119
+ initial_env_vars=[{"key": "API_URL", "value": "https://example.com"}],
120
+ )
121
+ project_id = created["project_id"]
122
+ ```
123
+
124
+ | Parameter | Type | Required | Description |
125
+ |-----------|------|:--------:|-------------|
126
+ | `name` | `str` | Yes | Unique per account; duplicate raises `ConflictError` |
127
+ | `area` | `str` | No | `"mainland"` / `"overseas"` / `"global"`; omitted = not sent |
128
+ | `initial_env_vars` | `list[dict]` | No | Set env vars at creation time |
129
+
130
+ ### `projects.list(**options)`
131
+
132
+ ```python
133
+ page = makers.projects.list(
134
+ name="docs",
135
+ page=0,
136
+ page_size=20,
137
+ order={"field": "created_on", "direction": "desc"},
138
+ )
139
+ ```
140
+
141
+ | Parameter | Type | Default | Description |
142
+ |-----------|------|---------|-------------|
143
+ | `project_ids` | `list[str]` | — | Filter by ID list |
144
+ | `name` | `str` | — | Filter by name |
145
+ | `status` | `str` | — | Filter by status |
146
+ | `provider` | `str` | — | Filter by provider |
147
+ | `page` | `int` | `0` | Zero-based page index |
148
+ | `page_size` | `int` | `20` | 1–100 |
149
+ | `order` | `dict` | — | `field`: `"created_on"` or `"modified_on"`; `direction`: `"asc"` or `"desc"` |
150
+
151
+ **Returns** `dict`: `{ "items", "page", "page_size", "total", "has_next" }`.
152
+
153
+ ### `projects.list_all(**options)`
154
+
155
+ ```python
156
+ for project in makers.projects.list_all():
157
+ print(project["project_id"], project["name"])
158
+ ```
159
+
160
+ Same options as `list` except no `page`. Returns `Iterator[dict]`.
161
+
162
+ ### `projects.get(*, project_id)`
163
+
164
+ Returns the full project dict. This is the only way to obtain `preset_domain`.
165
+
166
+ ```python
167
+ project = makers.projects.get(project_id=project_id)
168
+ print(project.get("preset_domain"))
169
+ ```
170
+
171
+ ### `projects.update(*, project_id, **fields)`
172
+
173
+ Updates project settings. Only provided fields are sent; omitted fields are not cleared.
174
+
175
+ ```python
176
+ makers.projects.update(
177
+ project_id=project_id,
178
+ name="new-name",
179
+ root_dir=".",
180
+ output_dir="dist",
181
+ build_cmd="npm run build",
182
+ install_cmd="npm install",
183
+ framework="other",
184
+ nodejs_version="20",
185
+ )
186
+ ```
187
+
188
+ | Parameter | Type | Description |
189
+ |-----------|------|-------------|
190
+ | `project_id` | `str` | Required |
191
+ | `name` | `str` | Project name |
192
+ | `root_dir` | `str` | Root directory |
193
+ | `output_dir` | `str` | Output directory |
194
+ | `build_cmd` | `str` | Build command |
195
+ | `install_cmd` | `str` | Install command |
196
+ | `framework` | `str` | Framework identifier |
197
+ | `nodejs_version` | `str` | Node.js version |
198
+
199
+ ### `projects.delete(*, project_id)`
200
+
201
+ Deletes a project. **Irreversible.**
202
+
203
+ ---
204
+
205
+ ## Environment variables
206
+
207
+ Methods live on `makers.projects`. Public fields: `key`, `value`, optional `comment`. Change project variables with `set_envs`; `deploy` does not accept env vars.
208
+
209
+ ### `projects.list_envs(*, project_id)`
210
+
211
+ Returns `list[dict]`. Values are automatically added to the redaction list.
212
+
213
+ ### `projects.set_envs(*, project_id, env_vars)`
214
+
215
+ Batch upsert. Internally reads existing vars first, then writes. Not atomic. Duplicate keys raise `ValidationError` before any network request.
216
+
217
+ ```python
218
+ makers.projects.set_envs(
219
+ project_id=project_id,
220
+ env_vars=[
221
+ {"key": "API_URL", "value": "https://example.com", "comment": "origin"},
222
+ ],
223
+ )
224
+ ```
225
+
226
+ ### `projects.delete_envs(*, project_id, keys)`
227
+
228
+ Deletes env vars by key. Duplicate keys raise `ValidationError`.
229
+
230
+ ---
231
+
232
+ ## Deployments
233
+
234
+ ### `deployments.deploy(*, project_id, artifact, **options)`
235
+
236
+ Deploys an artifact. Accepts exactly one variant:
237
+
238
+ | Variant | Type | Behavior |
239
+ |---------|------|----------|
240
+ | `"files"` | `dict[str, str \| bytes]` | SDK zips in memory |
241
+ | `"directory"` | `str` (+ optional `"exclude_patterns": list[str]`) | SDK zips the directory |
242
+ | `"archive"` | `str` (zip file path) | Uploaded directly after safety validation |
243
+
244
+ ```python
245
+ # Inline files
246
+ makers.deployments.deploy(
247
+ project_id=project_id,
248
+ artifact={"files": {"index.html": "<h1>ok</h1>"}},
249
+ wait=True,
250
+ )
251
+
252
+ # Application source (project root)
253
+ makers.deployments.deploy(
254
+ project_id=project_id,
255
+ artifact={"directory": "."},
256
+ wait=True,
257
+ )
258
+
259
+ # Static build output
260
+ makers.deployments.deploy(
261
+ project_id=project_id,
262
+ artifact={"directory": "./dist", "exclude_patterns": ["**/*.map"]},
263
+ )
264
+
265
+ # Prebuilt CLI output
266
+ makers.deployments.deploy(
267
+ project_id=project_id,
268
+ artifact={"directory": "./.edgeone"},
269
+ wait=True,
270
+ )
271
+
272
+ # Zip archive
273
+ makers.deployments.deploy(
274
+ project_id=project_id,
275
+ artifact={"archive": "./site.zip"},
276
+ )
277
+ ```
278
+
279
+ | Parameter | Type | Default | Description |
280
+ |-----------|------|---------|-------------|
281
+ | `project_id` | `str` | — | Required |
282
+ | `artifact` | `dict` | — | Required; exactly one variant |
283
+ | `env` | `str` | `"Production"` | `"Preview"` requires an existing production deployment |
284
+ | `wait` | `bool` | `False` | Poll until terminal status |
285
+ | `timeout` | `float` | `900` | Max wait time in **seconds** (15 min) |
286
+ | `poll_interval` | `float` | `5` | Poll interval in **seconds** |
287
+ | `upload_progress` | callable | — | Fires once after upload completes |
288
+ | `status_change` | callable | — | Fires on each status transition |
289
+
290
+ **Returns** `dict`. With `wait=False`, only `deployment_id`, `project_id`, and `env` are populated — that is not an openable site URL. After `wait=True` reaches `Success`, `preview_url` is an openable link and **expires**.
291
+
292
+ **Note**: `timeout` here is the overall wait budget (default 900s), distinct from the per-request `Makers(timeout=30)`.
293
+
294
+ **Directory input**: pass one directory. Typical inputs are application source (project root), static build output (`index.html` must be at that directory root), or prebuilt Makers/SSR output (`./.edgeone` after a local CLI build). Application source is the usual SDK path: the SDK zips the tree with default ignores and uploads it. It does not compile locally and does not itself run `npm run build` or `edgeone makers build`. For Upload projects, Pages may then run `edgeone makers build`. The SDK does not run framework adapters. Default ignores are a fixed list; the SDK does not read `.gitignore`. A caller-provided `archive` is uploaded as-is.
295
+
296
+ **Directory safety**: rejects symlinks; ignores `.git`, `node_modules`, the artifact-root `.edgeone` directory, `.env`, logs, temp and system files. Non-`.edgeone` directories also ignore any path segment that starts with `.`; `.well-known` is kept. Nested `.edgeone` paths are kept. Passing a directory named `.edgeone` packs CLI layout (`<parent>/.edgeone/...`, plus sibling `edgeone.json` when present), keeps nested `node_modules`, and keeps hidden files. Other directories still drop `node_modules` at any depth. These defaults are a fixed list, not `.gitignore`. `exclude_patterns` appends POSIX globs; negation (`!`) is rejected.
297
+
298
+ ### `deployments.wait(*, project_id, deployment_id, **options)`
299
+
300
+ Polls until a terminal status: `Success`, `Failed`, `Timeout`, `Cancelled`, or `Invalid`. After `Success`, `preview_url` is an openable site URL (signed for default upload projects) and **expires**. `get` / `list` / `wait=False` do not sign.
301
+
302
+ ```python
303
+ result = makers.deployments.wait(
304
+ project_id=project_id,
305
+ deployment_id=deployment_id,
306
+ timeout=900,
307
+ poll_interval=5,
308
+ status_change=lambda e: print(e["deployment"].get("status")),
309
+ )
310
+ ```
311
+
312
+ | Parameter | Type | Default | Description |
313
+ |-----------|------|---------|-------------|
314
+ | `project_id` | `str` | — | Required |
315
+ | `deployment_id` | `str` | — | Required |
316
+ | `timeout` | `float` | `900` | Raises `DeploymentTimeoutError` on expiry; **does not cancel the deployment** |
317
+ | `poll_interval` | `float` | `5` | |
318
+ | `status_change` | callable | — | |
319
+
320
+ ### `deployments.list(*, project_id, **options)`
321
+
322
+ | Parameter | Type | Default | Description |
323
+ |-----------|------|---------|-------------|
324
+ | `project_id` | `str` | — | Required |
325
+ | `status` | `list[str]` | — | Filter by status |
326
+ | `time_range` | `dict` | — | `{"start": ..., "end": ...}` ISO 8601 |
327
+ | `repo_branch` | `list[str]` | — | Filter by branch |
328
+ | `page` | `int` | `0` | |
329
+ | `page_size` | `int` | `20` | |
330
+ | `order` | `dict` | — | |
331
+
332
+ ### `deployments.list_all(*, project_id, **options)`
333
+
334
+ Same as `list` without `page`. Returns `Iterator[dict]`.
335
+
336
+ ### `deployments.get(*, project_id, deployment_id)`
337
+
338
+ Returns a single deployment dict. Raises `NotFoundError` if not found.
339
+
340
+ ### `deployments.get_log(*, project_id, deployment_id)`
341
+
342
+ Returns `{"log_url": ...}` — the build log URL.
343
+
344
+ ---
345
+
346
+ ## Data models
347
+
348
+ All values are plain `dict`. Optional fields may be absent — use `.get()` to access them.
349
+
350
+ ### Project
351
+
352
+ | Field | Type | Always present | Description |
353
+ |-------|------|:--------------:|-------------|
354
+ | `project_id` | `str` | Yes | |
355
+ | `name` | `str` | Yes | Unique per account |
356
+ | `status` | `str` | Yes | |
357
+ | `area` | `str` | No | `"mainland"` / `"overseas"` / `"global"` |
358
+ | `preset_domain` | `str` | No | Production domain; may be absent for new projects |
359
+ | `created_on` | `str` | Yes | ISO 8601 |
360
+ | `modified_on` | `str` | Yes | ISO 8601 |
361
+
362
+ ### Deployment
363
+
364
+ | Field | Type | Always present | Description |
365
+ |-------|------|:--------------:|-------------|
366
+ | `deployment_id` | `str` | Yes | |
367
+ | `project_id` | `str` | Yes | |
368
+ | `env` | `str` | Yes | `"Production"` or `"Preview"` |
369
+ | `status` | `str` | No | Terminal: `Success` / `Failed` / `Timeout` / `Cancelled` / `Invalid` |
370
+ | `preview_url` | `str` | No | After wait Success: openable site URL (expires). `get` / `wait=False` may expose the raw backend URL, which is not openable. |
371
+ | `code` | `str` | No | |
372
+ | `created_on` | `str` | No | Not returned with `wait=False` |
373
+ | `modified_on` | `str` | No | Not returned with `wait=False` |
374
+
375
+ ### Token
376
+
377
+ | Field | Type | Always present | Description |
378
+ |-------|------|:--------------:|-------------|
379
+ | `token` | `str` | Yes | Tenant API token; use it to construct a user-side `Makers` |
380
+ | `token_id` | `str` | Yes | Token identifier |
381
+ | `expired` | `int` | Yes | Backend expiry time as unix seconds |
382
+
383
+ ### EnvVar
384
+
385
+ | Field | Type | Always present |
386
+ |-------|------|:--------------:|
387
+ | `key` | `str` | Yes |
388
+ | `value` | `str` | Yes |
389
+ | `comment` | `str` | No |
390
+
391
+ ### Callbacks
392
+
393
+ **upload_progress event**: `{"uploaded_bytes", "total_bytes", "completed_files", "total_files"}`
394
+
395
+ **status_change event**: `{"deployment": dict, "previous_status": str | None}`
396
+
397
+ ---
398
+
399
+ ## Errors
400
+
401
+ All errors extend `MakersError` with fields: `code`, `message`, `request_id`, `http_status`, `cause`.
402
+
403
+ | Error class | Trigger |
404
+ |-------------|---------|
405
+ | `AuthError` | Code 105 or HTTP 401 |
406
+ | `ValidationError` | Code contains "Invalid" or HTTP 400 |
407
+ | `NotFoundError` | Code contains "NotFound" or HTTP 404 |
408
+ | `ConflictError` | Code contains "Conflict" or HTTP 409 |
409
+ | `RateLimitError` | Code 110 or HTTP 429 |
410
+ | `UploadError` | COS upload failure |
411
+ | `TimeoutError` | Request timeout |
412
+ | `DeploymentTimeoutError` | Wait timeout (extends `TimeoutError`) |
413
+
414
+ ```python
415
+ from makers_sdk import MakersError, NotFoundError, DeploymentTimeoutError
416
+
417
+ try:
418
+ makers.projects.get(project_id="missing")
419
+ except NotFoundError as error:
420
+ print(error.code, error.request_id, error.http_status)
421
+ except MakersError:
422
+ raise
423
+ ```
424
+
425
+ ### Retry behavior
426
+
427
+ 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.
428
+
429
+ ---
430
+
431
+ ## Development
432
+
433
+ ```sh
434
+ python -m pip install -e ".[test]"
435
+ python -m unittest discover -s tests -v
436
+ ```