matelab-python-sdk 0.1.0a13__tar.gz → 0.1.0a15__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.0a13 → matelab_python_sdk-0.1.0a15}/CHANGELOG.md +16 -0
- {matelab_python_sdk-0.1.0a13 → matelab_python_sdk-0.1.0a15}/PKG-INFO +61 -5
- {matelab_python_sdk-0.1.0a13 → matelab_python_sdk-0.1.0a15}/README.md +60 -4
- {matelab_python_sdk-0.1.0a13 → matelab_python_sdk-0.1.0a15}/pyproject.toml +1 -1
- {matelab_python_sdk-0.1.0a13 → matelab_python_sdk-0.1.0a15}/src/matelab/__init__.py +49 -3
- {matelab_python_sdk-0.1.0a13 → matelab_python_sdk-0.1.0a15}/src/matelab/domains/notebooks.py +37 -14
- {matelab_python_sdk-0.1.0a13 → matelab_python_sdk-0.1.0a15}/src/matelab/domains/records.py +31 -14
- matelab_python_sdk-0.1.0a15/src/matelab/domains/template_documents.py +527 -0
- {matelab_python_sdk-0.1.0a13 → matelab_python_sdk-0.1.0a15}/src/matelab/domains/templates.py +26 -12
- {matelab_python_sdk-0.1.0a13 → matelab_python_sdk-0.1.0a15}/tests/test_extended_records.py +17 -9
- {matelab_python_sdk-0.1.0a13 → matelab_python_sdk-0.1.0a15}/tests/test_notebook_lifecycle.py +16 -0
- {matelab_python_sdk-0.1.0a13 → matelab_python_sdk-0.1.0a15}/tests/test_notebooks.py +9 -5
- {matelab_python_sdk-0.1.0a13 → matelab_python_sdk-0.1.0a15}/tests/test_records.py +0 -12
- matelab_python_sdk-0.1.0a15/tests/test_template_documents.py +393 -0
- {matelab_python_sdk-0.1.0a13 → matelab_python_sdk-0.1.0a15}/tests/typing/consumer.py +27 -0
- {matelab_python_sdk-0.1.0a13 → matelab_python_sdk-0.1.0a15}/uv.lock +1 -1
- {matelab_python_sdk-0.1.0a13 → matelab_python_sdk-0.1.0a15}/.env.example +0 -0
- {matelab_python_sdk-0.1.0a13 → matelab_python_sdk-0.1.0a15}/.github/workflows/release.yml +0 -0
- {matelab_python_sdk-0.1.0a13 → matelab_python_sdk-0.1.0a15}/.gitignore +0 -0
- {matelab_python_sdk-0.1.0a13 → matelab_python_sdk-0.1.0a15}/AGENTS.md +0 -0
- {matelab_python_sdk-0.1.0a13 → matelab_python_sdk-0.1.0a15}/LICENSE +0 -0
- {matelab_python_sdk-0.1.0a13 → matelab_python_sdk-0.1.0a15}/NOTICE +0 -0
- {matelab_python_sdk-0.1.0a13 → matelab_python_sdk-0.1.0a15}/contracts/matelab-integration-v1.lock.json +0 -0
- {matelab_python_sdk-0.1.0a13 → matelab_python_sdk-0.1.0a15}/contracts/matelab-integration-v1.openapi.yaml +0 -0
- {matelab_python_sdk-0.1.0a13 → matelab_python_sdk-0.1.0a15}/docs/operation-coverage.yaml +0 -0
- {matelab_python_sdk-0.1.0a13 → matelab_python_sdk-0.1.0a15}/scripts/check_installed_package.py +0 -0
- {matelab_python_sdk-0.1.0a13 → matelab_python_sdk-0.1.0a15}/scripts/check_release.py +0 -0
- {matelab_python_sdk-0.1.0a13 → matelab_python_sdk-0.1.0a15}/scripts/generate_models.py +0 -0
- {matelab_python_sdk-0.1.0a13 → matelab_python_sdk-0.1.0a15}/src/matelab/_generated/__init__.py +0 -0
- {matelab_python_sdk-0.1.0a13 → matelab_python_sdk-0.1.0a15}/src/matelab/_generated/models.py +0 -0
- {matelab_python_sdk-0.1.0a13 → matelab_python_sdk-0.1.0a15}/src/matelab/_transport.py +0 -0
- {matelab_python_sdk-0.1.0a13 → matelab_python_sdk-0.1.0a15}/src/matelab/_wire_validation.py +0 -0
- {matelab_python_sdk-0.1.0a13 → matelab_python_sdk-0.1.0a15}/src/matelab/client.py +0 -0
- {matelab_python_sdk-0.1.0a13 → matelab_python_sdk-0.1.0a15}/src/matelab/domains/__init__.py +0 -0
- {matelab_python_sdk-0.1.0a13 → matelab_python_sdk-0.1.0a15}/src/matelab/domains/_attachment_content.py +0 -0
- {matelab_python_sdk-0.1.0a13 → matelab_python_sdk-0.1.0a15}/src/matelab/domains/cloud_drive.py +0 -0
- {matelab_python_sdk-0.1.0a13 → matelab_python_sdk-0.1.0a15}/src/matelab/domains/groups.py +0 -0
- {matelab_python_sdk-0.1.0a13 → matelab_python_sdk-0.1.0a15}/src/matelab/domains/literature.py +0 -0
- {matelab_python_sdk-0.1.0a13 → matelab_python_sdk-0.1.0a15}/src/matelab/domains/uploads.py +0 -0
- {matelab_python_sdk-0.1.0a13 → matelab_python_sdk-0.1.0a15}/src/matelab/domains/users.py +0 -0
- {matelab_python_sdk-0.1.0a13 → matelab_python_sdk-0.1.0a15}/src/matelab/errors.py +0 -0
- {matelab_python_sdk-0.1.0a13 → matelab_python_sdk-0.1.0a15}/src/matelab/py.typed +0 -0
- {matelab_python_sdk-0.1.0a13 → matelab_python_sdk-0.1.0a15}/src/matelab/session.py +0 -0
- {matelab_python_sdk-0.1.0a13 → matelab_python_sdk-0.1.0a15}/src/matelab/streaming.py +0 -0
- {matelab_python_sdk-0.1.0a13 → matelab_python_sdk-0.1.0a15}/tests/conftest.py +0 -0
- {matelab_python_sdk-0.1.0a13 → matelab_python_sdk-0.1.0a15}/tests/provider/test_provider_smoke.py +0 -0
- {matelab_python_sdk-0.1.0a13 → matelab_python_sdk-0.1.0a15}/tests/test_async_attachment_upload.py +0 -0
- {matelab_python_sdk-0.1.0a13 → matelab_python_sdk-0.1.0a15}/tests/test_client.py +0 -0
- {matelab_python_sdk-0.1.0a13 → matelab_python_sdk-0.1.0a15}/tests/test_cloud_drive.py +0 -0
- {matelab_python_sdk-0.1.0a13 → matelab_python_sdk-0.1.0a15}/tests/test_comments_and_downloads.py +0 -0
- {matelab_python_sdk-0.1.0a13 → matelab_python_sdk-0.1.0a15}/tests/test_consumer_smoke.py +0 -0
- {matelab_python_sdk-0.1.0a13 → matelab_python_sdk-0.1.0a15}/tests/test_errors.py +0 -0
- {matelab_python_sdk-0.1.0a13 → matelab_python_sdk-0.1.0a15}/tests/test_generation.py +0 -0
- {matelab_python_sdk-0.1.0a13 → matelab_python_sdk-0.1.0a15}/tests/test_groups.py +0 -0
- {matelab_python_sdk-0.1.0a13 → matelab_python_sdk-0.1.0a15}/tests/test_operation_coverage.py +0 -0
- {matelab_python_sdk-0.1.0a13 → matelab_python_sdk-0.1.0a15}/tests/test_record_lifecycle.py +0 -0
- {matelab_python_sdk-0.1.0a13 → matelab_python_sdk-0.1.0a15}/tests/test_record_mutations.py +0 -0
- {matelab_python_sdk-0.1.0a13 → matelab_python_sdk-0.1.0a15}/tests/test_release.py +0 -0
- {matelab_python_sdk-0.1.0a13 → matelab_python_sdk-0.1.0a15}/tests/test_streaming.py +0 -0
- {matelab_python_sdk-0.1.0a13 → matelab_python_sdk-0.1.0a15}/tests/test_template_lifecycle.py +0 -0
- {matelab_python_sdk-0.1.0a13 → matelab_python_sdk-0.1.0a15}/tests/test_templates.py +0 -0
- {matelab_python_sdk-0.1.0a13 → matelab_python_sdk-0.1.0a15}/tests/test_transport.py +0 -0
- {matelab_python_sdk-0.1.0a13 → matelab_python_sdk-0.1.0a15}/tests/test_uploads_and_literature.py +0 -0
- {matelab_python_sdk-0.1.0a13 → matelab_python_sdk-0.1.0a15}/tests/test_users.py +0 -0
- {matelab_python_sdk-0.1.0a13 → matelab_python_sdk-0.1.0a15}/tests/typing/pyproject.toml +0 -0
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.0a15 - 2026-08-10
|
|
4
|
+
|
|
5
|
+
- Breaking: removes `PublicNotebookRef` and uses one `NotebookRef` with `scope="owned"`, `"shared"`, or `"public"`.
|
|
6
|
+
Public `notebook_id` values identify `eln_public` catalog rows rather than source ELNs; public record results retain
|
|
7
|
+
their separate source notebook identity.
|
|
8
|
+
- Centralizes notebook-scope validation: public record listing accepts only a public ref with a non-empty title,
|
|
9
|
+
private operations reject public refs, and owned-only notebook mutations reject shared/public refs. `NotebookRef.title`
|
|
10
|
+
is a non-empty `str` preserved without whitespace normalization; an unexpected null from the wider owned/shared wire
|
|
11
|
+
contract is reported as a protocol violation, matching Provider write validation and audited runtime data.
|
|
12
|
+
|
|
13
|
+
## 0.1.0a14 - 2026-08-10
|
|
14
|
+
|
|
15
|
+
- Adds immutable semantic template modules, editable `TemplateDocument` values, ordered name-based patch intents,
|
|
16
|
+
stable content fingerprints, and `Templates.read_document()`, `save_document()`, and `patch_content()` while
|
|
17
|
+
retaining the low-level `TemplateModule`, `read()`, and `save_content()` interface.
|
|
18
|
+
|
|
3
19
|
## 0.1.0a13 - 2026-08-08
|
|
4
20
|
|
|
5
21
|
- Breaking: `Identity.username` now removes surrounding whitespace and rejects values that become empty.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: matelab-python-sdk
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.0a15
|
|
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.0a15`. `[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.0a15-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
|
|
@@ -612,8 +658,18 @@ deletion is named `permanently_delete_files` and is non-recoverable. Cloud downl
|
|
|
612
658
|
resolve bytes from the final file identity and reuse `DownloadStream`, thumbnail/preview choices and the PVD-002-safe
|
|
613
659
|
range subset. Cloud mutations are not automatically retried.
|
|
614
660
|
|
|
615
|
-
Owned
|
|
616
|
-
|
|
661
|
+
Owned, shared, and public catalog notebooks use one `NotebookRef`, distinguished by its
|
|
662
|
+
`scope` (`"owned"`, `"shared"`, or `"public"`). For owned/shared refs, `notebook_id` is the source `eln.id`;
|
|
663
|
+
for public refs it is the distinct `eln_public.id` catalog identity and must not be treated as a source ELN ID.
|
|
664
|
+
Public record entries expose that source identity separately as `PublicRecordRef.source_notebook_id`.
|
|
665
|
+
`NotebookRef.title` is a non-empty `str` preserved exactly as returned, without whitespace normalization. The pinned
|
|
666
|
+
owned/shared wire field remains nullable because the database column allows null, but Provider writes require a
|
|
667
|
+
non-empty title and a runtime database audit found no null or empty titles; the SDK therefore treats a null
|
|
668
|
+
notebook-list title as a protocol violation instead of exposing it publicly. Public
|
|
669
|
+
record listing additionally requires a non-empty title selector. Private operations reject public scope, and
|
|
670
|
+
public record listing accepts only public scope, while
|
|
671
|
+
owned-only notebook mutations continue to reject shared/public refs. `RecordRef` and `RecordVersionRef` keep
|
|
672
|
+
their Provider identifiers distinct. Historical reads first re-read the authorized current record and confirm
|
|
617
673
|
that the requested version is still present in its `modify_log`; both reads write Provider audit entries.
|
|
618
674
|
|
|
619
675
|
Errors are separated into semantic Provider errors, authentication errors, HTTP/transport errors,
|
|
@@ -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.0a15`. `[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.0a15-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
|
|
@@ -584,8 +630,18 @@ deletion is named `permanently_delete_files` and is non-recoverable. Cloud downl
|
|
|
584
630
|
resolve bytes from the final file identity and reuse `DownloadStream`, thumbnail/preview choices and the PVD-002-safe
|
|
585
631
|
range subset. Cloud mutations are not automatically retried.
|
|
586
632
|
|
|
587
|
-
Owned
|
|
588
|
-
|
|
633
|
+
Owned, shared, and public catalog notebooks use one `NotebookRef`, distinguished by its
|
|
634
|
+
`scope` (`"owned"`, `"shared"`, or `"public"`). For owned/shared refs, `notebook_id` is the source `eln.id`;
|
|
635
|
+
for public refs it is the distinct `eln_public.id` catalog identity and must not be treated as a source ELN ID.
|
|
636
|
+
Public record entries expose that source identity separately as `PublicRecordRef.source_notebook_id`.
|
|
637
|
+
`NotebookRef.title` is a non-empty `str` preserved exactly as returned, without whitespace normalization. The pinned
|
|
638
|
+
owned/shared wire field remains nullable because the database column allows null, but Provider writes require a
|
|
639
|
+
non-empty title and a runtime database audit found no null or empty titles; the SDK therefore treats a null
|
|
640
|
+
notebook-list title as a protocol violation instead of exposing it publicly. Public
|
|
641
|
+
record listing additionally requires a non-empty title selector. Private operations reject public scope, and
|
|
642
|
+
public record listing accepts only public scope, while
|
|
643
|
+
owned-only notebook mutations continue to reject shared/public refs. `RecordRef` and `RecordVersionRef` keep
|
|
644
|
+
their Provider identifiers distinct. Historical reads first re-read the authorized current record and confirm
|
|
589
645
|
that the requested version is still present in its `modify_log`; both reads write Provider audit entries.
|
|
590
646
|
|
|
591
647
|
Errors are separated into semantic Provider errors, authentication errors, HTTP/transport errors,
|
|
@@ -42,7 +42,6 @@ from matelab.domains.notebooks import (
|
|
|
42
42
|
NotebookShareRef,
|
|
43
43
|
NotebookShares,
|
|
44
44
|
NotebookSummary,
|
|
45
|
-
PublicNotebookRef,
|
|
46
45
|
PublicNotebookSummary,
|
|
47
46
|
)
|
|
48
47
|
from matelab.domains.records import (
|
|
@@ -99,13 +98,38 @@ from matelab.domains.records import (
|
|
|
99
98
|
StagedRecordAttachment,
|
|
100
99
|
StagedRecordCommentAttachment,
|
|
101
100
|
)
|
|
101
|
+
from matelab.domains.template_documents import (
|
|
102
|
+
TemplateChange,
|
|
103
|
+
TemplateCodeLanguage,
|
|
104
|
+
TemplateCodeModule,
|
|
105
|
+
TemplateCodeStyle,
|
|
106
|
+
TemplateDocument,
|
|
107
|
+
TemplateFieldAdd,
|
|
108
|
+
TemplateFieldRemove,
|
|
109
|
+
TemplateFieldRename,
|
|
110
|
+
TemplateFieldUpdate,
|
|
111
|
+
TemplateFilesModule,
|
|
112
|
+
TemplateFormField,
|
|
113
|
+
TemplateFormFieldType,
|
|
114
|
+
TemplateFormModule,
|
|
115
|
+
TemplateModule,
|
|
116
|
+
TemplateModuleAdd,
|
|
117
|
+
TemplateModuleContentUpdate,
|
|
118
|
+
TemplateModuleDefinition,
|
|
119
|
+
TemplateModuleRemove,
|
|
120
|
+
TemplateModuleRename,
|
|
121
|
+
TemplatePatch,
|
|
122
|
+
TemplateRichTextModule,
|
|
123
|
+
TemplateTableColumn,
|
|
124
|
+
TemplateTableColumnType,
|
|
125
|
+
TemplateTableModule,
|
|
126
|
+
)
|
|
102
127
|
from matelab.domains.templates import (
|
|
103
128
|
TemplateCollection,
|
|
104
129
|
TemplateLibraryEntryRef,
|
|
105
130
|
TemplateLibraryRelationKind,
|
|
106
131
|
TemplateMarketEntry,
|
|
107
132
|
TemplateMarketPage,
|
|
108
|
-
TemplateModule,
|
|
109
133
|
TemplateRef,
|
|
110
134
|
TemplateSource,
|
|
111
135
|
TemplateSummary,
|
|
@@ -180,7 +204,6 @@ __all__ = [
|
|
|
180
204
|
"PendingLiteratureShare",
|
|
181
205
|
"PendingLiteratureShareComment",
|
|
182
206
|
"PersonalLiteratureItemRef",
|
|
183
|
-
"PublicNotebookRef",
|
|
184
207
|
"PublicNotebookSummary",
|
|
185
208
|
"PublicRecordRef",
|
|
186
209
|
"PublicRecordSummary",
|
|
@@ -235,15 +258,38 @@ __all__ = [
|
|
|
235
258
|
"StagedRecordAttachment",
|
|
236
259
|
"StagedRecordCommentAttachment",
|
|
237
260
|
"StagedUploadSession",
|
|
261
|
+
"TemplateChange",
|
|
262
|
+
"TemplateCodeLanguage",
|
|
263
|
+
"TemplateCodeModule",
|
|
264
|
+
"TemplateCodeStyle",
|
|
238
265
|
"TemplateCollection",
|
|
266
|
+
"TemplateDocument",
|
|
267
|
+
"TemplateFieldAdd",
|
|
268
|
+
"TemplateFieldRemove",
|
|
269
|
+
"TemplateFieldRename",
|
|
270
|
+
"TemplateFieldUpdate",
|
|
271
|
+
"TemplateFilesModule",
|
|
272
|
+
"TemplateFormField",
|
|
273
|
+
"TemplateFormFieldType",
|
|
274
|
+
"TemplateFormModule",
|
|
239
275
|
"TemplateLibraryEntryRef",
|
|
240
276
|
"TemplateLibraryRelationKind",
|
|
241
277
|
"TemplateMarketEntry",
|
|
242
278
|
"TemplateMarketPage",
|
|
243
279
|
"TemplateModule",
|
|
280
|
+
"TemplateModuleAdd",
|
|
281
|
+
"TemplateModuleContentUpdate",
|
|
282
|
+
"TemplateModuleDefinition",
|
|
283
|
+
"TemplateModuleRemove",
|
|
284
|
+
"TemplateModuleRename",
|
|
285
|
+
"TemplatePatch",
|
|
244
286
|
"TemplateRef",
|
|
287
|
+
"TemplateRichTextModule",
|
|
245
288
|
"TemplateSource",
|
|
246
289
|
"TemplateSummary",
|
|
290
|
+
"TemplateTableColumn",
|
|
291
|
+
"TemplateTableColumnType",
|
|
292
|
+
"TemplateTableModule",
|
|
247
293
|
"Token",
|
|
248
294
|
"UploadBindingRef",
|
|
249
295
|
"UserRef",
|
{matelab_python_sdk-0.1.0a13 → matelab_python_sdk-0.1.0a15}/src/matelab/domains/notebooks.py
RENAMED
|
@@ -10,7 +10,7 @@ from matelab._transport import Encoding, Operation, SessionTransport
|
|
|
10
10
|
from matelab._wire_validation import encode_json_field, validate_request
|
|
11
11
|
from matelab.domains.groups import GroupRef
|
|
12
12
|
from matelab.domains.users import UserRef
|
|
13
|
-
from matelab.errors import MatelabUsageError
|
|
13
|
+
from matelab.errors import MatelabProtocolError, MatelabUsageError
|
|
14
14
|
|
|
15
15
|
_LIST_NOTEBOOKS = Operation(
|
|
16
16
|
method="GET",
|
|
@@ -60,21 +60,39 @@ _DELETE_SHARE = Operation(
|
|
|
60
60
|
|
|
61
61
|
@dataclass(frozen=True, slots=True)
|
|
62
62
|
class NotebookRef:
|
|
63
|
-
"""
|
|
63
|
+
"""Notebook identity and exact Provider selector across all visibility scopes.
|
|
64
|
+
|
|
65
|
+
``notebook_id`` is an ``eln.id`` for owned/shared scopes, but an
|
|
66
|
+
``eln_public.id`` catalog identity for public scope. It is not the source
|
|
67
|
+
ELN identity returned by public record entries. ``title`` is intentionally
|
|
68
|
+
non-empty and preserved verbatim: Provider writes require it and runtime
|
|
69
|
+
data contains no null or empty titles, even though the pinned owned/shared
|
|
70
|
+
wire field remains nullable.
|
|
71
|
+
"""
|
|
64
72
|
|
|
65
73
|
notebook_id: int
|
|
66
|
-
title: str
|
|
74
|
+
title: str
|
|
67
75
|
owner_userid: int
|
|
68
|
-
scope: Literal["owned", "shared"]
|
|
76
|
+
scope: Literal["owned", "shared", "public"]
|
|
69
77
|
|
|
78
|
+
def __post_init__(self) -> None:
|
|
79
|
+
if not self.title:
|
|
80
|
+
raise ValueError("Notebook title cannot be empty.")
|
|
70
81
|
|
|
71
|
-
@dataclass(frozen=True, slots=True)
|
|
72
|
-
class PublicNotebookRef:
|
|
73
|
-
"""Identity and title selector for a public-catalog notebook."""
|
|
74
82
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
83
|
+
def require_private_notebook(notebook: NotebookRef) -> None:
|
|
84
|
+
if notebook.scope == "public":
|
|
85
|
+
raise MatelabUsageError("This operation requires an owned or shared NotebookRef, not public scope.")
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _require_owned_notebook(notebook: NotebookRef) -> None:
|
|
89
|
+
if notebook.scope != "owned":
|
|
90
|
+
raise MatelabUsageError("This operation requires an owned NotebookRef.")
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def require_public_notebook(notebook: NotebookRef) -> None:
|
|
94
|
+
if notebook.scope != "public":
|
|
95
|
+
raise MatelabUsageError("Public record listing requires a public NotebookRef.")
|
|
78
96
|
|
|
79
97
|
|
|
80
98
|
@dataclass(frozen=True, slots=True)
|
|
@@ -99,7 +117,7 @@ class NotebookSummary:
|
|
|
99
117
|
|
|
100
118
|
@dataclass(frozen=True, slots=True)
|
|
101
119
|
class PublicNotebookSummary:
|
|
102
|
-
ref:
|
|
120
|
+
ref: NotebookRef
|
|
103
121
|
summary: str
|
|
104
122
|
record_count: int
|
|
105
123
|
created_at: str
|
|
@@ -204,6 +222,8 @@ def _parse_display_fields(raw: str | None) -> tuple[tuple[NotebookListField, ...
|
|
|
204
222
|
|
|
205
223
|
|
|
206
224
|
def _owned_or_shared(item: wire_models.ElnNotebook, *, scope: Literal["owned", "shared"]) -> NotebookSummary:
|
|
225
|
+
if item.showtext is None:
|
|
226
|
+
raise MatelabProtocolError("Matelab returned a null notebook title, violating the SDK runtime invariant.")
|
|
207
227
|
display_fields, configuration_valid = _parse_display_fields(item.requests)
|
|
208
228
|
return NotebookSummary(
|
|
209
229
|
ref=NotebookRef(notebook_id=item.id, title=item.showtext, owner_userid=item.userid, scope=scope),
|
|
@@ -232,7 +252,7 @@ class Notebooks:
|
|
|
232
252
|
shared=tuple(_owned_or_shared(item, scope="shared") for item in response.share),
|
|
233
253
|
public=tuple(
|
|
234
254
|
PublicNotebookSummary(
|
|
235
|
-
ref=
|
|
255
|
+
ref=NotebookRef(notebook_id=item.id, title=item.showtext, owner_userid=item.userid, scope="public"),
|
|
236
256
|
summary=item.comm,
|
|
237
257
|
record_count=item.num,
|
|
238
258
|
created_at=item.datetime_create,
|
|
@@ -270,8 +290,7 @@ class Notebooks:
|
|
|
270
290
|
notebook_types: tuple[JsonValue, ...] = (),
|
|
271
291
|
display_fields: tuple[NotebookListField, ...] | None = None,
|
|
272
292
|
) -> None:
|
|
273
|
-
|
|
274
|
-
raise MatelabUsageError("Notebook metadata can only be updated through an owned NotebookRef.")
|
|
293
|
+
_require_owned_notebook(notebook)
|
|
275
294
|
request = _save_request(
|
|
276
295
|
title=title,
|
|
277
296
|
summary=summary,
|
|
@@ -282,6 +301,7 @@ class Notebooks:
|
|
|
282
301
|
_ = await self._transport.request(_SAVE_NOTEBOOK, payload=request.model_dump(mode="json", exclude_unset=True))
|
|
283
302
|
|
|
284
303
|
async def shares(self, notebook: NotebookRef) -> NotebookShares:
|
|
304
|
+
require_private_notebook(notebook)
|
|
285
305
|
request = validate_request(
|
|
286
306
|
wire_models.ListNotebookSharesParametersQuery,
|
|
287
307
|
{"id": notebook.notebook_id},
|
|
@@ -309,6 +329,7 @@ class Notebooks:
|
|
|
309
329
|
)
|
|
310
330
|
|
|
311
331
|
async def share(self, notebook: NotebookRef, recipients: Sequence[UserRef]) -> None:
|
|
332
|
+
_require_owned_notebook(notebook)
|
|
312
333
|
request = validate_request(
|
|
313
334
|
wire_models.AddNotebookShareRequest,
|
|
314
335
|
{
|
|
@@ -331,6 +352,7 @@ class Notebooks:
|
|
|
331
352
|
read_audit_log: bool = False,
|
|
332
353
|
manage_shares: bool = False,
|
|
333
354
|
) -> None:
|
|
355
|
+
_require_owned_notebook(ref.notebook)
|
|
334
356
|
request = validate_request(
|
|
335
357
|
wire_models.UpdateNotebookSharePermissionsRequest,
|
|
336
358
|
{
|
|
@@ -348,6 +370,7 @@ class Notebooks:
|
|
|
348
370
|
_ = await self._transport.request(_UPDATE_SHARE, payload=request)
|
|
349
371
|
|
|
350
372
|
async def unshare(self, ref: NotebookShareRef) -> None:
|
|
373
|
+
_require_owned_notebook(ref.notebook)
|
|
351
374
|
request = validate_request(
|
|
352
375
|
wire_models.DeleteNotebookShareRequest,
|
|
353
376
|
{"id": ref.share_row_id},
|
|
@@ -15,7 +15,7 @@ from matelab._generated import models as wire_models
|
|
|
15
15
|
from matelab._transport import Encoding, Operation, SessionTransport, StreamOperation
|
|
16
16
|
from matelab._wire_validation import encode_json_field, validate_request
|
|
17
17
|
from matelab.domains._attachment_content import validated_attachment_content
|
|
18
|
-
from matelab.domains.notebooks import NotebookRef,
|
|
18
|
+
from matelab.domains.notebooks import NotebookRef, require_private_notebook, require_public_notebook
|
|
19
19
|
from matelab.domains.uploads import UploadBindingRef
|
|
20
20
|
from matelab.domains.users import UserRef
|
|
21
21
|
from matelab.errors import MatelabProtocolError, MatelabUsageError
|
|
@@ -1135,14 +1135,13 @@ def _render_comment(
|
|
|
1135
1135
|
return wire_html.strip(), canonical_html.strip(), next(iter(binding_uids), "")
|
|
1136
1136
|
|
|
1137
1137
|
|
|
1138
|
-
def
|
|
1139
|
-
|
|
1140
|
-
raise MatelabUsageError("Notebook selector cannot use a null Provider title.")
|
|
1138
|
+
def _private_notebook_title(notebook: NotebookRef) -> str:
|
|
1139
|
+
require_private_notebook(notebook)
|
|
1141
1140
|
return notebook.title
|
|
1142
1141
|
|
|
1143
1142
|
|
|
1144
1143
|
def _notebook_selector(notebook: NotebookRef) -> str | dict[str, JsonValue]:
|
|
1145
|
-
title =
|
|
1144
|
+
title = _private_notebook_title(notebook)
|
|
1146
1145
|
if notebook.scope == "shared":
|
|
1147
1146
|
return {"title": title, "user": notebook.owner_userid}
|
|
1148
1147
|
return title
|
|
@@ -1213,12 +1212,11 @@ class Records:
|
|
|
1213
1212
|
keywords: Sequence[str] | None = None,
|
|
1214
1213
|
content: str | None = None,
|
|
1215
1214
|
) -> RecordCollection:
|
|
1216
|
-
|
|
1217
|
-
raise MatelabUsageError("Cannot list records for a notebook whose Provider title is null.")
|
|
1215
|
+
title = _private_notebook_title(notebook)
|
|
1218
1216
|
request = validate_request(
|
|
1219
1217
|
wire_models.ListRecordsRequest,
|
|
1220
1218
|
{
|
|
1221
|
-
"eln":
|
|
1219
|
+
"eln": title,
|
|
1222
1220
|
"user": notebook.owner_userid if notebook.scope == "shared" else None,
|
|
1223
1221
|
"date_start": date_start if date_start is not None else "",
|
|
1224
1222
|
"date_end": date_end if date_end is not None else "",
|
|
@@ -1245,7 +1243,7 @@ class Records:
|
|
|
1245
1243
|
record_uid: str | None = None,
|
|
1246
1244
|
path: Sequence[str] | None = None,
|
|
1247
1245
|
) -> None:
|
|
1248
|
-
notebook_title =
|
|
1246
|
+
notebook_title = _private_notebook_title(notebook)
|
|
1249
1247
|
request = validate_request(
|
|
1250
1248
|
wire_models.CreateRecordRequest,
|
|
1251
1249
|
{
|
|
@@ -1268,7 +1266,7 @@ class Records:
|
|
|
1268
1266
|
items: Sequence[RecordImportItem],
|
|
1269
1267
|
template_owner: UserRef | None = None,
|
|
1270
1268
|
) -> RecordImportResult:
|
|
1271
|
-
notebook_title =
|
|
1269
|
+
notebook_title = _private_notebook_title(notebook)
|
|
1272
1270
|
staged_attachments: set[_StagedNotebookAttachmentIdentity] = set()
|
|
1273
1271
|
payload: dict[str, JsonValue] = {
|
|
1274
1272
|
"eln": notebook_title,
|
|
@@ -1316,6 +1314,8 @@ class Records:
|
|
|
1316
1314
|
record_uid: str | None = None,
|
|
1317
1315
|
) -> RecordCopyResult:
|
|
1318
1316
|
target = target_notebook or source.notebook
|
|
1317
|
+
require_private_notebook(source.notebook)
|
|
1318
|
+
require_private_notebook(target)
|
|
1319
1319
|
request = validate_request(
|
|
1320
1320
|
wire_models.CopyRecordRequest,
|
|
1321
1321
|
{
|
|
@@ -1331,7 +1331,7 @@ class Records:
|
|
|
1331
1331
|
return RecordCopyResult(record_database_id=response.id)
|
|
1332
1332
|
|
|
1333
1333
|
async def delete(self, *, notebook: NotebookRef, records: Sequence[RecordRef]) -> None:
|
|
1334
|
-
notebook_title =
|
|
1334
|
+
notebook_title = _private_notebook_title(notebook)
|
|
1335
1335
|
request = validate_request(
|
|
1336
1336
|
wire_models.DeleteRecordsRequest,
|
|
1337
1337
|
{
|
|
@@ -1344,6 +1344,7 @@ class Records:
|
|
|
1344
1344
|
_ = await self._transport.request(_DELETE_RECORDS, payload=request)
|
|
1345
1345
|
|
|
1346
1346
|
async def restore(self, *, notebook: NotebookRef, records: Sequence[DeletedRecordRef]) -> None:
|
|
1347
|
+
require_private_notebook(notebook)
|
|
1347
1348
|
request = validate_request(
|
|
1348
1349
|
wire_models.RestoreRecordsRequest,
|
|
1349
1350
|
{
|
|
@@ -1365,6 +1366,7 @@ class Records:
|
|
|
1365
1366
|
binding: UploadBindingRef | None = None,
|
|
1366
1367
|
content_type: str = "application/octet-stream",
|
|
1367
1368
|
) -> StagedRecordAttachment:
|
|
1369
|
+
require_private_notebook(source.notebook)
|
|
1368
1370
|
selected_binding = binding or UploadBindingRef.new()
|
|
1369
1371
|
response, actual_size, actual_sha256 = await self._upload_record_attachment(
|
|
1370
1372
|
notebook=source.notebook,
|
|
@@ -1403,6 +1405,7 @@ class Records:
|
|
|
1403
1405
|
one safe form-file-field update.
|
|
1404
1406
|
"""
|
|
1405
1407
|
|
|
1408
|
+
require_private_notebook(notebook)
|
|
1406
1409
|
uploader = self._authenticated_uploader()
|
|
1407
1410
|
selected_binding = binding or UploadBindingRef.new()
|
|
1408
1411
|
response, actual_size, actual_sha256 = await self._upload_record_attachment(
|
|
@@ -1440,6 +1443,7 @@ class Records:
|
|
|
1440
1443
|
expected_content_sha256: str | None = None,
|
|
1441
1444
|
password: str | None = None,
|
|
1442
1445
|
) -> Literal["provider_reported_persisted", "pending_browser_save", "provider_acknowledged_unclassified"]:
|
|
1446
|
+
require_private_notebook(source.notebook)
|
|
1443
1447
|
if expected_content_sha256 is not None and re.fullmatch(r"[0-9a-f]{64}", expected_content_sha256) is None:
|
|
1444
1448
|
raise MatelabUsageError("Expected record content fingerprint must be a lowercase SHA-256.")
|
|
1445
1449
|
selected_value_updates = dict(value_updates or {})
|
|
@@ -1497,7 +1501,7 @@ class Records:
|
|
|
1497
1501
|
request = validate_request(
|
|
1498
1502
|
wire_models.UpdateRecordRequest,
|
|
1499
1503
|
{
|
|
1500
|
-
"eln":
|
|
1504
|
+
"eln": _private_notebook_title(source.notebook),
|
|
1501
1505
|
"uid": source.record.record_uid,
|
|
1502
1506
|
"user": source.notebook.owner_userid if source.notebook.scope == "shared" else None,
|
|
1503
1507
|
"password": password,
|
|
@@ -1588,7 +1592,8 @@ class Records:
|
|
|
1588
1592
|
)
|
|
1589
1593
|
return RecordSearchResult(matches=tuple(matches), forwarded=response.code == 10)
|
|
1590
1594
|
|
|
1591
|
-
async def list_public(self, notebook:
|
|
1595
|
+
async def list_public(self, notebook: NotebookRef) -> tuple[PublicRecordSummary, ...]:
|
|
1596
|
+
require_public_notebook(notebook)
|
|
1592
1597
|
request = validate_request(
|
|
1593
1598
|
wire_models.PublicRecordListRequest,
|
|
1594
1599
|
{"eln": notebook.title},
|
|
@@ -1626,6 +1631,7 @@ class Records:
|
|
|
1626
1631
|
content: str = "",
|
|
1627
1632
|
keywords: Sequence[str] = (),
|
|
1628
1633
|
) -> RecordPage:
|
|
1634
|
+
require_private_notebook(notebook)
|
|
1629
1635
|
encoded_keywords = encode_json_field(list(keywords), ensure_ascii=False) if keywords else ""
|
|
1630
1636
|
request = validate_request(
|
|
1631
1637
|
wire_models.ListNotebookRecordPageParametersQuery,
|
|
@@ -1677,6 +1683,7 @@ class Records:
|
|
|
1677
1683
|
)
|
|
1678
1684
|
|
|
1679
1685
|
async def recycle_bin(self, notebook: NotebookRef, *, page: int = 1, page_size: int = 24) -> DeletedRecordPage:
|
|
1686
|
+
require_private_notebook(notebook)
|
|
1680
1687
|
if not 10 <= page_size <= 256:
|
|
1681
1688
|
raise MatelabUsageError("Recycle-bin page size must be between 10 and 256.")
|
|
1682
1689
|
request = validate_request(
|
|
@@ -1708,6 +1715,7 @@ class Records:
|
|
|
1708
1715
|
)
|
|
1709
1716
|
|
|
1710
1717
|
async def relations(self, *, notebook: NotebookRef, record: RecordRef) -> RecordRelations:
|
|
1718
|
+
require_private_notebook(notebook)
|
|
1711
1719
|
request = validate_request(
|
|
1712
1720
|
wire_models.ListRecordRelationsParametersQuery,
|
|
1713
1721
|
{"eln": notebook.notebook_id, "item_id": record.record_database_id},
|
|
@@ -1736,6 +1744,8 @@ class Records:
|
|
|
1736
1744
|
)
|
|
1737
1745
|
|
|
1738
1746
|
async def add_relation(self, *, source: RecordLocator, target: RecordLocator) -> None:
|
|
1747
|
+
require_private_notebook(source.notebook)
|
|
1748
|
+
require_private_notebook(target.notebook)
|
|
1739
1749
|
source_record = await self.read(notebook=source.notebook, record=source.record)
|
|
1740
1750
|
source_relations = await self.relations(notebook=source.notebook, record=source.record)
|
|
1741
1751
|
if not source_relations.editable:
|
|
@@ -1761,6 +1771,7 @@ class Records:
|
|
|
1761
1771
|
|
|
1762
1772
|
async def delete_relation(self, relation: RecordRelation) -> None:
|
|
1763
1773
|
source = relation.ref.source
|
|
1774
|
+
require_private_notebook(source.notebook)
|
|
1764
1775
|
before = await self.relations(notebook=source.notebook, record=source.record)
|
|
1765
1776
|
if not before.editable:
|
|
1766
1777
|
raise MatelabUsageError("The selected source record is not currently editable.")
|
|
@@ -1788,6 +1799,7 @@ class Records:
|
|
|
1788
1799
|
_ = await self._transport.request(_DELETE_RELATION, payload=request)
|
|
1789
1800
|
|
|
1790
1801
|
async def comments(self, *, notebook: NotebookRef, record: RecordRef) -> tuple[RecordComment, ...]:
|
|
1802
|
+
require_private_notebook(notebook)
|
|
1791
1803
|
request = validate_request(
|
|
1792
1804
|
wire_models.ListRecordCommentsParametersQuery,
|
|
1793
1805
|
{"eln": notebook.notebook_id, "item_id": record.record_database_id},
|
|
@@ -1808,6 +1820,7 @@ class Records:
|
|
|
1808
1820
|
binding: UploadBindingRef | None = None,
|
|
1809
1821
|
content_type: str = "application/octet-stream",
|
|
1810
1822
|
) -> StagedRecordCommentAttachment:
|
|
1823
|
+
require_private_notebook(source.notebook)
|
|
1811
1824
|
selected_binding = binding or UploadBindingRef.new()
|
|
1812
1825
|
_validate_upload_metadata(filename=filename, size=size, sha256=sha256, content_type=content_type)
|
|
1813
1826
|
async with validated_attachment_content(content, size=size, sha256=sha256) as prepared:
|
|
@@ -1864,6 +1877,7 @@ class Records:
|
|
|
1864
1877
|
html: str,
|
|
1865
1878
|
attachments: Mapping[str, StagedRecordCommentAttachment] | None = None,
|
|
1866
1879
|
) -> None:
|
|
1880
|
+
require_private_notebook(source.notebook)
|
|
1867
1881
|
wire_html, _, binding_uid = _render_comment(source, html=html, attachments=attachments)
|
|
1868
1882
|
await self._save_comment_request(source=source, comment_id=0, html=wire_html, binding_uid=binding_uid)
|
|
1869
1883
|
|
|
@@ -1904,6 +1918,7 @@ class Records:
|
|
|
1904
1918
|
async def download_attachment(
|
|
1905
1919
|
self, attachment: RecordAttachmentRef, *, byte_range: ByteRange | None = None
|
|
1906
1920
|
) -> DownloadStream:
|
|
1921
|
+
require_private_notebook(attachment.source.notebook)
|
|
1907
1922
|
request = validate_request(
|
|
1908
1923
|
wire_models.DownloadRecordAttachmentParametersQuery,
|
|
1909
1924
|
{
|
|
@@ -1933,6 +1948,7 @@ class Records:
|
|
|
1933
1948
|
but the current Provider only authorizes the supplied notebook and does
|
|
1934
1949
|
not independently verify that the attachment belongs to it.
|
|
1935
1950
|
"""
|
|
1951
|
+
require_private_notebook(attachment.source.notebook)
|
|
1936
1952
|
request = validate_request(
|
|
1937
1953
|
wire_models.DownloadRecordCommentAttachmentParametersQuery,
|
|
1938
1954
|
{
|
|
@@ -1953,6 +1969,7 @@ class Records:
|
|
|
1953
1969
|
async def read(
|
|
1954
1970
|
self, *, notebook: NotebookRef, record: RecordRef, version: RecordVersionRef | None = None
|
|
1955
1971
|
) -> Record:
|
|
1972
|
+
require_private_notebook(notebook)
|
|
1956
1973
|
if version is not None and (
|
|
1957
1974
|
version.notebook_id != notebook.notebook_id
|
|
1958
1975
|
or version.record_database_id != record.record_database_id
|
|
@@ -2622,7 +2639,7 @@ class Records:
|
|
|
2622
2639
|
sha256: str | None,
|
|
2623
2640
|
content_type: str,
|
|
2624
2641
|
) -> tuple[wire_models.UploadAttachmentResponse, int, str]:
|
|
2625
|
-
notebook_title =
|
|
2642
|
+
notebook_title = _private_notebook_title(notebook)
|
|
2626
2643
|
_validate_upload_metadata(filename=filename, size=size, sha256=sha256, content_type=content_type)
|
|
2627
2644
|
async with validated_attachment_content(content, size=size, sha256=sha256) as prepared:
|
|
2628
2645
|
prepared_content, actual_size, actual_sha256 = prepared
|