matelab-python-sdk 0.2.0a9__tar.gz → 0.3.0a1__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.
Files changed (73) hide show
  1. matelab_python_sdk-0.3.0a1/.agents/skills/matelab-sdk-release/SKILL.md +89 -0
  2. matelab_python_sdk-0.3.0a1/.agents/skills/matelab-sdk-release/agents/openai.yaml +4 -0
  3. matelab_python_sdk-0.3.0a1/.agents/skills/matelab-sdk-release/scripts/verify_pypi.py +165 -0
  4. matelab_python_sdk-0.3.0a1/.python-version +1 -0
  5. matelab_python_sdk-0.3.0a1/.workflow/release.yml +89 -0
  6. {matelab_python_sdk-0.2.0a9 → matelab_python_sdk-0.3.0a1}/PKG-INFO +8 -8
  7. {matelab_python_sdk-0.2.0a9 → matelab_python_sdk-0.3.0a1}/README.md +4 -4
  8. {matelab_python_sdk-0.2.0a9 → matelab_python_sdk-0.3.0a1}/contracts/matelab-integration-v1.lock.json +3 -3
  9. {matelab_python_sdk-0.2.0a9 → matelab_python_sdk-0.3.0a1}/contracts/matelab-integration-v1.openapi.yaml +26 -26
  10. {matelab_python_sdk-0.2.0a9 → matelab_python_sdk-0.3.0a1}/docs/agent-workflows.md +9 -9
  11. {matelab_python_sdk-0.2.0a9 → matelab_python_sdk-0.3.0a1}/docs/api.md +16 -14
  12. {matelab_python_sdk-0.2.0a9 → matelab_python_sdk-0.3.0a1}/docs/development.md +9 -2
  13. {matelab_python_sdk-0.2.0a9 → matelab_python_sdk-0.3.0a1}/docs/operation-coverage.yaml +1 -1
  14. {matelab_python_sdk-0.2.0a9 → matelab_python_sdk-0.3.0a1}/pyproject.toml +8 -8
  15. {matelab_python_sdk-0.2.0a9 → matelab_python_sdk-0.3.0a1}/scripts/generate_models.py +1 -1
  16. {matelab_python_sdk-0.2.0a9 → matelab_python_sdk-0.3.0a1}/src/matelab/_generated/models.py +5 -5
  17. {matelab_python_sdk-0.2.0a9 → matelab_python_sdk-0.3.0a1}/src/matelab/_transport.py +6 -8
  18. {matelab_python_sdk-0.2.0a9 → matelab_python_sdk-0.3.0a1}/src/matelab/_wire_validation.py +4 -5
  19. {matelab_python_sdk-0.2.0a9 → matelab_python_sdk-0.3.0a1}/src/matelab/domains/records.py +6 -7
  20. {matelab_python_sdk-0.2.0a9 → matelab_python_sdk-0.3.0a1}/src/matelab/domains/uploads.py +2 -2
  21. {matelab_python_sdk-0.2.0a9 → matelab_python_sdk-0.3.0a1}/tests/test_release.py +2 -2
  22. {matelab_python_sdk-0.2.0a9 → matelab_python_sdk-0.3.0a1}/tests/test_streaming.py +1 -1
  23. {matelab_python_sdk-0.2.0a9 → matelab_python_sdk-0.3.0a1}/tests/test_transport.py +1 -2
  24. {matelab_python_sdk-0.2.0a9 → matelab_python_sdk-0.3.0a1}/tests/test_uploads_and_literature.py +1 -1
  25. {matelab_python_sdk-0.2.0a9 → matelab_python_sdk-0.3.0a1}/uv.lock +157 -243
  26. matelab_python_sdk-0.2.0a9/.github/workflows/release.yml +0 -119
  27. {matelab_python_sdk-0.2.0a9 → matelab_python_sdk-0.3.0a1}/.env.example +0 -0
  28. {matelab_python_sdk-0.2.0a9 → matelab_python_sdk-0.3.0a1}/.gitignore +0 -0
  29. {matelab_python_sdk-0.2.0a9 → matelab_python_sdk-0.3.0a1}/AGENTS.md +0 -0
  30. {matelab_python_sdk-0.2.0a9 → matelab_python_sdk-0.3.0a1}/CONTEXT.md +0 -0
  31. {matelab_python_sdk-0.2.0a9 → matelab_python_sdk-0.3.0a1}/LICENSE +0 -0
  32. {matelab_python_sdk-0.2.0a9 → matelab_python_sdk-0.3.0a1}/NOTICE +0 -0
  33. {matelab_python_sdk-0.2.0a9 → matelab_python_sdk-0.3.0a1}/docs/agent-architecture.md +0 -0
  34. {matelab_python_sdk-0.2.0a9 → matelab_python_sdk-0.3.0a1}/scripts/check_installed_package.py +0 -0
  35. {matelab_python_sdk-0.2.0a9 → matelab_python_sdk-0.3.0a1}/scripts/check_release.py +0 -0
  36. {matelab_python_sdk-0.2.0a9 → matelab_python_sdk-0.3.0a1}/src/matelab/__init__.py +0 -0
  37. {matelab_python_sdk-0.2.0a9 → matelab_python_sdk-0.3.0a1}/src/matelab/_generated/__init__.py +0 -0
  38. {matelab_python_sdk-0.2.0a9 → matelab_python_sdk-0.3.0a1}/src/matelab/_hashes.py +0 -0
  39. {matelab_python_sdk-0.2.0a9 → matelab_python_sdk-0.3.0a1}/src/matelab/client.py +0 -0
  40. {matelab_python_sdk-0.2.0a9 → matelab_python_sdk-0.3.0a1}/src/matelab/domains/__init__.py +0 -0
  41. {matelab_python_sdk-0.2.0a9 → matelab_python_sdk-0.3.0a1}/src/matelab/domains/_attachment_content.py +0 -0
  42. {matelab_python_sdk-0.2.0a9 → matelab_python_sdk-0.3.0a1}/src/matelab/domains/cloud_drive.py +0 -0
  43. {matelab_python_sdk-0.2.0a9 → matelab_python_sdk-0.3.0a1}/src/matelab/domains/groups.py +0 -0
  44. {matelab_python_sdk-0.2.0a9 → matelab_python_sdk-0.3.0a1}/src/matelab/domains/literature.py +0 -0
  45. {matelab_python_sdk-0.2.0a9 → matelab_python_sdk-0.3.0a1}/src/matelab/domains/notebooks.py +0 -0
  46. {matelab_python_sdk-0.2.0a9 → matelab_python_sdk-0.3.0a1}/src/matelab/domains/templates.py +0 -0
  47. {matelab_python_sdk-0.2.0a9 → matelab_python_sdk-0.3.0a1}/src/matelab/domains/users.py +0 -0
  48. {matelab_python_sdk-0.2.0a9 → matelab_python_sdk-0.3.0a1}/src/matelab/errors.py +0 -0
  49. {matelab_python_sdk-0.2.0a9 → matelab_python_sdk-0.3.0a1}/src/matelab/py.typed +0 -0
  50. {matelab_python_sdk-0.2.0a9 → matelab_python_sdk-0.3.0a1}/src/matelab/session.py +0 -0
  51. {matelab_python_sdk-0.2.0a9 → matelab_python_sdk-0.3.0a1}/src/matelab/streaming.py +0 -0
  52. {matelab_python_sdk-0.2.0a9 → matelab_python_sdk-0.3.0a1}/tests/conftest.py +0 -0
  53. {matelab_python_sdk-0.2.0a9 → matelab_python_sdk-0.3.0a1}/tests/provider/test_provider_smoke.py +0 -0
  54. {matelab_python_sdk-0.2.0a9 → matelab_python_sdk-0.3.0a1}/tests/test_async_attachment_upload.py +0 -0
  55. {matelab_python_sdk-0.2.0a9 → matelab_python_sdk-0.3.0a1}/tests/test_client.py +0 -0
  56. {matelab_python_sdk-0.2.0a9 → matelab_python_sdk-0.3.0a1}/tests/test_cloud_drive.py +0 -0
  57. {matelab_python_sdk-0.2.0a9 → matelab_python_sdk-0.3.0a1}/tests/test_comments_and_downloads.py +0 -0
  58. {matelab_python_sdk-0.2.0a9 → matelab_python_sdk-0.3.0a1}/tests/test_consumer_smoke.py +0 -0
  59. {matelab_python_sdk-0.2.0a9 → matelab_python_sdk-0.3.0a1}/tests/test_errors.py +0 -0
  60. {matelab_python_sdk-0.2.0a9 → matelab_python_sdk-0.3.0a1}/tests/test_extended_records.py +0 -0
  61. {matelab_python_sdk-0.2.0a9 → matelab_python_sdk-0.3.0a1}/tests/test_generation.py +0 -0
  62. {matelab_python_sdk-0.2.0a9 → matelab_python_sdk-0.3.0a1}/tests/test_groups.py +0 -0
  63. {matelab_python_sdk-0.2.0a9 → matelab_python_sdk-0.3.0a1}/tests/test_notebook_lifecycle.py +0 -0
  64. {matelab_python_sdk-0.2.0a9 → matelab_python_sdk-0.3.0a1}/tests/test_notebooks.py +0 -0
  65. {matelab_python_sdk-0.2.0a9 → matelab_python_sdk-0.3.0a1}/tests/test_operation_coverage.py +0 -0
  66. {matelab_python_sdk-0.2.0a9 → matelab_python_sdk-0.3.0a1}/tests/test_record_lifecycle.py +0 -0
  67. {matelab_python_sdk-0.2.0a9 → matelab_python_sdk-0.3.0a1}/tests/test_record_mutations.py +0 -0
  68. {matelab_python_sdk-0.2.0a9 → matelab_python_sdk-0.3.0a1}/tests/test_records.py +0 -0
  69. {matelab_python_sdk-0.2.0a9 → matelab_python_sdk-0.3.0a1}/tests/test_template_lifecycle.py +0 -0
  70. {matelab_python_sdk-0.2.0a9 → matelab_python_sdk-0.3.0a1}/tests/test_templates.py +0 -0
  71. {matelab_python_sdk-0.2.0a9 → matelab_python_sdk-0.3.0a1}/tests/test_users.py +0 -0
  72. {matelab_python_sdk-0.2.0a9 → matelab_python_sdk-0.3.0a1}/tests/typing/consumer.py +0 -0
  73. {matelab_python_sdk-0.2.0a9 → matelab_python_sdk-0.3.0a1}/tests/typing/pyproject.toml +0 -0
