algokit-utils 5.0.0a3__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.
- algokit_abi/__init__.py +9 -0
- algokit_abi/_arc32_to_arc56.py +242 -0
- algokit_abi/_arc56_serde.py +161 -0
- algokit_abi/abi.py +667 -0
- algokit_abi/arc32.py +210 -0
- algokit_abi/arc56.py +821 -0
- algokit_abi/py.typed +0 -0
- algokit_algo25/__init__.py +38 -0
- algokit_algo25/_encoding.py +46 -0
- algokit_algo25/_wordlist.py +2065 -0
- algokit_algo25/exceptions.py +29 -0
- algokit_algo25/mnemonic.py +128 -0
- algokit_algo25/py.typed +0 -0
- algokit_algod_client/__init__.py +10 -0
- algokit_algod_client/client.py +1585 -0
- algokit_algod_client/config.py +36 -0
- algokit_algod_client/exceptions.py +59 -0
- algokit_algod_client/models/__init__.py +229 -0
- algokit_algod_client/models/_account.py +150 -0
- algokit_algod_client/models/_account_application_response.py +25 -0
- algokit_algod_client/models/_account_asset_response.py +25 -0
- algokit_algod_client/models/_account_participation.py +53 -0
- algokit_algod_client/models/_account_state_delta.py +30 -0
- algokit_algod_client/models/_allocations_for_genesis_file.py +23 -0
- algokit_algod_client/models/_allocations_for_genesis_file_state_model.py +42 -0
- algokit_algod_client/models/_application.py +23 -0
- algokit_algod_client/models/_application_initial_states.py +37 -0
- algokit_algod_client/models/_application_kvstorage.py +29 -0
- algokit_algod_client/models/_application_local_state.py +33 -0
- algokit_algod_client/models/_application_params.py +63 -0
- algokit_algod_client/models/_application_state_operation.py +41 -0
- algokit_algod_client/models/_application_state_schema.py +22 -0
- algokit_algod_client/models/_asset.py +23 -0
- algokit_algod_client/models/_asset_holding.py +29 -0
- algokit_algod_client/models/_asset_params.py +102 -0
- algokit_algod_client/models/_avm_key_value.py +28 -0
- algokit_algod_client/models/_avm_value.py +32 -0
- algokit_algod_client/models/_block.py +363 -0
- algokit_algod_client/models/_block_hash_response.py +14 -0
- algokit_algod_client/models/_block_txids_response.py +14 -0
- algokit_algod_client/models/_box.py +36 -0
- algokit_algod_client/models/_box_descriptor.py +24 -0
- algokit_algod_client/models/_boxes_response.py +21 -0
- algokit_algod_client/models/_build_version_contains_the_current_algod_build_version_information.py +34 -0
- algokit_algod_client/models/_compile_response.py +24 -0
- algokit_algod_client/models/_disassemble_response.py +14 -0
- algokit_algod_client/models/_error_response.py +22 -0
- algokit_algod_client/models/_eval_delta.py +32 -0
- algokit_algod_client/models/_eval_delta_key_value.py +28 -0
- algokit_algod_client/models/_genesis_file_in_json.py +53 -0
- algokit_algod_client/models/_get_block_time_stamp_offset_response.py +14 -0
- algokit_algod_client/models/_get_sync_round_response.py +14 -0
- algokit_algod_client/models/_ledger_state_delta.py +389 -0
- algokit_algod_client/models/_light_block_header_proof.py +32 -0
- algokit_algod_client/models/_node_status_response.py +118 -0
- algokit_algod_client/models/_pending_transaction_response.py +91 -0
- algokit_algod_client/models/_pending_transactions_response.py +29 -0
- algokit_algod_client/models/_post_transactions_response.py +14 -0
- algokit_algod_client/models/_scratch_change.py +23 -0
- algokit_algod_client/models/_serde_helpers.py +241 -0
- algokit_algod_client/models/_simulate_initial_states.py +25 -0
- algokit_algod_client/models/_simulate_request.py +54 -0
- algokit_algod_client/models/_simulate_request_transaction_group.py +25 -0
- algokit_algod_client/models/_simulate_response.py +44 -0
- algokit_algod_client/models/_simulate_trace_config.py +30 -0
- algokit_algod_client/models/_simulate_transaction_group_result.py +46 -0
- algokit_algod_client/models/_simulate_transaction_result.py +41 -0
- algokit_algod_client/models/_simulate_unnamed_resources_accessed.py +64 -0
- algokit_algod_client/models/_simulation_eval_overrides.py +40 -0
- algokit_algod_client/models/_simulation_opcode_trace_unit.py +55 -0
- algokit_algod_client/models/_simulation_transaction_exec_trace.py +82 -0
- algokit_algod_client/models/_source_map.py +30 -0
- algokit_algod_client/models/_state_delta.py +6 -0
- algokit_algod_client/models/_state_proof.py +28 -0
- algokit_algod_client/models/_state_proof_message.py +44 -0
- algokit_algod_client/models/_supply_response.py +26 -0
- algokit_algod_client/models/_teal_key_value.py +28 -0
- algokit_algod_client/models/_teal_key_value_store.py +6 -0
- algokit_algod_client/models/_teal_value.py +32 -0
- algokit_algod_client/models/_transaction_group_ledger_state_deltas_for_round_response.py +21 -0
- algokit_algod_client/models/_transaction_parameters_response.py +45 -0
- algokit_algod_client/models/_transaction_proof.py +44 -0
- algokit_algod_client/models/_version_contains_the_current_algod_version.py +38 -0
- algokit_algod_client/models/suggested_params.py +42 -0
- algokit_algod_client/py.typed +1 -0
- algokit_algod_client/types.py +7 -0
- algokit_algosdk/__init__.py +38 -0
- algokit_algosdk/account.py +32 -0
- algokit_algosdk/app_access.py +228 -0
- algokit_algosdk/box_reference.py +100 -0
- algokit_algosdk/constants.py +147 -0
- algokit_algosdk/encoding.py +89 -0
- algokit_algosdk/error.py +180 -0
- algokit_algosdk/logic.py +61 -0
- algokit_algosdk/logicsig.py +218 -0
- algokit_algosdk/mnemonic.py +216 -0
- algokit_algosdk/multisig.py +161 -0
- algokit_algosdk/py.typed +0 -0
- algokit_algosdk/transaction.py +596 -0
- algokit_algosdk/wordlist.py +2054 -0
- algokit_common/__init__.py +50 -0
- algokit_common/address.py +34 -0
- algokit_common/constants.py +47 -0
- algokit_common/hashing.py +25 -0
- algokit_common/py.typed +0 -0
- algokit_common/serde/__init__.py +40 -0
- algokit_common/serde/_core.py +610 -0
- algokit_common/serde/_primitives.py +135 -0
- algokit_common/source_map.py +158 -0
- algokit_indexer_client/__init__.py +10 -0
- algokit_indexer_client/client.py +1456 -0
- algokit_indexer_client/config.py +36 -0
- algokit_indexer_client/exceptions.py +59 -0
- algokit_indexer_client/models/__init__.py +148 -0
- algokit_indexer_client/models/_account.py +161 -0
- algokit_indexer_client/models/_account_participation.py +53 -0
- algokit_indexer_client/models/_account_response.py +19 -0
- algokit_indexer_client/models/_account_state_delta.py +29 -0
- algokit_indexer_client/models/_accounts_response.py +29 -0
- algokit_indexer_client/models/_application.py +35 -0
- algokit_indexer_client/models/_application_local_state.py +45 -0
- algokit_indexer_client/models/_application_local_states_response.py +29 -0
- algokit_indexer_client/models/_application_log_data.py +28 -0
- algokit_indexer_client/models/_application_logs_response.py +33 -0
- algokit_indexer_client/models/_application_params.py +62 -0
- algokit_indexer_client/models/_application_response.py +20 -0
- algokit_indexer_client/models/_application_state_schema.py +22 -0
- algokit_indexer_client/models/_applications_response.py +29 -0
- algokit_indexer_client/models/_asset.py +35 -0
- algokit_indexer_client/models/_asset_balances_response.py +29 -0
- algokit_indexer_client/models/_asset_holding.py +41 -0
- algokit_indexer_client/models/_asset_holdings_response.py +29 -0
- algokit_indexer_client/models/_asset_params.py +102 -0
- algokit_indexer_client/models/_asset_response.py +19 -0
- algokit_indexer_client/models/_assets_response.py +29 -0
- algokit_indexer_client/models/_block.py +150 -0
- algokit_indexer_client/models/_block_headers_response.py +29 -0
- algokit_indexer_client/models/_block_rewards.py +38 -0
- algokit_indexer_client/models/_block_upgrade_state.py +34 -0
- algokit_indexer_client/models/_block_upgrade_vote.py +26 -0
- algokit_indexer_client/models/_box.py +36 -0
- algokit_indexer_client/models/_box_descriptor.py +24 -0
- algokit_indexer_client/models/_box_reference.py +28 -0
- algokit_indexer_client/models/_boxes_response.py +29 -0
- algokit_indexer_client/models/_error_response.py +18 -0
- algokit_indexer_client/models/_eval_delta.py +32 -0
- algokit_indexer_client/models/_eval_delta_key_value.py +28 -0
- algokit_indexer_client/models/_hash_factory.py +14 -0
- algokit_indexer_client/models/_hb_proof_fields.py +57 -0
- algokit_indexer_client/models/_health_check.py +42 -0
- algokit_indexer_client/models/_holding_ref.py +23 -0
- algokit_indexer_client/models/_indexer_state_proof_message.py +40 -0
- algokit_indexer_client/models/_locals_ref.py +23 -0
- algokit_indexer_client/models/_merkle_array_proof.py +29 -0
- algokit_indexer_client/models/_mini_asset_holding.py +38 -0
- algokit_indexer_client/models/_on_completion.py +25 -0
- algokit_indexer_client/models/_participation_updates.py +22 -0
- algokit_indexer_client/models/_resource_ref.py +42 -0
- algokit_indexer_client/models/_serde_helpers.py +241 -0
- algokit_indexer_client/models/_state_delta.py +6 -0
- algokit_indexer_client/models/_state_proof_fields.py +57 -0
- algokit_indexer_client/models/_state_proof_participant.py +20 -0
- algokit_indexer_client/models/_state_proof_reveal.py +25 -0
- algokit_indexer_client/models/_state_proof_sig_slot.py +20 -0
- algokit_indexer_client/models/_state_proof_signature.py +37 -0
- algokit_indexer_client/models/_state_proof_tracking.py +32 -0
- algokit_indexer_client/models/_state_proof_verifier.py +24 -0
- algokit_indexer_client/models/_state_schema.py +25 -0
- algokit_indexer_client/models/_teal_key_value.py +28 -0
- algokit_indexer_client/models/_teal_key_value_store.py +6 -0
- algokit_indexer_client/models/_teal_value.py +32 -0
- algokit_indexer_client/models/_transaction.py +213 -0
- algokit_indexer_client/models/_transaction_application.py +105 -0
- algokit_indexer_client/models/_transaction_asset_config.py +31 -0
- algokit_indexer_client/models/_transaction_asset_freeze.py +29 -0
- algokit_indexer_client/models/_transaction_asset_transfer.py +41 -0
- algokit_indexer_client/models/_transaction_heartbeat.py +52 -0
- algokit_indexer_client/models/_transaction_keyreg.py +59 -0
- algokit_indexer_client/models/_transaction_payment.py +33 -0
- algokit_indexer_client/models/_transaction_response.py +19 -0
- algokit_indexer_client/models/_transaction_signature.py +35 -0
- algokit_indexer_client/models/_transaction_signature_logicsig.py +59 -0
- algokit_indexer_client/models/_transaction_signature_multisig.py +36 -0
- algokit_indexer_client/models/_transaction_signature_multisig_subsignature.py +28 -0
- algokit_indexer_client/models/_transaction_state_proof.py +32 -0
- algokit_indexer_client/models/_transactions_response.py +29 -0
- algokit_indexer_client/py.typed +1 -0
- algokit_indexer_client/types.py +7 -0
- algokit_kmd_client/__init__.py +10 -0
- algokit_kmd_client/client.py +1240 -0
- algokit_kmd_client/config.py +36 -0
- algokit_kmd_client/exceptions.py +59 -0
- algokit_kmd_client/models/__init__.py +112 -0
- algokit_kmd_client/models/_classical_signatures.py +4 -0
- algokit_kmd_client/models/_create_wallet_request.py +30 -0
- algokit_kmd_client/models/_create_wallet_response.py +19 -0
- algokit_kmd_client/models/_delete_key_request.py +27 -0
- algokit_kmd_client/models/_delete_multisig_request.py +27 -0
- algokit_kmd_client/models/_digest_represents_a32_byte_value_holding_the256_bit_hash_digest.py +4 -0
- algokit_kmd_client/models/_ed25519_public_key.py +4 -0
- algokit_kmd_client/models/_export_key_request.py +27 -0
- algokit_kmd_client/models/_export_key_response.py +24 -0
- algokit_kmd_client/models/_export_master_key_request.py +22 -0
- algokit_kmd_client/models/_export_master_key_response.py +18 -0
- algokit_kmd_client/models/_export_multisig_request.py +23 -0
- algokit_kmd_client/models/_export_multisig_response.py +26 -0
- algokit_kmd_client/models/_generate_key_request.py +18 -0
- algokit_kmd_client/models/_generate_key_response.py +19 -0
- algokit_kmd_client/models/_import_key_request.py +28 -0
- algokit_kmd_client/models/_import_key_response.py +19 -0
- algokit_kmd_client/models/_import_multisig_request.py +30 -0
- algokit_kmd_client/models/_import_multisig_response.py +19 -0
- algokit_kmd_client/models/_init_wallet_handle_token_request.py +22 -0
- algokit_kmd_client/models/_init_wallet_handle_token_response.py +18 -0
- algokit_kmd_client/models/_list_keys_request.py +18 -0
- algokit_kmd_client/models/_list_keys_response.py +18 -0
- algokit_kmd_client/models/_list_multisig_request.py +18 -0
- algokit_kmd_client/models/_list_multisig_response.py +18 -0
- algokit_kmd_client/models/_list_wallets_request.py +11 -0
- algokit_kmd_client/models/_list_wallets_response.py +25 -0
- algokit_kmd_client/models/_master_derivation_key.py +4 -0
- algokit_kmd_client/models/_multisig_sig.py +33 -0
- algokit_kmd_client/models/_multisig_subsig.py +23 -0
- algokit_kmd_client/models/_public_key.py +4 -0
- algokit_kmd_client/models/_release_wallet_handle_token_request.py +18 -0
- algokit_kmd_client/models/_rename_wallet_request.py +26 -0
- algokit_kmd_client/models/_rename_wallet_response.py +19 -0
- algokit_kmd_client/models/_renew_wallet_handle_token_request.py +18 -0
- algokit_kmd_client/models/_renew_wallet_handle_token_response.py +19 -0
- algokit_kmd_client/models/_serde_helpers.py +241 -0
- algokit_kmd_client/models/_sign_multisig_response.py +24 -0
- algokit_kmd_client/models/_sign_multisig_txn_request.py +45 -0
- algokit_kmd_client/models/_sign_program_multisig_request.py +50 -0
- algokit_kmd_client/models/_sign_program_multisig_response.py +24 -0
- algokit_kmd_client/models/_sign_program_request.py +37 -0
- algokit_kmd_client/models/_sign_program_response.py +24 -0
- algokit_kmd_client/models/_sign_transaction_response.py +24 -0
- algokit_kmd_client/models/_sign_txn_request.py +36 -0
- algokit_kmd_client/models/_signature.py +4 -0
- algokit_kmd_client/models/_tx_type.py +4 -0
- algokit_kmd_client/models/_versions_request.py +11 -0
- algokit_kmd_client/models/_versions_response.py +19 -0
- algokit_kmd_client/models/_wallet.py +38 -0
- algokit_kmd_client/models/_wallet_handle.py +24 -0
- algokit_kmd_client/models/_wallet_info_request.py +18 -0
- algokit_kmd_client/models/_wallet_info_response.py +19 -0
- algokit_kmd_client/py.typed +1 -0
- algokit_kmd_client/types.py +7 -0
- algokit_transact/__init__.py +190 -0
- algokit_transact/codec/__init__.py +0 -0
- algokit_transact/codec/msgpack.py +11 -0
- algokit_transact/codec/serde.py +7 -0
- algokit_transact/codec/signed.py +57 -0
- algokit_transact/codec/transaction.py +65 -0
- algokit_transact/exceptions.py +17 -0
- algokit_transact/logicsig.py +220 -0
- algokit_transact/models/__init__.py +0 -0
- algokit_transact/models/app_call.py +447 -0
- algokit_transact/models/asset_config.py +19 -0
- algokit_transact/models/asset_freeze.py +11 -0
- algokit_transact/models/asset_transfer.py +13 -0
- algokit_transact/models/common.py +17 -0
- algokit_transact/models/heartbeat.py +21 -0
- algokit_transact/models/key_registration.py +14 -0
- algokit_transact/models/payment.py +14 -0
- algokit_transact/models/signed_transaction.py +21 -0
- algokit_transact/models/state_proof.py +150 -0
- algokit_transact/models/transaction.py +88 -0
- algokit_transact/multisig.py +93 -0
- algokit_transact/ops/__init__.py +0 -0
- algokit_transact/ops/fees.py +47 -0
- algokit_transact/ops/group.py +28 -0
- algokit_transact/ops/ids.py +14 -0
- algokit_transact/ops/validate.py +503 -0
- algokit_transact/py.typed +0 -0
- algokit_transact/signer.py +195 -0
- algokit_transact/signing/__init__.py +0 -0
- algokit_transact/signing/logic_signature.py +19 -0
- algokit_transact/signing/multisig.py +84 -0
- algokit_transact/signing/types.py +39 -0
- algokit_transact/signing/validation.py +63 -0
- algokit_utils/__init__.py +23 -0
- algokit_utils/_debugging.py +304 -0
- algokit_utils/accounts/__init__.py +2 -0
- algokit_utils/accounts/account_manager.py +1051 -0
- algokit_utils/accounts/kmd_account_manager.py +206 -0
- algokit_utils/algo25.py +46 -0
- algokit_utils/algorand.py +383 -0
- algokit_utils/applications/__init__.py +7 -0
- algokit_utils/applications/abi.py +280 -0
- algokit_utils/applications/app_client.py +2193 -0
- algokit_utils/applications/app_deployer.py +788 -0
- algokit_utils/applications/app_factory.py +1140 -0
- algokit_utils/applications/app_manager.py +575 -0
- algokit_utils/applications/app_spec/__init__.py +6 -0
- algokit_utils/applications/enums.py +40 -0
- algokit_utils/assets/__init__.py +1 -0
- algokit_utils/assets/asset_manager.py +344 -0
- algokit_utils/clients/__init__.py +41 -0
- algokit_utils/clients/client_manager.py +756 -0
- algokit_utils/clients/dispenser_api_client.py +212 -0
- algokit_utils/common.py +40 -0
- algokit_utils/config.py +159 -0
- algokit_utils/errors/__init__.py +1 -0
- algokit_utils/errors/logic_error.py +160 -0
- algokit_utils/models/__init__.py +7 -0
- algokit_utils/models/account.py +12 -0
- algokit_utils/models/amount.py +198 -0
- algokit_utils/models/application.py +90 -0
- algokit_utils/models/network.py +29 -0
- algokit_utils/models/simulate.py +7 -0
- algokit_utils/models/state.py +53 -0
- algokit_utils/models/transaction.py +49 -0
- algokit_utils/protocols/__init__.py +3 -0
- algokit_utils/protocols/account.py +11 -0
- algokit_utils/protocols/signer.py +17 -0
- algokit_utils/protocols/typed_clients.py +110 -0
- algokit_utils/py.typed +0 -0
- algokit_utils/transact.py +195 -0
- algokit_utils/transactions/__init__.py +3 -0
- algokit_utils/transactions/builders/__init__.py +67 -0
- algokit_utils/transactions/builders/app.py +248 -0
- algokit_utils/transactions/builders/asset.py +256 -0
- algokit_utils/transactions/builders/common.py +263 -0
- algokit_utils/transactions/builders/keyreg.py +103 -0
- algokit_utils/transactions/builders/method_call.py +380 -0
- algokit_utils/transactions/builders/payment.py +43 -0
- algokit_utils/transactions/composer_resources.py +409 -0
- algokit_utils/transactions/fee_coverage.py +79 -0
- algokit_utils/transactions/helpers.py +9 -0
- algokit_utils/transactions/transaction_composer.py +1574 -0
- algokit_utils/transactions/transaction_creator.py +699 -0
- algokit_utils/transactions/transaction_sender.py +1240 -0
- algokit_utils/transactions/types.py +262 -0
- algokit_utils-5.0.0a3.dist-info/METADATA +105 -0
- algokit_utils-5.0.0a3.dist-info/RECORD +337 -0
- algokit_utils-5.0.0a3.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,610 @@
|
|
|
1
|
+
import builtins
|
|
2
|
+
import sys
|
|
3
|
+
import types
|
|
4
|
+
from collections.abc import Callable, Mapping
|
|
5
|
+
from dataclasses import dataclass, fields, is_dataclass
|
|
6
|
+
from enum import Enum
|
|
7
|
+
from typing import TypeVar, Union, cast, get_args, get_origin, get_type_hints
|
|
8
|
+
|
|
9
|
+
from algokit_common import address_from_public_key, public_key_from_address
|
|
10
|
+
from algokit_common.serde._primitives import (
|
|
11
|
+
decode_int_like,
|
|
12
|
+
encode_bool,
|
|
13
|
+
encode_int,
|
|
14
|
+
omit_defaults_and_sort,
|
|
15
|
+
sort_msgpack_value,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
__all__ = [
|
|
19
|
+
"DecodeError",
|
|
20
|
+
"EncodeError",
|
|
21
|
+
"addr",
|
|
22
|
+
"addr_seq",
|
|
23
|
+
"bytes_seq",
|
|
24
|
+
"enum_value",
|
|
25
|
+
"flatten",
|
|
26
|
+
"from_wire",
|
|
27
|
+
"int_seq",
|
|
28
|
+
"nested",
|
|
29
|
+
"sort_msgpack_value",
|
|
30
|
+
"to_wire",
|
|
31
|
+
"to_wire_canonical",
|
|
32
|
+
"wire",
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
DecodedValueT = TypeVar("DecodedValueT")
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class EncodeError(ValueError):
|
|
40
|
+
pass
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class DecodeError(ValueError):
|
|
44
|
+
pass
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
# Metadata helpers
|
|
48
|
+
def wire(
|
|
49
|
+
alias: str,
|
|
50
|
+
*,
|
|
51
|
+
encode: Callable[..., object] | None = None,
|
|
52
|
+
decode: Callable[..., object] | type | None = None,
|
|
53
|
+
omit_if_none: bool = True,
|
|
54
|
+
keep_zero: bool = False,
|
|
55
|
+
keep_false: bool = False,
|
|
56
|
+
omit_empty_seq: bool = True,
|
|
57
|
+
required: bool = False,
|
|
58
|
+
pass_obj: bool = False,
|
|
59
|
+
) -> dict[str, object]:
|
|
60
|
+
return {
|
|
61
|
+
"kind": "wire",
|
|
62
|
+
"alias": alias,
|
|
63
|
+
"encode": encode,
|
|
64
|
+
"decode": decode,
|
|
65
|
+
"omit_if_none": omit_if_none,
|
|
66
|
+
"keep_zero": keep_zero,
|
|
67
|
+
"keep_false": keep_false,
|
|
68
|
+
"omit_empty_seq": omit_empty_seq,
|
|
69
|
+
"required": required,
|
|
70
|
+
"pass_obj": pass_obj,
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
ChildType = type[object] | Callable[[], type[object]] | None
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _expects_text_value(type_hint: object) -> bool:
|
|
78
|
+
if isinstance(type_hint, type) and issubclass(type_hint, Enum):
|
|
79
|
+
return True
|
|
80
|
+
if type_hint is str:
|
|
81
|
+
return True
|
|
82
|
+
origin = get_origin(type_hint)
|
|
83
|
+
if origin is None:
|
|
84
|
+
return False
|
|
85
|
+
if origin is str:
|
|
86
|
+
return True
|
|
87
|
+
if origin in (list, tuple, set, frozenset, dict):
|
|
88
|
+
return False
|
|
89
|
+
args = [arg for arg in get_args(type_hint) if arg is not type(None)]
|
|
90
|
+
return any(_expects_text_value(arg) for arg in args)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def flatten(
|
|
94
|
+
child_cls: ChildType,
|
|
95
|
+
*,
|
|
96
|
+
present_if: Callable[[Mapping[str, object]], bool] | None = None,
|
|
97
|
+
) -> dict[str, object]:
|
|
98
|
+
return {"kind": "flatten", "child_cls": child_cls, "present_if": present_if}
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def nested(
|
|
102
|
+
alias: str,
|
|
103
|
+
child_cls: ChildType,
|
|
104
|
+
*,
|
|
105
|
+
present_if: Callable[[Mapping[str, object]], bool] | None = None,
|
|
106
|
+
omit_empty_seq: bool = True,
|
|
107
|
+
required: bool = False,
|
|
108
|
+
) -> dict[str, object]:
|
|
109
|
+
return {
|
|
110
|
+
"kind": "nested",
|
|
111
|
+
"alias": alias,
|
|
112
|
+
"child_cls": child_cls,
|
|
113
|
+
"present_if": present_if,
|
|
114
|
+
"omit_empty_seq": omit_empty_seq,
|
|
115
|
+
"required": required,
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
@dataclass(slots=True)
|
|
120
|
+
class _FieldHandler:
|
|
121
|
+
name: str
|
|
122
|
+
alias: str | None
|
|
123
|
+
encode_fn: Callable[..., object] | None
|
|
124
|
+
decode_fn: Callable[[object], object] | type | None
|
|
125
|
+
omit_if_none: bool
|
|
126
|
+
keep_zero: bool
|
|
127
|
+
keep_false: bool
|
|
128
|
+
omit_empty_seq: bool
|
|
129
|
+
required: bool
|
|
130
|
+
kind: str
|
|
131
|
+
child_cls: ChildType
|
|
132
|
+
nested_alias: str | None
|
|
133
|
+
present_if: Callable[[Mapping[str, object]], bool] | None = None
|
|
134
|
+
pass_obj: bool = False
|
|
135
|
+
expects_text: bool = False
|
|
136
|
+
nested_required: bool = False # For nested fields: whether they are required
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
class _SerdePlan:
|
|
140
|
+
__slots__ = ("cls", "fields")
|
|
141
|
+
|
|
142
|
+
def __init__(self, cls: type[object], handlers: list[_FieldHandler]) -> None:
|
|
143
|
+
self.cls = cls
|
|
144
|
+
self.fields = handlers
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
_SERDE_CACHE: dict[type[object], _SerdePlan] = {}
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def _get_dataclass(typ: type) -> type | None:
|
|
151
|
+
if get_origin(typ) in {Union, types.UnionType}:
|
|
152
|
+
typs = set(get_args(typ))
|
|
153
|
+
else:
|
|
154
|
+
typs = {typ}
|
|
155
|
+
typs = typs - {types.NoneType}
|
|
156
|
+
try:
|
|
157
|
+
(maybe_dataclass,) = typs
|
|
158
|
+
except ValueError:
|
|
159
|
+
return None
|
|
160
|
+
if is_dataclass(maybe_dataclass):
|
|
161
|
+
return cast(type, maybe_dataclass)
|
|
162
|
+
else:
|
|
163
|
+
return None
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def _compile_plan(cls: type[object]) -> _SerdePlan:
|
|
167
|
+
if not is_dataclass(cls):
|
|
168
|
+
raise TypeError(f"{cls!r} is not a dataclass")
|
|
169
|
+
handlers: list[_FieldHandler] = []
|
|
170
|
+
# Use explicit globalns with builtins and empty localns to avoid issues with
|
|
171
|
+
# dataclass fields that shadow builtin names (e.g. 'bytes', 'type').
|
|
172
|
+
# When slots=True, the class namespace contains member descriptors that can
|
|
173
|
+
# interfere with type hint evaluation.
|
|
174
|
+
module = sys.modules.get(cls.__module__, None)
|
|
175
|
+
globalns = {**vars(builtins), **(vars(module) if module else {})}
|
|
176
|
+
cls_type_hints = get_type_hints(cls, globalns=globalns, localns={})
|
|
177
|
+
for f in fields(cls):
|
|
178
|
+
meta = dict(f.metadata or {})
|
|
179
|
+
field_type = cls_type_hints[f.name]
|
|
180
|
+
maybe_dataclass = _get_dataclass(field_type)
|
|
181
|
+
if maybe_dataclass and not meta:
|
|
182
|
+
kind = "nested"
|
|
183
|
+
meta = {"child_cls": maybe_dataclass, "alias": f.name, "omit_if_none": True}
|
|
184
|
+
else:
|
|
185
|
+
kind = cast(str | None, meta.get("kind")) or "wire"
|
|
186
|
+
if kind not in ("wire", "flatten", "nested"):
|
|
187
|
+
kind = "wire"
|
|
188
|
+
|
|
189
|
+
if kind == "wire":
|
|
190
|
+
handlers.append(
|
|
191
|
+
_FieldHandler(
|
|
192
|
+
name=f.name,
|
|
193
|
+
alias=cast(str | None, meta.get("alias", f.name)),
|
|
194
|
+
encode_fn=cast(Callable[..., object] | None, meta.get("encode")),
|
|
195
|
+
decode_fn=cast(Callable[[object], object] | type | None, meta.get("decode")),
|
|
196
|
+
omit_if_none=bool(meta.get("omit_if_none", True)),
|
|
197
|
+
keep_zero=bool(meta.get("keep_zero", False)),
|
|
198
|
+
keep_false=bool(meta.get("keep_false", False)),
|
|
199
|
+
omit_empty_seq=bool(meta.get("omit_empty_seq", True)),
|
|
200
|
+
required=bool(meta.get("required", False)),
|
|
201
|
+
kind=kind,
|
|
202
|
+
child_cls=None,
|
|
203
|
+
nested_alias=None,
|
|
204
|
+
pass_obj=bool(meta.get("pass_obj", False)),
|
|
205
|
+
expects_text=_expects_text_value(field_type),
|
|
206
|
+
)
|
|
207
|
+
)
|
|
208
|
+
elif kind == "nested":
|
|
209
|
+
handlers.append(
|
|
210
|
+
_FieldHandler(
|
|
211
|
+
name=f.name,
|
|
212
|
+
alias=None,
|
|
213
|
+
encode_fn=None,
|
|
214
|
+
decode_fn=None,
|
|
215
|
+
omit_if_none=True,
|
|
216
|
+
keep_zero=False,
|
|
217
|
+
keep_false=False,
|
|
218
|
+
omit_empty_seq=meta.get("omit_empty_seq", True),
|
|
219
|
+
required=False,
|
|
220
|
+
kind=kind,
|
|
221
|
+
child_cls=cast(type[object] | None, meta.get("child_cls")),
|
|
222
|
+
nested_alias=cast(str | None, meta.get("alias")),
|
|
223
|
+
present_if=cast(Callable[[Mapping[str, object]], bool] | None, meta.get("present_if")),
|
|
224
|
+
nested_required=bool(meta.get("required", False)),
|
|
225
|
+
)
|
|
226
|
+
)
|
|
227
|
+
else: # flatten
|
|
228
|
+
handlers.append(
|
|
229
|
+
_FieldHandler(
|
|
230
|
+
name=f.name,
|
|
231
|
+
alias=None,
|
|
232
|
+
encode_fn=None,
|
|
233
|
+
decode_fn=None,
|
|
234
|
+
omit_if_none=True,
|
|
235
|
+
keep_zero=False,
|
|
236
|
+
keep_false=False,
|
|
237
|
+
omit_empty_seq=True,
|
|
238
|
+
required=False,
|
|
239
|
+
kind=kind,
|
|
240
|
+
child_cls=cast(type[object] | None, meta.get("child_cls")),
|
|
241
|
+
nested_alias=None,
|
|
242
|
+
present_if=cast(Callable[[Mapping[str, object]], bool] | None, meta.get("present_if")),
|
|
243
|
+
)
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
plan = _SerdePlan(cls, handlers)
|
|
247
|
+
_SERDE_CACHE[cls] = plan
|
|
248
|
+
return plan
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def _plan_for(cls: type[object]) -> _SerdePlan:
|
|
252
|
+
return _SERDE_CACHE.get(cls) or _compile_plan(cls)
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
# Cache for default instances to avoid repeated construction
|
|
256
|
+
_DEFAULT_INSTANCE_CACHE: dict[type[object], object] = {}
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def _construct_default_instance(cls: type[object]) -> object:
|
|
260
|
+
"""Construct a default instance of a dataclass with all required fields set to defaults.
|
|
261
|
+
|
|
262
|
+
This mirrors TypeScript's ObjectModelCodec.defaultValue() behavior:
|
|
263
|
+
- Required primitive fields get type-appropriate defaults (0, "", False, b"", etc.)
|
|
264
|
+
- Required nested object fields get recursively constructed default instances
|
|
265
|
+
- Optional fields are not set (they use their dataclass defaults, typically None)
|
|
266
|
+
|
|
267
|
+
The result is cached for performance.
|
|
268
|
+
"""
|
|
269
|
+
if cls in _DEFAULT_INSTANCE_CACHE:
|
|
270
|
+
return _DEFAULT_INSTANCE_CACHE[cls]
|
|
271
|
+
|
|
272
|
+
if not is_dataclass(cls):
|
|
273
|
+
raise TypeError(f"{cls!r} is not a dataclass")
|
|
274
|
+
|
|
275
|
+
plan = _plan_for(cls)
|
|
276
|
+
kwargs: dict[str, object] = {}
|
|
277
|
+
|
|
278
|
+
for h in plan.fields:
|
|
279
|
+
if h.kind == "wire":
|
|
280
|
+
# For wire fields, check if it has a default in the dataclass
|
|
281
|
+
# If not, we need to provide a default value for required primitives
|
|
282
|
+
# The dataclass defaults should already handle this via generator
|
|
283
|
+
pass
|
|
284
|
+
elif h.kind == "nested" and h.nested_required:
|
|
285
|
+
# Required nested fields need default instances
|
|
286
|
+
child_cls = _resolve_child_cls(h)
|
|
287
|
+
if child_cls is not None:
|
|
288
|
+
kwargs[h.name] = _construct_default_instance(child_cls)
|
|
289
|
+
# flatten and optional nested fields are not populated
|
|
290
|
+
|
|
291
|
+
# Create instance - dataclass defaults will fill in primitive defaults
|
|
292
|
+
try:
|
|
293
|
+
instance = cls(**kwargs)
|
|
294
|
+
except TypeError as exc:
|
|
295
|
+
raise DecodeError(f"Failed to construct default instance of {cls.__name__}: {exc}") from exc
|
|
296
|
+
|
|
297
|
+
_DEFAULT_INSTANCE_CACHE[cls] = instance
|
|
298
|
+
return instance
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def _encode_scalar(value: object, *, keep_zero: bool, keep_false: bool) -> object | None:
|
|
302
|
+
if value is None:
|
|
303
|
+
return None
|
|
304
|
+
if isinstance(value, bool):
|
|
305
|
+
return value if keep_false else encode_bool(value)
|
|
306
|
+
if isinstance(value, int):
|
|
307
|
+
return encode_int(value, keep_zero=keep_zero)
|
|
308
|
+
if isinstance(value, bytes | bytearray | memoryview):
|
|
309
|
+
return bytes(value) if isinstance(value, bytearray | memoryview) else value
|
|
310
|
+
return value
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
def _resolve_child_cls(h: _FieldHandler) -> type[object] | None:
|
|
314
|
+
child = h.child_cls
|
|
315
|
+
if child is None:
|
|
316
|
+
return None
|
|
317
|
+
if isinstance(child, type):
|
|
318
|
+
return child
|
|
319
|
+
return child()
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
def _encode_nested_field(out: dict[str, object], obj: object, h: _FieldHandler) -> None:
|
|
323
|
+
if (value := getattr(obj, h.name)) is None:
|
|
324
|
+
return
|
|
325
|
+
if not (nested_payload := to_wire(value)) and h.omit_empty_seq:
|
|
326
|
+
return
|
|
327
|
+
if h.nested_alias is None:
|
|
328
|
+
raise EncodeError(f"Missing nested alias for field {h.name!r}")
|
|
329
|
+
out[h.nested_alias] = nested_payload
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
def _encode_flatten_field(out: dict[str, object], obj: object, h: _FieldHandler) -> None:
|
|
333
|
+
if (value := getattr(obj, h.name)) is None:
|
|
334
|
+
return
|
|
335
|
+
if child_payload := to_wire(value):
|
|
336
|
+
out.update(child_payload)
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
def _encode_wire_field(out: dict[str, object], obj: object, h: _FieldHandler) -> None:
|
|
340
|
+
if not h.alias:
|
|
341
|
+
return
|
|
342
|
+
value = getattr(obj, h.name)
|
|
343
|
+
if value is None:
|
|
344
|
+
if h.required:
|
|
345
|
+
raise EncodeError(f"Field {h.name!r} is required but None")
|
|
346
|
+
if h.omit_if_none:
|
|
347
|
+
return
|
|
348
|
+
_set_path(out, h.alias, None)
|
|
349
|
+
return
|
|
350
|
+
|
|
351
|
+
if h.encode_fn is not None:
|
|
352
|
+
encoded = h.encode_fn(obj, value) if h.pass_obj else h.encode_fn(value)
|
|
353
|
+
else:
|
|
354
|
+
encoded = _encode_scalar(value, keep_zero=h.keep_zero, keep_false=h.keep_false)
|
|
355
|
+
|
|
356
|
+
if h.omit_empty_seq and isinstance(encoded, list | tuple) and not encoded:
|
|
357
|
+
return
|
|
358
|
+
if encoded is None and h.omit_if_none:
|
|
359
|
+
return
|
|
360
|
+
_set_path(out, h.alias, encoded)
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
def to_wire(obj: object) -> dict[str, object]:
|
|
364
|
+
"""Encode a dataclass instance to a wire-ready dict using field metadata."""
|
|
365
|
+
plan = _plan_for(obj.__class__)
|
|
366
|
+
out: dict[str, object] = {}
|
|
367
|
+
for h in plan.fields:
|
|
368
|
+
if h.kind == "nested":
|
|
369
|
+
_encode_nested_field(out, obj, h)
|
|
370
|
+
elif h.kind == "flatten":
|
|
371
|
+
_encode_flatten_field(out, obj, h)
|
|
372
|
+
elif h.kind == "wire":
|
|
373
|
+
_encode_wire_field(out, obj, h)
|
|
374
|
+
return out
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
def to_wire_canonical(obj: object) -> dict[str, object]:
|
|
378
|
+
"""Return canonical, ready-to-msgpack dict (omit defaults and sort keys)."""
|
|
379
|
+
return cast(dict[str, object], omit_defaults_and_sort(dict(to_wire(obj))))
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
def _decode_with_hint(raw: object, decode_fn: Callable[[object], object] | type | None) -> object:
|
|
383
|
+
if decode_fn is None:
|
|
384
|
+
if isinstance(raw, bytes | bytearray):
|
|
385
|
+
return bytes(raw)
|
|
386
|
+
if isinstance(raw, int):
|
|
387
|
+
return decode_int_like(raw)
|
|
388
|
+
return raw
|
|
389
|
+
|
|
390
|
+
if isinstance(decode_fn, type):
|
|
391
|
+
try:
|
|
392
|
+
return cast("Callable[[object], object]", decode_fn)(raw)
|
|
393
|
+
except Exception as exc:
|
|
394
|
+
raise DecodeError(f"Failed to construct {decode_fn.__name__} from {raw!r}") from exc
|
|
395
|
+
|
|
396
|
+
return decode_fn(raw)
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
def _wire_aliases_for(cls: type[object]) -> frozenset[str]:
|
|
400
|
+
plan = _plan_for(cls)
|
|
401
|
+
aliases = {h.alias for h in plan.fields if h.kind == "wire" and h.alias}
|
|
402
|
+
aliases.update(h.nested_alias for h in plan.fields if h.kind == "nested" and h.nested_alias)
|
|
403
|
+
# For flattened fields, recursively collect wire aliases from child classes
|
|
404
|
+
for h in plan.fields:
|
|
405
|
+
if h.kind == "flatten":
|
|
406
|
+
child_cls = _resolve_child_cls(h)
|
|
407
|
+
if child_cls is not None:
|
|
408
|
+
aliases.update(_wire_aliases_for(child_cls))
|
|
409
|
+
return frozenset(aliases)
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
def _decode_wire_field(kwargs: dict[str, object], h: _FieldHandler, payload: Mapping[str, object]) -> None:
|
|
413
|
+
if not (alias := h.alias):
|
|
414
|
+
return
|
|
415
|
+
if not _has_path(payload, alias):
|
|
416
|
+
return
|
|
417
|
+
if (raw := _get_path(payload, alias)) is None:
|
|
418
|
+
if h.required:
|
|
419
|
+
raise DecodeError(f"Missing required field {h.name!r} (alias {alias!r})")
|
|
420
|
+
kwargs[h.name] = None
|
|
421
|
+
return
|
|
422
|
+
value = raw
|
|
423
|
+
needs_text = bool(
|
|
424
|
+
h.expects_text and (h.decode_fn is None or (isinstance(h.decode_fn, type) and issubclass(h.decode_fn, Enum)))
|
|
425
|
+
)
|
|
426
|
+
if needs_text and isinstance(value, bytes | bytearray | memoryview):
|
|
427
|
+
raw_bytes = bytes(value)
|
|
428
|
+
try:
|
|
429
|
+
value = raw_bytes.decode("utf-8")
|
|
430
|
+
except UnicodeDecodeError:
|
|
431
|
+
# Some Algorand fields legitimately carry printable data inside binary slots.
|
|
432
|
+
value = raw_bytes
|
|
433
|
+
kwargs[h.name] = _decode_with_hint(value, h.decode_fn)
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
def _decode_nested_field(kwargs: dict[str, object], h: _FieldHandler, payload: Mapping[str, object]) -> None:
|
|
437
|
+
child_cls = _resolve_child_cls(h)
|
|
438
|
+
if child_cls is None or h.nested_alias is None:
|
|
439
|
+
kwargs[h.name] = None
|
|
440
|
+
return
|
|
441
|
+
if isinstance(raw_nested := payload.get(h.nested_alias), Mapping):
|
|
442
|
+
kwargs[h.name] = from_wire(child_cls, raw_nested)
|
|
443
|
+
return
|
|
444
|
+
# Field is missing or not a Mapping - check if it's required
|
|
445
|
+
if h.nested_required:
|
|
446
|
+
# Required nested fields get a default instance (mirrors TS ObjectModelCodec.defaultValue())
|
|
447
|
+
kwargs[h.name] = _construct_default_instance(child_cls)
|
|
448
|
+
return
|
|
449
|
+
if not h.omit_if_none:
|
|
450
|
+
kwargs[h.name] = None
|
|
451
|
+
|
|
452
|
+
|
|
453
|
+
def _decode_flatten_field(kwargs: dict[str, object], h: _FieldHandler, payload: Mapping[str, object]) -> None:
|
|
454
|
+
child_cls = _resolve_child_cls(h)
|
|
455
|
+
if child_cls is None:
|
|
456
|
+
kwargs[h.name] = None
|
|
457
|
+
return
|
|
458
|
+
alias_set = _wire_aliases_for(child_cls)
|
|
459
|
+
has_any = any(_has_path(payload, k) for k in alias_set)
|
|
460
|
+
# If present_if is provided, it takes precedence over the wire alias check.
|
|
461
|
+
# This is important for transaction types where the same wire keys (e.g., 'amt', 'rcv')
|
|
462
|
+
# could be present but the type field indicates a different transaction type.
|
|
463
|
+
if h.present_if is not None:
|
|
464
|
+
if not h.present_if(payload):
|
|
465
|
+
kwargs[h.name] = None
|
|
466
|
+
return
|
|
467
|
+
elif not has_any:
|
|
468
|
+
kwargs[h.name] = None
|
|
469
|
+
return
|
|
470
|
+
sub: dict[str, object] = {}
|
|
471
|
+
for key in alias_set:
|
|
472
|
+
if _has_path(payload, key):
|
|
473
|
+
_set_path(sub, key, _get_path(payload, key))
|
|
474
|
+
kwargs[h.name] = from_wire(child_cls, sub)
|
|
475
|
+
|
|
476
|
+
|
|
477
|
+
def _set_path(target: dict[str, object], path: str, value: object) -> None:
|
|
478
|
+
if "." not in path:
|
|
479
|
+
target[path] = value
|
|
480
|
+
return
|
|
481
|
+
parts = path.split(".")
|
|
482
|
+
cur = target
|
|
483
|
+
for key in parts[:-1]:
|
|
484
|
+
if not isinstance(nxt := cur.get(key), dict):
|
|
485
|
+
nxt = {}
|
|
486
|
+
cur[key] = nxt
|
|
487
|
+
cur = nxt
|
|
488
|
+
cur[parts[-1]] = value
|
|
489
|
+
|
|
490
|
+
|
|
491
|
+
def _get_path(source: Mapping[str, object], path: str) -> object | None:
|
|
492
|
+
if "." not in path:
|
|
493
|
+
return source.get(path)
|
|
494
|
+
cur: object = source
|
|
495
|
+
for key in path.split("."):
|
|
496
|
+
if not isinstance(cur, Mapping) or (cur := cur.get(key)) is None:
|
|
497
|
+
return None
|
|
498
|
+
return cur
|
|
499
|
+
|
|
500
|
+
|
|
501
|
+
def _has_path(source: Mapping[str, object], path: str) -> bool:
|
|
502
|
+
if "." not in path:
|
|
503
|
+
return path in source
|
|
504
|
+
cur: object = source
|
|
505
|
+
for key in path.split("."):
|
|
506
|
+
if not isinstance(cur, Mapping) or key not in cur:
|
|
507
|
+
return False
|
|
508
|
+
cur = cur[key]
|
|
509
|
+
return True
|
|
510
|
+
|
|
511
|
+
|
|
512
|
+
def from_wire(cls: type[DecodedValueT], payload: Mapping[str, object]) -> DecodedValueT:
|
|
513
|
+
"""Decode a wire dict into a dataclass instance using field metadata."""
|
|
514
|
+
plan = _plan_for(cls)
|
|
515
|
+
kwargs: dict[str, object] = {}
|
|
516
|
+
for h in plan.fields:
|
|
517
|
+
if h.kind == "wire":
|
|
518
|
+
_decode_wire_field(kwargs, h, payload)
|
|
519
|
+
elif h.kind == "nested":
|
|
520
|
+
_decode_nested_field(kwargs, h, payload)
|
|
521
|
+
elif h.kind == "flatten":
|
|
522
|
+
_decode_flatten_field(kwargs, h, payload)
|
|
523
|
+
try:
|
|
524
|
+
return cast(DecodedValueT, plan.cls(**kwargs))
|
|
525
|
+
except TypeError as exc:
|
|
526
|
+
raise DecodeError(f"Failed to construct {plan.cls.__name__}: {exc}") from exc
|
|
527
|
+
|
|
528
|
+
|
|
529
|
+
def addr(alias: str, *, omit_if_none: bool = True) -> dict[str, object]:
|
|
530
|
+
"""Typed helper for address fields (str <-> bytes)."""
|
|
531
|
+
return wire(
|
|
532
|
+
alias,
|
|
533
|
+
encode=lambda v: public_key_from_address(cast(str, v)),
|
|
534
|
+
decode=lambda v: address_from_public_key(cast(bytes, v)),
|
|
535
|
+
omit_if_none=omit_if_none,
|
|
536
|
+
)
|
|
537
|
+
|
|
538
|
+
|
|
539
|
+
E = TypeVar("E", bound=Enum)
|
|
540
|
+
|
|
541
|
+
|
|
542
|
+
def enum_value(alias: str, enum_type: type[E], *, fallback: E | None = None) -> dict[str, object]:
|
|
543
|
+
"""Typed helper for Enum fields that serialize via their .value.
|
|
544
|
+
|
|
545
|
+
Args:
|
|
546
|
+
alias: The wire format key name
|
|
547
|
+
enum_type: The Enum class to encode/decode
|
|
548
|
+
fallback: Optional fallback value to use when decoding an unknown value.
|
|
549
|
+
If not provided, decoding unknown values will raise DecodeError.
|
|
550
|
+
This is useful for forward-compatibility when new enum values may
|
|
551
|
+
be added in the future (e.g., new transaction types).
|
|
552
|
+
"""
|
|
553
|
+
|
|
554
|
+
def _decode(value: object) -> E:
|
|
555
|
+
# Normalize bytes to str (msgpack may return string values as bytes)
|
|
556
|
+
if isinstance(value, bytes | bytearray | memoryview):
|
|
557
|
+
value = bytes(value).decode("utf-8")
|
|
558
|
+
try:
|
|
559
|
+
return enum_type(value)
|
|
560
|
+
except ValueError:
|
|
561
|
+
if fallback is not None:
|
|
562
|
+
return fallback
|
|
563
|
+
raise
|
|
564
|
+
|
|
565
|
+
return wire(alias, encode=lambda e: e.value if isinstance(e, enum_type) else e, decode=_decode)
|
|
566
|
+
|
|
567
|
+
|
|
568
|
+
def bytes_seq(alias: str, *, omit_if_none: bool = True) -> dict[str, object]:
|
|
569
|
+
def _enc(value: object) -> object:
|
|
570
|
+
if value is None or not isinstance(value, tuple | list):
|
|
571
|
+
return None
|
|
572
|
+
out = [bytes(item) for item in value if isinstance(item, bytes | bytearray | memoryview)]
|
|
573
|
+
return out or None
|
|
574
|
+
|
|
575
|
+
def _dec(value: object) -> object:
|
|
576
|
+
if not isinstance(value, list):
|
|
577
|
+
return value
|
|
578
|
+
return tuple(bytes(item) for item in value if isinstance(item, bytes | bytearray | memoryview))
|
|
579
|
+
|
|
580
|
+
return wire(alias, encode=_enc, decode=_dec, omit_if_none=omit_if_none)
|
|
581
|
+
|
|
582
|
+
|
|
583
|
+
def int_seq(alias: str, *, omit_if_none: bool = True) -> dict[str, object]:
|
|
584
|
+
def _enc(value: object) -> object:
|
|
585
|
+
if value is None or not isinstance(value, tuple | list):
|
|
586
|
+
return None
|
|
587
|
+
out = [int(item) for item in value if isinstance(item, int)]
|
|
588
|
+
return out or None
|
|
589
|
+
|
|
590
|
+
def _dec(value: object) -> object:
|
|
591
|
+
if not isinstance(value, list):
|
|
592
|
+
return value
|
|
593
|
+
return tuple(int(item) for item in value if isinstance(item, int))
|
|
594
|
+
|
|
595
|
+
return wire(alias, encode=_enc, decode=_dec, omit_if_none=omit_if_none)
|
|
596
|
+
|
|
597
|
+
|
|
598
|
+
def addr_seq(alias: str, *, omit_if_none: bool = True) -> dict[str, object]:
|
|
599
|
+
def _enc(value: object) -> object:
|
|
600
|
+
if value is None or not isinstance(value, tuple | list):
|
|
601
|
+
return None
|
|
602
|
+
out = [public_key_from_address(cast(str, item)) for item in value]
|
|
603
|
+
return out or None
|
|
604
|
+
|
|
605
|
+
def _dec(value: object) -> object:
|
|
606
|
+
if not isinstance(value, list):
|
|
607
|
+
return value
|
|
608
|
+
return tuple(address_from_public_key(bytes(item)) for item in value if isinstance(item, bytes | bytearray))
|
|
609
|
+
|
|
610
|
+
return wire(alias, encode=_enc, decode=_dec, omit_if_none=omit_if_none)
|