matelab-python-sdk 0.2.0a1__tar.gz → 0.2.0a3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- matelab_python_sdk-0.2.0a3/.env.example +7 -0
- {matelab_python_sdk-0.2.0a1 → matelab_python_sdk-0.2.0a3}/.github/workflows/release.yml +2 -2
- {matelab_python_sdk-0.2.0a1 → matelab_python_sdk-0.2.0a3}/AGENTS.md +14 -9
- {matelab_python_sdk-0.2.0a1 → matelab_python_sdk-0.2.0a3}/CHANGELOG.md +23 -0
- matelab_python_sdk-0.2.0a3/PKG-INFO +26 -0
- {matelab_python_sdk-0.2.0a1 → matelab_python_sdk-0.2.0a3}/README.md +30 -34
- {matelab_python_sdk-0.2.0a1 → matelab_python_sdk-0.2.0a3}/docs/agent-architecture.md +2 -1
- matelab_python_sdk-0.2.0a3/docs/agent-workflows.md +129 -0
- {matelab_python_sdk-0.2.0a1 → matelab_python_sdk-0.2.0a3}/pyproject.toml +21 -22
- {matelab_python_sdk-0.2.0a1 → matelab_python_sdk-0.2.0a3}/scripts/check_release.py +6 -9
- {matelab_python_sdk-0.2.0a1 → matelab_python_sdk-0.2.0a3}/src/matelab/_generated/models.py +1 -1
- {matelab_python_sdk-0.2.0a1 → matelab_python_sdk-0.2.0a3}/src/matelab/_transport.py +7 -4
- {matelab_python_sdk-0.2.0a1 → matelab_python_sdk-0.2.0a3}/src/matelab/domains/records.py +9 -10
- {matelab_python_sdk-0.2.0a1 → matelab_python_sdk-0.2.0a3}/tests/test_async_attachment_upload.py +1 -18
- {matelab_python_sdk-0.2.0a1 → matelab_python_sdk-0.2.0a3}/tests/test_client.py +4 -1
- {matelab_python_sdk-0.2.0a1 → matelab_python_sdk-0.2.0a3}/tests/test_generation.py +5 -12
- {matelab_python_sdk-0.2.0a1 → matelab_python_sdk-0.2.0a3}/tests/test_operation_coverage.py +0 -2
- {matelab_python_sdk-0.2.0a1 → matelab_python_sdk-0.2.0a3}/tests/test_record_lifecycle.py +0 -14
- {matelab_python_sdk-0.2.0a1 → matelab_python_sdk-0.2.0a3}/tests/test_record_mutations.py +90 -0
- {matelab_python_sdk-0.2.0a1 → matelab_python_sdk-0.2.0a3}/tests/test_release.py +34 -42
- {matelab_python_sdk-0.2.0a1 → matelab_python_sdk-0.2.0a3}/tests/test_streaming.py +93 -8
- {matelab_python_sdk-0.2.0a1 → matelab_python_sdk-0.2.0a3}/tests/test_uploads_and_literature.py +45 -0
- matelab_python_sdk-0.2.0a3/uv.lock +940 -0
- matelab_python_sdk-0.2.0a1/.env.example +0 -14
- matelab_python_sdk-0.2.0a1/PKG-INFO +0 -993
- matelab_python_sdk-0.2.0a1/docs/agent-workflows.md +0 -79
- matelab_python_sdk-0.2.0a1/uv.lock +0 -921
- {matelab_python_sdk-0.2.0a1 → matelab_python_sdk-0.2.0a3}/.gitignore +0 -0
- {matelab_python_sdk-0.2.0a1 → matelab_python_sdk-0.2.0a3}/CONTEXT.md +0 -0
- {matelab_python_sdk-0.2.0a1 → matelab_python_sdk-0.2.0a3}/LICENSE +0 -0
- {matelab_python_sdk-0.2.0a1 → matelab_python_sdk-0.2.0a3}/NOTICE +0 -0
- {matelab_python_sdk-0.2.0a1 → matelab_python_sdk-0.2.0a3}/contracts/matelab-integration-v1.lock.json +0 -0
- {matelab_python_sdk-0.2.0a1 → matelab_python_sdk-0.2.0a3}/contracts/matelab-integration-v1.openapi.yaml +0 -0
- {matelab_python_sdk-0.2.0a1 → matelab_python_sdk-0.2.0a3}/docs/operation-coverage.yaml +0 -0
- {matelab_python_sdk-0.2.0a1 → matelab_python_sdk-0.2.0a3}/scripts/check_installed_package.py +0 -0
- {matelab_python_sdk-0.2.0a1 → matelab_python_sdk-0.2.0a3}/scripts/generate_models.py +0 -0
- {matelab_python_sdk-0.2.0a1 → matelab_python_sdk-0.2.0a3}/src/matelab/__init__.py +0 -0
- {matelab_python_sdk-0.2.0a1 → matelab_python_sdk-0.2.0a3}/src/matelab/_generated/__init__.py +0 -0
- {matelab_python_sdk-0.2.0a1 → matelab_python_sdk-0.2.0a3}/src/matelab/_hashes.py +0 -0
- {matelab_python_sdk-0.2.0a1 → matelab_python_sdk-0.2.0a3}/src/matelab/_wire_validation.py +0 -0
- {matelab_python_sdk-0.2.0a1 → matelab_python_sdk-0.2.0a3}/src/matelab/client.py +0 -0
- {matelab_python_sdk-0.2.0a1 → matelab_python_sdk-0.2.0a3}/src/matelab/domains/__init__.py +0 -0
- {matelab_python_sdk-0.2.0a1 → matelab_python_sdk-0.2.0a3}/src/matelab/domains/_attachment_content.py +0 -0
- {matelab_python_sdk-0.2.0a1 → matelab_python_sdk-0.2.0a3}/src/matelab/domains/cloud_drive.py +0 -0
- {matelab_python_sdk-0.2.0a1 → matelab_python_sdk-0.2.0a3}/src/matelab/domains/groups.py +0 -0
- {matelab_python_sdk-0.2.0a1 → matelab_python_sdk-0.2.0a3}/src/matelab/domains/literature.py +0 -0
- {matelab_python_sdk-0.2.0a1 → matelab_python_sdk-0.2.0a3}/src/matelab/domains/notebooks.py +0 -0
- {matelab_python_sdk-0.2.0a1 → matelab_python_sdk-0.2.0a3}/src/matelab/domains/templates.py +0 -0
- {matelab_python_sdk-0.2.0a1 → matelab_python_sdk-0.2.0a3}/src/matelab/domains/uploads.py +0 -0
- {matelab_python_sdk-0.2.0a1 → matelab_python_sdk-0.2.0a3}/src/matelab/domains/users.py +0 -0
- {matelab_python_sdk-0.2.0a1 → matelab_python_sdk-0.2.0a3}/src/matelab/errors.py +0 -0
- {matelab_python_sdk-0.2.0a1 → matelab_python_sdk-0.2.0a3}/src/matelab/py.typed +0 -0
- {matelab_python_sdk-0.2.0a1 → matelab_python_sdk-0.2.0a3}/src/matelab/session.py +0 -0
- {matelab_python_sdk-0.2.0a1 → matelab_python_sdk-0.2.0a3}/src/matelab/streaming.py +0 -0
- {matelab_python_sdk-0.2.0a1 → matelab_python_sdk-0.2.0a3}/tests/conftest.py +0 -0
- {matelab_python_sdk-0.2.0a1 → matelab_python_sdk-0.2.0a3}/tests/provider/test_provider_smoke.py +0 -0
- {matelab_python_sdk-0.2.0a1 → matelab_python_sdk-0.2.0a3}/tests/test_cloud_drive.py +0 -0
- {matelab_python_sdk-0.2.0a1 → matelab_python_sdk-0.2.0a3}/tests/test_comments_and_downloads.py +0 -0
- {matelab_python_sdk-0.2.0a1 → matelab_python_sdk-0.2.0a3}/tests/test_consumer_smoke.py +0 -0
- {matelab_python_sdk-0.2.0a1 → matelab_python_sdk-0.2.0a3}/tests/test_errors.py +0 -0
- {matelab_python_sdk-0.2.0a1 → matelab_python_sdk-0.2.0a3}/tests/test_extended_records.py +0 -0
- {matelab_python_sdk-0.2.0a1 → matelab_python_sdk-0.2.0a3}/tests/test_groups.py +0 -0
- {matelab_python_sdk-0.2.0a1 → matelab_python_sdk-0.2.0a3}/tests/test_notebook_lifecycle.py +0 -0
- {matelab_python_sdk-0.2.0a1 → matelab_python_sdk-0.2.0a3}/tests/test_notebooks.py +0 -0
- {matelab_python_sdk-0.2.0a1 → matelab_python_sdk-0.2.0a3}/tests/test_records.py +0 -0
- {matelab_python_sdk-0.2.0a1 → matelab_python_sdk-0.2.0a3}/tests/test_template_lifecycle.py +0 -0
- {matelab_python_sdk-0.2.0a1 → matelab_python_sdk-0.2.0a3}/tests/test_templates.py +0 -0
- {matelab_python_sdk-0.2.0a1 → matelab_python_sdk-0.2.0a3}/tests/test_transport.py +0 -0
- {matelab_python_sdk-0.2.0a1 → matelab_python_sdk-0.2.0a3}/tests/test_users.py +0 -0
- {matelab_python_sdk-0.2.0a1 → matelab_python_sdk-0.2.0a3}/tests/typing/consumer.py +0 -0
- {matelab_python_sdk-0.2.0a1 → matelab_python_sdk-0.2.0a3}/tests/typing/pyproject.toml +0 -0
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# SDK Provider smoke targets the confirmed isolated test service.
|
|
2
|
+
# Configure this repository's .env.test independently of matelab-spec.
|
|
3
|
+
# Authentication and external-token refresh persist Provider token state.
|
|
4
|
+
# Keep these names aligned with matelab-spec Provider Verification.
|
|
5
|
+
MATELAB_PROVIDER_BASE_URL=
|
|
6
|
+
MATELAB_PROVIDER_USERNAME=
|
|
7
|
+
MATELAB_PROVIDER_PASSWORD=
|
|
@@ -84,7 +84,7 @@ jobs:
|
|
|
84
84
|
uv pip install \
|
|
85
85
|
--python "${RUNNER_TEMP}/matelab-consumer/bin/python" \
|
|
86
86
|
dist/release/*.whl \
|
|
87
|
-
"basedpyright
|
|
87
|
+
"basedpyright<2,>=1.40.1" \
|
|
88
88
|
"nodejs-wheel-binaries==22.20.0"
|
|
89
89
|
"${RUNNER_TEMP}/matelab-consumer/bin/python" scripts/check_installed_package.py
|
|
90
90
|
"${RUNNER_TEMP}/matelab-consumer/bin/basedpyright" \
|
|
@@ -116,4 +116,4 @@ jobs:
|
|
|
116
116
|
name: python-package-distributions
|
|
117
117
|
path: dist
|
|
118
118
|
- name: Publish distributions
|
|
119
|
-
uses: pypa/gh-action-pypi-publish@
|
|
119
|
+
uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
|
|
@@ -5,11 +5,12 @@
|
|
|
5
5
|
## 维护顺序
|
|
6
6
|
|
|
7
7
|
1. **定界**:先读 `git status` 和适用权威;完成标准是任务范围、用户现有改动和所需外部权限均已识别。
|
|
8
|
-
2.
|
|
9
|
-
request、response、security、examples、成功码及 PVD/PCG
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
4.
|
|
8
|
+
2. **查证**:涉及 SDK 行为或 Contract 时,沿 public intent → wire operation → Provider evidence 核对受影响
|
|
9
|
+
operation 的 request、response、security、examples、成功码及 PVD/PCG,明确报告 Contract 缺口。文档、排版和
|
|
10
|
+
工具配置修改只核对相关权威。
|
|
11
|
+
3. **收敛**:只修改任务必需的层,检查 public、adapter、generated、tests、coverage 和 docs 中受影响的内容。
|
|
12
|
+
4. **验证**:按变更类型选择 [delivery gate](docs/agent-workflows.md#delivery-gate),先运行相关检查;代码变更先跑
|
|
13
|
+
聚焦测试,再跑完整门槛。完成标准是必需命令全部退出为零,skip 和 deselected 分别说明。
|
|
13
14
|
5. **交付**:复核最终 diff、状态和验证证据;完成标准是结果、限制和未执行项都可由用户独立核对。
|
|
14
15
|
|
|
15
16
|
## 分支指针
|
|
@@ -20,11 +21,13 @@
|
|
|
20
21
|
[`docs/agent-architecture.md#transport-session-and-errors`](docs/agent-architecture.md#transport-session-and-errors)。
|
|
21
22
|
- **Contract adoption**:同步 spec tag、snapshot、lock 或生成模型时执行
|
|
22
23
|
[`docs/agent-workflows.md#contract-adoption`](docs/agent-workflows.md#contract-adoption)。
|
|
23
|
-
- **
|
|
24
|
+
- **Configuration and dependencies**:修改 `pyproject.toml`、依赖范围、lock 或对应 CI 配置时读
|
|
25
|
+
[`docs/agent-workflows.md#configuration-and-dependencies`](docs/agent-workflows.md#configuration-and-dependencies)。
|
|
26
|
+
- **Delivery gate**:交付前按变更类型执行
|
|
24
27
|
[`docs/agent-workflows.md#delivery-gate`](docs/agent-workflows.md#delivery-gate)。
|
|
25
28
|
- **Provider validation**:任何实时 Provider smoke、读取或 mutation 前执行
|
|
26
29
|
[`docs/agent-workflows.md#provider-validation`](docs/agent-workflows.md#provider-validation)。
|
|
27
|
-
- **Release
|
|
30
|
+
- **Release**:准备版本或发布制品时执行;普通 commit/push 只应用其中的授权与 diff 审查要求
|
|
28
31
|
[`docs/agent-workflows.md#release`](docs/agent-workflows.md#release)。
|
|
29
32
|
|
|
30
33
|
## 权威
|
|
@@ -48,5 +51,7 @@
|
|
|
48
51
|
仓库只作只读证据来源。
|
|
49
52
|
- `/home/tnzhu/projects/matelab-spec` 定义 Wire Contract、兼容策略及 Provider 问题;`matelab-server` 是 Provider
|
|
50
53
|
实现,`matelab-mcp` 只提供 Consumer evidence,均不能反向定义 SDK interface。
|
|
51
|
-
-
|
|
52
|
-
|
|
54
|
+
- SDK 与 spec 可以使用同一个 Docker 测试服务,各自维护环境配置和测试职责;修改 SDK 不授权修改邻接仓库或
|
|
55
|
+
运行 spec 测试。
|
|
56
|
+
- commit、push、tag、publish、生产访问和真实 Provider mutation 需要用户对目标及副作用的明确授权。沿用会话中
|
|
57
|
+
已有的有效授权;只在目标、副作用超出授权或必要信息缺失时再询问。
|
|
@@ -3,6 +3,29 @@
|
|
|
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.2.0a3 - 2026-10-02
|
|
7
|
+
|
|
8
|
+
- Updates the pinned PyPI publishing action to v1.14.2, whose Twine 7 supports the Core Metadata 2.5 emitted by
|
|
9
|
+
Hatchling 1.32.4. Publishes the changes recorded under `0.2.0a2`; that candidate passed SDK and artifact checks but
|
|
10
|
+
was not uploaded to PyPI because the previous publisher rejected its metadata version.
|
|
11
|
+
|
|
12
|
+
## 0.2.0a2 - 2026-10-02
|
|
13
|
+
|
|
14
|
+
- Raises runtime and build dependency lower bounds to validated stable releases and uses compatible
|
|
15
|
+
development tool ranges, retaining `nodejs-wheel-binaries==22.20.0` as the sole system-specific exact pin. Refreshes
|
|
16
|
+
the lock against PyPI and aligns the installed-wheel CI checker with the declared Basedpyright range.
|
|
17
|
+
- Release artifact auditing compares dependency metadata with `pyproject.toml`, and release tests verify version
|
|
18
|
+
constraints and CI consistency without freezing dependency update values.
|
|
19
|
+
- Sets the distribution author to CMPDC, Institute of Physics, Chinese Academy of Sciences.
|
|
20
|
+
- Documents that SDK smoke can use the same isolated test service as spec while each repository keeps its own test
|
|
21
|
+
configuration; limits the SDK environment template to its smoke connection settings. Clarifies maintenance
|
|
22
|
+
validation by change type, keeps the default gate offline, and records the project and Basedpyright field order.
|
|
23
|
+
- Fixes rich-text and comment attachment markers whose names share a prefix, preserving exact attachment bindings
|
|
24
|
+
regardless of mapping order.
|
|
25
|
+
- Keeps download rejection errors within the public SDK error boundary when response cleanup fails.
|
|
26
|
+
- Consolidates duplicate attachment tests and removes documentation, formatting, and current operation-count
|
|
27
|
+
assertions. Strengthens SDK stream lifecycle and upload metadata validation coverage with controlled offline fixtures.
|
|
28
|
+
|
|
6
29
|
## 0.2.0a1 - 2026-10-02
|
|
7
30
|
|
|
8
31
|
- Establishes the `0.x` versioning rule: incompatible public changes advance the SDK minor version; alpha/beta/RC
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: matelab-python-sdk
|
|
3
|
+
Version: 0.2.0a3
|
|
4
|
+
Summary: Reusable async Python client for the Matelab Integration Contract
|
|
5
|
+
Author: CMPDC, Institute of Physics, Chinese Academy of Sciences
|
|
6
|
+
License-Expression: Apache-2.0
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
License-File: NOTICE
|
|
9
|
+
Keywords: asyncio,electronic-lab-notebook,eln,matelab,sdk
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Framework :: AsyncIO
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
19
|
+
Classifier: Topic :: Scientific/Engineering
|
|
20
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
21
|
+
Classifier: Typing :: Typed
|
|
22
|
+
Requires-Python: >=3.11
|
|
23
|
+
Requires-Dist: httpx2<3,>=2.13.1
|
|
24
|
+
Requires-Dist: jsonschema<5,>=4.26.0
|
|
25
|
+
Requires-Dist: pydantic<3,>=2.13.5
|
|
26
|
+
Requires-Dist: typing-extensions<5,>=4.16.0
|
|
@@ -7,7 +7,7 @@ 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.2.
|
|
10
|
+
The current alpha is `0.2.0a3`. `[project].version` in `pyproject.toml` is the sole SDK version source;
|
|
11
11
|
`uv.lock` only mirrors that source.
|
|
12
12
|
|
|
13
13
|
The SDK is pinned to the immutable `matelab-spec v2026.10.1` Contract Release. The sole pin,
|
|
@@ -38,7 +38,7 @@ To test the same artifact a downstream Consumer will install, build and install
|
|
|
38
38
|
|
|
39
39
|
```bash
|
|
40
40
|
uv build --no-build-isolation --out-dir dist/release
|
|
41
|
-
python -m pip install dist/release/matelab_python_sdk-0.2.
|
|
41
|
+
python -m pip install dist/release/matelab_python_sdk-0.2.0a3-py3-none-any.whl
|
|
42
42
|
```
|
|
43
43
|
|
|
44
44
|
Do not infer Provider compatibility from the SDK version alone. A release is also bound to the Contract
|
|
@@ -57,7 +57,7 @@ forwarding is outside its verified callable scope (PVD-049).
|
|
|
57
57
|
## Versioning
|
|
58
58
|
|
|
59
59
|
SDK versions express compatibility of the public Python interface and behavior. Python prerelease spelling follows
|
|
60
|
-
PEP 440: `0.2.
|
|
60
|
+
PEP 440: `0.2.0a3` is the third alpha candidate for `0.2.0`. During `0.x` development, incompatible changes advance
|
|
61
61
|
the minor version. From `1.0.0`, compatible fixes advance patch, compatible additions advance minor, and incompatible
|
|
62
62
|
changes advance major. Successive candidates for the same target version use `aN`, `bN`, or `rcN`.
|
|
63
63
|
|
|
@@ -872,16 +872,20 @@ aliases, and unrelated fields do not enter exceptions.
|
|
|
872
872
|
## Development
|
|
873
873
|
|
|
874
874
|
```bash
|
|
875
|
-
uv sync
|
|
876
|
-
uv run python scripts/generate_models.py
|
|
875
|
+
uv sync --frozen
|
|
877
876
|
uv run python scripts/generate_models.py --check
|
|
878
877
|
uv run ruff check .
|
|
879
878
|
uv run ruff format --check .
|
|
880
879
|
uv run basedpyright
|
|
881
|
-
uv run pytest
|
|
882
|
-
uv build
|
|
880
|
+
uv run pytest -m 'not provider'
|
|
881
|
+
uv build --no-build-isolation
|
|
883
882
|
```
|
|
884
883
|
|
|
884
|
+
This gate checks the SDK with offline fixtures and excludes the real Provider smoke. Run focused tests first when
|
|
885
|
+
changing code; documentation and configuration-only edits use the relevant checks described in
|
|
886
|
+
[the maintenance workflows](docs/agent-workflows.md#delivery-gate). Contract or generator changes regenerate the
|
|
887
|
+
private models with `uv run python scripts/generate_models.py` before running the gate.
|
|
888
|
+
|
|
885
889
|
The generator first verifies the contract lock, OpenAPI release metadata, and snapshot digest. It then
|
|
886
890
|
creates a temporary OpenAPI 3.1 generation projection, resolves references without network access, and
|
|
887
891
|
generates private component, operation-response, and parameter models. Generator emission uses its built-in formatter;
|
|
@@ -890,23 +894,19 @@ projection flattens pure
|
|
|
890
894
|
object inheritance and preserves constraints the model generator cannot express as self-contained JSON Schema
|
|
891
895
|
2020-12 metadata; the checked-in release snapshot remains unchanged. `--check` performs the same validation and
|
|
892
896
|
deterministic generation without writing the checked-in models. `WireModel` applies that metadata with the standard
|
|
893
|
-
`jsonschema` Draft 2020-12 validator; the SDK does not maintain a second hand-written schema interpreter.
|
|
894
|
-
|
|
895
|
-
`
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
`
|
|
899
|
-
against the complete test suite on the supported Python boundary versions rather than inferred from
|
|
900
|
-
`uv.lock`. The exact toolchain remains locked for development and release builds. Basedpyright and its
|
|
901
|
-
Node wheel retain the compatible exact pair `basedpyright==1.39.9` and
|
|
902
|
-
`nodejs-wheel-binaries==22.20.0`.
|
|
897
|
+
`jsonschema` Draft 2020-12 validator; the SDK does not maintain a second hand-written schema interpreter.
|
|
898
|
+
|
|
899
|
+
`pyproject.toml` declares runtime, build, and development compatibility ranges; `uv.lock` fixes the exact environment
|
|
900
|
+
for development and release builds. Runtime and build lower bounds are verified against the complete test suite on
|
|
901
|
+
the supported Python boundary versions. The sole exact development requirement is `nodejs-wheel-binaries`, retained
|
|
902
|
+
for system compatibility. The `0.x` generator and Ruff ranges allow patch updates within their current minor versions.
|
|
903
903
|
|
|
904
904
|
## Opt-in Provider consumer smoke
|
|
905
905
|
|
|
906
906
|
`tests/provider/test_provider_smoke.py` exercises the consumer flow through only the public SDK interface. Its base
|
|
907
907
|
scenario covers credential authentication, persistence of the complete returned Session, restoration through a new
|
|
908
|
-
`AsyncMatelab` instance, refresh with identity preservation, and notebook discovery.
|
|
909
|
-
|
|
908
|
+
`AsyncMatelab` instance, refresh with identity preservation, and notebook discovery. The development gate excludes it;
|
|
909
|
+
a direct pytest run skips it if any of its three connection variables are missing.
|
|
910
910
|
|
|
911
911
|
Raw Provider conformance remains the responsibility of `matelab-spec`, which sends direct HTTP requests and validates
|
|
912
912
|
the unmodified responses. The SDK does not repeat its route-by-route, cross-account, sharing, or attachment-isolation
|
|
@@ -918,31 +918,27 @@ Provider smoke is restricted to the confirmed isolated test service. Authenticat
|
|
|
918
918
|
token state. This side effect is inherent to the tested Provider operations; it cannot be disabled by a test setting.
|
|
919
919
|
Explicitly loading `.env.test` and selecting the `provider` marker is the opt-in for this flow.
|
|
920
920
|
|
|
921
|
-
|
|
921
|
+
SDK smoke can use the same isolated `matelab-test` service as `matelab-spec`. Each repository keeps its own test
|
|
922
|
+
configuration: the SDK uses `.env.test`, and spec keeps its existing `.env`.
|
|
923
|
+
|
|
924
|
+
Copy this repository's `.env.example` to the git-ignored local `.env.test`, then fill in the SDK connection settings:
|
|
922
925
|
|
|
923
926
|
- `MATELAB_PROVIDER_BASE_URL`
|
|
924
927
|
- `MATELAB_PROVIDER_USERNAME`
|
|
925
928
|
- `MATELAB_PROVIDER_PASSWORD`
|
|
926
929
|
|
|
927
|
-
These names intentionally match `matelab-spec` Provider Verification.
|
|
928
|
-
|
|
929
|
-
With the environment prepared:
|
|
930
|
+
These names intentionally match `matelab-spec` Provider Verification. Refreshing the restored Session must preserve
|
|
931
|
+
its authenticated identity. With the SDK environment prepared, run the public-interface smoke:
|
|
930
932
|
|
|
931
933
|
```bash
|
|
932
934
|
uv run --env-file .env.test pytest -m provider tests/provider/test_provider_smoke.py
|
|
933
935
|
```
|
|
934
936
|
|
|
935
|
-
This command runs only the SDK public-interface smoke;
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
cd ../matelab-spec
|
|
941
|
-
uv run --env-file ../matelab-python-sdk/.env.test pytest
|
|
942
|
-
```
|
|
943
|
-
|
|
944
|
-
The files are never loaded implicitly, so normal test runs remain safely skipped. Do not use either flow against
|
|
945
|
-
production, and never commit Provider credentials.
|
|
937
|
+
This command runs only the SDK public-interface smoke; Provider Verification is maintained in
|
|
938
|
+
`matelab-spec` with its own configuration and run instructions.
|
|
939
|
+
SDK tests do not load `.env.test` implicitly; without Provider environment variables, the real smoke remains skipped.
|
|
940
|
+
Offline SDK tests continue to use `MockTransport`. Do not use the smoke flow against production, and never commit
|
|
941
|
+
Provider credentials.
|
|
946
942
|
|
|
947
943
|
## Reproducible release build
|
|
948
944
|
|
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
identity、affected rows、排序、分页、原子性、CAS、幂等性和持久化结果。
|
|
13
13
|
- MCP typed refs、capabilities、fingerprints、FastMCP/Redis context 与 Agent 展示模型留在 Consumer。只有真实
|
|
14
14
|
第二种实现才能引入 seam。
|
|
15
|
-
-
|
|
15
|
+
- 源码语法遵循 `pyproject.toml` 中 `requires-python` 的最低版本;不使用 `from __future__ import annotations`,
|
|
16
|
+
前向引用优先靠定义顺序解决。
|
|
16
17
|
|
|
17
18
|
## Transport, Session, and errors
|
|
18
19
|
|
|
@@ -0,0 +1,129 @@
|
|
|
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
|
+
## Configuration and dependencies
|
|
25
|
+
|
|
26
|
+
配置值以 `pyproject.toml` 为准,`uv.lock` 固定本仓库的解析环境,不反向定义发布依赖范围。
|
|
27
|
+
|
|
28
|
+
- `[project]` 字段依次为 `name`、`version`、`description`、`requires-python`、`keywords`、`authors`、
|
|
29
|
+
`dependencies`,其他字段放在其后。
|
|
30
|
+
- `[tool.basedpyright]` 字段依次为 `pythonVersion`、`typeCheckingMode`、`include`、`exclude`、
|
|
31
|
+
`reportUnannotatedClassAttribute`、`reportIncompatibleUnannotatedOverride`,其他设置放在其后。沿用 SDK 的目标
|
|
32
|
+
Python 版本与检查路径,不照搬其他项目的值。
|
|
33
|
+
- runtime、build 和 dev 依赖声明兼容范围;`nodejs-wheel-binaries` 保留系统兼容性所需的精确固定。更新范围时
|
|
34
|
+
同步 lock 和安装后消费检查使用的 CI requirement。下限须有实际验证;上限排除未经验证的下一 major,
|
|
35
|
+
generator 和 Ruff 的 `0.x` 范围排除下一 minor。
|
|
36
|
+
- 构建后端范围和 wheel package 显式配置;metadata 与制品检查从这些声明推导预期值。
|
|
37
|
+
|
|
38
|
+
完成标准:配置可解析、范围与 lock 一致、受影响 CI 使用同一约束;字段重排不改变配置含义。
|
|
39
|
+
|
|
40
|
+
## Delivery gate
|
|
41
|
+
|
|
42
|
+
按本次变更选择验证范围:
|
|
43
|
+
|
|
44
|
+
- 纯文档修改检查相关事实、本地路径和 Markdown 引用;示例或接口说明变化要对照当前 public interface。
|
|
45
|
+
- 字段重排或显式写出既有默认值,检查语法与有效配置一致性。工具配置的有效设置变化运行对应工具。
|
|
46
|
+
- SDK 代码、测试行为、Contract、依赖范围或影响制品的构建元数据变化,先跑聚焦测试,再完成下列完整门槛。
|
|
47
|
+
- 发布准备在完整门槛之后继续执行 [release](#release) 的制品检查。
|
|
48
|
+
|
|
49
|
+
已有验证仍对应当前变更且无新失败或疑点时可以复用。文档整理不触发重复的完整测试、构建或实时 Provider 调用。
|
|
50
|
+
|
|
51
|
+
### SDK test scope
|
|
52
|
+
|
|
53
|
+
SDK 测试覆盖 public interface 到 wire adapter 的映射、认证与 Session、错误与重试、stream/上传生命周期及
|
|
54
|
+
分发制品。Provider 的逐路由原始 HTTP conformance 由 `matelab-spec` 负责,不在 SDK 重复实现或附带重跑。
|
|
55
|
+
|
|
56
|
+
新增或修改 operation 覆盖 method/path/encoding、认证、成功映射及适用的空结果、
|
|
57
|
+
required nullable 和 additive response fields;operation coverage 与 pinned OpenAPI exact match,implemented
|
|
58
|
+
operation 同时具备 public interface、wire adapter 和测试。删除或收紧 public interface 时同步移除过时测试,
|
|
59
|
+
不保留无承诺的墓碑 alias。生成测试覆盖 metadata/checksum、schema、required/nullable、extra policy、确定性和
|
|
60
|
+
stale detection。
|
|
61
|
+
|
|
62
|
+
优先使用 pinned sanitized examples 或小型受控 fixture 验证稳定行为。operation 数量从 pinned Contract 推导,
|
|
63
|
+
不固定当前数量;当前服务地址和依赖版本值不作为回归断言,除非它们本身就是明确的稳定契约。
|
|
64
|
+
配置值变化只验证语法、约束及相关行为,不另加冻结新值或重复实现逻辑的测试。
|
|
65
|
+
|
|
66
|
+
Ruff/Basedpyright 诊断通过修复代码或精确 ignore 解决,不缩小检查范围;Basedpyright 目标为 0 errors / 0 warnings。
|
|
67
|
+
|
|
68
|
+
### Full gate
|
|
69
|
+
|
|
70
|
+
在上面的完整门槛条件成立时运行:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
uv sync --frozen
|
|
74
|
+
uv run python scripts/generate_models.py --check
|
|
75
|
+
uv run ruff check .
|
|
76
|
+
uv run ruff format --check .
|
|
77
|
+
uv run basedpyright
|
|
78
|
+
uv run pytest -m 'not provider'
|
|
79
|
+
uv build --no-build-isolation
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
`not provider` 显式排除实时 smoke,但保留离线 smoke 配置测试。确需实时验证时单独执行
|
|
83
|
+
[Provider validation](#provider-validation),不通过加载凭证扩展完整门槛的副作用。
|
|
84
|
+
|
|
85
|
+
检查因权限失败或无进展时,先查看输出和自己启动的进程,区分普通失败与文件、socket、网络或子进程限制。
|
|
86
|
+
有沙箱阻塞证据后停止自己启动的停滞测试,通过工具审批机制申请最小范围重试;保留配置,不改权限、凭证或测试
|
|
87
|
+
行为来绕过限制。超时本身不是沙箱拒绝的证据;无法升级或被拒绝时报告阻塞。
|
|
88
|
+
|
|
89
|
+
完成标准:必需检查退出为零;skip 按测试名称和原因报告,不能计为通过;deselected 说明筛选范围;最终
|
|
90
|
+
`git diff --check` 无错误。本次新增修改限于任务范围,原有工作区改动按归属保留。
|
|
91
|
+
|
|
92
|
+
## Provider validation
|
|
93
|
+
|
|
94
|
+
1. 默认使用 pinned examples、`MockTransport` 和 SDK adapter tests。SDK 不自动读取 `.env` 或 `.env.test`;
|
|
95
|
+
smoke 从进程环境读取三个连接变量,缺少任一变量就 skip。默认门槛显式排除 `provider`;只有任务确需实时
|
|
96
|
+
验证且会话授权覆盖目标及副作用时才启用。
|
|
97
|
+
2. 执行前确认目标是隔离服务、`.env.test` 处于 Git ignore、命令显式加载该文件,并列出 authentication、refresh、
|
|
98
|
+
mutation 或带写痕迹读取的副作用。生产目标或 mutation 必须有对应的明确授权;已有授权不因多轮对话失效。
|
|
99
|
+
3. SDK public smoke 使用 README 的 `Opt-in Provider consumer smoke` 命令;route-by-route conformance 由
|
|
100
|
+
`matelab-spec` Provider Verification 负责。可以共用 Docker 中的隔离服务,但 SDK `.env.test` 与 spec 的环境
|
|
101
|
+
配置分别维护,不跨仓库传入 SDK 配置,也不因运行 SDK smoke 附带运行 spec 测试。两者的结果分开报告。
|
|
102
|
+
4. mutation 场景在写前记录 fixture,在写后回读,并尽力清理。完成标准:目标、Provider commit、命令、通过数、
|
|
103
|
+
skip/failure 和清理结果均可追溯;凭证及敏感 payload 未进入输出或提交。
|
|
104
|
+
|
|
105
|
+
## Release
|
|
106
|
+
|
|
107
|
+
版本规划和本地构建可以在请求范围内完成;commit、push、tag、publish 只执行会话已明确授权的步骤。普通
|
|
108
|
+
commit/push 审查并提交或推送已授权的 diff,不自动改版本、建 tag 或发布。发布准备完成后若缺少发布授权,
|
|
109
|
+
交付版本、diff、验证证据和制品结果,授权是执行外部动作前的最后一步。
|
|
110
|
+
|
|
111
|
+
以下步骤适用于实际发布;只准备版本或制品时止于所需步骤:
|
|
112
|
+
|
|
113
|
+
1. 读取 `[project].version`,确认待发布版本未发布、changelog 已追加且 Contract pin 完整。SDK 采用 SemVer 的
|
|
114
|
+
兼容规则,Python 版本拼写遵循 PEP 440;公开接口
|
|
115
|
+
删除、重命名、类型收紧或不兼容行为变化按 breaking change 处理。`0.x` 阶段 breaking change 递增 minor;
|
|
116
|
+
`1.0.0` 起兼容修复递增 patch、兼容新增递增 minor、breaking change 递增 major。同一候选版本的预发布使用
|
|
117
|
+
`aN`、`bN`、`rcN` 并在阶段内递增序号;改变目标版本后重新从 1 开始。spec 的年月版本与 SDK 独立,通过
|
|
118
|
+
Contract lock 关联,不按 spec 版本号机械递增 SDK。完成标准:版本、文档和 tag 名 `v{version}` 一致。
|
|
119
|
+
2. 在完整 delivery gate 通过且 commit 已授权后创建单一 release commit。完成标准:commit 仅包含已审查、
|
|
120
|
+
已授权的 diff;隔离无关用户改动,不把清理用户工作区作为发布前提。
|
|
121
|
+
3. 以 release commit 的 committer timestamp 设置 `SOURCE_DATE_EPOCH`,从锁定环境构建两次并逐字节比较 wheel
|
|
122
|
+
与 sdist;运行 `scripts/check_release.py`,再从 wheel 验证公开导入和类型消费。完成标准:制品可复现,
|
|
123
|
+
Apache-2.0 `LICENSE`/`NOTICE`、PEP 639 METADATA、`py.typed` 与公开边界审计通过,`_generated` 未公开,
|
|
124
|
+
checksum 已记录。未提交的本地构建标明对应工作区状态,不作为正式发布制品。
|
|
125
|
+
4. 创建指向 release commit 的 annotated `v{version}` tag。先推送 `origin` 的 main/tag,再推送 GitHub mirror 的
|
|
126
|
+
main/tag;只有与项目版本完全匹配的 tag 触发 Trusted Publishing workflow,第三方 Action 固定 immutable
|
|
127
|
+
commit SHA。
|
|
128
|
+
5. 跟踪 CI 的 Python 边界测试、生成检查、可复现构建、安装后消费和 PyPI publish。完成标准:两个远端 main/tag
|
|
129
|
+
都指向 release commit,workflow success,PyPI 对应版本可读取;原有无关工作区改动保留。
|
|
@@ -1,17 +1,24 @@
|
|
|
1
1
|
[build-system]
|
|
2
|
-
requires = ["hatchling>=1.
|
|
2
|
+
requires = ["hatchling>=1.32.4,<2"]
|
|
3
3
|
build-backend = "hatchling.build"
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "matelab-python-sdk"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.0a3"
|
|
8
8
|
description = "Reusable async Python client for the Matelab Integration Contract"
|
|
9
|
-
readme = "README.md"
|
|
10
9
|
requires-python = ">=3.11"
|
|
10
|
+
keywords = ["matelab", "electronic-lab-notebook", "eln", "asyncio", "sdk"]
|
|
11
|
+
authors = [
|
|
12
|
+
{ name = "CMPDC, Institute of Physics, Chinese Academy of Sciences" },
|
|
13
|
+
]
|
|
14
|
+
dependencies = [
|
|
15
|
+
"httpx2>=2.13.1,<3",
|
|
16
|
+
"jsonschema>=4.26.0,<5",
|
|
17
|
+
"pydantic>=2.13.5,<3",
|
|
18
|
+
"typing-extensions>=4.16.0,<5",
|
|
19
|
+
]
|
|
11
20
|
license = "Apache-2.0"
|
|
12
21
|
license-files = ["LICENSE", "NOTICE"]
|
|
13
|
-
authors = [{ name = "朱天念", email = "zhutiannian@gmail.com" }]
|
|
14
|
-
keywords = ["matelab", "electronic-lab-notebook", "eln", "asyncio", "sdk"]
|
|
15
22
|
classifiers = [
|
|
16
23
|
"Development Status :: 3 - Alpha",
|
|
17
24
|
"Framework :: AsyncIO",
|
|
@@ -26,28 +33,19 @@ classifiers = [
|
|
|
26
33
|
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
27
34
|
"Typing :: Typed",
|
|
28
35
|
]
|
|
29
|
-
dependencies = [
|
|
30
|
-
"httpx2>=2.9.1,<3",
|
|
31
|
-
"jsonschema>=4.26,<5",
|
|
32
|
-
"pydantic>=2.13.4,<3",
|
|
33
|
-
"typing-extensions>=4.14.1,<5",
|
|
34
|
-
]
|
|
35
36
|
|
|
36
37
|
[dependency-groups]
|
|
37
38
|
dev = [
|
|
38
|
-
"basedpyright
|
|
39
|
+
"basedpyright>=1.40.1,<2",
|
|
39
40
|
"nodejs-wheel-binaries==22.20.0",
|
|
40
|
-
"datamodel-code-generator",
|
|
41
|
-
"hatchling>=1.
|
|
42
|
-
"pytest",
|
|
43
|
-
"pytest-asyncio",
|
|
44
|
-
"pyyaml",
|
|
45
|
-
"ruff",
|
|
41
|
+
"datamodel-code-generator>=0.83.0,<0.84",
|
|
42
|
+
"hatchling>=1.32.4,<2",
|
|
43
|
+
"pytest>=9.1.1,<10",
|
|
44
|
+
"pytest-asyncio>=1.4.0,<2",
|
|
45
|
+
"pyyaml>=6.0.3,<7",
|
|
46
|
+
"ruff>=0.16.10,<0.17",
|
|
46
47
|
]
|
|
47
48
|
|
|
48
|
-
[tool.uv]
|
|
49
|
-
index-url = "https://repo.huaweicloud.com/repository/pypi/simple"
|
|
50
|
-
|
|
51
49
|
[tool.ruff]
|
|
52
50
|
line-length = 120
|
|
53
51
|
lint.extend-select = ["I"]
|
|
@@ -55,9 +53,10 @@ lint.isort.split-on-trailing-comma = false
|
|
|
55
53
|
format.skip-magic-trailing-comma = true
|
|
56
54
|
|
|
57
55
|
[tool.basedpyright]
|
|
56
|
+
pythonVersion = "3.11"
|
|
57
|
+
typeCheckingMode = "recommended"
|
|
58
58
|
include = ["src", "tests", "scripts"]
|
|
59
59
|
exclude = ["src/matelab/_generated"]
|
|
60
|
-
pythonVersion = "3.11"
|
|
61
60
|
reportUnannotatedClassAttribute = "none"
|
|
62
61
|
reportIncompatibleUnannotatedOverride = "error"
|
|
63
62
|
|
|
@@ -11,20 +11,17 @@ from pathlib import Path, PurePosixPath
|
|
|
11
11
|
from typing import cast
|
|
12
12
|
from zipfile import ZipFile
|
|
13
13
|
|
|
14
|
+
from packaging.requirements import Requirement
|
|
15
|
+
|
|
14
16
|
_ROOT = Path(__file__).resolve().parents[1]
|
|
15
17
|
_PROJECT = cast(dict[str, object], tomllib.loads((_ROOT / "pyproject.toml").read_text())["project"])
|
|
16
18
|
_PROJECT_NAME = cast(str, _PROJECT["name"])
|
|
17
19
|
_VERSION = cast(str, _PROJECT["version"])
|
|
18
20
|
_DIST_INFO = f"{_PROJECT_NAME.replace('-', '_')}-{_VERSION}.dist-info"
|
|
19
|
-
_EXPECTED_REQUIRES_DIST = {
|
|
20
|
-
"httpx2<3,>=2.9.1",
|
|
21
|
-
"jsonschema<5,>=4.26",
|
|
22
|
-
"pydantic<3,>=2.13.4",
|
|
23
|
-
"typing-extensions<5,>=4.14.1",
|
|
24
|
-
}
|
|
21
|
+
_EXPECTED_REQUIRES_DIST = {str(Requirement(value)) for value in cast(list[str], _PROJECT["dependencies"])}
|
|
25
22
|
_EXPECTED_LICENSE_FILES = set(cast(list[str], _PROJECT["license-files"]))
|
|
26
23
|
_AUTHOR = cast(list[dict[str, str]], _PROJECT["authors"])[0]
|
|
27
|
-
|
|
24
|
+
_EXPECTED_AUTHOR = _AUTHOR["name"]
|
|
28
25
|
_ALLOWED_ENV_EXAMPLE = ".env.example"
|
|
29
26
|
_FORBIDDEN_PARTS = {
|
|
30
27
|
".env",
|
|
@@ -98,8 +95,8 @@ def _check_metadata(artifact: Path, metadata_bytes: bytes) -> None:
|
|
|
98
95
|
raise ValueError(f"{artifact.name} contains unexpected License-Expression metadata")
|
|
99
96
|
if set(metadata.get_all("License-File", [])) != _EXPECTED_LICENSE_FILES:
|
|
100
97
|
raise ValueError(f"{artifact.name} contains unexpected License-File metadata")
|
|
101
|
-
if metadata["Author-email"]
|
|
102
|
-
raise ValueError(f"{artifact.name} contains unexpected
|
|
98
|
+
if metadata["Author"] != _EXPECTED_AUTHOR or metadata["Author-email"] is not None:
|
|
99
|
+
raise ValueError(f"{artifact.name} contains unexpected author metadata")
|
|
103
100
|
|
|
104
101
|
|
|
105
102
|
def _audit_wheel(path: Path) -> None:
|
|
@@ -80,7 +80,7 @@ def _provider_error(code: int) -> MatelabAuthenticationError | MatelabProviderEr
|
|
|
80
80
|
|
|
81
81
|
|
|
82
82
|
def _header(response: Response, name: str) -> str:
|
|
83
|
-
return
|
|
83
|
+
return response.headers.get(name, "")
|
|
84
84
|
|
|
85
85
|
|
|
86
86
|
def _parse_content_length(response: Response) -> int | None:
|
|
@@ -293,7 +293,8 @@ class SessionTransport:
|
|
|
293
293
|
raise MatelabTransportError("Could not exchange data with the Matelab Provider.") from None
|
|
294
294
|
|
|
295
295
|
if response.status_code not in {200, 206}:
|
|
296
|
-
|
|
296
|
+
with suppress(RequestError):
|
|
297
|
+
await response.aclose()
|
|
297
298
|
raise MatelabTransportError(
|
|
298
299
|
f"Matelab returned HTTP {response.status_code}.", status_code=response.status_code
|
|
299
300
|
)
|
|
@@ -321,7 +322,8 @@ class SessionTransport:
|
|
|
321
322
|
continue
|
|
322
323
|
raise _provider_error(code)
|
|
323
324
|
if operation.media_types is not None and media_type not in operation.media_types:
|
|
324
|
-
|
|
325
|
+
with suppress(RequestError):
|
|
326
|
+
await response.aclose()
|
|
325
327
|
raise MatelabProtocolError("Matelab returned an unexpected download media type.")
|
|
326
328
|
|
|
327
329
|
content_length = _parse_content_length(response)
|
|
@@ -331,6 +333,7 @@ class SessionTransport:
|
|
|
331
333
|
or not _CONTENT_RANGE_PATTERN.fullmatch(_header(response, "Content-Range"))
|
|
332
334
|
or not _header(response, "Content-Disposition")
|
|
333
335
|
):
|
|
334
|
-
|
|
336
|
+
with suppress(RequestError):
|
|
337
|
+
await response.aclose()
|
|
335
338
|
raise MatelabProtocolError("Matelab returned invalid partial-download metadata.")
|
|
336
339
|
return DownloadStream(response, content_length=content_length)
|
|
@@ -976,10 +976,9 @@ def _encode_record_rich_text(update: RecordRichTextUpdate) -> str:
|
|
|
976
976
|
markers = set(_ATTACHMENT_MARKER_PATTERN.findall(update.html))
|
|
977
977
|
if markers != set(update.attachments):
|
|
978
978
|
raise MatelabUsageError("Rich-text attachment names must exactly match matelab-attachment:<name> markers.")
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
return encoded
|
|
979
|
+
return _ATTACHMENT_MARKER_PATTERN.sub(
|
|
980
|
+
lambda match: _encode_record_attachment_reference(update.attachments[match.group(1)]), update.html
|
|
981
|
+
)
|
|
983
982
|
|
|
984
983
|
|
|
985
984
|
def _validate_upload_filename(filename: str) -> None:
|
|
@@ -1001,15 +1000,15 @@ def _bind_comment_attachments(
|
|
|
1001
1000
|
|
|
1002
1001
|
first = next(iter(selected_attachments.values()))
|
|
1003
1002
|
binding_uid = first._binding_uid # pyright: ignore[reportPrivateUsage]
|
|
1004
|
-
for
|
|
1003
|
+
for attachment in selected_attachments.values():
|
|
1005
1004
|
attachment_binding_uid = attachment._binding_uid # pyright: ignore[reportPrivateUsage]
|
|
1006
1005
|
if attachment.source != source or attachment_binding_uid != binding_uid:
|
|
1007
1006
|
raise MatelabUsageError("Comment attachments must belong to the same record and upload batch.")
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
return
|
|
1007
|
+
bound_body = _ATTACHMENT_MARKER_PATTERN.sub(
|
|
1008
|
+
lambda match: selected_attachments[match.group(1)]._temporary_url, # pyright: ignore[reportPrivateUsage]
|
|
1009
|
+
body,
|
|
1010
|
+
)
|
|
1011
|
+
return bound_body, binding_uid
|
|
1013
1012
|
|
|
1014
1013
|
|
|
1015
1014
|
def _notebook_selector(notebook: NotebookRef) -> str | dict[str, JsonValue]:
|
{matelab_python_sdk-0.2.0a1 → matelab_python_sdk-0.2.0a3}/tests/test_async_attachment_upload.py
RENAMED
|
@@ -44,24 +44,6 @@ def _upload_response(
|
|
|
44
44
|
)
|
|
45
45
|
|
|
46
46
|
|
|
47
|
-
@pytest.mark.asyncio
|
|
48
|
-
async def test_upload_attachment_computes_missing_async_checksum(
|
|
49
|
-
provider_session: Session, provider_envelope: Callable[..., dict[str, object]]
|
|
50
|
-
) -> None:
|
|
51
|
-
async def content():
|
|
52
|
-
yield _CONTENT
|
|
53
|
-
|
|
54
|
-
async def handler(request: Request) -> Response:
|
|
55
|
-
assert _SHA256.encode() in request.content
|
|
56
|
-
return _upload_response(provider_envelope, request)
|
|
57
|
-
|
|
58
|
-
async with AsyncClient(base_url="https://matelab.example/api", transport=MockTransport(handler)) as http_client:
|
|
59
|
-
client = AsyncMatelab("https://ignored.example", session=provider_session, http_client=http_client)
|
|
60
|
-
staged = await client.records.upload_attachment(_NOTEBOOK, filename="alpha.txt", content=content())
|
|
61
|
-
|
|
62
|
-
assert staged.binding.uid.startswith("upl-")
|
|
63
|
-
|
|
64
|
-
|
|
65
47
|
@pytest.mark.asyncio
|
|
66
48
|
async def test_record_attachment_upload_uses_shared_async_content_handling(
|
|
67
49
|
provider_session: Session, provider_envelope: Callable[..., dict[str, object]]
|
|
@@ -80,6 +62,7 @@ async def test_record_attachment_upload_uses_shared_async_content_handling(
|
|
|
80
62
|
|
|
81
63
|
assert staged.filename == "alpha.txt"
|
|
82
64
|
assert staged.sha256 == _SHA256
|
|
65
|
+
assert staged.binding.uid.startswith("upl-")
|
|
83
66
|
|
|
84
67
|
|
|
85
68
|
@pytest.mark.asyncio
|