matelab-python-sdk 0.2.0a7__tar.gz → 0.2.0a9__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.0a7 → matelab_python_sdk-0.2.0a9}/CONTEXT.md +2 -2
- {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/PKG-INFO +2 -2
- {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/contracts/matelab-integration-v1.lock.json +3 -3
- {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/contracts/matelab-integration-v1.openapi.yaml +81 -39
- {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/docs/api.md +57 -20
- {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/docs/operation-coverage.yaml +1 -1
- {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/pyproject.toml +9 -8
- {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/src/matelab/_generated/models.py +6 -3
- {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/src/matelab/domains/records.py +49 -22
- {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/src/matelab/domains/uploads.py +2 -1
- {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/tests/conftest.py +13 -1
- {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/tests/test_record_lifecycle.py +101 -4
- {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/tests/test_record_mutations.py +143 -1
- {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/tests/test_uploads_and_literature.py +13 -5
- {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/tests/typing/consumer.py +16 -0
- {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/uv.lock +63 -50
- {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/.env.example +0 -0
- {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/.github/workflows/release.yml +0 -0
- {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/.gitignore +0 -0
- {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/AGENTS.md +0 -0
- {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/LICENSE +0 -0
- {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/NOTICE +0 -0
- {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/README.md +0 -0
- {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/docs/agent-architecture.md +0 -0
- {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/docs/agent-workflows.md +0 -0
- {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/docs/development.md +0 -0
- {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/scripts/check_installed_package.py +0 -0
- {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/scripts/check_release.py +0 -0
- {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/scripts/generate_models.py +0 -0
- {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/src/matelab/__init__.py +0 -0
- {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/src/matelab/_generated/__init__.py +0 -0
- {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/src/matelab/_hashes.py +0 -0
- {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/src/matelab/_transport.py +0 -0
- {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/src/matelab/_wire_validation.py +0 -0
- {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/src/matelab/client.py +0 -0
- {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/src/matelab/domains/__init__.py +0 -0
- {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/src/matelab/domains/_attachment_content.py +0 -0
- {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/src/matelab/domains/cloud_drive.py +0 -0
- {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/src/matelab/domains/groups.py +0 -0
- {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/src/matelab/domains/literature.py +0 -0
- {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/src/matelab/domains/notebooks.py +0 -0
- {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/src/matelab/domains/templates.py +0 -0
- {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/src/matelab/domains/users.py +0 -0
- {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/src/matelab/errors.py +0 -0
- {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/src/matelab/py.typed +0 -0
- {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/src/matelab/session.py +0 -0
- {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/src/matelab/streaming.py +0 -0
- {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/tests/provider/test_provider_smoke.py +0 -0
- {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/tests/test_async_attachment_upload.py +0 -0
- {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/tests/test_client.py +0 -0
- {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/tests/test_cloud_drive.py +0 -0
- {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/tests/test_comments_and_downloads.py +0 -0
- {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/tests/test_consumer_smoke.py +0 -0
- {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/tests/test_errors.py +0 -0
- {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/tests/test_extended_records.py +0 -0
- {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/tests/test_generation.py +0 -0
- {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/tests/test_groups.py +0 -0
- {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/tests/test_notebook_lifecycle.py +0 -0
- {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/tests/test_notebooks.py +0 -0
- {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/tests/test_operation_coverage.py +0 -0
- {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/tests/test_records.py +0 -0
- {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/tests/test_release.py +0 -0
- {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/tests/test_streaming.py +0 -0
- {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/tests/test_template_lifecycle.py +0 -0
- {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/tests/test_templates.py +0 -0
- {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/tests/test_transport.py +0 -0
- {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/tests/test_users.py +0 -0
- {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/tests/typing/pyproject.toml +0 -0
|
@@ -86,8 +86,8 @@ A notebook-scoped hierarchical container used to organize records. Records at th
|
|
|
86
86
|
_Avoid_: Subtype, record type, template
|
|
87
87
|
|
|
88
88
|
**Record import value**:
|
|
89
|
-
A template-shaped value composed of JSON-compatible scalars and containers, with a staged attachment
|
|
90
|
-
allowed wherever the template expects a file.
|
|
89
|
+
A template-shaped value composed of JSON-compatible scalars and containers, with a staged attachment or existing
|
|
90
|
+
cloud file allowed wherever the template expects a file.
|
|
91
91
|
_Avoid_: Arbitrary object, stored record module, Provider attachment reference
|
|
92
92
|
|
|
93
93
|
**Encrypted record modules**:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: matelab-python-sdk
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.0a9
|
|
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
|
|
@@ -20,7 +20,7 @@ Classifier: Topic :: Scientific/Engineering
|
|
|
20
20
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
21
21
|
Classifier: Typing :: Typed
|
|
22
22
|
Requires-Python: >=3.11
|
|
23
|
-
Requires-Dist: httpx2>=2.
|
|
23
|
+
Requires-Dist: httpx2>=2.16.0
|
|
24
24
|
Requires-Dist: jsonschema>=4.26.0
|
|
25
25
|
Requires-Dist: pydantic>=2.14.1
|
|
26
26
|
Requires-Dist: typing-extensions>=4.16.0
|
{matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/contracts/matelab-integration-v1.lock.json
RENAMED
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
"schema_version": 1,
|
|
3
3
|
"contract_line": "integration-v1",
|
|
4
4
|
"release": {
|
|
5
|
-
"tag": "v2026.
|
|
6
|
-
"commit": "
|
|
5
|
+
"tag": "v2026.11.1",
|
|
6
|
+
"commit": "402a158052995850e0b7fad5ed5399c8a2eed294"
|
|
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": "f9d3bd0baa4e5e70fe13870c21d9be70e40e2686831a0ffcc4632b39f83a3257"
|
|
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.
|
|
4
|
+
version: "2026.11.1"
|
|
5
5
|
description: |
|
|
6
6
|
Current PHP wire contract for generating SDKs against the selected
|
|
7
7
|
`matelab-server` Provider version.
|
|
@@ -10,15 +10,20 @@ info:
|
|
|
10
10
|
and attachment operations that resolve a data server support the
|
|
11
11
|
`localhost` branch; remote forwarding is excluded pending downstream
|
|
12
12
|
wire evidence and end-to-end verification (PVD-049).
|
|
13
|
+
Integration requests omit the optional browser `Version` header. If it is
|
|
14
|
+
supplied, PHP applies `intval` and compares it with the current minimum
|
|
15
|
+
20261029; a lower value returns HTTP 200 with string `code` and `errcode`
|
|
16
|
+
equal to `reload`. That browser refresh branch is outside the integer
|
|
17
|
+
response envelopes selected by this integration contract.
|
|
13
18
|
x-contract-line: integration-v1
|
|
14
19
|
x-provider-evidence:
|
|
15
20
|
repository: /home/tnzhu/projects/matelab-server
|
|
16
|
-
commit:
|
|
21
|
+
commit: 00f17b6ce0906bb78b0a3e9bb8c50abee90ca3d0
|
|
17
22
|
verificationRuntime:
|
|
18
|
-
baseUrl:
|
|
23
|
+
baseUrl: https://matelab.iphy.ac.cn/eln-test/api
|
|
19
24
|
container: matelab-test-server-1
|
|
20
25
|
coreSourceComparison: sha256-exact
|
|
21
|
-
coreSourceFileCount:
|
|
26
|
+
coreSourceFileCount: 152
|
|
22
27
|
dataServerScope: localhost
|
|
23
28
|
files:
|
|
24
29
|
- controller/Actions.php
|
|
@@ -56,9 +61,9 @@ info:
|
|
|
56
61
|
- sql/matelab.sql
|
|
57
62
|
- sql/recommend.sql
|
|
58
63
|
x-provider-audit:
|
|
59
|
-
currentCommit:
|
|
60
|
-
previousReleaseEvidenceCommit:
|
|
61
|
-
lastProviderVerifiedCommit:
|
|
64
|
+
currentCommit: 00f17b6ce0906bb78b0a3e9bb8c50abee90ca3d0
|
|
65
|
+
previousReleaseEvidenceCommit: 161c4560ae1cb6267d46841d6fe8adc8270ee1bc
|
|
66
|
+
lastProviderVerifiedCommit: 00f17b6ce0906bb78b0a3e9bb8c50abee90ca3d0
|
|
62
67
|
contractStatus: current-provider-aligned
|
|
63
68
|
# Summary of changes adopted by current Supported Routes since the last
|
|
64
69
|
# released contract, including contract corrections. The Provider diff is
|
|
@@ -66,29 +71,8 @@ info:
|
|
|
66
71
|
# updates against that fixed release baseline; use [] if no relevant changes.
|
|
67
72
|
# Entries are not per-change verification results or a cumulative history.
|
|
68
73
|
incorporatedChanges:
|
|
69
|
-
- {operationId:
|
|
70
|
-
- {operationId:
|
|
71
|
-
- {operationId: uploadRecordAttachment, change: single-fragment-hash-failure-rollback}
|
|
72
|
-
- {operationId: searchUsers, change: utf8-byte-minimum-without-a-keyword-length-cap-or-whitespace-filter}
|
|
73
|
-
- {operationId: listNotebookRecords, change: owner-id-or-email-selector-and-caller-defaults}
|
|
74
|
-
- {operationId: createBlankRecord, change: owner-id-or-email-selector-and-php-zero-title-rejection}
|
|
75
|
-
- {operationId: importRecordDataset, change: owner-and-template-owner-selectors-json-list-data-and-php-zero-string-rejection}
|
|
76
|
-
- {operationId: updateRecordData, change: owner-selector-numeric-extraction-selectors-and-module-specific-field-validation}
|
|
77
|
-
- {operationId: deleteRecordsByUid, change: owner-id-or-email-selector-and-caller-defaults}
|
|
78
|
-
- {operationId: saveNotebookMetadata, change: numeric-string-and-number-extraction-selectors}
|
|
79
|
-
- {operationId: searchRecordData, change: numeric-string-and-number-extraction-selectors}
|
|
80
|
-
- {operationId: addNotebookShare, change: provider-deduplicated-recipient-lists}
|
|
81
|
-
- {operationId: shareMultipleTemplatesWithUsers, change: list-limits-apply-after-provider-deduplication}
|
|
82
|
-
- {operationId: listLiteratureItems, change: duplicate-source-rating-and-keyword-filters-are-accepted}
|
|
83
|
-
- {operationId: shareLiteratureItems, change: provider-deduplicated-ids-and-optional-empty-recipient-list}
|
|
84
|
-
- {operationId: moveCloudDriveFiles, change: provider-deduplicated-file-ids}
|
|
85
|
-
- {operationId: deleteCloudDriveFiles, change: provider-deduplicated-file-ids}
|
|
86
|
-
- {operationId: listDeletedRecordPage, change: unpaged-zero-page-size-omits-count-and-page}
|
|
87
|
-
- {operationId: extractLiteratureMetadata, change: nonempty-doi-takes-precedence-over-staged-file-id}
|
|
88
|
-
- {operationId: saveTemplateContent, change: php-name-validation-and-json-body-checks-apply-only-on-write}
|
|
89
|
-
- {operationId: listTemplates, change: stored-keywords-accept-nonnull-json-values}
|
|
90
|
-
- {operationId: readTemplateContent, change: stored-module-names-and-language-are-returned-without-template-write-validation}
|
|
91
|
-
- {operationId: readRecord, change: record-update-names-and-stored-module-language-use-raw-response-types}
|
|
74
|
+
- {operationId: importRecordDataset, change: authorized-cloud-drive-hash-references-pass-permission-query}
|
|
75
|
+
- {operationId: updateRecordData, change: authorized-cloud-drive-hash-references-pass-permission-query}
|
|
92
76
|
x-consumer-evidence:
|
|
93
77
|
- repository: /home/tnzhu/projects/matelab-mcp
|
|
94
78
|
commit: 94a129b2df0ef0fbcf179c10550fb7c9a5dd2e6f
|
|
@@ -109,10 +93,11 @@ info:
|
|
|
109
93
|
identity must come from token issuance. ELN-group and notebook-list
|
|
110
94
|
calls still provide Consumer evidence for their canonical GET forms.
|
|
111
95
|
- repository: /home/tnzhu/projects/matelab-front
|
|
112
|
-
commit:
|
|
96
|
+
commit: 55bc8d95d9d4ef02841621f0b793d273cea66137
|
|
113
97
|
files:
|
|
114
98
|
- src/App.vue
|
|
115
99
|
- src/fn/index.js
|
|
100
|
+
- src/store/index.js
|
|
116
101
|
- src/components/fullview.vue
|
|
117
102
|
- src/components/userSelect.vue
|
|
118
103
|
- src/components/users.vue
|
|
@@ -128,7 +113,8 @@ info:
|
|
|
128
113
|
The current Front dev commit reads environment/menu from GET
|
|
129
114
|
`/actions/app` and obtains session identity from token issuance.
|
|
130
115
|
The shared upload client sends byte offsets to `/actions/bigupload`
|
|
131
|
-
and uses the acknowledged offset for the next fragment.
|
|
116
|
+
and uses the acknowledged offset for the next fragment. GET requests
|
|
117
|
+
also carry the browser version stored in `src/store/index.js`.
|
|
132
118
|
Existing record, comment, template, literature and cloud-drive calls
|
|
133
119
|
retain their Consumer use cases; no new routes are added by this audit.
|
|
134
120
|
x-record-attachment-staging:
|
|
@@ -1648,7 +1634,13 @@ paths:
|
|
|
1648
1634
|
Template-shaped file values supplied by the request must be strings in
|
|
1649
1635
|
the `#file{name}` or `#hash{filename.ext, hash}` form. The current
|
|
1650
1636
|
validator rejects even valid `#base64` input (PVD-042); inline base64
|
|
1651
|
-
is outside the callable contract.
|
|
1637
|
+
is outside the callable contract. Existing cloud-drive files can be
|
|
1638
|
+
referenced by `#hash{filename.ext, hash}` when the selected notebook
|
|
1639
|
+
owner's userid owns the personal-root file (`root_id=0`), or has a
|
|
1640
|
+
`files_permissions` row for its shared root. An existing cloud quote
|
|
1641
|
+
alone does not grant access. The reference creates a record attachment
|
|
1642
|
+
quote without consuming the cloud-drive file.
|
|
1643
|
+
In particular,
|
|
1652
1644
|
table/form file arrays cannot contain explicit JSON null values on the
|
|
1653
1645
|
current Provider. To create empty table file cells, omit the file column
|
|
1654
1646
|
and let the Provider complete it with canonical null cells.
|
|
@@ -1666,6 +1658,17 @@ paths:
|
|
|
1666
1658
|
template: Example Template
|
|
1667
1659
|
dataset:
|
|
1668
1660
|
- {uid: GENERATED-WIRE-UID, title: Imported Example, data: {Notes: Sanitized}, keyword: calibration}
|
|
1661
|
+
existing_cloud_attachment:
|
|
1662
|
+
summary: Reuse an authorized cloud-drive file without consuming it
|
|
1663
|
+
value:
|
|
1664
|
+
eln: Example Notebook
|
|
1665
|
+
template: Example Template
|
|
1666
|
+
dataset:
|
|
1667
|
+
- uid: REC-CLOUD-EXAMPLE-001
|
|
1668
|
+
title: Existing cloud attachment
|
|
1669
|
+
data:
|
|
1670
|
+
Attachments:
|
|
1671
|
+
File: ["#hash{evidence.zip, 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef}"]
|
|
1669
1672
|
preuploaded_attachment:
|
|
1670
1673
|
summary: Upload-event UID and future record UID are deliberately different
|
|
1671
1674
|
value:
|
|
@@ -1699,6 +1702,12 @@ paths:
|
|
|
1699
1702
|
value: {code: 0, msg: Data imported, errcode: 0, errmsg: Data imported, id: [9004], eln_id: 12}
|
|
1700
1703
|
error:
|
|
1701
1704
|
value: {code: 4, msg: Multiple errors, errcode: 4, errmsg: Multiple errors, errs: [Invalid imported field]}
|
|
1705
|
+
inaccessible_hash:
|
|
1706
|
+
value:
|
|
1707
|
+
code: 2
|
|
1708
|
+
msg: The referenced hash 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef does not exist
|
|
1709
|
+
errcode: 2
|
|
1710
|
+
errmsg: The referenced hash 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef does not exist
|
|
1702
1711
|
/eln_api/export:
|
|
1703
1712
|
post:
|
|
1704
1713
|
operationId: exportRecords
|
|
@@ -1793,7 +1802,12 @@ paths:
|
|
|
1793
1802
|
strings containing canonical `elnurl://h=...&s=...&f=...` references.
|
|
1794
1803
|
The Provider scans the incoming richtext value, resolves hashes against
|
|
1795
1804
|
caller-owned staging or authorized existing file quotes, and binds the
|
|
1796
|
-
resulting attachments while saving the record.
|
|
1805
|
+
resulting attachments while saving the record. Template-shaped
|
|
1806
|
+
`#hash{filename.ext, hash}` values also accept existing cloud-drive
|
|
1807
|
+
files: permission is checked against the selected notebook owner's
|
|
1808
|
+
userid, using personal-root ownership or a `files_permissions` row for
|
|
1809
|
+
the shared root. The cloud file is retained; an inaccessible hash is
|
|
1810
|
+
rejected before record content is written.
|
|
1797
1811
|
|
|
1798
1812
|
This operation does not modify record title or other record metadata.
|
|
1799
1813
|
`UpdateRecordRequest` rejects a top-level `title`; the Front uses the
|
|
@@ -1848,6 +1862,15 @@ paths:
|
|
|
1848
1862
|
examples:
|
|
1849
1863
|
update:
|
|
1850
1864
|
value: {eln: Example Notebook, uid: REC-EXAMPLE-001, modify: [{path: [Notes], data: Sanitized update}]}
|
|
1865
|
+
add_existing_cloud_attachment_field:
|
|
1866
|
+
value:
|
|
1867
|
+
eln: Example Notebook
|
|
1868
|
+
uid: REC-EXAMPLE-001
|
|
1869
|
+
add:
|
|
1870
|
+
- module: Attachments
|
|
1871
|
+
name: Cloud file
|
|
1872
|
+
type: file
|
|
1873
|
+
data: ["#hash{evidence.zip, 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef}"]
|
|
1851
1874
|
add_preuploaded_attachment_field:
|
|
1852
1875
|
value:
|
|
1853
1876
|
eln: Example Notebook
|
|
@@ -1951,6 +1974,12 @@ paths:
|
|
|
1951
1974
|
value: {code: 0, msg: Collaboration data update completed, errcode: 0, errmsg: Collaboration data update completed}
|
|
1952
1975
|
error:
|
|
1953
1976
|
value: {code: 4, msg: Multiple errors, errcode: 4, errmsg: Multiple errors, errs: [Invalid update path]}
|
|
1977
|
+
inaccessible_hash:
|
|
1978
|
+
value:
|
|
1979
|
+
code: 2
|
|
1980
|
+
msg: The referenced hash 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef does not exist
|
|
1981
|
+
errcode: 2
|
|
1982
|
+
errmsg: The referenced hash 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef does not exist
|
|
1954
1983
|
/eln_api/delete:
|
|
1955
1984
|
post:
|
|
1956
1985
|
operationId: deleteRecordsByUid
|
|
@@ -1993,7 +2022,10 @@ paths:
|
|
|
1993
2022
|
contracted abort, status, expiration, or idempotent retry. A hash
|
|
1994
2023
|
mismatch in this one-fragment flow rolls back the temporary row and
|
|
1995
2024
|
new bytes. Errors after import/update writing starts still do not prove
|
|
1996
|
-
whether the stage was consumed.
|
|
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.
|
|
2028
|
+
Raw identifiers identify the staging row;
|
|
1997
2029
|
their filesystem components are sanitized and can alias (PVD-051).
|
|
1998
2030
|
x-known-provider-issues: [PVD-014, PVD-051]
|
|
1999
2031
|
security: [{bearerAuth: []}]
|
|
@@ -3703,11 +3735,18 @@ paths:
|
|
|
3703
3735
|
successful response's `offset`. Omitting it defaults to zero and
|
|
3704
3736
|
restarts incomplete bytes rather than appending. The final fragment
|
|
3705
3737
|
returns the combined SHA-256, size, source filename, temporary row id,
|
|
3706
|
-
and legacy preview query.
|
|
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.
|
|
3707
3744
|
|
|
3708
3745
|
`abort=1` is the compensating cleanup operation. It requires the same
|
|
3709
|
-
`uid` and `tempid
|
|
3710
|
-
|
|
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
|
|
3711
3750
|
states return the standard HTTP 200 code-2 error envelope ("upload
|
|
3712
3751
|
cancelled"); that response does not prove completed staging was deleted.
|
|
3713
3752
|
Complete uploads must be consumed by their intended finalizer; this
|
|
@@ -7450,7 +7489,10 @@ components:
|
|
|
7450
7489
|
file:
|
|
7451
7490
|
type: string
|
|
7452
7491
|
format: binary
|
|
7453
|
-
description:
|
|
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.
|
|
7454
7496
|
allOf:
|
|
7455
7497
|
- if:
|
|
7456
7498
|
required: [abort]
|
|
@@ -5,18 +5,20 @@ See the [README](../README.md) for installation and quick start and [CONTEXT.md]
|
|
|
5
5
|
|
|
6
6
|
## Contract
|
|
7
7
|
|
|
8
|
-
The SDK is pinned to the immutable `matelab-spec v2026.
|
|
8
|
+
The SDK is pinned to the immutable `matelab-spec v2026.11.1` Contract Release. The sole pin,
|
|
9
9
|
[contracts/matelab-integration-v1.lock.json](../contracts/matelab-integration-v1.lock.json), records peeled commit
|
|
10
|
-
`
|
|
11
|
-
and SHA-256 `
|
|
12
|
-
`
|
|
10
|
+
`402a158052995850e0b7fad5ed5399c8a2eed294`, source `openapi/matelab-integration-v1.openapi.yaml`, the local snapshot path,
|
|
11
|
+
and SHA-256 `f9d3bd0baa4e5e70fe13870c21d9be70e40e2686831a0ffcc4632b39f83a3257`. The annotated tag object is
|
|
12
|
+
`18dbfd99f36ee7fc8cd620a251c7973d86e8dedd`.
|
|
13
13
|
|
|
14
|
-
The Contract uses Provider source evidence from `
|
|
14
|
+
The Contract uses Provider source evidence from `00f17b6ce0906bb78b0a3e9bb8c50abee90ca3d0`; v2026.11.1 validation ran
|
|
15
15
|
against an isolated test instance and does not establish production behavior. The Front Consumer evidence
|
|
16
|
-
`
|
|
16
|
+
`55bc8d95d9d4ef02841621f0b793d273cea66137` informs cross-Consumer capability review but does not define the SDK interface.
|
|
17
17
|
|
|
18
18
|
This snapshot covers the Workerman runtime with the `localhost` data-server branch for ELN records and attachments;
|
|
19
19
|
remote data-server forwarding is outside its verified callable scope (PVD-049).
|
|
20
|
+
The SDK does not add the optional browser `Version` header. Integrators should also omit it from injected HTTP clients:
|
|
21
|
+
an outdated value triggers a string `reload` envelope outside the integration response schemas.
|
|
20
22
|
|
|
21
23
|
## Design
|
|
22
24
|
|
|
@@ -191,14 +193,15 @@ record_ids = await client.records.import_dataset(
|
|
|
191
193
|
```
|
|
192
194
|
|
|
193
195
|
`RecordImportItem.data` uses the recursive `RecordImportValue` type: JSON-compatible scalars and nested mappings,
|
|
194
|
-
lists, or tuples may contain a `StagedAttachment` wherever the template expects a file. Arbitrary Python
|
|
196
|
+
lists, or tuples may contain a `StagedAttachment` or `CloudFile` wherever the template expects a file. Arbitrary Python
|
|
195
197
|
objects are outside the interface and are also rejected at runtime before a Provider request.
|
|
196
198
|
|
|
197
199
|
`content` accepts `bytes`, a synchronous `IO[bytes]`, or an `AsyncIterable[bytes]`. Callers do not supply a size,
|
|
198
200
|
checksum, or multipart content type. The SDK calculates the SHA-256 itself; streams are fully consumed into a
|
|
199
201
|
`SpooledTemporaryFile`, rewound, and only then sent to the Provider. Temporary-file write, seek, read, and close
|
|
200
202
|
operations run outside the event-loop thread, including after the spool rolls to disk. The Contract fixes attachment
|
|
201
|
-
parts to `application/octet-stream`.
|
|
203
|
+
parts to `application/octet-stream`. Temporary record-attachment bytes retain the source filename's final extension,
|
|
204
|
+
stripped to ASCII letters and digits with case preserved; the response retains the original source filename.
|
|
202
205
|
|
|
203
206
|
An exception raised by an async source is propagated unchanged after the SDK closes its temporary file. The SDK reads
|
|
204
207
|
streams through end-of-file and imposes no upload-size policy; callers remain responsible for limits such as an HTTP
|
|
@@ -218,6 +221,33 @@ The Provider supplies no staging status, abort, TTL, atomicity, idempotency, or
|
|
|
218
221
|
persist handles must durably claim their own `uploaded -> finalizing/indeterminate` transition before calling a
|
|
219
222
|
finalizer.
|
|
220
223
|
|
|
224
|
+
### Existing cloud-drive attachments
|
|
225
|
+
|
|
226
|
+
Use a `CloudFile` returned by `cloud_drive.list()` directly in template-shaped import data or in a new form file field:
|
|
227
|
+
|
|
228
|
+
```python
|
|
229
|
+
from matelab import RecordFormAttachmentFieldAddition, RecordImportItem
|
|
230
|
+
|
|
231
|
+
cloud_file = (await client.cloud_drive.list()).files[0]
|
|
232
|
+
item = RecordImportItem(
|
|
233
|
+
record_uid="REC-CLOUD-001", title="Cloud attachment", data={"Attachments": {"File": [cloud_file]}}
|
|
234
|
+
)
|
|
235
|
+
await client.records.import_dataset(notebook=notebook, template_title="Example Template", items=(item,))
|
|
236
|
+
await client.records.update(
|
|
237
|
+
source, (RecordFormAttachmentFieldAddition(module="Attachments", name="Cloud file", attachment=cloud_file),)
|
|
238
|
+
)
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
The SDK encodes the reference; the Provider retains the source cloud file. The same `CloudFile` may appear in
|
|
242
|
+
multiple records or requests; it does not enter the SDK's one-shot staging claims. Imports containing any
|
|
243
|
+
`StagedAttachment` retain the single-record and single-use restrictions. Cloud-file form additions may be combined
|
|
244
|
+
with other non-conflicting changes; other attachment update intents still require `StagedAttachment`.
|
|
245
|
+
|
|
246
|
+
The Provider checks access for the selected notebook owner's userid: personal-root ownership or permission on the
|
|
247
|
+
file's shared root is required; an existing file quote alone grants no access. Inaccessible hashes are rejected
|
|
248
|
+
before record content is written. Avoid overlapping reuse with a pending upload of the same bytes: Provider hash
|
|
249
|
+
lookup can select and consume that upload and use its filename, while retaining the source cloud file.
|
|
250
|
+
|
|
221
251
|
### Attachment-bearing record update boundary
|
|
222
252
|
|
|
223
253
|
`records.update(source, changes, ...)` accepts one non-empty sequence of `RecordChange` intents. Value updates and
|
|
@@ -253,7 +283,8 @@ await client.records.update(
|
|
|
253
283
|
)
|
|
254
284
|
```
|
|
255
285
|
|
|
256
|
-
|
|
286
|
+
With `StagedAttachment`, these three operations use the Provider's staged-name finalizer and must be the only mutation
|
|
287
|
+
in their update request.
|
|
257
288
|
`RecordTableFileCellSet` requires an existing file column and a cell whose immediate canonical value is exactly
|
|
258
289
|
`null`. `RecordTableRowAttachmentAppend` addresses the new row by the row count from the SDK's immediate read and
|
|
259
290
|
supports one file column. A locally rejected target does not spend the handle; once transport starts, an ambiguous
|
|
@@ -317,9 +348,10 @@ preserves that omission in `Record.modules` and does not invent a timestamp; nul
|
|
|
317
348
|
attachment shapes remain response errors. This exception can be removed after the Provider canonicalizes current and
|
|
318
349
|
historical record content.
|
|
319
350
|
|
|
320
|
-
|
|
321
|
-
finalization request
|
|
322
|
-
must not be called while a concurrent editor is known to be active. The Provider offers
|
|
351
|
+
Staged attachment inputs reject handles from another notebook or authenticated uploader and duplicate use within one
|
|
352
|
+
finalization request. Raw Provider attachment strings remain outside the public interface. Row/index-based operations
|
|
353
|
+
use an immediate SDK read and must not be called while a concurrent editor is known to be active. The Provider offers
|
|
354
|
+
no expected hash or revision,
|
|
323
355
|
so the SDK does not present its read-before-write checks as concurrency control.
|
|
324
356
|
|
|
325
357
|
File-bearing structure changes do not consume staging:
|
|
@@ -353,10 +385,9 @@ This requests only a canonical record-content mutation. A successful response ac
|
|
|
353
385
|
it does not prove attachment-quote cleanup or byte deletion. Collaboration-pending is reported as
|
|
354
386
|
`"pending"`, not persisted.
|
|
355
387
|
|
|
356
|
-
`RecordRichTextUpdate` accepts
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
used for the other finalizer through the same client.
|
|
388
|
+
`RecordRichTextUpdate` accepts `StagedAttachment`. Imports containing a staged attachment are limited to one record;
|
|
389
|
+
callers must use a fresh staged attachment for each request. Once an import or update request using a staged attachment
|
|
390
|
+
starts, that handle cannot be retried or used for the other finalizer through the same client.
|
|
360
391
|
|
|
361
392
|
## Operation coverage
|
|
362
393
|
|
|
@@ -692,19 +723,25 @@ assert isinstance(staged, StagedFile)
|
|
|
692
723
|
The SDK derives `fragment_size` and computes the complete SHA-256 for a single `bytes` fragment. File-like content
|
|
693
724
|
must declare its fragment size. For multiple fragments, pass the returned `StagedUploadSession` into the next call and
|
|
694
725
|
supply `complete_sha256` on the final call. `next_offset` is the Provider-acknowledged byte offset, checked against the
|
|
695
|
-
declared fragment size and sent with the next fragment.
|
|
696
|
-
|
|
726
|
+
declared fragment size and sent with the next fragment. Repeat the filename's final extension across fragments:
|
|
727
|
+
the Provider removes characters outside `[A-Za-z0-9]` and preserves case, so `paper.P-d_f` and `renamed.Pdf` select
|
|
728
|
+
the same bytes. Changing that sanitized extension cannot resume the upload and returns code 2 when no matching
|
|
729
|
+
file exists at the requested offset. The SDK sends each filename unchanged, checks the returned filename and total
|
|
730
|
+
size, and compares hashes when a local expected hash is available. A final result contains the Provider hash, temporary row
|
|
697
731
|
identity and direct `binding`; it no longer embeds resumable session state. The temporary Provider URL and upload-session
|
|
698
732
|
identity needed by internal binding and cleanup remain hidden. Callers do not need to
|
|
699
733
|
splice either into content. The result does not claim that a later literature/cloud operation checks the uploader or
|
|
700
734
|
consumes the file exactly once. `uploads.abort()` accepts either an active `StagedUploadSession` or a completed
|
|
701
|
-
`StagedFile` and returns `None` for the Provider's code-2 cancellation signal.
|
|
702
|
-
|
|
735
|
+
`StagedFile` and returns `None` for the Provider's code-2 cancellation signal. Without a completed staging row, it
|
|
736
|
+
removes all incomplete extension variants, including extensionless bytes. A completed row prevents any cleanup for
|
|
737
|
+
that identity; completed staging must be consumed by its intended finalizer (PVD-052).
|
|
703
738
|
Binding UIDs allow up to 100 characters; upload-session IDs allow up to 45 (PVD-024). Use fresh identities: different
|
|
704
739
|
raw identifiers can alias after Provider filename sanitization (PVD-051). Workerman upload errors supply no usable
|
|
705
740
|
resume offset (PVD-050). After a failed non-final fragment, cancel incomplete bytes and restart with fresh identities.
|
|
706
741
|
An ambiguous final response can leave completed staging that `abort()` cannot delete.
|
|
707
742
|
Staging mutations are never automatically retried.
|
|
743
|
+
After upgrading from the previous Provider's extensionless storage, restart old incomplete uploads from offset zero
|
|
744
|
+
or cancel them and use fresh session identities.
|
|
708
745
|
|
|
709
746
|
Literature identities distinguish the personal library from shared libraries:
|
|
710
747
|
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
[build-system]
|
|
2
|
-
requires = ["hatchling>=1.
|
|
2
|
+
requires = ["hatchling>=1.33.0"]
|
|
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.0a9"
|
|
8
8
|
description = "Reusable async Python client for the Matelab Integration Contract"
|
|
9
9
|
requires-python = ">=3.11"
|
|
10
10
|
keywords = ["matelab", "electronic-lab-notebook", "eln", "asyncio", "sdk"]
|
|
@@ -12,7 +12,7 @@ authors = [
|
|
|
12
12
|
{ name = "CMPDC, Institute of Physics, Chinese Academy of Sciences" },
|
|
13
13
|
]
|
|
14
14
|
dependencies = [
|
|
15
|
-
"httpx2>=2.
|
|
15
|
+
"httpx2>=2.16.0",
|
|
16
16
|
"jsonschema>=4.26.0",
|
|
17
17
|
"pydantic>=2.14.1",
|
|
18
18
|
"typing-extensions>=4.16.0",
|
|
@@ -39,12 +39,12 @@ classifiers = [
|
|
|
39
39
|
dev = [
|
|
40
40
|
"basedpyright>=1.40.2",
|
|
41
41
|
"nodejs-wheel-binaries==22.20.0",
|
|
42
|
-
"datamodel-code-generator>=0.
|
|
43
|
-
"hatchling>=1.
|
|
44
|
-
"pytest>=9.
|
|
45
|
-
"pytest-asyncio>=1.4.
|
|
42
|
+
"datamodel-code-generator>=0.87.0",
|
|
43
|
+
"hatchling>=1.33.0",
|
|
44
|
+
"pytest>=9.2.0",
|
|
45
|
+
"pytest-asyncio>=1.4.1",
|
|
46
46
|
"pyyaml>=6.0.3",
|
|
47
|
-
"ruff>=0.17.
|
|
47
|
+
"ruff>=0.17.3",
|
|
48
48
|
]
|
|
49
49
|
|
|
50
50
|
[tool.ruff]
|
|
@@ -65,6 +65,7 @@ reportIncompatibleUnannotatedOverride = "error"
|
|
|
65
65
|
markers = [
|
|
66
66
|
"provider: opt-in smoke tests against an explicitly acknowledged real Provider target",
|
|
67
67
|
]
|
|
68
|
+
asyncio_default_fixture_loop_scope = "function"
|
|
68
69
|
|
|
69
70
|
[tool.hatch.build.targets.wheel]
|
|
70
71
|
packages = ["src/matelab"]
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
#
|
|
3
3
|
# @generated by datamodel-codegen:
|
|
4
4
|
# filename: openapi.yaml
|
|
5
|
-
# version: 0.
|
|
5
|
+
# version: 0.87.0
|
|
6
6
|
|
|
7
7
|
from enum import StrEnum
|
|
8
8
|
from typing import Annotated, Any, Literal
|
|
@@ -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 = "2026.
|
|
14
|
+
OPENAPI_INFO_VERSION = "2026.11.1"
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
class ResponseEnvelope(WireModel):
|
|
@@ -2759,7 +2759,10 @@ class StageTemporaryFileRequest(WireModel):
|
|
|
2759
2759
|
"""
|
|
2760
2760
|
file: bytes | None = None
|
|
2761
2761
|
"""
|
|
2762
|
-
Required for upload branches and omitted for abort.
|
|
2762
|
+
Required for upload branches and omitted for abort. The multipart
|
|
2763
|
+
filename's sanitized final extension selects the temporary bytes;
|
|
2764
|
+
keep that extension unchanged throughout continuation.
|
|
2765
|
+
|
|
2763
2766
|
"""
|
|
2764
2767
|
|
|
2765
2768
|
|