Ayon-Python-Api 0.1.13__tar.gz → 0.1.15__tar.gz
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.
- ayon-python-api-0.1.15/PKG-INFO +72 -0
- ayon-python-api-0.1.15/README.md +56 -0
- {ayon-python-api-0.1.13 → ayon-python-api-0.1.15}/ayon_api/__init__.py +2 -0
- {ayon-python-api-0.1.13 → ayon-python-api-0.1.15}/ayon_api/_api.py +5 -0
- {ayon-python-api-0.1.13 → ayon-python-api-0.1.15}/ayon_api/constants.py +1 -1
- {ayon-python-api-0.1.13 → ayon-python-api-0.1.15}/ayon_api/entity_hub.py +18 -3
- {ayon-python-api-0.1.13 → ayon-python-api-0.1.15}/ayon_api/operations.py +18 -151
- {ayon-python-api-0.1.13 → ayon-python-api-0.1.15}/ayon_api/server_api.py +741 -42
- {ayon-python-api-0.1.13 → ayon-python-api-0.1.15}/ayon_api/utils.py +7 -1
- {ayon-python-api-0.1.13 → ayon-python-api-0.1.15}/ayon_api/version.py +1 -1
- ayon-python-api-0.1.15/ayon_python_api.egg-info/PKG-INFO +72 -0
- {ayon-python-api-0.1.13 → ayon-python-api-0.1.15}/pyproject.toml +1 -1
- ayon-python-api-0.1.13/PKG-INFO +0 -31
- ayon-python-api-0.1.13/README.md +0 -15
- ayon-python-api-0.1.13/ayon_python_api.egg-info/PKG-INFO +0 -31
- {ayon-python-api-0.1.13 → ayon-python-api-0.1.15}/LICENSE +0 -0
- {ayon-python-api-0.1.13 → ayon-python-api-0.1.15}/ayon_api/events.py +0 -0
- {ayon-python-api-0.1.13 → ayon-python-api-0.1.15}/ayon_api/exceptions.py +0 -0
- {ayon-python-api-0.1.13 → ayon-python-api-0.1.15}/ayon_api/graphql.py +0 -0
- {ayon-python-api-0.1.13 → ayon-python-api-0.1.15}/ayon_api/graphql_queries.py +0 -0
- {ayon-python-api-0.1.13 → ayon-python-api-0.1.15}/ayon_api/thumbnails.py +0 -0
- {ayon-python-api-0.1.13 → ayon-python-api-0.1.15}/ayon_python_api.egg-info/SOURCES.txt +0 -0
- {ayon-python-api-0.1.13 → ayon-python-api-0.1.15}/ayon_python_api.egg-info/dependency_links.txt +0 -0
- {ayon-python-api-0.1.13 → ayon-python-api-0.1.15}/ayon_python_api.egg-info/requires.txt +0 -0
- {ayon-python-api-0.1.13 → ayon-python-api-0.1.15}/ayon_python_api.egg-info/top_level.txt +0 -0
- {ayon-python-api-0.1.13 → ayon-python-api-0.1.15}/setup.cfg +0 -0
- {ayon-python-api-0.1.13 → ayon-python-api-0.1.15}/setup.py +0 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: ayon-python-api
|
|
3
|
+
Version: 0.1.15
|
|
4
|
+
Summary: AYON Python API
|
|
5
|
+
Home-page: https://github.com/ynput/ayon-python-api
|
|
6
|
+
Author: ynput.io
|
|
7
|
+
Author-email: info@ynput.io
|
|
8
|
+
License: Apache License (2.0)
|
|
9
|
+
Keywords: AYON,ynput,OpenPype,vfx
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Programming Language :: Python
|
|
12
|
+
Classifier: Programming Language :: Python :: 2
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
License-File: LICENSE
|
|
16
|
+
|
|
17
|
+
# AYON server API
|
|
18
|
+
Python client for connection server. Client must be (at least for some time) Python 2 compatible because will be used in DCC that are "older".
|
|
19
|
+
|
|
20
|
+
AYON Python api should support connection to server with raw REST functions and prepared functionality for work with entities. Must not contain only functionality that can be used with core server functionality.
|
|
21
|
+
|
|
22
|
+
Module support singleton connection which is using `AYON_SERVER_URL` and `AYON_TOKEN` environment variables as source for connection. The singleton connection is using `ServerAPI` object. There can be created multiple connection to different server at one time, for that purpose use `ServerAPIBase` object.
|
|
23
|
+
|
|
24
|
+
## Install
|
|
25
|
+
AYON python api is available on PyPi:
|
|
26
|
+
|
|
27
|
+
pip install ayon-python-api
|
|
28
|
+
|
|
29
|
+
For development purposes you may follow [build](#build-wheel) guide to build and install custom wheels.
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
## Cloning the repository
|
|
33
|
+
Repository does not have submodules or special cases. Clone is simple as:
|
|
34
|
+
|
|
35
|
+
git clone git@github.com:ynput/ayon-python-api.git
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
## Build wheel
|
|
39
|
+
For wheel build is required a `wheel` module from PyPi:
|
|
40
|
+
|
|
41
|
+
pip install wheel
|
|
42
|
+
|
|
43
|
+
Open terminal and change directory to ayon-python-api repository and build wheel:
|
|
44
|
+
|
|
45
|
+
cd <REPOSITORY ROOT>/ayon-python-api
|
|
46
|
+
python setup.py sdist bdist_wheel
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
Once finished a wheel should be created in `./dist/ayon_python_api-<VERSION>-py3-none-any`.
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
### Wheel installation
|
|
54
|
+
The wheel file can be used to install using pip:
|
|
55
|
+
|
|
56
|
+
pip install <REPOSITORY ROOT>/dist/ayon_python_api-<VERSION>-py3-none-any
|
|
57
|
+
|
|
58
|
+
If pip complain that `ayon-python-api` is already installed just uninstall existing one first:
|
|
59
|
+
|
|
60
|
+
pip uninstall ayon-python-api
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
## TODOs
|
|
64
|
+
- Find more suitable name of `ServerAPI` objects (right now is used `con` or `connection`)
|
|
65
|
+
- Add all available CRUD operation on entities using REST
|
|
66
|
+
- Add folder and task changes to operations
|
|
67
|
+
- Enhance entity hub
|
|
68
|
+
- Entity hub should use operations session to do changes
|
|
69
|
+
- Entity hub could also handle 'subset', 'version' and 'representation' entities
|
|
70
|
+
- Missing docstrings in EntityHub -> especially entity arguments are missing
|
|
71
|
+
- Pass docstrings and arguments definitions from `ServerAPI` methods to global functions
|
|
72
|
+
- Missing websockets connection
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# AYON server API
|
|
2
|
+
Python client for connection server. Client must be (at least for some time) Python 2 compatible because will be used in DCC that are "older".
|
|
3
|
+
|
|
4
|
+
AYON Python api should support connection to server with raw REST functions and prepared functionality for work with entities. Must not contain only functionality that can be used with core server functionality.
|
|
5
|
+
|
|
6
|
+
Module support singleton connection which is using `AYON_SERVER_URL` and `AYON_TOKEN` environment variables as source for connection. The singleton connection is using `ServerAPI` object. There can be created multiple connection to different server at one time, for that purpose use `ServerAPIBase` object.
|
|
7
|
+
|
|
8
|
+
## Install
|
|
9
|
+
AYON python api is available on PyPi:
|
|
10
|
+
|
|
11
|
+
pip install ayon-python-api
|
|
12
|
+
|
|
13
|
+
For development purposes you may follow [build](#build-wheel) guide to build and install custom wheels.
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
## Cloning the repository
|
|
17
|
+
Repository does not have submodules or special cases. Clone is simple as:
|
|
18
|
+
|
|
19
|
+
git clone git@github.com:ynput/ayon-python-api.git
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
## Build wheel
|
|
23
|
+
For wheel build is required a `wheel` module from PyPi:
|
|
24
|
+
|
|
25
|
+
pip install wheel
|
|
26
|
+
|
|
27
|
+
Open terminal and change directory to ayon-python-api repository and build wheel:
|
|
28
|
+
|
|
29
|
+
cd <REPOSITORY ROOT>/ayon-python-api
|
|
30
|
+
python setup.py sdist bdist_wheel
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
Once finished a wheel should be created in `./dist/ayon_python_api-<VERSION>-py3-none-any`.
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
### Wheel installation
|
|
38
|
+
The wheel file can be used to install using pip:
|
|
39
|
+
|
|
40
|
+
pip install <REPOSITORY ROOT>/dist/ayon_python_api-<VERSION>-py3-none-any
|
|
41
|
+
|
|
42
|
+
If pip complain that `ayon-python-api` is already installed just uninstall existing one first:
|
|
43
|
+
|
|
44
|
+
pip uninstall ayon-python-api
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
## TODOs
|
|
48
|
+
- Find more suitable name of `ServerAPI` objects (right now is used `con` or `connection`)
|
|
49
|
+
- Add all available CRUD operation on entities using REST
|
|
50
|
+
- Add folder and task changes to operations
|
|
51
|
+
- Enhance entity hub
|
|
52
|
+
- Entity hub should use operations session to do changes
|
|
53
|
+
- Entity hub could also handle 'subset', 'version' and 'representation' entities
|
|
54
|
+
- Missing docstrings in EntityHub -> especially entity arguments are missing
|
|
55
|
+
- Pass docstrings and arguments definitions from `ServerAPI` methods to global functions
|
|
56
|
+
- Missing websockets connection
|
|
@@ -119,6 +119,7 @@ from ._api import (
|
|
|
119
119
|
get_representations,
|
|
120
120
|
get_representations_parents,
|
|
121
121
|
get_representation_parents,
|
|
122
|
+
get_repre_ids_by_context_filters,
|
|
122
123
|
|
|
123
124
|
create_thumbnail,
|
|
124
125
|
get_thumbnail,
|
|
@@ -245,6 +246,7 @@ __all__ = (
|
|
|
245
246
|
"get_representations",
|
|
246
247
|
"get_representations_parents",
|
|
247
248
|
"get_representation_parents",
|
|
249
|
+
"get_repre_ids_by_context_filters",
|
|
248
250
|
|
|
249
251
|
"create_thumbnail",
|
|
250
252
|
"get_thumbnail",
|
|
@@ -751,6 +751,11 @@ def get_representations_parents(*args, **kwargs):
|
|
|
751
751
|
return con.get_representations_parents(*args, **kwargs)
|
|
752
752
|
|
|
753
753
|
|
|
754
|
+
def get_repre_ids_by_context_filters(*args, **kwargs):
|
|
755
|
+
con = get_server_api_connection()
|
|
756
|
+
return con.get_repre_ids_by_context_filters(*args, **kwargs)
|
|
757
|
+
|
|
758
|
+
|
|
754
759
|
def create_project(
|
|
755
760
|
project_name,
|
|
756
761
|
project_code,
|
|
@@ -877,9 +877,7 @@ class BaseEntity(object):
|
|
|
877
877
|
if created is None:
|
|
878
878
|
created = entity_id is None
|
|
879
879
|
|
|
880
|
-
entity_id =
|
|
881
|
-
if entity_id is None:
|
|
882
|
-
entity_id = create_entity_id()
|
|
880
|
+
entity_id = self._prepare_entity_id(entity_id)
|
|
883
881
|
|
|
884
882
|
if data is None:
|
|
885
883
|
data = {}
|
|
@@ -924,6 +922,12 @@ class BaseEntity(object):
|
|
|
924
922
|
def __setitem__(self, item, value):
|
|
925
923
|
return setattr(self, item, value)
|
|
926
924
|
|
|
925
|
+
def _prepare_entity_id(self, entity_id):
|
|
926
|
+
entity_id = convert_entity_id(entity_id)
|
|
927
|
+
if entity_id is None:
|
|
928
|
+
entity_id = create_entity_id()
|
|
929
|
+
return entity_id
|
|
930
|
+
|
|
927
931
|
@property
|
|
928
932
|
def id(self):
|
|
929
933
|
"""Access to entity id under which is entity available on server.
|
|
@@ -1347,6 +1351,13 @@ class ProjectEntity(BaseEntity):
|
|
|
1347
1351
|
self._orig_folder_types = copy.deepcopy(folder_types)
|
|
1348
1352
|
self._orig_task_types = copy.deepcopy(task_types)
|
|
1349
1353
|
|
|
1354
|
+
def _prepare_entity_id(self, entity_id):
|
|
1355
|
+
if entity_id != self.project_name:
|
|
1356
|
+
raise ValueError(
|
|
1357
|
+
"Unexpected entity id value \"{}\". Expected \"{}\"".format(
|
|
1358
|
+
entity_id, self.project_name))
|
|
1359
|
+
return entity_id
|
|
1360
|
+
|
|
1350
1361
|
def get_parent(self, *args, **kwargs):
|
|
1351
1362
|
return None
|
|
1352
1363
|
|
|
@@ -1426,6 +1437,10 @@ class FolderEntity(BaseEntity):
|
|
|
1426
1437
|
|
|
1427
1438
|
def __init__(self, folder_type, *args, label=None, path=None, **kwargs):
|
|
1428
1439
|
super(FolderEntity, self).__init__(*args, **kwargs)
|
|
1440
|
+
# Autofill project as parent of folder if is not yet set
|
|
1441
|
+
# - this can be guessed only if folder was just created
|
|
1442
|
+
if self.created and self._parent_id is UNKNOWN_VALUE:
|
|
1443
|
+
self._parent_id = self.project_name
|
|
1429
1444
|
|
|
1430
1445
|
self._folder_type = folder_type
|
|
1431
1446
|
self._label = label
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import copy
|
|
2
2
|
import collections
|
|
3
3
|
import uuid
|
|
4
|
-
import datetime
|
|
5
4
|
from abc import ABCMeta, abstractproperty
|
|
6
5
|
|
|
7
6
|
import six
|
|
@@ -137,13 +136,20 @@ def new_version_entity(
|
|
|
137
136
|
if data is None:
|
|
138
137
|
data = {}
|
|
139
138
|
|
|
140
|
-
|
|
139
|
+
output = {
|
|
141
140
|
"id": _create_or_convert_to_id(entity_id),
|
|
142
141
|
"version": int(version),
|
|
143
142
|
"subsetId": _create_or_convert_to_id(subset_id),
|
|
144
143
|
"attrib": attribs,
|
|
145
144
|
"data": data
|
|
146
145
|
}
|
|
146
|
+
if task_id:
|
|
147
|
+
output["taskId"] = task_id
|
|
148
|
+
if thumbnail_id:
|
|
149
|
+
output["thumbnailId"] = thumbnail_id
|
|
150
|
+
if author:
|
|
151
|
+
output["author"] = author
|
|
152
|
+
return output
|
|
147
153
|
|
|
148
154
|
|
|
149
155
|
def new_hero_version_entity(
|
|
@@ -180,13 +186,20 @@ def new_hero_version_entity(
|
|
|
180
186
|
if data is None:
|
|
181
187
|
data = {}
|
|
182
188
|
|
|
183
|
-
|
|
189
|
+
output = {
|
|
184
190
|
"id": _create_or_convert_to_id(entity_id),
|
|
185
191
|
"version": -abs(int(version)),
|
|
186
192
|
"subsetId": subset_id,
|
|
187
193
|
"attrib": attribs,
|
|
188
194
|
"data": data
|
|
189
195
|
}
|
|
196
|
+
if task_id:
|
|
197
|
+
output["taskId"] = task_id
|
|
198
|
+
if thumbnail_id:
|
|
199
|
+
output["thumbnailId"] = thumbnail_id
|
|
200
|
+
if author:
|
|
201
|
+
output["author"] = author
|
|
202
|
+
return output
|
|
190
203
|
|
|
191
204
|
|
|
192
205
|
def new_representation_entity(
|
|
@@ -198,7 +211,6 @@ def new_representation_entity(
|
|
|
198
211
|
name (str): Representation name considered as unique identifier
|
|
199
212
|
of representation under version.
|
|
200
213
|
version_id (str): Id of parent version.
|
|
201
|
-
context (Dict[str, Any]): Representation context used to fill template.
|
|
202
214
|
attribs (Dict[str, Any]): Explicitly set attributes of representation.
|
|
203
215
|
data (Dict[str, Any]): Representation entity data.
|
|
204
216
|
entity_id (str): Predefined id of entity. New id is created
|
|
@@ -236,6 +248,8 @@ def new_workfile_info_doc(
|
|
|
236
248
|
task_name (str): Task under which was workfile created.
|
|
237
249
|
files (List[str]): List of rootless filepaths related to workfile.
|
|
238
250
|
data (Dict[str, Any]): Additional metadata.
|
|
251
|
+
entity_id (str): Predefined id of entity. New id is created
|
|
252
|
+
if not passed.
|
|
239
253
|
|
|
240
254
|
Returns:
|
|
241
255
|
Dict[str, Any]: Skeleton of workfile info entity.
|
|
@@ -254,153 +268,6 @@ def new_workfile_info_doc(
|
|
|
254
268
|
}
|
|
255
269
|
|
|
256
270
|
|
|
257
|
-
def _prepare_update_data(old_doc, new_doc, replace):
|
|
258
|
-
changes = {}
|
|
259
|
-
for key, value in new_doc.items():
|
|
260
|
-
if key not in old_doc or value != old_doc[key]:
|
|
261
|
-
changes[key] = value
|
|
262
|
-
|
|
263
|
-
if replace:
|
|
264
|
-
for key in old_doc.keys():
|
|
265
|
-
if key not in new_doc:
|
|
266
|
-
changes[key] = REMOVED_VALUE
|
|
267
|
-
return changes
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
def prepare_folder_update_data(old_doc, new_doc, replace=True):
|
|
271
|
-
"""Compare two folder entities and prepare update data.
|
|
272
|
-
|
|
273
|
-
Based on compared values will create update data for
|
|
274
|
-
'UpdateOperation'.
|
|
275
|
-
|
|
276
|
-
Empty output means that entities data are identical.
|
|
277
|
-
|
|
278
|
-
Returns:
|
|
279
|
-
Dict[str, Any]: Changes between old and new entity.
|
|
280
|
-
"""
|
|
281
|
-
|
|
282
|
-
# changes = {}
|
|
283
|
-
# for key, value in new_doc.items():
|
|
284
|
-
# if key in ("data", ):
|
|
285
|
-
# continue
|
|
286
|
-
#
|
|
287
|
-
# if key not in old_doc or value != old_doc[key]:
|
|
288
|
-
# changes[key] = value
|
|
289
|
-
#
|
|
290
|
-
# return _prepare_update_data(old_doc, new_doc, replace)
|
|
291
|
-
|
|
292
|
-
raise NotImplementedError(
|
|
293
|
-
"'prepare_folder_update_data' not yet implemented"
|
|
294
|
-
)
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
def prepare_subset_update_data(old_doc, new_doc, replace=True):
|
|
298
|
-
"""Compare two subset entities and prepare update data.
|
|
299
|
-
|
|
300
|
-
Based on compared values will create update data for
|
|
301
|
-
'UpdateOperation'.
|
|
302
|
-
|
|
303
|
-
Empty output means that entities are identical.
|
|
304
|
-
|
|
305
|
-
Returns:
|
|
306
|
-
Dict[str, Any]: Changes between old and new entity.
|
|
307
|
-
"""
|
|
308
|
-
|
|
309
|
-
# return _prepare_update_data(old_doc, new_doc, replace)
|
|
310
|
-
|
|
311
|
-
raise NotImplementedError(
|
|
312
|
-
"'prepare_folder_update_data' not yet implemented"
|
|
313
|
-
)
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
def prepare_version_update_data(old_doc, new_doc, replace=True):
|
|
317
|
-
"""Compare two version entities and prepare update data.
|
|
318
|
-
|
|
319
|
-
Based on compared values will create update data for
|
|
320
|
-
'UpdateOperation'.
|
|
321
|
-
|
|
322
|
-
Empty output means that entities are identical.
|
|
323
|
-
|
|
324
|
-
Returns:
|
|
325
|
-
Dict[str, Any]: Changes between old and new entity.
|
|
326
|
-
"""
|
|
327
|
-
|
|
328
|
-
# return _prepare_update_data(old_doc, new_doc, replace)
|
|
329
|
-
|
|
330
|
-
raise NotImplementedError(
|
|
331
|
-
"'prepare_folder_update_data' not yet implemented"
|
|
332
|
-
)
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
def prepare_hero_version_update_data(old_doc, new_doc, replace=True):
|
|
336
|
-
"""Compare two hero version entities and prepare update data.
|
|
337
|
-
|
|
338
|
-
Based on compared values will create update data for 'UpdateOperation'.
|
|
339
|
-
|
|
340
|
-
Empty output means that entities are identical.
|
|
341
|
-
|
|
342
|
-
Returns:
|
|
343
|
-
Dict[str, Any]: Changes between old and new entity.
|
|
344
|
-
"""
|
|
345
|
-
|
|
346
|
-
# return _prepare_update_data(old_doc, new_doc, replace)
|
|
347
|
-
|
|
348
|
-
raise NotImplementedError(
|
|
349
|
-
"'prepare_folder_update_data' not yet implemented"
|
|
350
|
-
)
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
def prepare_representation_update_data(old_doc, new_doc, replace=True):
|
|
354
|
-
"""Compare two representation entities and prepare update data.
|
|
355
|
-
|
|
356
|
-
Based on compared values will create update data for
|
|
357
|
-
'UpdateOperation'.
|
|
358
|
-
|
|
359
|
-
Empty output means that entities are identical.
|
|
360
|
-
|
|
361
|
-
Returns:
|
|
362
|
-
Dict[str, Any]: Changes between old and new entity.
|
|
363
|
-
"""
|
|
364
|
-
|
|
365
|
-
# return _prepare_update_data(old_doc, new_doc, replace)
|
|
366
|
-
|
|
367
|
-
raise NotImplementedError(
|
|
368
|
-
"'prepare_folder_update_data' not yet implemented"
|
|
369
|
-
)
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
def prepare_workfile_info_update_data(old_doc, new_doc, replace=True):
|
|
373
|
-
"""Compare two workfile info entities and prepare update data.
|
|
374
|
-
|
|
375
|
-
Based on compared values will create update data for
|
|
376
|
-
'UpdateOperation'.
|
|
377
|
-
|
|
378
|
-
Empty output means that entities are identical.
|
|
379
|
-
|
|
380
|
-
Returns:
|
|
381
|
-
Dict[str, Any]: Changes between old and new entity.
|
|
382
|
-
"""
|
|
383
|
-
|
|
384
|
-
# return _prepare_update_data(old_doc, new_doc, replace)
|
|
385
|
-
|
|
386
|
-
raise NotImplementedError(
|
|
387
|
-
"'prepare_folder_update_data' not yet implemented"
|
|
388
|
-
)
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
def entity_data_json_default(value):
|
|
392
|
-
if isinstance(value, datetime.datetime):
|
|
393
|
-
return int(value.timestamp())
|
|
394
|
-
|
|
395
|
-
raise TypeError(
|
|
396
|
-
"Object of type {} is not JSON serializable".format(str(type(value)))
|
|
397
|
-
)
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
def failed_json_default(value):
|
|
401
|
-
return "< Failed value {} > {}".format(type(value), str(value))
|
|
402
|
-
|
|
403
|
-
|
|
404
271
|
@six.add_metaclass(ABCMeta)
|
|
405
272
|
class AbstractOperation(object):
|
|
406
273
|
"""Base operation class.
|