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.
Files changed (40) hide show
  1. fabric_chaincode_python-2.5.2.dist-info/METADATA +101 -0
  2. fabric_chaincode_python-2.5.2.dist-info/RECORD +40 -0
  3. fabric_chaincode_python-2.5.2.dist-info/WHEEL +5 -0
  4. fabric_chaincode_python-2.5.2.dist-info/licenses/LICENSE +201 -0
  5. fabric_chaincode_python-2.5.2.dist-info/licenses/NOTICE +18 -0
  6. fabric_chaincode_python-2.5.2.dist-info/top_level.txt +1 -0
  7. src/__init__.py +5 -0
  8. src/fabric_contract_api/__init__.py +51 -0
  9. src/fabric_contract_api/contractapi/__init__.py +29 -0
  10. src/fabric_contract_api/contractapi/contract.py +141 -0
  11. src/fabric_contract_api/contractapi/contract_chaincode.py +552 -0
  12. src/fabric_contract_api/contractapi/system_contract.py +45 -0
  13. src/fabric_contract_api/contractapi/transaction_context.py +160 -0
  14. src/fabric_contract_api/contractapi/utils/__init__.py +4 -0
  15. src/fabric_contract_api/contractapi/utils/undefined_interface.py +33 -0
  16. src/fabric_contract_api/internal/__init__.py +16 -0
  17. src/fabric_contract_api/internal/contract_function.py +453 -0
  18. src/fabric_contract_api/internal/transaction_handler.py +131 -0
  19. src/fabric_contract_api/internal/types.py +180 -0
  20. src/fabric_contract_api/internal/types_handler.py +224 -0
  21. src/fabric_contract_api/internal/utils.py +59 -0
  22. src/fabric_contract_api/metadata/__init__.py +32 -0
  23. src/fabric_contract_api/metadata/metadata.py +456 -0
  24. src/fabric_contract_api/metadata/schema.py +238 -0
  25. src/fabric_contract_api/serializer/__init__.py +5 -0
  26. src/fabric_contract_api/serializer/json_transaction_serializer.py +363 -0
  27. src/fabric_contract_api/serializer/transaction_serializer.py +45 -0
  28. src/fabric_shim/__init__.py +2 -0
  29. src/fabric_shim/chaincode.py +8 -0
  30. src/fabric_shim/handler.py +336 -0
  31. src/fabric_shim/interfaces.py +134 -0
  32. src/fabric_shim/iterators.py +107 -0
  33. src/fabric_shim/logger.py +1 -0
  34. src/fabric_shim/logging.py +63 -0
  35. src/fabric_shim/msg_queue_handler.py +91 -0
  36. src/fabric_shim/response.py +49 -0
  37. src/fabric_shim/server.py +155 -0
  38. src/fabric_shim/stub.py +342 -0
  39. src/fabric_shim/utils.py +41 -0
  40. 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"]
@@ -0,0 +1,2 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ __all__ = ["chaincode", "handler", "interfaces", "iterators", "server", "stub"]
@@ -0,0 +1,8 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+
3
+
4
+ # The main API list of Shim is as follows:
5
+ #
6
+ # start(): start the chaincode
7
+ # success(): Create a successful response object
8
+ # error(): create a failure response object