fabric-chaincode-python 2.5.2__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.
- fabric_chaincode_python-2.5.2.dist-info/METADATA +101 -0
- fabric_chaincode_python-2.5.2.dist-info/RECORD +40 -0
- fabric_chaincode_python-2.5.2.dist-info/WHEEL +5 -0
- fabric_chaincode_python-2.5.2.dist-info/licenses/LICENSE +201 -0
- fabric_chaincode_python-2.5.2.dist-info/licenses/NOTICE +18 -0
- fabric_chaincode_python-2.5.2.dist-info/top_level.txt +1 -0
- src/__init__.py +5 -0
- src/fabric_contract_api/__init__.py +51 -0
- src/fabric_contract_api/contractapi/__init__.py +29 -0
- src/fabric_contract_api/contractapi/contract.py +141 -0
- src/fabric_contract_api/contractapi/contract_chaincode.py +552 -0
- src/fabric_contract_api/contractapi/system_contract.py +45 -0
- src/fabric_contract_api/contractapi/transaction_context.py +160 -0
- src/fabric_contract_api/contractapi/utils/__init__.py +4 -0
- src/fabric_contract_api/contractapi/utils/undefined_interface.py +33 -0
- src/fabric_contract_api/internal/__init__.py +16 -0
- src/fabric_contract_api/internal/contract_function.py +453 -0
- src/fabric_contract_api/internal/transaction_handler.py +131 -0
- src/fabric_contract_api/internal/types.py +180 -0
- src/fabric_contract_api/internal/types_handler.py +224 -0
- src/fabric_contract_api/internal/utils.py +59 -0
- src/fabric_contract_api/metadata/__init__.py +32 -0
- src/fabric_contract_api/metadata/metadata.py +456 -0
- src/fabric_contract_api/metadata/schema.py +238 -0
- src/fabric_contract_api/serializer/__init__.py +5 -0
- src/fabric_contract_api/serializer/json_transaction_serializer.py +363 -0
- src/fabric_contract_api/serializer/transaction_serializer.py +45 -0
- src/fabric_shim/__init__.py +2 -0
- src/fabric_shim/chaincode.py +8 -0
- src/fabric_shim/handler.py +336 -0
- src/fabric_shim/interfaces.py +134 -0
- src/fabric_shim/iterators.py +107 -0
- src/fabric_shim/logger.py +1 -0
- src/fabric_shim/logging.py +63 -0
- src/fabric_shim/msg_queue_handler.py +91 -0
- src/fabric_shim/response.py +49 -0
- src/fabric_shim/server.py +155 -0
- src/fabric_shim/stub.py +342 -0
- src/fabric_shim/utils.py +41 -0
- src/version.py +3 -0
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
"""JSON transaction serializer.
|
|
3
|
+
|
|
4
|
+
This is the Python equivalent of Go's ``serializer/json_transaction_serializer.go``.
|
|
5
|
+
It converts string arguments to typed Python values using the standard
|
|
6
|
+
:mod:`json` library, validates them against their JSON-schema (when
|
|
7
|
+
:mod:`jsonschema` is available) and serialises the return value back into a
|
|
8
|
+
JSON string.
|
|
9
|
+
|
|
10
|
+
The serializer supports:
|
|
11
|
+
|
|
12
|
+
* basic scalars (``str``, ``bool``, ``int``, ``float``)
|
|
13
|
+
* ``bytes`` / ``bytearray`` (treated as raw byte strings)
|
|
14
|
+
* :class:`datetime.datetime` and :class:`datetime.date` (RFC 3339)
|
|
15
|
+
* ``list`` / ``typing.List[T]``
|
|
16
|
+
* ``dict`` / ``typing.Dict[str, V]``
|
|
17
|
+
* dataclasses and plain classes (serialised as JSON objects)
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
import dataclasses
|
|
23
|
+
import datetime as _dt
|
|
24
|
+
import json
|
|
25
|
+
import typing as _t
|
|
26
|
+
from typing import Any, Optional, Tuple
|
|
27
|
+
|
|
28
|
+
from ..internal import types as _types
|
|
29
|
+
from ..metadata import ComponentMetadata, ParameterMetadata, ReturnMetadata
|
|
30
|
+
from .transaction_serializer import TransactionSerializer
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class JSONSerializer(TransactionSerializer):
|
|
34
|
+
"""Default JSON transaction serializer.
|
|
35
|
+
|
|
36
|
+
Mirrors Go's ``JSONSerializer``.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
# ------------------------------------------------------------------
|
|
40
|
+
# FromString
|
|
41
|
+
# ------------------------------------------------------------------
|
|
42
|
+
|
|
43
|
+
def from_string(self, value: str, field_type: Any,
|
|
44
|
+
param_metadata: Optional[ParameterMetadata],
|
|
45
|
+
components: Optional[ComponentMetadata]) -> Tuple[Any, Optional[Exception]]:
|
|
46
|
+
try:
|
|
47
|
+
converted = self._convert_arg(field_type, value)
|
|
48
|
+
except Exception as exc:
|
|
49
|
+
return None, exc
|
|
50
|
+
|
|
51
|
+
if param_metadata is not None and param_metadata.compiled_schema is not None:
|
|
52
|
+
err = self._validate_against_schema(
|
|
53
|
+
param_metadata.name, field_type, value,
|
|
54
|
+
converted, param_metadata.compiled_schema,
|
|
55
|
+
)
|
|
56
|
+
if err is not None:
|
|
57
|
+
return None, err
|
|
58
|
+
|
|
59
|
+
return converted, None
|
|
60
|
+
|
|
61
|
+
# ------------------------------------------------------------------
|
|
62
|
+
# ToString
|
|
63
|
+
# ------------------------------------------------------------------
|
|
64
|
+
|
|
65
|
+
def to_string(self, result: Any, result_type: Any,
|
|
66
|
+
returns: Optional[ReturnMetadata],
|
|
67
|
+
components: Optional[ComponentMetadata]) -> Tuple[str, Optional[Exception]]:
|
|
68
|
+
if result is None:
|
|
69
|
+
return "", None
|
|
70
|
+
|
|
71
|
+
# Special-case known types first.
|
|
72
|
+
if result_type is _dt.datetime or isinstance(result, _dt.datetime):
|
|
73
|
+
s = result.strftime("%Y-%m-%dT%H:%M:%S.%fZ") if isinstance(result, _dt.datetime) else str(result)
|
|
74
|
+
if returns is not None and returns.compiled_schema is not None:
|
|
75
|
+
err = self._validate_against_schema(
|
|
76
|
+
"return", result_type, s, result, returns.compiled_schema
|
|
77
|
+
)
|
|
78
|
+
if err is not None:
|
|
79
|
+
return "", err
|
|
80
|
+
return s, None
|
|
81
|
+
|
|
82
|
+
if _types.is_bytes(result_type) or isinstance(result, (bytes, bytearray)):
|
|
83
|
+
try:
|
|
84
|
+
s = result.decode("utf-8") if isinstance(result, (bytes, bytearray)) else str(result)
|
|
85
|
+
except Exception:
|
|
86
|
+
s = str(result)
|
|
87
|
+
if returns is not None and returns.compiled_schema is not None:
|
|
88
|
+
err = self._validate_against_schema(
|
|
89
|
+
"return", result_type, s, s, returns.compiled_schema
|
|
90
|
+
)
|
|
91
|
+
if err is not None:
|
|
92
|
+
return "", err
|
|
93
|
+
return s, None
|
|
94
|
+
|
|
95
|
+
if self._is_marshalling_type(result_type) or self._is_marshalling_type(type(result)):
|
|
96
|
+
try:
|
|
97
|
+
s = self._to_json_string(result)
|
|
98
|
+
except Exception as exc:
|
|
99
|
+
return "", exc
|
|
100
|
+
if returns is not None and returns.compiled_schema is not None:
|
|
101
|
+
err = self._validate_against_schema(
|
|
102
|
+
"return", result_type, s, s, returns.compiled_schema
|
|
103
|
+
)
|
|
104
|
+
if err is not None:
|
|
105
|
+
return "", err
|
|
106
|
+
return s, None
|
|
107
|
+
|
|
108
|
+
# Basic scalar — use the basic-type adapter's serialiser when one
|
|
109
|
+
# is available so that ``bool`` becomes ``"true"``/``"false"`` (not
|
|
110
|
+
# Python's ``"True"``/``"False"``).
|
|
111
|
+
adapter = _types.BasicTypes.get(result_type)
|
|
112
|
+
if adapter is not None and result_type is bool:
|
|
113
|
+
# bool needs JSON-style lowercase.
|
|
114
|
+
s = "true" if result else "false"
|
|
115
|
+
elif adapter is not None and result_type in (int, float):
|
|
116
|
+
s = str(result)
|
|
117
|
+
else:
|
|
118
|
+
s = str(result)
|
|
119
|
+
if returns is not None and returns.compiled_schema is not None:
|
|
120
|
+
err = self._validate_against_schema(
|
|
121
|
+
"return", result_type, s, result, returns.compiled_schema
|
|
122
|
+
)
|
|
123
|
+
if err is not None:
|
|
124
|
+
return "", err
|
|
125
|
+
return s, None
|
|
126
|
+
|
|
127
|
+
# ------------------------------------------------------------------
|
|
128
|
+
# Internal helpers
|
|
129
|
+
# ------------------------------------------------------------------
|
|
130
|
+
|
|
131
|
+
def _convert_arg(self, field_type: Any, param_value: str) -> Any:
|
|
132
|
+
field_type = _strip_optional(field_type)
|
|
133
|
+
|
|
134
|
+
if field_type is _dt.datetime or field_type is _dt.date:
|
|
135
|
+
try:
|
|
136
|
+
return _dt.datetime.fromisoformat(param_value.replace("Z", "+00:00"))
|
|
137
|
+
except ValueError as exc:
|
|
138
|
+
raise ValueError(
|
|
139
|
+
f"value {param_value!r} was not passed in expected format "
|
|
140
|
+
f"{getattr(field_type, '__name__', field_type)}"
|
|
141
|
+
) from exc
|
|
142
|
+
|
|
143
|
+
if _types.is_bytes(field_type):
|
|
144
|
+
return param_value.encode("utf-8")
|
|
145
|
+
|
|
146
|
+
origin = _t.get_origin(field_type)
|
|
147
|
+
if origin in (list, _t.List, tuple, _t.Tuple, dict, _t.Dict):
|
|
148
|
+
try:
|
|
149
|
+
return json.loads(param_value) if param_value != "" else self._empty_for(field_type)
|
|
150
|
+
except json.JSONDecodeError as exc:
|
|
151
|
+
raise ValueError(
|
|
152
|
+
f"value {param_value!r} was not passed in expected format "
|
|
153
|
+
f"{getattr(field_type, '__name__', str(field_type))}"
|
|
154
|
+
) from exc
|
|
155
|
+
|
|
156
|
+
# User-defined classes (dataclasses or plain classes) are JSON-decoded.
|
|
157
|
+
if isinstance(field_type, type) and field_type not in _types.BasicTypes and (
|
|
158
|
+
dataclasses.is_dataclass(field_type) or _is_user_class(field_type)):
|
|
159
|
+
try:
|
|
160
|
+
obj = json.loads(param_value)
|
|
161
|
+
except json.JSONDecodeError as exc:
|
|
162
|
+
raise ValueError(
|
|
163
|
+
f"value {param_value!r} was not passed in expected format "
|
|
164
|
+
f"{field_type.__name__}"
|
|
165
|
+
) from exc
|
|
166
|
+
if isinstance(obj, dict):
|
|
167
|
+
try:
|
|
168
|
+
if dataclasses.is_dataclass(field_type):
|
|
169
|
+
return _dataclass_from_dict(field_type, obj)
|
|
170
|
+
return _plain_class_from_dict(field_type, obj)
|
|
171
|
+
except Exception as exc:
|
|
172
|
+
raise ValueError(
|
|
173
|
+
f"value {param_value!r} was not passed in expected format "
|
|
174
|
+
f"{field_type.__name__}: {exc}"
|
|
175
|
+
) from exc
|
|
176
|
+
return obj
|
|
177
|
+
|
|
178
|
+
if field_type is _t.Any or field_type is object:
|
|
179
|
+
return param_value
|
|
180
|
+
|
|
181
|
+
# Basic scalar
|
|
182
|
+
adapter = _types.BasicTypes.get(field_type)
|
|
183
|
+
if adapter is None:
|
|
184
|
+
raise ValueError(
|
|
185
|
+
f"conversion error. type {field_type!r} is not supported"
|
|
186
|
+
)
|
|
187
|
+
return adapter.convert(param_value)
|
|
188
|
+
|
|
189
|
+
def _empty_for(self, field_type: Any) -> Any:
|
|
190
|
+
origin = _t.get_origin(field_type)
|
|
191
|
+
if origin in (list, _t.List, tuple, _t.Tuple):
|
|
192
|
+
return []
|
|
193
|
+
if origin in (dict, _t.Dict):
|
|
194
|
+
return {}
|
|
195
|
+
return None
|
|
196
|
+
|
|
197
|
+
def _is_marshalling_type(self, tp: Any) -> bool:
|
|
198
|
+
if _types.is_bytes(tp):
|
|
199
|
+
return False
|
|
200
|
+
# Basic scalars (str/int/bool/float) are not "marshalling" types —
|
|
201
|
+
# they get passed straight through as plain strings.
|
|
202
|
+
if tp in _types.BasicTypes:
|
|
203
|
+
return False
|
|
204
|
+
origin = _t.get_origin(tp)
|
|
205
|
+
if origin in (list, _t.List, tuple, _t.Tuple, dict, _t.Dict):
|
|
206
|
+
return True
|
|
207
|
+
if isinstance(tp, type):
|
|
208
|
+
if dataclasses.is_dataclass(tp):
|
|
209
|
+
return True
|
|
210
|
+
# Only user-defined classes are "marshalling" types — builtins
|
|
211
|
+
# such as ``str``/``int`` satisfy ``hasattr(tp, "__dict__")``
|
|
212
|
+
# but must not go through JSON marshalling.
|
|
213
|
+
return _is_user_class(tp)
|
|
214
|
+
return False
|
|
215
|
+
|
|
216
|
+
def _to_json_string(self, value: Any) -> str:
|
|
217
|
+
if dataclasses.is_dataclass(value):
|
|
218
|
+
return json.dumps(_dataclass_to_dict(value), default=_json_default)
|
|
219
|
+
if isinstance(value, (list, tuple)):
|
|
220
|
+
return json.dumps(list(value), default=_json_default)
|
|
221
|
+
if isinstance(value, dict):
|
|
222
|
+
return json.dumps(value, default=_json_default)
|
|
223
|
+
if hasattr(value, "__dict__"):
|
|
224
|
+
d = {k: v for k, v in vars(value).items() if not k.startswith("_") and not callable(v)}
|
|
225
|
+
return json.dumps(d, default=_json_default)
|
|
226
|
+
return json.dumps(value, default=_json_default)
|
|
227
|
+
|
|
228
|
+
def _validate_against_schema(self, prop_name: str, typ: Any,
|
|
229
|
+
string_value: str, obj: Any,
|
|
230
|
+
compiled_schema: Any) -> Optional[Exception]:
|
|
231
|
+
try:
|
|
232
|
+
import jsonschema # type: ignore
|
|
233
|
+
except Exception:
|
|
234
|
+
return None
|
|
235
|
+
|
|
236
|
+
# Build the document to validate. Complex types (lists, dicts,
|
|
237
|
+
# structs) are validated against their JSON-decoded form so that
|
|
238
|
+
# $ref to #/components/schemas/... resolves correctly.
|
|
239
|
+
to_validate: Any
|
|
240
|
+
if isinstance(obj, _dt.datetime):
|
|
241
|
+
to_validate = {prop_name: string_value}
|
|
242
|
+
elif _t.get_origin(typ) in (list, _t.List, tuple, _t.Tuple, dict, _t.Dict):
|
|
243
|
+
try:
|
|
244
|
+
to_validate = {prop_name: json.loads(string_value)}
|
|
245
|
+
except json.JSONDecodeError as exc:
|
|
246
|
+
return exc
|
|
247
|
+
elif isinstance(typ, type) and typ not in _types.BasicTypes and (
|
|
248
|
+
dataclasses.is_dataclass(typ) or _is_user_class(typ)):
|
|
249
|
+
try:
|
|
250
|
+
to_validate = {prop_name: json.loads(string_value)}
|
|
251
|
+
except json.JSONDecodeError as exc:
|
|
252
|
+
return exc
|
|
253
|
+
else:
|
|
254
|
+
to_validate = {prop_name: obj}
|
|
255
|
+
|
|
256
|
+
# ``compiled_schema`` is a jsonschema Validator instance.
|
|
257
|
+
try:
|
|
258
|
+
if isinstance(compiled_schema, dict) and "raw" in compiled_schema:
|
|
259
|
+
# jsonschema wasn't available at compile time — skip
|
|
260
|
+
return None
|
|
261
|
+
errors = list(compiled_schema.iter_errors(to_validate))
|
|
262
|
+
except Exception as exc:
|
|
263
|
+
return exc
|
|
264
|
+
|
|
265
|
+
if errors:
|
|
266
|
+
from ..internal.utils import validate_errors_to_string
|
|
267
|
+
msgs = [f"{err.message} at {list(err.absolute_path)}" for err in errors]
|
|
268
|
+
return ValueError(
|
|
269
|
+
"value did not match schema:\n" + validate_errors_to_string(msgs)
|
|
270
|
+
)
|
|
271
|
+
return None
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
# ---------------------------------------------------------------------------
|
|
275
|
+
# Helpers
|
|
276
|
+
# ---------------------------------------------------------------------------
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
_BASIC_TYPE_NAMES = {"str", "int", "float", "bool", "bytes", "bytearray",
|
|
280
|
+
"list", "dict", "set", "tuple", "object", "NoneType",
|
|
281
|
+
"type"}
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
def _is_user_class(tp: Any) -> bool:
|
|
285
|
+
"""Return ``True`` if *tp* is a user-defined class (not a builtin).
|
|
286
|
+
|
|
287
|
+
Used to avoid the trap where ``str``/``int`` etc. satisfy
|
|
288
|
+
``hasattr(tp, "__dict__")`` because they're classes with class-level
|
|
289
|
+
attributes.
|
|
290
|
+
"""
|
|
291
|
+
if not isinstance(tp, type):
|
|
292
|
+
return False
|
|
293
|
+
if tp.__module__ in ("builtins", "typing", "dataclasses"):
|
|
294
|
+
return False
|
|
295
|
+
return tp.__name__ not in _BASIC_TYPE_NAMES
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def _strip_optional(tp: Any) -> Any:
|
|
299
|
+
origin = _t.get_origin(tp)
|
|
300
|
+
if origin is _t.Union:
|
|
301
|
+
args = [a for a in _t.get_args(tp) if a is not type(None)]
|
|
302
|
+
if len(args) == 1:
|
|
303
|
+
return args[0]
|
|
304
|
+
return tp
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
def _json_default(obj: Any) -> Any:
|
|
308
|
+
if isinstance(obj, _dt.datetime):
|
|
309
|
+
return obj.strftime("%Y-%m-%dT%H:%M:%S.%fZ")
|
|
310
|
+
if isinstance(obj, _dt.date):
|
|
311
|
+
return obj.isoformat()
|
|
312
|
+
if isinstance(obj, (bytes, bytearray)):
|
|
313
|
+
return obj.decode("utf-8", errors="replace")
|
|
314
|
+
if dataclasses.is_dataclass(obj):
|
|
315
|
+
return _dataclass_to_dict(obj)
|
|
316
|
+
if hasattr(obj, "__dict__"):
|
|
317
|
+
return {k: v for k, v in vars(obj).items() if not k.startswith("_") and not callable(v)}
|
|
318
|
+
raise TypeError(f"Object of type {type(obj).__name__} is not JSON serialisable")
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
def _dataclass_to_dict(obj: Any) -> Any:
|
|
322
|
+
out: dict = {}
|
|
323
|
+
for f in dataclasses.fields(obj):
|
|
324
|
+
value = getattr(obj, f.name)
|
|
325
|
+
out[f.name] = value
|
|
326
|
+
return out
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
def _dataclass_from_dict(cls: type, d: dict) -> Any:
|
|
330
|
+
kwargs = {}
|
|
331
|
+
hints = _t.get_type_hints(cls.__init__)
|
|
332
|
+
for f in dataclasses.fields(cls):
|
|
333
|
+
if f.name in d:
|
|
334
|
+
kwargs[f.name] = d[f.name]
|
|
335
|
+
elif f.default is not dataclasses.MISSING:
|
|
336
|
+
continue
|
|
337
|
+
elif f.default_factory is not dataclasses.MISSING: # type: ignore[misc]
|
|
338
|
+
continue
|
|
339
|
+
return cls(**kwargs)
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
def _plain_class_from_dict(cls: type, d: dict) -> Any:
|
|
343
|
+
"""Instantiate *cls* from a dict, mapping keys to ``__init__`` parameters."""
|
|
344
|
+
import inspect
|
|
345
|
+
|
|
346
|
+
sig = inspect.signature(cls.__init__)
|
|
347
|
+
params = sig.parameters
|
|
348
|
+
kwargs: dict = {}
|
|
349
|
+
for pname, param in params.items():
|
|
350
|
+
if pname == "self":
|
|
351
|
+
continue
|
|
352
|
+
if pname in d:
|
|
353
|
+
kwargs[pname] = d[pname]
|
|
354
|
+
elif param.default is not inspect.Parameter.empty:
|
|
355
|
+
continue
|
|
356
|
+
else:
|
|
357
|
+
# If the parameter is required but missing, leave it out and let
|
|
358
|
+
# the constructor raise a meaningful error.
|
|
359
|
+
continue
|
|
360
|
+
return cls(**kwargs)
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
__all__ = ["JSONSerializer"]
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
"""Transaction serializers for :mod:`fabric_contract_api`.
|
|
3
|
+
|
|
4
|
+
A transaction serializer is responsible for converting the string arguments
|
|
5
|
+
that arrive in a Fabric transaction payload into the typed Python values
|
|
6
|
+
expected by a contract function, and for converting the value returned by
|
|
7
|
+
the contract function back into a string that can be sent back to the peer.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import abc
|
|
13
|
+
from typing import Any, Optional, Tuple, Type
|
|
14
|
+
|
|
15
|
+
from ..metadata import ComponentMetadata, ParameterMetadata, ReturnMetadata
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class TransactionSerializer(abc.ABC):
|
|
19
|
+
"""Abstract base for transaction serializers.
|
|
20
|
+
|
|
21
|
+
Mirrors Go's ``serializer.TransactionSerializer`` interface.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
@abc.abstractmethod
|
|
25
|
+
def from_string(self, value: str, field_type: Any,
|
|
26
|
+
param_metadata: Optional[ParameterMetadata],
|
|
27
|
+
components: Optional[ComponentMetadata]) -> Tuple[Any, Optional[Exception]]:
|
|
28
|
+
"""Convert *value* into a Python value of *field_type*.
|
|
29
|
+
|
|
30
|
+
Returns ``(converted_value, None)`` on success or
|
|
31
|
+
``(None, Exception)`` on failure.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
@abc.abstractmethod
|
|
35
|
+
def to_string(self, result: Any, result_type: Any,
|
|
36
|
+
returns: Optional[ReturnMetadata],
|
|
37
|
+
components: Optional[ComponentMetadata]) -> Tuple[str, Optional[Exception]]:
|
|
38
|
+
"""Convert *result* back into a string representation.
|
|
39
|
+
|
|
40
|
+
Returns ``(string, None)`` on success or ``("", Exception)`` on
|
|
41
|
+
failure.
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
__all__ = ["TransactionSerializer"]
|