matelab-python-sdk 0.1.0a2__tar.gz → 0.1.0a3__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.0a3/.env.example +6 -0
- {matelab_python_sdk-0.1.0a2 → matelab_python_sdk-0.1.0a3}/CHANGELOG.md +13 -0
- {matelab_python_sdk-0.1.0a2 → matelab_python_sdk-0.1.0a3}/PKG-INFO +76 -82
- {matelab_python_sdk-0.1.0a2 → matelab_python_sdk-0.1.0a3}/README.md +73 -79
- {matelab_python_sdk-0.1.0a2 → matelab_python_sdk-0.1.0a3}/docs/roadmap.md +35 -24
- {matelab_python_sdk-0.1.0a2 → matelab_python_sdk-0.1.0a3}/pyproject.toml +4 -9
- {matelab_python_sdk-0.1.0a2 → matelab_python_sdk-0.1.0a3}/scripts/check_installed_package.py +1 -1
- {matelab_python_sdk-0.1.0a2 → matelab_python_sdk-0.1.0a3}/scripts/check_release.py +7 -3
- {matelab_python_sdk-0.1.0a2 → matelab_python_sdk-0.1.0a3}/src/matelab/__init__.py +0 -68
- {matelab_python_sdk-0.1.0a2 → matelab_python_sdk-0.1.0a3}/src/matelab/_transport.py +12 -12
- {matelab_python_sdk-0.1.0a2 → matelab_python_sdk-0.1.0a3}/src/matelab/client.py +17 -24
- {matelab_python_sdk-0.1.0a2 → matelab_python_sdk-0.1.0a3}/src/matelab/cloud_drive.py +48 -210
- {matelab_python_sdk-0.1.0a2 → matelab_python_sdk-0.1.0a3}/src/matelab/groups.py +3 -5
- matelab_python_sdk-0.1.0a2/src/matelab/literature.py → matelab_python_sdk-0.1.0a3/src/matelab/literature/__init__.py +64 -233
- {matelab_python_sdk-0.1.0a2 → matelab_python_sdk-0.1.0a3}/src/matelab/notebooks.py +33 -124
- matelab_python_sdk-0.1.0a2/src/matelab/records.py → matelab_python_sdk-0.1.0a3/src/matelab/records/__init__.py +100 -350
- {matelab_python_sdk-0.1.0a2 → matelab_python_sdk-0.1.0a3}/src/matelab/streaming.py +3 -3
- {matelab_python_sdk-0.1.0a2 → matelab_python_sdk-0.1.0a3}/src/matelab/templates.py +62 -188
- {matelab_python_sdk-0.1.0a2 → matelab_python_sdk-0.1.0a3}/src/matelab/uploads.py +13 -38
- {matelab_python_sdk-0.1.0a2 → matelab_python_sdk-0.1.0a3}/src/matelab/users.py +3 -6
- matelab_python_sdk-0.1.0a3/tests/conftest.py +58 -0
- matelab_python_sdk-0.1.0a3/tests/provider/test_provider_smoke.py +68 -0
- {matelab_python_sdk-0.1.0a2 → matelab_python_sdk-0.1.0a3}/tests/test_client.py +72 -80
- {matelab_python_sdk-0.1.0a2 → matelab_python_sdk-0.1.0a3}/tests/test_cloud_drive.py +55 -65
- {matelab_python_sdk-0.1.0a2 → matelab_python_sdk-0.1.0a3}/tests/test_comments_and_downloads.py +21 -22
- {matelab_python_sdk-0.1.0a2 → matelab_python_sdk-0.1.0a3}/tests/test_consumer_smoke.py +7 -7
- {matelab_python_sdk-0.1.0a2 → matelab_python_sdk-0.1.0a3}/tests/test_extended_records.py +51 -56
- {matelab_python_sdk-0.1.0a2 → matelab_python_sdk-0.1.0a3}/tests/test_groups.py +13 -14
- {matelab_python_sdk-0.1.0a2 → matelab_python_sdk-0.1.0a3}/tests/test_notebook_lifecycle.py +74 -88
- matelab_python_sdk-0.1.0a3/tests/test_notebooks.py +71 -0
- {matelab_python_sdk-0.1.0a2 → matelab_python_sdk-0.1.0a3}/tests/test_operation_coverage.py +2 -2
- {matelab_python_sdk-0.1.0a2 → matelab_python_sdk-0.1.0a3}/tests/test_record_lifecycle.py +61 -74
- {matelab_python_sdk-0.1.0a2 → matelab_python_sdk-0.1.0a3}/tests/test_record_mutations.py +109 -140
- {matelab_python_sdk-0.1.0a2 → matelab_python_sdk-0.1.0a3}/tests/test_records.py +82 -64
- {matelab_python_sdk-0.1.0a2 → matelab_python_sdk-0.1.0a3}/tests/test_release.py +36 -4
- {matelab_python_sdk-0.1.0a2 → matelab_python_sdk-0.1.0a3}/tests/test_streaming.py +41 -41
- {matelab_python_sdk-0.1.0a2 → matelab_python_sdk-0.1.0a3}/tests/test_template_lifecycle.py +96 -112
- {matelab_python_sdk-0.1.0a2 → matelab_python_sdk-0.1.0a3}/tests/test_templates.py +39 -40
- {matelab_python_sdk-0.1.0a2 → matelab_python_sdk-0.1.0a3}/tests/test_transport.py +87 -87
- {matelab_python_sdk-0.1.0a2 → matelab_python_sdk-0.1.0a3}/tests/test_uploads_and_literature.py +90 -106
- {matelab_python_sdk-0.1.0a2 → matelab_python_sdk-0.1.0a3}/tests/test_users.py +30 -31
- {matelab_python_sdk-0.1.0a2 → matelab_python_sdk-0.1.0a3}/uv.lock +27 -31
- matelab_python_sdk-0.1.0a2/tests/conftest.py +0 -20
- matelab_python_sdk-0.1.0a2/tests/provider/test_provider_smoke.py +0 -128
- matelab_python_sdk-0.1.0a2/tests/test_notebooks.py +0 -128
- {matelab_python_sdk-0.1.0a2 → matelab_python_sdk-0.1.0a3}/.github/workflows/release.yml +0 -0
- {matelab_python_sdk-0.1.0a2 → matelab_python_sdk-0.1.0a3}/.gitignore +0 -0
- {matelab_python_sdk-0.1.0a2 → matelab_python_sdk-0.1.0a3}/AGENTS.md +0 -0
- {matelab_python_sdk-0.1.0a2 → matelab_python_sdk-0.1.0a3}/LICENSE +0 -0
- {matelab_python_sdk-0.1.0a2 → matelab_python_sdk-0.1.0a3}/NOTICE +0 -0
- {matelab_python_sdk-0.1.0a2 → matelab_python_sdk-0.1.0a3}/contracts/matelab-integration-v1.lock.json +0 -0
- {matelab_python_sdk-0.1.0a2 → matelab_python_sdk-0.1.0a3}/contracts/matelab-integration-v1.openapi.yaml +0 -0
- {matelab_python_sdk-0.1.0a2 → matelab_python_sdk-0.1.0a3}/docs/operation-coverage.yaml +0 -0
- {matelab_python_sdk-0.1.0a2 → matelab_python_sdk-0.1.0a3}/scripts/generate_models.py +0 -0
- {matelab_python_sdk-0.1.0a2 → matelab_python_sdk-0.1.0a3}/src/matelab/_generated/__init__.py +0 -0
- {matelab_python_sdk-0.1.0a2 → matelab_python_sdk-0.1.0a3}/src/matelab/_generated/models.py +0 -0
- {matelab_python_sdk-0.1.0a2 → matelab_python_sdk-0.1.0a3}/src/matelab/errors.py +0 -0
- {matelab_python_sdk-0.1.0a2 → matelab_python_sdk-0.1.0a3}/src/matelab/models.py +0 -0
- {matelab_python_sdk-0.1.0a2 → matelab_python_sdk-0.1.0a3}/src/matelab/py.typed +0 -0
- {matelab_python_sdk-0.1.0a2 → matelab_python_sdk-0.1.0a3}/tests/test_generation.py +0 -0
- {matelab_python_sdk-0.1.0a2 → matelab_python_sdk-0.1.0a3}/tests/typing/consumer.py +0 -0
- {matelab_python_sdk-0.1.0a2 → matelab_python_sdk-0.1.0a3}/tests/typing/pyproject.toml +0 -0
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# Provider consumer tests must target the confirmed isolated test service.
|
|
2
|
+
# Authentication and external-token refresh persist Provider token state.
|
|
3
|
+
# Keep these names aligned with matelab-spec Provider Verification.
|
|
4
|
+
MATELAB_PROVIDER_BASE_URL=
|
|
5
|
+
MATELAB_PROVIDER_USERNAME=
|
|
6
|
+
MATELAB_PROVIDER_PASSWORD=
|
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.0a3 - 2026-07-28
|
|
4
|
+
|
|
5
|
+
- Replaces HTTPX with HTTPX2 2.9.1 and raises the Pydantic compatibility floor to 2.13.4. Injected clients must now
|
|
6
|
+
be `httpx2.AsyncClient` instances; HTTPX and HTTPX2 objects are not interchangeable. Default TLS verification now
|
|
7
|
+
uses the operating system trust store.
|
|
8
|
+
- Shrinks the alpha public surface from 184 to 150 symbols by returning existing domain values, tuples, booleans, or
|
|
9
|
+
`None` from simple mutations instead of defining endpoint-shaped result dataclasses. Contract invariants remain
|
|
10
|
+
documented operation semantics rather than repeated constant fields.
|
|
11
|
+
- Reorganizes the record and literature implementations into domain packages and keeps generated wire imports behind
|
|
12
|
+
one private namespace without changing the generated-model boundary.
|
|
13
|
+
- Aligns the opt-in SDK Provider smoke with the isolated test service's shared `MATELAB_PROVIDER_*` environment
|
|
14
|
+
variables and adds a safe `.env.example`.
|
|
15
|
+
|
|
3
16
|
## 0.1.0a2 - 2026-07-27
|
|
4
17
|
|
|
5
18
|
- Adopts the immutable `matelab-spec v0.1.1` release.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: matelab-python-sdk
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.0a3
|
|
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
|
|
@@ -20,15 +20,15 @@ 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:
|
|
24
|
-
Requires-Dist: pydantic<3,>=2.
|
|
23
|
+
Requires-Dist: httpx2<3,>=2.9.1
|
|
24
|
+
Requires-Dist: pydantic<3,>=2.13.4
|
|
25
25
|
Description-Content-Type: text/markdown
|
|
26
26
|
|
|
27
27
|
# matelab-python-sdk
|
|
28
28
|
|
|
29
29
|
Reusable async Python client for the Matelab Integration Contract.
|
|
30
30
|
|
|
31
|
-
The current alpha is `0.1.
|
|
31
|
+
The current alpha is `0.1.0a3`. `[project].version` in `pyproject.toml` is the sole SDK version source;
|
|
32
32
|
`uv.lock` only mirrors that source.
|
|
33
33
|
|
|
34
34
|
The SDK is pinned to the immutable `matelab-spec v0.1.1` Contract Release. The sole release pin is
|
|
@@ -57,7 +57,7 @@ To test the same artifact a downstream Consumer will install, build and install
|
|
|
57
57
|
|
|
58
58
|
```bash
|
|
59
59
|
uv build --no-build-isolation --out-dir dist/release
|
|
60
|
-
python -m pip install dist/release/matelab_python_sdk-0.1.
|
|
60
|
+
python -m pip install dist/release/matelab_python_sdk-0.1.0a3-py3-none-any.whl
|
|
61
61
|
```
|
|
62
62
|
|
|
63
63
|
Do not infer Provider compatibility from the SDK version alone. A release is also bound to the Contract
|
|
@@ -155,6 +155,9 @@ alice_client = AsyncMatelab(session=alice_session, http_client=shared_http_clien
|
|
|
155
155
|
bob_client = AsyncMatelab(session=bob_session, http_client=shared_http_client)
|
|
156
156
|
```
|
|
157
157
|
|
|
158
|
+
An injected `http_client` must be an `httpx2.AsyncClient`. The independently distributed `httpx.AsyncClient` has
|
|
159
|
+
similar methods but uses incompatible request, response, transport, and exception types.
|
|
160
|
+
|
|
158
161
|
`src/matelab/_generated` is a private wire layer. Applications should not depend on its file
|
|
159
162
|
layout or generated class names. The distribution includes `py.typed`, so type checkers can consume the
|
|
160
163
|
public annotations directly from an installed wheel.
|
|
@@ -284,8 +287,8 @@ groups = await client.groups.list()
|
|
|
284
287
|
targets = await client.users.search("Example Researcher", scope=UserSearchScope.SAME_INSTITUTE)
|
|
285
288
|
```
|
|
286
289
|
|
|
287
|
-
|
|
288
|
-
`groups.members_for`, not to every returned group. These two discovery interfaces are experimental because the
|
|
290
|
+
Ordering remains Provider-unspecified and is documented rather than repeated as a constant result field. Group
|
|
291
|
+
members belong only to `groups.members_for`, not to every returned group. These two discovery interfaces are experimental because the
|
|
289
292
|
Provider returns members for an unstable first group and user search is unpaged, unordered, and not field-minimized
|
|
290
293
|
(PVD-006, PVD-029, PCG-011).
|
|
291
294
|
|
|
@@ -294,17 +297,16 @@ Notebook create/update and direct sharing keep write acknowledgement separate fr
|
|
|
294
297
|
```python
|
|
295
298
|
from matelab import NotebookMetadata, NotebookSharePermissionGrant
|
|
296
299
|
|
|
297
|
-
|
|
300
|
+
await client.notebooks.create(NotebookMetadata(title="Example Notebook"))
|
|
298
301
|
shares = await client.notebooks.share(notebook, [target.ref])
|
|
299
|
-
updated = await client.notebooks.update_share(
|
|
300
|
-
shares.visible_after_write[0].ref, NotebookSharePermissionGrant(write=True, create=True)
|
|
301
|
-
)
|
|
302
|
+
updated = await client.notebooks.update_share(shares[0].ref, NotebookSharePermissionGrant(write=True, create=True))
|
|
302
303
|
```
|
|
303
304
|
|
|
304
|
-
Create
|
|
305
|
-
ID
|
|
306
|
-
|
|
307
|
-
share mask of zero still has effective read access (PVD-010), and share-list order remains
|
|
305
|
+
Create returns `None` because the Provider returns no identity. Owned updates return the notebook row observed by
|
|
306
|
+
database ID, or `None` when it is missing, containing PVD-003 false success without claiming an atomic guarantee.
|
|
307
|
+
Share returns only requested relations visible during readback, and permission updates return the observed relation
|
|
308
|
+
or `None`. A stored share mask of zero still has effective read access (PVD-010), and share-list order remains
|
|
309
|
+
unspecified.
|
|
308
310
|
|
|
309
311
|
Template discovery keeps a template database identity separate from direct-share, market-acquisition, and group
|
|
310
312
|
relation identities:
|
|
@@ -316,18 +318,18 @@ content = await client.templates.read(templates.owned[0].ref)
|
|
|
316
318
|
```
|
|
317
319
|
|
|
318
320
|
The market result reports the Provider `total_count`, the requested and effective page sizes, and a `has_more`
|
|
319
|
-
value
|
|
321
|
+
value derived from the total; it does not claim a stable order or continuation token. Canonical
|
|
320
322
|
modules are mapped to public `TemplateModule` values and retain additive module attributes. Template reads remain
|
|
321
323
|
experimental because Provider discovery ordering/pagination and historical `images` compatibility are not fully
|
|
322
324
|
stable (PCG-003, PCG-009, PVD-013, PVD-022).
|
|
323
325
|
|
|
324
326
|
Template writes remain separate operations: metadata, canonical modules, and usage HTML are not presented as one
|
|
325
|
-
transaction. Metadata create returns the Provider template ID
|
|
326
|
-
Provider supplies
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
327
|
+
transaction. Metadata create returns the Provider template ID plus an optional observed summary; copy and share
|
|
328
|
+
return `None` because the Provider supplies no new identity or per-recipient rows. Group addition and market
|
|
329
|
+
acquisition return the observed relation or `None`, while removal returns readback-confirmed absence as `bool`.
|
|
330
|
+
Direct-share, market-acquisition, and group relation refs remain distinct. Marketplace revision and uploader-binding
|
|
331
|
+
limitations are documented operation semantics rather than constant fields on every result (PVD-021, PVD-026).
|
|
332
|
+
`UploadBindingRef.new()` creates the fresh hidden correlation value required by intro attachment binding.
|
|
331
333
|
|
|
332
334
|
Extended record reads stay behind the same `records` interface:
|
|
333
335
|
|
|
@@ -362,8 +364,8 @@ imported = await client.records.import_dataset(
|
|
|
362
364
|
)
|
|
363
365
|
```
|
|
364
366
|
|
|
365
|
-
A caller-supplied blank-record UID
|
|
366
|
-
|
|
367
|
+
A caller-supplied blank-record UID returns its matching readback; when the Provider generates it, create returns
|
|
368
|
+
`None` rather than inventing identity. Import validates the complete batch with generated wire models but cannot map returned database
|
|
367
369
|
IDs to individual inputs or promise atomicity (PCG-008). Delete means moving records into the recycle bin, not
|
|
368
370
|
permanent deletion. Delete and restore results classify only post-write observations; restore never treats an active
|
|
369
371
|
row with the same UID but a different database ID as proof of success (PCG-005). Record mutations are not
|
|
@@ -373,19 +375,21 @@ Record patching exposes a deliberately narrower capability than the raw Provider
|
|
|
373
375
|
cannot smuggle Provider-native attachment strings; staged attachments use separate form-removal, table-replacement,
|
|
374
376
|
files append/replace/remove, and rich-text types. Unsafe form replacement and table-file removal are absent, while a
|
|
375
377
|
files/images removal is rejected when the observed module contains the same hash more than once (PVD-014 through
|
|
376
|
-
PVD-016). `Record.content_sha256` can be supplied as a client-side precondition,
|
|
377
|
-
|
|
378
|
+
PVD-016). `Record.content_sha256` can be supplied as a client-side precondition, documented as advisory
|
|
379
|
+
read-before-write rather than Provider CAS. `RecordUpdateResult` retains only the before/after evidence and whether
|
|
380
|
+
the acknowledgement means persisted, pending browser save, or unclassified. Database, active-browser, and unclassified acknowledgements
|
|
378
381
|
remain distinct, and mutation retries stay disabled.
|
|
379
382
|
|
|
380
383
|
Relation addition reads both endpoints and checks their resolved data server before writing; this reduces PVD-019
|
|
381
|
-
risk but is not an atomic Provider authorization guarantee. Relation
|
|
382
|
-
|
|
383
|
-
|
|
384
|
+
risk but is not an atomic Provider authorization guarantee. Relation addition returns matching relations observed
|
|
385
|
+
afterward. Relation deletion refuses an observed cross-notebook target-ID collision because the Provider ignores
|
|
386
|
+
target notebook identity (PVD-020), then returns the complete post-write relation snapshot.
|
|
384
387
|
|
|
385
388
|
Comment upload follows the Provider's literal one-request `upload` field, not the incompatible Front fragment
|
|
386
|
-
protocol (PVD-037). Comment
|
|
387
|
-
a currently observed caller-owned comment and read it back, containing the
|
|
388
|
-
(PVD-004). Staged comment attachments have no Contract abort operation, and
|
|
389
|
+
protocol (PVD-037). Comment create/update return matching post-write comment rows; delete returns whether the selected
|
|
390
|
+
comment is absent. Edit and delete require a currently observed caller-owned comment and read it back, containing the
|
|
391
|
+
Provider's edit false-success behavior (PVD-004). Staged comment attachments have no Contract abort operation, and
|
|
392
|
+
binding remains affected by PVD-026.
|
|
389
393
|
|
|
390
394
|
Attachment bytes are streamed and must be consumed or closed explicitly:
|
|
391
395
|
|
|
@@ -425,7 +429,8 @@ For multiple fragments, pass `StagedFileFragment.session` into the next call. `n
|
|
|
425
429
|
caller-side total derived from declared fragment sizes; the Provider does not confirm an offset. A final result
|
|
426
430
|
contains the Provider hash, size, temporary row identity and fresh hidden binding value, but does not claim that a
|
|
427
431
|
later literature/cloud operation checks the uploader or consumes the file exactly once. `uploads.abort` exposes the
|
|
428
|
-
Provider's legacy code-2 cancellation signal as compensating cleanup that is not independently
|
|
432
|
+
Provider's legacy code-2 cancellation signal as a `None`-returning compensating cleanup that is not independently
|
|
433
|
+
verified (PVD-028).
|
|
429
434
|
Staging mutations are never automatically retried.
|
|
430
435
|
|
|
431
436
|
Literature identities distinguish the personal library, shared libraries and pending incoming copies:
|
|
@@ -441,18 +446,15 @@ schema = await client.literature.creation_schema()
|
|
|
441
446
|
if schema.metadata_extraction_available:
|
|
442
447
|
candidates = await client.literature.extract_metadata(DoiMetadataSource("10.0000/example"))
|
|
443
448
|
|
|
444
|
-
|
|
445
|
-
LiteratureMetadata(title="Example import", doi="10.0000/example"), staged_pdf=staged
|
|
446
|
-
)
|
|
447
|
-
assert created.created_item is None
|
|
449
|
+
await client.literature.create(LiteratureMetadata(title="Example import", doi="10.0000/example"), staged_pdf=staged)
|
|
448
450
|
```
|
|
449
451
|
|
|
450
|
-
Create never guesses the new item from list position because the Provider returns no ID. Canonical update reads the
|
|
452
|
+
Create returns `None` and never guesses the new item from list position because the Provider returns no ID. Canonical update reads the
|
|
451
453
|
item first and refuses to drop source/hidden fields unless `allow_source_metadata_loss=True` is explicit (PVD-027).
|
|
452
454
|
PDF replace/delete are separate, read-back-verified mutations and are not presented as atomic with metadata
|
|
453
|
-
(PCG-010). Permanent personal deletion is named `permanently_delete
|
|
454
|
-
non-recoverable. Sharing requires list-observed item summaries, user-search summaries and a
|
|
455
|
-
|
|
455
|
+
(PCG-010). Permanent personal deletion is named `permanently_delete` and returns snapshot-confirmed absence as a
|
|
456
|
+
`bool`; the operation is non-recoverable. Sharing requires list-observed item summaries, user-search summaries and a
|
|
457
|
+
resolved caller identity, then returns `None` because the Provider supplies no per-recipient IDs (PVD-012, PVD-036).
|
|
456
458
|
|
|
457
459
|
Literature comments use one public save intent: detail is read first, an existing caller-owned comment is edited, and
|
|
458
460
|
otherwise a comment is created. Multiple caller-owned comments are rejected as ambiguous (PVD-035). A staged
|
|
@@ -469,11 +471,11 @@ from matelab import CloudFolderMetadata
|
|
|
469
471
|
|
|
470
472
|
listing = await client.cloud_drive.list()
|
|
471
473
|
folder_result = await client.cloud_drive.create_folder(CloudFolderMetadata(name="Example data"))
|
|
472
|
-
|
|
474
|
+
matches = await client.cloud_drive.bind_staged_file(staged, target=folder_result.folder)
|
|
473
475
|
|
|
474
|
-
if
|
|
476
|
+
if len(matches) == 1:
|
|
475
477
|
renamed = await client.cloud_drive.update_file(
|
|
476
|
-
|
|
478
|
+
matches[0], filename="example-renamed.pdf", description="Sanitized description"
|
|
477
479
|
)
|
|
478
480
|
```
|
|
479
481
|
|
|
@@ -482,12 +484,14 @@ than flattening them into one ambiguous collection. Folder browse results retain
|
|
|
482
484
|
are explicitly root-wide and return `location=None` because the Provider omits each match's folder ID. Ordering has
|
|
483
485
|
no stable ID tie-breaker (PCG-003, PVD-013).
|
|
484
486
|
|
|
485
|
-
Folder create returns the Provider ID and all folder mutations read back the
|
|
486
|
-
completed `StagedFile`, then paginates the target folder and returns
|
|
487
|
-
filename/hash/size
|
|
487
|
+
Folder create returns the Provider ID plus an optional observed folder, and all folder mutations read back the
|
|
488
|
+
complete tree. Staged finalize accepts a completed `StagedFile`, then paginates the target folder and returns all
|
|
489
|
+
exact filename/hash/size matches; zero, one, or multiple results preserve missing and ambiguous observations without
|
|
490
|
+
another result model. This is useful evidence, not an uploader-ownership guarantee: the Provider binds by
|
|
488
491
|
temporary row ID without checking its owner (PVD-031), and finalize atomicity/idempotency remain absent (PCG-012).
|
|
489
|
-
Batch move
|
|
490
|
-
|
|
492
|
+
Batch move returns identities observed in the target, while permanent delete returns identities confirmed absent;
|
|
493
|
+
neither claims Provider per-item results or atomicity. Permanent deletion is named `permanently_delete_files` and is
|
|
494
|
+
non-recoverable. Cloud downloads
|
|
491
495
|
resolve bytes from the final file identity and reuse `DownloadStream`, thumbnail/preview choices and the PVD-002-safe
|
|
492
496
|
range subset. Cloud mutations are not automatically retried.
|
|
493
497
|
|
|
@@ -526,8 +530,8 @@ generates private component, operation-response, and parameter models. The proje
|
|
|
526
530
|
object inheritance that the generator cannot otherwise preserve correctly; the checked-in release
|
|
527
531
|
snapshot remains unchanged. `--check` performs the same validation and deterministic generation without
|
|
528
532
|
writing the checked-in models. The current lock resolves `datamodel-code-generator 0.71.0` and
|
|
529
|
-
`hatchling 1.31.0`. Published metadata requires `
|
|
530
|
-
`pydantic>=2.
|
|
533
|
+
`hatchling 1.31.0`. Published metadata requires `httpx2>=2.9.1,<3` and
|
|
534
|
+
`pydantic>=2.13.4,<3`; the build backend requires `hatchling>=1.27,<2`. These lower bounds are verified
|
|
531
535
|
against the complete test suite on the supported Python boundary versions rather than inferred from
|
|
532
536
|
`uv.lock`. The exact toolchain remains locked for development and release builds. Basedpyright and its
|
|
533
537
|
Node wheel retain the compatible exact pair `basedpyright==1.39.9` and
|
|
@@ -535,47 +539,37 @@ Node wheel retain the compatible exact pair `basedpyright==1.39.9` and
|
|
|
535
539
|
|
|
536
540
|
## Opt-in Provider consumer smoke
|
|
537
541
|
|
|
538
|
-
`tests/provider/test_provider_smoke.py` exercises the consumer flow through only the public SDK interface
|
|
539
|
-
credential authentication, extraction of the returned token pair, exchange through a new
|
|
540
|
-
`AsyncMatelab.bind_external_tokens` instance, userid consistency checks,
|
|
541
|
-
|
|
542
|
+
`tests/provider/test_provider_smoke.py` exercises the consumer flow through only the public SDK interface. Its base
|
|
543
|
+
scenario covers credential authentication, extraction of the returned token pair, exchange through a new
|
|
544
|
+
`AsyncMatelab.bind_external_tokens` instance, userid consistency checks, and notebook discovery. It is not Provider
|
|
545
|
+
Verification and is skipped by default.
|
|
542
546
|
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
+
Raw Provider conformance remains the responsibility of `matelab-spec`, which sends direct HTTP requests and validates
|
|
548
|
+
the unmodified responses. The SDK does not repeat its route-by-route, cross-account, sharing, or attachment-isolation
|
|
549
|
+
verification. Representative SDK adapter tests instead feed the pinned OpenAPI's sanitized response examples through
|
|
550
|
+
`MockTransport` and assert the resulting public values; synthetic fixtures remain where SDK-specific encoding,
|
|
551
|
+
error, retry, and compatibility boundaries require evidence beyond those examples.
|
|
547
552
|
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
MATELAB_PROVIDER_SMOKE_ALLOW_WRITES=1
|
|
552
|
-
```
|
|
553
|
-
|
|
554
|
-
For an explicitly authorized production test fixture, leave `MATELAB_PROVIDER_SMOKE_ISOLATED` empty and use:
|
|
555
|
-
|
|
556
|
-
```dotenv
|
|
557
|
-
MATELAB_PROVIDER_SMOKE_PRODUCTION_ACKNOWLEDGED=1
|
|
558
|
-
```
|
|
553
|
+
Provider smoke is restricted to the confirmed isolated test service. Authentication and external-token refresh
|
|
554
|
+
persist Provider token state. This side effect is inherent to the tested Provider operations; it cannot be disabled
|
|
555
|
+
by a test setting. Explicitly loading `.env.test` and selecting the `provider` marker is the opt-in for this flow.
|
|
559
556
|
|
|
560
|
-
|
|
557
|
+
Copy `.env.example` to the git-ignored local `.env.test`, then fill in the shared Provider connection settings:
|
|
561
558
|
|
|
562
|
-
- `
|
|
563
|
-
- `
|
|
564
|
-
- `
|
|
565
|
-
- `MATELAB_PROVIDER_SMOKE_EXPECTED_USERID`
|
|
566
|
-
- `MATELAB_PROVIDER_SMOKE_NOTEBOOK_ID`
|
|
567
|
-
- `MATELAB_PROVIDER_SMOKE_RECORD_DATABASE_ID`
|
|
568
|
-
- `MATELAB_PROVIDER_SMOKE_RECORD_UID`
|
|
559
|
+
- `MATELAB_PROVIDER_BASE_URL`
|
|
560
|
+
- `MATELAB_PROVIDER_USERNAME`
|
|
561
|
+
- `MATELAB_PROVIDER_PASSWORD`
|
|
569
562
|
|
|
570
|
-
|
|
571
|
-
|
|
563
|
+
These names intentionally match `matelab-spec` Provider Verification. The isolated target may copy them from the spec
|
|
564
|
+
`.env` into this repository's `.env.test`. The identities returned by authentication and external token binding must
|
|
565
|
+
agree. With the environment prepared:
|
|
572
566
|
|
|
573
567
|
```bash
|
|
574
568
|
uv run --env-file .env.test pytest -m provider tests/provider/test_provider_smoke.py
|
|
575
569
|
```
|
|
576
570
|
|
|
577
|
-
The file is never loaded implicitly, so the normal test suite remains safely skipped.
|
|
578
|
-
|
|
571
|
+
The file is never loaded implicitly, so the normal test suite remains safely skipped. Do not use this flow against
|
|
572
|
+
production, and never commit its credentials.
|
|
579
573
|
|
|
580
574
|
## Reproducible release build
|
|
581
575
|
|
|
@@ -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.0a3`. `[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.1.1` 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.0a3-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
|
|
@@ -129,6 +129,9 @@ alice_client = AsyncMatelab(session=alice_session, http_client=shared_http_clien
|
|
|
129
129
|
bob_client = AsyncMatelab(session=bob_session, http_client=shared_http_client)
|
|
130
130
|
```
|
|
131
131
|
|
|
132
|
+
An injected `http_client` must be an `httpx2.AsyncClient`. The independently distributed `httpx.AsyncClient` has
|
|
133
|
+
similar methods but uses incompatible request, response, transport, and exception types.
|
|
134
|
+
|
|
132
135
|
`src/matelab/_generated` is a private wire layer. Applications should not depend on its file
|
|
133
136
|
layout or generated class names. The distribution includes `py.typed`, so type checkers can consume the
|
|
134
137
|
public annotations directly from an installed wheel.
|
|
@@ -258,8 +261,8 @@ groups = await client.groups.list()
|
|
|
258
261
|
targets = await client.users.search("Example Researcher", scope=UserSearchScope.SAME_INSTITUTE)
|
|
259
262
|
```
|
|
260
263
|
|
|
261
|
-
|
|
262
|
-
`groups.members_for`, not to every returned group. These two discovery interfaces are experimental because the
|
|
264
|
+
Ordering remains Provider-unspecified and is documented rather than repeated as a constant result field. Group
|
|
265
|
+
members belong only to `groups.members_for`, not to every returned group. These two discovery interfaces are experimental because the
|
|
263
266
|
Provider returns members for an unstable first group and user search is unpaged, unordered, and not field-minimized
|
|
264
267
|
(PVD-006, PVD-029, PCG-011).
|
|
265
268
|
|
|
@@ -268,17 +271,16 @@ Notebook create/update and direct sharing keep write acknowledgement separate fr
|
|
|
268
271
|
```python
|
|
269
272
|
from matelab import NotebookMetadata, NotebookSharePermissionGrant
|
|
270
273
|
|
|
271
|
-
|
|
274
|
+
await client.notebooks.create(NotebookMetadata(title="Example Notebook"))
|
|
272
275
|
shares = await client.notebooks.share(notebook, [target.ref])
|
|
273
|
-
updated = await client.notebooks.update_share(
|
|
274
|
-
shares.visible_after_write[0].ref, NotebookSharePermissionGrant(write=True, create=True)
|
|
275
|
-
)
|
|
276
|
+
updated = await client.notebooks.update_share(shares[0].ref, NotebookSharePermissionGrant(write=True, create=True))
|
|
276
277
|
```
|
|
277
278
|
|
|
278
|
-
Create
|
|
279
|
-
ID
|
|
280
|
-
|
|
281
|
-
share mask of zero still has effective read access (PVD-010), and share-list order remains
|
|
279
|
+
Create returns `None` because the Provider returns no identity. Owned updates return the notebook row observed by
|
|
280
|
+
database ID, or `None` when it is missing, containing PVD-003 false success without claiming an atomic guarantee.
|
|
281
|
+
Share returns only requested relations visible during readback, and permission updates return the observed relation
|
|
282
|
+
or `None`. A stored share mask of zero still has effective read access (PVD-010), and share-list order remains
|
|
283
|
+
unspecified.
|
|
282
284
|
|
|
283
285
|
Template discovery keeps a template database identity separate from direct-share, market-acquisition, and group
|
|
284
286
|
relation identities:
|
|
@@ -290,18 +292,18 @@ content = await client.templates.read(templates.owned[0].ref)
|
|
|
290
292
|
```
|
|
291
293
|
|
|
292
294
|
The market result reports the Provider `total_count`, the requested and effective page sizes, and a `has_more`
|
|
293
|
-
value
|
|
295
|
+
value derived from the total; it does not claim a stable order or continuation token. Canonical
|
|
294
296
|
modules are mapped to public `TemplateModule` values and retain additive module attributes. Template reads remain
|
|
295
297
|
experimental because Provider discovery ordering/pagination and historical `images` compatibility are not fully
|
|
296
298
|
stable (PCG-003, PCG-009, PVD-013, PVD-022).
|
|
297
299
|
|
|
298
300
|
Template writes remain separate operations: metadata, canonical modules, and usage HTML are not presented as one
|
|
299
|
-
transaction. Metadata create returns the Provider template ID
|
|
300
|
-
Provider supplies
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
301
|
+
transaction. Metadata create returns the Provider template ID plus an optional observed summary; copy and share
|
|
302
|
+
return `None` because the Provider supplies no new identity or per-recipient rows. Group addition and market
|
|
303
|
+
acquisition return the observed relation or `None`, while removal returns readback-confirmed absence as `bool`.
|
|
304
|
+
Direct-share, market-acquisition, and group relation refs remain distinct. Marketplace revision and uploader-binding
|
|
305
|
+
limitations are documented operation semantics rather than constant fields on every result (PVD-021, PVD-026).
|
|
306
|
+
`UploadBindingRef.new()` creates the fresh hidden correlation value required by intro attachment binding.
|
|
305
307
|
|
|
306
308
|
Extended record reads stay behind the same `records` interface:
|
|
307
309
|
|
|
@@ -336,8 +338,8 @@ imported = await client.records.import_dataset(
|
|
|
336
338
|
)
|
|
337
339
|
```
|
|
338
340
|
|
|
339
|
-
A caller-supplied blank-record UID
|
|
340
|
-
|
|
341
|
+
A caller-supplied blank-record UID returns its matching readback; when the Provider generates it, create returns
|
|
342
|
+
`None` rather than inventing identity. Import validates the complete batch with generated wire models but cannot map returned database
|
|
341
343
|
IDs to individual inputs or promise atomicity (PCG-008). Delete means moving records into the recycle bin, not
|
|
342
344
|
permanent deletion. Delete and restore results classify only post-write observations; restore never treats an active
|
|
343
345
|
row with the same UID but a different database ID as proof of success (PCG-005). Record mutations are not
|
|
@@ -347,19 +349,21 @@ Record patching exposes a deliberately narrower capability than the raw Provider
|
|
|
347
349
|
cannot smuggle Provider-native attachment strings; staged attachments use separate form-removal, table-replacement,
|
|
348
350
|
files append/replace/remove, and rich-text types. Unsafe form replacement and table-file removal are absent, while a
|
|
349
351
|
files/images removal is rejected when the observed module contains the same hash more than once (PVD-014 through
|
|
350
|
-
PVD-016). `Record.content_sha256` can be supplied as a client-side precondition,
|
|
351
|
-
|
|
352
|
+
PVD-016). `Record.content_sha256` can be supplied as a client-side precondition, documented as advisory
|
|
353
|
+
read-before-write rather than Provider CAS. `RecordUpdateResult` retains only the before/after evidence and whether
|
|
354
|
+
the acknowledgement means persisted, pending browser save, or unclassified. Database, active-browser, and unclassified acknowledgements
|
|
352
355
|
remain distinct, and mutation retries stay disabled.
|
|
353
356
|
|
|
354
357
|
Relation addition reads both endpoints and checks their resolved data server before writing; this reduces PVD-019
|
|
355
|
-
risk but is not an atomic Provider authorization guarantee. Relation
|
|
356
|
-
|
|
357
|
-
|
|
358
|
+
risk but is not an atomic Provider authorization guarantee. Relation addition returns matching relations observed
|
|
359
|
+
afterward. Relation deletion refuses an observed cross-notebook target-ID collision because the Provider ignores
|
|
360
|
+
target notebook identity (PVD-020), then returns the complete post-write relation snapshot.
|
|
358
361
|
|
|
359
362
|
Comment upload follows the Provider's literal one-request `upload` field, not the incompatible Front fragment
|
|
360
|
-
protocol (PVD-037). Comment
|
|
361
|
-
a currently observed caller-owned comment and read it back, containing the
|
|
362
|
-
(PVD-004). Staged comment attachments have no Contract abort operation, and
|
|
363
|
+
protocol (PVD-037). Comment create/update return matching post-write comment rows; delete returns whether the selected
|
|
364
|
+
comment is absent. Edit and delete require a currently observed caller-owned comment and read it back, containing the
|
|
365
|
+
Provider's edit false-success behavior (PVD-004). Staged comment attachments have no Contract abort operation, and
|
|
366
|
+
binding remains affected by PVD-026.
|
|
363
367
|
|
|
364
368
|
Attachment bytes are streamed and must be consumed or closed explicitly:
|
|
365
369
|
|
|
@@ -399,7 +403,8 @@ For multiple fragments, pass `StagedFileFragment.session` into the next call. `n
|
|
|
399
403
|
caller-side total derived from declared fragment sizes; the Provider does not confirm an offset. A final result
|
|
400
404
|
contains the Provider hash, size, temporary row identity and fresh hidden binding value, but does not claim that a
|
|
401
405
|
later literature/cloud operation checks the uploader or consumes the file exactly once. `uploads.abort` exposes the
|
|
402
|
-
Provider's legacy code-2 cancellation signal as compensating cleanup that is not independently
|
|
406
|
+
Provider's legacy code-2 cancellation signal as a `None`-returning compensating cleanup that is not independently
|
|
407
|
+
verified (PVD-028).
|
|
403
408
|
Staging mutations are never automatically retried.
|
|
404
409
|
|
|
405
410
|
Literature identities distinguish the personal library, shared libraries and pending incoming copies:
|
|
@@ -415,18 +420,15 @@ schema = await client.literature.creation_schema()
|
|
|
415
420
|
if schema.metadata_extraction_available:
|
|
416
421
|
candidates = await client.literature.extract_metadata(DoiMetadataSource("10.0000/example"))
|
|
417
422
|
|
|
418
|
-
|
|
419
|
-
LiteratureMetadata(title="Example import", doi="10.0000/example"), staged_pdf=staged
|
|
420
|
-
)
|
|
421
|
-
assert created.created_item is None
|
|
423
|
+
await client.literature.create(LiteratureMetadata(title="Example import", doi="10.0000/example"), staged_pdf=staged)
|
|
422
424
|
```
|
|
423
425
|
|
|
424
|
-
Create never guesses the new item from list position because the Provider returns no ID. Canonical update reads the
|
|
426
|
+
Create returns `None` and never guesses the new item from list position because the Provider returns no ID. Canonical update reads the
|
|
425
427
|
item first and refuses to drop source/hidden fields unless `allow_source_metadata_loss=True` is explicit (PVD-027).
|
|
426
428
|
PDF replace/delete are separate, read-back-verified mutations and are not presented as atomic with metadata
|
|
427
|
-
(PCG-010). Permanent personal deletion is named `permanently_delete
|
|
428
|
-
non-recoverable. Sharing requires list-observed item summaries, user-search summaries and a
|
|
429
|
-
|
|
429
|
+
(PCG-010). Permanent personal deletion is named `permanently_delete` and returns snapshot-confirmed absence as a
|
|
430
|
+
`bool`; the operation is non-recoverable. Sharing requires list-observed item summaries, user-search summaries and a
|
|
431
|
+
resolved caller identity, then returns `None` because the Provider supplies no per-recipient IDs (PVD-012, PVD-036).
|
|
430
432
|
|
|
431
433
|
Literature comments use one public save intent: detail is read first, an existing caller-owned comment is edited, and
|
|
432
434
|
otherwise a comment is created. Multiple caller-owned comments are rejected as ambiguous (PVD-035). A staged
|
|
@@ -443,11 +445,11 @@ from matelab import CloudFolderMetadata
|
|
|
443
445
|
|
|
444
446
|
listing = await client.cloud_drive.list()
|
|
445
447
|
folder_result = await client.cloud_drive.create_folder(CloudFolderMetadata(name="Example data"))
|
|
446
|
-
|
|
448
|
+
matches = await client.cloud_drive.bind_staged_file(staged, target=folder_result.folder)
|
|
447
449
|
|
|
448
|
-
if
|
|
450
|
+
if len(matches) == 1:
|
|
449
451
|
renamed = await client.cloud_drive.update_file(
|
|
450
|
-
|
|
452
|
+
matches[0], filename="example-renamed.pdf", description="Sanitized description"
|
|
451
453
|
)
|
|
452
454
|
```
|
|
453
455
|
|
|
@@ -456,12 +458,14 @@ than flattening them into one ambiguous collection. Folder browse results retain
|
|
|
456
458
|
are explicitly root-wide and return `location=None` because the Provider omits each match's folder ID. Ordering has
|
|
457
459
|
no stable ID tie-breaker (PCG-003, PVD-013).
|
|
458
460
|
|
|
459
|
-
Folder create returns the Provider ID and all folder mutations read back the
|
|
460
|
-
completed `StagedFile`, then paginates the target folder and returns
|
|
461
|
-
filename/hash/size
|
|
461
|
+
Folder create returns the Provider ID plus an optional observed folder, and all folder mutations read back the
|
|
462
|
+
complete tree. Staged finalize accepts a completed `StagedFile`, then paginates the target folder and returns all
|
|
463
|
+
exact filename/hash/size matches; zero, one, or multiple results preserve missing and ambiguous observations without
|
|
464
|
+
another result model. This is useful evidence, not an uploader-ownership guarantee: the Provider binds by
|
|
462
465
|
temporary row ID without checking its owner (PVD-031), and finalize atomicity/idempotency remain absent (PCG-012).
|
|
463
|
-
Batch move
|
|
464
|
-
|
|
466
|
+
Batch move returns identities observed in the target, while permanent delete returns identities confirmed absent;
|
|
467
|
+
neither claims Provider per-item results or atomicity. Permanent deletion is named `permanently_delete_files` and is
|
|
468
|
+
non-recoverable. Cloud downloads
|
|
465
469
|
resolve bytes from the final file identity and reuse `DownloadStream`, thumbnail/preview choices and the PVD-002-safe
|
|
466
470
|
range subset. Cloud mutations are not automatically retried.
|
|
467
471
|
|
|
@@ -500,8 +504,8 @@ generates private component, operation-response, and parameter models. The proje
|
|
|
500
504
|
object inheritance that the generator cannot otherwise preserve correctly; the checked-in release
|
|
501
505
|
snapshot remains unchanged. `--check` performs the same validation and deterministic generation without
|
|
502
506
|
writing the checked-in models. The current lock resolves `datamodel-code-generator 0.71.0` and
|
|
503
|
-
`hatchling 1.31.0`. Published metadata requires `
|
|
504
|
-
`pydantic>=2.
|
|
507
|
+
`hatchling 1.31.0`. Published metadata requires `httpx2>=2.9.1,<3` and
|
|
508
|
+
`pydantic>=2.13.4,<3`; the build backend requires `hatchling>=1.27,<2`. These lower bounds are verified
|
|
505
509
|
against the complete test suite on the supported Python boundary versions rather than inferred from
|
|
506
510
|
`uv.lock`. The exact toolchain remains locked for development and release builds. Basedpyright and its
|
|
507
511
|
Node wheel retain the compatible exact pair `basedpyright==1.39.9` and
|
|
@@ -509,47 +513,37 @@ Node wheel retain the compatible exact pair `basedpyright==1.39.9` and
|
|
|
509
513
|
|
|
510
514
|
## Opt-in Provider consumer smoke
|
|
511
515
|
|
|
512
|
-
`tests/provider/test_provider_smoke.py` exercises the consumer flow through only the public SDK interface
|
|
513
|
-
credential authentication, extraction of the returned token pair, exchange through a new
|
|
514
|
-
`AsyncMatelab.bind_external_tokens` instance, userid consistency checks,
|
|
515
|
-
|
|
516
|
+
`tests/provider/test_provider_smoke.py` exercises the consumer flow through only the public SDK interface. Its base
|
|
517
|
+
scenario covers credential authentication, extraction of the returned token pair, exchange through a new
|
|
518
|
+
`AsyncMatelab.bind_external_tokens` instance, userid consistency checks, and notebook discovery. It is not Provider
|
|
519
|
+
Verification and is skipped by default.
|
|
516
520
|
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
+
Raw Provider conformance remains the responsibility of `matelab-spec`, which sends direct HTTP requests and validates
|
|
522
|
+
the unmodified responses. The SDK does not repeat its route-by-route, cross-account, sharing, or attachment-isolation
|
|
523
|
+
verification. Representative SDK adapter tests instead feed the pinned OpenAPI's sanitized response examples through
|
|
524
|
+
`MockTransport` and assert the resulting public values; synthetic fixtures remain where SDK-specific encoding,
|
|
525
|
+
error, retry, and compatibility boundaries require evidence beyond those examples.
|
|
521
526
|
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
MATELAB_PROVIDER_SMOKE_ALLOW_WRITES=1
|
|
526
|
-
```
|
|
527
|
-
|
|
528
|
-
For an explicitly authorized production test fixture, leave `MATELAB_PROVIDER_SMOKE_ISOLATED` empty and use:
|
|
529
|
-
|
|
530
|
-
```dotenv
|
|
531
|
-
MATELAB_PROVIDER_SMOKE_PRODUCTION_ACKNOWLEDGED=1
|
|
532
|
-
```
|
|
527
|
+
Provider smoke is restricted to the confirmed isolated test service. Authentication and external-token refresh
|
|
528
|
+
persist Provider token state. This side effect is inherent to the tested Provider operations; it cannot be disabled
|
|
529
|
+
by a test setting. Explicitly loading `.env.test` and selecting the `provider` marker is the opt-in for this flow.
|
|
533
530
|
|
|
534
|
-
|
|
531
|
+
Copy `.env.example` to the git-ignored local `.env.test`, then fill in the shared Provider connection settings:
|
|
535
532
|
|
|
536
|
-
- `
|
|
537
|
-
- `
|
|
538
|
-
- `
|
|
539
|
-
- `MATELAB_PROVIDER_SMOKE_EXPECTED_USERID`
|
|
540
|
-
- `MATELAB_PROVIDER_SMOKE_NOTEBOOK_ID`
|
|
541
|
-
- `MATELAB_PROVIDER_SMOKE_RECORD_DATABASE_ID`
|
|
542
|
-
- `MATELAB_PROVIDER_SMOKE_RECORD_UID`
|
|
533
|
+
- `MATELAB_PROVIDER_BASE_URL`
|
|
534
|
+
- `MATELAB_PROVIDER_USERNAME`
|
|
535
|
+
- `MATELAB_PROVIDER_PASSWORD`
|
|
543
536
|
|
|
544
|
-
|
|
545
|
-
|
|
537
|
+
These names intentionally match `matelab-spec` Provider Verification. The isolated target may copy them from the spec
|
|
538
|
+
`.env` into this repository's `.env.test`. The identities returned by authentication and external token binding must
|
|
539
|
+
agree. With the environment prepared:
|
|
546
540
|
|
|
547
541
|
```bash
|
|
548
542
|
uv run --env-file .env.test pytest -m provider tests/provider/test_provider_smoke.py
|
|
549
543
|
```
|
|
550
544
|
|
|
551
|
-
The file is never loaded implicitly, so the normal test suite remains safely skipped.
|
|
552
|
-
|
|
545
|
+
The file is never loaded implicitly, so the normal test suite remains safely skipped. Do not use this flow against
|
|
546
|
+
production, and never commit its credentials.
|
|
553
547
|
|
|
554
548
|
## Reproducible release build
|
|
555
549
|
|