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.
Files changed (39) hide show
  1. cacholong_cli/AsyncTyper.py +17 -0
  2. cacholong_cli/__init__.py +0 -0
  3. cacholong_cli/cli.py +110 -0
  4. cacholong_cli/commands/accounts.py +105 -0
  5. cacholong_cli/commands/addresses.py +280 -0
  6. cacholong_cli/commands/companies.py +100 -0
  7. cacholong_cli/commands/dns_records.py +275 -0
  8. cacholong_cli/commands/dns_templates.py +141 -0
  9. cacholong_cli/commands/dns_zones.py +277 -0
  10. cacholong_cli/commands/products.py +121 -0
  11. cacholong_cli/commands/purchases.py +127 -0
  12. cacholong_cli/common.py +179 -0
  13. cacholong_cli/connection.py +18 -0
  14. cacholong_cloud_cli-0.3.0.dist-info/METADATA +115 -0
  15. cacholong_cloud_cli-0.3.0.dist-info/RECORD +39 -0
  16. cacholong_cloud_cli-0.3.0.dist-info/WHEEL +4 -0
  17. cacholong_cloud_cli-0.3.0.dist-info/entry_points.txt +3 -0
  18. cacholong_sdk/__init__.py +18 -0
  19. cacholong_sdk/account.py +14 -0
  20. cacholong_sdk/address.py +14 -0
  21. cacholong_sdk/api_schema.py +124 -0
  22. cacholong_sdk/common.py +119 -0
  23. cacholong_sdk/company.py +14 -0
  24. cacholong_sdk/connection.py +22 -0
  25. cacholong_sdk/dns_record.py +14 -0
  26. cacholong_sdk/dns_template.py +14 -0
  27. cacholong_sdk/dns_zone.py +14 -0
  28. cacholong_sdk/exception.py +7 -0
  29. cacholong_sdk/product.py +14 -0
  30. cacholong_sdk/purchase.py +14 -0
  31. jsonapi_client/__init__.py +42 -0
  32. jsonapi_client/common.py +186 -0
  33. jsonapi_client/document.py +161 -0
  34. jsonapi_client/exceptions.py +62 -0
  35. jsonapi_client/filter.py +155 -0
  36. jsonapi_client/objects.py +187 -0
  37. jsonapi_client/relationships.py +447 -0
  38. jsonapi_client/resourceobject.py +656 -0
  39. 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
+