matelab-python-sdk 0.1.0a1__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.
matelab/cloud_drive.py ADDED
@@ -0,0 +1,789 @@
1
+ import json
2
+ import math
3
+ import re
4
+ from collections.abc import Mapping, Sequence
5
+ from dataclasses import dataclass, field
6
+ from enum import StrEnum
7
+ from types import MappingProxyType
8
+ from typing import Literal, TypeAlias, cast
9
+
10
+ from pydantic import ValidationError
11
+
12
+ from matelab._generated.models import BasicSuccessResponse as WireBasicSuccessResponse
13
+ from matelab._generated.models import BindCloudDriveFileRequest as WireBindCloudDriveFileRequest
14
+ from matelab._generated.models import CloudDriveFile as WireCloudDriveFile
15
+ from matelab._generated.models import CloudDriveFileIdList as WireCloudDriveFileIdList
16
+ from matelab._generated.models import CloudDriveFileListResponse as WireCloudDriveFileListResponse
17
+ from matelab._generated.models import CloudDriveFolder as WireCloudDriveFolder
18
+ from matelab._generated.models import CloudDriveFolderIdRequest as WireCloudDriveFolderIdRequest
19
+ from matelab._generated.models import CloudDriveIdentityResponse as WireCloudDriveIdentityResponse
20
+ from matelab._generated.models import CloudDriveRootPermissions as WireCloudDriveRootPermissions
21
+ from matelab._generated.models import DeleteCloudDriveFilesRequest as WireDeleteCloudDriveFilesRequest
22
+ from matelab._generated.models import DownloadCloudDriveFileParametersQuery as WireDownloadCloudDriveFileQuery
23
+ from matelab._generated.models import EditCloudDriveFileRequest as WireEditCloudDriveFileRequest
24
+ from matelab._generated.models import ListCloudDriveFilesParametersQuery as WireListCloudDriveFilesQuery
25
+ from matelab._generated.models import MoveCloudDriveFolderRequest as WireMoveCloudDriveFolderRequest
26
+ from matelab._generated.models import SaveCloudDriveFolderRequest as WireSaveCloudDriveFolderRequest
27
+ from matelab._generated.models import TransferCloudDriveFilesRequest as WireTransferCloudDriveFilesRequest
28
+ from matelab._transport import Encoding, Operation, SessionTransport, StreamOperation, multipart_fields
29
+ from matelab.errors import MatelabError, MatelabProtocolError, MatelabUsageError, MatelabVerificationError
30
+ from matelab.streaming import ByteRange, DownloadStream
31
+ from matelab.uploads import StagedFile
32
+ from matelab.users import UserRef
33
+
34
+ _LIST_FILES = Operation(
35
+ method="GET",
36
+ path="/files/files",
37
+ encoding=Encoding.QUERY,
38
+ response_model=WireCloudDriveFileListResponse,
39
+ success_codes=frozenset({0}),
40
+ retry_on_access_expired=True,
41
+ )
42
+ _SAVE_FOLDER = Operation(
43
+ method="POST",
44
+ path="/files/filetype_edit",
45
+ encoding=Encoding.MULTIPART,
46
+ response_model=WireCloudDriveIdentityResponse,
47
+ success_codes=frozenset({0}),
48
+ )
49
+ _DELETE_FOLDER = Operation(
50
+ method="POST",
51
+ path="/files/filetype_del",
52
+ encoding=Encoding.MULTIPART,
53
+ response_model=WireBasicSuccessResponse,
54
+ success_codes=frozenset({0}),
55
+ )
56
+ _MOVE_FOLDER = Operation(
57
+ method="POST",
58
+ path="/files/filetype_move",
59
+ encoding=Encoding.MULTIPART,
60
+ response_model=WireBasicSuccessResponse,
61
+ success_codes=frozenset({0}),
62
+ )
63
+ _BIND_STAGED_FILE = Operation(
64
+ method="POST",
65
+ path="/files/file_add",
66
+ encoding=Encoding.MULTIPART,
67
+ response_model=WireBasicSuccessResponse,
68
+ success_codes=frozenset({0}),
69
+ )
70
+ _EDIT_FILE = Operation(
71
+ method="POST",
72
+ path="/files/file_edit",
73
+ encoding=Encoding.MULTIPART,
74
+ response_model=WireBasicSuccessResponse,
75
+ success_codes=frozenset({0}),
76
+ )
77
+ _DELETE_FILES = Operation(
78
+ method="POST",
79
+ path="/files/file_del",
80
+ encoding=Encoding.MULTIPART,
81
+ response_model=WireBasicSuccessResponse,
82
+ success_codes=frozenset({0}),
83
+ )
84
+ _MOVE_FILES = Operation(
85
+ method="POST",
86
+ path="/files/move",
87
+ encoding=Encoding.MULTIPART,
88
+ response_model=WireBasicSuccessResponse,
89
+ success_codes=frozenset({0}),
90
+ )
91
+ _DOWNLOAD_FILE = StreamOperation(method="GET", path="/files/url", retry_on_access_expired=True)
92
+ _SHA256_PATTERN = re.compile(r"^[0-9a-f]{64}$")
93
+
94
+
95
+ def _empty_mapping() -> Mapping[str, object]:
96
+ return MappingProxyType({})
97
+
98
+
99
+ class CloudDriveOrder(StrEnum):
100
+ MODIFIED_AT = "modified_at"
101
+ FILENAME = "filename"
102
+
103
+
104
+ @dataclass(frozen=True, slots=True)
105
+ class CloudDriveRootRef:
106
+ """The caller's synthetic personal cloud-drive root."""
107
+
108
+
109
+ @dataclass(frozen=True, slots=True)
110
+ class CloudFolderRef:
111
+ cloud_folder_id: int
112
+
113
+
114
+ CloudDriveLocation: TypeAlias = CloudDriveRootRef | CloudFolderRef
115
+
116
+
117
+ @dataclass(frozen=True, slots=True)
118
+ class CloudFileRef:
119
+ cloud_file_id: int
120
+
121
+
122
+ @dataclass(frozen=True, slots=True)
123
+ class CloudDriveRootPermissions:
124
+ permission_mask: int
125
+ can_create_folder: bool
126
+ can_delete_folder: bool
127
+ can_edit_folder: bool
128
+ can_create_file: bool
129
+ can_delete_file: bool
130
+ can_view_file: bool
131
+ can_edit_file: bool
132
+ attributes: Mapping[str, object] = field(default_factory=_empty_mapping)
133
+
134
+
135
+ @dataclass(frozen=True, slots=True)
136
+ class CloudDriveRoot:
137
+ ref: CloudDriveRootRef
138
+ name: str
139
+ description: str
140
+ permissions: CloudDriveRootPermissions
141
+
142
+
143
+ @dataclass(frozen=True, slots=True)
144
+ class CloudFolder:
145
+ ref: CloudFolderRef
146
+ name: str
147
+ parent: CloudDriveLocation
148
+ direct_file_count: int
149
+ description: str | None
150
+ attributes: Mapping[str, object] = field(default_factory=_empty_mapping)
151
+
152
+
153
+ @dataclass(frozen=True, slots=True)
154
+ class CloudFile:
155
+ ref: CloudFileRef
156
+ location: CloudDriveLocation | None
157
+ filename: str
158
+ sha256: str = field(repr=False)
159
+ size: int
160
+ owner: UserRef
161
+ owner_name: str | None
162
+ modified_at: str | None
163
+ description: str
164
+ display_date: str
165
+ display_time: str
166
+ attributes: Mapping[str, object] = field(default_factory=_empty_mapping)
167
+
168
+
169
+ @dataclass(frozen=True, slots=True)
170
+ class CloudDriveQuota:
171
+ quota_gib: int | None
172
+ used_bytes: int
173
+
174
+ @property
175
+ def unlimited(self) -> bool:
176
+ return self.quota_gib is None
177
+
178
+
179
+ @dataclass(frozen=True, slots=True)
180
+ class CloudFolderTree:
181
+ folders: tuple[CloudFolder, ...]
182
+ ordering: Literal["provider_unspecified"] = "provider_unspecified"
183
+
184
+
185
+ @dataclass(frozen=True, slots=True)
186
+ class CloudFilePage:
187
+ files: tuple[CloudFile, ...]
188
+ folder: CloudDriveLocation | None
189
+ search: str
190
+ scope: Literal["selected_folder", "search_across_personal_root"]
191
+ page: int
192
+ requested_page_size: int
193
+ effective_page_size: int
194
+ total_count: int
195
+ has_more: bool
196
+ order: CloudDriveOrder
197
+ descending: bool
198
+ ordering: Literal["provider_selected_without_stable_id_tiebreaker"] = (
199
+ "provider_selected_without_stable_id_tiebreaker"
200
+ )
201
+
202
+
203
+ @dataclass(frozen=True, slots=True)
204
+ class CloudDriveListing:
205
+ file_page: CloudFilePage
206
+ folder_tree: CloudFolderTree
207
+ quota: CloudDriveQuota
208
+ root: CloudDriveRoot
209
+
210
+
211
+ @dataclass(frozen=True, slots=True)
212
+ class CloudFolderMetadata:
213
+ name: str
214
+ description: str = ""
215
+
216
+
217
+ @dataclass(frozen=True, slots=True)
218
+ class CloudFolderSaveResult:
219
+ folder: CloudFolderRef
220
+ requested: CloudFolderMetadata
221
+ action: Literal["created", "updated"]
222
+ observed: CloudFolder | None
223
+ confirmation: Literal["observed_matching", "observed_mismatch", "not_observed"]
224
+ automatic_retry: Literal[False] = False
225
+
226
+
227
+ @dataclass(frozen=True, slots=True)
228
+ class CloudFolderMoveResult:
229
+ folder: CloudFolderRef
230
+ target: CloudDriveLocation
231
+ observed: CloudFolder | None
232
+ confirmation: Literal["observed_in_target", "observed_elsewhere", "not_observed"]
233
+ automatic_retry: Literal[False] = False
234
+
235
+
236
+ @dataclass(frozen=True, slots=True)
237
+ class CloudFolderDeleteResult:
238
+ folder: CloudFolderRef
239
+ absence_confirmed: bool
240
+ verification: Literal["personal_folder_tree_snapshot"] = "personal_folder_tree_snapshot"
241
+ recoverable: Literal[False] = False
242
+ automatic_retry: Literal[False] = False
243
+
244
+
245
+ @dataclass(frozen=True, slots=True)
246
+ class CloudFileBindResult:
247
+ staged_file: StagedFile
248
+ target: CloudDriveLocation
249
+ observed: CloudFile | None
250
+ cloud_file: CloudFileRef | None
251
+ confirmation: Literal["uniquely_observed", "not_observed", "ambiguous_observation"]
252
+ staging_owner_verification: Literal["provider_does_not_verify_uploader"] = "provider_does_not_verify_uploader"
253
+ finalize_atomicity: Literal["not_contractually_guaranteed"] = "not_contractually_guaranteed"
254
+ automatic_retry: Literal[False] = False
255
+
256
+
257
+ @dataclass(frozen=True, slots=True)
258
+ class CloudFileUpdateResult:
259
+ file: CloudFileRef
260
+ requested_filename: str
261
+ requested_description: str
262
+ observed: CloudFile | None
263
+ confirmation: Literal["observed_matching", "observed_mismatch", "not_observed"]
264
+ automatic_retry: Literal[False] = False
265
+
266
+
267
+ @dataclass(frozen=True, slots=True)
268
+ class CloudFileMoveResult:
269
+ files: tuple[CloudFileRef, ...]
270
+ target: CloudDriveLocation
271
+ observed_in_target: tuple[CloudFileRef, ...]
272
+ not_observed_in_target: tuple[CloudFileRef, ...]
273
+ provider_item_results: None = None
274
+ atomicity: Literal["provider_does_not_report_or_guarantee_batch_atomicity"] = (
275
+ "provider_does_not_report_or_guarantee_batch_atomicity"
276
+ )
277
+ automatic_retry: Literal[False] = False
278
+
279
+
280
+ @dataclass(frozen=True, slots=True)
281
+ class CloudFilePermanentDeleteResult:
282
+ files: tuple[CloudFileRef, ...]
283
+ confirmed_absent: tuple[CloudFileRef, ...]
284
+ still_visible: tuple[CloudFileRef, ...]
285
+ provider_item_results: None = None
286
+ verification: Literal["post_delete_snapshots"] = "post_delete_snapshots"
287
+ atomicity: Literal["provider_does_not_report_or_guarantee_batch_atomicity"] = (
288
+ "provider_does_not_report_or_guarantee_batch_atomicity"
289
+ )
290
+ recoverable: Literal[False] = False
291
+ automatic_retry: Literal[False] = False
292
+
293
+
294
+ class CloudDrive:
295
+ """Personal cloud-drive browsing, lifecycle, staging finalization, and streaming."""
296
+
297
+ def __init__(self, transport: SessionTransport) -> None:
298
+ self._transport: SessionTransport = transport
299
+
300
+ async def list(
301
+ self,
302
+ folder: CloudDriveLocation | None = None,
303
+ *,
304
+ page: int = 1,
305
+ page_size: int = 20,
306
+ order: CloudDriveOrder = CloudDriveOrder.MODIFIED_AT,
307
+ descending: bool = True,
308
+ search: str = "",
309
+ ) -> CloudDriveListing:
310
+ selected = folder or CloudDriveRootRef()
311
+ folder_id = self._location_id(selected)
312
+ try:
313
+ selected_order = CloudDriveOrder(order)
314
+ except (TypeError, ValueError):
315
+ raise MatelabUsageError("Cloud-drive order must be modified_at or filename.") from None
316
+ if type(cast(object, descending)) is not bool:
317
+ raise MatelabUsageError("Cloud-drive descending selection must be a boolean.")
318
+ try:
319
+ request = WireListCloudDriveFilesQuery.model_validate(
320
+ {
321
+ "type_id": folder_id,
322
+ "root_id": 0,
323
+ "page": page,
324
+ "page_size": page_size,
325
+ "order_by": 0 if selected_order is CloudDriveOrder.MODIFIED_AT else 1,
326
+ "order_desc": int(descending),
327
+ "search": search,
328
+ }
329
+ )
330
+ except ValidationError:
331
+ raise MatelabUsageError("Cloud-drive list input does not satisfy the Integration Contract.") from None
332
+ response = await self._transport.request(_LIST_FILES, payload=request.model_dump(mode="json"))
333
+ return self._listing(
334
+ response,
335
+ selected=selected,
336
+ page=request.page,
337
+ page_size=request.page_size,
338
+ order=selected_order,
339
+ descending=descending,
340
+ search=request.search,
341
+ )
342
+
343
+ async def create_folder(
344
+ self, metadata: CloudFolderMetadata, *, parent: CloudDriveLocation | None = None
345
+ ) -> CloudFolderSaveResult:
346
+ selected_parent = parent or CloudDriveRootRef()
347
+ request = self._folder_request(metadata, folder=None, parent=selected_parent)
348
+ response = await self._transport.request(_SAVE_FOLDER, files=multipart_fields(request.model_dump(mode="json")))
349
+ folder = CloudFolderRef(cloud_folder_id=response.id)
350
+ observed = self._find_folder((await self._verified_list("cloud-drive folder creation")).folder_tree, folder)
351
+ return CloudFolderSaveResult(
352
+ folder=folder,
353
+ requested=metadata,
354
+ action="created",
355
+ observed=observed,
356
+ confirmation=self._folder_save_confirmation(observed, metadata, selected_parent),
357
+ )
358
+
359
+ async def update_folder(self, folder: CloudFolderRef, metadata: CloudFolderMetadata) -> CloudFolderSaveResult:
360
+ request = self._folder_request(metadata, folder=folder, parent=CloudDriveRootRef())
361
+ response = await self._transport.request(_SAVE_FOLDER, files=multipart_fields(request.model_dump(mode="json")))
362
+ if response.id != folder.cloud_folder_id:
363
+ raise MatelabProtocolError("Matelab returned a different folder identity for a cloud-drive update.")
364
+ observed = self._find_folder((await self._verified_list("cloud-drive folder update")).folder_tree, folder)
365
+ if observed is None:
366
+ confirmation: Literal["observed_matching", "observed_mismatch", "not_observed"] = "not_observed"
367
+ elif observed.name == metadata.name and (observed.description or "") == metadata.description:
368
+ confirmation = "observed_matching"
369
+ else:
370
+ confirmation = "observed_mismatch"
371
+ return CloudFolderSaveResult(
372
+ folder=folder, requested=metadata, action="updated", observed=observed, confirmation=confirmation
373
+ )
374
+
375
+ async def move_folder(
376
+ self, folder: CloudFolderRef, target: CloudDriveLocation | None = None
377
+ ) -> CloudFolderMoveResult:
378
+ selected_target = target or CloudDriveRootRef()
379
+ try:
380
+ request = WireMoveCloudDriveFolderRequest.model_validate(
381
+ {"id": self._folder_id(folder), "target": self._location_id(selected_target)}
382
+ )
383
+ except ValidationError:
384
+ raise MatelabUsageError("Cloud-drive folder move does not satisfy the Integration Contract.") from None
385
+ _ = await self._transport.request(_MOVE_FOLDER, files=multipart_fields(request.model_dump(mode="json")))
386
+ observed = self._find_folder((await self._verified_list("cloud-drive folder move")).folder_tree, folder)
387
+ if observed is None:
388
+ confirmation: Literal["observed_in_target", "observed_elsewhere", "not_observed"] = "not_observed"
389
+ elif observed.parent == selected_target:
390
+ confirmation = "observed_in_target"
391
+ else:
392
+ confirmation = "observed_elsewhere"
393
+ return CloudFolderMoveResult(
394
+ folder=folder, target=selected_target, observed=observed, confirmation=confirmation
395
+ )
396
+
397
+ async def delete_folder(self, folder: CloudFolderRef) -> CloudFolderDeleteResult:
398
+ try:
399
+ request = WireCloudDriveFolderIdRequest(id=self._folder_id(folder))
400
+ except ValidationError:
401
+ raise MatelabUsageError("Cloud-drive folder identity does not satisfy the Integration Contract.") from None
402
+ _ = await self._transport.request(_DELETE_FOLDER, files=multipart_fields(request.model_dump(mode="json")))
403
+ observed = self._find_folder((await self._verified_list("cloud-drive folder deletion")).folder_tree, folder)
404
+ return CloudFolderDeleteResult(folder=folder, absence_confirmed=observed is None)
405
+
406
+ async def bind_staged_file(
407
+ self, staged_file: StagedFile, *, target: CloudDriveLocation | None = None
408
+ ) -> CloudFileBindResult:
409
+ self._validate_staged_file(staged_file)
410
+ selected_target = target or CloudDriveRootRef()
411
+ try:
412
+ request = WireBindCloudDriveFileRequest.model_validate(
413
+ {"file_id": staged_file.temporary_file_id, "type_id": self._location_id(selected_target), "root_id": 0}
414
+ )
415
+ except ValidationError:
416
+ raise MatelabUsageError(
417
+ "Cloud-drive staged-file binding does not satisfy the Integration Contract."
418
+ ) from None
419
+ _ = await self._transport.request(_BIND_STAGED_FILE, files=multipart_fields(request.model_dump(mode="json")))
420
+ files = await self._verified_folder_files(selected_target, "cloud-drive staged-file binding")
421
+ matches = tuple(
422
+ item
423
+ for item in files
424
+ if item.filename == staged_file.filename
425
+ and item.sha256 == staged_file.sha256
426
+ and item.size == staged_file.size
427
+ )
428
+ if len(matches) == 1:
429
+ confirmation: Literal["uniquely_observed", "not_observed", "ambiguous_observation"] = "uniquely_observed"
430
+ observed = matches[0]
431
+ elif matches:
432
+ confirmation = "ambiguous_observation"
433
+ observed = None
434
+ else:
435
+ confirmation = "not_observed"
436
+ observed = None
437
+ return CloudFileBindResult(
438
+ staged_file=staged_file,
439
+ target=selected_target,
440
+ observed=observed,
441
+ cloud_file=observed.ref if observed is not None else None,
442
+ confirmation=confirmation,
443
+ )
444
+
445
+ async def update_file(self, file: CloudFile, *, filename: str, description: str = "") -> CloudFileUpdateResult:
446
+ self._validate_observed_file(file)
447
+ if self._extension(file.filename) != self._extension(filename):
448
+ raise MatelabUsageError("Cloud-drive file rename must preserve the existing filename extension.")
449
+ try:
450
+ request = WireEditCloudDriveFileRequest.model_validate(
451
+ {"id": file.ref.cloud_file_id, "filename": filename, "comm": description}
452
+ )
453
+ except ValidationError:
454
+ raise MatelabUsageError("Cloud-drive file metadata does not satisfy the Integration Contract.") from None
455
+ _ = await self._transport.request(_EDIT_FILE, files=multipart_fields(request.model_dump(mode="json")))
456
+ observed = await self._verified_locate_file(
457
+ file.ref, location=file.location, search=filename, action="cloud-drive file update"
458
+ )
459
+ if observed is None:
460
+ confirmation: Literal["observed_matching", "observed_mismatch", "not_observed"] = "not_observed"
461
+ elif (
462
+ observed.filename == filename
463
+ and observed.description == description
464
+ and observed.sha256 == file.sha256
465
+ and observed.size == file.size
466
+ ):
467
+ confirmation = "observed_matching"
468
+ else:
469
+ confirmation = "observed_mismatch"
470
+ return CloudFileUpdateResult(
471
+ file=file.ref,
472
+ requested_filename=filename,
473
+ requested_description=description,
474
+ observed=observed,
475
+ confirmation=confirmation,
476
+ )
477
+
478
+ async def move_files(
479
+ self, files: Sequence[CloudFileRef], *, target: CloudDriveLocation | None = None
480
+ ) -> CloudFileMoveResult:
481
+ selected = self._unique_file_refs(files)
482
+ selected_target = target or CloudDriveRootRef()
483
+ encoded = self._wire_file_ids(selected)
484
+ try:
485
+ request = WireTransferCloudDriveFilesRequest(
486
+ files=encoded, target=self._location_id(selected_target), root_id=0
487
+ )
488
+ except ValidationError:
489
+ raise MatelabUsageError("Cloud-drive file move does not satisfy the Integration Contract.") from None
490
+ _ = await self._transport.request(_MOVE_FILES, files=multipart_fields(request.model_dump(mode="json")))
491
+ after = await self._verified_folder_files(selected_target, "cloud-drive file move")
492
+ observed_ids = {item.ref.cloud_file_id for item in after}
493
+ observed = tuple(item for item in selected if item.cloud_file_id in observed_ids)
494
+ missing = tuple(item for item in selected if item.cloud_file_id not in observed_ids)
495
+ return CloudFileMoveResult(
496
+ files=selected, target=selected_target, observed_in_target=observed, not_observed_in_target=missing
497
+ )
498
+
499
+ async def permanently_delete_files(self, files: Sequence[CloudFile]) -> CloudFilePermanentDeleteResult:
500
+ selected_files = tuple(files)
501
+ for item in selected_files:
502
+ self._validate_observed_file(item)
503
+ refs = self._unique_file_refs(tuple(item.ref for item in selected_files))
504
+ if len(selected_files) != len(refs):
505
+ raise MatelabUsageError("Cloud-drive deletion requires distinct observed files.")
506
+ try:
507
+ request = WireDeleteCloudDriveFilesRequest(ids=self._wire_file_ids(refs))
508
+ except ValidationError:
509
+ raise MatelabUsageError("Cloud-drive file deletion does not satisfy the Integration Contract.") from None
510
+ _ = await self._transport.request(_DELETE_FILES, files=multipart_fields(request.model_dump(mode="json")))
511
+ still_visible: list[CloudFileRef] = []
512
+ for item in selected_files:
513
+ observed = await self._verified_locate_file(
514
+ item.ref, location=item.location, search=item.filename, action="cloud-drive permanent file deletion"
515
+ )
516
+ if observed is not None:
517
+ still_visible.append(item.ref)
518
+ visible_ids = {item.cloud_file_id for item in still_visible}
519
+ return CloudFilePermanentDeleteResult(
520
+ files=refs,
521
+ confirmed_absent=tuple(item for item in refs if item.cloud_file_id not in visible_ids),
522
+ still_visible=tuple(still_visible),
523
+ )
524
+
525
+ async def download(
526
+ self, file: CloudFileRef, *, thumbnail: bool = False, preview: bool = True, byte_range: ByteRange | None = None
527
+ ) -> DownloadStream:
528
+ if type(cast(object, thumbnail)) is not bool or type(cast(object, preview)) is not bool:
529
+ raise MatelabUsageError("Cloud-drive thumbnail and preview selections must be booleans.")
530
+ try:
531
+ request = WireDownloadCloudDriveFileQuery.model_validate(
532
+ {"id": self._file_id(file), "thumb": int(thumbnail), "c": int(preview)}
533
+ )
534
+ except ValidationError:
535
+ raise MatelabUsageError("Cloud-drive file identity does not satisfy the Integration Contract.") from None
536
+ return await self._transport.stream(
537
+ _DOWNLOAD_FILE,
538
+ payload=request.model_dump(mode="json"),
539
+ range_header=byte_range.to_header() if byte_range is not None else None,
540
+ )
541
+
542
+ async def _verified_list(self, action: str) -> CloudDriveListing:
543
+ try:
544
+ return await self.list()
545
+ except MatelabError as exc:
546
+ raise MatelabVerificationError(
547
+ f"Matelab acknowledged the {action}, but cloud-drive readback failed; the mutation may have persisted."
548
+ ) from exc
549
+
550
+ async def _verified_folder_files(self, location: CloudDriveLocation, action: str) -> tuple[CloudFile, ...]:
551
+ try:
552
+ return await self._folder_files(location)
553
+ except MatelabError as exc:
554
+ raise MatelabVerificationError(
555
+ f"Matelab acknowledged the {action}, but target-folder readback failed; the mutation may have persisted."
556
+ ) from exc
557
+
558
+ async def _verified_locate_file(
559
+ self, ref: CloudFileRef, *, location: CloudDriveLocation | None, search: str, action: str
560
+ ) -> CloudFile | None:
561
+ try:
562
+ candidates = (
563
+ await self._folder_files(location) if location is not None else await self._search_files(search)
564
+ )
565
+ return next((item for item in candidates if item.ref == ref), None)
566
+ except MatelabError as exc:
567
+ raise MatelabVerificationError(
568
+ f"Matelab acknowledged the {action}, but file readback failed; the mutation may have persisted."
569
+ ) from exc
570
+
571
+ async def _folder_files(self, location: CloudDriveLocation) -> tuple[CloudFile, ...]:
572
+ first = await self.list(location, page=1, page_size=256)
573
+ files = list(first.file_page.files)
574
+ for page_number in range(2, math.ceil(first.file_page.total_count / first.file_page.effective_page_size) + 1):
575
+ page = await self.list(location, page=page_number, page_size=first.file_page.effective_page_size)
576
+ files.extend(page.file_page.files)
577
+ return tuple(files)
578
+
579
+ async def _search_files(self, search: str) -> tuple[CloudFile, ...]:
580
+ first = await self.list(page=1, page_size=256, search=search)
581
+ files = list(first.file_page.files)
582
+ for page_number in range(2, math.ceil(first.file_page.total_count / first.file_page.effective_page_size) + 1):
583
+ page = await self.list(page=page_number, page_size=first.file_page.effective_page_size, search=search)
584
+ files.extend(page.file_page.files)
585
+ return tuple(files)
586
+
587
+ @staticmethod
588
+ def _listing(
589
+ response: WireCloudDriveFileListResponse,
590
+ *,
591
+ selected: CloudDriveLocation,
592
+ page: int,
593
+ page_size: int,
594
+ order: CloudDriveOrder,
595
+ descending: bool,
596
+ search: str,
597
+ ) -> CloudDriveListing:
598
+ normalized_page = max(1, page)
599
+ effective_page_size = max(10, page_size)
600
+ file_location = None if search else selected
601
+ return CloudDriveListing(
602
+ file_page=CloudFilePage(
603
+ files=tuple(CloudDrive._file(item, file_location) for item in response.files),
604
+ folder=file_location,
605
+ search=search,
606
+ scope="search_across_personal_root" if search else "selected_folder",
607
+ page=normalized_page,
608
+ requested_page_size=page_size,
609
+ effective_page_size=effective_page_size,
610
+ total_count=response.count,
611
+ has_more=normalized_page * effective_page_size < response.count,
612
+ order=order,
613
+ descending=descending,
614
+ ),
615
+ folder_tree=CloudFolderTree(folders=tuple(CloudDrive._folder(item) for item in response.types)),
616
+ quota=CloudDriveQuota(quota_gib=response.quota or None, used_bytes=response.used),
617
+ root=CloudDrive._root(response.root_type),
618
+ )
619
+
620
+ @staticmethod
621
+ def _file(item: WireCloudDriveFile, location: CloudDriveLocation | None) -> CloudFile:
622
+ return CloudFile(
623
+ ref=CloudFileRef(cloud_file_id=item.id),
624
+ location=location,
625
+ filename=item.filename,
626
+ sha256=item.hash.root,
627
+ size=int(item.size),
628
+ owner=UserRef(userid=item.userid),
629
+ owner_name=item.username,
630
+ modified_at=item.date_time,
631
+ description=item.comm,
632
+ display_date=item.date,
633
+ display_time=item.time,
634
+ attributes=MappingProxyType(cast(dict[str, object], dict(item.model_extra or {}))),
635
+ )
636
+
637
+ @staticmethod
638
+ def _folder(item: WireCloudDriveFolder) -> CloudFolder:
639
+ parent: CloudDriveLocation = (
640
+ CloudDriveRootRef() if item.parent == 0 else CloudFolderRef(cloud_folder_id=item.parent)
641
+ )
642
+ return CloudFolder(
643
+ ref=CloudFolderRef(cloud_folder_id=item.id),
644
+ name=item.showtext,
645
+ parent=parent,
646
+ direct_file_count=item.num,
647
+ description=item.comm,
648
+ attributes=MappingProxyType(cast(dict[str, object], dict(item.model_extra or {}))),
649
+ )
650
+
651
+ @staticmethod
652
+ def _root(item: WireCloudDriveRootPermissions) -> CloudDriveRoot:
653
+ return CloudDriveRoot(
654
+ ref=CloudDriveRootRef(),
655
+ name=item.showtext,
656
+ description=item.comm,
657
+ permissions=CloudDriveRootPermissions(
658
+ permission_mask=item.power,
659
+ can_create_folder=item.dir_create,
660
+ can_delete_folder=item.dir_delete,
661
+ can_edit_folder=item.dir_edit,
662
+ can_create_file=item.file_create,
663
+ can_delete_file=item.file_delete,
664
+ can_view_file=item.file_view,
665
+ can_edit_file=item.file_edit,
666
+ attributes=MappingProxyType(cast(dict[str, object], dict(item.model_extra or {}))),
667
+ ),
668
+ )
669
+
670
+ @staticmethod
671
+ def _folder_request(
672
+ metadata: CloudFolderMetadata, *, folder: CloudFolderRef | None, parent: CloudDriveLocation
673
+ ) -> WireSaveCloudDriveFolderRequest:
674
+ raw_metadata = cast(object, metadata)
675
+ if not isinstance(raw_metadata, CloudFolderMetadata):
676
+ raise MatelabUsageError("Cloud-drive folder metadata is invalid.")
677
+ try:
678
+ return WireSaveCloudDriveFolderRequest.model_validate(
679
+ {
680
+ "id": folder.cloud_folder_id if folder is not None else 0,
681
+ "parent": CloudDrive._location_id(parent),
682
+ "root_id": 0,
683
+ "showtext": raw_metadata.name,
684
+ "comm": raw_metadata.description,
685
+ }
686
+ )
687
+ except ValidationError:
688
+ raise MatelabUsageError("Cloud-drive folder metadata does not satisfy the Integration Contract.") from None
689
+
690
+ @staticmethod
691
+ def _folder_save_confirmation(
692
+ observed: CloudFolder | None, metadata: CloudFolderMetadata, parent: CloudDriveLocation
693
+ ) -> Literal["observed_matching", "observed_mismatch", "not_observed"]:
694
+ if observed is None:
695
+ return "not_observed"
696
+ if (
697
+ observed.name == metadata.name
698
+ and (observed.description or "") == metadata.description
699
+ and observed.parent == parent
700
+ ):
701
+ return "observed_matching"
702
+ return "observed_mismatch"
703
+
704
+ @staticmethod
705
+ def _find_folder(tree: CloudFolderTree, ref: CloudFolderRef) -> CloudFolder | None:
706
+ return next((item for item in tree.folders if item.ref == ref), None)
707
+
708
+ @staticmethod
709
+ def _validate_staged_file(staged: StagedFile) -> None:
710
+ raw_staged = cast(object, staged)
711
+ if (
712
+ not isinstance(raw_staged, StagedFile)
713
+ or raw_staged.temporary_file_id < 1
714
+ or not raw_staged.binding.uid
715
+ or len(raw_staged.binding.uid) > 45
716
+ or not raw_staged.session.upload_session_id
717
+ or len(raw_staged.session.upload_session_id) > 45
718
+ or raw_staged.session.next_offset != raw_staged.size
719
+ or not raw_staged.filename
720
+ or raw_staged.size < 0
721
+ or _SHA256_PATTERN.fullmatch(raw_staged.sha256) is None
722
+ or raw_staged.completion != "provider_acknowledged_complete"
723
+ or raw_staged.binding_state != "staged_not_bound"
724
+ ):
725
+ raise MatelabUsageError("Completed staged-file metadata is invalid.")
726
+
727
+ @staticmethod
728
+ def _validate_observed_file(file: CloudFile) -> None:
729
+ raw_file = cast(object, file)
730
+ if (
731
+ not isinstance(raw_file, CloudFile)
732
+ or CloudDrive._file_id(raw_file.ref) < 1
733
+ or not raw_file.filename
734
+ or raw_file.size < 0
735
+ or _SHA256_PATTERN.fullmatch(raw_file.sha256) is None
736
+ ):
737
+ raise MatelabUsageError("Observed cloud-drive file metadata is invalid.")
738
+ if raw_file.location is not None:
739
+ _ = CloudDrive._location_id(raw_file.location)
740
+
741
+ @staticmethod
742
+ def _wire_file_ids(refs: tuple[CloudFileRef, ...]) -> str:
743
+ ids = [item.cloud_file_id for item in refs]
744
+ try:
745
+ wire = WireCloudDriveFileIdList.model_validate(ids)
746
+ payload = cast(list[object], wire.model_dump(mode="json"))
747
+ return json.dumps(payload, separators=(",", ":"))
748
+ except (ValidationError, TypeError, ValueError):
749
+ raise MatelabUsageError("Cloud-drive file identities do not satisfy the Integration Contract.") from None
750
+
751
+ @staticmethod
752
+ def _unique_file_refs(files: Sequence[CloudFileRef]) -> tuple[CloudFileRef, ...]:
753
+ selected = tuple(files)
754
+ ids = tuple(CloudDrive._file_id(item) for item in selected)
755
+ if not selected or len(ids) != len(set(ids)):
756
+ raise MatelabUsageError("Cloud-drive file selection must contain distinct positive identities.")
757
+ return selected
758
+
759
+ @staticmethod
760
+ def _location_id(location: CloudDriveLocation) -> int:
761
+ raw_location = cast(object, location)
762
+ if isinstance(raw_location, CloudDriveRootRef):
763
+ return 0
764
+ if isinstance(raw_location, CloudFolderRef):
765
+ return CloudDrive._folder_id(raw_location)
766
+ raise MatelabUsageError("Cloud-drive location identity is invalid.")
767
+
768
+ @staticmethod
769
+ def _folder_id(folder: CloudFolderRef) -> int:
770
+ raw_folder = cast(object, folder)
771
+ if (
772
+ isinstance(raw_folder, CloudFolderRef)
773
+ and type(raw_folder.cloud_folder_id) is int
774
+ and raw_folder.cloud_folder_id >= 1
775
+ ):
776
+ return raw_folder.cloud_folder_id
777
+ raise MatelabUsageError("Cloud-drive folder identity is invalid.")
778
+
779
+ @staticmethod
780
+ def _file_id(file: CloudFileRef) -> int:
781
+ raw_file = cast(object, file)
782
+ if isinstance(raw_file, CloudFileRef) and type(raw_file.cloud_file_id) is int and raw_file.cloud_file_id >= 1:
783
+ return raw_file.cloud_file_id
784
+ raise MatelabUsageError("Cloud-drive file identity is invalid.")
785
+
786
+ @staticmethod
787
+ def _extension(filename: str) -> str:
788
+ tail = filename.rpartition(".")
789
+ return f".{tail[2]}" if tail[0] and tail[2] else ""