matelab-python-sdk 0.1.0a14__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.0a14 → matelab_python_sdk-0.1.0a15}/CHANGELOG.md +10 -0
- {matelab_python_sdk-0.1.0a14 → matelab_python_sdk-0.1.0a15}/PKG-INFO +15 -5
- {matelab_python_sdk-0.1.0a14 → matelab_python_sdk-0.1.0a15}/README.md +14 -4
- {matelab_python_sdk-0.1.0a14 → matelab_python_sdk-0.1.0a15}/pyproject.toml +1 -1
- {matelab_python_sdk-0.1.0a14 → matelab_python_sdk-0.1.0a15}/src/matelab/__init__.py +0 -2
- {matelab_python_sdk-0.1.0a14 → matelab_python_sdk-0.1.0a15}/src/matelab/domains/notebooks.py +37 -14
- {matelab_python_sdk-0.1.0a14 → matelab_python_sdk-0.1.0a15}/src/matelab/domains/records.py +31 -14
- {matelab_python_sdk-0.1.0a14 → matelab_python_sdk-0.1.0a15}/tests/test_extended_records.py +17 -9
- {matelab_python_sdk-0.1.0a14 → matelab_python_sdk-0.1.0a15}/tests/test_notebook_lifecycle.py +16 -0
- {matelab_python_sdk-0.1.0a14 → matelab_python_sdk-0.1.0a15}/tests/test_notebooks.py +9 -5
- {matelab_python_sdk-0.1.0a14 → matelab_python_sdk-0.1.0a15}/tests/test_records.py +0 -12
- {matelab_python_sdk-0.1.0a14 → matelab_python_sdk-0.1.0a15}/uv.lock +1 -1
- {matelab_python_sdk-0.1.0a14 → matelab_python_sdk-0.1.0a15}/.env.example +0 -0
- {matelab_python_sdk-0.1.0a14 → matelab_python_sdk-0.1.0a15}/.github/workflows/release.yml +0 -0
- {matelab_python_sdk-0.1.0a14 → matelab_python_sdk-0.1.0a15}/.gitignore +0 -0
- {matelab_python_sdk-0.1.0a14 → matelab_python_sdk-0.1.0a15}/AGENTS.md +0 -0
- {matelab_python_sdk-0.1.0a14 → matelab_python_sdk-0.1.0a15}/LICENSE +0 -0
- {matelab_python_sdk-0.1.0a14 → matelab_python_sdk-0.1.0a15}/NOTICE +0 -0
- {matelab_python_sdk-0.1.0a14 → matelab_python_sdk-0.1.0a15}/contracts/matelab-integration-v1.lock.json +0 -0
- {matelab_python_sdk-0.1.0a14 → matelab_python_sdk-0.1.0a15}/contracts/matelab-integration-v1.openapi.yaml +0 -0
- {matelab_python_sdk-0.1.0a14 → matelab_python_sdk-0.1.0a15}/docs/operation-coverage.yaml +0 -0
- {matelab_python_sdk-0.1.0a14 → matelab_python_sdk-0.1.0a15}/scripts/check_installed_package.py +0 -0
- {matelab_python_sdk-0.1.0a14 → matelab_python_sdk-0.1.0a15}/scripts/check_release.py +0 -0
- {matelab_python_sdk-0.1.0a14 → matelab_python_sdk-0.1.0a15}/scripts/generate_models.py +0 -0
- {matelab_python_sdk-0.1.0a14 → matelab_python_sdk-0.1.0a15}/src/matelab/_generated/__init__.py +0 -0
- {matelab_python_sdk-0.1.0a14 → matelab_python_sdk-0.1.0a15}/src/matelab/_generated/models.py +0 -0
- {matelab_python_sdk-0.1.0a14 → matelab_python_sdk-0.1.0a15}/src/matelab/_transport.py +0 -0
- {matelab_python_sdk-0.1.0a14 → matelab_python_sdk-0.1.0a15}/src/matelab/_wire_validation.py +0 -0
- {matelab_python_sdk-0.1.0a14 → matelab_python_sdk-0.1.0a15}/src/matelab/client.py +0 -0
- {matelab_python_sdk-0.1.0a14 → matelab_python_sdk-0.1.0a15}/src/matelab/domains/__init__.py +0 -0
- {matelab_python_sdk-0.1.0a14 → matelab_python_sdk-0.1.0a15}/src/matelab/domains/_attachment_content.py +0 -0
- {matelab_python_sdk-0.1.0a14 → matelab_python_sdk-0.1.0a15}/src/matelab/domains/cloud_drive.py +0 -0
- {matelab_python_sdk-0.1.0a14 → matelab_python_sdk-0.1.0a15}/src/matelab/domains/groups.py +0 -0
- {matelab_python_sdk-0.1.0a14 → matelab_python_sdk-0.1.0a15}/src/matelab/domains/literature.py +0 -0
- {matelab_python_sdk-0.1.0a14 → matelab_python_sdk-0.1.0a15}/src/matelab/domains/template_documents.py +0 -0
- {matelab_python_sdk-0.1.0a14 → matelab_python_sdk-0.1.0a15}/src/matelab/domains/templates.py +0 -0
- {matelab_python_sdk-0.1.0a14 → matelab_python_sdk-0.1.0a15}/src/matelab/domains/uploads.py +0 -0
- {matelab_python_sdk-0.1.0a14 → matelab_python_sdk-0.1.0a15}/src/matelab/domains/users.py +0 -0
- {matelab_python_sdk-0.1.0a14 → matelab_python_sdk-0.1.0a15}/src/matelab/errors.py +0 -0
- {matelab_python_sdk-0.1.0a14 → matelab_python_sdk-0.1.0a15}/src/matelab/py.typed +0 -0
- {matelab_python_sdk-0.1.0a14 → matelab_python_sdk-0.1.0a15}/src/matelab/session.py +0 -0
- {matelab_python_sdk-0.1.0a14 → matelab_python_sdk-0.1.0a15}/src/matelab/streaming.py +0 -0
- {matelab_python_sdk-0.1.0a14 → matelab_python_sdk-0.1.0a15}/tests/conftest.py +0 -0
- {matelab_python_sdk-0.1.0a14 → matelab_python_sdk-0.1.0a15}/tests/provider/test_provider_smoke.py +0 -0
- {matelab_python_sdk-0.1.0a14 → matelab_python_sdk-0.1.0a15}/tests/test_async_attachment_upload.py +0 -0
- {matelab_python_sdk-0.1.0a14 → matelab_python_sdk-0.1.0a15}/tests/test_client.py +0 -0
- {matelab_python_sdk-0.1.0a14 → matelab_python_sdk-0.1.0a15}/tests/test_cloud_drive.py +0 -0
- {matelab_python_sdk-0.1.0a14 → matelab_python_sdk-0.1.0a15}/tests/test_comments_and_downloads.py +0 -0
- {matelab_python_sdk-0.1.0a14 → matelab_python_sdk-0.1.0a15}/tests/test_consumer_smoke.py +0 -0
- {matelab_python_sdk-0.1.0a14 → matelab_python_sdk-0.1.0a15}/tests/test_errors.py +0 -0
- {matelab_python_sdk-0.1.0a14 → matelab_python_sdk-0.1.0a15}/tests/test_generation.py +0 -0
- {matelab_python_sdk-0.1.0a14 → matelab_python_sdk-0.1.0a15}/tests/test_groups.py +0 -0
- {matelab_python_sdk-0.1.0a14 → matelab_python_sdk-0.1.0a15}/tests/test_operation_coverage.py +0 -0
- {matelab_python_sdk-0.1.0a14 → matelab_python_sdk-0.1.0a15}/tests/test_record_lifecycle.py +0 -0
- {matelab_python_sdk-0.1.0a14 → matelab_python_sdk-0.1.0a15}/tests/test_record_mutations.py +0 -0
- {matelab_python_sdk-0.1.0a14 → matelab_python_sdk-0.1.0a15}/tests/test_release.py +0 -0
- {matelab_python_sdk-0.1.0a14 → matelab_python_sdk-0.1.0a15}/tests/test_streaming.py +0 -0
- {matelab_python_sdk-0.1.0a14 → matelab_python_sdk-0.1.0a15}/tests/test_template_documents.py +0 -0
- {matelab_python_sdk-0.1.0a14 → matelab_python_sdk-0.1.0a15}/tests/test_template_lifecycle.py +0 -0
- {matelab_python_sdk-0.1.0a14 → matelab_python_sdk-0.1.0a15}/tests/test_templates.py +0 -0
- {matelab_python_sdk-0.1.0a14 → matelab_python_sdk-0.1.0a15}/tests/test_transport.py +0 -0
- {matelab_python_sdk-0.1.0a14 → matelab_python_sdk-0.1.0a15}/tests/test_uploads_and_literature.py +0 -0
- {matelab_python_sdk-0.1.0a14 → matelab_python_sdk-0.1.0a15}/tests/test_users.py +0 -0
- {matelab_python_sdk-0.1.0a14 → matelab_python_sdk-0.1.0a15}/tests/typing/consumer.py +0 -0
- {matelab_python_sdk-0.1.0a14 → matelab_python_sdk-0.1.0a15}/tests/typing/pyproject.toml +0 -0
|
@@ -1,5 +1,15 @@
|
|
|
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
|
+
|
|
3
13
|
## 0.1.0a14 - 2026-08-10
|
|
4
14
|
|
|
5
15
|
- Adds immutable semantic template modules, editable `TemplateDocument` values, ordered name-based patch intents,
|
|
@@ -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
|
|
@@ -658,8 +658,18 @@ deletion is named `permanently_delete_files` and is non-recoverable. Cloud downl
|
|
|
658
658
|
resolve bytes from the final file identity and reuse `DownloadStream`, thumbnail/preview choices and the PVD-002-safe
|
|
659
659
|
range subset. Cloud mutations are not automatically retried.
|
|
660
660
|
|
|
661
|
-
Owned
|
|
662
|
-
|
|
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
|
|
663
673
|
that the requested version is still present in its `modify_log`; both reads write Provider audit entries.
|
|
664
674
|
|
|
665
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
|
|
@@ -630,8 +630,18 @@ deletion is named `permanently_delete_files` and is non-recoverable. Cloud downl
|
|
|
630
630
|
resolve bytes from the final file identity and reuse `DownloadStream`, thumbnail/preview choices and the PVD-002-safe
|
|
631
631
|
range subset. Cloud mutations are not automatically retried.
|
|
632
632
|
|
|
633
|
-
Owned
|
|
634
|
-
|
|
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
|
|
635
645
|
that the requested version is still present in its `modify_log`; both reads write Provider audit entries.
|
|
636
646
|
|
|
637
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 (
|
|
@@ -205,7 +204,6 @@ __all__ = [
|
|
|
205
204
|
"PendingLiteratureShare",
|
|
206
205
|
"PendingLiteratureShareComment",
|
|
207
206
|
"PersonalLiteratureItemRef",
|
|
208
|
-
"PublicNotebookRef",
|
|
209
207
|
"PublicNotebookSummary",
|
|
210
208
|
"PublicRecordRef",
|
|
211
209
|
"PublicRecordSummary",
|
{matelab_python_sdk-0.1.0a14 → 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
|
|
@@ -10,7 +10,6 @@ from matelab import (
|
|
|
10
10
|
MatelabProtocolError,
|
|
11
11
|
MatelabUsageError,
|
|
12
12
|
NotebookRef,
|
|
13
|
-
PublicNotebookRef,
|
|
14
13
|
PublicRecordRef,
|
|
15
14
|
RecordCommentRef,
|
|
16
15
|
RecordLocator,
|
|
@@ -127,17 +126,11 @@ async def test_export_records_preserves_empty_result_and_rejects_invalid_selecti
|
|
|
127
126
|
notebook=NotebookRef(notebook_id=12, title="Owned Example", owner_userid=101, scope="owned"),
|
|
128
127
|
record=RecordRef(record_database_id=9001, record_uid="REC-EXAMPLE-001"),
|
|
129
128
|
)
|
|
130
|
-
invalid = RecordLocator(
|
|
131
|
-
notebook=NotebookRef(notebook_id=12, title=None, owner_userid=101, scope="owned"),
|
|
132
|
-
record=RecordRef(record_database_id=9001, record_uid="REC-EXAMPLE-001"),
|
|
133
|
-
)
|
|
134
129
|
async with AsyncClient(base_url="https://matelab.example/api", transport=MockTransport(handler)) as http_client:
|
|
135
130
|
client = AsyncMatelab("https://ignored.example", session=provider_session, http_client=http_client)
|
|
136
131
|
result = await client.records.export([valid])
|
|
137
132
|
with pytest.raises(MatelabUsageError, match="export selection"):
|
|
138
133
|
_ = await client.records.export([])
|
|
139
|
-
with pytest.raises(MatelabUsageError, match="null Provider title"):
|
|
140
|
-
_ = await client.records.export([invalid])
|
|
141
134
|
|
|
142
135
|
assert result.records == ()
|
|
143
136
|
assert result.forwarded is False
|
|
@@ -231,7 +224,7 @@ async def test_search_records_preserves_empty_forwarded_result_and_rejects_ambig
|
|
|
231
224
|
async def test_list_public_records_keeps_public_and_source_identity_distinct(
|
|
232
225
|
provider_session: Session, provider_envelope: Callable[..., dict[str, object]]
|
|
233
226
|
) -> None:
|
|
234
|
-
notebook =
|
|
227
|
+
notebook = NotebookRef(notebook_id=7, title="Public Example", owner_userid=0, scope="public")
|
|
235
228
|
|
|
236
229
|
async def handler(request: Request) -> Response:
|
|
237
230
|
assert request.method == "POST"
|
|
@@ -508,5 +501,20 @@ async def test_public_record_list_rejects_schema_mismatch(provider_session: Sess
|
|
|
508
501
|
client = AsyncMatelab("https://ignored.example", session=provider_session, http_client=http_client)
|
|
509
502
|
with pytest.raises(MatelabProtocolError, match="does not match"):
|
|
510
503
|
_ = await client.records.list_public(
|
|
511
|
-
|
|
504
|
+
NotebookRef(notebook_id=7, title="Public Example", owner_userid=0, scope="public")
|
|
512
505
|
)
|
|
506
|
+
|
|
507
|
+
|
|
508
|
+
@pytest.mark.asyncio
|
|
509
|
+
async def test_record_operations_reject_wrong_notebook_scope_before_http(provider_session: Session) -> None:
|
|
510
|
+
async def handler(_: Request) -> Response:
|
|
511
|
+
raise AssertionError("invalid notebook scope must be rejected before HTTP")
|
|
512
|
+
|
|
513
|
+
public = NotebookRef(notebook_id=7, title="Public Example", owner_userid=0, scope="public")
|
|
514
|
+
private = NotebookRef(notebook_id=12, title="Owned Example", owner_userid=101, scope="owned")
|
|
515
|
+
async with AsyncClient(base_url="https://matelab.example/api", transport=MockTransport(handler)) as http_client:
|
|
516
|
+
client = AsyncMatelab("https://ignored.example", session=provider_session, http_client=http_client)
|
|
517
|
+
with pytest.raises(MatelabUsageError, match="owned or shared"):
|
|
518
|
+
_ = await client.records.list(notebook=public)
|
|
519
|
+
with pytest.raises(MatelabUsageError, match="public NotebookRef"):
|
|
520
|
+
_ = await client.records.list_public(private)
|
{matelab_python_sdk-0.1.0a14 → matelab_python_sdk-0.1.0a15}/tests/test_notebook_lifecycle.py
RENAMED
|
@@ -144,6 +144,22 @@ async def test_update_notebook_rejects_shared_scope_before_http(provider_session
|
|
|
144
144
|
_ = await client.notebooks.update(notebook, title="Shared")
|
|
145
145
|
|
|
146
146
|
|
|
147
|
+
@pytest.mark.asyncio
|
|
148
|
+
async def test_private_and_owned_notebook_operations_reject_public_scope_before_http(
|
|
149
|
+
provider_session: Session,
|
|
150
|
+
) -> None:
|
|
151
|
+
async def handler(_: Request) -> Response:
|
|
152
|
+
raise AssertionError("public scope must be rejected before HTTP")
|
|
153
|
+
|
|
154
|
+
notebook = NotebookRef(notebook_id=7, title="Public", owner_userid=0, scope="public")
|
|
155
|
+
async with AsyncClient(base_url="https://matelab.example/api", transport=MockTransport(handler)) as http_client:
|
|
156
|
+
client = AsyncMatelab("https://ignored.example", session=provider_session, http_client=http_client)
|
|
157
|
+
with pytest.raises(MatelabUsageError, match="owned or shared"):
|
|
158
|
+
_ = await client.notebooks.shares(notebook)
|
|
159
|
+
with pytest.raises(MatelabUsageError, match="owned NotebookRef"):
|
|
160
|
+
_ = await client.notebooks.update(notebook, title="Public")
|
|
161
|
+
|
|
162
|
+
|
|
147
163
|
@pytest.mark.asyncio
|
|
148
164
|
async def test_list_notebook_shares_separates_stored_mask_from_effective_read(
|
|
149
165
|
provider_session: Session, provider_envelope: Callable[..., dict[str, object]]
|
|
@@ -3,7 +3,7 @@ from collections.abc import Callable
|
|
|
3
3
|
import pytest
|
|
4
4
|
from httpx2 import AsyncClient, MockTransport, Request, Response
|
|
5
5
|
|
|
6
|
-
from matelab import AsyncMatelab,
|
|
6
|
+
from matelab import AsyncMatelab, MatelabProtocolError, NotebookRef, Session
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
@pytest.mark.asyncio
|
|
@@ -27,7 +27,7 @@ async def test_list_notebooks_maps_pinned_contract_success_example(
|
|
|
27
27
|
assert notebooks.shared[0].ref == NotebookRef(
|
|
28
28
|
notebook_id=23, title="Shared Example", owner_userid=202, scope="shared"
|
|
29
29
|
)
|
|
30
|
-
assert notebooks.public[0].ref ==
|
|
30
|
+
assert notebooks.public[0].ref == NotebookRef(notebook_id=7, title="Public Example", owner_userid=0, scope="public")
|
|
31
31
|
assert len(notebooks.all) == 3
|
|
32
32
|
|
|
33
33
|
|
|
@@ -56,7 +56,11 @@ async def test_list_notebooks_maps_pinned_contract_nullable_example(
|
|
|
56
56
|
|
|
57
57
|
async with AsyncClient(base_url="https://matelab.example/api", transport=MockTransport(handler)) as http_client:
|
|
58
58
|
client = AsyncMatelab("https://ignored.example", session=provider_session, http_client=http_client)
|
|
59
|
-
|
|
59
|
+
with pytest.raises(MatelabProtocolError, match="null notebook title"):
|
|
60
|
+
_ = await client.notebooks.list()
|
|
61
|
+
|
|
60
62
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
+
def test_notebook_ref_rejects_empty_title_without_stripping_provider_selector() -> None:
|
|
64
|
+
with pytest.raises(ValueError, match="Notebook title cannot be empty"):
|
|
65
|
+
_ = NotebookRef(notebook_id=12, title="", owner_userid=101, scope="owned")
|
|
66
|
+
assert NotebookRef(notebook_id=12, title=" ", owner_userid=101, scope="owned").title == " "
|
|
@@ -157,18 +157,6 @@ async def test_read_current_record_maps_pinned_contract_success_example(
|
|
|
157
157
|
)
|
|
158
158
|
|
|
159
159
|
|
|
160
|
-
@pytest.mark.asyncio
|
|
161
|
-
async def test_list_records_rejects_null_notebook_selector_before_http() -> None:
|
|
162
|
-
async def handler(_: Request) -> Response:
|
|
163
|
-
raise AssertionError("null title must be rejected before HTTP")
|
|
164
|
-
|
|
165
|
-
notebook = NotebookRef(notebook_id=12, title=None, owner_userid=101, scope="owned")
|
|
166
|
-
async with AsyncClient(base_url="https://matelab.example/api", transport=MockTransport(handler)) as http_client:
|
|
167
|
-
client = AsyncMatelab("https://ignored.example", session=_session(), http_client=http_client)
|
|
168
|
-
with pytest.raises(MatelabUsageError, match="title is null"):
|
|
169
|
-
_ = await client.records.list(notebook=notebook)
|
|
170
|
-
|
|
171
|
-
|
|
172
160
|
@pytest.mark.asyncio
|
|
173
161
|
async def test_read_current_record_maps_required_nullable_and_open_modules() -> None:
|
|
174
162
|
async def handler(request: Request) -> Response:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{matelab_python_sdk-0.1.0a14 → matelab_python_sdk-0.1.0a15}/scripts/check_installed_package.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{matelab_python_sdk-0.1.0a14 → matelab_python_sdk-0.1.0a15}/src/matelab/_generated/__init__.py
RENAMED
|
File without changes
|
{matelab_python_sdk-0.1.0a14 → matelab_python_sdk-0.1.0a15}/src/matelab/_generated/models.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{matelab_python_sdk-0.1.0a14 → matelab_python_sdk-0.1.0a15}/src/matelab/domains/cloud_drive.py
RENAMED
|
File without changes
|
|
File without changes
|
{matelab_python_sdk-0.1.0a14 → matelab_python_sdk-0.1.0a15}/src/matelab/domains/literature.py
RENAMED
|
File without changes
|
|
File without changes
|
{matelab_python_sdk-0.1.0a14 → matelab_python_sdk-0.1.0a15}/src/matelab/domains/templates.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{matelab_python_sdk-0.1.0a14 → matelab_python_sdk-0.1.0a15}/tests/provider/test_provider_smoke.py
RENAMED
|
File without changes
|
{matelab_python_sdk-0.1.0a14 → matelab_python_sdk-0.1.0a15}/tests/test_async_attachment_upload.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{matelab_python_sdk-0.1.0a14 → matelab_python_sdk-0.1.0a15}/tests/test_comments_and_downloads.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{matelab_python_sdk-0.1.0a14 → matelab_python_sdk-0.1.0a15}/tests/test_operation_coverage.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{matelab_python_sdk-0.1.0a14 → matelab_python_sdk-0.1.0a15}/tests/test_template_documents.py
RENAMED
|
File without changes
|
{matelab_python_sdk-0.1.0a14 → matelab_python_sdk-0.1.0a15}/tests/test_template_lifecycle.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{matelab_python_sdk-0.1.0a14 → matelab_python_sdk-0.1.0a15}/tests/test_uploads_and_literature.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|