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,160 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ """Transaction context for :mod:`fabric_contract_api`.
3
+
4
+ This module is the Python equivalent of Go's
5
+ ``contractapi/transaction_context.go``. It defines:
6
+
7
+ * :class:`TransactionContextInterface` — an :class:`abc.ABC` describing the
8
+ minimum surface area a transaction context must provide (``get_stub`` and
9
+ ``get_client_identity``).
10
+ * :class:`SettableTransactionContextInterface` — an :class:`abc.ABC` that
11
+ adds ``set_stub`` and ``set_client_identity`` so the chaincode dispatcher
12
+ can inject the active :class:`ChaincodeStub` and client identity.
13
+ * :class:`TransactionContext` — a default implementation of both interfaces
14
+ that contracts can embed.
15
+ * :class:`ClientIdentity` — a minimal client-identity wrapper that mirrors
16
+ the surface area of Go's ``cid.ClientIdentity``.
17
+ """
18
+
19
+ from __future__ import annotations
20
+
21
+ import abc
22
+ from typing import Any, Optional
23
+
24
+
25
+ class ClientIdentity:
26
+ """Minimal client-identity implementation.
27
+
28
+ Mirrors the surface area of Go's ``cid.ClientIdentity`` that the contract
29
+ API relies on. Built from the proposal metadata decoded by the shim's
30
+ :class:`ChaincodeStub`.
31
+ """
32
+
33
+ def __init__(self, stub: Any) -> None:
34
+ self._stub = stub
35
+ self._msp_id: Optional[str] = None
36
+ self._cert: Optional[bytes] = None
37
+ self._attrs: dict = {}
38
+ self._loaded = False
39
+
40
+ def _ensure_loaded(self) -> None:
41
+ if self._loaded:
42
+ return
43
+ self._loaded = True
44
+ try:
45
+ creator = self._stub.get_creator()
46
+ if isinstance(creator, dict):
47
+ self._msp_id = creator.get("mspid")
48
+ self._cert = creator.get("idBytes")
49
+ except Exception:
50
+ pass
51
+
52
+ def get_mspid(self) -> str:
53
+ """Return the caller's MSP ID."""
54
+ self._ensure_loaded()
55
+ return self._msp_id or ""
56
+
57
+ def get_id(self) -> str:
58
+ """Return the caller's DN, base64-encoded.
59
+
60
+ Mirrors Go's ``ClientIdentity.GetID``.
61
+ """
62
+ import base64
63
+ self._ensure_loaded()
64
+ if not self._cert:
65
+ raise Exception("Failed to get client identity. Client identity does not have a certificate")
66
+ # Strip PEM headers if present.
67
+ cert = self._cert
68
+ if isinstance(cert, bytes):
69
+ cert_str = cert.decode("utf-8", errors="replace")
70
+ else:
71
+ cert_str = str(cert)
72
+ cert_str = cert_str.replace("-----BEGIN CERTIFICATE-----", "").replace("-----END CERTIFICATE-----", "")
73
+ cert_str = "".join(cert_str.split())
74
+ try:
75
+ raw = base64.b64decode(cert_str)
76
+ except Exception:
77
+ raw = cert_str.encode()
78
+ return base64.b64encode(raw).decode()
79
+
80
+ def get_attribute(self, attr_name: str) -> Optional[str]:
81
+ """Return a single attribute from the caller's certificate."""
82
+ self._ensure_loaded()
83
+ return self._attrs.get(attr_name)
84
+
85
+ def assert_attribute(self, attr_name: str) -> bool:
86
+ """Return ``True`` if *attr_name* exists in the caller's certificate."""
87
+ self._ensure_loaded()
88
+ return attr_name in self._attrs
89
+
90
+ def get_x509(self) -> bytes:
91
+ """Return the raw X.509 certificate bytes of the caller."""
92
+ self._ensure_loaded()
93
+ if not self._cert:
94
+ raise Exception("Client identity does not have a certificate")
95
+ if isinstance(self._cert, str):
96
+ return self._cert.encode()
97
+ return self._cert
98
+
99
+
100
+ class TransactionContextInterface(abc.ABC):
101
+ """Read-only interface for transaction contexts."""
102
+
103
+ @abc.abstractmethod
104
+ def get_stub(self) -> Any:
105
+ """Return the active :class:`ChaincodeStub`."""
106
+
107
+ @abc.abstractmethod
108
+ def get_client_identity(self) -> ClientIdentity:
109
+ """Return the caller's :class:`ClientIdentity`."""
110
+
111
+
112
+ class SettableTransactionContextInterface(TransactionContextInterface):
113
+ """Interface that adds the setters used by the chaincode dispatcher."""
114
+
115
+ @abc.abstractmethod
116
+ def set_stub(self, stub: Any) -> None: ...
117
+
118
+ @abc.abstractmethod
119
+ def set_client_identity(self, ci: ClientIdentity) -> None: ...
120
+
121
+
122
+ class TransactionContext(SettableTransactionContextInterface):
123
+ """Default transaction context.
124
+
125
+ Contracts that don't need a custom transaction context can simply embed
126
+ :class:`TransactionContext`::
127
+
128
+ class MyContract(Contract):
129
+ TransactionContextHandler = TransactionContext
130
+
131
+ def my_transaction(self, ctx: TransactionContextInterface) -> str:
132
+ stub = ctx.get_stub()
133
+ ...
134
+ """
135
+
136
+ def __init__(self) -> None:
137
+ self._stub: Any = None
138
+ self._client_identity: Optional[ClientIdentity] = None
139
+
140
+ def set_stub(self, stub: Any) -> None:
141
+ self._stub = stub
142
+
143
+ def set_client_identity(self, ci: ClientIdentity) -> None:
144
+ self._client_identity = ci
145
+
146
+ def get_stub(self) -> Any:
147
+ return self._stub
148
+
149
+ def get_client_identity(self) -> ClientIdentity:
150
+ if self._client_identity is None:
151
+ self._client_identity = ClientIdentity(self._stub)
152
+ return self._client_identity
153
+
154
+
155
+ __all__ = [
156
+ "ClientIdentity",
157
+ "TransactionContext",
158
+ "TransactionContextInterface",
159
+ "SettableTransactionContextInterface",
160
+ ]
@@ -0,0 +1,4 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ """Utility helpers for :mod:`fabric_contract_api.contractapi`."""
3
+
4
+ from .undefined_interface import UndefinedInterface # noqa: F401
@@ -0,0 +1,33 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ """Sentinel type for the "no return value" case in after-transactions.
3
+
4
+ Mirrors Go's ``contractapi/utils/undefined_interface.go``. When an
5
+ after-transaction function declares a single non-context parameter, the
6
+ contract chaincode passes it an instance of :class:`UndefinedInterface` when
7
+ the named function returned no success value.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+
13
+ class UndefinedInterface:
14
+ """Sentinel type used when a contract function returns no value."""
15
+
16
+ _instance: "UndefinedInterface | None" = None
17
+
18
+ def __new__(cls) -> "UndefinedInterface":
19
+ if cls._instance is None:
20
+ cls._instance = super().__new__(cls)
21
+ return cls._instance
22
+
23
+ def __repr__(self) -> str: # pragma: no cover - cosmetic
24
+ return "UndefinedInterface"
25
+
26
+ def __eq__(self, other: object) -> bool:
27
+ return isinstance(other, UndefinedInterface)
28
+
29
+ def __hash__(self) -> int:
30
+ return hash("UndefinedInterface")
31
+
32
+
33
+ __all__ = ["UndefinedInterface"]
@@ -0,0 +1,16 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ """Internal package for :mod:`fabric_contract_api`.
3
+
4
+ This package mirrors Go's ``internal/`` directory and contains the building
5
+ blocks used by :mod:`fabric_contract_api.contractapi`.
6
+
7
+ To avoid circular imports we intentionally do **not** eagerly re-export the
8
+ sub-modules here — they are imported lazily by the modules that need them.
9
+ """
10
+
11
+ # Sub-modules are imported on demand:
12
+ # from .internal.types import BasicTypes, ErrorType, TimeType, is_bytes
13
+ # from .internal.utils import slice_as_comma_sentence, string_in_slice, validate_errors_to_string
14
+ # from .internal.contract_function import CallType, ContractFunction
15
+ # from .internal.transaction_handler import TransactionHandler, TransactionHandlerType, new_transaction_handler
16
+ # from .internal.types_handler import type_is_valid, type_matches_interface, list_basic_types
@@ -0,0 +1,453 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ """Contract function wrapper.
3
+
4
+ This is the Python equivalent of Go's ``internal/contract_function.go``.
5
+ A :class:`ContractFunction` wraps a Python callable (typically a method of a
6
+ contract class) along with a description of its parameters and return type
7
+ so that it can be called by the :class:`ContractChaincode` dispatcher.
8
+
9
+ Compared to the Go implementation, the Python version does not need to use
10
+ runtime reflection (``reflect``) — instead it uses :mod:`inspect` and
11
+ :mod:`typing` annotations to extract parameter and return types at
12
+ registration time.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import asyncio
18
+ import dataclasses
19
+ import inspect
20
+ import typing as _t
21
+ from typing import Any, Callable, Dict, List, Optional, Tuple
22
+
23
+ from ..metadata import (
24
+ ComponentMetadata,
25
+ ParameterMetadata,
26
+ ReturnMetadata,
27
+ TransactionMetadata,
28
+ )
29
+ from ..metadata.schema import get_schema
30
+ from ..internal.types_handler import type_is_valid, type_matches_interface
31
+ from ..internal.types import ErrorType, TimeType
32
+ from ..internal.utils import slice_as_comma_sentence
33
+ from .types import BasicTypes
34
+
35
+
36
+ # ---------------------------------------------------------------------------
37
+ # CallType enum
38
+ # ---------------------------------------------------------------------------
39
+
40
+
41
+ class CallType:
42
+ """Enum-like constants for the call type of a contract function.
43
+
44
+ Mirrors Go's ``CallType`` constants.
45
+ """
46
+
47
+ NA = 0
48
+ SUBMIT = 1
49
+ EVALUATE = 2
50
+
51
+
52
+ # ---------------------------------------------------------------------------
53
+ # Helpers
54
+ # ---------------------------------------------------------------------------
55
+
56
+
57
+ def _strip_optional(tp: Any) -> Any:
58
+ origin = _t.get_origin(tp)
59
+ if origin is _t.Union:
60
+ args = [a for a in _t.get_args(tp) if a is not type(None)]
61
+ if len(args) == 1:
62
+ return args[0]
63
+ return tp
64
+
65
+
66
+ def _is_context_type(tp: Any, context_handler_type: Any) -> bool:
67
+ """Return ``True`` if *tp* is the transaction-context type.
68
+
69
+ A type is considered the context type when:
70
+ * it equals ``context_handler_type``; or
71
+ * it is an :class:`abc.ABC` protocol and *context_handler_type* structurally
72
+ matches it (mirrors the Go interface check).
73
+ """
74
+ tp = _strip_optional(tp)
75
+ if tp is context_handler_type:
76
+ return True
77
+ # If the parameter type is an abstract base class or a typing.Protocol,
78
+ # check whether the context type implements all of its abstract methods.
79
+ if tp is not None and getattr(tp, "__abstractmethods__", None):
80
+ if type_matches_interface(context_handler_type, tp) is None:
81
+ return True
82
+ return False
83
+
84
+
85
+ def _parse_param(p: inspect.Parameter, method_name: str,
86
+ context_handler_type: Any) -> Tuple[bool, Any]:
87
+ """Parse a single parameter.
88
+
89
+ Returns ``(is_context, type_annotation)``. *type_annotation* is ``None``
90
+ when the parameter is the transaction context (and thus should not be
91
+ passed as a positional arg from the serializer).
92
+ """
93
+ annotation = p.annotation
94
+ if annotation is inspect.Parameter.empty:
95
+ # No annotation — assume it's the context if it's the first
96
+ # parameter named ``ctx``/``context``; otherwise treat as Any.
97
+ if p.name in ("ctx", "context", "ctx_", "transaction_context"):
98
+ return True, context_handler_type
99
+ return False, _t.Any
100
+
101
+ if _is_context_type(annotation, context_handler_type):
102
+ return True, annotation
103
+ return False, annotation
104
+
105
+
106
+ def _parse_method(fn: Callable[..., Any], context_handler_type: Any
107
+ ) -> Tuple[List[Any], bool, Any, bool]:
108
+ """Parse the signature of *fn*.
109
+
110
+ Returns ``(field_types, uses_context, success_type, returns_error)``.
111
+ """
112
+ sig = inspect.signature(fn)
113
+ try:
114
+ hints = _t.get_type_hints(fn)
115
+ except Exception:
116
+ hints = {}
117
+
118
+ field_types: List[Any] = []
119
+ uses_context = False
120
+
121
+ params = list(sig.parameters.values())
122
+ if params and params[0].name == "self":
123
+ params = params[1:]
124
+
125
+ for i, p in enumerate(params):
126
+ annotation = hints.get(p.name, p.annotation)
127
+ if annotation is inspect.Parameter.empty:
128
+ annotation = _t.Any
129
+
130
+ is_ctx, tp = _parse_param_named(p, annotation, context_handler_type)
131
+ if is_ctx:
132
+ if i != 0:
133
+ raise ValueError(
134
+ f"functions requiring the TransactionContext must require it "
135
+ f"as the first parameter. {fn.__name__} takes it as parameter {i}"
136
+ )
137
+ uses_context = True
138
+ continue
139
+
140
+ err = type_is_valid(tp, allow_error=False)
141
+ if err is not None:
142
+ raise ValueError(
143
+ f"{fn.__name__} contains invalid parameter type. {err}"
144
+ )
145
+ field_types.append(tp)
146
+
147
+ return_type = hints.get("return", None)
148
+ success_type, returns_error = _parse_return(return_type, fn.__name__)
149
+
150
+ return field_types, uses_context, success_type, returns_error
151
+
152
+
153
+ def _parse_param_named(p: inspect.Parameter, annotation: Any,
154
+ context_handler_type: Any) -> Tuple[bool, Any]:
155
+ """Like :func:`_parse_param` but uses the resolved annotation."""
156
+ if _is_context_type(annotation, context_handler_type):
157
+ return True, annotation
158
+ return False, annotation
159
+
160
+
161
+ def _parse_return(return_type: Any, method_name: str) -> Tuple[Any, bool]:
162
+ """Parse a return annotation.
163
+
164
+ Returns ``(success_type, returns_error)``. ``success_type`` is ``None``
165
+ when the function does not return a value. ``returns_error`` is ``True``
166
+ when the function declares an error return (a tuple whose last element is
167
+ ``Exception`` / ``BaseException``).
168
+
169
+ The Python contract API supports three idioms for returning errors from a
170
+ contract function:
171
+
172
+ 1. **Raise an exception** — the simplest, most Pythonic form. The
173
+ function's return type annotation is the success type only.
174
+ 2. **Return ``(value, exception_or_None)``** — a Go-style tuple. The
175
+ return annotation is ``Tuple[T, Optional[Exception]]``. When the
176
+ exception is ``None`` the success value is used; otherwise the
177
+ exception is propagated.
178
+ 3. **Return ``Optional[T]`` and raise on ``None``** — equivalent to
179
+ form 1 for type-checking purposes.
180
+ """
181
+ if return_type is None or return_type is inspect.Parameter.empty:
182
+ return None, False
183
+ if return_type is type(None):
184
+ return None, False
185
+
186
+ return_type = _strip_optional(return_type)
187
+ origin = _t.get_origin(return_type)
188
+ args = _t.get_args(return_type)
189
+
190
+ if origin is tuple or origin is _t.Tuple:
191
+ if not args:
192
+ return None, True
193
+ success = args[0]
194
+ if len(args) == 2 and (args[1] is Exception or args[1] is BaseException):
195
+ return success, True
196
+ if len(args) == 2:
197
+ # Treat the second element as an error type if it's a subclass of
198
+ # BaseException, otherwise as a regular success type pair.
199
+ second = args[1]
200
+ if isinstance(second, type) and issubclass(second, BaseException):
201
+ return success, True
202
+ return return_type, False
203
+ return return_type, False
204
+
205
+ if isinstance(return_type, type) and issubclass(return_type, BaseException):
206
+ return None, True
207
+
208
+ return return_type, False
209
+
210
+
211
+ # ---------------------------------------------------------------------------
212
+ # ContractFunction
213
+ # ---------------------------------------------------------------------------
214
+
215
+
216
+ @dataclasses.dataclass
217
+ class _ContractFunctionParams:
218
+ context: Optional[Any] = None # the type annotation of the context, or None
219
+ fields: List[Any] = dataclasses.field(default_factory=list)
220
+
221
+
222
+ @dataclasses.dataclass
223
+ class _ContractFunctionReturns:
224
+ success: Optional[Any] = None
225
+ error: bool = False
226
+
227
+
228
+ class ContractFunction:
229
+ """Wraps a callable so that it can be dispatched by the chaincode.
230
+
231
+ Public attributes mirror Go's ``ContractFunction``:
232
+
233
+ * ``function`` — the underlying callable.
234
+ * ``call_type`` — one of :class:`CallType` constants.
235
+ * ``params`` — a :class:`_ContractFunctionParams` describing the parameters.
236
+ * ``returns`` — a :class:`_ContractFunctionReturns` describing the return.
237
+ """
238
+
239
+ def __init__(self, fn: Callable[..., Any], call_type: int,
240
+ param_details: _ContractFunctionParams,
241
+ return_details: _ContractFunctionReturns) -> None:
242
+ self.function = fn
243
+ self.call_type = call_type
244
+ self.params = param_details
245
+ self.returns = return_details
246
+
247
+ # ------------------------------------------------------------------
248
+ # Construction helpers
249
+ # ------------------------------------------------------------------
250
+
251
+ @classmethod
252
+ def from_callable(cls, fn: Callable[..., Any], call_type: int,
253
+ context_handler_type: Any) -> "ContractFunction":
254
+ """Build a :class:`ContractFunction` from an arbitrary callable.
255
+
256
+ Mirrors Go's ``NewContractFunctionFromFunc``.
257
+ """
258
+ if not callable(fn):
259
+ raise TypeError(
260
+ f"cannot create new contract function from {type(fn).__name__}. "
261
+ "Can only use callable"
262
+ )
263
+ param_details, return_details = cls._parse(fn, context_handler_type)
264
+ return cls(fn, call_type, param_details, return_details)
265
+
266
+ @classmethod
267
+ def from_method(cls, instance: Any, method_name: str, call_type: int,
268
+ context_handler_type: Any) -> "ContractFunction":
269
+ """Build a :class:`ContractFunction` from a method on *instance*.
270
+
271
+ Mirrors Go's ``NewContractFunctionFromReflect``.
272
+ """
273
+ fn = getattr(instance, method_name)
274
+ return cls.from_callable(fn, call_type, context_handler_type)
275
+
276
+ @staticmethod
277
+ def _parse(fn: Callable[..., Any], context_handler_type: Any
278
+ ) -> Tuple[_ContractFunctionParams, _ContractFunctionReturns]:
279
+ try:
280
+ field_types, uses_context, success_type, returns_error = _parse_method(
281
+ fn, context_handler_type
282
+ )
283
+ except ValueError:
284
+ raise
285
+ return (
286
+ _ContractFunctionParams(
287
+ context=context_handler_type if uses_context else None,
288
+ fields=field_types,
289
+ ),
290
+ _ContractFunctionReturns(success=success_type, error=returns_error),
291
+ )
292
+
293
+ # ------------------------------------------------------------------
294
+ # Metadata
295
+ # ------------------------------------------------------------------
296
+
297
+ def reflect_metadata(self, name: str,
298
+ existing_components: ComponentMetadata) -> TransactionMetadata:
299
+ """Return the metadata describing this function.
300
+
301
+ Mirrors Go's ``ContractFunction.ReflectMetadata``.
302
+ """
303
+ tx_metadata = TransactionMetadata(name=name, tag=[])
304
+ tx_type = "SUBMIT"
305
+ tx_type_deprecated = "submit"
306
+ if self.call_type == CallType.EVALUATE:
307
+ tx_type = "EVALUATE"
308
+ tx_type_deprecated = "evaluate"
309
+ tx_metadata.tag = [tx_type_deprecated, tx_type]
310
+
311
+ for index, field in enumerate(self.params.fields):
312
+ schema = get_schema(field, existing_components)
313
+ param = ParameterMetadata(name=f"param{index}", schema=schema)
314
+ tx_metadata.parameters.append(param)
315
+
316
+ if self.returns.success is not None:
317
+ schema = get_schema(self.returns.success, existing_components)
318
+ tx_metadata.returns = ReturnMetadata(schema=schema)
319
+
320
+ return tx_metadata
321
+
322
+ # ------------------------------------------------------------------
323
+ # Calling
324
+ # ------------------------------------------------------------------
325
+
326
+ async def call(self, ctx: Any, supplementary_metadata: Optional[TransactionMetadata],
327
+ components: Optional[ComponentMetadata], serializer, *params: str
328
+ ) -> Tuple[str, Any, Optional[Exception]]:
329
+ """Invoke the wrapped function with the given string parameters.
330
+
331
+ Returns ``(success_string, success_value, error)`` — mirroring Go's
332
+ ``ContractFunction.Call``.
333
+ """
334
+ parameter_metadata: Optional[List[ParameterMetadata]] = None
335
+ if supplementary_metadata is not None:
336
+ parameter_metadata = supplementary_metadata.parameters
337
+
338
+ try:
339
+ values = self._format_args(ctx, parameter_metadata, components,
340
+ list(params), serializer)
341
+ except Exception as exc:
342
+ return "", None, exc
343
+
344
+ # Invoke the underlying function. Both sync and async callables are
345
+ # supported.
346
+ try:
347
+ if inspect.iscoroutinefunction(self.function):
348
+ raw_response = await self.function(*values)
349
+ else:
350
+ raw_response = self.function(*values)
351
+ except Exception as exc:
352
+ return "", None, exc
353
+
354
+ returns_metadata = None
355
+ if supplementary_metadata is not None:
356
+ returns_metadata = supplementary_metadata.returns
357
+
358
+ return self._handle_response(raw_response, returns_metadata, components, serializer)
359
+
360
+ # ------------------------------------------------------------------
361
+ # Argument formatting
362
+ # ------------------------------------------------------------------
363
+
364
+ def _format_args(self, ctx: Any,
365
+ supplementary_metadata: Optional[List[ParameterMetadata]],
366
+ components: Optional[ComponentMetadata],
367
+ params: List[str], serializer) -> List[Any]:
368
+ """Convert the list of string parameters into typed Python values."""
369
+ num_params = len(self.params.fields)
370
+
371
+ if supplementary_metadata is not None and len(supplementary_metadata) != num_params:
372
+ raise ValueError(
373
+ f"incorrect number of params in supplementary metadata. "
374
+ f"Expected {num_params}, received {len(supplementary_metadata)}"
375
+ )
376
+
377
+ values: List[Any] = []
378
+ if self.params.context is not None:
379
+ values.append(ctx)
380
+
381
+ if len(params) < num_params:
382
+ raise ValueError(
383
+ f"incorrect number of params. Expected {num_params}, received {len(params)}"
384
+ )
385
+
386
+ for i in range(num_params):
387
+ field_type = self.params.fields[i]
388
+ param_metadata = supplementary_metadata[i] if supplementary_metadata else None
389
+ value, err = serializer.from_string(
390
+ params[i], field_type, param_metadata, components,
391
+ )
392
+ if err is not None:
393
+ param_name = ""
394
+ if param_metadata is not None:
395
+ param_name = " " + param_metadata.name
396
+ raise ValueError(
397
+ f"error managing parameter{param_name}. {err}"
398
+ ) from err
399
+ values.append(value)
400
+
401
+ return values
402
+
403
+ # ------------------------------------------------------------------
404
+ # Response handling
405
+ # ------------------------------------------------------------------
406
+
407
+ def _handle_response(self, response: Any,
408
+ returns_metadata: Optional[ReturnMetadata],
409
+ components: Optional[ComponentMetadata],
410
+ serializer) -> Tuple[str, Any, Optional[Exception]]:
411
+ """Validate and serialise the function's return value.
412
+
413
+ Supports:
414
+ * no return -> ``("", None, None)``
415
+ * single success value -> ``(stringified, value, None)``
416
+ * Go-style ``(value, exception_or_None)`` tuple -> success + error
417
+ """
418
+ success_response: Any = None
419
+ error_response: Optional[BaseException] = None
420
+
421
+ if self.returns.error and isinstance(response, tuple) and len(response) == 2:
422
+ success_response = response[0]
423
+ err = response[1]
424
+ if isinstance(err, BaseException):
425
+ error_response = err
426
+ elif err is not None:
427
+ # The "error" slot was a non-exception — treat as a success-only return.
428
+ success_response = response
429
+ else:
430
+ success_response = response
431
+
432
+ success_string = ""
433
+ iface: Any = None
434
+
435
+ if self.returns.success is not None and success_response is not None:
436
+ if serializer is not None:
437
+ s, err = serializer.to_string(
438
+ success_response, self.returns.success,
439
+ returns_metadata, components,
440
+ )
441
+ if err is not None:
442
+ wrapped = ValueError(
443
+ f"error handling success response. {err}"
444
+ )
445
+ wrapped.__cause__ = err
446
+ return "", None, wrapped
447
+ success_string = s
448
+ iface = success_response
449
+
450
+ return success_string, iface, error_response
451
+
452
+
453
+ __all__ = ["CallType", "ContractFunction"]