matelab-python-sdk 0.1.0a21__tar.gz → 0.1.0a23__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.
- matelab_python_sdk-0.1.0a23/AGENTS.md +52 -0
- {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/CHANGELOG.md +16 -0
- {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/PKG-INFO +16 -10
- {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/README.md +15 -9
- {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/contracts/matelab-integration-v1.lock.json +3 -3
- {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/contracts/matelab-integration-v1.openapi.yaml +16 -11
- matelab_python_sdk-0.1.0a23/docs/agent-architecture.md +28 -0
- matelab_python_sdk-0.1.0a23/docs/agent-workflows.md +76 -0
- {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/docs/operation-coverage.yaml +1 -1
- {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/pyproject.toml +1 -1
- {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/src/matelab/_generated/models.py +3 -6
- {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/src/matelab/domains/records.py +54 -3
- {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/tests/test_generation.py +10 -5
- {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/tests/test_records.py +72 -1
- {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/uv.lock +1 -1
- matelab_python_sdk-0.1.0a21/AGENTS.md +0 -94
- {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/.env.example +0 -0
- {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/.github/workflows/release.yml +0 -0
- {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/.gitignore +0 -0
- {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/CONTEXT.md +0 -0
- {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/LICENSE +0 -0
- {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/NOTICE +0 -0
- {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/scripts/check_installed_package.py +0 -0
- {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/scripts/check_release.py +0 -0
- {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/scripts/generate_models.py +0 -0
- {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/src/matelab/__init__.py +0 -0
- {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/src/matelab/_generated/__init__.py +0 -0
- {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/src/matelab/_hashes.py +0 -0
- {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/src/matelab/_transport.py +0 -0
- {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/src/matelab/_wire_validation.py +0 -0
- {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/src/matelab/client.py +0 -0
- {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/src/matelab/domains/__init__.py +0 -0
- {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/src/matelab/domains/_attachment_content.py +0 -0
- {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/src/matelab/domains/cloud_drive.py +0 -0
- {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/src/matelab/domains/groups.py +0 -0
- {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/src/matelab/domains/literature.py +0 -0
- {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/src/matelab/domains/notebooks.py +0 -0
- {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/src/matelab/domains/templates.py +0 -0
- {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/src/matelab/domains/uploads.py +0 -0
- {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/src/matelab/domains/users.py +0 -0
- {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/src/matelab/errors.py +0 -0
- {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/src/matelab/py.typed +0 -0
- {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/src/matelab/session.py +0 -0
- {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/src/matelab/streaming.py +0 -0
- {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/tests/conftest.py +0 -0
- {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/tests/provider/test_provider_smoke.py +0 -0
- {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/tests/test_async_attachment_upload.py +0 -0
- {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/tests/test_client.py +0 -0
- {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/tests/test_cloud_drive.py +0 -0
- {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/tests/test_comments_and_downloads.py +0 -0
- {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/tests/test_consumer_smoke.py +0 -0
- {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/tests/test_errors.py +0 -0
- {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/tests/test_extended_records.py +0 -0
- {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/tests/test_groups.py +0 -0
- {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/tests/test_notebook_lifecycle.py +0 -0
- {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/tests/test_notebooks.py +0 -0
- {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/tests/test_operation_coverage.py +0 -0
- {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/tests/test_record_lifecycle.py +0 -0
- {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/tests/test_record_mutations.py +0 -0
- {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/tests/test_release.py +0 -0
- {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/tests/test_streaming.py +0 -0
- {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/tests/test_template_lifecycle.py +0 -0
- {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/tests/test_templates.py +0 -0
- {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/tests/test_transport.py +0 -0
- {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/tests/test_uploads_and_literature.py +0 -0
- {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/tests/test_users.py +0 -0
- {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/tests/typing/consumer.py +0 -0
- {matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/tests/typing/pyproject.toml +0 -0
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# AGENTS.md
|
|
2
|
+
|
|
3
|
+
本文件只承载每次维护都必须在上下文中的步骤、权威和授权边界。分支规则通过下列指针按需加载。
|
|
4
|
+
|
|
5
|
+
## 维护顺序
|
|
6
|
+
|
|
7
|
+
1. **定界**:先读 `git status` 和适用权威;完成标准是任务范围、用户现有改动和所需外部权限均已识别。
|
|
8
|
+
2. **查证**:沿 public intent → wire operation → Provider evidence 核对;完成标准是每个受影响 operation 的
|
|
9
|
+
request、response、security、examples、成功码及 PVD/PCG 均有结论,Contract 缺口已明确报告。
|
|
10
|
+
3. **收敛**:只修改任务必需的层;完成标准是 public、adapter、generated、tests、coverage 和 docs 的影响均已
|
|
11
|
+
记账,未加入 Provider 推测、Consumer 私有概念或无真实第二实现的 seam。
|
|
12
|
+
4. **验证**:先跑聚焦测试,再跑适用的完整门槛;完成标准是必需命令全部退出为零,skip 单独说明。
|
|
13
|
+
5. **交付**:复核最终 diff、状态和验证证据;完成标准是结果、限制和未执行项都可由用户独立核对。
|
|
14
|
+
|
|
15
|
+
## 分支指针
|
|
16
|
+
|
|
17
|
+
- **SDK architecture**:修改 public 类型、命名、身份空间或模块边界时读
|
|
18
|
+
[`docs/agent-architecture.md#public-interface`](docs/agent-architecture.md#public-interface);修改 transport、认证、
|
|
19
|
+
Session、stream 或错误时读
|
|
20
|
+
[`docs/agent-architecture.md#transport-session-and-errors`](docs/agent-architecture.md#transport-session-and-errors)。
|
|
21
|
+
- **Contract adoption**:同步 spec tag、snapshot、lock 或生成模型时执行
|
|
22
|
+
[`docs/agent-workflows.md#contract-adoption`](docs/agent-workflows.md#contract-adoption)。
|
|
23
|
+
- **Delivery gate**:代码、Contract、依赖或构建元数据变化后执行
|
|
24
|
+
[`docs/agent-workflows.md#delivery-gate`](docs/agent-workflows.md#delivery-gate)。
|
|
25
|
+
- **Provider validation**:任何实时 Provider smoke、读取或 mutation 前执行
|
|
26
|
+
[`docs/agent-workflows.md#provider-validation`](docs/agent-workflows.md#provider-validation)。
|
|
27
|
+
- **Release**:版本、制品、commit、tag、push 或 publish 任务执行
|
|
28
|
+
[`docs/agent-workflows.md#release`](docs/agent-workflows.md#release)。
|
|
29
|
+
|
|
30
|
+
## 权威
|
|
31
|
+
|
|
32
|
+
| 权威 | 负责内容 |
|
|
33
|
+
| --- | --- |
|
|
34
|
+
| `README.md` | 当前 public interface、集成语义、示例及人类流程 |
|
|
35
|
+
| `CONTEXT.md` | public 类型和命名采用的领域词汇 |
|
|
36
|
+
| `CHANGELOG.md` | SDK 版本变化与迁移影响;不定义当前接口 |
|
|
37
|
+
| `docs/operation-coverage.yaml` | operation 状态、公开入口、稳定性和已知问题 |
|
|
38
|
+
| `pyproject.toml` | SDK 版本、依赖范围、构建与工具配置;`uv.lock` 只锁定本仓库环境 |
|
|
39
|
+
| Contract lock 与 snapshot | Contract pin 与 wire schema |
|
|
40
|
+
| `src/matelab/__init__.py::__all__` | 公开导入边界 |
|
|
41
|
+
|
|
42
|
+
机器权威变化时,同一变更同步受影响的当前态文档;历史只追加到 `CHANGELOG.md`。测试提供行为证据,不替代
|
|
43
|
+
接口、coverage 或 Contract 权威。
|
|
44
|
+
|
|
45
|
+
## 范围与授权
|
|
46
|
+
|
|
47
|
+
- 现有工作区改动属于用户;在其上做最小、可归因的修改。默认只写本仓库,邻接的 spec、Provider 和 Consumer
|
|
48
|
+
仓库只作只读证据来源。
|
|
49
|
+
- `/home/tnzhu/projects/matelab-spec` 定义 Wire Contract、兼容策略及 Provider 问题;`matelab-server` 是 Provider
|
|
50
|
+
实现,`matelab-mcp` 只提供 Consumer evidence,均不能反向定义 SDK interface。
|
|
51
|
+
- commit、push、tag、publish、生产访问和真实 Provider mutation 需要本轮明确授权;授权只覆盖用户指定的目标与
|
|
52
|
+
副作用。
|
|
@@ -3,6 +3,22 @@
|
|
|
3
3
|
This file records release-to-release changes and migration impact. It is not the authority for the current interface;
|
|
4
4
|
see [README.md](README.md) for current behavior and [AGENTS.md](AGENTS.md) for maintenance policy.
|
|
5
5
|
|
|
6
|
+
## 0.1.0a23 - 2026-09-04
|
|
7
|
+
|
|
8
|
+
- Temporarily accepts `readRecord` table file attachments that contain valid `filename`, `hash`, and `size` fields but
|
|
9
|
+
omit the Contract-required `timestamp`. The SDK preserves the omission instead of synthesizing a timestamp; explicit
|
|
10
|
+
null timestamps and other malformed attachment values remain rejected.
|
|
11
|
+
|
|
12
|
+
## 0.1.0a22 - 2026-09-03
|
|
13
|
+
|
|
14
|
+
- Adopts immutable `matelab-spec v0.4.5` at commit `6afac7ff151680130e6e4bc6d95782589b97f4e7` and OpenAPI
|
|
15
|
+
SHA-256 `31a8d03b4366e2345dfccdbbaaaccf6754b2711428afa4295d778d121988460d`, aligned with Provider source evidence
|
|
16
|
+
`59eb05b233e5ddd023a60ccb5e5c5f2ae22a70f0` and its verified production-core equivalent.
|
|
17
|
+
- Tightens the private `readRecord` wire model so required `datetime_locked` is a non-negative integer; the Provider
|
|
18
|
+
normalizes an unavailable lock timestamp to `0`. The public `Record` interface remains unchanged.
|
|
19
|
+
- Retains the root-only `RecordFilesAttachmentAppend` boundary because `UpdateRecordRequest.add[]` still excludes the
|
|
20
|
+
folder `path` declared by the dedicated staged-name append schema; PVD-044 remains contract-contained.
|
|
21
|
+
|
|
6
22
|
## 0.1.0a21 - 2026-08-28
|
|
7
23
|
|
|
8
24
|
- Adopts immutable `matelab-spec v0.4.4` at commit `be070c5f931b2063327a3beaccc53a360204b64e` and OpenAPI
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: matelab-python-sdk
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.0a23
|
|
4
4
|
Summary: Reusable async Python client for the Matelab Integration Contract
|
|
5
5
|
Author-email: 朱天念 <zhutiannian@gmail.com>
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -35,10 +35,10 @@ This README describes the current SDK interface, integration semantics, examples
|
|
|
35
35
|
[AGENTS.md](AGENTS.md) for durable maintenance rules, and `docs/operation-coverage.yaml` for the machine-readable
|
|
36
36
|
operation inventory.
|
|
37
37
|
|
|
38
|
-
The current alpha is `0.1.
|
|
38
|
+
The current alpha is `0.1.0a23`. `[project].version` in `pyproject.toml` is the sole SDK version source;
|
|
39
39
|
`uv.lock` only mirrors that source.
|
|
40
40
|
|
|
41
|
-
The SDK is pinned to the immutable `matelab-spec v0.4.
|
|
41
|
+
The SDK is pinned to the immutable `matelab-spec v0.4.5` Contract Release. The sole release pin is
|
|
42
42
|
`contracts/matelab-integration-v1.lock.json`, which records
|
|
43
43
|
the source tag, commit, OpenAPI path, local snapshot path, and SHA-256.
|
|
44
44
|
|
|
@@ -64,14 +64,14 @@ To test the same artifact a downstream Consumer will install, build and install
|
|
|
64
64
|
|
|
65
65
|
```bash
|
|
66
66
|
uv build --no-build-isolation --out-dir dist/release
|
|
67
|
-
python -m pip install dist/release/matelab_python_sdk-0.1.
|
|
67
|
+
python -m pip install dist/release/matelab_python_sdk-0.1.0a23-py3-none-any.whl
|
|
68
68
|
```
|
|
69
69
|
|
|
70
70
|
Do not infer Provider compatibility from the SDK version alone. A release is also bound to the Contract
|
|
71
71
|
tag, commit, and checksum recorded below.
|
|
72
72
|
|
|
73
73
|
The Contract and public SDK interface track the current reviewed Provider at
|
|
74
|
-
`
|
|
74
|
+
`59eb05b233e5ddd023a60ccb5e5c5f2ae22a70f0`. Provider limitations that remain relevant to integrations are documented
|
|
75
75
|
alongside the affected interfaces below.
|
|
76
76
|
|
|
77
77
|
## Design
|
|
@@ -121,7 +121,7 @@ success code it does not allow raises `MatelabProtocolError` rather than assigni
|
|
|
121
121
|
`MatelabTransportError.status_code` remains available for HTTP failures. Provider response bodies, top-level `msg`,
|
|
122
122
|
legacy `errmsg`, opaque `errs`, uncontracted debug fields, and request payloads are not attached to exceptions because
|
|
123
123
|
Provider-authored diagnostics may contain stack traces, credentials, or submitted values.
|
|
124
|
-
The pinned v0.4.
|
|
124
|
+
The pinned v0.4.5 Contract also declares optional code-4 `errs`, but PVD-041 and PCG-015 record opaque array/object
|
|
125
125
|
containers and insufficient evidence for requiredness, cardinality, or exclusion from other codes. The SDK
|
|
126
126
|
therefore keeps those diagnostics wire-only and does not expose `errs` as stable public metadata. A typed detail
|
|
127
127
|
interface remains blocked until a future Contract release can define one from stronger Provider evidence. `retryable`
|
|
@@ -317,7 +317,7 @@ result cannot be retried through the same client.
|
|
|
317
317
|
|
|
318
318
|
Files-module append and table/files replacement use the general record attachment intents and may be combined with
|
|
319
319
|
other non-conflicting record changes. `RecordFilesAttachmentAppend` currently targets the module root. Although
|
|
320
|
-
v0.4.
|
|
320
|
+
v0.4.5 records Provider evidence for safe folder paths, its top-level `UpdateRecordRequest.add[]` schema still excludes
|
|
321
321
|
the `path` property declared by the dedicated staged-name append shape; the SDK therefore does not expose folder paths
|
|
322
322
|
until that wire-schema conflict is resolved (PVD-044):
|
|
323
323
|
|
|
@@ -354,6 +354,12 @@ not accept a caption. Record reads likewise accept only one canonical attachment
|
|
|
354
354
|
an attachment array violates the pinned response contract. Both replacement forms require the replacement hash to
|
|
355
355
|
differ from the current occurrence.
|
|
356
356
|
|
|
357
|
+
As a temporary Provider-compatibility exception, `records.read()` also accepts a table file attachment object whose
|
|
358
|
+
otherwise valid `filename`, `hash`, and `size` are present but whose Contract-required `timestamp` is omitted. The SDK
|
|
359
|
+
preserves that omission in `Record.modules` and does not invent a timestamp; null timestamps and all other malformed
|
|
360
|
+
attachment shapes remain response errors. This exception can be removed after the Provider canonicalizes current and
|
|
361
|
+
historical record content.
|
|
362
|
+
|
|
357
363
|
All record attachment intents reject handles from another notebook or authenticated uploader, duplicate use within one
|
|
358
364
|
finalization request, and raw Provider attachment references. Row/index-based operations use an immediate SDK read and
|
|
359
365
|
must not be called while a concurrent editor is known to be active. The Provider offers no expected hash or revision,
|
|
@@ -920,6 +926,6 @@ uv run python scripts/check_release.py dist/release/*.whl dist/release/*.tar.gz
|
|
|
920
926
|
```
|
|
921
927
|
|
|
922
928
|
Rebuilding the same commit with the same locked environment and `SOURCE_DATE_EPOCH` must produce
|
|
923
|
-
byte-identical wheel and source distribution hashes. The release is bound to immutable `matelab-spec v0.4.
|
|
924
|
-
`
|
|
925
|
-
`
|
|
929
|
+
byte-identical wheel and source distribution hashes. The release is bound to immutable `matelab-spec v0.4.5`, commit
|
|
930
|
+
`6afac7ff151680130e6e4bc6d95782589b97f4e7`, and OpenAPI SHA-256
|
|
931
|
+
`31a8d03b4366e2345dfccdbbaaaccf6754b2711428afa4295d778d121988460d`.
|
|
@@ -7,10 +7,10 @@ This README describes the current SDK interface, integration semantics, examples
|
|
|
7
7
|
[AGENTS.md](AGENTS.md) for durable maintenance rules, and `docs/operation-coverage.yaml` for the machine-readable
|
|
8
8
|
operation inventory.
|
|
9
9
|
|
|
10
|
-
The current alpha is `0.1.
|
|
10
|
+
The current alpha is `0.1.0a23`. `[project].version` in `pyproject.toml` is the sole SDK version source;
|
|
11
11
|
`uv.lock` only mirrors that source.
|
|
12
12
|
|
|
13
|
-
The SDK is pinned to the immutable `matelab-spec v0.4.
|
|
13
|
+
The SDK is pinned to the immutable `matelab-spec v0.4.5` Contract Release. The sole release pin is
|
|
14
14
|
`contracts/matelab-integration-v1.lock.json`, which records
|
|
15
15
|
the source tag, commit, OpenAPI path, local snapshot path, and SHA-256.
|
|
16
16
|
|
|
@@ -36,14 +36,14 @@ To test the same artifact a downstream Consumer will install, build and install
|
|
|
36
36
|
|
|
37
37
|
```bash
|
|
38
38
|
uv build --no-build-isolation --out-dir dist/release
|
|
39
|
-
python -m pip install dist/release/matelab_python_sdk-0.1.
|
|
39
|
+
python -m pip install dist/release/matelab_python_sdk-0.1.0a23-py3-none-any.whl
|
|
40
40
|
```
|
|
41
41
|
|
|
42
42
|
Do not infer Provider compatibility from the SDK version alone. A release is also bound to the Contract
|
|
43
43
|
tag, commit, and checksum recorded below.
|
|
44
44
|
|
|
45
45
|
The Contract and public SDK interface track the current reviewed Provider at
|
|
46
|
-
`
|
|
46
|
+
`59eb05b233e5ddd023a60ccb5e5c5f2ae22a70f0`. Provider limitations that remain relevant to integrations are documented
|
|
47
47
|
alongside the affected interfaces below.
|
|
48
48
|
|
|
49
49
|
## Design
|
|
@@ -93,7 +93,7 @@ success code it does not allow raises `MatelabProtocolError` rather than assigni
|
|
|
93
93
|
`MatelabTransportError.status_code` remains available for HTTP failures. Provider response bodies, top-level `msg`,
|
|
94
94
|
legacy `errmsg`, opaque `errs`, uncontracted debug fields, and request payloads are not attached to exceptions because
|
|
95
95
|
Provider-authored diagnostics may contain stack traces, credentials, or submitted values.
|
|
96
|
-
The pinned v0.4.
|
|
96
|
+
The pinned v0.4.5 Contract also declares optional code-4 `errs`, but PVD-041 and PCG-015 record opaque array/object
|
|
97
97
|
containers and insufficient evidence for requiredness, cardinality, or exclusion from other codes. The SDK
|
|
98
98
|
therefore keeps those diagnostics wire-only and does not expose `errs` as stable public metadata. A typed detail
|
|
99
99
|
interface remains blocked until a future Contract release can define one from stronger Provider evidence. `retryable`
|
|
@@ -289,7 +289,7 @@ result cannot be retried through the same client.
|
|
|
289
289
|
|
|
290
290
|
Files-module append and table/files replacement use the general record attachment intents and may be combined with
|
|
291
291
|
other non-conflicting record changes. `RecordFilesAttachmentAppend` currently targets the module root. Although
|
|
292
|
-
v0.4.
|
|
292
|
+
v0.4.5 records Provider evidence for safe folder paths, its top-level `UpdateRecordRequest.add[]` schema still excludes
|
|
293
293
|
the `path` property declared by the dedicated staged-name append shape; the SDK therefore does not expose folder paths
|
|
294
294
|
until that wire-schema conflict is resolved (PVD-044):
|
|
295
295
|
|
|
@@ -326,6 +326,12 @@ not accept a caption. Record reads likewise accept only one canonical attachment
|
|
|
326
326
|
an attachment array violates the pinned response contract. Both replacement forms require the replacement hash to
|
|
327
327
|
differ from the current occurrence.
|
|
328
328
|
|
|
329
|
+
As a temporary Provider-compatibility exception, `records.read()` also accepts a table file attachment object whose
|
|
330
|
+
otherwise valid `filename`, `hash`, and `size` are present but whose Contract-required `timestamp` is omitted. The SDK
|
|
331
|
+
preserves that omission in `Record.modules` and does not invent a timestamp; null timestamps and all other malformed
|
|
332
|
+
attachment shapes remain response errors. This exception can be removed after the Provider canonicalizes current and
|
|
333
|
+
historical record content.
|
|
334
|
+
|
|
329
335
|
All record attachment intents reject handles from another notebook or authenticated uploader, duplicate use within one
|
|
330
336
|
finalization request, and raw Provider attachment references. Row/index-based operations use an immediate SDK read and
|
|
331
337
|
must not be called while a concurrent editor is known to be active. The Provider offers no expected hash or revision,
|
|
@@ -892,6 +898,6 @@ uv run python scripts/check_release.py dist/release/*.whl dist/release/*.tar.gz
|
|
|
892
898
|
```
|
|
893
899
|
|
|
894
900
|
Rebuilding the same commit with the same locked environment and `SOURCE_DATE_EPOCH` must produce
|
|
895
|
-
byte-identical wheel and source distribution hashes. The release is bound to immutable `matelab-spec v0.4.
|
|
896
|
-
`
|
|
897
|
-
`
|
|
901
|
+
byte-identical wheel and source distribution hashes. The release is bound to immutable `matelab-spec v0.4.5`, commit
|
|
902
|
+
`6afac7ff151680130e6e4bc6d95782589b97f4e7`, and OpenAPI SHA-256
|
|
903
|
+
`31a8d03b4366e2345dfccdbbaaaccf6754b2711428afa4295d778d121988460d`.
|
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
"schema_version": 1,
|
|
3
3
|
"contract_line": "integration-v1",
|
|
4
4
|
"release": {
|
|
5
|
-
"tag": "v0.4.
|
|
6
|
-
"commit": "
|
|
5
|
+
"tag": "v0.4.5",
|
|
6
|
+
"commit": "6afac7ff151680130e6e4bc6d95782589b97f4e7"
|
|
7
7
|
},
|
|
8
8
|
"openapi": {
|
|
9
9
|
"source_path": "openapi/matelab-integration-v1.openapi.yaml",
|
|
10
10
|
"snapshot": "matelab-integration-v1.openapi.yaml",
|
|
11
|
-
"sha256": "
|
|
11
|
+
"sha256": "31a8d03b4366e2345dfccdbbaaaccf6754b2711428afa4295d778d121988460d"
|
|
12
12
|
}
|
|
13
13
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
openapi: 3.1.0
|
|
2
2
|
info:
|
|
3
3
|
title: Matelab Integration Contract
|
|
4
|
-
version: 0.4.
|
|
4
|
+
version: 0.4.5
|
|
5
5
|
description: |
|
|
6
6
|
Current PHP wire contract for generating SDKs against the selected
|
|
7
7
|
`matelab-server` Provider version.
|
|
@@ -9,7 +9,12 @@ info:
|
|
|
9
9
|
x-contract-line: integration-v1
|
|
10
10
|
x-provider-evidence:
|
|
11
11
|
repository: /home/tnzhu/projects/matelab-server
|
|
12
|
-
commit:
|
|
12
|
+
commit: 59eb05b233e5ddd023a60ccb5e5c5f2ae22a70f0
|
|
13
|
+
productionRuntime:
|
|
14
|
+
source: /home/elnhr/matelab
|
|
15
|
+
workerStartedAt: "2026-09-02T08:11:00+08:00"
|
|
16
|
+
coreEquivalentCommit: 86c57f60aa66bf794d6227c77d1d483b3ea8c271
|
|
17
|
+
comparison: crlf-insensitive-core-tree
|
|
13
18
|
files:
|
|
14
19
|
- controller/Actions.php
|
|
15
20
|
- controller/Tokens.php
|
|
@@ -34,13 +39,16 @@ info:
|
|
|
34
39
|
- lib/Route.php
|
|
35
40
|
- lib/HttpServer.php
|
|
36
41
|
- lib/MultiException.php
|
|
42
|
+
- config/Db.php
|
|
37
43
|
- config/Channel.php
|
|
38
44
|
- sql/eln.sql
|
|
39
45
|
- sql/matelab.sql
|
|
40
46
|
- sql/recommend.sql
|
|
41
47
|
x-provider-audit:
|
|
42
|
-
currentCommit:
|
|
43
|
-
|
|
48
|
+
currentCommit: 59eb05b233e5ddd023a60ccb5e5c5f2ae22a70f0
|
|
49
|
+
productionRuntimeCoreEquivalentCommit: 86c57f60aa66bf794d6227c77d1d483b3ea8c271
|
|
50
|
+
previousReleaseEvidenceCommit: 7bd9704286057653632424fa82f413d99cd69ab6
|
|
51
|
+
lastProviderVerifiedCommit: 59eb05b233e5ddd023a60ccb5e5c5f2ae22a70f0
|
|
44
52
|
contractStatus: current-provider-aligned
|
|
45
53
|
incorporatedChanges:
|
|
46
54
|
- {operationId: stageTemporaryFile, change: staging-identifiers-expanded-to-100}
|
|
@@ -52,7 +60,7 @@ info:
|
|
|
52
60
|
- {operationId: updateRecordData, change: safe-files-folder-path-restored-to-callable-shape}
|
|
53
61
|
- {operationId: readApplicationBootstrap, change: identity-and-notifications-moved-out-of-app-response}
|
|
54
62
|
- {operationId: readRecord, change: table-file-cell-readback-modeled-as-attachment-object-or-null}
|
|
55
|
-
- {operationId: readRecord, change:
|
|
63
|
+
- {operationId: readRecord, change: missing-datetime-locked-normalized-to-zero}
|
|
56
64
|
- {operationId: readTemplateContent, change: json-code-null-readback-accepted}
|
|
57
65
|
- {operationId: saveTemplateContent, change: empty-number-and-boolean-cells-preserved}
|
|
58
66
|
- {operationId: saveTemplateContent, change: noncanonical-module-structures-contract-contained}
|
|
@@ -2568,7 +2576,7 @@ paths:
|
|
|
2568
2576
|
uid: REC-EXAMPLE-001
|
|
2569
2577
|
current_version: 70001
|
|
2570
2578
|
item_id: 9001
|
|
2571
|
-
datetime_locked:
|
|
2579
|
+
datetime_locked: 0
|
|
2572
2580
|
locked: false
|
|
2573
2581
|
signs: []
|
|
2574
2582
|
signed: false
|
|
@@ -6527,12 +6535,9 @@ components:
|
|
|
6527
6535
|
type: integer
|
|
6528
6536
|
minimum: 1
|
|
6529
6537
|
datetime_locked:
|
|
6530
|
-
type:
|
|
6538
|
+
type: integer
|
|
6531
6539
|
minimum: 0
|
|
6532
|
-
description:
|
|
6533
|
-
Unix timestamp in milliseconds; null for the current unlocked
|
|
6534
|
-
record. Provider paths that normalize the absent timestamp may
|
|
6535
|
-
return zero.
|
|
6540
|
+
description: Unix timestamp in milliseconds; zero when no lock timestamp is available.
|
|
6536
6541
|
locked:
|
|
6537
6542
|
type: boolean
|
|
6538
6543
|
signs:
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# SDK Architecture Invariants
|
|
2
|
+
|
|
3
|
+
本文件按 `AGENTS.md` 的 architecture 指针加载;只记录环境和配置无法直接表达的设计边界。
|
|
4
|
+
|
|
5
|
+
## Public interface
|
|
6
|
+
|
|
7
|
+
- 本项目是 Matelab Integration Contract 的通用异步 Python Consumer,负责隐藏 HTTP 编码、认证刷新、业务错误、
|
|
8
|
+
wire model 和 Provider 兼容细节。
|
|
9
|
+
- `AsyncMatelab` 是稳定入口。public interface 表达用户意图,并隐藏 controller、path、opcode、wire 字段、
|
|
10
|
+
generated model 和认证刷新细节。
|
|
11
|
+
- 公共类型区分 userid、database id、UID、relation id、staging id 等身份空间。只表达 Provider 实际返回的
|
|
12
|
+
identity、affected rows、排序、分页、原子性、CAS、幂等性和持久化结果。
|
|
13
|
+
- MCP typed refs、capabilities、fingerprints、FastMCP/Redis context 与 Agent 展示模型留在 Consumer。只有真实
|
|
14
|
+
第二种实现才能引入 seam。
|
|
15
|
+
- 支持 Python 3.11+;源码不使用 `from __future__ import annotations`,前向引用优先靠定义顺序解决。
|
|
16
|
+
|
|
17
|
+
## Transport, Session, and errors
|
|
18
|
+
|
|
19
|
+
- `base_url` 是 Provider API 根;query、JSON、form、multipart、streaming、bearer 和成功码逐 operation 配置。
|
|
20
|
+
- `Token`、`Identity`、`Session` 是冻结且拒绝额外字段的 public model,组成规范化 Session JSON。每个
|
|
21
|
+
`AsyncMatelab` 最多持有一个最新 Session;SDK 不绑定裸 token。不同逻辑 Session 使用不同 client,可共享注入的
|
|
22
|
+
`httpx2.AsyncClient`。
|
|
23
|
+
- SDK 负责 bearer 注入、到期前刷新、单锁并发刷新和明确允许的单次 code-5 replay。集成方负责 Session
|
|
24
|
+
持久化、加密、撤销、身份归属和跨进程锁;mutation 默认不重试,multipart/stream 仅在可重放时重试。
|
|
25
|
+
- token 在 repr 中隐藏;显式 Session JSON 保留凭证。日志、异常和提交内容排除凭证、SSO 参数、附件内容及
|
|
26
|
+
敏感 payload。download stream 必须可显式关闭。
|
|
27
|
+
- transport、Provider business/authentication、Contract/schema 与 usage 错误保持稳定区分;公开错误提供集成级
|
|
28
|
+
`category` 和保守的 `retryable`,下游不重新解释 Provider 数字 code。
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# Agent Maintenance Workflows
|
|
2
|
+
|
|
3
|
+
本文件只在对应分支触发时加载。机器值始终从仓库读取;这里记录顺序、授权边界和完成标准。
|
|
4
|
+
|
|
5
|
+
## Contract adoption
|
|
6
|
+
|
|
7
|
+
1. 读取当前工作区、Contract lock 和远端 release refs。只选择新的 annotated tag;移动分支和改写后的旧
|
|
8
|
+
snapshot 不能成为 SDK pin。记录 tag object、peeled commit、OpenAPI path 与 SHA-256。完成标准:四者固定且
|
|
9
|
+
互相一致。
|
|
10
|
+
2. 比较当前 snapshot 与 release commit。逐项核对 operation request、response、security、examples、成功码、
|
|
11
|
+
Provider evidence 及 PVD/PCG。完成标准:每个 schema/operation 差异都分类为 generated-only、adapter、public
|
|
12
|
+
interface、test、coverage 或 docs 影响。
|
|
13
|
+
3. request/response 均由生成 wire model 校验,adapter 只映射 public Python 名称与 wire 字段。required nullable
|
|
14
|
+
使用无默认值的 `T | None`;request 拒绝未知字段,response 接受新增字段。若专用 schema、顶层 request 或
|
|
15
|
+
Provider evidence 相互矛盾,保留当前安全边界并报告缺口。
|
|
16
|
+
4. 从 release commit 复制 OpenAPI snapshot;同一变更更新 lock 的 tag、commit、snapshot path 和 SHA-256,运行
|
|
17
|
+
生成脚本并审查完整 generated diff。`_generated` 只由生成脚本修改;projection 只补偿生成器表达能力并保留
|
|
18
|
+
inherited required、`if/then`、`oneOf`、`anyOf` 和 `not`。完成标准:snapshot hash 与 lock 相等,生成差异
|
|
19
|
+
全部可由 OpenAPI 差异解释,`--check` 确定、非写入且能发现 stale output。
|
|
20
|
+
5. 同步 runtime、测试、operation coverage、README、CHANGELOG、`pyproject.toml` 与 `uv.lock` 中受影响的当前态。
|
|
21
|
+
完成标准:每个 Contract 差异都有实现或明确 containment,历史 changelog 未被改写。
|
|
22
|
+
6. 执行 [delivery gate](#delivery-gate)。
|
|
23
|
+
|
|
24
|
+
## Delivery gate
|
|
25
|
+
|
|
26
|
+
先运行改动对应的聚焦测试。新增或修改 operation 覆盖 method/path/encoding、认证、成功映射及适用的空结果、
|
|
27
|
+
required nullable 和 additive response fields;operation coverage 与 pinned OpenAPI exact match,implemented
|
|
28
|
+
operation 同时具备 public interface、wire adapter 和测试。删除或收紧 public interface 时同步移除过时测试,
|
|
29
|
+
不保留无承诺的墓碑 alias。生成测试覆盖 metadata/checksum、schema、required/nullable、extra policy、确定性和
|
|
30
|
+
stale detection。
|
|
31
|
+
|
|
32
|
+
Ruff/Basedpyright 诊断通过修复代码或精确 ignore 解决,不缩小检查范围;Basedpyright 目标为 0 errors / 0 warnings。
|
|
33
|
+
依赖变化声明已验证下限并排除未经验证的下一 major,构建后端范围与 wheel package 显式配置。
|
|
34
|
+
|
|
35
|
+
交付代码、Contract、依赖或构建元数据前,依次完成:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
uv sync --frozen
|
|
39
|
+
uv run python scripts/generate_models.py --check
|
|
40
|
+
uv run ruff check .
|
|
41
|
+
uv run ruff format --check .
|
|
42
|
+
uv run basedpyright
|
|
43
|
+
uv run pytest
|
|
44
|
+
uv build --no-build-isolation
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
若沙箱导致缓存、Node wheel 或解释器发现失败,在沙箱外复核同一锁定命令。完成标准:所有命令退出为零;skip
|
|
48
|
+
按测试名称和原因报告,不能计为通过;最终 `git diff --check` 无错误,最终 diff 只包含任务范围内文件。
|
|
49
|
+
|
|
50
|
+
## Provider validation
|
|
51
|
+
|
|
52
|
+
1. 默认使用 pinned examples、`MockTransport` 和 SDK adapter tests。Provider smoke 默认 skip 且不自动读取
|
|
53
|
+
`.env`;实时 Provider 只在当前任务确实需要且本轮已明确授权时启用。
|
|
54
|
+
2. 执行前确认目标是隔离服务、`.env.test` 处于 Git ignore、命令显式加载该文件,并列出 authentication、refresh、
|
|
55
|
+
mutation 或带写痕迹读取的副作用。生产目标或 mutation 需要本轮单独明确授权。
|
|
56
|
+
3. SDK public smoke 使用 README 的 `Opt-in Provider consumer smoke` 命令;route-by-route conformance 由
|
|
57
|
+
`matelab-spec` Provider Verification 负责。两者的结果分开报告。
|
|
58
|
+
4. mutation 场景在写前记录 fixture,在写后回读,并尽力清理。完成标准:目标、Provider commit、命令、通过数、
|
|
59
|
+
skip/failure 和清理结果均可追溯;凭证及敏感 payload 未进入输出或提交。
|
|
60
|
+
|
|
61
|
+
## Release
|
|
62
|
+
|
|
63
|
+
1. 只有用户明确要求 commit、push、tag 或 publish 时进入发布流程。读取 `[project].version`,确认版本未发布、
|
|
64
|
+
changelog 已追加且 Contract pin 完整。SDK 遵循 SemVer;公开接口删除、重命名、类型收紧或行为变化按 breaking
|
|
65
|
+
change 处理,Contract Line 不充当 SDK 版本。完成标准:版本、文档和 tag 名 `v{version}` 一致。
|
|
66
|
+
2. 在完整 delivery gate 通过后创建单一 release commit。完成标准:tracked worktree 干净,commit 仅包含已审查
|
|
67
|
+
diff。
|
|
68
|
+
3. 以 release commit 的 committer timestamp 设置 `SOURCE_DATE_EPOCH`,从锁定环境构建两次并逐字节比较 wheel
|
|
69
|
+
与 sdist;运行 `scripts/check_release.py`,再从 wheel 验证公开导入和类型消费。完成标准:制品可复现,
|
|
70
|
+
Apache-2.0 `LICENSE`/`NOTICE`、PEP 639 METADATA、`py.typed` 与公开边界审计通过,`_generated` 未公开,
|
|
71
|
+
checksum 已记录。
|
|
72
|
+
4. 创建指向 release commit 的 annotated `v{version}` tag。先推送 `origin` 的 main/tag,再推送 GitHub mirror 的
|
|
73
|
+
main/tag;只有与项目版本完全匹配的 tag 触发 Trusted Publishing workflow,第三方 Action 固定 immutable
|
|
74
|
+
commit SHA。
|
|
75
|
+
5. 跟踪 CI 的 Python 边界测试、生成检查、可复现构建、安装后消费和 PyPI publish。完成标准:两个远端 main/tag
|
|
76
|
+
都指向 release commit,workflow success,PyPI 对应版本可读取,本地 tracked worktree 干净。
|
{matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/src/matelab/_generated/models.py
RENAMED
|
@@ -11,7 +11,7 @@ from pydantic import ConfigDict, Field, RootModel, SecretStr, StrictBool, Strict
|
|
|
11
11
|
|
|
12
12
|
from matelab._wire_validation import WireModel
|
|
13
13
|
|
|
14
|
-
OPENAPI_INFO_VERSION = "0.4.
|
|
14
|
+
OPENAPI_INFO_VERSION = "0.4.5"
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
class ResponseEnvelope(WireModel):
|
|
@@ -3741,12 +3741,9 @@ class ItemViewResponse(WireModel):
|
|
|
3741
3741
|
"""
|
|
3742
3742
|
current_version: Annotated[StrictInt, Field(ge=0)]
|
|
3743
3743
|
item_id: Annotated[StrictInt, Field(ge=1)]
|
|
3744
|
-
datetime_locked: Annotated[StrictInt
|
|
3744
|
+
datetime_locked: Annotated[StrictInt, Field(ge=0)]
|
|
3745
3745
|
"""
|
|
3746
|
-
Unix timestamp in milliseconds;
|
|
3747
|
-
record. Provider paths that normalize the absent timestamp may
|
|
3748
|
-
return zero.
|
|
3749
|
-
|
|
3746
|
+
Unix timestamp in milliseconds; zero when no lock timestamp is available.
|
|
3750
3747
|
"""
|
|
3751
3748
|
locked: StrictBool
|
|
3752
3749
|
signs: list[RecordSignature]
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import re
|
|
2
2
|
import secrets
|
|
3
3
|
from collections.abc import AsyncIterable, Iterable, Iterator, Mapping, Sequence
|
|
4
|
+
from copy import deepcopy
|
|
4
5
|
from dataclasses import dataclass, field
|
|
5
6
|
from html.parser import HTMLParser
|
|
6
7
|
from types import MappingProxyType
|
|
7
|
-
from typing import IO, Literal, TypeAlias, cast
|
|
8
|
+
from typing import IO, Literal, Self, TypeAlias, cast
|
|
8
9
|
from urllib.parse import unquote
|
|
9
10
|
|
|
10
|
-
from pydantic import BaseModel, JsonValue
|
|
11
|
+
from pydantic import BaseModel, JsonValue, model_validator
|
|
11
12
|
from typing_extensions import override
|
|
12
13
|
|
|
13
14
|
from matelab._generated import models as wire_models
|
|
@@ -21,6 +22,56 @@ from matelab.domains.users import UserRef
|
|
|
21
22
|
from matelab.errors import MatelabProtocolError, MatelabUsageError
|
|
22
23
|
from matelab.streaming import ByteRange, DownloadStream
|
|
23
24
|
|
|
25
|
+
_MISSING_TIMESTAMP_MARKER = "__matelab_missing_record_attachment_timestamp__"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _table_file_attachments(modules: JsonValue) -> Iterator[dict[str, JsonValue]]:
|
|
29
|
+
if not isinstance(modules, list):
|
|
30
|
+
return
|
|
31
|
+
for module in modules:
|
|
32
|
+
if not isinstance(module, dict) or module.get("type") != "table":
|
|
33
|
+
continue
|
|
34
|
+
columns = module.get("data")
|
|
35
|
+
if not isinstance(columns, list):
|
|
36
|
+
continue
|
|
37
|
+
for column in columns:
|
|
38
|
+
if not isinstance(column, dict) or column.get("type") != "file":
|
|
39
|
+
continue
|
|
40
|
+
values = column.get("data")
|
|
41
|
+
if not isinstance(values, list):
|
|
42
|
+
continue
|
|
43
|
+
for value in values:
|
|
44
|
+
if isinstance(value, dict):
|
|
45
|
+
yield value
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class _ReadRecordResponse(wire_models.ItemViewResponse):
|
|
49
|
+
"""Contain the Provider's temporary omission of stored attachment timestamps."""
|
|
50
|
+
|
|
51
|
+
@model_validator(mode="before")
|
|
52
|
+
@classmethod
|
|
53
|
+
def _allow_missing_attachment_timestamp(cls, value: object) -> object:
|
|
54
|
+
if not isinstance(value, dict):
|
|
55
|
+
return value
|
|
56
|
+
payload = deepcopy(cast(dict[str, JsonValue], value))
|
|
57
|
+
modules = payload.get("modules")
|
|
58
|
+
if modules is None:
|
|
59
|
+
return payload
|
|
60
|
+
for attachment in _table_file_attachments(modules):
|
|
61
|
+
if "timestamp" not in attachment and _MISSING_TIMESTAMP_MARKER not in attachment:
|
|
62
|
+
attachment["timestamp"] = 1
|
|
63
|
+
attachment[_MISSING_TIMESTAMP_MARKER] = True
|
|
64
|
+
return payload
|
|
65
|
+
|
|
66
|
+
@model_validator(mode="after")
|
|
67
|
+
def _restore_missing_attachment_timestamp(self) -> Self:
|
|
68
|
+
modules: JsonValue = [{"type": module.type, "data": cast(JsonValue, module.data)} for module in self.modules]
|
|
69
|
+
for attachment in _table_file_attachments(modules):
|
|
70
|
+
if attachment.pop(_MISSING_TIMESTAMP_MARKER, None) is True:
|
|
71
|
+
del attachment["timestamp"]
|
|
72
|
+
return self
|
|
73
|
+
|
|
74
|
+
|
|
24
75
|
_LIST_RECORDS = Operation(
|
|
25
76
|
method="POST",
|
|
26
77
|
path="/eln_api/items",
|
|
@@ -68,7 +119,7 @@ _READ_RECORD = Operation(
|
|
|
68
119
|
method="GET",
|
|
69
120
|
path="/eln_items/item_view",
|
|
70
121
|
encoding=Encoding.QUERY,
|
|
71
|
-
response_model=
|
|
122
|
+
response_model=_ReadRecordResponse,
|
|
72
123
|
success_codes=frozenset({0}),
|
|
73
124
|
retry_on_access_expired=True,
|
|
74
125
|
)
|
|
@@ -88,7 +88,7 @@ def test_generated_response_preserves_required_nullable_fields() -> None:
|
|
|
88
88
|
"uid": None,
|
|
89
89
|
"current_version": 0,
|
|
90
90
|
"item_id": 1,
|
|
91
|
-
"datetime_locked":
|
|
91
|
+
"datetime_locked": 0,
|
|
92
92
|
"locked": False,
|
|
93
93
|
"signs": [],
|
|
94
94
|
"signed": False,
|
|
@@ -107,7 +107,12 @@ def test_generated_response_preserves_required_nullable_fields() -> None:
|
|
|
107
107
|
assert ItemViewResponse.model_fields["keywords"].is_required()
|
|
108
108
|
assert response.keywords is None
|
|
109
109
|
assert ItemViewResponse.model_fields["datetime_locked"].is_required()
|
|
110
|
-
assert response.datetime_locked
|
|
110
|
+
assert response.datetime_locked == 0
|
|
111
|
+
|
|
112
|
+
invalid = response.model_dump(mode="json")
|
|
113
|
+
invalid["datetime_locked"] = None
|
|
114
|
+
with pytest.raises(ValidationError):
|
|
115
|
+
_ = ItemViewResponse.model_validate(invalid)
|
|
111
116
|
|
|
112
117
|
|
|
113
118
|
def _item_view_payload(modules: list[object]) -> dict[str, object]:
|
|
@@ -523,7 +528,7 @@ def test_generated_models_cover_every_component_schema_and_non_streaming_operati
|
|
|
523
528
|
}
|
|
524
529
|
assert expected_response_models <= class_names
|
|
525
530
|
assert len(operation_ids) == 71
|
|
526
|
-
assert wire_models.OPENAPI_INFO_VERSION == "0.4.
|
|
531
|
+
assert wire_models.OPENAPI_INFO_VERSION == "0.4.5"
|
|
527
532
|
|
|
528
533
|
|
|
529
534
|
def test_generation_is_deterministic(tmp_path: Path) -> None:
|
|
@@ -635,8 +640,8 @@ def test_contract_lock_is_the_single_release_pin() -> None:
|
|
|
635
640
|
openapi = cast(dict[str, Any], lock["openapi"])
|
|
636
641
|
assert set(release) == {"tag", "commit"}
|
|
637
642
|
assert set(openapi) == {"source_path", "snapshot", "sha256"}
|
|
638
|
-
assert release == {"tag": "v0.4.
|
|
639
|
-
assert openapi["sha256"] == "
|
|
643
|
+
assert release == {"tag": "v0.4.5", "commit": "6afac7ff151680130e6e4bc6d95782589b97f4e7"}
|
|
644
|
+
assert openapi["sha256"] == "31a8d03b4366e2345dfccdbbaaaccf6754b2711428afa4295d778d121988460d"
|
|
640
645
|
assert re.fullmatch(r"[0-9a-f]{40}", cast(str, release["commit"]))
|
|
641
646
|
assert re.fullmatch(r"[0-9a-f]{64}", cast(str, openapi["sha256"]))
|
|
642
647
|
assert not (ROOT / "contracts" / "matelab-integration-v1.sha256").exists()
|
|
@@ -243,7 +243,7 @@ async def test_read_current_record_maps_required_nullable_and_open_modules() ->
|
|
|
243
243
|
uid="REC-EXAMPLE-001",
|
|
244
244
|
current_version=70001,
|
|
245
245
|
item_id=9001,
|
|
246
|
-
datetime_locked=
|
|
246
|
+
datetime_locked=0,
|
|
247
247
|
locked=False,
|
|
248
248
|
signs=[
|
|
249
249
|
{
|
|
@@ -303,6 +303,77 @@ async def test_read_current_record_maps_required_nullable_and_open_modules() ->
|
|
|
303
303
|
assert "74034dae" not in repr(record.attachments[0])
|
|
304
304
|
|
|
305
305
|
|
|
306
|
+
@pytest.mark.asyncio
|
|
307
|
+
async def test_read_tolerates_provider_table_attachment_without_timestamp() -> None:
|
|
308
|
+
payload = _item_view_payload(uid="REC-EXAMPLE-001")
|
|
309
|
+
attachment = {
|
|
310
|
+
"hash": "d6a8f491a743ee3c10b42b4519d7545d7f1d79a7e98ed2ef9c03c0c1d75a1a91",
|
|
311
|
+
"filename": "evidence.pdf",
|
|
312
|
+
"size": 34,
|
|
313
|
+
}
|
|
314
|
+
payload["modules"] = [
|
|
315
|
+
{
|
|
316
|
+
"type": "table",
|
|
317
|
+
"name": "Data sheet",
|
|
318
|
+
"rows": ["row-1"],
|
|
319
|
+
"data": [{"name": "File", "type": "file", "data": [attachment]}],
|
|
320
|
+
}
|
|
321
|
+
]
|
|
322
|
+
|
|
323
|
+
async def handler(_: Request) -> Response:
|
|
324
|
+
return Response(200, json=payload)
|
|
325
|
+
|
|
326
|
+
notebook = NotebookRef(notebook_id=23, title="Shared Example", owner_userid=202, scope="shared")
|
|
327
|
+
source = RecordLocator(notebook=notebook, record=RecordRef(record_id=9001, record_uid="REC-EXAMPLE-001"))
|
|
328
|
+
async with AsyncClient(base_url="https://matelab.example/api", transport=MockTransport(handler)) as http_client:
|
|
329
|
+
client = AsyncMatelab("https://ignored.example", session=_session(), http_client=http_client)
|
|
330
|
+
record = await client.records.read(source)
|
|
331
|
+
|
|
332
|
+
assert record.modules[0] == {
|
|
333
|
+
"name": "Data sheet",
|
|
334
|
+
"type": "table",
|
|
335
|
+
"data": [{"name": "File", "type": "file", "data": [attachment]}],
|
|
336
|
+
"language": None,
|
|
337
|
+
"style": None,
|
|
338
|
+
"rows": ["row-1"],
|
|
339
|
+
}
|
|
340
|
+
assert [(item.filename, item.size) for item in record.attachments] == [("evidence.pdf", 34)]
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
@pytest.mark.parametrize("invalid_change", ["filename", "hash", "size", "null_timestamp"])
|
|
344
|
+
@pytest.mark.asyncio
|
|
345
|
+
async def test_read_timestamp_compatibility_does_not_relax_other_attachment_requirements(invalid_change: str) -> None:
|
|
346
|
+
payload = _item_view_payload(uid="REC-EXAMPLE-001")
|
|
347
|
+
attachment: dict[str, object] = {
|
|
348
|
+
"hash": "d6a8f491a743ee3c10b42b4519d7545d7f1d79a7e98ed2ef9c03c0c1d75a1a91",
|
|
349
|
+
"filename": "evidence.pdf",
|
|
350
|
+
"size": 34,
|
|
351
|
+
"timestamp": 1_767_946_200_000,
|
|
352
|
+
}
|
|
353
|
+
if invalid_change == "null_timestamp":
|
|
354
|
+
attachment["timestamp"] = None
|
|
355
|
+
else:
|
|
356
|
+
del attachment[invalid_change]
|
|
357
|
+
payload["modules"] = [
|
|
358
|
+
{
|
|
359
|
+
"type": "table",
|
|
360
|
+
"name": "Data sheet",
|
|
361
|
+
"rows": ["row-1"],
|
|
362
|
+
"data": [{"name": "File", "type": "file", "data": [attachment]}],
|
|
363
|
+
}
|
|
364
|
+
]
|
|
365
|
+
|
|
366
|
+
async def handler(_: Request) -> Response:
|
|
367
|
+
return Response(200, json=payload)
|
|
368
|
+
|
|
369
|
+
notebook = NotebookRef(notebook_id=23, title="Shared Example", owner_userid=202, scope="shared")
|
|
370
|
+
source = RecordLocator(notebook=notebook, record=RecordRef(record_id=9001, record_uid="REC-EXAMPLE-001"))
|
|
371
|
+
async with AsyncClient(base_url="https://matelab.example/api", transport=MockTransport(handler)) as http_client:
|
|
372
|
+
client = AsyncMatelab("https://ignored.example", session=_session(), http_client=http_client)
|
|
373
|
+
with pytest.raises(MatelabProtocolError, match="does not match the Integration Contract"):
|
|
374
|
+
_ = await client.records.read(source)
|
|
375
|
+
|
|
376
|
+
|
|
306
377
|
@pytest.mark.asyncio
|
|
307
378
|
async def test_read_rejects_historical_table_attachment_array_at_public_boundary() -> None:
|
|
308
379
|
payload = _item_view_payload(uid="REC-EXAMPLE-001")
|
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
# AGENTS.md
|
|
2
|
-
|
|
3
|
-
本文档只保留维护 `matelab-python-sdk` 必须长期遵守的约束,不记录当前功能清单、迁移历史、阶段性状态或
|
|
4
|
-
一次性验收数字。
|
|
5
|
-
|
|
6
|
-
## 文档与权威边界
|
|
7
|
-
|
|
8
|
-
- `README.md`:当前 public interface、集成语义、示例及可执行的开发/发布流程;不承担版本迁移历史。
|
|
9
|
-
- `CONTEXT.md`:公共领域术语及易混概念的统一词汇;不记录实现或协议细节。
|
|
10
|
-
- `CHANGELOG.md`:按 SDK 版本记录变化和迁移影响;不作为当前接口说明。
|
|
11
|
-
- `docs/operation-coverage.yaml`:operation 状态、公开入口、稳定性和已知问题的机器权威;不写叙述性用法。
|
|
12
|
-
- `pyproject.toml`:SDK 版本、依赖范围、构建和工具配置的机器权威;`uv.lock` 只锁定本仓库环境。
|
|
13
|
-
- `contracts/matelab-integration-v1.lock.json` 及其 snapshot:Contract pin 和 wire schema 的唯一机器权威。
|
|
14
|
-
- `src/matelab/__init__.py::__all__`:公开导入边界;测试是行为证据,不替代上述文档。
|
|
15
|
-
- 本文件只规定维护原则和质量门槛。机器权威变化时,同一变更必须同步受影响的叙述文档,避免复制无归属的
|
|
16
|
-
版本号、配置值或 operation 清单。
|
|
17
|
-
|
|
18
|
-
## 工作范围与 public interface
|
|
19
|
-
|
|
20
|
-
- 本项目是 Matelab Integration Contract 的通用异步 Python Consumer,负责隐藏 HTTP 编码、认证刷新、业务
|
|
21
|
-
错误、wire model 和 Provider 兼容细节。
|
|
22
|
-
- `/home/tnzhu/projects/matelab-spec` 负责 Wire Contract、兼容策略及 Provider 问题;`matelab-server` 是 Provider
|
|
23
|
-
实现,`matelab-mcp` 只是 Consumer 证据,均不能反向定义 SDK interface。
|
|
24
|
-
- 修改前执行 `git status`,现有改动视为用户工作。默认只修改本仓库;未经明确授权,不修改相邻仓库、不迁移
|
|
25
|
-
MCP、不 commit/push/tag/publish,也不对真实 Provider 执行 mutation。
|
|
26
|
-
- `AsyncMatelab` 是稳定入口,能力按领域组织。public interface 只表达用户意图,不泄漏 controller、path、
|
|
27
|
-
opcode、wire 字段、generated model 或认证刷新细节。
|
|
28
|
-
- 公共类型必须区分 userid、database id、UID、relation id、staging id 等身份空间。Provider 未返回的 identity、
|
|
29
|
-
affected rows、排序、分页、原子性、CAS、幂等性或持久化结果不得推测;legacy 限制应诚实表达。
|
|
30
|
-
- MCP typed refs、capabilities、fingerprints、FastMCP/Redis context 和 Agent 展示模型不得进入 SDK。仅在存在真实
|
|
31
|
-
第二种实现时增加 seam,不为测试预设抽象。
|
|
32
|
-
- 支持 Python 3.11+;源码不使用 `from __future__ import annotations`,前向引用优先通过定义顺序解决。
|
|
33
|
-
|
|
34
|
-
## Contract、生成与 transport
|
|
35
|
-
|
|
36
|
-
- 每个 operation 必须核对 pinned OpenAPI 的 request、response、security、examples 和成功码,并查看相关
|
|
37
|
-
PVD/PCG。Contract 不足时可只读参考 Provider/Consumer;仍无法安全表达就报告缺口,不猜测或静默适配漂移。
|
|
38
|
-
- Contract Adoption 只能来自不可变 release tag,并在同一变更中更新唯一 lock 的 tag、commit、snapshot path
|
|
39
|
-
和 SHA-256,重新生成并复核差异;不得跟踪移动分支或改写正式 snapshot。
|
|
40
|
-
- request/response 均由生成 wire model 校验,adapter 只负责 public Python 名称与 wire 字段映射。required
|
|
41
|
-
nullable 使用无默认值的 `T | None`;request 拒绝未知字段,response 兼容新增字段。
|
|
42
|
-
- 不手工编辑 `_generated`。generation projection 只能补偿生成器表达能力,并保留 inherited required 及
|
|
43
|
-
`if/then`、`oneOf`、`anyOf`、`not`;生成变化必须可追溯,`--check` 必须确定、非写入且能发现 stale output。
|
|
44
|
-
- `base_url` 是 Provider API 根;query、JSON、form、multipart、streaming、bearer 和成功码均按 operation 配置,
|
|
45
|
-
不能用全局默认掩盖差异。
|
|
46
|
-
- `Token`、`Identity` 和 `Session` 是冻结、拒绝额外字段的 public model,组成规范化 Session JSON。每个
|
|
47
|
-
`AsyncMatelab` 最多持有一个最新 Session;SDK 不绑定裸 token,持久化、加密、撤销、身份归属和跨进程锁
|
|
48
|
-
属于集成方。不同逻辑 Session 不共享 client,但可共享注入的 `httpx.AsyncClient`。
|
|
49
|
-
- token 在 repr 中隐藏,显式 Session JSON 保留凭证;凭证、SSO 参数、附件内容及敏感 payload 不得进入日志、
|
|
50
|
-
异常或提交内容。
|
|
51
|
-
- access 到期前刷新;并发刷新使用单锁;安全请求遇到 `code=5` 最多刷新重试一次。mutation 默认不重试,
|
|
52
|
-
multipart/stream 仅在可重放时重试,download stream 必须可显式关闭。
|
|
53
|
-
- transport、Provider business/authentication、Contract/schema 和 usage 错误必须稳定区分;公开错误直接提供
|
|
54
|
-
集成级 `category` 和保守的 `retryable`,下游不应重新解释 Provider 数字 code。
|
|
55
|
-
|
|
56
|
-
## 测试与质量门槛
|
|
57
|
-
|
|
58
|
-
- 测试以 public interface 为主。新增或修改 operation 至少覆盖 method/path/encoding、认证、成功映射及适用的
|
|
59
|
-
空结果、required nullable、additive response fields;通用 transport/auth/error 语义在集中测试中覆盖。
|
|
60
|
-
- 删除或收紧 public interface 时同步删除过时测试;无明确兼容承诺时不保留墓碑 alias 或测试。
|
|
61
|
-
- operation coverage 必须与 pinned OpenAPI exact match;implemented operation 必须同时具备 public interface、
|
|
62
|
-
wire adapter 和测试。生成测试必须覆盖 metadata/checksum、schema、required/nullable、extra policy、确定性和
|
|
63
|
-
stale detection。
|
|
64
|
-
- Provider smoke 默认 skip 且不自动读取 `.env`;本地 `.env.test` 必须保持 Git ignore。只能通过显式
|
|
65
|
-
`uv run --env-file .env.test` 对已确认的隔离目标 opt in;生产目标、mutation 或带写痕迹的读取必须获得本轮
|
|
66
|
-
明确授权,并回读、尽力清理。
|
|
67
|
-
- Ruff/Basedpyright 的具体配置以 `pyproject.toml` 为准;不得通过排除测试、全局关规则或缩小检查范围消除诊断。
|
|
68
|
-
Basedpyright 目标为 0 errors / 0 warnings,必要 ignore 必须精确并说明原因。
|
|
69
|
-
- 先跑相关测试,交付前跑完整门槛;skip 必须说明原因,不能报告为通过:
|
|
70
|
-
|
|
71
|
-
```bash
|
|
72
|
-
uv sync --frozen
|
|
73
|
-
uv run python scripts/generate_models.py --check
|
|
74
|
-
uv run ruff check .
|
|
75
|
-
uv run ruff format --check .
|
|
76
|
-
uv run basedpyright
|
|
77
|
-
uv run pytest
|
|
78
|
-
uv build --no-build-isolation
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
若沙箱造成缓存、Node wheel 或解释器发现失败,应在沙箱外复核同一命令,不得改配置或使用未锁定工具绕过。
|
|
82
|
-
|
|
83
|
-
## 依赖、版本与发布
|
|
84
|
-
|
|
85
|
-
- runtime dependency 必须声明经支持 Python 版本验证的下限,并排除未经验证的下一个 major;构建后端范围和
|
|
86
|
-
wheel package 必须显式配置,不能依赖自动发现。
|
|
87
|
-
- `[project].version` 是唯一 SDK 版本源;SDK 使用 SemVer,Contract Line 不是 SDK 版本。公开接口删除、重命名、
|
|
88
|
-
类型收紧或行为变化均按 breaking change 处理。
|
|
89
|
-
- 发行采用 Apache-2.0;`LICENSE`、`NOTICE`、PEP 639 metadata 和制品内容必须一致,wheel 包含 `py.typed` 且
|
|
90
|
-
不公开 `_generated`。
|
|
91
|
-
- Gitee `origin` 是内部主仓库,私有 GitHub mirror 只承载 PyPI Trusted Publishing。release workflow 仅由与
|
|
92
|
-
项目版本完全匹配的 `v*` tag 触发,第三方 Action 固定不可变 commit SHA。
|
|
93
|
-
- release 必须来自干净 revision、锁定环境和固定 `SOURCE_DATE_EPOCH`,记录 Contract 与制品 checksum;候选
|
|
94
|
-
制品须审计内容/METADATA、验证安装后的公开导入和类型消费、覆盖 Python 版本边界并确认可复现构建。
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/scripts/check_installed_package.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/src/matelab/_generated/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/src/matelab/domains/cloud_drive.py
RENAMED
|
File without changes
|
|
File without changes
|
{matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/src/matelab/domains/literature.py
RENAMED
|
File without changes
|
{matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/src/matelab/domains/notebooks.py
RENAMED
|
File without changes
|
{matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/src/matelab/domains/templates.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/tests/provider/test_provider_smoke.py
RENAMED
|
File without changes
|
{matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/tests/test_async_attachment_upload.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/tests/test_comments_and_downloads.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/tests/test_notebook_lifecycle.py
RENAMED
|
File without changes
|
|
File without changes
|
{matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/tests/test_operation_coverage.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/tests/test_template_lifecycle.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{matelab_python_sdk-0.1.0a21 → matelab_python_sdk-0.1.0a23}/tests/test_uploads_and_literature.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|