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/__init__.py +372 -0
- matelab/_generated/__init__.py +1 -0
- matelab/_generated/models.py +3553 -0
- matelab/_transport.py +481 -0
- matelab/client.py +191 -0
- matelab/cloud_drive.py +789 -0
- matelab/errors.py +38 -0
- matelab/groups.py +78 -0
- matelab/literature.py +1243 -0
- matelab/models.py +27 -0
- matelab/notebooks.py +467 -0
- matelab/py.typed +1 -0
- matelab/records.py +2511 -0
- matelab/streaming.py +82 -0
- matelab/templates.py +737 -0
- matelab/uploads.py +247 -0
- matelab/users.py +77 -0
- matelab_python_sdk-0.1.0a1.dist-info/METADATA +538 -0
- matelab_python_sdk-0.1.0a1.dist-info/RECORD +22 -0
- matelab_python_sdk-0.1.0a1.dist-info/WHEEL +4 -0
- matelab_python_sdk-0.1.0a1.dist-info/licenses/LICENSE +202 -0
- matelab_python_sdk-0.1.0a1.dist-info/licenses/NOTICE +2 -0
matelab/templates.py
ADDED
|
@@ -0,0 +1,737 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from collections.abc import Iterable, Mapping, Sequence
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from enum import StrEnum
|
|
5
|
+
from types import MappingProxyType
|
|
6
|
+
from typing import Literal, cast
|
|
7
|
+
|
|
8
|
+
from pydantic import ValidationError
|
|
9
|
+
|
|
10
|
+
from matelab._generated.models import AddGroupTemplateRequest as WireAddGroupTemplateRequest
|
|
11
|
+
from matelab._generated.models import BasicSuccessResponse as WireBasicSuccessResponse
|
|
12
|
+
from matelab._generated.models import CopyTemplateRequest as WireCopyTemplateRequest
|
|
13
|
+
from matelab._generated.models import DeleteGroupTemplateRequest as WireDeleteGroupTemplateRequest
|
|
14
|
+
from matelab._generated.models import ListTemplateStoreParametersQuery as WireListTemplateStoreQuery
|
|
15
|
+
from matelab._generated.models import OwnedTemplateListItem as WireOwnedTemplateListItem
|
|
16
|
+
from matelab._generated.models import ReadTemplateContentParametersQuery as WireReadTemplateContentQuery
|
|
17
|
+
from matelab._generated.models import RemoveUserTemplateRelationRequest as WireRemoveUserTemplateRelationRequest
|
|
18
|
+
from matelab._generated.models import SaveTemplateContentRequest as WireSaveTemplateContentRequest
|
|
19
|
+
from matelab._generated.models import SaveTemplateMetadataRequest as WireSaveTemplateMetadataRequest
|
|
20
|
+
from matelab._generated.models import SaveTemplateMetadataResponse as WireSaveTemplateMetadataResponse
|
|
21
|
+
from matelab._generated.models import ShareMultipleTemplatesRequest as WireShareMultipleTemplatesRequest
|
|
22
|
+
from matelab._generated.models import TemplateContentResponse as WireTemplateContentResponse
|
|
23
|
+
from matelab._generated.models import TemplateIdRequest as WireTemplateIdRequest
|
|
24
|
+
from matelab._generated.models import TemplateListResponse as WireTemplateListResponse
|
|
25
|
+
from matelab._generated.models import TemplateModule as WireTemplateModule
|
|
26
|
+
from matelab._generated.models import TemplateStoreListResponse as WireTemplateStoreListResponse
|
|
27
|
+
from matelab._generated.models import UpdateTemplateIntroRequest as WireUpdateTemplateIntroRequest
|
|
28
|
+
from matelab._generated.models import UpdateTemplateIntroResponse as WireUpdateTemplateIntroResponse
|
|
29
|
+
from matelab._transport import Encoding, Operation, SessionTransport, multipart_fields
|
|
30
|
+
from matelab.errors import MatelabError, MatelabProtocolError, MatelabUsageError, MatelabVerificationError
|
|
31
|
+
from matelab.groups import GroupRef
|
|
32
|
+
from matelab.uploads import UploadBindingRef
|
|
33
|
+
from matelab.users import UserRef
|
|
34
|
+
|
|
35
|
+
_LIST_TEMPLATES = Operation(
|
|
36
|
+
method="GET",
|
|
37
|
+
path="/eln_template/list",
|
|
38
|
+
encoding=Encoding.NONE,
|
|
39
|
+
response_model=WireTemplateListResponse,
|
|
40
|
+
success_codes=frozenset({0}),
|
|
41
|
+
retry_on_access_expired=True,
|
|
42
|
+
)
|
|
43
|
+
_LIST_TEMPLATE_MARKET = Operation(
|
|
44
|
+
method="GET",
|
|
45
|
+
path="/eln_template/store_list",
|
|
46
|
+
encoding=Encoding.QUERY,
|
|
47
|
+
response_model=WireTemplateStoreListResponse,
|
|
48
|
+
success_codes=frozenset({0}),
|
|
49
|
+
retry_on_access_expired=True,
|
|
50
|
+
)
|
|
51
|
+
_READ_TEMPLATE = Operation(
|
|
52
|
+
method="GET",
|
|
53
|
+
path="/eln_template/content",
|
|
54
|
+
encoding=Encoding.QUERY,
|
|
55
|
+
response_model=WireTemplateContentResponse,
|
|
56
|
+
success_codes=frozenset({0}),
|
|
57
|
+
retry_on_access_expired=True,
|
|
58
|
+
)
|
|
59
|
+
_SHARE_TEMPLATES = Operation(
|
|
60
|
+
method="POST",
|
|
61
|
+
path="/eln_template/share_multiple",
|
|
62
|
+
encoding=Encoding.MULTIPART,
|
|
63
|
+
response_model=WireBasicSuccessResponse,
|
|
64
|
+
success_codes=frozenset({0}),
|
|
65
|
+
)
|
|
66
|
+
_REMOVE_USER_LIBRARY_ENTRY = Operation(
|
|
67
|
+
method="POST",
|
|
68
|
+
path="/eln_template/refuse",
|
|
69
|
+
encoding=Encoding.MULTIPART,
|
|
70
|
+
response_model=WireBasicSuccessResponse,
|
|
71
|
+
success_codes=frozenset({0}),
|
|
72
|
+
)
|
|
73
|
+
_ADD_GROUP_TEMPLATE = Operation(
|
|
74
|
+
method="POST",
|
|
75
|
+
path="/eln_template/group_add",
|
|
76
|
+
encoding=Encoding.MULTIPART,
|
|
77
|
+
response_model=WireBasicSuccessResponse,
|
|
78
|
+
success_codes=frozenset({0}),
|
|
79
|
+
)
|
|
80
|
+
_REMOVE_GROUP_TEMPLATE = Operation(
|
|
81
|
+
method="POST",
|
|
82
|
+
path="/eln_template/group_del",
|
|
83
|
+
encoding=Encoding.MULTIPART,
|
|
84
|
+
response_model=WireBasicSuccessResponse,
|
|
85
|
+
success_codes=frozenset({0}),
|
|
86
|
+
)
|
|
87
|
+
_ACQUIRE_MARKET_TEMPLATE = Operation(
|
|
88
|
+
method="POST",
|
|
89
|
+
path="/eln_template/store_buy",
|
|
90
|
+
encoding=Encoding.JSON,
|
|
91
|
+
response_model=WireBasicSuccessResponse,
|
|
92
|
+
success_codes=frozenset({0}),
|
|
93
|
+
)
|
|
94
|
+
_SAVE_TEMPLATE_METADATA = Operation(
|
|
95
|
+
method="POST",
|
|
96
|
+
path="/eln_template/edit",
|
|
97
|
+
encoding=Encoding.JSON,
|
|
98
|
+
response_model=WireSaveTemplateMetadataResponse,
|
|
99
|
+
success_codes=frozenset({0}),
|
|
100
|
+
)
|
|
101
|
+
_SAVE_TEMPLATE_CONTENT = Operation(
|
|
102
|
+
method="POST",
|
|
103
|
+
path="/eln_items/templateSave",
|
|
104
|
+
encoding=Encoding.JSON,
|
|
105
|
+
response_model=WireBasicSuccessResponse,
|
|
106
|
+
success_codes=frozenset({0}),
|
|
107
|
+
)
|
|
108
|
+
_COPY_TEMPLATE = Operation(
|
|
109
|
+
method="POST",
|
|
110
|
+
path="/eln_template/saveas",
|
|
111
|
+
encoding=Encoding.JSON,
|
|
112
|
+
response_model=WireBasicSuccessResponse,
|
|
113
|
+
success_codes=frozenset({0}),
|
|
114
|
+
)
|
|
115
|
+
_UPDATE_TEMPLATE_INTRO = Operation(
|
|
116
|
+
method="POST",
|
|
117
|
+
path="/eln_template/edit_intro",
|
|
118
|
+
encoding=Encoding.JSON,
|
|
119
|
+
response_model=WireUpdateTemplateIntroResponse,
|
|
120
|
+
success_codes=frozenset({0}),
|
|
121
|
+
)
|
|
122
|
+
_SUBMIT_TEMPLATE = Operation(
|
|
123
|
+
method="POST",
|
|
124
|
+
path="/eln_template/pub",
|
|
125
|
+
encoding=Encoding.JSON,
|
|
126
|
+
response_model=WireBasicSuccessResponse,
|
|
127
|
+
success_codes=frozenset({0}),
|
|
128
|
+
)
|
|
129
|
+
_MODULE_RESERVED_FIELDS = frozenset({"name", "type", "data", "language", "style"})
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
class TemplateSource(StrEnum):
|
|
133
|
+
OWNED = "owned"
|
|
134
|
+
SHARED = "shared"
|
|
135
|
+
MARKET_ACQUIRED = "market_acquired"
|
|
136
|
+
GROUP = "group"
|
|
137
|
+
DEFAULT = "default"
|
|
138
|
+
PENDING_MARKET_REVIEW = "pending_market_review"
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
class TemplateLibraryRelationKind(StrEnum):
|
|
142
|
+
DIRECT_SHARE = "direct_share"
|
|
143
|
+
MARKET_ACQUISITION = "market_acquisition"
|
|
144
|
+
GROUP = "group"
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
@dataclass(frozen=True, slots=True)
|
|
148
|
+
class TemplateRef:
|
|
149
|
+
template_id: int
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
@dataclass(frozen=True, slots=True)
|
|
153
|
+
class TemplateLibraryEntryRef:
|
|
154
|
+
"""A relation identity kept distinct from its template database identity."""
|
|
155
|
+
|
|
156
|
+
relation_id: int
|
|
157
|
+
template: TemplateRef
|
|
158
|
+
kind: TemplateLibraryRelationKind
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
@dataclass(frozen=True, slots=True)
|
|
162
|
+
class TemplateSummary:
|
|
163
|
+
ref: TemplateRef
|
|
164
|
+
source: TemplateSource
|
|
165
|
+
title: str | None
|
|
166
|
+
summary: str | None
|
|
167
|
+
library_entry: TemplateLibraryEntryRef | None = None
|
|
168
|
+
owner: UserRef | None = None
|
|
169
|
+
owner_name: str | None = None
|
|
170
|
+
group: GroupRef | None = None
|
|
171
|
+
group_name: str | None = None
|
|
172
|
+
group_admin: bool | None = None
|
|
173
|
+
intro: str | None = None
|
|
174
|
+
modified_at: str | None = None
|
|
175
|
+
keywords: tuple[object, ...] | None = None
|
|
176
|
+
version: int | None = None
|
|
177
|
+
market_version: int | None = None
|
|
178
|
+
publication_reason: str | None = None
|
|
179
|
+
render_metadata: str | None = None
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
@dataclass(frozen=True, slots=True)
|
|
183
|
+
class TemplateCollection:
|
|
184
|
+
owned: tuple[TemplateSummary, ...]
|
|
185
|
+
shared: tuple[TemplateSummary, ...]
|
|
186
|
+
market_acquired: tuple[TemplateSummary, ...]
|
|
187
|
+
group: tuple[TemplateSummary, ...]
|
|
188
|
+
default: tuple[TemplateSummary, ...]
|
|
189
|
+
pending_market_review: tuple[TemplateSummary, ...]
|
|
190
|
+
ordering: Literal["provider_unspecified"] = "provider_unspecified"
|
|
191
|
+
|
|
192
|
+
@property
|
|
193
|
+
def all(self) -> tuple[TemplateSummary, ...]:
|
|
194
|
+
return (
|
|
195
|
+
*self.owned,
|
|
196
|
+
*self.shared,
|
|
197
|
+
*self.market_acquired,
|
|
198
|
+
*self.group,
|
|
199
|
+
*self.default,
|
|
200
|
+
*self.pending_market_review,
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
@dataclass(frozen=True, slots=True)
|
|
205
|
+
class TemplateMarketEntry:
|
|
206
|
+
ref: TemplateRef
|
|
207
|
+
title: str
|
|
208
|
+
summary: str | None
|
|
209
|
+
intro: str | None
|
|
210
|
+
modified_at: str | None
|
|
211
|
+
published_at: str | None
|
|
212
|
+
publisher_name: str | None
|
|
213
|
+
acquisition_count: int | None
|
|
214
|
+
acquired: bool
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
@dataclass(frozen=True, slots=True)
|
|
218
|
+
class TemplateMarketPage:
|
|
219
|
+
items: tuple[TemplateMarketEntry, ...]
|
|
220
|
+
page: int
|
|
221
|
+
requested_page_size: int
|
|
222
|
+
effective_page_size: int
|
|
223
|
+
total_count: int
|
|
224
|
+
has_more: bool
|
|
225
|
+
has_more_basis: Literal["derived_from_total"] = "derived_from_total"
|
|
226
|
+
ordering: Literal["provider_unspecified"] = "provider_unspecified"
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
@dataclass(frozen=True, slots=True)
|
|
230
|
+
class TemplateModule:
|
|
231
|
+
name: str
|
|
232
|
+
module_type: str
|
|
233
|
+
data: object
|
|
234
|
+
data_present: bool
|
|
235
|
+
language: str | None
|
|
236
|
+
style: object
|
|
237
|
+
attributes: Mapping[str, object]
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
@dataclass(frozen=True, slots=True)
|
|
241
|
+
class TemplateContent:
|
|
242
|
+
ref: TemplateRef
|
|
243
|
+
modules: tuple[TemplateModule, ...]
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
@dataclass(frozen=True, slots=True)
|
|
247
|
+
class TemplateMetadata:
|
|
248
|
+
title: str
|
|
249
|
+
summary: str | None = None
|
|
250
|
+
keywords: tuple[str, ...] = ()
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
@dataclass(frozen=True, slots=True)
|
|
254
|
+
class TemplateShareResult:
|
|
255
|
+
templates: tuple[TemplateRef, ...]
|
|
256
|
+
recipients: tuple[UserRef, ...]
|
|
257
|
+
affected_relations: None = None
|
|
258
|
+
result_scope: Literal["provider_acknowledged_without_relation_results"] = (
|
|
259
|
+
"provider_acknowledged_without_relation_results"
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
@dataclass(frozen=True, slots=True)
|
|
264
|
+
class TemplateLibraryRemovalResult:
|
|
265
|
+
entry: TemplateLibraryEntryRef
|
|
266
|
+
absence_confirmed: bool
|
|
267
|
+
provider_semantics: Literal["idempotent_acknowledgement"] = "idempotent_acknowledgement"
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
@dataclass(frozen=True, slots=True)
|
|
271
|
+
class TemplateGroupAddResult:
|
|
272
|
+
template: TemplateRef
|
|
273
|
+
group: GroupRef
|
|
274
|
+
observed_entry: TemplateLibraryEntryRef | None
|
|
275
|
+
observation: Literal["relation_observed", "relation_not_observed"]
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
@dataclass(frozen=True, slots=True)
|
|
279
|
+
class TemplateGroupRemoveResult:
|
|
280
|
+
entry: TemplateLibraryEntryRef
|
|
281
|
+
absence_confirmed: bool
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
@dataclass(frozen=True, slots=True)
|
|
285
|
+
class TemplateMarketAcquireResult:
|
|
286
|
+
template: TemplateRef
|
|
287
|
+
observed_entry: TemplateLibraryEntryRef | None
|
|
288
|
+
observation: Literal["relation_observed", "relation_not_observed"]
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
@dataclass(frozen=True, slots=True)
|
|
292
|
+
class TemplateMetadataResult:
|
|
293
|
+
template: TemplateRef
|
|
294
|
+
requested: TemplateMetadata
|
|
295
|
+
action: Literal["created", "updated"]
|
|
296
|
+
observed: TemplateSummary | None
|
|
297
|
+
confirmation: Literal["observed_matching", "observed_mismatch", "not_observed"]
|
|
298
|
+
publication_revision_advanced: Literal[False] = False
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
@dataclass(frozen=True, slots=True)
|
|
302
|
+
class TemplateContentSaveResult:
|
|
303
|
+
template: TemplateRef
|
|
304
|
+
requested: tuple[TemplateModule, ...]
|
|
305
|
+
observed: TemplateContent
|
|
306
|
+
confirmation: Literal["observed_matching", "observed_mismatch"]
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
@dataclass(frozen=True, slots=True)
|
|
310
|
+
class TemplateCopyResult:
|
|
311
|
+
source: TemplateRef
|
|
312
|
+
requested_title: str
|
|
313
|
+
copied_template: None = None
|
|
314
|
+
identity_status: Literal["provider_did_not_return_identity"] = "provider_did_not_return_identity"
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
@dataclass(frozen=True, slots=True)
|
|
318
|
+
class TemplateIntroUpdateResult:
|
|
319
|
+
template: TemplateRef
|
|
320
|
+
intro: str
|
|
321
|
+
upload_binding: UploadBindingRef
|
|
322
|
+
publication_revision_advanced: Literal[False] = False
|
|
323
|
+
binding_authorization: Literal["provider_does_not_verify_uploader"] = "provider_does_not_verify_uploader"
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
@dataclass(frozen=True, slots=True)
|
|
327
|
+
class TemplateMarketSubmissionResult:
|
|
328
|
+
template: TemplateRef
|
|
329
|
+
observed_pending_review: bool
|
|
330
|
+
observation: Literal["pending_entry_observed", "pending_entry_not_observed"]
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
class Templates:
|
|
334
|
+
"""Template discovery, market search, and canonical content reads."""
|
|
335
|
+
|
|
336
|
+
def __init__(self, transport: SessionTransport) -> None:
|
|
337
|
+
self._transport: SessionTransport = transport
|
|
338
|
+
|
|
339
|
+
async def list(self) -> TemplateCollection:
|
|
340
|
+
response = await self._transport.request(_LIST_TEMPLATES)
|
|
341
|
+
return TemplateCollection(
|
|
342
|
+
owned=tuple(
|
|
343
|
+
TemplateSummary(
|
|
344
|
+
ref=TemplateRef(template_id=item.template_id),
|
|
345
|
+
source=TemplateSource.OWNED,
|
|
346
|
+
title=item.template_name,
|
|
347
|
+
summary=item.comm,
|
|
348
|
+
owner=UserRef(userid=item.userid_from) if item.userid_from else None,
|
|
349
|
+
owner_name=item.username,
|
|
350
|
+
intro=item.intro,
|
|
351
|
+
modified_at=item.datetime,
|
|
352
|
+
keywords=tuple(cast(list[object], item.keyword)),
|
|
353
|
+
version=item.version,
|
|
354
|
+
market_version=item.store_version,
|
|
355
|
+
publication_reason=self._publication_reason(item),
|
|
356
|
+
render_metadata=item.template_show,
|
|
357
|
+
)
|
|
358
|
+
for item in response.my
|
|
359
|
+
),
|
|
360
|
+
shared=tuple(
|
|
361
|
+
TemplateSummary(
|
|
362
|
+
ref=TemplateRef(template_id=item.template_id),
|
|
363
|
+
source=TemplateSource.SHARED,
|
|
364
|
+
title=item.template_name,
|
|
365
|
+
summary=item.comm,
|
|
366
|
+
library_entry=TemplateLibraryEntryRef(
|
|
367
|
+
relation_id=item.id,
|
|
368
|
+
template=TemplateRef(template_id=item.template_id),
|
|
369
|
+
kind=TemplateLibraryRelationKind.DIRECT_SHARE,
|
|
370
|
+
),
|
|
371
|
+
owner=UserRef(userid=item.userid_from) if item.userid_from is not None else None,
|
|
372
|
+
owner_name=item.username,
|
|
373
|
+
)
|
|
374
|
+
for item in response.share
|
|
375
|
+
),
|
|
376
|
+
market_acquired=tuple(
|
|
377
|
+
TemplateSummary(
|
|
378
|
+
ref=TemplateRef(template_id=item.template_id),
|
|
379
|
+
source=TemplateSource.MARKET_ACQUIRED,
|
|
380
|
+
title=item.template_name,
|
|
381
|
+
summary=item.comm,
|
|
382
|
+
library_entry=TemplateLibraryEntryRef(
|
|
383
|
+
relation_id=item.id,
|
|
384
|
+
template=TemplateRef(template_id=item.template_id),
|
|
385
|
+
kind=TemplateLibraryRelationKind.MARKET_ACQUISITION,
|
|
386
|
+
),
|
|
387
|
+
owner_name=item.username,
|
|
388
|
+
)
|
|
389
|
+
for item in response.stores
|
|
390
|
+
),
|
|
391
|
+
group=tuple(
|
|
392
|
+
TemplateSummary(
|
|
393
|
+
ref=TemplateRef(template_id=item.template_id),
|
|
394
|
+
source=TemplateSource.GROUP,
|
|
395
|
+
title=item.template_name,
|
|
396
|
+
summary=item.comm,
|
|
397
|
+
library_entry=TemplateLibraryEntryRef(
|
|
398
|
+
relation_id=item.id,
|
|
399
|
+
template=TemplateRef(template_id=item.template_id),
|
|
400
|
+
kind=TemplateLibraryRelationKind.GROUP,
|
|
401
|
+
),
|
|
402
|
+
owner=UserRef(userid=item.userid) if item.userid is not None else None,
|
|
403
|
+
owner_name=item.username,
|
|
404
|
+
group=GroupRef(group_id=item.group_id),
|
|
405
|
+
group_name=item.group_name,
|
|
406
|
+
group_admin=item.admin,
|
|
407
|
+
modified_at=item.datetime,
|
|
408
|
+
)
|
|
409
|
+
for item in response.group
|
|
410
|
+
),
|
|
411
|
+
default=tuple(
|
|
412
|
+
TemplateSummary(
|
|
413
|
+
ref=TemplateRef(template_id=item.template_id),
|
|
414
|
+
source=TemplateSource.DEFAULT,
|
|
415
|
+
title=item.template_name,
|
|
416
|
+
summary=item.comm,
|
|
417
|
+
)
|
|
418
|
+
for item in response.default
|
|
419
|
+
),
|
|
420
|
+
pending_market_review=tuple(
|
|
421
|
+
TemplateSummary(
|
|
422
|
+
ref=TemplateRef(template_id=item.template_id),
|
|
423
|
+
source=TemplateSource.PENDING_MARKET_REVIEW,
|
|
424
|
+
title=item.template_name,
|
|
425
|
+
summary=item.comm,
|
|
426
|
+
owner_name=item.username,
|
|
427
|
+
)
|
|
428
|
+
for item in response.pending
|
|
429
|
+
),
|
|
430
|
+
)
|
|
431
|
+
|
|
432
|
+
async def search_market(self, search: str = "", *, page: int = 1, page_size: int = 10) -> TemplateMarketPage:
|
|
433
|
+
try:
|
|
434
|
+
request = WireListTemplateStoreQuery(search=search, page=page, page_size=page_size)
|
|
435
|
+
except ValidationError:
|
|
436
|
+
raise MatelabUsageError("Template-market filters do not satisfy the Integration Contract.") from None
|
|
437
|
+
response = await self._transport.request(_LIST_TEMPLATE_MARKET, payload=request.model_dump(mode="json"))
|
|
438
|
+
effective_page_size = max(10, request.page_size)
|
|
439
|
+
return TemplateMarketPage(
|
|
440
|
+
items=tuple(
|
|
441
|
+
TemplateMarketEntry(
|
|
442
|
+
ref=TemplateRef(template_id=item.id),
|
|
443
|
+
title=item.showtext,
|
|
444
|
+
summary=item.comm,
|
|
445
|
+
intro=item.intro,
|
|
446
|
+
modified_at=item.datetime_modify,
|
|
447
|
+
published_at=item.dtime_pub,
|
|
448
|
+
publisher_name=item.username,
|
|
449
|
+
acquisition_count=item.buy_count,
|
|
450
|
+
acquired=bool(item.my),
|
|
451
|
+
)
|
|
452
|
+
for item in response.items
|
|
453
|
+
),
|
|
454
|
+
page=request.page,
|
|
455
|
+
requested_page_size=request.page_size,
|
|
456
|
+
effective_page_size=effective_page_size,
|
|
457
|
+
total_count=response.count,
|
|
458
|
+
has_more=request.page * effective_page_size < response.count,
|
|
459
|
+
)
|
|
460
|
+
|
|
461
|
+
async def read(self, template: TemplateRef) -> TemplateContent:
|
|
462
|
+
try:
|
|
463
|
+
request = WireReadTemplateContentQuery(id=template.template_id)
|
|
464
|
+
except ValidationError:
|
|
465
|
+
raise MatelabUsageError("Template identity does not satisfy the Integration Contract.") from None
|
|
466
|
+
response = await self._transport.request(_READ_TEMPLATE, payload=request.model_dump(mode="json"))
|
|
467
|
+
return TemplateContent(ref=template, modules=tuple(self._module(module) for module in response.content))
|
|
468
|
+
|
|
469
|
+
async def share(self, templates: Sequence[TemplateRef], recipients: Sequence[UserRef]) -> TemplateShareResult:
|
|
470
|
+
selected = tuple(templates)
|
|
471
|
+
targets = tuple(recipients)
|
|
472
|
+
template_ids = self._bounded_unique_ids(
|
|
473
|
+
(template.template_id for template in selected), subject="Template share selection"
|
|
474
|
+
)
|
|
475
|
+
userids = self._bounded_unique_ids(
|
|
476
|
+
(recipient.userid for recipient in targets), subject="Template share recipients"
|
|
477
|
+
)
|
|
478
|
+
try:
|
|
479
|
+
request = WireShareMultipleTemplatesRequest(
|
|
480
|
+
templates=json.dumps(template_ids, separators=(",", ":")),
|
|
481
|
+
users=json.dumps(userids, separators=(",", ":")),
|
|
482
|
+
)
|
|
483
|
+
except ValidationError:
|
|
484
|
+
raise MatelabUsageError("Template share request does not satisfy the Integration Contract.") from None
|
|
485
|
+
_ = await self._transport.request(_SHARE_TEMPLATES, files=multipart_fields(request.model_dump(mode="json")))
|
|
486
|
+
return TemplateShareResult(templates=selected, recipients=targets)
|
|
487
|
+
|
|
488
|
+
async def unshare(self, entry: TemplateLibraryEntryRef) -> TemplateLibraryRemovalResult:
|
|
489
|
+
if entry.kind is not TemplateLibraryRelationKind.DIRECT_SHARE:
|
|
490
|
+
raise MatelabUsageError("Template unshare requires a direct-share library relation.")
|
|
491
|
+
return await self._remove_user_library_entry(entry)
|
|
492
|
+
|
|
493
|
+
async def remove_market_acquisition(self, entry: TemplateLibraryEntryRef) -> TemplateLibraryRemovalResult:
|
|
494
|
+
if entry.kind is not TemplateLibraryRelationKind.MARKET_ACQUISITION:
|
|
495
|
+
raise MatelabUsageError("Removing a market acquisition requires a market-acquisition relation.")
|
|
496
|
+
return await self._remove_user_library_entry(entry)
|
|
497
|
+
|
|
498
|
+
async def add_to_group(self, template: TemplateRef, group: GroupRef) -> TemplateGroupAddResult:
|
|
499
|
+
try:
|
|
500
|
+
request = WireAddGroupTemplateRequest(template_id=template.template_id, group_id=group.group_id)
|
|
501
|
+
except ValidationError:
|
|
502
|
+
raise MatelabUsageError("Template/group identity does not satisfy the Integration Contract.") from None
|
|
503
|
+
_ = await self._transport.request(_ADD_GROUP_TEMPLATE, files=multipart_fields(request.model_dump(mode="json")))
|
|
504
|
+
after = await self._verified_list("template group addition")
|
|
505
|
+
observed = next(
|
|
506
|
+
(
|
|
507
|
+
item.library_entry
|
|
508
|
+
for item in after.group
|
|
509
|
+
if item.ref == template and item.group == group and item.library_entry is not None
|
|
510
|
+
),
|
|
511
|
+
None,
|
|
512
|
+
)
|
|
513
|
+
return TemplateGroupAddResult(
|
|
514
|
+
template=template,
|
|
515
|
+
group=group,
|
|
516
|
+
observed_entry=observed,
|
|
517
|
+
observation="relation_observed" if observed is not None else "relation_not_observed",
|
|
518
|
+
)
|
|
519
|
+
|
|
520
|
+
async def remove_from_group(self, entry: TemplateLibraryEntryRef) -> TemplateGroupRemoveResult:
|
|
521
|
+
if entry.kind is not TemplateLibraryRelationKind.GROUP:
|
|
522
|
+
raise MatelabUsageError("Removing a group template requires a group library relation.")
|
|
523
|
+
try:
|
|
524
|
+
request = WireDeleteGroupTemplateRequest(id=entry.relation_id)
|
|
525
|
+
except ValidationError:
|
|
526
|
+
raise MatelabUsageError("Group-template relation does not satisfy the Integration Contract.") from None
|
|
527
|
+
_ = await self._transport.request(
|
|
528
|
+
_REMOVE_GROUP_TEMPLATE, files=multipart_fields(request.model_dump(mode="json"))
|
|
529
|
+
)
|
|
530
|
+
after = await self._verified_list("template group removal")
|
|
531
|
+
return TemplateGroupRemoveResult(entry=entry, absence_confirmed=not self._relation_present(after.group, entry))
|
|
532
|
+
|
|
533
|
+
async def acquire_from_market(self, template: TemplateRef) -> TemplateMarketAcquireResult:
|
|
534
|
+
try:
|
|
535
|
+
request = WireTemplateIdRequest(id=template.template_id)
|
|
536
|
+
except ValidationError:
|
|
537
|
+
raise MatelabUsageError("Template identity does not satisfy the Integration Contract.") from None
|
|
538
|
+
_ = await self._transport.request(_ACQUIRE_MARKET_TEMPLATE, payload=request.model_dump(mode="json"))
|
|
539
|
+
after = await self._verified_list("template market acquisition")
|
|
540
|
+
observed = next(
|
|
541
|
+
(
|
|
542
|
+
item.library_entry
|
|
543
|
+
for item in after.market_acquired
|
|
544
|
+
if item.ref == template and item.library_entry is not None
|
|
545
|
+
),
|
|
546
|
+
None,
|
|
547
|
+
)
|
|
548
|
+
return TemplateMarketAcquireResult(
|
|
549
|
+
template=template,
|
|
550
|
+
observed_entry=observed,
|
|
551
|
+
observation="relation_observed" if observed is not None else "relation_not_observed",
|
|
552
|
+
)
|
|
553
|
+
|
|
554
|
+
async def create(self, metadata: TemplateMetadata) -> TemplateMetadataResult:
|
|
555
|
+
return await self._save_metadata(template=None, metadata=metadata)
|
|
556
|
+
|
|
557
|
+
async def update(self, template: TemplateRef, metadata: TemplateMetadata) -> TemplateMetadataResult:
|
|
558
|
+
return await self._save_metadata(template=template, metadata=metadata)
|
|
559
|
+
|
|
560
|
+
async def save_content(self, template: TemplateRef, modules: Sequence[TemplateModule]) -> TemplateContentSaveResult:
|
|
561
|
+
requested = tuple(modules)
|
|
562
|
+
payloads = self._module_payloads(requested)
|
|
563
|
+
try:
|
|
564
|
+
request = WireSaveTemplateContentRequest(
|
|
565
|
+
id=template.template_id, data=json.dumps(payloads, ensure_ascii=False, separators=(",", ":"))
|
|
566
|
+
)
|
|
567
|
+
except (ValidationError, TypeError, ValueError):
|
|
568
|
+
raise MatelabUsageError("Template content does not satisfy the Integration Contract.") from None
|
|
569
|
+
_ = await self._transport.request(_SAVE_TEMPLATE_CONTENT, payload=request.model_dump(mode="json"))
|
|
570
|
+
observed = await self._verified_read(template, "template content replacement")
|
|
571
|
+
return TemplateContentSaveResult(
|
|
572
|
+
template=template,
|
|
573
|
+
requested=requested,
|
|
574
|
+
observed=observed,
|
|
575
|
+
confirmation="observed_matching" if observed.modules == requested else "observed_mismatch",
|
|
576
|
+
)
|
|
577
|
+
|
|
578
|
+
async def copy(self, template: TemplateRef, *, title: str) -> TemplateCopyResult:
|
|
579
|
+
try:
|
|
580
|
+
request = WireCopyTemplateRequest(id=template.template_id, template_name=title)
|
|
581
|
+
except ValidationError:
|
|
582
|
+
raise MatelabUsageError("Template copy input does not satisfy the Integration Contract.") from None
|
|
583
|
+
_ = await self._transport.request(_COPY_TEMPLATE, payload=request.model_dump(mode="json"))
|
|
584
|
+
return TemplateCopyResult(source=template, requested_title=title)
|
|
585
|
+
|
|
586
|
+
async def update_intro(
|
|
587
|
+
self, template: TemplateRef, *, intro: str | None, upload_binding: UploadBindingRef
|
|
588
|
+
) -> TemplateIntroUpdateResult:
|
|
589
|
+
try:
|
|
590
|
+
request = WireUpdateTemplateIntroRequest(id=template.template_id, intro=intro, uid=upload_binding.uid)
|
|
591
|
+
except ValidationError:
|
|
592
|
+
raise MatelabUsageError("Template intro input does not satisfy the Integration Contract.") from None
|
|
593
|
+
response = await self._transport.request(_UPDATE_TEMPLATE_INTRO, payload=request.model_dump(mode="json"))
|
|
594
|
+
return TemplateIntroUpdateResult(template=template, intro=response.intro, upload_binding=upload_binding)
|
|
595
|
+
|
|
596
|
+
async def submit_to_market(self, template: TemplateRef) -> TemplateMarketSubmissionResult:
|
|
597
|
+
try:
|
|
598
|
+
request = WireTemplateIdRequest(id=template.template_id)
|
|
599
|
+
except ValidationError:
|
|
600
|
+
raise MatelabUsageError("Template identity does not satisfy the Integration Contract.") from None
|
|
601
|
+
_ = await self._transport.request(_SUBMIT_TEMPLATE, payload=request.model_dump(mode="json"))
|
|
602
|
+
after = await self._verified_list("template market submission")
|
|
603
|
+
observed = any(item.ref == template for item in after.pending_market_review)
|
|
604
|
+
return TemplateMarketSubmissionResult(
|
|
605
|
+
template=template,
|
|
606
|
+
observed_pending_review=observed,
|
|
607
|
+
observation="pending_entry_observed" if observed else "pending_entry_not_observed",
|
|
608
|
+
)
|
|
609
|
+
|
|
610
|
+
async def _remove_user_library_entry(self, entry: TemplateLibraryEntryRef) -> TemplateLibraryRemovalResult:
|
|
611
|
+
try:
|
|
612
|
+
request = WireRemoveUserTemplateRelationRequest(id=entry.relation_id)
|
|
613
|
+
except ValidationError:
|
|
614
|
+
raise MatelabUsageError("Template library relation does not satisfy the Integration Contract.") from None
|
|
615
|
+
_ = await self._transport.request(
|
|
616
|
+
_REMOVE_USER_LIBRARY_ENTRY, files=multipart_fields(request.model_dump(mode="json"))
|
|
617
|
+
)
|
|
618
|
+
after = await self._verified_list("template library removal")
|
|
619
|
+
scope = after.shared if entry.kind is TemplateLibraryRelationKind.DIRECT_SHARE else after.market_acquired
|
|
620
|
+
return TemplateLibraryRemovalResult(entry=entry, absence_confirmed=not self._relation_present(scope, entry))
|
|
621
|
+
|
|
622
|
+
async def _save_metadata(
|
|
623
|
+
self, *, template: TemplateRef | None, metadata: TemplateMetadata
|
|
624
|
+
) -> TemplateMetadataResult:
|
|
625
|
+
if any(not isinstance(keyword, str) for keyword in cast(tuple[object, ...], metadata.keywords)):
|
|
626
|
+
raise MatelabUsageError("Template keywords must be strings.")
|
|
627
|
+
try:
|
|
628
|
+
request = WireSaveTemplateMetadataRequest(
|
|
629
|
+
id=template.template_id if template is not None else 0,
|
|
630
|
+
showtext=metadata.title,
|
|
631
|
+
comm=metadata.summary,
|
|
632
|
+
keyword=json.dumps(metadata.keywords, ensure_ascii=False, separators=(",", ":")),
|
|
633
|
+
)
|
|
634
|
+
except (ValidationError, TypeError, ValueError):
|
|
635
|
+
raise MatelabUsageError("Template metadata does not satisfy the Integration Contract.") from None
|
|
636
|
+
response = await self._transport.request(_SAVE_TEMPLATE_METADATA, payload=request.model_dump(mode="json"))
|
|
637
|
+
expected_action = "edit" if template is not None else "add"
|
|
638
|
+
if response.action.value != expected_action or (template is not None and response.id != template.template_id):
|
|
639
|
+
raise MatelabProtocolError("Matelab returned an inconsistent template metadata action or identity.")
|
|
640
|
+
result_ref = TemplateRef(template_id=response.id)
|
|
641
|
+
after = await self._verified_list("template metadata save")
|
|
642
|
+
observed = next((item for item in after.owned if item.ref == result_ref), None)
|
|
643
|
+
if observed is None:
|
|
644
|
+
confirmation: Literal["observed_matching", "observed_mismatch", "not_observed"] = "not_observed"
|
|
645
|
+
elif self._metadata_matches(observed, metadata):
|
|
646
|
+
confirmation = "observed_matching"
|
|
647
|
+
else:
|
|
648
|
+
confirmation = "observed_mismatch"
|
|
649
|
+
return TemplateMetadataResult(
|
|
650
|
+
template=result_ref,
|
|
651
|
+
requested=metadata,
|
|
652
|
+
action="updated" if template is not None else "created",
|
|
653
|
+
observed=observed,
|
|
654
|
+
confirmation=confirmation,
|
|
655
|
+
)
|
|
656
|
+
|
|
657
|
+
async def _verified_list(self, action: str) -> TemplateCollection:
|
|
658
|
+
try:
|
|
659
|
+
return await self.list()
|
|
660
|
+
except MatelabError as exc:
|
|
661
|
+
raise MatelabVerificationError(
|
|
662
|
+
f"Matelab acknowledged the {action}, but readback failed; the mutation may have persisted."
|
|
663
|
+
) from exc
|
|
664
|
+
|
|
665
|
+
async def _verified_read(self, template: TemplateRef, action: str) -> TemplateContent:
|
|
666
|
+
try:
|
|
667
|
+
return await self.read(template)
|
|
668
|
+
except MatelabError as exc:
|
|
669
|
+
raise MatelabVerificationError(
|
|
670
|
+
f"Matelab acknowledged the {action}, but readback failed; the mutation may have persisted."
|
|
671
|
+
) from exc
|
|
672
|
+
|
|
673
|
+
@staticmethod
|
|
674
|
+
def _bounded_unique_ids(values: Iterable[int], *, subject: str) -> tuple[int, ...]:
|
|
675
|
+
selected = tuple(values)
|
|
676
|
+
if (
|
|
677
|
+
not 1 <= len(selected) <= 10
|
|
678
|
+
or any(type(value) is not int or value < 1 for value in selected)
|
|
679
|
+
or len(selected) != len(set(selected))
|
|
680
|
+
):
|
|
681
|
+
raise MatelabUsageError(f"{subject} must contain one through ten distinct positive ids.")
|
|
682
|
+
return selected
|
|
683
|
+
|
|
684
|
+
@staticmethod
|
|
685
|
+
def _relation_present(scope: Sequence[TemplateSummary], entry: TemplateLibraryEntryRef) -> bool:
|
|
686
|
+
return any(item.library_entry == entry for item in scope)
|
|
687
|
+
|
|
688
|
+
@staticmethod
|
|
689
|
+
def _metadata_matches(observed: TemplateSummary, requested: TemplateMetadata) -> bool:
|
|
690
|
+
return (
|
|
691
|
+
observed.title == requested.title
|
|
692
|
+
and (observed.summary or "") == (requested.summary or "")
|
|
693
|
+
and observed.keywords == requested.keywords
|
|
694
|
+
)
|
|
695
|
+
|
|
696
|
+
@staticmethod
|
|
697
|
+
def _module_payloads(modules: tuple[TemplateModule, ...]) -> tuple[dict[str, object], ...]:
|
|
698
|
+
if len({module.name for module in modules}) != len(modules):
|
|
699
|
+
raise MatelabUsageError("Template module names must be unique.")
|
|
700
|
+
payloads: list[dict[str, object]] = []
|
|
701
|
+
try:
|
|
702
|
+
for module in modules:
|
|
703
|
+
attributes = dict(module.attributes)
|
|
704
|
+
if _MODULE_RESERVED_FIELDS & attributes.keys():
|
|
705
|
+
raise MatelabUsageError("Template module attributes cannot replace canonical fields.")
|
|
706
|
+
payload: dict[str, object] = {**attributes, "name": module.name, "type": module.module_type}
|
|
707
|
+
if module.data_present:
|
|
708
|
+
payload["data"] = module.data
|
|
709
|
+
if module.language is not None:
|
|
710
|
+
payload["language"] = module.language
|
|
711
|
+
if module.style is not None:
|
|
712
|
+
payload["style"] = module.style
|
|
713
|
+
wire = WireTemplateModule.model_validate(payload)
|
|
714
|
+
payloads.append(wire.model_dump(mode="json", exclude_unset=True))
|
|
715
|
+
_ = json.dumps(payloads, ensure_ascii=False)
|
|
716
|
+
except (ValidationError, TypeError, ValueError):
|
|
717
|
+
raise MatelabUsageError("Template modules do not satisfy the Integration Contract.") from None
|
|
718
|
+
return tuple(payloads)
|
|
719
|
+
|
|
720
|
+
@staticmethod
|
|
721
|
+
def _publication_reason(item: WireOwnedTemplateListItem) -> str:
|
|
722
|
+
extra = cast(dict[str, object], item.model_extra or {})
|
|
723
|
+
stable_alias = extra.get("pub_reason")
|
|
724
|
+
return stable_alias if isinstance(stable_alias, str) else item.Ifnull_pub_reason_____
|
|
725
|
+
|
|
726
|
+
@staticmethod
|
|
727
|
+
def _module(module: WireTemplateModule) -> TemplateModule:
|
|
728
|
+
attributes = MappingProxyType(cast(dict[str, object], dict(module.model_extra or {})))
|
|
729
|
+
return TemplateModule(
|
|
730
|
+
name=module.name,
|
|
731
|
+
module_type=module.type,
|
|
732
|
+
data=cast(object, module.data),
|
|
733
|
+
data_present="data" in module.model_fields_set,
|
|
734
|
+
language=module.language,
|
|
735
|
+
style=cast(object, module.style),
|
|
736
|
+
attributes=attributes,
|
|
737
|
+
)
|