matelab-python-sdk 0.1.0a12__tar.gz → 0.1.0a13__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a13}/CHANGELOG.md +8 -0
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a13}/PKG-INFO +3 -3
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a13}/README.md +2 -2
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a13}/pyproject.toml +1 -1
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a13}/src/matelab/_transport.py +8 -11
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a13}/src/matelab/_wire_validation.py +6 -10
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a13}/src/matelab/client.py +9 -7
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a13}/src/matelab/domains/cloud_drive.py +1 -3
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a13}/src/matelab/domains/literature.py +1 -3
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a13}/src/matelab/domains/records.py +2 -2
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a13}/src/matelab/session.py +4 -2
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a13}/src/matelab/streaming.py +6 -8
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a13}/tests/test_client.py +5 -4
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a13}/tests/test_streaming.py +2 -2
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a13}/uv.lock +1 -1
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a13}/.env.example +0 -0
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a13}/.github/workflows/release.yml +0 -0
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a13}/.gitignore +0 -0
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a13}/AGENTS.md +0 -0
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a13}/LICENSE +0 -0
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a13}/NOTICE +0 -0
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a13}/contracts/matelab-integration-v1.lock.json +0 -0
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a13}/contracts/matelab-integration-v1.openapi.yaml +0 -0
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a13}/docs/operation-coverage.yaml +0 -0
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a13}/scripts/check_installed_package.py +0 -0
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a13}/scripts/check_release.py +0 -0
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a13}/scripts/generate_models.py +0 -0
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a13}/src/matelab/__init__.py +0 -0
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a13}/src/matelab/_generated/__init__.py +0 -0
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a13}/src/matelab/_generated/models.py +0 -0
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a13}/src/matelab/domains/__init__.py +0 -0
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a13}/src/matelab/domains/_attachment_content.py +0 -0
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a13}/src/matelab/domains/groups.py +0 -0
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a13}/src/matelab/domains/notebooks.py +0 -0
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a13}/src/matelab/domains/templates.py +0 -0
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a13}/src/matelab/domains/uploads.py +0 -0
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a13}/src/matelab/domains/users.py +0 -0
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a13}/src/matelab/errors.py +0 -0
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a13}/src/matelab/py.typed +0 -0
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a13}/tests/conftest.py +0 -0
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a13}/tests/provider/test_provider_smoke.py +0 -0
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a13}/tests/test_async_attachment_upload.py +0 -0
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a13}/tests/test_cloud_drive.py +0 -0
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a13}/tests/test_comments_and_downloads.py +0 -0
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a13}/tests/test_consumer_smoke.py +0 -0
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a13}/tests/test_errors.py +0 -0
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a13}/tests/test_extended_records.py +0 -0
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a13}/tests/test_generation.py +0 -0
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a13}/tests/test_groups.py +0 -0
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a13}/tests/test_notebook_lifecycle.py +0 -0
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a13}/tests/test_notebooks.py +0 -0
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a13}/tests/test_operation_coverage.py +0 -0
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a13}/tests/test_record_lifecycle.py +0 -0
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a13}/tests/test_record_mutations.py +0 -0
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a13}/tests/test_records.py +0 -0
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a13}/tests/test_release.py +0 -0
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a13}/tests/test_template_lifecycle.py +0 -0
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a13}/tests/test_templates.py +0 -0
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a13}/tests/test_transport.py +0 -0
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a13}/tests/test_uploads_and_literature.py +0 -0
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a13}/tests/test_users.py +0 -0
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a13}/tests/typing/consumer.py +0 -0
- {matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a13}/tests/typing/pyproject.toml +0 -0
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.0a13 - 2026-08-08
|
|
4
|
+
|
|
5
|
+
- Breaking: `Identity.username` now removes surrounding whitespace and rejects values that become empty.
|
|
6
|
+
- Keeps `ByteRange` typed through the transport adapter and closes the underlying HTTPX2 response when download reads
|
|
7
|
+
or iteration finish or fail, while retaining explicit `DownloadStream.aclose()` for deferred framework consumers.
|
|
8
|
+
- Consolidates Provider session validation through the public `Session` models and simplifies private wire-rule and
|
|
9
|
+
token-refresh validation without changing the pinned Integration Contract.
|
|
10
|
+
|
|
3
11
|
## 0.1.0a12 - 2026-08-07
|
|
4
12
|
|
|
5
13
|
- Breaking: removes `MatelabAttachmentValidationError`; local attachment size and checksum mismatches now use the
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: matelab-python-sdk
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.0a13
|
|
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.0a13`. `[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.0a13-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
|
|
@@ -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.0a13`. `[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.0a13-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
|
|
@@ -12,7 +12,6 @@ from pydantic import BaseModel, JsonValue, ValidationError
|
|
|
12
12
|
from typing_extensions import override
|
|
13
13
|
|
|
14
14
|
from matelab._generated import models as wire_models
|
|
15
|
-
from matelab._wire_validation import validate_request
|
|
16
15
|
from matelab.errors import (
|
|
17
16
|
MatelabAuthenticationError,
|
|
18
17
|
MatelabErrorCategory,
|
|
@@ -22,7 +21,7 @@ from matelab.errors import (
|
|
|
22
21
|
MatelabUsageError,
|
|
23
22
|
)
|
|
24
23
|
from matelab.session import Session, Token
|
|
25
|
-
from matelab.streaming import DownloadStream
|
|
24
|
+
from matelab.streaming import ByteRange, DownloadStream
|
|
26
25
|
|
|
27
26
|
WireResponse = TypeVar("WireResponse", bound=BaseModel)
|
|
28
27
|
_ACCESS_EXPIRY_SKEW_MS = 30_000
|
|
@@ -152,12 +151,10 @@ class SessionTransport:
|
|
|
152
151
|
return session
|
|
153
152
|
if session.refresh.expires_at_ms <= time.time_ns() // 1_000_000:
|
|
154
153
|
raise MatelabAuthenticationError("Matelab refresh token expired.")
|
|
155
|
-
|
|
156
|
-
wire_models.TokenRefreshRequest
|
|
157
|
-
|
|
158
|
-
"The Matelab refresh token is invalid."
|
|
159
|
-
error_type=MatelabAuthenticationError,
|
|
160
|
-
)
|
|
154
|
+
try:
|
|
155
|
+
request = wire_models.TokenRefreshRequest.model_validate({"refresh_token": session.refresh.value})
|
|
156
|
+
except ValidationError:
|
|
157
|
+
raise MatelabAuthenticationError("The Matelab refresh token is invalid.") from None
|
|
161
158
|
response = await self.request(_REFRESH_TOKENS, payload=request)
|
|
162
159
|
self.session = Session(
|
|
163
160
|
access=Token(value=response.access.token, expires_at_ms=response.access.expiredAt),
|
|
@@ -266,15 +263,15 @@ class SessionTransport:
|
|
|
266
263
|
raise MatelabProtocolError("Matelab response does not match the Integration Contract.") from None
|
|
267
264
|
|
|
268
265
|
async def stream(
|
|
269
|
-
self, operation: StreamOperation, *, payload: _Payload,
|
|
266
|
+
self, operation: StreamOperation, *, payload: _Payload, byte_range: ByteRange | None = None
|
|
270
267
|
) -> DownloadStream:
|
|
271
268
|
encoded_payload = _payload_mapping(payload)
|
|
272
269
|
retry_access_expired = True
|
|
273
270
|
while True:
|
|
274
271
|
access_session = await self.session_for_request()
|
|
275
272
|
headers = {"Authorization": f"Bearer {access_session.access.value}"}
|
|
276
|
-
if
|
|
277
|
-
headers["Range"] =
|
|
273
|
+
if byte_range is not None:
|
|
274
|
+
headers["Range"] = byte_range.to_header()
|
|
278
275
|
|
|
279
276
|
request = self._http.build_request(
|
|
280
277
|
operation.method,
|
|
@@ -8,7 +8,7 @@ from jsonschema.protocols import Validator
|
|
|
8
8
|
from pydantic import BaseModel, JsonValue, ValidationError, model_validator
|
|
9
9
|
from pydantic_core import PydanticCustomError
|
|
10
10
|
|
|
11
|
-
from matelab.errors import
|
|
11
|
+
from matelab.errors import MatelabUsageError
|
|
12
12
|
|
|
13
13
|
_RUNTIME_RULES_KEY = "matelab-runtime-rules"
|
|
14
14
|
JsonObject = dict[str, JsonValue]
|
|
@@ -20,23 +20,19 @@ def validate_request(
|
|
|
20
20
|
model: type[ModelT],
|
|
21
21
|
data: Mapping[str, WireValue],
|
|
22
22
|
message: str,
|
|
23
|
-
*,
|
|
24
|
-
error_type: type[MatelabUsageError] | type[MatelabAuthenticationError] = MatelabUsageError,
|
|
25
23
|
) -> ModelT:
|
|
26
24
|
"""Validate caller input without exposing it in the public exception."""
|
|
27
25
|
try:
|
|
28
26
|
return model.model_validate(data)
|
|
29
27
|
except ValidationError:
|
|
30
|
-
raise
|
|
28
|
+
raise MatelabUsageError(message) from None
|
|
31
29
|
|
|
32
30
|
|
|
33
31
|
def encode_json_field(value: JsonValue, *, ensure_ascii: bool = True) -> str:
|
|
34
32
|
return json.dumps(value, ensure_ascii=ensure_ascii, separators=(",", ":"))
|
|
35
33
|
|
|
36
34
|
|
|
37
|
-
def _rules(extra: JsonObject
|
|
38
|
-
if extra is None:
|
|
39
|
-
return None
|
|
35
|
+
def _rules(extra: JsonObject) -> JsonObject | None:
|
|
40
36
|
rules = extra.get(_RUNTIME_RULES_KEY)
|
|
41
37
|
if rules is None:
|
|
42
38
|
return None
|
|
@@ -64,7 +60,7 @@ class WireModel(BaseModel):
|
|
|
64
60
|
data = cast(Mapping[str, WireValue], value)
|
|
65
61
|
|
|
66
62
|
model_extra = cls.model_config.get("json_schema_extra")
|
|
67
|
-
model_rules = _rules(model_extra if isinstance(model_extra, dict) else None
|
|
63
|
+
model_rules = _rules(model_extra) if isinstance(model_extra, dict) else None
|
|
68
64
|
if model_rules is not None:
|
|
69
65
|
_validate_schema(data, model_rules)
|
|
70
66
|
|
|
@@ -73,12 +69,12 @@ class WireModel(BaseModel):
|
|
|
73
69
|
if key not in data:
|
|
74
70
|
continue
|
|
75
71
|
extra = field.json_schema_extra
|
|
76
|
-
json_extra = extra if isinstance(extra, dict) else
|
|
72
|
+
json_extra = extra if isinstance(extra, dict) else {}
|
|
77
73
|
field_rules = _rules(json_extra)
|
|
78
74
|
raw_value = data[key]
|
|
79
75
|
if field_rules is not None:
|
|
80
76
|
_validate_schema(raw_value, field_rules)
|
|
81
|
-
content_schema = json_extra.get("contentSchema")
|
|
77
|
+
content_schema = json_extra.get("contentSchema")
|
|
82
78
|
if isinstance(content_schema, dict):
|
|
83
79
|
if raw_value in (None, "") and not field.is_required():
|
|
84
80
|
continue
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from typing import Self
|
|
2
2
|
|
|
3
3
|
from httpx2 import AsyncClient
|
|
4
|
+
from pydantic import ValidationError
|
|
4
5
|
|
|
5
6
|
from matelab._generated import models as wire_models
|
|
6
7
|
from matelab._transport import Encoding, Operation, SessionTransport
|
|
@@ -39,17 +40,18 @@ _CHAT_SSO = Operation(
|
|
|
39
40
|
def _session_from_wire(
|
|
40
41
|
source: wire_models.TokenLoginResponse | wire_models.ChatSsoCodeExchangeResponse,
|
|
41
42
|
) -> Session:
|
|
42
|
-
if source.username is None or not source.username.strip():
|
|
43
|
-
raise MatelabProtocolError("Matelab returned an identity with an empty username.")
|
|
44
43
|
if isinstance(source, wire_models.TokenLoginResponse):
|
|
45
44
|
access, refresh = source.access, source.refresh
|
|
46
45
|
else:
|
|
47
46
|
access, refresh = source.token.access, source.token.refresh
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
47
|
+
try:
|
|
48
|
+
return Session(
|
|
49
|
+
access=Token(value=access.token, expires_at_ms=access.expiredAt),
|
|
50
|
+
refresh=Token(value=refresh.token, expires_at_ms=refresh.expiredAt),
|
|
51
|
+
identity=Identity.model_validate(source, from_attributes=True),
|
|
52
|
+
)
|
|
53
|
+
except ValidationError:
|
|
54
|
+
raise MatelabProtocolError("Matelab returned invalid session data.") from None
|
|
53
55
|
|
|
54
56
|
|
|
55
57
|
class AsyncMatelab:
|
{matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a13}/src/matelab/domains/cloud_drive.py
RENAMED
|
@@ -332,6 +332,4 @@ class CloudDrive:
|
|
|
332
332
|
{"id": file.cloud_file_id, "thumb": int(thumbnail), "c": int(preview)},
|
|
333
333
|
"Cloud-drive file identity does not satisfy the Integration Contract.",
|
|
334
334
|
)
|
|
335
|
-
return await self._transport.stream(
|
|
336
|
-
_DOWNLOAD_FILE, payload=request, range_header=byte_range.to_header() if byte_range is not None else None
|
|
337
|
-
)
|
|
335
|
+
return await self._transport.stream(_DOWNLOAD_FILE, payload=request, byte_range=byte_range)
|
{matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a13}/src/matelab/domains/literature.py
RENAMED
|
@@ -718,9 +718,7 @@ class Literature:
|
|
|
718
718
|
},
|
|
719
719
|
"Literature PDF identity does not satisfy the Integration Contract.",
|
|
720
720
|
)
|
|
721
|
-
return await self._transport.stream(
|
|
722
|
-
_DOWNLOAD_PDF, payload=request, range_header=byte_range.to_header() if byte_range is not None else None
|
|
723
|
-
)
|
|
721
|
+
return await self._transport.stream(_DOWNLOAD_PDF, payload=request, byte_range=byte_range)
|
|
724
722
|
|
|
725
723
|
async def create(self, metadata: LiteratureMetadata, *, staged_pdf: StagedFile | None = None) -> None:
|
|
726
724
|
"""Create a literature item; the Provider acknowledgement contains no new item identity."""
|
|
@@ -1921,7 +1921,7 @@ class Records:
|
|
|
1921
1921
|
return await self._transport.stream(
|
|
1922
1922
|
_DOWNLOAD_RECORD_ATTACHMENT,
|
|
1923
1923
|
payload=request,
|
|
1924
|
-
|
|
1924
|
+
byte_range=byte_range,
|
|
1925
1925
|
)
|
|
1926
1926
|
|
|
1927
1927
|
async def download_comment_attachment(
|
|
@@ -1947,7 +1947,7 @@ class Records:
|
|
|
1947
1947
|
return await self._transport.stream(
|
|
1948
1948
|
_DOWNLOAD_COMMENT_ATTACHMENT,
|
|
1949
1949
|
payload=request,
|
|
1950
|
-
|
|
1950
|
+
byte_range=byte_range,
|
|
1951
1951
|
)
|
|
1952
1952
|
|
|
1953
1953
|
async def read(
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
from
|
|
1
|
+
from typing import Annotated
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel, ConfigDict, Field, StringConstraints
|
|
2
4
|
|
|
3
5
|
|
|
4
6
|
class Token(BaseModel):
|
|
@@ -16,7 +18,7 @@ class Identity(BaseModel):
|
|
|
16
18
|
model_config = ConfigDict(frozen=True, extra="forbid")
|
|
17
19
|
|
|
18
20
|
userid: int = Field(gt=0)
|
|
19
|
-
username: str =
|
|
21
|
+
username: Annotated[str, StringConstraints(strip_whitespace=True, min_length=1)]
|
|
20
22
|
email: str | None
|
|
21
23
|
|
|
22
24
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from collections.abc import AsyncIterator
|
|
2
|
-
from contextlib import
|
|
2
|
+
from contextlib import aclosing
|
|
3
3
|
from dataclasses import dataclass
|
|
4
4
|
from typing import Self
|
|
5
5
|
|
|
@@ -75,19 +75,17 @@ class DownloadStream:
|
|
|
75
75
|
|
|
76
76
|
async def aiter_bytes(self, chunk_size: int | None = None) -> AsyncIterator[bytes]:
|
|
77
77
|
try:
|
|
78
|
-
async
|
|
79
|
-
|
|
78
|
+
async with aclosing(self._response) as response:
|
|
79
|
+
async for chunk in response.aiter_bytes(chunk_size):
|
|
80
|
+
yield chunk
|
|
80
81
|
except RequestError:
|
|
81
|
-
with suppress(MatelabTransportError):
|
|
82
|
-
await self.aclose()
|
|
83
82
|
raise MatelabTransportError("Could not read data from the Matelab Provider.") from None
|
|
84
83
|
|
|
85
84
|
async def read(self) -> bytes:
|
|
86
85
|
try:
|
|
87
|
-
|
|
86
|
+
async with aclosing(self._response) as response:
|
|
87
|
+
return await response.aread()
|
|
88
88
|
except RequestError:
|
|
89
|
-
with suppress(MatelabTransportError):
|
|
90
|
-
await self.aclose()
|
|
91
89
|
raise MatelabTransportError("Could not read data from the Matelab Provider.") from None
|
|
92
90
|
|
|
93
91
|
async def aclose(self) -> None:
|
|
@@ -169,7 +169,7 @@ async def test_authenticate_rejects_empty_provider_username(username: str | None
|
|
|
169
169
|
|
|
170
170
|
async with AsyncClient(base_url="https://matelab.example/api", transport=MockTransport(handler)) as http_client:
|
|
171
171
|
client = AsyncMatelab("https://ignored.example", session=existing_session, http_client=http_client)
|
|
172
|
-
with pytest.raises(MatelabProtocolError, match="
|
|
172
|
+
with pytest.raises(MatelabProtocolError, match="invalid session data"):
|
|
173
173
|
_ = await client.authenticate("alice@example.org", "password")
|
|
174
174
|
|
|
175
175
|
assert client.session is existing_session
|
|
@@ -297,12 +297,13 @@ def test_session_models_validate_values_and_extra_fields() -> None:
|
|
|
297
297
|
_ = Token(value="access", expires_at_ms=0)
|
|
298
298
|
with pytest.raises(ValidationError, match="greater_than"):
|
|
299
299
|
_ = Identity(userid=0, username="User", email=None)
|
|
300
|
-
|
|
301
|
-
|
|
300
|
+
for username in ("", " "):
|
|
301
|
+
with pytest.raises(ValidationError, match="string_too_short"):
|
|
302
|
+
_ = Identity(userid=1, username=username, email=None)
|
|
302
303
|
with pytest.raises(ValidationError, match="extra_forbidden"):
|
|
303
304
|
_ = Session.model_validate({**_session().model_dump(), "schema_version": 1})
|
|
304
305
|
identity = Identity(userid=1, username=" Example User ", email=None)
|
|
305
|
-
assert identity.username == "
|
|
306
|
+
assert identity.username == "Example User"
|
|
306
307
|
assert Identity.model_json_schema()["properties"]["username"]["minLength"] == 1
|
|
307
308
|
|
|
308
309
|
|
|
@@ -97,7 +97,7 @@ async def test_stream_returns_lazy_binary_response_and_closes_on_context_exit(pr
|
|
|
97
97
|
|
|
98
98
|
async with AsyncClient(base_url="https://matelab.example/api", transport=MockTransport(handler)) as http_client:
|
|
99
99
|
transport = SessionTransport(http_client=http_client, session=provider_session)
|
|
100
|
-
stream = await transport.stream(_DOWNLOAD, payload={"h": _HASH},
|
|
100
|
+
stream = await transport.stream(_DOWNLOAD, payload={"h": _HASH}, byte_range=ByteRange.from_start(0))
|
|
101
101
|
assert stream.status_code == 200
|
|
102
102
|
assert stream.content_type == "application/octet-stream"
|
|
103
103
|
assert stream.content_length == 16
|
|
@@ -175,7 +175,7 @@ async def test_stream_validates_and_exposes_partial_response_metadata(provider_s
|
|
|
175
175
|
|
|
176
176
|
async with AsyncClient(base_url="https://matelab.example/api", transport=MockTransport(handler)) as http_client:
|
|
177
177
|
stream = await SessionTransport(http_client=http_client, session=provider_session).stream(
|
|
178
|
-
_DOWNLOAD, payload={"h": _HASH},
|
|
178
|
+
_DOWNLOAD, payload={"h": _HASH}, byte_range=ByteRange.between(1, 4)
|
|
179
179
|
)
|
|
180
180
|
async with stream:
|
|
181
181
|
assert await stream.read() == b"part"
|
|
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.0a12 → matelab_python_sdk-0.1.0a13}/scripts/check_installed_package.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a13}/src/matelab/_generated/__init__.py
RENAMED
|
File without changes
|
{matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a13}/src/matelab/_generated/models.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a13}/src/matelab/domains/notebooks.py
RENAMED
|
File without changes
|
{matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a13}/src/matelab/domains/templates.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.0a12 → matelab_python_sdk-0.1.0a13}/tests/provider/test_provider_smoke.py
RENAMED
|
File without changes
|
{matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a13}/tests/test_async_attachment_upload.py
RENAMED
|
File without changes
|
|
File without changes
|
{matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a13}/tests/test_comments_and_downloads.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.0a12 → matelab_python_sdk-0.1.0a13}/tests/test_notebook_lifecycle.py
RENAMED
|
File without changes
|
|
File without changes
|
{matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a13}/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.0a12 → matelab_python_sdk-0.1.0a13}/tests/test_template_lifecycle.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{matelab_python_sdk-0.1.0a12 → matelab_python_sdk-0.1.0a13}/tests/test_uploads_and_literature.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|