matelab-python-sdk 0.2.0a7__py3-none-any.whl → 0.2.0a9__py3-none-any.whl

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.
@@ -2,7 +2,7 @@
2
2
  #
3
3
  # @generated by datamodel-codegen:
4
4
  # filename: openapi.yaml
5
- # version: 0.86.0
5
+ # version: 0.87.0
6
6
 
7
7
  from enum import StrEnum
8
8
  from typing import Annotated, Any, Literal
@@ -11,7 +11,7 @@ from pydantic import ConfigDict, Field, RootModel, SecretStr, StrictBool, Strict
11
11
 
12
12
  from matelab._wire_validation import WireModel
13
13
 
14
- OPENAPI_INFO_VERSION = "2026.10.3"
14
+ OPENAPI_INFO_VERSION = "2026.11.1"
15
15
 
16
16
 
17
17
  class ResponseEnvelope(WireModel):
@@ -2759,7 +2759,10 @@ class StageTemporaryFileRequest(WireModel):
2759
2759
  """
2760
2760
  file: bytes | None = None
2761
2761
  """
2762
- Required for upload branches and omitted for abort.
2762
+ Required for upload branches and omitted for abort. The multipart
2763
+ filename's sanitized final extension selects the temporary bytes;
2764
+ keep that extension unchanged throughout continuation.
2765
+
2763
2766
  """
2764
2767
 
2765
2768
 
@@ -16,6 +16,7 @@ from matelab._hashes import LOWERCASE_SHA256_PATTERN
16
16
  from matelab._transport import Encoding, Operation, SessionTransport, StreamOperation
17
17
  from matelab._wire_validation import WireValue, encode_json_field, validate_multipart_request, validate_request
18
18
  from matelab.domains._attachment_content import prepare_attachment_content
19
+ from matelab.domains.cloud_drive import CloudFile
19
20
  from matelab.domains.notebooks import NotebookPermissions, NotebookRef, validate_notebook_scope
20
21
  from matelab.domains.uploads import UploadBindingRef
21
22
  from matelab.domains.users import UserRef
@@ -432,6 +433,7 @@ RecordImportValue: TypeAlias = (
432
433
  | bool
433
434
  | None
434
435
  | StagedAttachment
436
+ | CloudFile
435
437
  | Mapping[str, "RecordImportValue"]
436
438
  | list["RecordImportValue"]
437
439
  | tuple["RecordImportValue", ...]
@@ -488,11 +490,11 @@ class RecordValueAddition:
488
490
 
489
491
  @dataclass(frozen=True, slots=True)
490
492
  class RecordFormAttachmentFieldAddition:
491
- """Add one new file field and finalize one staged attachment."""
493
+ """Add one file field from a staged upload or an existing cloud file."""
492
494
 
493
495
  module: str
494
496
  name: str
495
- attachment: StagedAttachment
497
+ attachment: StagedAttachment | CloudFile
496
498
 
497
499
 
498
500
  @dataclass(frozen=True, slots=True)
@@ -929,8 +931,8 @@ def _current_attachment(source: RecordLocator, current: Record, requested: Recor
929
931
  return attachment
930
932
 
931
933
 
932
- def _iter_import_attachments(value: RecordImportValue) -> Iterator[StagedAttachment]:
933
- if isinstance(value, StagedAttachment):
934
+ def _iter_import_attachments(value: RecordImportValue) -> Iterator[StagedAttachment | CloudFile]:
935
+ if isinstance(value, StagedAttachment | CloudFile):
934
936
  yield value
935
937
  elif isinstance(value, Mapping):
936
938
  for item in value.values():
@@ -940,13 +942,13 @@ def _iter_import_attachments(value: RecordImportValue) -> Iterator[StagedAttachm
940
942
  yield from _iter_import_attachments(item)
941
943
 
942
944
 
943
- def _iter_dataset_attachments(items: Sequence[RecordImportItem]) -> Iterator[StagedAttachment]:
945
+ def _iter_dataset_attachments(items: Sequence[RecordImportItem]) -> Iterator[StagedAttachment | CloudFile]:
944
946
  for item in items:
945
947
  for value in item.data.values():
946
948
  yield from _iter_import_attachments(value)
947
949
 
948
950
 
949
- def _iter_update_attachments(changes: tuple[RecordChange, ...]) -> Iterator[StagedAttachment]:
951
+ def _iter_update_attachments(changes: tuple[RecordChange, ...]) -> Iterator[StagedAttachment | CloudFile]:
950
952
  for change in changes:
951
953
  if isinstance(
952
954
  change,
@@ -965,6 +967,7 @@ def _iter_update_attachments(changes: tuple[RecordChange, ...]) -> Iterator[Stag
965
967
  def _validate_staged_name_update_shape(changes: tuple[RecordChange, ...]) -> None:
966
968
  has_finalizer = any(
967
969
  isinstance(change, RecordFormAttachmentFieldAddition | RecordTableFileCellSet | RecordTableRowAttachmentAppend)
970
+ and isinstance(change.attachment, StagedAttachment)
968
971
  or isinstance(change, RecordFilesAttachmentAppend)
969
972
  and bool(change.folder_path)
970
973
  for change in changes
@@ -994,7 +997,7 @@ def _record_update_requires_current_state(changes: tuple[RecordChange, ...]) ->
994
997
  )
995
998
 
996
999
 
997
- def _encode_record_attachment_reference(attachment: StagedAttachment) -> str:
1000
+ def _encode_record_attachment_reference(attachment: StagedAttachment | CloudFile) -> str:
998
1001
  return f"#hash{{{attachment.filename}, {attachment.sha256}}}"
999
1002
 
1000
1003
 
@@ -1925,7 +1928,7 @@ class Records:
1925
1928
  | RecordTableRowAttachmentAppend
1926
1929
  | RecordFilesAttachmentAppend,
1927
1930
  ):
1928
- self._compile_staged_record_attachment_change(change, patch)
1931
+ self._compile_record_attachment_addition(change, patch)
1929
1932
  else:
1930
1933
  self._compile_observed_record_attachment_change(
1931
1934
  source, current, change, removals, value_deletions, patch
@@ -1939,7 +1942,7 @@ class Records:
1939
1942
  patch.claim(path)
1940
1943
  patch.modify.append({"path": list(path), "data": _encode_record_rich_text(change)})
1941
1944
 
1942
- def _compile_staged_record_attachment_change(
1945
+ def _compile_record_attachment_addition(
1943
1946
  self,
1944
1947
  change: (
1945
1948
  RecordFormAttachmentFieldAddition
@@ -1953,19 +1956,31 @@ class Records:
1953
1956
  patch.reject_deleted_module(change.module)
1954
1957
  module = patch.modules.get(change.module)
1955
1958
  if module is None or module.get("type") != "form":
1956
- raise MatelabUsageError("A record attachment can be finalized only into an existing form module.")
1957
- if not change.name or _module_field(module, change.name) is not None:
1958
- raise MatelabUsageError("A record attachment requires a new non-empty form field name.")
1959
+ raise MatelabUsageError("A record attachment requires an existing form module.")
1960
+ if (
1961
+ not change.name
1962
+ or _module_field(module, change.name) is not None
1963
+ or any(
1964
+ isinstance(addition, dict)
1965
+ and addition.get("module") == change.module
1966
+ and addition.get("name") == change.name
1967
+ for addition in patch.add_values
1968
+ )
1969
+ ):
1970
+ raise MatelabUsageError("A record attachment requires a new, unique non-empty form field name.")
1971
+ patch.claim((change.module, change.name))
1972
+ attachment = change.attachment
1973
+ if isinstance(attachment, CloudFile):
1974
+ model = wire_models.AddValueOperation
1975
+ reference = _encode_record_attachment_reference(attachment)
1976
+ else:
1977
+ model = wire_models.AddNewFormFileFieldWithStagedAttachmentOperation
1978
+ reference = f"#file{{{attachment.binding.uid}}}"
1959
1979
  patch.add_values.append(
1960
1980
  _operation_payload(
1961
- wire_models.AddNewFormFileFieldWithStagedAttachmentOperation,
1962
- {
1963
- "module": change.module,
1964
- "name": change.name,
1965
- "type": "file",
1966
- "data": [f"#file{{{change.attachment.binding.uid}}}"],
1967
- },
1968
- "New form-file attachment finalizer does not satisfy the contract.",
1981
+ model,
1982
+ {"module": change.module, "name": change.name, "type": "file", "data": [reference]},
1983
+ "New form-file attachment does not satisfy the contract.",
1969
1984
  )
1970
1985
  )
1971
1986
  return
@@ -2235,11 +2250,21 @@ class Records:
2235
2250
  )
2236
2251
 
2237
2252
  def _staged_attachment_keys(
2238
- self, notebook: NotebookRef, attachments: Iterable[StagedAttachment]
2253
+ self, notebook: NotebookRef, attachments: Iterable[StagedAttachment | CloudFile]
2239
2254
  ) -> set[_StagedAttachmentKey]:
2240
2255
  uploader = UserRef(userid=self._transport.require_session().identity.userid)
2241
2256
  keys: set[_StagedAttachmentKey] = set()
2242
2257
  for attachment in attachments:
2258
+ if isinstance(attachment, CloudFile):
2259
+ if (
2260
+ not attachment.filename
2261
+ or len(attachment.filename) > 255
2262
+ or "\r" in attachment.filename
2263
+ or "\n" in attachment.filename
2264
+ or LOWERCASE_SHA256_PATTERN.fullmatch(attachment.sha256) is None
2265
+ ):
2266
+ raise MatelabUsageError("Cloud-file metadata does not satisfy the Integration Contract.")
2267
+ continue
2243
2268
  if attachment.notebook != notebook:
2244
2269
  raise MatelabUsageError("Staged attachment belongs to a different notebook.")
2245
2270
  if attachment.uploader != uploader:
@@ -2262,9 +2287,11 @@ class Records:
2262
2287
  """Encode one domain import value."""
2263
2288
  if isinstance(value, StagedAttachment):
2264
2289
  return f"#file{{{value.binding.uid}}}"
2290
+ if isinstance(value, CloudFile):
2291
+ return _encode_record_attachment_reference(value)
2265
2292
  if isinstance(value, str):
2266
2293
  if _contains_native_attachment_reference(value):
2267
- raise MatelabUsageError("Provider-native attachment references require a staged attachment object.")
2294
+ raise MatelabUsageError("Provider-native attachment references require a typed attachment object.")
2268
2295
  return value
2269
2296
  if isinstance(value, Mapping):
2270
2297
  return {name: self._encode_import_value(item) for name, item in value.items()}
@@ -79,6 +79,7 @@ class Uploads:
79
79
  complete_sha256: str | None = None,
80
80
  content_type: str = "application/octet-stream",
81
81
  ) -> StagedUploadSession | StagedFile:
82
+ """Stage bytes; preserve the sanitized, case-sensitive filename extension when resuming."""
82
83
  if session is None:
83
84
  session = StagedUploadSession(
84
85
  binding=UploadBindingRef.new(), upload_session_id=f"tmp-{secrets.token_urlsafe(24)}"
@@ -162,7 +163,7 @@ class Uploads:
162
163
  )
163
164
 
164
165
  async def abort(self, upload: StagedUploadSession | StagedFile) -> None:
165
- """Cancel incomplete bytes; completed files remain staged (PVD-052)."""
166
+ """Cancel incomplete extension variants; completed staging identities remain untouched (PVD-052)."""
166
167
  if isinstance(upload, StagedFile):
167
168
  # The completed file keeps this Provider identity private except for same-module cleanup.
168
169
  upload_session_id = upload._upload_session_id # pyright: ignore[reportPrivateUsage]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: matelab-python-sdk
3
- Version: 0.2.0a7
3
+ Version: 0.2.0a9
4
4
  Summary: Reusable async Python client for the Matelab Integration Contract
5
5
  Author: CMPDC, Institute of Physics, Chinese Academy of Sciences
6
6
  License-Expression: Apache-2.0
@@ -20,7 +20,7 @@ Classifier: Topic :: Scientific/Engineering
20
20
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
21
21
  Classifier: Typing :: Typed
22
22
  Requires-Python: >=3.11
23
- Requires-Dist: httpx2>=2.15.1
23
+ Requires-Dist: httpx2>=2.16.0
24
24
  Requires-Dist: jsonschema>=4.26.0
25
25
  Requires-Dist: pydantic>=2.14.1
26
26
  Requires-Dist: typing-extensions>=4.16.0
@@ -8,19 +8,19 @@ matelab/py.typed,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
8
8
  matelab/session.py,sha256=D8i9uyAt32-bADotS-Lrb7RpmWRNYNIkIpZUijTYvy4,899
9
9
  matelab/streaming.py,sha256=36epH2josWk87Ors_JY0YsjNmtiq3NZa2iB9djLmlnY,3792
10
10
  matelab/_generated/__init__.py,sha256=jmGbXLLe3JorsGU7OUdF8iKGewFMRqkCzgyFAw7C15w,79
11
- matelab/_generated/models.py,sha256=X_rGeNiyDhu06kX0X6wUjOSH2oBBBjdHYX4CvO3pyCU,179560
11
+ matelab/_generated/models.py,sha256=VhE1ixBb-qflSTEO8VEbixnB6jtHshXqCd9gAgJBGQ8,179704
12
12
  matelab/domains/__init__.py,sha256=C34IWsGoCAwP7r6uYKJ79DJkToZyrZ4LWA87ufc1i0Q,65
13
13
  matelab/domains/_attachment_content.py,sha256=z26hwFpdgAk2x1w8EGe00XBLtJ3QFDX8BPS35Zc5SoE,1461
14
14
  matelab/domains/cloud_drive.py,sha256=EoqAYWvLVUfVOsic3g-TxsQEtAI3wTp5h0_vB79KpwY,10406
15
15
  matelab/domains/groups.py,sha256=H8-Da2WTm2Ui4m3Nr1bhMNMn7stbzbRa3EmpkTRmkBU,1300
16
16
  matelab/domains/literature.py,sha256=xpdHuFCR1RpEPB_Lk8VOaddlIpGQ2rnp4kxl5a8Bk4M,27473
17
17
  matelab/domains/notebooks.py,sha256=_SvffaViECSEOkN5iwXtSv6El3fK2OYaR4RJvUCXfGk,13477
18
- matelab/domains/records.py,sha256=laluv0XiN26369PcO-mGxlugaDrqE-M67yICFYwoMzI,94263
18
+ matelab/domains/records.py,sha256=U3zOLwTjJ1YbnuayGXKroEKhUZLhsmjRCYtnFaDyJzQ,95614
19
19
  matelab/domains/templates.py,sha256=-x7RqRLhrRnAK3HGK3c4I3C-SsAk7zpicIcvzoVd414,17698
20
- matelab/domains/uploads.py,sha256=1aW_FlxKQzX9mKsaskBdetk5Mxzxi07JLGA-2Sm-tUM,7570
20
+ matelab/domains/uploads.py,sha256=6y4i85UWGxumasWw9HAubcgtK_fqgZLnBUbX0oghxQU,7699
21
21
  matelab/domains/users.py,sha256=ZhlLzC_AadwYqNqxQrqkUBgIUqL7MJ6FgMyxwq-az8s,1445
22
- matelab_python_sdk-0.2.0a7.dist-info/METADATA,sha256=i4ucOVYjS7P9THP8gPAiSqgGXmIXGFXikNhxOyCS4aU,2436
23
- matelab_python_sdk-0.2.0a7.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
24
- matelab_python_sdk-0.2.0a7.dist-info/licenses/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
25
- matelab_python_sdk-0.2.0a7.dist-info/licenses/NOTICE,sha256=Hc4scCJTof5VL3JCPI2aaewnnzCejIHUGuBVYBf-klc,98
26
- matelab_python_sdk-0.2.0a7.dist-info/RECORD,,
22
+ matelab_python_sdk-0.2.0a9.dist-info/METADATA,sha256=l5luHe5D2BRV0uAUoy1EusPL6A_bpCGQSlqHEp08_Ro,2436
23
+ matelab_python_sdk-0.2.0a9.dist-info/WHEEL,sha256=2iZrJkDIkeDh0k9SikKMqFuRP-ua6mb4Ir1AsER4BEw,87
24
+ matelab_python_sdk-0.2.0a9.dist-info/licenses/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
25
+ matelab_python_sdk-0.2.0a9.dist-info/licenses/NOTICE,sha256=Hc4scCJTof5VL3JCPI2aaewnnzCejIHUGuBVYBf-klc,98
26
+ matelab_python_sdk-0.2.0a9.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.32.4
2
+ Generator: hatchling 1.33.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any