@@ -0,0 +1,89 @@
1
+ ---
2
+ name: matelab-sdk-release
3
+ description: 完成 matelab-python-sdk 的 spec 增量同步、必要适配、完整验证、审查和 PyPI 发布。用于该项目的完整交付;仅同步、验证或提交推送时遵守用户限定的终点。
4
+ ---
5
+
6
+ # Matelab SDK 完整交付
7
+
8
+ 在包含本 skill 的 `matelab-python-sdk` 仓库根目录执行。
9
+ 以下项目路径均相对此仓库。先读适用 `AGENTS.md`;具体规范以仓库当前文件为准。
10
+
11
+ ## 调用范围
12
+
13
+ - 用户要求执行 `$matelab-sdk-release` 且未限制阶段时,完成同步、适配、测试、审查、提交、推送、tag 和
14
+ PyPI 发布;可附目标版本。不重复索取已覆盖目标和副作用的授权。
15
+ - “只同步”“只验证”“仅提交推送”等限制优先,停在相应阶段;解释、制作或修改本技能不执行发布。
16
+ - 待发布内容包括相对上次已发布 tag 的提交及工作区改动,不能仅凭工作区干净就认定无变更。
17
+ 无待发布内容且当前版本已发布时,核对状态后结束;目标 tag 已推送时,优先继续其 CI/PyPI 核验。
18
+ - 依赖更新仅在用户要求时进行。真实业务 mutation、生产访问及新增发布目标不属于默认范围。
19
+
20
+ ## 1. 定界与 spec 增量
21
+
22
+ 先检查 status、当前版本、Contract lock、最近发布 tag 和实际 remotes,保留用户已有改动。
23
+ 按 `docs/agent-workflows.md#contract-adoption` 选择新的远端 annotated spec tag,固定 tag object、peeled commit、
24
+ snapshot 和 SHA-256。
25
+
26
+ 比较相对当前 pin 的 request、response、security、examples、成功码及 PVD/PCG,只适配本次增量。
27
+ 即使仅 description/examples 改变,也检查现有 public 输入能否表达新能力。专用 schema 与顶层请求可能服务不同
28
+ 流程,先核对适用范围,再判断是否确有契约矛盾;把 SDK 尚未实现的能力如实归为 SDK 缺项。
29
+
30
+ 从 release commit 复制快照、更新 lock、运行生成脚本,审查生成差异;同步受影响的 adapter、public 类型、测试、
31
+ coverage 和当前态文档。公开接口和生命周期沿 public intent → wire operation → Provider evidence 核对。
32
+ 按需读 `docs/agent-architecture.md`,邻接仓库仅作只读证据,不运行 spec 测试或修改其配置。
33
+
34
+ ## 2. 验证与审查
35
+
36
+ 执行 `docs/agent-workflows.md#delivery-gate`,具体命令取 `docs/development.md#development`。
37
+ 代码、契约、依赖或构建行为变化先聚焦再完整测试;聚焦用例不能代替完整 `pytest`。修复需补充的用例来自契约、
38
+ 独立示例或可验证属性,不复刻实现,不增加 Provider 逐路由 conformance。
39
+
40
+ 真实 smoke 按 `docs/agent-workflows.md#provider-validation` 确认目标、ignore 和授权。授权覆盖隔离服务的登录、
41
+ Session 恢复、刷新和查询时,完整测试显式加载 SDK 自己的配置:
42
+
43
+ ```bash
44
+ uv run --frozen --env-file .env.test pytest -ra
45
+ ```
46
+
47
+ 否则运行不加载 Provider 连接变量的完整测试,明确报告 smoke 的 skip 及原因;用户要求的实时验证缺失时不能声称
48
+ 已经完成。区分 MockTransport 的 SDK 映射证据与真实服务证据,禁止将凭证或敏感 payload 打印或提交。
49
+
50
+ 对实质代码差异使用系统 `review-agent` skill,委派一个全新、只读子代理(`fork_turns: "none"`)。根代理处理
51
+ 可证实问题并验证受影响部分。复用仍覆盖当前差异的完整测试、静态检查和审查结果,记录命令、结果及其覆盖的
52
+ commit/diff;只改版本后补版本一致性检查,不重跑未受影响的全套。
53
+
54
+ ## 3. 收敛并一次提交
55
+
56
+ 按 `docs/development.md#versioning` 选择兼容的下个 SDK 版本;使用用户指定版本时先检查 tag 和 PyPI 是否占用。
57
+ SDK 版本与 spec 日历版本独立,不跳过未占用的预发布序号。`pyproject.toml` 是唯一版本来源,更新 `uv.lock` 的
58
+ 本项目版本。仅改版本时保持其余 lock 内容,使用发布 workflow 固定的 uv 检查兼容性。
59
+
60
+ 按需读配置/依赖章节;不因发布顺手升级依赖、Node、uv 或流水线插件。文档在提交前完成,仅维护受影响的当前态,不新建
61
+ CHANGELOG。正常发布由 CI 完成可复现构建、制品审计和安装后消费;只有打包故障或打包配置变化需要时才本地构建,
62
+ 使用临时目录,不积累 `dist/`。
63
+
64
+ 复核最终 diff、版本、Contract pin 和有效验证证据,将本次尚未提交的实现、测试、文档及版本号放入同一个
65
+ release commit。已有提交的历史仅在另有授权时改写。
66
+
67
+ ## 4. 推送、发布并核验
68
+
69
+ 执行 `docs/agent-workflows.md#release`:创建指向 release commit 的 annotated `v{version}` tag,推送
70
+ `origin` 的 main/tag,并验证远端 refs;tag 推送自动触发发布 CI。跟踪该 commit/tag 的 CI,确认测试、
71
+ 可复现构建、制品审计、安装后消费和 PyPI 上传成功;tag 推送成功不等于发布完成。
72
+
73
+ 从本次 CI 构建审计输出或下载的制品取得 wheel/sdist 的 SHA-256,不能仅用 PyPI 自报哈希证明 CI/PyPI 一致。
74
+ 在 SDK 锁定开发环境中运行本技能的只读核验器:
75
+
76
+ ```bash
77
+ uv run --frozen --no-sync python -B .agents/skills/matelab-sdk-release/scripts/verify_pypi.py \
78
+ --repo "$PWD" --ref RELEASE_COMMIT \
79
+ --sha256 'WHEEL_FILENAME=CI_SHA256' --sha256 'SDIST_FILENAME=CI_SHA256'
80
+ ```
81
+
82
+ 参数替换为这次发布的 commit、文件名和 CI 哈希。
83
+ 纯核验使用现有环境;`--no-sync` 禁止自动同步虚拟环境,`-B` 禁止写入字节码,环境缺失时如实报告。
84
+ [核验器](scripts/verify_pypi.py) 在内存中下载制品,比对发布提交的源码、配置、版本、依赖和 README,不构建或保留
85
+ 历史制品。PyPI 暂未可读时允许短暂重试,等待期间保持进度更新。
86
+
87
+ 失败时定位具体阶段,优先重试失败环节;不通过删测试、跳检查或自动另选版本制造成功。改变已推送历史或处理失败
88
+ 候选前核对现有授权,已发布版本不可覆盖。最后报告版本/链接、单次提交、`origin` 的 main/tag refs、CI/PyPI 核验和工作区状态;
89
+ 明确剩余失败或未验证项。
@@ -0,0 +1,4 @@
1
+ interface:
2
+ display_name: "Matelab SDK 完整发布"
3
+ short_description: "Matelab SDK 的 spec 同步、完整验证、单次提交与 PyPI 发布"
4
+ default_prompt: "使用 $matelab-sdk-release 完成 Matelab Python SDK 的 spec 增量同步、必要适配、完整测试和审查,将版本号与全部改动放进同一个提交,推送并发布到 PyPI。"
@@ -0,0 +1,165 @@
1
+ """Read-only comparison of a Matelab SDK release, CI hashes and PyPI artifacts."""
2
+
3
+ import argparse
4
+ import hashlib
5
+ import io
6
+ import json
7
+ import re
8
+ import subprocess
9
+ import tarfile
10
+ import tomllib
11
+ import zipfile
12
+ from email.message import Message
13
+ from email.parser import BytesParser
14
+ from pathlib import Path
15
+ from urllib.parse import quote, urlsplit
16
+ from urllib.request import urlopen
17
+
18
+ from packaging.requirements import Requirement
19
+
20
+
21
+ def require(condition: bool, message: str) -> None:
22
+ if not condition:
23
+ raise ValueError(message)
24
+
25
+
26
+ def git_bytes(repo: Path, commit: str, path: str) -> bytes:
27
+ return subprocess.check_output(["git", "-C", str(repo), "show", f"{commit}:{path}"])
28
+
29
+
30
+ def ci_hash(value: str) -> tuple[str, str]:
31
+ name, separator, digest = value.partition("=")
32
+ if not separator or not name or re.fullmatch(r"[a-fA-F0-9]{64}", digest) is None:
33
+ raise argparse.ArgumentTypeError("Expected FILENAME=64_HEX_SHA256 from this release's CI artifacts")
34
+ return name, digest.lower()
35
+
36
+
37
+ def metadata_matches(metadata: Message, project: dict, readme: str) -> None:
38
+ require(metadata["Version"] == project["version"], "Artifact version mismatch")
39
+ require(metadata["Requires-Python"] == project["requires-python"], "Artifact Python requirement mismatch")
40
+ expected = {Requirement(value) for value in project["dependencies"]}
41
+ actual = {Requirement(value) for value in metadata.get_all("Requires-Dist", [])}
42
+ require(actual == expected, "Artifact dependency mismatch")
43
+ require(metadata["License-Expression"] == project["license"], "Artifact license mismatch")
44
+ author = ", ".join(entry["name"] for entry in project["authors"])
45
+ require(metadata["Author"] == author, "Artifact author mismatch")
46
+ description = metadata.get_payload()
47
+ require(isinstance(description, str) and description.strip() == readme.strip(), "Artifact README mismatch")
48
+
49
+
50
+ def verify_wheel(payload: bytes, repo: Path, commit: str, project: dict, readme: str, tracked: set[str]) -> int:
51
+ package = {path.removeprefix("src/"): path for path in tracked if path.startswith("src/matelab/")}
52
+ metadata_dir = f"matelab_python_sdk-{project['version']}.dist-info"
53
+ with zipfile.ZipFile(io.BytesIO(payload)) as archive:
54
+ names = archive.namelist()
55
+ require(len(names) == len(set(names)), "Duplicate wheel members")
56
+ require({name for name in names if name.startswith("matelab/")} == set(package), "Wheel package file mismatch")
57
+ for name, source in package.items():
58
+ require(archive.read(name) == git_bytes(repo, commit, source), f"Wheel source mismatch: {source}")
59
+ metadata_matches(BytesParser().parsebytes(archive.read(f"{metadata_dir}/METADATA")), project, readme)
60
+ for license_file in project["license-files"]:
61
+ require(
62
+ archive.read(f"{metadata_dir}/licenses/{license_file}") == git_bytes(repo, commit, license_file),
63
+ f"Wheel license file mismatch: {license_file}",
64
+ )
65
+ return len(package)
66
+
67
+
68
+ def verify_sdist(payload: bytes, repo: Path, commit: str, project: dict, readme: str, tracked: set[str]) -> int:
69
+ prefix = f"matelab_python_sdk-{project['version']}/"
70
+ seen: set[str] = set()
71
+ with tarfile.open(fileobj=io.BytesIO(payload), mode="r:gz") as archive:
72
+ for member in archive.getmembers():
73
+ require(member.name.startswith(prefix) or member.name == prefix.rstrip("/"), "Unexpected sdist root")
74
+ if member.isdir():
75
+ continue
76
+ require(member.isfile(), "Unexpected sdist link or special member")
77
+ path = member.name.removeprefix(prefix)
78
+ require(path not in seen, f"Duplicate sdist member: {path}")
79
+ seen.add(path)
80
+ stream = archive.extractfile(member)
81
+ require(stream is not None, f"Unreadable sdist member: {path}")
82
+ contents = stream.read()
83
+ if path == "PKG-INFO":
84
+ metadata_matches(BytesParser().parsebytes(contents), project, readme)
85
+ else:
86
+ require(path in tracked, f"Untracked sdist member: {path}")
87
+ require(contents == git_bytes(repo, commit, path), f"Sdist source mismatch: {path}")
88
+ required = {path for path in tracked if path.startswith("src/matelab/")}
89
+ required.update({"PKG-INFO", "pyproject.toml", "uv.lock", project["readme"], *project["license-files"]})
90
+ required.update(path for path in tracked if path.startswith("contracts/"))
91
+ require(required <= seen, f"Missing sdist files: {sorted(required - seen)}")
92
+ return len(seen - {"PKG-INFO"})
93
+
94
+
95
+ def main() -> None:
96
+ parser = argparse.ArgumentParser(description=__doc__)
97
+ parser.add_argument("--repo", type=Path, default=Path.cwd())
98
+ parser.add_argument("--ref", required=True, help="Release commit or immutable release tag")
99
+ parser.add_argument(
100
+ "--sha256", action="append", type=ci_hash, required=True, help="FILENAME=CI_SHA256; repeat per file"
101
+ )
102
+ args = parser.parse_args()
103
+ repo = args.repo.resolve()
104
+ commit = subprocess.check_output(
105
+ ["git", "-C", str(repo), "rev-parse", "--verify", "--end-of-options", f"{args.ref}^{{commit}}"], text=True
106
+ ).strip()
107
+ project = tomllib.loads(git_bytes(repo, commit, "pyproject.toml").decode())["project"]
108
+ require(project["name"] == "matelab-python-sdk", "This verifier is for matelab-python-sdk")
109
+ expected_hashes = dict(args.sha256)
110
+ require(len(expected_hashes) == len(args.sha256), "Duplicate expected artifact names")
111
+ readme = git_bytes(repo, commit, project["readme"]).decode()
112
+ tracked = set(
113
+ subprocess.check_output(
114
+ ["git", "-C", str(repo), "ls-tree", "-r", "--name-only", commit], text=True
115
+ ).splitlines()
116
+ )
117
+ url = f"https://pypi.org/pypi/{quote(project['name'], safe='')}/{quote(project['version'], safe='')}/json"
118
+ with urlopen(url, timeout=30) as response:
119
+ published = json.load(response)
120
+ info = published["info"]
121
+ require(info["version"] == project["version"], "PyPI version mismatch")
122
+ require(info["requires_python"] == project["requires-python"], "PyPI Python requirement mismatch")
123
+ require(info["description"].strip() == readme.strip(), "PyPI README mismatch")
124
+ require(
125
+ {Requirement(value) for value in info["requires_dist"] or []}
126
+ == {Requirement(value) for value in project["dependencies"]},
127
+ "PyPI dependency mismatch",
128
+ )
129
+ entries = published["urls"]
130
+ require(len(entries) == len(expected_hashes), "PyPI/CI artifact count mismatch")
131
+ require({entry["filename"] for entry in entries} == set(expected_hashes), "PyPI/CI artifact names mismatch")
132
+ counts = {}
133
+ for entry in entries:
134
+ name = entry["filename"]
135
+ digest = expected_hashes[name]
136
+ require(entry["digests"]["sha256"] == digest, f"CI/PyPI hash mismatch: {name}")
137
+ location = urlsplit(entry["url"])
138
+ require(
139
+ location.scheme == "https" and location.hostname == "files.pythonhosted.org", "Unexpected artifact host"
140
+ )
141
+ with urlopen(entry["url"], timeout=30) as response:
142
+ payload = response.read()
143
+ require(hashlib.sha256(payload).hexdigest() == digest, f"Downloaded hash mismatch: {name}")
144
+ if entry["packagetype"] == "bdist_wheel":
145
+ counts["wheel_package_files"] = verify_wheel(payload, repo, commit, project, readme, tracked)
146
+ elif entry["packagetype"] == "sdist":
147
+ counts["sdist_source_files"] = verify_sdist(payload, repo, commit, project, readme, tracked)
148
+ else:
149
+ raise ValueError(f"Unexpected artifact type: {entry['packagetype']}")
150
+ require("wheel_package_files" in counts and "sdist_source_files" in counts, "Wheel and sdist are both required")
151
+ print(json.dumps({"version": project["version"], "commit": commit, "files": expected_hashes, **counts}, indent=2))
152
+
153
+
154
+ if __name__ == "__main__":
155
+ try:
156
+ main()
157
+ except (
158
+ OSError,
159
+ ValueError,
160
+ KeyError,
161
+ subprocess.CalledProcessError,
162
+ tarfile.TarError,
163
+ zipfile.BadZipFile,
164
+ ) as error:
165
+ raise SystemExit(f"Release verification failed: {error}") from None
@@ -0,0 +1 @@
1
+ 3.15
@@ -0,0 +1,89 @@
1
+ version: "1.0"
2
+ name: sdk-release
3
+ displayName: Publish SDK to PyPI
4
+ ref_variable:
5
+ - UV_PUBLISH_TOKEN
6
+ triggers:
7
+ push:
8
+ tags:
9
+ prefix:
10
+ - v
11
+ stages:
12
+ - name: release
13
+ displayName: Test, build and publish
14
+ strategy: naturally
15
+ trigger: auto
16
+ steps:
17
+ - step: build@python
18
+ name: build-and-publish
19
+ displayName: Validate and publish release
20
+ # Gitee's Python bootstraps uv; the SDK runs on uv-managed Python 3.15.
21
+ pythonVersion: "3.14"
22
+ commands: |
23
+ set +x
24
+ set -eu
25
+ # Gitee injects its pytest tooling into Python's import path.
26
+ unset PYTHONPATH PYTHONHOME
27
+ python -m pip install --user --index-url https://pypi.org/simple "uv==0.13.7"
28
+ export PATH="$(python -m site --user-base)/bin:$PATH"
29
+ uv --version
30
+ release_tag="v$(uv version --short)"
31
+ build_ref="${GITEE_BRANCH:?Gitee build ref is required}"
32
+ if [ "$build_ref" != main ] && [ "$build_ref" != "$release_tag" ]; then
33
+ echo "Release ref must be $release_tag; received $build_ref" >&2
34
+ exit 1
35
+ fi
36
+ if [ "$(git rev-parse HEAD)" != "${GITEE_COMMIT:?Gitee trigger commit is required}" ]; then
37
+ echo "Checkout does not match the Gitee build commit" >&2
38
+ exit 1
39
+ fi
40
+ export SOURCE_DATE_EPOCH="$(git show -s --format=%ct HEAD)"
41
+ unset MATELAB_PROVIDER_BASE_URL MATELAB_PROVIDER_USERNAME MATELAB_PROVIDER_PASSWORD
42
+
43
+ test_status=0
44
+ for python_version in 3.12 3.13 3.14 3.15; do
45
+ uv run --frozen --exact --python "$python_version" pytest -rs || test_status=1
46
+ done
47
+ test "$test_status" -eq 0
48
+
49
+ export UV_PYTHON="$(cat .python-version)"
50
+ uv run --frozen python scripts/generate_models.py --check
51
+ uv run --frozen ruff check .
52
+ uv run --frozen ruff format --check .
53
+ uv run --frozen basedpyright
54
+
55
+ mkdir -p dist/release dist/reproducible
56
+ uv build --no-build-isolation --out-dir dist/release
57
+ uv build --no-build-isolation --out-dir dist/reproducible
58
+ cmp dist/release/*.whl dist/reproducible/*.whl
59
+ cmp dist/release/*.tar.gz dist/reproducible/*.tar.gz
60
+ uv run --frozen python scripts/check_release.py dist/release/*.whl dist/release/*.tar.gz
61
+ (cd dist/release && sha256sum *.whl *.tar.gz > SHA256SUMS)
62
+ cat dist/release/SHA256SUMS
63
+
64
+ sdk_consumer_dir="$(mktemp -d)"
65
+ trap 'rm -rf -- "$sdk_consumer_dir"' EXIT
66
+ uv venv "$sdk_consumer_dir/venv"
67
+ uv pip install \
68
+ --python "$sdk_consumer_dir/venv/bin/python" \
69
+ dist/release/*.whl \
70
+ "basedpyright>=1.40.2" \
71
+ "nodejs-wheel-binaries==22.20.0"
72
+ "$sdk_consumer_dir/venv/bin/python" scripts/check_installed_package.py
73
+ "$sdk_consumer_dir/venv/bin/basedpyright" \
74
+ --pythonpath "$sdk_consumer_dir/venv/bin/python" \
75
+ --project tests/typing/pyproject.toml
76
+
77
+ if [ "$build_ref" = main ]; then
78
+ echo "Validation passed on main; publishing requires tag $release_tag"
79
+ exit 0
80
+ fi
81
+ : "${UV_PUBLISH_TOKEN:?Configure UV_PUBLISH_TOKEN as a protected pipeline secret}"
82
+ uv publish --trusted-publishing never \
83
+ --publish-url https://upload.pypi.org/legacy/ \
84
+ dist/release/*.whl dist/release/*.tar.gz
85
+ artifacts:
86
+ - name: RELEASE_ARTIFACT
87
+ path:
88
+ - ./dist/release
89
+ type: .tar.gz
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: matelab-python-sdk
3
- Version: 0.2.0a9
3
+ Version: 0.3.0a1
4
4
  Summary: Reusable async Python client for the Matelab Integration Contract
5
5
  Author: CMPDC, Institute of Physics, Chinese Academy of Sciences
6
6
  License-Expression: Apache-2.0
@@ -12,24 +12,24 @@ Classifier: Framework :: AsyncIO
12
12
  Classifier: Operating System :: OS Independent
13
13
  Classifier: Programming Language :: Python :: 3
14
14
  Classifier: Programming Language :: Python :: 3 :: Only
15
- Classifier: Programming Language :: Python :: 3.11
16
15
  Classifier: Programming Language :: Python :: 3.12
17
16
  Classifier: Programming Language :: Python :: 3.13
18
17
  Classifier: Programming Language :: Python :: 3.14
18
+ Classifier: Programming Language :: Python :: 3.15
19
19
  Classifier: Topic :: Scientific/Engineering
20
20
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
21
21
  Classifier: Typing :: Typed
22
- Requires-Python: >=3.11
22
+ Requires-Python: >=3.12
23
23
  Requires-Dist: httpx2>=2.16.0
24
24
  Requires-Dist: jsonschema>=4.26.0
25
- Requires-Dist: pydantic>=2.14.1
25
+ Requires-Dist: pydantic>=2.14.3
26
26
  Requires-Dist: typing-extensions>=4.16.0
27
27
  Description-Content-Type: text/markdown
28
28
 
29
29
  # matelab-python-sdk
30
30
 
31
31
  Async Python client for the Matelab Integration Contract, with typed interfaces for authentication,
32
- notebooks, records, attachments, cloud files, templates, and literature. Requires Python 3.11 or newer.
32
+ notebooks, records, attachments, cloud files, templates, and literature. Requires Python 3.12 or newer.
33
33
 
34
34
  ## Installation
35
35
 
@@ -64,6 +64,6 @@ asyncio.run(main())
64
64
 
65
65
  ## Documentation
66
66
 
67
- - [API reference](https://github.com/tiannianzhu/matelab-python-sdk/blob/main/docs/api.md): Session persistence, errors, examples, and Provider limits.
68
- - [Operation coverage](https://github.com/tiannianzhu/matelab-python-sdk/blob/main/docs/operation-coverage.yaml): supported operations.
69
- - [Development and releases](https://github.com/tiannianzhu/matelab-python-sdk/blob/main/docs/development.md): checks, Provider smoke, versioning, and builds.
67
+ - [API reference](https://gitee.com/mat-code/matelab-python-sdk/blob/main/docs/api.md): Session persistence, errors, examples, and Provider limits.
68
+ - [Operation coverage](https://gitee.com/mat-code/matelab-python-sdk/blob/main/docs/operation-coverage.yaml): supported operations.
69
+ - [Development and releases](https://gitee.com/mat-code/matelab-python-sdk/blob/main/docs/development.md): checks, Provider smoke, versioning, and builds.
@@ -1,7 +1,7 @@
1
1
  # matelab-python-sdk
2
2
 
3
3
  Async Python client for the Matelab Integration Contract, with typed interfaces for authentication,
4
- notebooks, records, attachments, cloud files, templates, and literature. Requires Python 3.11 or newer.
4
+ notebooks, records, attachments, cloud files, templates, and literature. Requires Python 3.12 or newer.
5
5
 
6
6
  ## Installation
7
7
 
@@ -36,6 +36,6 @@ asyncio.run(main())
36
36
 
37
37
  ## Documentation
38
38
 
39
- - [API reference](https://github.com/tiannianzhu/matelab-python-sdk/blob/main/docs/api.md): Session persistence, errors, examples, and Provider limits.
40
- - [Operation coverage](https://github.com/tiannianzhu/matelab-python-sdk/blob/main/docs/operation-coverage.yaml): supported operations.
41
- - [Development and releases](https://github.com/tiannianzhu/matelab-python-sdk/blob/main/docs/development.md): checks, Provider smoke, versioning, and builds.
39
+ - [API reference](https://gitee.com/mat-code/matelab-python-sdk/blob/main/docs/api.md): Session persistence, errors, examples, and Provider limits.
40
+ - [Operation coverage](https://gitee.com/mat-code/matelab-python-sdk/blob/main/docs/operation-coverage.yaml): supported operations.
41
+ - [Development and releases](https://gitee.com/mat-code/matelab-python-sdk/blob/main/docs/development.md): checks, Provider smoke, versioning, and builds.
@@ -2,12 +2,12 @@
2
2
  "schema_version": 1,
3
3
  "contract_line": "integration-v1",
4
4
  "release": {
5
- "tag": "v2026.11.1",
6
- "commit": "402a158052995850e0b7fad5ed5399c8a2eed294"
5
+ "tag": "v2026.11.2",
6
+ "commit": "a986f73b0edc6bb67f2993a9ee1291aba31df5e5"
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": "f9d3bd0baa4e5e70fe13870c21d9be70e40e2686831a0ffcc4632b39f83a3257"
11
+ "sha256": "4a485f0ab2b589136cdcccad77407d8b5768e6e8cbb8e3a7636d8b9a07649991"
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: "2026.11.1"
4
+ version: "2026.11.2"
5
5
  description: |
6
6
  Current PHP wire contract for generating SDKs against the selected
7
7
  `matelab-server` Provider version.
@@ -18,12 +18,12 @@ info:
18
18
  x-contract-line: integration-v1
19
19
  x-provider-evidence:
20
20
  repository: /home/tnzhu/projects/matelab-server
21
- commit: 00f17b6ce0906bb78b0a3e9bb8c50abee90ca3d0
21
+ commit: f5a714e17cd76076efe7579db279936ac29b50a6
22
22
  verificationRuntime:
23
23
  baseUrl: https://matelab.iphy.ac.cn/eln-test/api
24
24
  container: matelab-test-server-1
25
25
  coreSourceComparison: sha256-exact
26
- coreSourceFileCount: 152
26
+ coreSourceFileCount: 153
27
27
  dataServerScope: localhost
28
28
  files:
29
29
  - controller/Actions.php
@@ -53,6 +53,7 @@ info:
53
53
  - lib/Route.php
54
54
  - lib/HttpServer.php
55
55
  - lib/WsServer.php
56
+ - start.php
56
57
  - lib/MultiException.php
57
58
  - config/Db.php
58
59
  - config/Channel.php
@@ -61,9 +62,9 @@ info:
61
62
  - sql/matelab.sql
62
63
  - sql/recommend.sql
63
64
  x-provider-audit:
64
- currentCommit: 00f17b6ce0906bb78b0a3e9bb8c50abee90ca3d0
65
- previousReleaseEvidenceCommit: 161c4560ae1cb6267d46841d6fe8adc8270ee1bc
66
- lastProviderVerifiedCommit: 00f17b6ce0906bb78b0a3e9bb8c50abee90ca3d0
65
+ currentCommit: f5a714e17cd76076efe7579db279936ac29b50a6
66
+ previousReleaseEvidenceCommit: 00f17b6ce0906bb78b0a3e9bb8c50abee90ca3d0
67
+ lastProviderVerifiedCommit: f5a714e17cd76076efe7579db279936ac29b50a6
67
68
  contractStatus: current-provider-aligned
68
69
  # Summary of changes adopted by current Supported Routes since the last
69
70
  # released contract, including contract corrections. The Provider diff is
@@ -71,8 +72,7 @@ info:
71
72
  # updates against that fixed release baseline; use [] if no relevant changes.
72
73
  # Entries are not per-change verification results or a cumulative history.
73
74
  incorporatedChanges:
74
- - {operationId: importRecordDataset, change: authorized-cloud-drive-hash-references-pass-permission-query}
75
- - {operationId: updateRecordData, change: authorized-cloud-drive-hash-references-pass-permission-query}
75
+ - {operationId: stageTemporaryFile, change: filename-independent-continuation-with-final-fragment-name-and-extensionless-abort}
76
76
  x-consumer-evidence:
77
77
  - repository: /home/tnzhu/projects/matelab-mcp
78
78
  commit: 94a129b2df0ef0fbcf179c10550fb7c9a5dd2e6f
@@ -93,7 +93,7 @@ info:
93
93
  identity must come from token issuance. ELN-group and notebook-list
94
94
  calls still provide Consumer evidence for their canonical GET forms.
95
95
  - repository: /home/tnzhu/projects/matelab-front
96
- commit: 55bc8d95d9d4ef02841621f0b793d273cea66137
96
+ commit: f7a7b952d7981f169d5c2585aea5c94b418086a3
97
97
  files:
98
98
  - src/App.vue
99
99
  - src/fn/index.js
@@ -2020,11 +2020,11 @@ paths:
2020
2020
  workflow therefore preserves the exact upload `eln`/`user` selector and
2021
2021
  permits one occurrence in one target record. The operation has no
2022
2022
  contracted abort, status, expiration, or idempotent retry. A hash
2023
- mismatch in this one-fragment flow rolls back the temporary row and
2024
- new bytes. Errors after import/update writing starts still do not prove
2025
- whether the stage was consumed. Temporary bytes retain the multipart
2026
- filename's extension after removing non-ASCII-alphanumeric characters;
2027
- the response still reports the original source filename.
2023
+ mismatch in this one-fragment flow occurs before row registration and
2024
+ removes the new bytes. Errors after import/update writing starts still
2025
+ do not prove whether the stage was consumed. On completion, bytes retain
2026
+ the multipart filename's extension after removing characters outside
2027
+ `[A-Za-z0-9]`; the response still reports the original source filename.
2028
2028
  Raw identifiers identify the staging row;
2029
2029
  their filesystem components are sanitized and can alias (PVD-051).
2030
2030
  x-known-provider-issues: [PVD-014, PVD-051]
@@ -3735,18 +3735,18 @@ paths:
3735
3735
  successful response's `offset`. Omitting it defaults to zero and
3736
3736
  restarts incomplete bytes rather than appending. The final fragment
3737
3737
  returns the combined SHA-256, size, source filename, temporary row id,
3738
- and legacy preview query. The multipart filename's final extension,
3739
- stripped of characters outside `[A-Za-z0-9]` with case preserved,
3740
- also selects the temporary bytes. Repeat that extension on every
3741
- fragment; changing it cannot continue the previous bytes and returns
3742
- code 2 when the requested offset has no matching file. A different
3743
- filename stem with the same sanitized extension can continue them.
3738
+ and legacy preview query. Incomplete bytes are selected by the same
3739
+ `uid` and `tempid`, independently of the multipart filename and extension.
3740
+ Both may change between fragments. Only after final hash validation does
3741
+ PHP append the final fragment's extension, stripped of characters outside
3742
+ `[A-Za-z0-9]` with case preserved, and register the completed row. The
3743
+ response `filename` is the final fragment's original source filename.
3744
3744
 
3745
3745
  `abort=1` is the compensating cleanup operation. It requires the same
3746
3746
  `uid` and `tempid` and omits `file`. If no completed row exists for
3747
- those identifiers, it removes incomplete bytes for all extension
3748
- variants, including the extensionless file. If a completed row exists,
3749
- it preserves the row and bytes without cleaning any variants. Both
3747
+ those identifiers, it removes the extensionless incomplete file.
3748
+ It does not scan extension-suffixed orphan files. If a completed row
3749
+ exists, it preserves the row and bytes without cleaning any files. Both
3750
3750
  states return the standard HTTP 200 code-2 error envelope ("upload
3751
3751
  cancelled"); that response does not prove completed staging was deleted.
3752
3752
  Complete uploads must be consumed by their intended finalizer; this
@@ -7490,9 +7490,9 @@ components:
7490
7490
  type: string
7491
7491
  format: binary
7492
7492
  description: |
7493
- Required for upload branches and omitted for abort. The multipart
7494
- filename's sanitized final extension selects the temporary bytes;
7495
- keep that extension unchanged throughout continuation.
7493
+ Required for upload branches and omitted for abort. Filename and
7494
+ extension may change between fragments in the same uid/tempid
7495
+ session. Completion uses the final fragment's source filename.
7496
7496
  allOf:
7497
7497
  - if:
7498
7498
  required: [abort]
@@ -31,8 +31,9 @@
31
31
  - `[tool.basedpyright]` 字段依次为 `pythonVersion`、`typeCheckingMode`、`include`、`exclude`、
32
32
  `reportUnannotatedClassAttribute`、`reportIncompatibleUnannotatedOverride`,其他设置放在其后。沿用 SDK 的目标
33
33
  Python 版本与检查路径,不照搬其他项目的值。
34
- - runtime、build 和 dev 依赖仅声明已验证的 `>=` 下限;`nodejs-wheel-binaries` 保留系统所需的精确版本。
35
- 更新声明时同步 lock 和安装后消费检查使用的 CI requirement。
34
+ - 升级 runtime、build 或 dev 依赖时,必须将验证通过的新版本同步为 `pyproject.toml` 中对应依赖的 `>=` 下限,
35
+ 同时更新对应 CI 约束(包括安装后消费检查)。`uv.lock` 必须通过 `uv lock` 等 uv 命令自动更新,不手工编辑。
36
+ 例外:`nodejs-wheel-binaries` 保留系统所需的精确版本,并同步 lock 和对应 CI 约束。
36
37
  - 构建后端依赖和 wheel package 显式配置;metadata 与制品检查从这些声明推导预期值。
37
38
 
38
39
  完成标准:配置可解析、声明与 lock 一致、受影响 CI 使用同一约束;字段重排不改变配置含义。
@@ -85,16 +86,15 @@ Ruff/Basedpyright 诊断通过修复代码或精确 ignore 解决,不缩小检
85
86
  ## Release
86
87
 
87
88
  普通 commit/push 只处理已授权的 diff,不自动改版本、建 tag 或发布。发布准备完成但缺少外部操作授权时,
88
- 先交付可审查结果,再请求执行授权。以下步骤适用于实际发布:
89
+ 先交付可审查结果,再请求执行授权。发布与普通推送只同步 `origin`。以下步骤适用于实际发布:
89
90
 
90
91
  1. 按 [版本规则](development.md#versioning) 选择 SDK 版本,读取 `[project].version`,确认待发布版本未发布
91
92
  且 Contract pin 完整。公开接口删除、重命名、类型收紧或不兼容行为变化按 breaking change 处理。
92
93
  完成标准:版本、当前文档和 tag 名 `v{version}` 一致。
93
94
  2. 提交前同步受影响的当前文档,完成 [delivery gate](#delivery-gate)。构建元数据或发布流程变化时,核对
94
- metadata 版本与固定的 PyPI 发布 Action 是否兼容;需要本地制品证据时使用
95
+ metadata 与流水线固定版本的 uv 是否兼容;需要本地制品证据时使用
95
96
  [手工构建命令](development.md#reproducible-release-build)。创建已审查、已授权的 release commit。
96
- 3. 创建指向 release commit 的 annotated `v{version}` tag。先推送 `origin` 的 main/tag,再推送 GitHub mirror 的
97
- main/tag;只有与项目版本完全匹配的 tag 触发 Trusted Publishing workflow,第三方 Action 固定 immutable
98
- commit SHA。
99
- 4. 跟踪 [发布 CI](../.github/workflows/release.yml) 的测试、可复现构建、制品审计、安装后消费和 PyPI publish。
100
- 完成标准:两个远端 main/tag 对应 release commit,CI 全部通过,PyPI 对应版本可读取。
97
+ 3. 创建指向 release commit 的 annotated `v{version}` tag,推送 `origin` 的 main/tag;tag 推送自动触发
98
+ [发布 CI](../.workflow/release.yml)。
99
+ 4. 跟踪发布 CI 的测试、可复现构建、制品审计、安装后消费和 PyPI publish。
100
+ 完成标准:`origin` 的 main/tag 对应 release commit,CI 全部通过,PyPI 对应版本与 CI 制品哈希一致。