cacholong-cloud-cli 0.3.0__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.
- cacholong_cli/AsyncTyper.py +17 -0
- cacholong_cli/__init__.py +0 -0
- cacholong_cli/cli.py +110 -0
- cacholong_cli/commands/accounts.py +105 -0
- cacholong_cli/commands/addresses.py +280 -0
- cacholong_cli/commands/companies.py +100 -0
- cacholong_cli/commands/dns_records.py +275 -0
- cacholong_cli/commands/dns_templates.py +141 -0
- cacholong_cli/commands/dns_zones.py +277 -0
- cacholong_cli/commands/products.py +121 -0
- cacholong_cli/commands/purchases.py +127 -0
- cacholong_cli/common.py +179 -0
- cacholong_cli/connection.py +18 -0
- cacholong_cloud_cli-0.3.0.dist-info/METADATA +115 -0
- cacholong_cloud_cli-0.3.0.dist-info/RECORD +39 -0
- cacholong_cloud_cli-0.3.0.dist-info/WHEEL +4 -0
- cacholong_cloud_cli-0.3.0.dist-info/entry_points.txt +3 -0
- cacholong_sdk/__init__.py +18 -0
- cacholong_sdk/account.py +14 -0
- cacholong_sdk/address.py +14 -0
- cacholong_sdk/api_schema.py +124 -0
- cacholong_sdk/common.py +119 -0
- cacholong_sdk/company.py +14 -0
- cacholong_sdk/connection.py +22 -0
- cacholong_sdk/dns_record.py +14 -0
- cacholong_sdk/dns_template.py +14 -0
- cacholong_sdk/dns_zone.py +14 -0
- cacholong_sdk/exception.py +7 -0
- cacholong_sdk/product.py +14 -0
- cacholong_sdk/purchase.py +14 -0
- jsonapi_client/__init__.py +42 -0
- jsonapi_client/common.py +186 -0
- jsonapi_client/document.py +161 -0
- jsonapi_client/exceptions.py +62 -0
- jsonapi_client/filter.py +155 -0
- jsonapi_client/objects.py +187 -0
- jsonapi_client/relationships.py +447 -0
- jsonapi_client/resourceobject.py +656 -0
- jsonapi_client/session.py +755 -0
|
@@ -0,0 +1,447 @@
|
|
|
1
|
+
"""
|
|
2
|
+
JSON API Python client
|
|
3
|
+
https://github.com/qvantel/jsonapi-client
|
|
4
|
+
|
|
5
|
+
(see JSON API specification in http://jsonapi.org/)
|
|
6
|
+
|
|
7
|
+
Copyright (c) 2017, Qvantel
|
|
8
|
+
All rights reserved.
|
|
9
|
+
|
|
10
|
+
Redistribution and use in source and binary forms, with or without
|
|
11
|
+
modification, are permitted provided that the following conditions are met:
|
|
12
|
+
* Redistributions of source code must retain the above copyright
|
|
13
|
+
notice, this list of conditions and the following disclaimer.
|
|
14
|
+
* Redistributions in binary form must reproduce the above copyright
|
|
15
|
+
notice, this list of conditions and the following disclaimer in the
|
|
16
|
+
documentation and/or other materials provided with the distribution.
|
|
17
|
+
* Neither the name of the Qvantel nor the
|
|
18
|
+
names of its contributors may be used to endorse or promote products
|
|
19
|
+
derived from this software without specific prior written permission.
|
|
20
|
+
|
|
21
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
22
|
+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
23
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
24
|
+
DISCLAIMED. IN NO EVENT SHALL QVANTEL BE LIABLE FOR ANY
|
|
25
|
+
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
26
|
+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
27
|
+
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
28
|
+
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
29
|
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
30
|
+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
import collections
|
|
34
|
+
import logging
|
|
35
|
+
from typing import List, Union, Iterable, Dict, Tuple, Awaitable, TYPE_CHECKING
|
|
36
|
+
|
|
37
|
+
from .common import AbstractJsonObject, RelationType, ResourceTuple
|
|
38
|
+
from .objects import (Meta, Links, ResourceIdentifier, RESOURCE_TYPES)
|
|
39
|
+
from .resourceobject import ResourceObject
|
|
40
|
+
|
|
41
|
+
logger = logging.getLogger(__name__)
|
|
42
|
+
|
|
43
|
+
R_IDENT_TYPES = Union[str, ResourceObject, ResourceIdentifier, ResourceTuple]
|
|
44
|
+
|
|
45
|
+
if TYPE_CHECKING:
|
|
46
|
+
from .filter import Modifier
|
|
47
|
+
from .document import Document
|
|
48
|
+
from .session import Session
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class AbstractRelationship(AbstractJsonObject):
|
|
52
|
+
"""
|
|
53
|
+
Relationships are containers for ResourceObjects related to relationships.
|
|
54
|
+
ResourceObjects are automatically fetched if not in async mode.
|
|
55
|
+
If in async mode, .fetch() needs to be awaited first.
|
|
56
|
+
|
|
57
|
+
http://jsonapi.org/format/#document-resource-object-relationships
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
def __init__(self,
|
|
61
|
+
session: 'Session',
|
|
62
|
+
data: dict,
|
|
63
|
+
resource_types: List[str]=None,
|
|
64
|
+
relation_type: str='') -> None:
|
|
65
|
+
"""
|
|
66
|
+
:param resource_types: List of allowed resource types
|
|
67
|
+
:param relation_type: Relation type, either 'to-one' or 'to-many',
|
|
68
|
+
or not specified (empty string).
|
|
69
|
+
"""
|
|
70
|
+
self._resources: Dict[Tuple[str, str], ResourceObject] = None
|
|
71
|
+
self._invalid = False
|
|
72
|
+
self._is_dirty: bool = False
|
|
73
|
+
self._resource_types = resource_types or []
|
|
74
|
+
self._relation_type = relation_type
|
|
75
|
+
|
|
76
|
+
super().__init__(session, data)
|
|
77
|
+
|
|
78
|
+
@property
|
|
79
|
+
def is_single(self) -> bool:
|
|
80
|
+
raise NotImplementedError
|
|
81
|
+
|
|
82
|
+
def _modify_sync(self, modifier: 'Modifier') -> 'Document':
|
|
83
|
+
url = modifier.url_with_modifiers(self.url)
|
|
84
|
+
return self.session.fetch_document_by_url(url)
|
|
85
|
+
|
|
86
|
+
async def _modify_async(self, modifier: 'Modifier'):
|
|
87
|
+
url = modifier.url_with_modifiers(self.url)
|
|
88
|
+
return self.session.fetch_document_by_url_async(url)
|
|
89
|
+
|
|
90
|
+
def filter(self, filter: 'Modifier') -> 'Union[Awaitable[Document], Document]':
|
|
91
|
+
"""
|
|
92
|
+
Receive filtered list of resources. Use Modifier instance.
|
|
93
|
+
|
|
94
|
+
If in async mode, this needs to be awaited.
|
|
95
|
+
"""
|
|
96
|
+
if self.session.enable_async:
|
|
97
|
+
return self._modify_async(filter)
|
|
98
|
+
else:
|
|
99
|
+
return self._modify_sync(filter)
|
|
100
|
+
|
|
101
|
+
@property
|
|
102
|
+
def is_dirty(self) -> bool:
|
|
103
|
+
return self._is_dirty
|
|
104
|
+
|
|
105
|
+
def mark_clean(self):
|
|
106
|
+
"""
|
|
107
|
+
Mark this relationship as clean (not modified/dirty).
|
|
108
|
+
"""
|
|
109
|
+
self._is_dirty = False
|
|
110
|
+
|
|
111
|
+
def mark_dirty(self):
|
|
112
|
+
"""
|
|
113
|
+
Mark this relationship as modified/dirty.
|
|
114
|
+
"""
|
|
115
|
+
self._is_dirty = True
|
|
116
|
+
|
|
117
|
+
async def _fetch_async(self) -> 'List[ResourceObject]':
|
|
118
|
+
raise NotImplementedError
|
|
119
|
+
|
|
120
|
+
def _fetch_sync(self) -> 'List[ResourceObject]':
|
|
121
|
+
raise NotImplementedError
|
|
122
|
+
|
|
123
|
+
def fetch(self) -> 'Union[Awaitable[List[ResourceObject]], List[ResourceObject]]':
|
|
124
|
+
"""
|
|
125
|
+
Fetch ResourceObjects. In practice this needs to be used only if in async mode
|
|
126
|
+
and then this needs to be awaited.
|
|
127
|
+
|
|
128
|
+
In blocking (sync) mode this is called automatically when .resource or
|
|
129
|
+
.resources is accessed.
|
|
130
|
+
"""
|
|
131
|
+
if self.session.enable_async:
|
|
132
|
+
return self._fetch_async()
|
|
133
|
+
else:
|
|
134
|
+
return self._fetch_sync()
|
|
135
|
+
|
|
136
|
+
def _handle_data(self, data):
|
|
137
|
+
self.links = Links(self.session, data.get('links', {}))
|
|
138
|
+
self.meta = Meta(self.session, data.get('meta', {}))
|
|
139
|
+
self._resource_data = data.get('data', {})
|
|
140
|
+
|
|
141
|
+
@property
|
|
142
|
+
def resources(self) -> 'List[Union[ResourceIdentifier, ResourceObject]]':
|
|
143
|
+
"""
|
|
144
|
+
Return related ResourceObjects. If this relationship has been
|
|
145
|
+
modified (waiting to be committed (PATCH)), this also returns
|
|
146
|
+
ResourceIdentifier objects of those new linked resources.
|
|
147
|
+
|
|
148
|
+
In async mode, you need to first await .fetch()
|
|
149
|
+
"""
|
|
150
|
+
return ((self._resources is not None and list(self._resources.values()))
|
|
151
|
+
or self._fetch_sync())
|
|
152
|
+
|
|
153
|
+
@property
|
|
154
|
+
def resource(self) -> 'ResourceObject':
|
|
155
|
+
"""
|
|
156
|
+
If there is only 1 resource, return it.
|
|
157
|
+
|
|
158
|
+
In async mode, you need to first await .fetch()
|
|
159
|
+
"""
|
|
160
|
+
if len(self.resources) > 1:
|
|
161
|
+
logger.warning('More than 1 resource, use .resources instead!')
|
|
162
|
+
return self.resources[0]
|
|
163
|
+
|
|
164
|
+
@property
|
|
165
|
+
def as_json_resource_identifiers(self) -> dict:
|
|
166
|
+
"""
|
|
167
|
+
Return resource identifier -style linkage (used in posting/patching)
|
|
168
|
+
|
|
169
|
+
i.e.
|
|
170
|
+
|
|
171
|
+
{'type': 'model_name', 'id': '1'}
|
|
172
|
+
|
|
173
|
+
or list of these.
|
|
174
|
+
"""
|
|
175
|
+
raise NotImplementedError
|
|
176
|
+
|
|
177
|
+
@property
|
|
178
|
+
def is_fetched(self) -> bool:
|
|
179
|
+
return bool(self._resources)
|
|
180
|
+
|
|
181
|
+
def set(self, new_value, type_=None) -> None:
|
|
182
|
+
"""
|
|
183
|
+
This function is used when new values is set as targets of this relationship.
|
|
184
|
+
|
|
185
|
+
Implement in subclasses
|
|
186
|
+
"""
|
|
187
|
+
raise NotImplementedError
|
|
188
|
+
|
|
189
|
+
def __str__(self):
|
|
190
|
+
raise NotImplementedError
|
|
191
|
+
|
|
192
|
+
@property
|
|
193
|
+
def url(self) -> str:
|
|
194
|
+
raise NotImplementedError
|
|
195
|
+
|
|
196
|
+
@property
|
|
197
|
+
def type(self) -> str:
|
|
198
|
+
"""
|
|
199
|
+
Return the type of this relationship, if there is only 1 allowed type.
|
|
200
|
+
"""
|
|
201
|
+
if len(self._resource_types) != 1:
|
|
202
|
+
raise TypeError('Type needs to be specified manually, use .set or .add')
|
|
203
|
+
return self._resource_types[0]
|
|
204
|
+
|
|
205
|
+
def __bool__(self):
|
|
206
|
+
raise NotImplementedError
|
|
207
|
+
|
|
208
|
+
def _value_to_identifier(self, value: R_IDENT_TYPES, type_: str='') \
|
|
209
|
+
-> 'Union[ResourceIdentifier, ResourceObject]':
|
|
210
|
+
if isinstance(value, RESOURCE_TYPES):
|
|
211
|
+
r_ident = ResourceIdentifier(self.session, {'id': value.id, 'type': value.type, 'meta': value.meta})
|
|
212
|
+
else:
|
|
213
|
+
r_ident = ResourceIdentifier(self.session, {'id': value,
|
|
214
|
+
'type': type_ or self.type})
|
|
215
|
+
res = self._resources and self._resources.get((r_ident.type, r_ident.id))
|
|
216
|
+
return res or r_ident
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
class SingleRelationship(AbstractRelationship):
|
|
220
|
+
"""
|
|
221
|
+
Relationship class for to-one type relationships, that are received from
|
|
222
|
+
server as ResourceIdentifiers.
|
|
223
|
+
"""
|
|
224
|
+
def _handle_data(self, data):
|
|
225
|
+
super()._handle_data(data)
|
|
226
|
+
if self._resource_data is None:
|
|
227
|
+
self._resource_identifier = None
|
|
228
|
+
else:
|
|
229
|
+
self._resource_identifier = ResourceIdentifier(self.session, self._resource_data)
|
|
230
|
+
del self._resource_data # This is not intended to be used after this
|
|
231
|
+
|
|
232
|
+
async def _fetch_async(self) -> 'List[ResourceObject]':
|
|
233
|
+
self.session.assert_async()
|
|
234
|
+
res_id = self._resource_identifier
|
|
235
|
+
if res_id is None:
|
|
236
|
+
self._resources = {None: None}
|
|
237
|
+
else:
|
|
238
|
+
res = await self.session.fetch_resource_by_resource_identifier_async(res_id)
|
|
239
|
+
self._resources = {(res.type, res.id): res}
|
|
240
|
+
return list(self._resources.values())
|
|
241
|
+
|
|
242
|
+
def _fetch_sync(self) -> 'List[ResourceObject]':
|
|
243
|
+
self.session.assert_sync()
|
|
244
|
+
res_id = self._resource_identifier
|
|
245
|
+
if res_id is None:
|
|
246
|
+
self._resources = {None: None}
|
|
247
|
+
else:
|
|
248
|
+
res = self.session.fetch_resource_by_resource_identifier(res_id)
|
|
249
|
+
self._resources = {(res.type, res.id): res}
|
|
250
|
+
return list(self._resources.values())
|
|
251
|
+
|
|
252
|
+
def __bool__(self):
|
|
253
|
+
return bool(self._resource_identifier)
|
|
254
|
+
|
|
255
|
+
def __str__(self):
|
|
256
|
+
return str(self._resource_identifier)
|
|
257
|
+
|
|
258
|
+
@property
|
|
259
|
+
def is_single(self) -> bool:
|
|
260
|
+
return True
|
|
261
|
+
|
|
262
|
+
@property
|
|
263
|
+
def url(self) -> str:
|
|
264
|
+
if self._resource_identifier is None:
|
|
265
|
+
return self.links.related
|
|
266
|
+
return self._resource_identifier.url
|
|
267
|
+
|
|
268
|
+
@property
|
|
269
|
+
def as_json_resource_identifiers(self) -> dict:
|
|
270
|
+
if self._resource_identifier is None:
|
|
271
|
+
return None
|
|
272
|
+
return self._resource_identifier.as_resource_identifier_dict()
|
|
273
|
+
|
|
274
|
+
def _value_to_identifier(self, value: R_IDENT_TYPES, type_: str='') \
|
|
275
|
+
-> 'Union[ResourceIdentifier, ResourceObject]':
|
|
276
|
+
if value is None:
|
|
277
|
+
return None
|
|
278
|
+
return super()._value_to_identifier(value, type_)
|
|
279
|
+
|
|
280
|
+
def set(self, new_value: R_IDENT_TYPES, type_: str='') -> None:
|
|
281
|
+
|
|
282
|
+
self._resource_identifier = self._value_to_identifier(new_value, type_)
|
|
283
|
+
self.mark_dirty()
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
class MultiRelationship(AbstractRelationship):
|
|
287
|
+
"""
|
|
288
|
+
Relationship class for to-many type relationships, that are received from
|
|
289
|
+
server as ResourceIdentifiers.
|
|
290
|
+
"""
|
|
291
|
+
def _handle_data(self, data):
|
|
292
|
+
super()._handle_data(data)
|
|
293
|
+
self._resource_identifiers = [ResourceIdentifier(self.session, d)
|
|
294
|
+
for d in self._resource_data]
|
|
295
|
+
del self._resource_data
|
|
296
|
+
|
|
297
|
+
@property
|
|
298
|
+
def is_single(self) -> bool:
|
|
299
|
+
return False
|
|
300
|
+
|
|
301
|
+
async def _fetch_async(self) -> 'List[ResourceObject]':
|
|
302
|
+
self.session.assert_async()
|
|
303
|
+
self._resources = {}
|
|
304
|
+
for res_id in self._resource_identifiers:
|
|
305
|
+
res = await self.session.fetch_resource_by_resource_identifier_async(res_id)
|
|
306
|
+
self._resources[(res.type, res.id)] = res
|
|
307
|
+
return list(self._resources.values())
|
|
308
|
+
|
|
309
|
+
def _fetch_sync(self) -> 'List[ResourceObject]':
|
|
310
|
+
self.session.assert_sync()
|
|
311
|
+
self._resources = {}
|
|
312
|
+
for res_id in self._resource_identifiers:
|
|
313
|
+
res = self.session.fetch_resource_by_resource_identifier(res_id)
|
|
314
|
+
self._resources[(res.type, res.id)] = res
|
|
315
|
+
return list(self._resources.values())
|
|
316
|
+
|
|
317
|
+
def __str__(self):
|
|
318
|
+
return str(self._resource_identifiers)
|
|
319
|
+
|
|
320
|
+
@property
|
|
321
|
+
def url(self) -> str:
|
|
322
|
+
return self.links.related
|
|
323
|
+
|
|
324
|
+
@property
|
|
325
|
+
def as_json_resource_identifiers(self) -> List[dict]:
|
|
326
|
+
return [res.as_resource_identifier_dict() for res in self._resource_identifiers]
|
|
327
|
+
|
|
328
|
+
def set(self, new_values: Iterable[R_IDENT_TYPES], type_: str=None) -> None:
|
|
329
|
+
self._resource_identifiers = [self._value_to_identifier(value, type_)
|
|
330
|
+
for value in new_values]
|
|
331
|
+
self.mark_dirty()
|
|
332
|
+
|
|
333
|
+
def clear(self):
|
|
334
|
+
"""
|
|
335
|
+
Remove all target resources (commit will remove them on server side).
|
|
336
|
+
"""
|
|
337
|
+
self._resource_identifiers.clear()
|
|
338
|
+
self.mark_dirty()
|
|
339
|
+
|
|
340
|
+
def add(self, new_value: Union[R_IDENT_TYPES, Iterable[R_IDENT_TYPES]], type_=None) -> None:
|
|
341
|
+
"""
|
|
342
|
+
Add new resources
|
|
343
|
+
"""
|
|
344
|
+
if type_ is None:
|
|
345
|
+
type_ = self.type
|
|
346
|
+
if isinstance(new_value, collections.abc.Iterable):
|
|
347
|
+
self._resource_identifiers.extend(
|
|
348
|
+
[self._value_to_identifier(val, type_) for val in new_value])
|
|
349
|
+
else:
|
|
350
|
+
self._resource_identifiers.append(self._value_to_identifier(new_value, type_))
|
|
351
|
+
|
|
352
|
+
self.mark_dirty()
|
|
353
|
+
|
|
354
|
+
def __add__(self, other):
|
|
355
|
+
return self.add(other)
|
|
356
|
+
|
|
357
|
+
def __bool__(self):
|
|
358
|
+
return bool(self._resource_identifiers)
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
class LinkRelationship(AbstractRelationship):
|
|
362
|
+
"""
|
|
363
|
+
Relationship class for to-one or to-many type relationships, that are received from
|
|
364
|
+
server with only link information (no ResourceIdentifiers).
|
|
365
|
+
"""
|
|
366
|
+
def __init__(self, *args, **kwargs):
|
|
367
|
+
self._resource_identifiers = None
|
|
368
|
+
self._document: 'Document' = None
|
|
369
|
+
super().__init__(*args, **kwargs)
|
|
370
|
+
|
|
371
|
+
def __bool__(self):
|
|
372
|
+
return bool(self._resource_identifiers)
|
|
373
|
+
|
|
374
|
+
@property
|
|
375
|
+
def document(self) -> 'Document':
|
|
376
|
+
doc = getattr(self, '_document', None)
|
|
377
|
+
if doc is None:
|
|
378
|
+
self._fetch_sync()
|
|
379
|
+
return self._document
|
|
380
|
+
|
|
381
|
+
@property
|
|
382
|
+
def is_single(self) -> bool:
|
|
383
|
+
if self._relation_type:
|
|
384
|
+
return self._relation_type == RelationType.TO_ONE
|
|
385
|
+
else:
|
|
386
|
+
return False
|
|
387
|
+
|
|
388
|
+
async def _fetch_async(self) -> 'List[ResourceObject]':
|
|
389
|
+
self.session.assert_async()
|
|
390
|
+
self._document = \
|
|
391
|
+
await self.session.fetch_document_by_url_async(self.links.related.url)
|
|
392
|
+
if self.session.use_relationship_iterator:
|
|
393
|
+
return self._document.iterator()
|
|
394
|
+
self._resources = {(r.type, r.id): r for r in self._document.resources}
|
|
395
|
+
return list(self._resources.values())
|
|
396
|
+
|
|
397
|
+
def _fetch_sync(self) -> 'List[ResourceObject]':
|
|
398
|
+
self.session.assert_sync()
|
|
399
|
+
self._document = self.session.fetch_document_by_url(self.links.related.url)
|
|
400
|
+
if self.session.use_relationship_iterator:
|
|
401
|
+
return self._document.iterator()
|
|
402
|
+
self._resources = {(r.type, r.id): r for r in self._document.resources}
|
|
403
|
+
return list(self._resources.values())
|
|
404
|
+
|
|
405
|
+
def mark_clean(self):
|
|
406
|
+
self._is_dirty = False
|
|
407
|
+
if self._document:
|
|
408
|
+
self._document.mark_invalid()
|
|
409
|
+
|
|
410
|
+
def __str__(self):
|
|
411
|
+
return (f'{self.url} ({len(self.resources)}) dirty: {self.is_dirty}'
|
|
412
|
+
if self.is_fetched else self.url)
|
|
413
|
+
|
|
414
|
+
@property
|
|
415
|
+
def as_json_resource_identifiers(self) -> Union[list, dict]:
|
|
416
|
+
if self.is_single:
|
|
417
|
+
return self.resource.as_resource_identifier_dict()
|
|
418
|
+
else:
|
|
419
|
+
return [res.as_resource_identifier_dict() for res in self.resources]
|
|
420
|
+
|
|
421
|
+
@property
|
|
422
|
+
def url(self) -> str:
|
|
423
|
+
return str(self.links.related)
|
|
424
|
+
|
|
425
|
+
def set(self, new_value: Union[Iterable[R_IDENT_TYPES], R_IDENT_TYPES],
|
|
426
|
+
type_: str='') -> None:
|
|
427
|
+
if isinstance(new_value, collections.abc.Iterable):
|
|
428
|
+
if self.is_single:
|
|
429
|
+
logger.warning('This should contain list of resources, '
|
|
430
|
+
'but only one is given')
|
|
431
|
+
resources = [self._value_to_identifier(val, type_) for val in new_value]
|
|
432
|
+
self._resources = {(r.type, r.id):r for r in resources}
|
|
433
|
+
else:
|
|
434
|
+
if not self.is_single:
|
|
435
|
+
logger.warning('This should contain only 1 resource, '
|
|
436
|
+
'but a list of values is given')
|
|
437
|
+
res = self._value_to_identifier(new_value, type_)
|
|
438
|
+
self._resources = {(res.type, res.id): res}
|
|
439
|
+
self.mark_dirty()
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
class MetaRelationship(AbstractRelationship):
|
|
443
|
+
"""
|
|
444
|
+
Handle relationship manually through meta object. We don't know what to do
|
|
445
|
+
about them as they are custom data.
|
|
446
|
+
"""
|
|
447
|
+
|