matelab-python-sdk 0.1.0a12__tar.gz → 0.1.0a14__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.0a12 → matelab_python_sdk-0.1.0a14}/CHANGELOG.md +14 -0
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a14}/PKG-INFO +49 -3
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a14}/README.md +48 -2
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a14}/pyproject.toml +1 -1
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a14}/src/matelab/__init__.py +49 -1
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a14}/src/matelab/_transport.py +8 -11
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a14}/src/matelab/_wire_validation.py +6 -10
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a14}/src/matelab/client.py +9 -7
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a14}/src/matelab/domains/cloud_drive.py +1 -3
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a14}/src/matelab/domains/literature.py +1 -3
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a14}/src/matelab/domains/records.py +2 -2
- matelab_python_sdk-0.1.0a14/src/matelab/domains/template_documents.py +527 -0
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a14}/src/matelab/domains/templates.py +26 -12
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a14}/src/matelab/session.py +4 -2
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a14}/src/matelab/streaming.py +6 -8
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a14}/tests/test_client.py +5 -4
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a14}/tests/test_streaming.py +2 -2
- matelab_python_sdk-0.1.0a14/tests/test_template_documents.py +393 -0
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a14}/tests/typing/consumer.py +27 -0
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a14}/uv.lock +1 -1
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a14}/.env.example +0 -0
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a14}/.github/workflows/release.yml +0 -0
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a14}/.gitignore +0 -0
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a14}/AGENTS.md +0 -0
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a14}/LICENSE +0 -0
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a14}/NOTICE +0 -0
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a14}/contracts/matelab-integration-v1.lock.json +0 -0
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a14}/contracts/matelab-integration-v1.openapi.yaml +0 -0
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a14}/docs/operation-coverage.yaml +0 -0
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a14}/scripts/check_installed_package.py +0 -0
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a14}/scripts/check_release.py +0 -0
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a14}/scripts/generate_models.py +0 -0
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a14}/src/matelab/_generated/__init__.py +0 -0
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a14}/src/matelab/_generated/models.py +0 -0
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a14}/src/matelab/domains/__init__.py +0 -0
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a14}/src/matelab/domains/_attachment_content.py +0 -0
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a14}/src/matelab/domains/groups.py +0 -0
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a14}/src/matelab/domains/notebooks.py +0 -0
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a14}/src/matelab/domains/uploads.py +0 -0
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a14}/src/matelab/domains/users.py +0 -0
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a14}/src/matelab/errors.py +0 -0
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a14}/src/matelab/py.typed +0 -0
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a14}/tests/conftest.py +0 -0
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a14}/tests/provider/test_provider_smoke.py +0 -0
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a14}/tests/test_async_attachment_upload.py +0 -0
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a14}/tests/test_cloud_drive.py +0 -0
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a14}/tests/test_comments_and_downloads.py +0 -0
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a14}/tests/test_consumer_smoke.py +0 -0
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a14}/tests/test_errors.py +0 -0
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a14}/tests/test_extended_records.py +0 -0
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a14}/tests/test_generation.py +0 -0
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a14}/tests/test_groups.py +0 -0
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a14}/tests/test_notebook_lifecycle.py +0 -0
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a14}/tests/test_notebooks.py +0 -0
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a14}/tests/test_operation_coverage.py +0 -0
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a14}/tests/test_record_lifecycle.py +0 -0
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a14}/tests/test_record_mutations.py +0 -0
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a14}/tests/test_records.py +0 -0
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a14}/tests/test_release.py +0 -0
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a14}/tests/test_template_lifecycle.py +0 -0
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a14}/tests/test_templates.py +0 -0
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a14}/tests/test_transport.py +0 -0
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a14}/tests/test_uploads_and_literature.py +0 -0
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a14}/tests/test_users.py +0 -0
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a14}/tests/typing/pyproject.toml +0 -0
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.0a14 - 2026-08-10
|
|
4
|
+
|
|
5
|
+
- Adds immutable semantic template modules, editable `TemplateDocument` values, ordered name-based patch intents,
|
|
6
|
+
stable content fingerprints, and `Templates.read_document()`, `save_document()`, and `patch_content()` while
|
|
7
|
+
retaining the low-level `TemplateModule`, `read()`, and `save_content()` interface.
|
|
8
|
+
|
|
9
|
+
## 0.1.0a13 - 2026-08-08
|
|
10
|
+
|
|
11
|
+
- Breaking: `Identity.username` now removes surrounding whitespace and rejects values that become empty.
|
|
12
|
+
- Keeps `ByteRange` typed through the transport adapter and closes the underlying HTTPX2 response when download reads
|
|
13
|
+
or iteration finish or fail, while retaining explicit `DownloadStream.aclose()` for deferred framework consumers.
|
|
14
|
+
- Consolidates Provider session validation through the public `Session` models and simplifies private wire-rule and
|
|
15
|
+
token-refresh validation without changing the pinned Integration Contract.
|
|
16
|
+
|
|
3
17
|
## 0.1.0a12 - 2026-08-07
|
|
4
18
|
|
|
5
19
|
- Breaking: removes `MatelabAttachmentValidationError`; local attachment size and checksum mismatches now use the
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: matelab-python-sdk
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.0a14
|
|
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
|
|
@@ -30,7 +30,7 @@ Description-Content-Type: text/markdown
|
|
|
30
30
|
|
|
31
31
|
Reusable async Python client for the Matelab Integration Contract.
|
|
32
32
|
|
|
33
|
-
The current alpha is `0.1.
|
|
33
|
+
The current alpha is `0.1.0a14`. `[project].version` in `pyproject.toml` is the sole SDK version source;
|
|
34
34
|
`uv.lock` only mirrors that source.
|
|
35
35
|
|
|
36
36
|
The SDK is pinned to the immutable `matelab-spec v0.4.0` Contract Release. The sole release pin is
|
|
@@ -59,7 +59,7 @@ To test the same artifact a downstream Consumer will install, build and install
|
|
|
59
59
|
|
|
60
60
|
```bash
|
|
61
61
|
uv build --no-build-isolation --out-dir dist/release
|
|
62
|
-
python -m pip install dist/release/matelab_python_sdk-0.1.
|
|
62
|
+
python -m pip install dist/release/matelab_python_sdk-0.1.0a14-py3-none-any.whl
|
|
63
63
|
```
|
|
64
64
|
|
|
65
65
|
Do not infer Provider compatibility from the SDK version alone. A release is also bound to the Contract
|
|
@@ -450,6 +450,52 @@ market = await client.templates.search_market("calibration", page=1, page_size=2
|
|
|
450
450
|
modules = await client.templates.read(templates.owned[0].ref)
|
|
451
451
|
```
|
|
452
452
|
|
|
453
|
+
Semantic template documents hide canonical `uid`, `width`, `data`, `rows`, `folders`, and option encoding:
|
|
454
|
+
|
|
455
|
+
```python
|
|
456
|
+
from matelab import (
|
|
457
|
+
TemplateDocument,
|
|
458
|
+
TemplateFieldAdd,
|
|
459
|
+
TemplateFormField,
|
|
460
|
+
TemplateFormModule,
|
|
461
|
+
TemplatePatch,
|
|
462
|
+
TemplateRichTextModule,
|
|
463
|
+
)
|
|
464
|
+
|
|
465
|
+
document = TemplateDocument.create(
|
|
466
|
+
(
|
|
467
|
+
TemplateFormModule(
|
|
468
|
+
name="Basic",
|
|
469
|
+
fields=(TemplateFormField(name="Temperature", field_type="number", required=True, unit="K"),),
|
|
470
|
+
),
|
|
471
|
+
TemplateRichTextModule(name="Notes", content="<p>Instructions</p>"),
|
|
472
|
+
)
|
|
473
|
+
)
|
|
474
|
+
template = await client.templates.create(title="Measurement")
|
|
475
|
+
await client.templates.save_document(template, document)
|
|
476
|
+
|
|
477
|
+
observed = await client.templates.read_document(template)
|
|
478
|
+
await client.templates.patch_content(
|
|
479
|
+
template,
|
|
480
|
+
TemplatePatch(
|
|
481
|
+
changes=(
|
|
482
|
+
TemplateFieldAdd(
|
|
483
|
+
module="Basic",
|
|
484
|
+
field=TemplateFormField(name="Operator", field_type="text", required=True),
|
|
485
|
+
),
|
|
486
|
+
)
|
|
487
|
+
),
|
|
488
|
+
expected_fingerprint=observed.fingerprint,
|
|
489
|
+
)
|
|
490
|
+
updated = await client.templates.read_document(template)
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
`TemplateDocument` is immutable at its interface, preserves unknown canonical properties, keeps existing UIDs stable,
|
|
494
|
+
and converts losslessly back to `TemplateModule`. Patches are ordered name-based intents and reject missing, duplicate,
|
|
495
|
+
or incompatible targets before saving. The fingerprint preflight detects a stale prior read, but is client-side only:
|
|
496
|
+
the current Provider has no atomic template patch or CAS (PCG-007). `patch_content()` returns `None`; explicitly
|
|
497
|
+
re-read before using a new fingerprint because the mutation response contains no saved representation.
|
|
498
|
+
|
|
453
499
|
The market result reports the Provider `total_count`, the requested and effective page sizes, and a `has_more`
|
|
454
500
|
value derived from the total; it does not claim a stable order or continuation token. Canonical
|
|
455
501
|
modules are mapped to public `TemplateModule` values and retain additive module attributes. Template reads remain
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Reusable async Python client for the Matelab Integration Contract.
|
|
4
4
|
|
|
5
|
-
The current alpha is `0.1.
|
|
5
|
+
The current alpha is `0.1.0a14`. `[project].version` in `pyproject.toml` is the sole SDK version source;
|
|
6
6
|
`uv.lock` only mirrors that source.
|
|
7
7
|
|
|
8
8
|
The SDK is pinned to the immutable `matelab-spec v0.4.0` Contract Release. The sole release pin is
|
|
@@ -31,7 +31,7 @@ To test the same artifact a downstream Consumer will install, build and install
|
|
|
31
31
|
|
|
32
32
|
```bash
|
|
33
33
|
uv build --no-build-isolation --out-dir dist/release
|
|
34
|
-
python -m pip install dist/release/matelab_python_sdk-0.1.
|
|
34
|
+
python -m pip install dist/release/matelab_python_sdk-0.1.0a14-py3-none-any.whl
|
|
35
35
|
```
|
|
36
36
|
|
|
37
37
|
Do not infer Provider compatibility from the SDK version alone. A release is also bound to the Contract
|
|
@@ -422,6 +422,52 @@ market = await client.templates.search_market("calibration", page=1, page_size=2
|
|
|
422
422
|
modules = await client.templates.read(templates.owned[0].ref)
|
|
423
423
|
```
|
|
424
424
|
|
|
425
|
+
Semantic template documents hide canonical `uid`, `width`, `data`, `rows`, `folders`, and option encoding:
|
|
426
|
+
|
|
427
|
+
```python
|
|
428
|
+
from matelab import (
|
|
429
|
+
TemplateDocument,
|
|
430
|
+
TemplateFieldAdd,
|
|
431
|
+
TemplateFormField,
|
|
432
|
+
TemplateFormModule,
|
|
433
|
+
TemplatePatch,
|
|
434
|
+
TemplateRichTextModule,
|
|
435
|
+
)
|
|
436
|
+
|
|
437
|
+
document = TemplateDocument.create(
|
|
438
|
+
(
|
|
439
|
+
TemplateFormModule(
|
|
440
|
+
name="Basic",
|
|
441
|
+
fields=(TemplateFormField(name="Temperature", field_type="number", required=True, unit="K"),),
|
|
442
|
+
),
|
|
443
|
+
TemplateRichTextModule(name="Notes", content="<p>Instructions</p>"),
|
|
444
|
+
)
|
|
445
|
+
)
|
|
446
|
+
template = await client.templates.create(title="Measurement")
|
|
447
|
+
await client.templates.save_document(template, document)
|
|
448
|
+
|
|
449
|
+
observed = await client.templates.read_document(template)
|
|
450
|
+
await client.templates.patch_content(
|
|
451
|
+
template,
|
|
452
|
+
TemplatePatch(
|
|
453
|
+
changes=(
|
|
454
|
+
TemplateFieldAdd(
|
|
455
|
+
module="Basic",
|
|
456
|
+
field=TemplateFormField(name="Operator", field_type="text", required=True),
|
|
457
|
+
),
|
|
458
|
+
)
|
|
459
|
+
),
|
|
460
|
+
expected_fingerprint=observed.fingerprint,
|
|
461
|
+
)
|
|
462
|
+
updated = await client.templates.read_document(template)
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
`TemplateDocument` is immutable at its interface, preserves unknown canonical properties, keeps existing UIDs stable,
|
|
466
|
+
and converts losslessly back to `TemplateModule`. Patches are ordered name-based intents and reject missing, duplicate,
|
|
467
|
+
or incompatible targets before saving. The fingerprint preflight detects a stale prior read, but is client-side only:
|
|
468
|
+
the current Provider has no atomic template patch or CAS (PCG-007). `patch_content()` returns `None`; explicitly
|
|
469
|
+
re-read before using a new fingerprint because the mutation response contains no saved representation.
|
|
470
|
+
|
|
425
471
|
The market result reports the Provider `total_count`, the requested and effective page sizes, and a `has_more`
|
|
426
472
|
value derived from the total; it does not claim a stable order or continuation token. Canonical
|
|
427
473
|
modules are mapped to public `TemplateModule` values and retain additive module attributes. Template reads remain
|
|
@@ -99,13 +99,38 @@ from matelab.domains.records import (
|
|
|
99
99
|
StagedRecordAttachment,
|
|
100
100
|
StagedRecordCommentAttachment,
|
|
101
101
|
)
|
|
102
|
+
from matelab.domains.template_documents import (
|
|
103
|
+
TemplateChange,
|
|
104
|
+
TemplateCodeLanguage,
|
|
105
|
+
TemplateCodeModule,
|
|
106
|
+
TemplateCodeStyle,
|
|
107
|
+
TemplateDocument,
|
|
108
|
+
TemplateFieldAdd,
|
|
109
|
+
TemplateFieldRemove,
|
|
110
|
+
TemplateFieldRename,
|
|
111
|
+
TemplateFieldUpdate,
|
|
112
|
+
TemplateFilesModule,
|
|
113
|
+
TemplateFormField,
|
|
114
|
+
TemplateFormFieldType,
|
|
115
|
+
TemplateFormModule,
|
|
116
|
+
TemplateModule,
|
|
117
|
+
TemplateModuleAdd,
|
|
118
|
+
TemplateModuleContentUpdate,
|
|
119
|
+
TemplateModuleDefinition,
|
|
120
|
+
TemplateModuleRemove,
|
|
121
|
+
TemplateModuleRename,
|
|
122
|
+
TemplatePatch,
|
|
123
|
+
TemplateRichTextModule,
|
|
124
|
+
TemplateTableColumn,
|
|
125
|
+
TemplateTableColumnType,
|
|
126
|
+
TemplateTableModule,
|
|
127
|
+
)
|
|
102
128
|
from matelab.domains.templates import (
|
|
103
129
|
TemplateCollection,
|
|
104
130
|
TemplateLibraryEntryRef,
|
|
105
131
|
TemplateLibraryRelationKind,
|
|
106
132
|
TemplateMarketEntry,
|
|
107
133
|
TemplateMarketPage,
|
|
108
|
-
TemplateModule,
|
|
109
134
|
TemplateRef,
|
|
110
135
|
TemplateSource,
|
|
111
136
|
TemplateSummary,
|
|
@@ -235,15 +260,38 @@ __all__ = [
|
|
|
235
260
|
"StagedRecordAttachment",
|
|
236
261
|
"StagedRecordCommentAttachment",
|
|
237
262
|
"StagedUploadSession",
|
|
263
|
+
"TemplateChange",
|
|
264
|
+
"TemplateCodeLanguage",
|
|
265
|
+
"TemplateCodeModule",
|
|
266
|
+
"TemplateCodeStyle",
|
|
238
267
|
"TemplateCollection",
|
|
268
|
+
"TemplateDocument",
|
|
269
|
+
"TemplateFieldAdd",
|
|
270
|
+
"TemplateFieldRemove",
|
|
271
|
+
"TemplateFieldRename",
|
|
272
|
+
"TemplateFieldUpdate",
|
|
273
|
+
"TemplateFilesModule",
|
|
274
|
+
"TemplateFormField",
|
|
275
|
+
"TemplateFormFieldType",
|
|
276
|
+
"TemplateFormModule",
|
|
239
277
|
"TemplateLibraryEntryRef",
|
|
240
278
|
"TemplateLibraryRelationKind",
|
|
241
279
|
"TemplateMarketEntry",
|
|
242
280
|
"TemplateMarketPage",
|
|
243
281
|
"TemplateModule",
|
|
282
|
+
"TemplateModuleAdd",
|
|
283
|
+
"TemplateModuleContentUpdate",
|
|
284
|
+
"TemplateModuleDefinition",
|
|
285
|
+
"TemplateModuleRemove",
|
|
286
|
+
"TemplateModuleRename",
|
|
287
|
+
"TemplatePatch",
|
|
244
288
|
"TemplateRef",
|
|
289
|
+
"TemplateRichTextModule",
|
|
245
290
|
"TemplateSource",
|
|
246
291
|
"TemplateSummary",
|
|
292
|
+
"TemplateTableColumn",
|
|
293
|
+
"TemplateTableColumnType",
|
|
294
|
+
"TemplateTableModule",
|
|
247
295
|
"Token",
|
|
248
296
|
"UploadBindingRef",
|
|
249
297
|
"UserRef",
|
|
@@ -12,7 +12,6 @@ from pydantic import BaseModel, JsonValue, ValidationError
|
|
|
12
12
|
from typing_extensions import override
|
|
13
13
|
|
|
14
14
|
from matelab._generated import models as wire_models
|
|
15
|
-
from matelab._wire_validation import validate_request
|
|
16
15
|
from matelab.errors import (
|
|
17
16
|
MatelabAuthenticationError,
|
|
18
17
|
MatelabErrorCategory,
|
|
@@ -22,7 +21,7 @@ from matelab.errors import (
|
|
|
22
21
|
MatelabUsageError,
|
|
23
22
|
)
|
|
24
23
|
from matelab.session import Session, Token
|
|
25
|
-
from matelab.streaming import DownloadStream
|
|
24
|
+
from matelab.streaming import ByteRange, DownloadStream
|
|
26
25
|
|
|
27
26
|
WireResponse = TypeVar("WireResponse", bound=BaseModel)
|
|
28
27
|
_ACCESS_EXPIRY_SKEW_MS = 30_000
|
|
@@ -152,12 +151,10 @@ class SessionTransport:
|
|
|
152
151
|
return session
|
|
153
152
|
if session.refresh.expires_at_ms <= time.time_ns() // 1_000_000:
|
|
154
153
|
raise MatelabAuthenticationError("Matelab refresh token expired.")
|
|
155
|
-
|
|
156
|
-
wire_models.TokenRefreshRequest
|
|
157
|
-
|
|
158
|
-
"The Matelab refresh token is invalid."
|
|
159
|
-
error_type=MatelabAuthenticationError,
|
|
160
|
-
)
|
|
154
|
+
try:
|
|
155
|
+
request = wire_models.TokenRefreshRequest.model_validate({"refresh_token": session.refresh.value})
|
|
156
|
+
except ValidationError:
|
|
157
|
+
raise MatelabAuthenticationError("The Matelab refresh token is invalid.") from None
|
|
161
158
|
response = await self.request(_REFRESH_TOKENS, payload=request)
|
|
162
159
|
self.session = Session(
|
|
163
160
|
access=Token(value=response.access.token, expires_at_ms=response.access.expiredAt),
|
|
@@ -266,15 +263,15 @@ class SessionTransport:
|
|
|
266
263
|
raise MatelabProtocolError("Matelab response does not match the Integration Contract.") from None
|
|
267
264
|
|
|
268
265
|
async def stream(
|
|
269
|
-
self, operation: StreamOperation, *, payload: _Payload,
|
|
266
|
+
self, operation: StreamOperation, *, payload: _Payload, byte_range: ByteRange | None = None
|
|
270
267
|
) -> DownloadStream:
|
|
271
268
|
encoded_payload = _payload_mapping(payload)
|
|
272
269
|
retry_access_expired = True
|
|
273
270
|
while True:
|
|
274
271
|
access_session = await self.session_for_request()
|
|
275
272
|
headers = {"Authorization": f"Bearer {access_session.access.value}"}
|
|
276
|
-
if
|
|
277
|
-
headers["Range"] =
|
|
273
|
+
if byte_range is not None:
|
|
274
|
+
headers["Range"] = byte_range.to_header()
|
|
278
275
|
|
|
279
276
|
request = self._http.build_request(
|
|
280
277
|
operation.method,
|
|
@@ -8,7 +8,7 @@ from jsonschema.protocols import Validator
|
|
|
8
8
|
from pydantic import BaseModel, JsonValue, ValidationError, model_validator
|
|
9
9
|
from pydantic_core import PydanticCustomError
|
|
10
10
|
|
|
11
|
-
from matelab.errors import
|
|
11
|
+
from matelab.errors import MatelabUsageError
|
|
12
12
|
|
|
13
13
|
_RUNTIME_RULES_KEY = "matelab-runtime-rules"
|
|
14
14
|
JsonObject = dict[str, JsonValue]
|
|
@@ -20,23 +20,19 @@ def validate_request(
|
|
|
20
20
|
model: type[ModelT],
|
|
21
21
|
data: Mapping[str, WireValue],
|
|
22
22
|
message: str,
|
|
23
|
-
*,
|
|
24
|
-
error_type: type[MatelabUsageError] | type[MatelabAuthenticationError] = MatelabUsageError,
|
|
25
23
|
) -> ModelT:
|
|
26
24
|
"""Validate caller input without exposing it in the public exception."""
|
|
27
25
|
try:
|
|
28
26
|
return model.model_validate(data)
|
|
29
27
|
except ValidationError:
|
|
30
|
-
raise
|
|
28
|
+
raise MatelabUsageError(message) from None
|
|
31
29
|
|
|
32
30
|
|
|
33
31
|
def encode_json_field(value: JsonValue, *, ensure_ascii: bool = True) -> str:
|
|
34
32
|
return json.dumps(value, ensure_ascii=ensure_ascii, separators=(",", ":"))
|
|
35
33
|
|
|
36
34
|
|
|
37
|
-
def _rules(extra: JsonObject
|
|
38
|
-
if extra is None:
|
|
39
|
-
return None
|
|
35
|
+
def _rules(extra: JsonObject) -> JsonObject | None:
|
|
40
36
|
rules = extra.get(_RUNTIME_RULES_KEY)
|
|
41
37
|
if rules is None:
|
|
42
38
|
return None
|
|
@@ -64,7 +60,7 @@ class WireModel(BaseModel):
|
|
|
64
60
|
data = cast(Mapping[str, WireValue], value)
|
|
65
61
|
|
|
66
62
|
model_extra = cls.model_config.get("json_schema_extra")
|
|
67
|
-
model_rules = _rules(model_extra if isinstance(model_extra, dict) else None
|
|
63
|
+
model_rules = _rules(model_extra) if isinstance(model_extra, dict) else None
|
|
68
64
|
if model_rules is not None:
|
|
69
65
|
_validate_schema(data, model_rules)
|
|
70
66
|
|
|
@@ -73,12 +69,12 @@ class WireModel(BaseModel):
|
|
|
73
69
|
if key not in data:
|
|
74
70
|
continue
|
|
75
71
|
extra = field.json_schema_extra
|
|
76
|
-
json_extra = extra if isinstance(extra, dict) else
|
|
72
|
+
json_extra = extra if isinstance(extra, dict) else {}
|
|
77
73
|
field_rules = _rules(json_extra)
|
|
78
74
|
raw_value = data[key]
|
|
79
75
|
if field_rules is not None:
|
|
80
76
|
_validate_schema(raw_value, field_rules)
|
|
81
|
-
content_schema = json_extra.get("contentSchema")
|
|
77
|
+
content_schema = json_extra.get("contentSchema")
|
|
82
78
|
if isinstance(content_schema, dict):
|
|
83
79
|
if raw_value in (None, "") and not field.is_required():
|
|
84
80
|
continue
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from typing import Self
|
|
2
2
|
|
|
3
3
|
from httpx2 import AsyncClient
|
|
4
|
+
from pydantic import ValidationError
|
|
4
5
|
|
|
5
6
|
from matelab._generated import models as wire_models
|
|
6
7
|
from matelab._transport import Encoding, Operation, SessionTransport
|
|
@@ -39,17 +40,18 @@ _CHAT_SSO = Operation(
|
|
|
39
40
|
def _session_from_wire(
|
|
40
41
|
source: wire_models.TokenLoginResponse | wire_models.ChatSsoCodeExchangeResponse,
|
|
41
42
|
) -> Session:
|
|
42
|
-
if source.username is None or not source.username.strip():
|
|
43
|
-
raise MatelabProtocolError("Matelab returned an identity with an empty username.")
|
|
44
43
|
if isinstance(source, wire_models.TokenLoginResponse):
|
|
45
44
|
access, refresh = source.access, source.refresh
|
|
46
45
|
else:
|
|
47
46
|
access, refresh = source.token.access, source.token.refresh
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
47
|
+
try:
|
|
48
|
+
return Session(
|
|
49
|
+
access=Token(value=access.token, expires_at_ms=access.expiredAt),
|
|
50
|
+
refresh=Token(value=refresh.token, expires_at_ms=refresh.expiredAt),
|
|
51
|
+
identity=Identity.model_validate(source, from_attributes=True),
|
|
52
|
+
)
|
|
53
|
+
except ValidationError:
|
|
54
|
+
raise MatelabProtocolError("Matelab returned invalid session data.") from None
|
|
53
55
|
|
|
54
56
|
|
|
55
57
|
class AsyncMatelab:
|
{matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a14}/src/matelab/domains/cloud_drive.py
RENAMED
|
@@ -332,6 +332,4 @@ class CloudDrive:
|
|
|
332
332
|
{"id": file.cloud_file_id, "thumb": int(thumbnail), "c": int(preview)},
|
|
333
333
|
"Cloud-drive file identity does not satisfy the Integration Contract.",
|
|
334
334
|
)
|
|
335
|
-
return await self._transport.stream(
|
|
336
|
-
_DOWNLOAD_FILE, payload=request, range_header=byte_range.to_header() if byte_range is not None else None
|
|
337
|
-
)
|
|
335
|
+
return await self._transport.stream(_DOWNLOAD_FILE, payload=request, byte_range=byte_range)
|
{matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a14}/src/matelab/domains/literature.py
RENAMED
|
@@ -718,9 +718,7 @@ class Literature:
|
|
|
718
718
|
},
|
|
719
719
|
"Literature PDF identity does not satisfy the Integration Contract.",
|
|
720
720
|
)
|
|
721
|
-
return await self._transport.stream(
|
|
722
|
-
_DOWNLOAD_PDF, payload=request, range_header=byte_range.to_header() if byte_range is not None else None
|
|
723
|
-
)
|
|
721
|
+
return await self._transport.stream(_DOWNLOAD_PDF, payload=request, byte_range=byte_range)
|
|
724
722
|
|
|
725
723
|
async def create(self, metadata: LiteratureMetadata, *, staged_pdf: StagedFile | None = None) -> None:
|
|
726
724
|
"""Create a literature item; the Provider acknowledgement contains no new item identity."""
|
|
@@ -1921,7 +1921,7 @@ class Records:
|
|
|
1921
1921
|
return await self._transport.stream(
|
|
1922
1922
|
_DOWNLOAD_RECORD_ATTACHMENT,
|
|
1923
1923
|
payload=request,
|
|
1924
|
-
|
|
1924
|
+
byte_range=byte_range,
|
|
1925
1925
|
)
|
|
1926
1926
|
|
|
1927
1927
|
async def download_comment_attachment(
|
|
@@ -1947,7 +1947,7 @@ class Records:
|
|
|
1947
1947
|
return await self._transport.stream(
|
|
1948
1948
|
_DOWNLOAD_COMMENT_ATTACHMENT,
|
|
1949
1949
|
payload=request,
|
|
1950
|
-
|
|
1950
|
+
byte_range=byte_range,
|
|
1951
1951
|
)
|
|
1952
1952
|
|
|
1953
1953
|
async def read(
|