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.
Files changed (68) hide show
  1. {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/CONTEXT.md +2 -2
  2. {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/PKG-INFO +2 -2
  3. {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/contracts/matelab-integration-v1.lock.json +3 -3
  4. {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/contracts/matelab-integration-v1.openapi.yaml +81 -39
  5. {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/docs/api.md +57 -20
  6. {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/docs/operation-coverage.yaml +1 -1
  7. {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/pyproject.toml +9 -8
  8. {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/src/matelab/_generated/models.py +6 -3
  9. {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/src/matelab/domains/records.py +49 -22
  10. {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/src/matelab/domains/uploads.py +2 -1
  11. {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/tests/conftest.py +13 -1
  12. {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/tests/test_record_lifecycle.py +101 -4
  13. {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/tests/test_record_mutations.py +143 -1
  14. {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/tests/test_uploads_and_literature.py +13 -5
  15. {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/tests/typing/consumer.py +16 -0
  16. {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/uv.lock +63 -50
  17. {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/.env.example +0 -0
  18. {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/.github/workflows/release.yml +0 -0
  19. {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/.gitignore +0 -0
  20. {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/AGENTS.md +0 -0
  21. {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/LICENSE +0 -0
  22. {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/NOTICE +0 -0
  23. {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/README.md +0 -0
  24. {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/docs/agent-architecture.md +0 -0
  25. {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/docs/agent-workflows.md +0 -0
  26. {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/docs/development.md +0 -0
  27. {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/scripts/check_installed_package.py +0 -0
  28. {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/scripts/check_release.py +0 -0
  29. {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/scripts/generate_models.py +0 -0
  30. {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/src/matelab/__init__.py +0 -0
  31. {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/src/matelab/_generated/__init__.py +0 -0
  32. {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/src/matelab/_hashes.py +0 -0
  33. {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/src/matelab/_transport.py +0 -0
  34. {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/src/matelab/_wire_validation.py +0 -0
  35. {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/src/matelab/client.py +0 -0
  36. {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/src/matelab/domains/__init__.py +0 -0
  37. {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/src/matelab/domains/_attachment_content.py +0 -0
  38. {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/src/matelab/domains/cloud_drive.py +0 -0
  39. {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/src/matelab/domains/groups.py +0 -0
  40. {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/src/matelab/domains/literature.py +0 -0
  41. {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/src/matelab/domains/notebooks.py +0 -0
  42. {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/src/matelab/domains/templates.py +0 -0
  43. {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/src/matelab/domains/users.py +0 -0
  44. {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/src/matelab/errors.py +0 -0
  45. {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/src/matelab/py.typed +0 -0
  46. {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/src/matelab/session.py +0 -0
  47. {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/src/matelab/streaming.py +0 -0
  48. {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/tests/provider/test_provider_smoke.py +0 -0
  49. {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/tests/test_async_attachment_upload.py +0 -0
  50. {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/tests/test_client.py +0 -0
  51. {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/tests/test_cloud_drive.py +0 -0
  52. {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/tests/test_comments_and_downloads.py +0 -0
  53. {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/tests/test_consumer_smoke.py +0 -0
  54. {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/tests/test_errors.py +0 -0
  55. {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/tests/test_extended_records.py +0 -0
  56. {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/tests/test_generation.py +0 -0
  57. {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/tests/test_groups.py +0 -0
  58. {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/tests/test_notebook_lifecycle.py +0 -0
  59. {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/tests/test_notebooks.py +0 -0
  60. {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/tests/test_operation_coverage.py +0 -0
  61. {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/tests/test_records.py +0 -0
  62. {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/tests/test_release.py +0 -0
  63. {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/tests/test_streaming.py +0 -0
  64. {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/tests/test_template_lifecycle.py +0 -0
  65. {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/tests/test_templates.py +0 -0
  66. {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/tests/test_transport.py +0 -0
  67. {matelab_python_sdk-0.2.0a7 → matelab_python_sdk-0.2.0a9}/tests/test_users.py +0 -0
  68. {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.0a7
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.15.1
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
@@ -2,12 +2,12 @@
2
2
  "schema_version": 1,
3
3
  "contract_line": "integration-v1",
4
4
  "release": {
5
- "tag": "v2026.10.3",
6
- "commit": "c668cff75b7fcedbee048a8f1c8792162bf4eb77"
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": "757017be241f1014f3b5966c2a0f89a4ef578a700ebd626c51d02a8ff92a6b2a"
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.10.3"
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: b70f590571b860f043365c9f2d6f95818d595862
21
+ commit: 00f17b6ce0906bb78b0a3e9bb8c50abee90ca3d0
17
22
  verificationRuntime:
18
- baseUrl: http://127.0.0.1:18080/api
23
+ baseUrl: https://matelab.iphy.ac.cn/eln-test/api
19
24
  container: matelab-test-server-1
20
25
  coreSourceComparison: sha256-exact
21
- coreSourceFileCount: 157
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: b70f590571b860f043365c9f2d6f95818d595862
60
- previousReleaseEvidenceCommit: 3f39d47b7ad43bed618b029a7d9cc51fab852eb6
61
- lastProviderVerifiedCommit: b70f590571b860f043365c9f2d6f95818d595862
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: stageTemporaryFile, change: byte-offset-continuation-and-45-character-session-limit}
70
- - {operationId: stageTemporaryFile, change: abort-only-cleans-incomplete-bytes-and-workerman-errors-have-no-resume-anchor}
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: c6a2fef86ef215256556d340b964f56dded7d3ce
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. In particular,
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. Raw identifiers identify the staging row;
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`, omits `file`, and removes incomplete bytes only.
3710
- It preserves an already completed staging row and its bytes. Both
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: Required for upload branches and omitted for abort.
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.10.3` Contract Release. The sole pin,
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
- `c668cff75b7fcedbee048a8f1c8792162bf4eb77`, source `openapi/matelab-integration-v1.openapi.yaml`, the local snapshot path,
11
- and SHA-256 `757017be241f1014f3b5966c2a0f89a4ef578a700ebd626c51d02a8ff92a6b2a`. The annotated tag object is
12
- `b1d3ff039c576032f69ec7116347af1ba441ba5f`.
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 `b70f590571b860f043365c9f2d6f95818d595862`; v2026.10.3 validation ran
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
- `c6a2fef86ef215256556d340b964f56dded7d3ce` informs cross-Consumer capability review but does not define the SDK interface.
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
- These three operations use the Provider's staged-name finalizer and must be the only mutation in their update request.
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
- All record attachment intents reject handles from another notebook or authenticated uploader, duplicate use within one
321
- finalization request, and raw Provider attachment references. Row/index-based operations use an immediate SDK read and
322
- must not be called while a concurrent editor is known to be active. The Provider offers no expected hash or revision,
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 the same `StagedAttachment` as import and other update intents. Attachment-bearing
357
- multi-record import is forbidden, so callers must split it into single-record imports with a fresh staged attachment
358
- for each request. Once an import or update request using a staged attachment starts, that handle cannot be retried or
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. The SDK verifies the returned filename and total size, and
696
- compares hashes when a local expected hash is available. A final result contains the Provider hash, temporary row
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. It removes incomplete bytes only;
702
- completed staging rows and bytes remain and must be consumed by their intended finalizer (PVD-052).
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
 
@@ -3,7 +3,7 @@
3
3
  schema_version: 1
4
4
  contract:
5
5
  line: integration-v1
6
- release: v2026.10.3
6
+ release: v2026.11.1
7
7
  operation_count: 71
8
8
 
9
9
  operations:
@@ -1,10 +1,10 @@
1
1
  [build-system]
2
- requires = ["hatchling>=1.32.4"]
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.0a7"
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.1",
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.86.0",
43
- "hatchling>=1.32.4",
44
- "pytest>=9.1.1",
45
- "pytest-asyncio>=1.4.0",
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.1",
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.86.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.10.3"
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