matelab-python-sdk 0.1.0a19__tar.gz → 0.1.0a20__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {matelab_python_sdk-0.1.0a19 → matelab_python_sdk-0.1.0a20}/CHANGELOG.md +12 -0
- {matelab_python_sdk-0.1.0a19 → matelab_python_sdk-0.1.0a20}/PKG-INFO +17 -12
- {matelab_python_sdk-0.1.0a19 → matelab_python_sdk-0.1.0a20}/README.md +16 -11
- {matelab_python_sdk-0.1.0a19 → matelab_python_sdk-0.1.0a20}/contracts/matelab-integration-v1.lock.json +3 -3
- {matelab_python_sdk-0.1.0a19 → matelab_python_sdk-0.1.0a20}/contracts/matelab-integration-v1.openapi.yaml +43 -4
- {matelab_python_sdk-0.1.0a19 → matelab_python_sdk-0.1.0a20}/docs/operation-coverage.yaml +2 -2
- {matelab_python_sdk-0.1.0a19 → matelab_python_sdk-0.1.0a20}/pyproject.toml +1 -1
- {matelab_python_sdk-0.1.0a19 → matelab_python_sdk-0.1.0a20}/src/matelab/_generated/models.py +132 -10
- {matelab_python_sdk-0.1.0a19 → matelab_python_sdk-0.1.0a20}/src/matelab/domains/records.py +1 -7
- {matelab_python_sdk-0.1.0a19 → matelab_python_sdk-0.1.0a20}/tests/test_generation.py +124 -3
- {matelab_python_sdk-0.1.0a19 → matelab_python_sdk-0.1.0a20}/tests/test_record_mutations.py +16 -8
- {matelab_python_sdk-0.1.0a19 → matelab_python_sdk-0.1.0a20}/tests/test_records.py +103 -12
- {matelab_python_sdk-0.1.0a19 → matelab_python_sdk-0.1.0a20}/uv.lock +1 -1
- {matelab_python_sdk-0.1.0a19 → matelab_python_sdk-0.1.0a20}/.env.example +0 -0
- {matelab_python_sdk-0.1.0a19 → matelab_python_sdk-0.1.0a20}/.github/workflows/release.yml +0 -0
- {matelab_python_sdk-0.1.0a19 → matelab_python_sdk-0.1.0a20}/.gitignore +0 -0
- {matelab_python_sdk-0.1.0a19 → matelab_python_sdk-0.1.0a20}/AGENTS.md +0 -0
- {matelab_python_sdk-0.1.0a19 → matelab_python_sdk-0.1.0a20}/CONTEXT.md +0 -0
- {matelab_python_sdk-0.1.0a19 → matelab_python_sdk-0.1.0a20}/LICENSE +0 -0
- {matelab_python_sdk-0.1.0a19 → matelab_python_sdk-0.1.0a20}/NOTICE +0 -0
- {matelab_python_sdk-0.1.0a19 → matelab_python_sdk-0.1.0a20}/scripts/check_installed_package.py +0 -0
- {matelab_python_sdk-0.1.0a19 → matelab_python_sdk-0.1.0a20}/scripts/check_release.py +0 -0
- {matelab_python_sdk-0.1.0a19 → matelab_python_sdk-0.1.0a20}/scripts/generate_models.py +0 -0
- {matelab_python_sdk-0.1.0a19 → matelab_python_sdk-0.1.0a20}/src/matelab/__init__.py +0 -0
- {matelab_python_sdk-0.1.0a19 → matelab_python_sdk-0.1.0a20}/src/matelab/_generated/__init__.py +0 -0
- {matelab_python_sdk-0.1.0a19 → matelab_python_sdk-0.1.0a20}/src/matelab/_hashes.py +0 -0
- {matelab_python_sdk-0.1.0a19 → matelab_python_sdk-0.1.0a20}/src/matelab/_transport.py +0 -0
- {matelab_python_sdk-0.1.0a19 → matelab_python_sdk-0.1.0a20}/src/matelab/_wire_validation.py +0 -0
- {matelab_python_sdk-0.1.0a19 → matelab_python_sdk-0.1.0a20}/src/matelab/client.py +0 -0
- {matelab_python_sdk-0.1.0a19 → matelab_python_sdk-0.1.0a20}/src/matelab/domains/__init__.py +0 -0
- {matelab_python_sdk-0.1.0a19 → matelab_python_sdk-0.1.0a20}/src/matelab/domains/_attachment_content.py +0 -0
- {matelab_python_sdk-0.1.0a19 → matelab_python_sdk-0.1.0a20}/src/matelab/domains/cloud_drive.py +0 -0
- {matelab_python_sdk-0.1.0a19 → matelab_python_sdk-0.1.0a20}/src/matelab/domains/groups.py +0 -0
- {matelab_python_sdk-0.1.0a19 → matelab_python_sdk-0.1.0a20}/src/matelab/domains/literature.py +0 -0
- {matelab_python_sdk-0.1.0a19 → matelab_python_sdk-0.1.0a20}/src/matelab/domains/notebooks.py +0 -0
- {matelab_python_sdk-0.1.0a19 → matelab_python_sdk-0.1.0a20}/src/matelab/domains/templates.py +0 -0
- {matelab_python_sdk-0.1.0a19 → matelab_python_sdk-0.1.0a20}/src/matelab/domains/uploads.py +0 -0
- {matelab_python_sdk-0.1.0a19 → matelab_python_sdk-0.1.0a20}/src/matelab/domains/users.py +0 -0
- {matelab_python_sdk-0.1.0a19 → matelab_python_sdk-0.1.0a20}/src/matelab/errors.py +0 -0
- {matelab_python_sdk-0.1.0a19 → matelab_python_sdk-0.1.0a20}/src/matelab/py.typed +0 -0
- {matelab_python_sdk-0.1.0a19 → matelab_python_sdk-0.1.0a20}/src/matelab/session.py +0 -0
- {matelab_python_sdk-0.1.0a19 → matelab_python_sdk-0.1.0a20}/src/matelab/streaming.py +0 -0
- {matelab_python_sdk-0.1.0a19 → matelab_python_sdk-0.1.0a20}/tests/conftest.py +0 -0
- {matelab_python_sdk-0.1.0a19 → matelab_python_sdk-0.1.0a20}/tests/provider/test_provider_smoke.py +0 -0
- {matelab_python_sdk-0.1.0a19 → matelab_python_sdk-0.1.0a20}/tests/test_async_attachment_upload.py +0 -0
- {matelab_python_sdk-0.1.0a19 → matelab_python_sdk-0.1.0a20}/tests/test_client.py +0 -0
- {matelab_python_sdk-0.1.0a19 → matelab_python_sdk-0.1.0a20}/tests/test_cloud_drive.py +0 -0
- {matelab_python_sdk-0.1.0a19 → matelab_python_sdk-0.1.0a20}/tests/test_comments_and_downloads.py +0 -0
- {matelab_python_sdk-0.1.0a19 → matelab_python_sdk-0.1.0a20}/tests/test_consumer_smoke.py +0 -0
- {matelab_python_sdk-0.1.0a19 → matelab_python_sdk-0.1.0a20}/tests/test_errors.py +0 -0
- {matelab_python_sdk-0.1.0a19 → matelab_python_sdk-0.1.0a20}/tests/test_extended_records.py +0 -0
- {matelab_python_sdk-0.1.0a19 → matelab_python_sdk-0.1.0a20}/tests/test_groups.py +0 -0
- {matelab_python_sdk-0.1.0a19 → matelab_python_sdk-0.1.0a20}/tests/test_notebook_lifecycle.py +0 -0
- {matelab_python_sdk-0.1.0a19 → matelab_python_sdk-0.1.0a20}/tests/test_notebooks.py +0 -0
- {matelab_python_sdk-0.1.0a19 → matelab_python_sdk-0.1.0a20}/tests/test_operation_coverage.py +0 -0
- {matelab_python_sdk-0.1.0a19 → matelab_python_sdk-0.1.0a20}/tests/test_record_lifecycle.py +0 -0
- {matelab_python_sdk-0.1.0a19 → matelab_python_sdk-0.1.0a20}/tests/test_release.py +0 -0
- {matelab_python_sdk-0.1.0a19 → matelab_python_sdk-0.1.0a20}/tests/test_streaming.py +0 -0
- {matelab_python_sdk-0.1.0a19 → matelab_python_sdk-0.1.0a20}/tests/test_template_lifecycle.py +0 -0
- {matelab_python_sdk-0.1.0a19 → matelab_python_sdk-0.1.0a20}/tests/test_templates.py +0 -0
- {matelab_python_sdk-0.1.0a19 → matelab_python_sdk-0.1.0a20}/tests/test_transport.py +0 -0
- {matelab_python_sdk-0.1.0a19 → matelab_python_sdk-0.1.0a20}/tests/test_uploads_and_literature.py +0 -0
- {matelab_python_sdk-0.1.0a19 → matelab_python_sdk-0.1.0a20}/tests/test_users.py +0 -0
- {matelab_python_sdk-0.1.0a19 → matelab_python_sdk-0.1.0a20}/tests/typing/consumer.py +0 -0
- {matelab_python_sdk-0.1.0a19 → matelab_python_sdk-0.1.0a20}/tests/typing/pyproject.toml +0 -0
|
@@ -3,6 +3,18 @@
|
|
|
3
3
|
This file records release-to-release changes and migration impact. It is not the authority for the current interface;
|
|
4
4
|
see [README.md](README.md) for current behavior and [AGENTS.md](AGENTS.md) for maintenance policy.
|
|
5
5
|
|
|
6
|
+
## 0.1.0a20 - 2026-08-24
|
|
7
|
+
|
|
8
|
+
- Adopts immutable `matelab-spec v0.4.3` at commit `7965ac9803505ae35dc5ce36f52afc3c2c133c45` and OpenAPI
|
|
9
|
+
SHA-256 `6576812dd13f92ad58131f4edb8dc919d1b1fe0f19a695c2d25fbe6d13a8445b`, aligned with Provider evidence
|
|
10
|
+
`a3e6b961800f6b1ab666f0d3f6cd4c64c2a3ce27`.
|
|
11
|
+
- Breaking: tightens record readback to one canonical attachment object or `null` per table file cell, removing the
|
|
12
|
+
obsolete attachment-array compatibility path. Template decoding now accepts nullable JSON-code data and preserves
|
|
13
|
+
empty-string number/boolean cells alongside their scalar and null forms.
|
|
14
|
+
- Records PVD-046 while keeping `templates.save_content()` deliberately narrower than the Provider: the request remains
|
|
15
|
+
a canonical module list, and every table file cell remains one canonical attachment object or `null`, never an
|
|
16
|
+
arbitrary or multi-attachment array.
|
|
17
|
+
|
|
6
18
|
## 0.1.0a19 - 2026-08-23
|
|
7
19
|
|
|
8
20
|
- Re-adopts the force-rewritten `matelab-spec v0.4.2` at commit
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: matelab-python-sdk
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.0a20
|
|
4
4
|
Summary: Reusable async Python client for the Matelab Integration Contract
|
|
5
5
|
Author-email: 朱天念 <zhutiannian@gmail.com>
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -35,10 +35,10 @@ This README describes the current SDK interface, integration semantics, examples
|
|
|
35
35
|
[AGENTS.md](AGENTS.md) for durable maintenance rules, and `docs/operation-coverage.yaml` for the machine-readable
|
|
36
36
|
operation inventory.
|
|
37
37
|
|
|
38
|
-
The current alpha is `0.1.
|
|
38
|
+
The current alpha is `0.1.0a20`. `[project].version` in `pyproject.toml` is the sole SDK version source;
|
|
39
39
|
`uv.lock` only mirrors that source.
|
|
40
40
|
|
|
41
|
-
The SDK is pinned to the
|
|
41
|
+
The SDK is pinned to the immutable `matelab-spec v0.4.3` Contract Release. The sole release pin is
|
|
42
42
|
`contracts/matelab-integration-v1.lock.json`, which records
|
|
43
43
|
the source tag, commit, OpenAPI path, local snapshot path, and SHA-256.
|
|
44
44
|
|
|
@@ -64,7 +64,7 @@ To test the same artifact a downstream Consumer will install, build and install
|
|
|
64
64
|
|
|
65
65
|
```bash
|
|
66
66
|
uv build --no-build-isolation --out-dir dist/release
|
|
67
|
-
python -m pip install dist/release/matelab_python_sdk-0.1.
|
|
67
|
+
python -m pip install dist/release/matelab_python_sdk-0.1.0a20-py3-none-any.whl
|
|
68
68
|
```
|
|
69
69
|
|
|
70
70
|
Do not infer Provider compatibility from the SDK version alone. A release is also bound to the Contract
|
|
@@ -121,7 +121,7 @@ success code it does not allow raises `MatelabProtocolError` rather than assigni
|
|
|
121
121
|
`MatelabTransportError.status_code` remains available for HTTP failures. Provider response bodies, top-level `msg`,
|
|
122
122
|
legacy `errmsg`, opaque `errs`, uncontracted debug fields, and request payloads are not attached to exceptions because
|
|
123
123
|
Provider-authored diagnostics may contain stack traces, credentials, or submitted values.
|
|
124
|
-
The pinned v0.4.
|
|
124
|
+
The pinned v0.4.3 Contract also declares optional code-4 `errs`, but PVD-041 and PCG-015 record opaque array/object
|
|
125
125
|
containers and insufficient evidence for requiredness, cardinality, or exclusion from other codes. The SDK
|
|
126
126
|
therefore keeps those diagnostics wire-only and does not expose `errs` as stable public metadata. A typed detail
|
|
127
127
|
interface remains blocked until a future Contract release can define one from stronger Provider evidence. `retryable`
|
|
@@ -348,7 +348,9 @@ await client.records.update(
|
|
|
348
348
|
|
|
349
349
|
For a files occurrence, `caption=None` preserves the observed string caption (an observed null caption normalizes to
|
|
350
350
|
the required empty string). Table replacement requires exactly one current attachment in the selected cell and does
|
|
351
|
-
not accept a caption.
|
|
351
|
+
not accept a caption. Record reads likewise accept only one canonical attachment object or `null` in a table file cell;
|
|
352
|
+
an attachment array violates the pinned response contract. Both replacement forms require the replacement hash to
|
|
353
|
+
differ from the current occurrence.
|
|
352
354
|
|
|
353
355
|
All record attachment intents reject handles from another notebook or authenticated uploader, duplicate use within one
|
|
354
356
|
finalization request, and raw Provider attachment references. Row/index-based operations use an immediate SDK read and
|
|
@@ -500,9 +502,12 @@ await client.templates.save_content(template, modules)
|
|
|
500
502
|
`TemplateModule.data_present` distinguishes a missing `data` property from an explicit null value, while
|
|
501
503
|
`attributes` prevents additive canonical properties from being dropped during a read/save cycle. The SDK does not
|
|
502
504
|
synthesize frontend editor UIDs, layout widths, rows, folders, option encoding, or a fixed set of display module
|
|
503
|
-
types.
|
|
504
|
-
|
|
505
|
-
|
|
505
|
+
types. A JSON-language code module with style metadata may return its JSON string or explicit `null`; number and boolean
|
|
506
|
+
table cells preserve the Provider's empty string as distinct from scalar and `null` values. The Provider accepts some
|
|
507
|
+
non-canonical module roots and arbitrary table file arrays, but the SDK does not expose that behavior: saved content
|
|
508
|
+
must remain a canonical module list, and a table file cell must be one canonical attachment object or `null` (PVD-046).
|
|
509
|
+
The Provider supports only whole-content replacement and has no atomic patch or compare-and-swap operation (PCG-007),
|
|
510
|
+
so a read-modify-save sequence can overwrite a concurrent change.
|
|
506
511
|
|
|
507
512
|
The market result retains the Provider's `total_count` and derives `has_more` without echoing the caller's page
|
|
508
513
|
arguments; it does not claim a stable order or continuation token. Template reads remain experimental
|
|
@@ -912,6 +917,6 @@ uv run python scripts/check_release.py dist/release/*.whl dist/release/*.tar.gz
|
|
|
912
917
|
```
|
|
913
918
|
|
|
914
919
|
Rebuilding the same commit with the same locked environment and `SOURCE_DATE_EPOCH` must produce
|
|
915
|
-
byte-identical wheel and source distribution hashes. The release is bound to
|
|
916
|
-
|
|
917
|
-
`
|
|
920
|
+
byte-identical wheel and source distribution hashes. The release is bound to immutable `matelab-spec v0.4.3`, commit
|
|
921
|
+
`7965ac9803505ae35dc5ce36f52afc3c2c133c45`, and OpenAPI SHA-256
|
|
922
|
+
`6576812dd13f92ad58131f4edb8dc919d1b1fe0f19a695c2d25fbe6d13a8445b`.
|
|
@@ -7,10 +7,10 @@ This README describes the current SDK interface, integration semantics, examples
|
|
|
7
7
|
[AGENTS.md](AGENTS.md) for durable maintenance rules, and `docs/operation-coverage.yaml` for the machine-readable
|
|
8
8
|
operation inventory.
|
|
9
9
|
|
|
10
|
-
The current alpha is `0.1.
|
|
10
|
+
The current alpha is `0.1.0a20`. `[project].version` in `pyproject.toml` is the sole SDK version source;
|
|
11
11
|
`uv.lock` only mirrors that source.
|
|
12
12
|
|
|
13
|
-
The SDK is pinned to the
|
|
13
|
+
The SDK is pinned to the immutable `matelab-spec v0.4.3` Contract Release. The sole release pin is
|
|
14
14
|
`contracts/matelab-integration-v1.lock.json`, which records
|
|
15
15
|
the source tag, commit, OpenAPI path, local snapshot path, and SHA-256.
|
|
16
16
|
|
|
@@ -36,7 +36,7 @@ To test the same artifact a downstream Consumer will install, build and install
|
|
|
36
36
|
|
|
37
37
|
```bash
|
|
38
38
|
uv build --no-build-isolation --out-dir dist/release
|
|
39
|
-
python -m pip install dist/release/matelab_python_sdk-0.1.
|
|
39
|
+
python -m pip install dist/release/matelab_python_sdk-0.1.0a20-py3-none-any.whl
|
|
40
40
|
```
|
|
41
41
|
|
|
42
42
|
Do not infer Provider compatibility from the SDK version alone. A release is also bound to the Contract
|
|
@@ -93,7 +93,7 @@ success code it does not allow raises `MatelabProtocolError` rather than assigni
|
|
|
93
93
|
`MatelabTransportError.status_code` remains available for HTTP failures. Provider response bodies, top-level `msg`,
|
|
94
94
|
legacy `errmsg`, opaque `errs`, uncontracted debug fields, and request payloads are not attached to exceptions because
|
|
95
95
|
Provider-authored diagnostics may contain stack traces, credentials, or submitted values.
|
|
96
|
-
The pinned v0.4.
|
|
96
|
+
The pinned v0.4.3 Contract also declares optional code-4 `errs`, but PVD-041 and PCG-015 record opaque array/object
|
|
97
97
|
containers and insufficient evidence for requiredness, cardinality, or exclusion from other codes. The SDK
|
|
98
98
|
therefore keeps those diagnostics wire-only and does not expose `errs` as stable public metadata. A typed detail
|
|
99
99
|
interface remains blocked until a future Contract release can define one from stronger Provider evidence. `retryable`
|
|
@@ -320,7 +320,9 @@ await client.records.update(
|
|
|
320
320
|
|
|
321
321
|
For a files occurrence, `caption=None` preserves the observed string caption (an observed null caption normalizes to
|
|
322
322
|
the required empty string). Table replacement requires exactly one current attachment in the selected cell and does
|
|
323
|
-
not accept a caption.
|
|
323
|
+
not accept a caption. Record reads likewise accept only one canonical attachment object or `null` in a table file cell;
|
|
324
|
+
an attachment array violates the pinned response contract. Both replacement forms require the replacement hash to
|
|
325
|
+
differ from the current occurrence.
|
|
324
326
|
|
|
325
327
|
All record attachment intents reject handles from another notebook or authenticated uploader, duplicate use within one
|
|
326
328
|
finalization request, and raw Provider attachment references. Row/index-based operations use an immediate SDK read and
|
|
@@ -472,9 +474,12 @@ await client.templates.save_content(template, modules)
|
|
|
472
474
|
`TemplateModule.data_present` distinguishes a missing `data` property from an explicit null value, while
|
|
473
475
|
`attributes` prevents additive canonical properties from being dropped during a read/save cycle. The SDK does not
|
|
474
476
|
synthesize frontend editor UIDs, layout widths, rows, folders, option encoding, or a fixed set of display module
|
|
475
|
-
types.
|
|
476
|
-
|
|
477
|
-
|
|
477
|
+
types. A JSON-language code module with style metadata may return its JSON string or explicit `null`; number and boolean
|
|
478
|
+
table cells preserve the Provider's empty string as distinct from scalar and `null` values. The Provider accepts some
|
|
479
|
+
non-canonical module roots and arbitrary table file arrays, but the SDK does not expose that behavior: saved content
|
|
480
|
+
must remain a canonical module list, and a table file cell must be one canonical attachment object or `null` (PVD-046).
|
|
481
|
+
The Provider supports only whole-content replacement and has no atomic patch or compare-and-swap operation (PCG-007),
|
|
482
|
+
so a read-modify-save sequence can overwrite a concurrent change.
|
|
478
483
|
|
|
479
484
|
The market result retains the Provider's `total_count` and derives `has_more` without echoing the caller's page
|
|
480
485
|
arguments; it does not claim a stable order or continuation token. Template reads remain experimental
|
|
@@ -884,6 +889,6 @@ uv run python scripts/check_release.py dist/release/*.whl dist/release/*.tar.gz
|
|
|
884
889
|
```
|
|
885
890
|
|
|
886
891
|
Rebuilding the same commit with the same locked environment and `SOURCE_DATE_EPOCH` must produce
|
|
887
|
-
byte-identical wheel and source distribution hashes. The release is bound to
|
|
888
|
-
|
|
889
|
-
`
|
|
892
|
+
byte-identical wheel and source distribution hashes. The release is bound to immutable `matelab-spec v0.4.3`, commit
|
|
893
|
+
`7965ac9803505ae35dc5ce36f52afc3c2c133c45`, and OpenAPI SHA-256
|
|
894
|
+
`6576812dd13f92ad58131f4edb8dc919d1b1fe0f19a695c2d25fbe6d13a8445b`.
|
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
"schema_version": 1,
|
|
3
3
|
"contract_line": "integration-v1",
|
|
4
4
|
"release": {
|
|
5
|
-
"tag": "v0.4.
|
|
6
|
-
"commit": "
|
|
5
|
+
"tag": "v0.4.3",
|
|
6
|
+
"commit": "7965ac9803505ae35dc5ce36f52afc3c2c133c45"
|
|
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": "6576812dd13f92ad58131f4edb8dc919d1b1fe0f19a695c2d25fbe6d13a8445b"
|
|
12
12
|
}
|
|
13
13
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
openapi: 3.1.0
|
|
2
2
|
info:
|
|
3
3
|
title: Matelab Integration Contract
|
|
4
|
-
version: 0.4.
|
|
4
|
+
version: 0.4.3
|
|
5
5
|
description: |
|
|
6
6
|
Current PHP wire contract for generating SDKs against the selected
|
|
7
7
|
`matelab-server` Provider version.
|
|
@@ -50,6 +50,10 @@ info:
|
|
|
50
50
|
- {operationId: extractLiteratureMetadata, change: doi-format-validated-before-extraction}
|
|
51
51
|
- {operationId: searchRecordData, change: encrypted-records-excluded}
|
|
52
52
|
- {operationId: updateRecordData, change: files-folder-path-removed-from-callable-shape}
|
|
53
|
+
- {operationId: readRecord, change: table-file-cell-readback-modeled-as-attachment-object-or-null}
|
|
54
|
+
- {operationId: readTemplateContent, change: json-code-null-readback-accepted}
|
|
55
|
+
- {operationId: saveTemplateContent, change: empty-number-and-boolean-cells-preserved}
|
|
56
|
+
- {operationId: saveTemplateContent, change: noncanonical-module-structures-contract-contained}
|
|
53
57
|
x-consumer-evidence:
|
|
54
58
|
- repository: /home/tnzhu/projects/matelab-mcp
|
|
55
59
|
commit: 94a129b2df0ef0fbcf179c10550fb7c9a5dd2e6f
|
|
@@ -827,6 +831,7 @@ paths:
|
|
|
827
831
|
operationId: saveTemplateContent
|
|
828
832
|
tags: [Templates]
|
|
829
833
|
summary: Replace canonical modules for an owned template
|
|
834
|
+
x-known-provider-issues: [PVD-046]
|
|
830
835
|
security: [{bearerAuth: []}]
|
|
831
836
|
requestBody:
|
|
832
837
|
required: true
|
|
@@ -2541,6 +2546,19 @@ paths:
|
|
|
2541
2546
|
name: Notes
|
|
2542
2547
|
uid: module-example
|
|
2543
2548
|
data: Sanitized content
|
|
2549
|
+
- type: table
|
|
2550
|
+
name: Measurements
|
|
2551
|
+
uid: table-example
|
|
2552
|
+
rows: [row-example]
|
|
2553
|
+
data:
|
|
2554
|
+
- type: file
|
|
2555
|
+
name: Evidence
|
|
2556
|
+
uid: column-example
|
|
2557
|
+
data:
|
|
2558
|
+
- filename: evidence.txt
|
|
2559
|
+
hash: 74034daeef860e12596c34f7e83703e668d578f4a85e72ec1645117223d4e6a7
|
|
2560
|
+
size: 26
|
|
2561
|
+
timestamp: 1767946200000
|
|
2544
2562
|
template_id: 8
|
|
2545
2563
|
datetime_modify: 1767946200000
|
|
2546
2564
|
username: Example User
|
|
@@ -4914,7 +4932,7 @@ components:
|
|
|
4914
4932
|
required: [data]
|
|
4915
4933
|
properties:
|
|
4916
4934
|
data:
|
|
4917
|
-
type: string
|
|
4935
|
+
type: [string, "null"]
|
|
4918
4936
|
contentMediaType: application/json
|
|
4919
4937
|
additionalProperties: true
|
|
4920
4938
|
TemplateTableColumn:
|
|
@@ -4949,6 +4967,7 @@ components:
|
|
|
4949
4967
|
oneOf:
|
|
4950
4968
|
- {type: number}
|
|
4951
4969
|
- {type: string, pattern: '^[+-]?(?:[0-9]+(?:\.[0-9]*)?|\.[0-9]+)(?:[eE][+-]?[0-9]+)?$'}
|
|
4970
|
+
- {type: string, const: ""}
|
|
4952
4971
|
- {type: "null"}
|
|
4953
4972
|
- if:
|
|
4954
4973
|
properties: {type: {const: bool}}
|
|
@@ -4957,7 +4976,11 @@ components:
|
|
|
4957
4976
|
properties:
|
|
4958
4977
|
data:
|
|
4959
4978
|
type: array
|
|
4960
|
-
items:
|
|
4979
|
+
items:
|
|
4980
|
+
oneOf:
|
|
4981
|
+
- {type: boolean}
|
|
4982
|
+
- {type: string, const: ""}
|
|
4983
|
+
- {type: "null"}
|
|
4961
4984
|
- if:
|
|
4962
4985
|
properties: {type: {const: file}}
|
|
4963
4986
|
required: [type]
|
|
@@ -4967,9 +4990,25 @@ components:
|
|
|
4967
4990
|
type: array
|
|
4968
4991
|
items:
|
|
4969
4992
|
oneOf:
|
|
4970
|
-
- {
|
|
4993
|
+
- {$ref: "#/components/schemas/RecordAttachment"}
|
|
4971
4994
|
- {type: "null"}
|
|
4972
4995
|
additionalProperties: true
|
|
4996
|
+
RecordAttachment:
|
|
4997
|
+
type: object
|
|
4998
|
+
required: [filename, hash, size, timestamp]
|
|
4999
|
+
properties:
|
|
5000
|
+
filename:
|
|
5001
|
+
type: string
|
|
5002
|
+
description: Original filename; the Provider's canonical empty attachment uses an empty string.
|
|
5003
|
+
hash:
|
|
5004
|
+
type: string
|
|
5005
|
+
pattern: "^(?:[0-9a-f]{64})?$"
|
|
5006
|
+
description: SHA-256 digest, or an empty string in the Provider's canonical empty attachment.
|
|
5007
|
+
size: {type: integer, minimum: 0}
|
|
5008
|
+
timestamp:
|
|
5009
|
+
type: integer
|
|
5010
|
+
minimum: 1
|
|
5011
|
+
description: Provider-generated Unix timestamp in milliseconds.
|
|
4973
5012
|
TemplateFormItem:
|
|
4974
5013
|
type: object
|
|
4975
5014
|
required: [name]
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
schema_version: 1
|
|
4
4
|
contract:
|
|
5
5
|
line: integration-v1
|
|
6
|
-
release: v0.4.
|
|
6
|
+
release: v0.4.3
|
|
7
7
|
operation_count: 71
|
|
8
8
|
|
|
9
9
|
operations:
|
|
@@ -133,7 +133,7 @@ operations:
|
|
|
133
133
|
state: implemented
|
|
134
134
|
stability: experimental
|
|
135
135
|
public_interface: AsyncMatelab.templates.save_content
|
|
136
|
-
known_issues: [PCG-007]
|
|
136
|
+
known_issues: [PCG-007, PVD-046]
|
|
137
137
|
|
|
138
138
|
- operation_id: copyTemplate
|
|
139
139
|
method: POST
|
{matelab_python_sdk-0.1.0a19 → matelab_python_sdk-0.1.0a20}/src/matelab/_generated/models.py
RENAMED
|
@@ -11,7 +11,7 @@ from pydantic import ConfigDict, Field, RootModel, SecretStr, StrictBool, Strict
|
|
|
11
11
|
|
|
12
12
|
from matelab._wire_validation import WireModel
|
|
13
13
|
|
|
14
|
-
OPENAPI_INFO_VERSION = "0.4.
|
|
14
|
+
OPENAPI_INFO_VERSION = "0.4.3"
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
class ResponseEnvelope(WireModel):
|
|
@@ -638,7 +638,11 @@ class SaveTemplateContentRequest(WireModel):
|
|
|
638
638
|
"then": {
|
|
639
639
|
"required": ["data"],
|
|
640
640
|
"properties": {
|
|
641
|
-
"data": {
|
|
641
|
+
"data": {
|
|
642
|
+
"type": ["string", "null"],
|
|
643
|
+
"contentMediaType": "application/json",
|
|
644
|
+
"nullable": True,
|
|
645
|
+
}
|
|
642
646
|
},
|
|
643
647
|
},
|
|
644
648
|
},
|
|
@@ -686,6 +690,7 @@ class SaveTemplateContentRequest(WireModel):
|
|
|
686
690
|
"type": "string",
|
|
687
691
|
"pattern": "^[+-]?(?:[0-9]+(?:\\.[0-9]*)?|\\.[0-9]+)(?:[eE][+-]?[0-9]+)?$",
|
|
688
692
|
},
|
|
693
|
+
{"type": "string", "const": ""},
|
|
689
694
|
{"type": "null"},
|
|
690
695
|
]
|
|
691
696
|
},
|
|
@@ -699,7 +704,13 @@ class SaveTemplateContentRequest(WireModel):
|
|
|
699
704
|
"properties": {
|
|
700
705
|
"data": {
|
|
701
706
|
"type": "array",
|
|
702
|
-
"items": {
|
|
707
|
+
"items": {
|
|
708
|
+
"oneOf": [
|
|
709
|
+
{"type": "boolean"},
|
|
710
|
+
{"type": "string", "const": ""},
|
|
711
|
+
{"type": "null"},
|
|
712
|
+
]
|
|
713
|
+
},
|
|
703
714
|
}
|
|
704
715
|
}
|
|
705
716
|
},
|
|
@@ -710,7 +721,9 @@ class SaveTemplateContentRequest(WireModel):
|
|
|
710
721
|
"properties": {
|
|
711
722
|
"data": {
|
|
712
723
|
"type": "array",
|
|
713
|
-
"items": {
|
|
724
|
+
"items": {
|
|
725
|
+
"oneOf": [{"$ref": "#/$defs/RecordAttachment"}, {"type": "null"}]
|
|
726
|
+
},
|
|
714
727
|
}
|
|
715
728
|
}
|
|
716
729
|
},
|
|
@@ -718,6 +731,27 @@ class SaveTemplateContentRequest(WireModel):
|
|
|
718
731
|
],
|
|
719
732
|
"additionalProperties": True,
|
|
720
733
|
},
|
|
734
|
+
"RecordAttachment": {
|
|
735
|
+
"type": "object",
|
|
736
|
+
"required": ["filename", "hash", "size", "timestamp"],
|
|
737
|
+
"properties": {
|
|
738
|
+
"filename": {
|
|
739
|
+
"type": "string",
|
|
740
|
+
"description": "Original filename; the Provider's canonical empty attachment uses an empty string.",
|
|
741
|
+
},
|
|
742
|
+
"hash": {
|
|
743
|
+
"type": "string",
|
|
744
|
+
"pattern": "^(?:[0-9a-f]{64})?$",
|
|
745
|
+
"description": "SHA-256 digest, or an empty string in the Provider's canonical empty attachment.",
|
|
746
|
+
},
|
|
747
|
+
"size": {"type": "integer", "minimum": 0},
|
|
748
|
+
"timestamp": {
|
|
749
|
+
"type": "integer",
|
|
750
|
+
"minimum": 1,
|
|
751
|
+
"description": "Provider-generated Unix timestamp in milliseconds.",
|
|
752
|
+
},
|
|
753
|
+
},
|
|
754
|
+
},
|
|
721
755
|
"TemplateFormItem": {
|
|
722
756
|
"type": "object",
|
|
723
757
|
"required": ["name"],
|
|
@@ -789,7 +823,13 @@ class TemplateModule(WireModel):
|
|
|
789
823
|
},
|
|
790
824
|
"then": {
|
|
791
825
|
"required": ["data"],
|
|
792
|
-
"properties": {
|
|
826
|
+
"properties": {
|
|
827
|
+
"data": {
|
|
828
|
+
"type": ["string", "null"],
|
|
829
|
+
"contentMediaType": "application/json",
|
|
830
|
+
"nullable": True,
|
|
831
|
+
}
|
|
832
|
+
},
|
|
793
833
|
},
|
|
794
834
|
},
|
|
795
835
|
],
|
|
@@ -835,6 +875,7 @@ class TemplateModule(WireModel):
|
|
|
835
875
|
"type": "string",
|
|
836
876
|
"pattern": "^[+-]?(?:[0-9]+(?:\\.[0-9]*)?|\\.[0-9]+)(?:[eE][+-]?[0-9]+)?$",
|
|
837
877
|
},
|
|
878
|
+
{"type": "string", "const": ""},
|
|
838
879
|
{"type": "null"},
|
|
839
880
|
]
|
|
840
881
|
},
|
|
@@ -848,7 +889,13 @@ class TemplateModule(WireModel):
|
|
|
848
889
|
"properties": {
|
|
849
890
|
"data": {
|
|
850
891
|
"type": "array",
|
|
851
|
-
"items": {
|
|
892
|
+
"items": {
|
|
893
|
+
"oneOf": [
|
|
894
|
+
{"type": "boolean"},
|
|
895
|
+
{"type": "string", "const": ""},
|
|
896
|
+
{"type": "null"},
|
|
897
|
+
]
|
|
898
|
+
},
|
|
852
899
|
}
|
|
853
900
|
}
|
|
854
901
|
},
|
|
@@ -859,7 +906,9 @@ class TemplateModule(WireModel):
|
|
|
859
906
|
"properties": {
|
|
860
907
|
"data": {
|
|
861
908
|
"type": "array",
|
|
862
|
-
"items": {
|
|
909
|
+
"items": {
|
|
910
|
+
"oneOf": [{"$ref": "#/$defs/RecordAttachment"}, {"type": "null"}]
|
|
911
|
+
},
|
|
863
912
|
}
|
|
864
913
|
}
|
|
865
914
|
},
|
|
@@ -867,6 +916,27 @@ class TemplateModule(WireModel):
|
|
|
867
916
|
],
|
|
868
917
|
"additionalProperties": True,
|
|
869
918
|
},
|
|
919
|
+
"RecordAttachment": {
|
|
920
|
+
"type": "object",
|
|
921
|
+
"required": ["filename", "hash", "size", "timestamp"],
|
|
922
|
+
"properties": {
|
|
923
|
+
"filename": {
|
|
924
|
+
"type": "string",
|
|
925
|
+
"description": "Original filename; the Provider's canonical empty attachment uses an empty string.",
|
|
926
|
+
},
|
|
927
|
+
"hash": {
|
|
928
|
+
"type": "string",
|
|
929
|
+
"pattern": "^(?:[0-9a-f]{64})?$",
|
|
930
|
+
"description": "SHA-256 digest, or an empty string in the Provider's canonical empty attachment.",
|
|
931
|
+
},
|
|
932
|
+
"size": {"type": "integer", "minimum": 0},
|
|
933
|
+
"timestamp": {
|
|
934
|
+
"type": "integer",
|
|
935
|
+
"minimum": 1,
|
|
936
|
+
"description": "Provider-generated Unix timestamp in milliseconds.",
|
|
937
|
+
},
|
|
938
|
+
},
|
|
939
|
+
},
|
|
870
940
|
"TemplateFormItem": {
|
|
871
941
|
"type": "object",
|
|
872
942
|
"required": ["name"],
|
|
@@ -929,6 +999,7 @@ class TemplateTableColumn(WireModel):
|
|
|
929
999
|
"type": "string",
|
|
930
1000
|
"pattern": "^[+-]?(?:[0-9]+(?:\\.[0-9]*)?|\\.[0-9]+)(?:[eE][+-]?[0-9]+)?$",
|
|
931
1001
|
},
|
|
1002
|
+
{"type": "string", "const": ""},
|
|
932
1003
|
{"type": "null"},
|
|
933
1004
|
]
|
|
934
1005
|
},
|
|
@@ -940,7 +1011,16 @@ class TemplateTableColumn(WireModel):
|
|
|
940
1011
|
"if": {"properties": {"type": {"const": "bool"}}, "required": ["type"]},
|
|
941
1012
|
"then": {
|
|
942
1013
|
"properties": {
|
|
943
|
-
"data": {
|
|
1014
|
+
"data": {
|
|
1015
|
+
"type": "array",
|
|
1016
|
+
"items": {
|
|
1017
|
+
"oneOf": [
|
|
1018
|
+
{"type": "boolean"},
|
|
1019
|
+
{"type": "string", "const": ""},
|
|
1020
|
+
{"type": "null"},
|
|
1021
|
+
]
|
|
1022
|
+
},
|
|
1023
|
+
}
|
|
944
1024
|
}
|
|
945
1025
|
},
|
|
946
1026
|
},
|
|
@@ -948,11 +1028,37 @@ class TemplateTableColumn(WireModel):
|
|
|
948
1028
|
"if": {"properties": {"type": {"const": "file"}}, "required": ["type"]},
|
|
949
1029
|
"then": {
|
|
950
1030
|
"properties": {
|
|
951
|
-
"data": {
|
|
1031
|
+
"data": {
|
|
1032
|
+
"type": "array",
|
|
1033
|
+
"items": {"oneOf": [{"$ref": "#/$defs/RecordAttachment"}, {"type": "null"}]},
|
|
1034
|
+
}
|
|
952
1035
|
}
|
|
953
1036
|
},
|
|
954
1037
|
},
|
|
955
|
-
]
|
|
1038
|
+
],
|
|
1039
|
+
"$defs": {
|
|
1040
|
+
"RecordAttachment": {
|
|
1041
|
+
"type": "object",
|
|
1042
|
+
"required": ["filename", "hash", "size", "timestamp"],
|
|
1043
|
+
"properties": {
|
|
1044
|
+
"filename": {
|
|
1045
|
+
"type": "string",
|
|
1046
|
+
"description": "Original filename; the Provider's canonical empty attachment uses an empty string.",
|
|
1047
|
+
},
|
|
1048
|
+
"hash": {
|
|
1049
|
+
"type": "string",
|
|
1050
|
+
"pattern": "^(?:[0-9a-f]{64})?$",
|
|
1051
|
+
"description": "SHA-256 digest, or an empty string in the Provider's canonical empty attachment.",
|
|
1052
|
+
},
|
|
1053
|
+
"size": {"type": "integer", "minimum": 0},
|
|
1054
|
+
"timestamp": {
|
|
1055
|
+
"type": "integer",
|
|
1056
|
+
"minimum": 1,
|
|
1057
|
+
"description": "Provider-generated Unix timestamp in milliseconds.",
|
|
1058
|
+
},
|
|
1059
|
+
},
|
|
1060
|
+
}
|
|
1061
|
+
},
|
|
956
1062
|
}
|
|
957
1063
|
},
|
|
958
1064
|
)
|
|
@@ -964,6 +1070,22 @@ class TemplateTableColumn(WireModel):
|
|
|
964
1070
|
data: list[Any]
|
|
965
1071
|
|
|
966
1072
|
|
|
1073
|
+
class RecordAttachment(WireModel):
|
|
1074
|
+
filename: str
|
|
1075
|
+
"""
|
|
1076
|
+
Original filename; the Provider's canonical empty attachment uses an empty string.
|
|
1077
|
+
"""
|
|
1078
|
+
hash: Annotated[str, Field(pattern="^(?:[0-9a-f]{64})?$")]
|
|
1079
|
+
"""
|
|
1080
|
+
SHA-256 digest, or an empty string in the Provider's canonical empty attachment.
|
|
1081
|
+
"""
|
|
1082
|
+
size: Annotated[StrictInt, Field(ge=0)]
|
|
1083
|
+
timestamp: Annotated[StrictInt, Field(ge=1)]
|
|
1084
|
+
"""
|
|
1085
|
+
Provider-generated Unix timestamp in milliseconds.
|
|
1086
|
+
"""
|
|
1087
|
+
|
|
1088
|
+
|
|
967
1089
|
class TemplateFormItem(WireModel):
|
|
968
1090
|
model_config = ConfigDict(extra="allow")
|
|
969
1091
|
name: Annotated[str, Field(min_length=1, pattern=".*[^0-9.\\-+eE ].*")]
|
|
@@ -722,13 +722,7 @@ def _record_attachment_refs(
|
|
|
722
722
|
):
|
|
723
723
|
continue
|
|
724
724
|
for position, value in enumerate(field_values):
|
|
725
|
-
|
|
726
|
-
for attachment in value:
|
|
727
|
-
add_structured(
|
|
728
|
-
attachment, kind=kind, module=module_name, field_name=field_name, position=position
|
|
729
|
-
)
|
|
730
|
-
else:
|
|
731
|
-
add_structured(value, kind=kind, module=module_name, field_name=field_name, position=position)
|
|
725
|
+
add_structured(value, kind=kind, module=module_name, field_name=field_name, position=position)
|
|
732
726
|
elif module_type in {"files", "images"} and isinstance(values, list):
|
|
733
727
|
file_kind = "files_module" if module_type == "files" else "images_module"
|
|
734
728
|
for position, value in enumerate(values):
|
|
@@ -108,6 +108,127 @@ def test_generated_response_preserves_required_nullable_fields() -> None:
|
|
|
108
108
|
assert response.keywords is None
|
|
109
109
|
|
|
110
110
|
|
|
111
|
+
def _item_view_payload(modules: list[object]) -> dict[str, object]:
|
|
112
|
+
return {
|
|
113
|
+
"code": 0,
|
|
114
|
+
"msg": "",
|
|
115
|
+
"errcode": 0,
|
|
116
|
+
"errmsg": "",
|
|
117
|
+
"title": None,
|
|
118
|
+
"keywords": None,
|
|
119
|
+
"modules": modules,
|
|
120
|
+
"template_id": None,
|
|
121
|
+
"datetime_modify": 1,
|
|
122
|
+
"username": None,
|
|
123
|
+
"modify_log": [],
|
|
124
|
+
"uid": None,
|
|
125
|
+
"current_version": 0,
|
|
126
|
+
"item_id": 1,
|
|
127
|
+
"datetime_locked": 0,
|
|
128
|
+
"locked": False,
|
|
129
|
+
"signs": [],
|
|
130
|
+
"signed": False,
|
|
131
|
+
"owner": False,
|
|
132
|
+
"eln_text": None,
|
|
133
|
+
"eln_server": "localhost",
|
|
134
|
+
"eln_id": 1,
|
|
135
|
+
"editable": False,
|
|
136
|
+
"keyword_eln": [],
|
|
137
|
+
"pdf_enable": False,
|
|
138
|
+
"template_show": "",
|
|
139
|
+
"template_text": "",
|
|
140
|
+
"template_intro": "",
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def _record_attachment() -> dict[str, object]:
|
|
145
|
+
return {"filename": "evidence.txt", "hash": "0" * 64, "size": 26, "timestamp": 1_767_946_200_000}
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def _file_table_module(cell: object) -> dict[str, object]:
|
|
149
|
+
return {"name": "Measurements", "type": "table", "data": [{"name": "Evidence", "type": "file", "data": [cell]}]}
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def test_item_view_file_cells_are_one_record_attachment_or_null() -> None:
|
|
153
|
+
attachment = _record_attachment()
|
|
154
|
+
for cell in (attachment, None):
|
|
155
|
+
response = ItemViewResponse.model_validate(_item_view_payload([_file_table_module(cell)]))
|
|
156
|
+
assert response.modules[0].name == "Measurements"
|
|
157
|
+
|
|
158
|
+
with pytest.raises(ValidationError):
|
|
159
|
+
_ = ItemViewResponse.model_validate(_item_view_payload([_file_table_module([attachment])]))
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def test_record_attachment_requires_all_fields_and_preserves_canonical_empty_attachment() -> None:
|
|
163
|
+
attachment = _record_attachment()
|
|
164
|
+
assert wire_models.RecordAttachment.model_validate(attachment).model_dump() == attachment
|
|
165
|
+
assert wire_models.RecordAttachment(filename="", hash="", size=0, timestamp=1).filename == ""
|
|
166
|
+
|
|
167
|
+
for field in attachment:
|
|
168
|
+
with pytest.raises(ValidationError):
|
|
169
|
+
_ = wire_models.RecordAttachment.model_validate(
|
|
170
|
+
{key: value for key, value in attachment.items() if key != field}
|
|
171
|
+
)
|
|
172
|
+
for invalid_attachment in (
|
|
173
|
+
{**attachment, "hash": "not-a-sha256"},
|
|
174
|
+
{**attachment, "size": -1},
|
|
175
|
+
{**attachment, "timestamp": 0},
|
|
176
|
+
):
|
|
177
|
+
with pytest.raises(ValidationError):
|
|
178
|
+
_ = wire_models.RecordAttachment.model_validate(invalid_attachment)
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def test_template_json_code_modules_with_style_accept_json_string_or_null_data() -> None:
|
|
182
|
+
for data in ('{"enabled":true}', None):
|
|
183
|
+
module = wire_models.TemplateModule.model_validate(
|
|
184
|
+
{"name": "Configuration", "type": "code", "language": "json", "style": None, "data": data}
|
|
185
|
+
)
|
|
186
|
+
assert module.data == data
|
|
187
|
+
|
|
188
|
+
without_style = wire_models.TemplateModule.model_validate(
|
|
189
|
+
{"name": "Configuration", "type": "code", "language": "json", "data": {"enabled": True}}
|
|
190
|
+
)
|
|
191
|
+
assert without_style.data == {"enabled": True}
|
|
192
|
+
|
|
193
|
+
invalid_modules: tuple[dict[str, object], ...] = (
|
|
194
|
+
{"name": "Configuration", "type": "code", "language": "json", "style": None},
|
|
195
|
+
{"name": "Configuration", "type": "code", "language": "json", "style": None, "data": {}},
|
|
196
|
+
)
|
|
197
|
+
for invalid_module in invalid_modules:
|
|
198
|
+
with pytest.raises(ValidationError):
|
|
199
|
+
_ = wire_models.TemplateModule.model_validate(invalid_module)
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def test_template_number_and_boolean_cells_accept_empty_strings() -> None:
|
|
203
|
+
number = wire_models.TemplateTableColumn.model_validate(
|
|
204
|
+
{"name": "Reading", "type": "number", "data": [1, "1.5", "", None]}
|
|
205
|
+
)
|
|
206
|
+
boolean = wire_models.TemplateTableColumn.model_validate(
|
|
207
|
+
{"name": "Published", "type": "bool", "data": [True, "", None]}
|
|
208
|
+
)
|
|
209
|
+
assert number.data == [1, "1.5", "", None]
|
|
210
|
+
assert boolean.data == [True, "", None]
|
|
211
|
+
|
|
212
|
+
for invalid_column in (
|
|
213
|
+
{"name": "Reading", "type": "number", "data": ["not-a-number"]},
|
|
214
|
+
{"name": "Published", "type": "bool", "data": ["true"]},
|
|
215
|
+
):
|
|
216
|
+
with pytest.raises(ValidationError):
|
|
217
|
+
_ = wire_models.TemplateTableColumn.model_validate(invalid_column)
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def test_save_template_content_keeps_file_cells_narrowed_to_one_attachment() -> None:
|
|
221
|
+
attachment = _record_attachment()
|
|
222
|
+
content = json.dumps([_file_table_module(attachment)])
|
|
223
|
+
request = wire_models.SaveTemplateContentRequest.model_validate({"id": 1, "data": content})
|
|
224
|
+
assert request.data == content
|
|
225
|
+
|
|
226
|
+
with pytest.raises(ValidationError):
|
|
227
|
+
_ = wire_models.SaveTemplateContentRequest.model_validate(
|
|
228
|
+
{"id": 1, "data": json.dumps([_file_table_module([attachment])])}
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
|
|
111
232
|
def test_generated_requests_reject_unknown_fields() -> None:
|
|
112
233
|
with pytest.raises(ValidationError):
|
|
113
234
|
_ = ListRecordsRequest.model_validate({"eln": "Example Notebook", "unexpected": True})
|
|
@@ -364,7 +485,7 @@ def test_generated_models_cover_every_component_schema_and_non_streaming_operati
|
|
|
364
485
|
}
|
|
365
486
|
assert expected_response_models <= class_names
|
|
366
487
|
assert len(operation_ids) == 71
|
|
367
|
-
assert wire_models.OPENAPI_INFO_VERSION == "0.4.
|
|
488
|
+
assert wire_models.OPENAPI_INFO_VERSION == "0.4.3"
|
|
368
489
|
|
|
369
490
|
|
|
370
491
|
def test_generation_is_deterministic(tmp_path: Path) -> None:
|
|
@@ -476,8 +597,8 @@ def test_contract_lock_is_the_single_release_pin() -> None:
|
|
|
476
597
|
openapi = cast(dict[str, Any], lock["openapi"])
|
|
477
598
|
assert set(release) == {"tag", "commit"}
|
|
478
599
|
assert set(openapi) == {"source_path", "snapshot", "sha256"}
|
|
479
|
-
assert release == {"tag": "v0.4.
|
|
480
|
-
assert openapi["sha256"] == "
|
|
600
|
+
assert release == {"tag": "v0.4.3", "commit": "7965ac9803505ae35dc5ce36f52afc3c2c133c45"}
|
|
601
|
+
assert openapi["sha256"] == "6576812dd13f92ad58131f4edb8dc919d1b1fe0f19a695c2d25fbe6d13a8445b"
|
|
481
602
|
assert re.fullmatch(r"[0-9a-f]{40}", cast(str, release["commit"]))
|
|
482
603
|
assert re.fullmatch(r"[0-9a-f]{64}", cast(str, openapi["sha256"]))
|
|
483
604
|
assert not (ROOT / "contracts" / "matelab-integration-v1.sha256").exists()
|
|
@@ -342,7 +342,11 @@ async def test_record_attachment_mutations_are_separate_and_collaboration_is_not
|
|
|
342
342
|
"type": "table",
|
|
343
343
|
"rows": ["row-1"],
|
|
344
344
|
"data": [
|
|
345
|
-
{
|
|
345
|
+
{
|
|
346
|
+
"name": "Evidence",
|
|
347
|
+
"type": "file",
|
|
348
|
+
"data": [{"hash": _HASH_B, "filename": "table.txt", "size": 5, "timestamp": 1}],
|
|
349
|
+
}
|
|
346
350
|
],
|
|
347
351
|
},
|
|
348
352
|
{
|
|
@@ -616,7 +620,7 @@ async def test_update_sets_one_null_table_file_cell_from_record_upload(
|
|
|
616
620
|
{
|
|
617
621
|
"name": "Evidence",
|
|
618
622
|
"type": "file",
|
|
619
|
-
"data": [
|
|
623
|
+
"data": [{"hash": _HASH_A, "filename": "evidence.txt", "size": 5, "timestamp": 1}],
|
|
620
624
|
}
|
|
621
625
|
],
|
|
622
626
|
}
|
|
@@ -676,7 +680,7 @@ async def test_update_appends_table_row_with_scalar_values_and_one_staged_file(
|
|
|
676
680
|
{
|
|
677
681
|
"name": "Evidence",
|
|
678
682
|
"type": "file",
|
|
679
|
-
"data": [None, None,
|
|
683
|
+
"data": [None, None, {"hash": _HASH_A, "filename": "evidence.txt", "size": 5, "timestamp": 1}],
|
|
680
684
|
},
|
|
681
685
|
],
|
|
682
686
|
}
|
|
@@ -774,7 +778,11 @@ async def test_update_replaces_observed_table_or_files_occurrence_with_one_inten
|
|
|
774
778
|
"type": "table",
|
|
775
779
|
"rows": ["row-1"],
|
|
776
780
|
"data": [
|
|
777
|
-
{
|
|
781
|
+
{
|
|
782
|
+
"name": "Evidence",
|
|
783
|
+
"type": "file",
|
|
784
|
+
"data": [{"hash": _HASH_A, "filename": "evidence.txt", "size": 5, "timestamp": 1}],
|
|
785
|
+
}
|
|
778
786
|
],
|
|
779
787
|
}
|
|
780
788
|
table_after = {
|
|
@@ -785,7 +793,7 @@ async def test_update_replaces_observed_table_or_files_occurrence_with_one_inten
|
|
|
785
793
|
{
|
|
786
794
|
"name": "Evidence",
|
|
787
795
|
"type": "file",
|
|
788
|
-
"data": [
|
|
796
|
+
"data": [{"hash": _HASH_B, "filename": "replacement.txt", "size": 5, "timestamp": 1}],
|
|
789
797
|
}
|
|
790
798
|
],
|
|
791
799
|
}
|
|
@@ -878,7 +886,7 @@ async def test_update_adds_empty_file_column_and_deletes_complete_file_fields(
|
|
|
878
886
|
{
|
|
879
887
|
"name": "Old evidence",
|
|
880
888
|
"type": "file",
|
|
881
|
-
"data": [
|
|
889
|
+
"data": [{"hash": _HASH_B, "filename": "old.txt", "size": 5, "timestamp": 1}, None],
|
|
882
890
|
},
|
|
883
891
|
{"name": "Label", "type": "text", "data": ["A", "B"]},
|
|
884
892
|
],
|
|
@@ -1035,7 +1043,7 @@ async def test_table_file_cell_prewrite_rejection_preserves_stage_for_a_later_va
|
|
|
1035
1043
|
{
|
|
1036
1044
|
"name": "Evidence",
|
|
1037
1045
|
"type": "file",
|
|
1038
|
-
"data": [
|
|
1046
|
+
"data": [{"hash": _HASH_B, "filename": "existing.txt", "size": 5, "timestamp": 1}],
|
|
1039
1047
|
}
|
|
1040
1048
|
],
|
|
1041
1049
|
}
|
|
@@ -1057,7 +1065,7 @@ async def test_table_file_cell_prewrite_rejection_preserves_stage_for_a_later_va
|
|
|
1057
1065
|
{
|
|
1058
1066
|
"name": "Evidence",
|
|
1059
1067
|
"type": "file",
|
|
1060
|
-
"data": [
|
|
1068
|
+
"data": [{"hash": _HASH_A, "filename": "evidence.txt", "size": 5, "timestamp": 1}],
|
|
1061
1069
|
}
|
|
1062
1070
|
],
|
|
1063
1071
|
}
|
|
@@ -145,16 +145,37 @@ async def test_read_current_record_maps_pinned_contract_success_example(
|
|
|
145
145
|
assert record.source == source
|
|
146
146
|
assert record.title == "Calibration Run"
|
|
147
147
|
assert record.keywords == ("calibration",)
|
|
148
|
-
assert record.modules ==
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
148
|
+
assert record.modules[0] == {
|
|
149
|
+
"name": "Notes",
|
|
150
|
+
"type": "richtext",
|
|
151
|
+
"data": "Sanitized content",
|
|
152
|
+
"language": None,
|
|
153
|
+
"style": None,
|
|
154
|
+
"uid": "module-example",
|
|
155
|
+
}
|
|
156
|
+
assert record.modules[1] == {
|
|
157
|
+
"name": "Measurements",
|
|
158
|
+
"type": "table",
|
|
159
|
+
"data": [
|
|
160
|
+
{
|
|
161
|
+
"name": "Evidence",
|
|
162
|
+
"type": "file",
|
|
163
|
+
"data": [
|
|
164
|
+
{
|
|
165
|
+
"filename": "evidence.txt",
|
|
166
|
+
"hash": "74034daeef860e12596c34f7e83703e668d578f4a85e72ec1645117223d4e6a7",
|
|
167
|
+
"size": 26,
|
|
168
|
+
"timestamp": 1_767_946_200_000,
|
|
169
|
+
}
|
|
170
|
+
],
|
|
171
|
+
"uid": "column-example",
|
|
172
|
+
}
|
|
173
|
+
],
|
|
174
|
+
"language": None,
|
|
175
|
+
"style": None,
|
|
176
|
+
"uid": "table-example",
|
|
177
|
+
"rows": ["row-example"],
|
|
178
|
+
}
|
|
158
179
|
assert record.versions == (RecordVersionRef(source=source, version_id=70001),)
|
|
159
180
|
|
|
160
181
|
|
|
@@ -183,7 +204,28 @@ async def test_read_current_record_maps_required_nullable_and_open_modules() ->
|
|
|
183
204
|
"filename": "example.bin",
|
|
184
205
|
"size": 12,
|
|
185
206
|
},
|
|
186
|
-
}
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
"type": "table",
|
|
210
|
+
"name": "Measurements",
|
|
211
|
+
"uid": "table-example",
|
|
212
|
+
"rows": ["row-1", "row-2"],
|
|
213
|
+
"data": [
|
|
214
|
+
{
|
|
215
|
+
"name": "Evidence",
|
|
216
|
+
"type": "file",
|
|
217
|
+
"data": [
|
|
218
|
+
{
|
|
219
|
+
"hash": "d6a8f491a743ee3c10b42b4519d7545d7f1d79a7e98ed2ef9c03c0c1d75a1a91",
|
|
220
|
+
"filename": "evidence.pdf",
|
|
221
|
+
"size": 34,
|
|
222
|
+
"timestamp": 1_767_946_200_000,
|
|
223
|
+
},
|
|
224
|
+
None,
|
|
225
|
+
],
|
|
226
|
+
}
|
|
227
|
+
],
|
|
228
|
+
},
|
|
187
229
|
],
|
|
188
230
|
template_id=None,
|
|
189
231
|
datetime_modify=1_767_946_200_000,
|
|
@@ -242,14 +284,63 @@ async def test_read_current_record_maps_required_nullable_and_open_modules() ->
|
|
|
242
284
|
assert record.author_name is None
|
|
243
285
|
assert record.modules[0]["uid"] == "module-example"
|
|
244
286
|
assert record.modules[0]["future_module_field"] == {"supported": True}
|
|
287
|
+
table_data = record.modules[1]["data"]
|
|
288
|
+
assert isinstance(table_data, list)
|
|
289
|
+
file_column = table_data[0]
|
|
290
|
+
assert isinstance(file_column, dict)
|
|
291
|
+
file_cells = file_column["data"]
|
|
292
|
+
assert isinstance(file_cells, list)
|
|
293
|
+
assert file_cells[1] is None
|
|
245
294
|
assert record.versions[0].version_id == 70001
|
|
246
295
|
assert [(item.filename, item.size, item.observed_version) for item in record.attachments] == [
|
|
247
|
-
("
|
|
296
|
+
("evidence.pdf", 34, None),
|
|
297
|
+
("example.bin", 12, None),
|
|
248
298
|
]
|
|
249
299
|
assert record.attachments[0].source == source
|
|
300
|
+
assert record.attachments[0].location is not None
|
|
301
|
+
assert record.attachments[0].location.kind == "table_file"
|
|
302
|
+
assert record.attachments[0].location.position == 0
|
|
250
303
|
assert "74034dae" not in repr(record.attachments[0])
|
|
251
304
|
|
|
252
305
|
|
|
306
|
+
@pytest.mark.asyncio
|
|
307
|
+
async def test_read_rejects_historical_table_attachment_array_at_public_boundary() -> None:
|
|
308
|
+
payload = _item_view_payload(uid="REC-EXAMPLE-001")
|
|
309
|
+
payload["modules"] = [
|
|
310
|
+
{
|
|
311
|
+
"type": "table",
|
|
312
|
+
"name": "Measurements",
|
|
313
|
+
"rows": ["row-1"],
|
|
314
|
+
"data": [
|
|
315
|
+
{
|
|
316
|
+
"name": "Evidence",
|
|
317
|
+
"type": "file",
|
|
318
|
+
"data": [
|
|
319
|
+
[
|
|
320
|
+
{
|
|
321
|
+
"hash": "d6a8f491a743ee3c10b42b4519d7545d7f1d79a7e98ed2ef9c03c0c1d75a1a91",
|
|
322
|
+
"filename": "evidence.pdf",
|
|
323
|
+
"size": 34,
|
|
324
|
+
"timestamp": 1_767_946_200_000,
|
|
325
|
+
}
|
|
326
|
+
]
|
|
327
|
+
],
|
|
328
|
+
}
|
|
329
|
+
],
|
|
330
|
+
}
|
|
331
|
+
]
|
|
332
|
+
|
|
333
|
+
async def handler(_: Request) -> Response:
|
|
334
|
+
return Response(200, json=payload)
|
|
335
|
+
|
|
336
|
+
notebook = NotebookRef(notebook_id=23, title="Shared Example", owner_userid=202, scope="shared")
|
|
337
|
+
source = RecordLocator(notebook=notebook, record=RecordRef(record_id=9001, record_uid="REC-EXAMPLE-001"))
|
|
338
|
+
async with AsyncClient(base_url="https://matelab.example/api", transport=MockTransport(handler)) as http_client:
|
|
339
|
+
client = AsyncMatelab("https://ignored.example", session=_session(), http_client=http_client)
|
|
340
|
+
with pytest.raises(MatelabProtocolError, match="does not match the Integration Contract"):
|
|
341
|
+
_ = await client.records.read(source)
|
|
342
|
+
|
|
343
|
+
|
|
253
344
|
@pytest.mark.asyncio
|
|
254
345
|
async def test_read_current_record_rejects_mismatched_uid() -> None:
|
|
255
346
|
async def handler(_: Request) -> Response:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{matelab_python_sdk-0.1.0a19 → matelab_python_sdk-0.1.0a20}/scripts/check_installed_package.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{matelab_python_sdk-0.1.0a19 → matelab_python_sdk-0.1.0a20}/src/matelab/_generated/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{matelab_python_sdk-0.1.0a19 → matelab_python_sdk-0.1.0a20}/src/matelab/domains/cloud_drive.py
RENAMED
|
File without changes
|
|
File without changes
|
{matelab_python_sdk-0.1.0a19 → matelab_python_sdk-0.1.0a20}/src/matelab/domains/literature.py
RENAMED
|
File without changes
|
{matelab_python_sdk-0.1.0a19 → matelab_python_sdk-0.1.0a20}/src/matelab/domains/notebooks.py
RENAMED
|
File without changes
|
{matelab_python_sdk-0.1.0a19 → matelab_python_sdk-0.1.0a20}/src/matelab/domains/templates.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{matelab_python_sdk-0.1.0a19 → matelab_python_sdk-0.1.0a20}/tests/provider/test_provider_smoke.py
RENAMED
|
File without changes
|
{matelab_python_sdk-0.1.0a19 → matelab_python_sdk-0.1.0a20}/tests/test_async_attachment_upload.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{matelab_python_sdk-0.1.0a19 → matelab_python_sdk-0.1.0a20}/tests/test_comments_and_downloads.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{matelab_python_sdk-0.1.0a19 → matelab_python_sdk-0.1.0a20}/tests/test_notebook_lifecycle.py
RENAMED
|
File without changes
|
|
File without changes
|
{matelab_python_sdk-0.1.0a19 → matelab_python_sdk-0.1.0a20}/tests/test_operation_coverage.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{matelab_python_sdk-0.1.0a19 → matelab_python_sdk-0.1.0a20}/tests/test_template_lifecycle.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{matelab_python_sdk-0.1.0a19 → matelab_python_sdk-0.1.0a20}/tests/test_uploads_and_literature.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|