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,1240 @@
|
|
|
1
|
+
# AUTO-GENERATED: oas_generator
|
|
2
|
+
import random
|
|
3
|
+
import time
|
|
4
|
+
from dataclasses import is_dataclass
|
|
5
|
+
from typing import Any, Literal, TypeVar, overload
|
|
6
|
+
|
|
7
|
+
import httpx
|
|
8
|
+
import msgpack
|
|
9
|
+
|
|
10
|
+
from algokit_common.serde import from_wire, to_wire
|
|
11
|
+
|
|
12
|
+
from . import models
|
|
13
|
+
from .config import ClientConfig
|
|
14
|
+
from .exceptions import UnexpectedStatusError
|
|
15
|
+
from .types import Headers
|
|
16
|
+
|
|
17
|
+
# HTTP status codes that warrant a retry (aligned with algokit-utils-ts)
|
|
18
|
+
_RETRY_STATUS_CODES: frozenset[int] = frozenset({408, 413, 429, 500, 502, 503, 504})
|
|
19
|
+
# Network error codes that warrant a retry (aligned with algokit-utils-ts)
|
|
20
|
+
_RETRY_ERROR_CODES: frozenset[str] = frozenset(
|
|
21
|
+
{
|
|
22
|
+
"ETIMEDOUT",
|
|
23
|
+
"ECONNRESET",
|
|
24
|
+
"EADDRINUSE",
|
|
25
|
+
"ECONNREFUSED",
|
|
26
|
+
"EPIPE",
|
|
27
|
+
"ENOTFOUND",
|
|
28
|
+
"ENETUNREACH",
|
|
29
|
+
"EAI_AGAIN",
|
|
30
|
+
"EPROTO",
|
|
31
|
+
}
|
|
32
|
+
)
|
|
33
|
+
_MAX_BACKOFF_MS: float = 10_000.0
|
|
34
|
+
_DEFAULT_MAX_TRIES: int = 5
|
|
35
|
+
|
|
36
|
+
ModelT = TypeVar("ModelT")
|
|
37
|
+
ListModelT = TypeVar("ListModelT")
|
|
38
|
+
PrimitiveT = TypeVar("PrimitiveT")
|
|
39
|
+
|
|
40
|
+
# Prefixed markers used when converting unhashable msgpack map keys into hashable tuples
|
|
41
|
+
_UNHASHABLE_PREFIXES: dict[str, str] = {
|
|
42
|
+
"dict": "__dict_key__",
|
|
43
|
+
"list": "__list_key__",
|
|
44
|
+
"set": "__set_key__",
|
|
45
|
+
"generic": "__unhashable__",
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class KmdClient:
|
|
50
|
+
def __init__(self, config: ClientConfig | None = None, *, http_client: httpx.Client | None = None) -> None:
|
|
51
|
+
self._config = config or ClientConfig()
|
|
52
|
+
# Track whether a custom HTTP client was provided to avoid retry conflicts
|
|
53
|
+
self._uses_custom_client = http_client is not None
|
|
54
|
+
self._client = http_client or httpx.Client(
|
|
55
|
+
base_url=self._config.base_url,
|
|
56
|
+
timeout=self._config.timeout,
|
|
57
|
+
verify=self._config.verify,
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
def close(self) -> None:
|
|
61
|
+
self._client.close()
|
|
62
|
+
|
|
63
|
+
def _calculate_max_tries(self) -> int:
|
|
64
|
+
"""Calculate maximum number of tries from config.max_retries."""
|
|
65
|
+
max_retries = self._config.max_retries
|
|
66
|
+
if not isinstance(max_retries, int) or max_retries < 0:
|
|
67
|
+
return _DEFAULT_MAX_TRIES
|
|
68
|
+
return max_retries + 1
|
|
69
|
+
|
|
70
|
+
def _should_retry(self, error: Exception | None, status_code: int | None, attempt: int, max_tries: int) -> bool:
|
|
71
|
+
"""Determine if a request should be retried based on error/status and attempt count."""
|
|
72
|
+
if attempt >= max_tries:
|
|
73
|
+
return False
|
|
74
|
+
|
|
75
|
+
# Check HTTP status code
|
|
76
|
+
if status_code is not None and status_code in _RETRY_STATUS_CODES:
|
|
77
|
+
return True
|
|
78
|
+
|
|
79
|
+
# Check network error codes (aligned with algokit-utils-ts)
|
|
80
|
+
if error is not None:
|
|
81
|
+
error_code = self._extract_error_code(error)
|
|
82
|
+
if error_code and error_code in _RETRY_ERROR_CODES:
|
|
83
|
+
return True
|
|
84
|
+
|
|
85
|
+
return False
|
|
86
|
+
|
|
87
|
+
def _extract_error_code(self, error: BaseException) -> str | None:
|
|
88
|
+
"""Extract error code from exception, checking common attributes."""
|
|
89
|
+
# Check for 'code' attribute (common in OS/network errors)
|
|
90
|
+
if hasattr(error, "code") and isinstance(error.code, str):
|
|
91
|
+
return error.code
|
|
92
|
+
# Check for errno attribute
|
|
93
|
+
if hasattr(error, "errno") and error.errno is not None:
|
|
94
|
+
import errno as errno_module
|
|
95
|
+
|
|
96
|
+
try:
|
|
97
|
+
return errno_module.errorcode.get(error.errno)
|
|
98
|
+
except (TypeError, AttributeError):
|
|
99
|
+
pass
|
|
100
|
+
# Check __cause__ for wrapped errors
|
|
101
|
+
if error.__cause__ is not None:
|
|
102
|
+
return self._extract_error_code(error.__cause__)
|
|
103
|
+
return None
|
|
104
|
+
|
|
105
|
+
def _request_with_retry(self, request_kwargs: dict[str, Any]) -> httpx.Response:
|
|
106
|
+
"""Execute request with exponential backoff retry for transient failures.
|
|
107
|
+
|
|
108
|
+
When a custom HTTP client is provided, retries are disabled to avoid
|
|
109
|
+
conflicts with any retry mechanism the custom client may implement.
|
|
110
|
+
"""
|
|
111
|
+
# Disable retries when using a custom HTTP client to avoid conflicts
|
|
112
|
+
# with the client's own retry mechanism
|
|
113
|
+
if self._uses_custom_client:
|
|
114
|
+
return self._client.request(**request_kwargs)
|
|
115
|
+
|
|
116
|
+
max_tries = self._calculate_max_tries()
|
|
117
|
+
attempt = 1
|
|
118
|
+
last_error: Exception | None = None
|
|
119
|
+
|
|
120
|
+
while attempt <= max_tries:
|
|
121
|
+
status_code: int | None = None
|
|
122
|
+
try:
|
|
123
|
+
response = self._client.request(**request_kwargs)
|
|
124
|
+
status_code = response.status_code
|
|
125
|
+
if not self._should_retry(None, status_code, attempt, max_tries):
|
|
126
|
+
return response
|
|
127
|
+
except httpx.TransportError as exc:
|
|
128
|
+
last_error = exc
|
|
129
|
+
if not self._should_retry(exc, None, attempt, max_tries):
|
|
130
|
+
raise
|
|
131
|
+
|
|
132
|
+
if attempt == 1:
|
|
133
|
+
backoff_ms = 0.0
|
|
134
|
+
else:
|
|
135
|
+
base_backoff = min(1000.0 * (2 ** (attempt - 1)), _MAX_BACKOFF_MS)
|
|
136
|
+
jitter = 0.5 + random.random() # Random value between 0.5 and 1.5
|
|
137
|
+
backoff_ms = base_backoff * jitter
|
|
138
|
+
if backoff_ms > 0:
|
|
139
|
+
time.sleep(backoff_ms / 1000.0)
|
|
140
|
+
attempt += 1
|
|
141
|
+
|
|
142
|
+
# Should not reach here, but satisfy type checker
|
|
143
|
+
if last_error:
|
|
144
|
+
raise last_error
|
|
145
|
+
raise RuntimeError(f"Request failed after {max_tries} attempt(s)")
|
|
146
|
+
|
|
147
|
+
# default
|
|
148
|
+
|
|
149
|
+
def create_wallet(
|
|
150
|
+
self,
|
|
151
|
+
body: models.CreateWalletRequest,
|
|
152
|
+
) -> models.CreateWalletResponse:
|
|
153
|
+
"""
|
|
154
|
+
Create a wallet
|
|
155
|
+
"""
|
|
156
|
+
|
|
157
|
+
path = "/v1/wallet"
|
|
158
|
+
params: dict[str, Any] = {}
|
|
159
|
+
headers: Headers = self._config.resolve_headers()
|
|
160
|
+
|
|
161
|
+
accept_value: str | None = None
|
|
162
|
+
|
|
163
|
+
body_media_types = ["application/json"]
|
|
164
|
+
|
|
165
|
+
headers.setdefault("accept", accept_value or "application/json")
|
|
166
|
+
request_kwargs: dict[str, Any] = {
|
|
167
|
+
"method": "POST",
|
|
168
|
+
"url": path,
|
|
169
|
+
"params": params,
|
|
170
|
+
"headers": headers,
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if body is not None:
|
|
174
|
+
self._assign_body(
|
|
175
|
+
request_kwargs,
|
|
176
|
+
body,
|
|
177
|
+
{
|
|
178
|
+
"model": "CreateWalletRequest",
|
|
179
|
+
},
|
|
180
|
+
body_media_types,
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
response = self._request_with_retry(request_kwargs)
|
|
184
|
+
if response.is_success:
|
|
185
|
+
return self._decode_response(response, model=models.CreateWalletResponse)
|
|
186
|
+
|
|
187
|
+
raise UnexpectedStatusError(response.status_code, response.text)
|
|
188
|
+
|
|
189
|
+
def delete_key(
|
|
190
|
+
self,
|
|
191
|
+
body: models.DeleteKeyRequest,
|
|
192
|
+
) -> None:
|
|
193
|
+
"""
|
|
194
|
+
Delete a key
|
|
195
|
+
"""
|
|
196
|
+
|
|
197
|
+
path = "/v1/key"
|
|
198
|
+
params: dict[str, Any] = {}
|
|
199
|
+
headers: Headers = self._config.resolve_headers()
|
|
200
|
+
|
|
201
|
+
accept_value: str | None = None
|
|
202
|
+
|
|
203
|
+
body_media_types = ["application/json"]
|
|
204
|
+
|
|
205
|
+
headers.setdefault("accept", accept_value or "application/json")
|
|
206
|
+
request_kwargs: dict[str, Any] = {
|
|
207
|
+
"method": "DELETE",
|
|
208
|
+
"url": path,
|
|
209
|
+
"params": params,
|
|
210
|
+
"headers": headers,
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if body is not None:
|
|
214
|
+
self._assign_body(
|
|
215
|
+
request_kwargs,
|
|
216
|
+
body,
|
|
217
|
+
{
|
|
218
|
+
"model": "DeleteKeyRequest",
|
|
219
|
+
},
|
|
220
|
+
body_media_types,
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
response = self._request_with_retry(request_kwargs)
|
|
224
|
+
if response.is_success:
|
|
225
|
+
return
|
|
226
|
+
|
|
227
|
+
raise UnexpectedStatusError(response.status_code, response.text)
|
|
228
|
+
|
|
229
|
+
def delete_multisig(
|
|
230
|
+
self,
|
|
231
|
+
body: models.DeleteMultisigRequest,
|
|
232
|
+
) -> None:
|
|
233
|
+
"""
|
|
234
|
+
Delete a multisig
|
|
235
|
+
"""
|
|
236
|
+
|
|
237
|
+
path = "/v1/multisig"
|
|
238
|
+
params: dict[str, Any] = {}
|
|
239
|
+
headers: Headers = self._config.resolve_headers()
|
|
240
|
+
|
|
241
|
+
accept_value: str | None = None
|
|
242
|
+
|
|
243
|
+
body_media_types = ["application/json"]
|
|
244
|
+
|
|
245
|
+
headers.setdefault("accept", accept_value or "application/json")
|
|
246
|
+
request_kwargs: dict[str, Any] = {
|
|
247
|
+
"method": "DELETE",
|
|
248
|
+
"url": path,
|
|
249
|
+
"params": params,
|
|
250
|
+
"headers": headers,
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if body is not None:
|
|
254
|
+
self._assign_body(
|
|
255
|
+
request_kwargs,
|
|
256
|
+
body,
|
|
257
|
+
{
|
|
258
|
+
"model": "DeleteMultisigRequest",
|
|
259
|
+
},
|
|
260
|
+
body_media_types,
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
response = self._request_with_retry(request_kwargs)
|
|
264
|
+
if response.is_success:
|
|
265
|
+
return
|
|
266
|
+
|
|
267
|
+
raise UnexpectedStatusError(response.status_code, response.text)
|
|
268
|
+
|
|
269
|
+
def export_key(
|
|
270
|
+
self,
|
|
271
|
+
body: models.ExportKeyRequest,
|
|
272
|
+
) -> models.ExportKeyResponse:
|
|
273
|
+
"""
|
|
274
|
+
Export a key
|
|
275
|
+
"""
|
|
276
|
+
|
|
277
|
+
path = "/v1/key/export"
|
|
278
|
+
params: dict[str, Any] = {}
|
|
279
|
+
headers: Headers = self._config.resolve_headers()
|
|
280
|
+
|
|
281
|
+
accept_value: str | None = None
|
|
282
|
+
|
|
283
|
+
body_media_types = ["application/json"]
|
|
284
|
+
|
|
285
|
+
headers.setdefault("accept", accept_value or "application/json")
|
|
286
|
+
request_kwargs: dict[str, Any] = {
|
|
287
|
+
"method": "POST",
|
|
288
|
+
"url": path,
|
|
289
|
+
"params": params,
|
|
290
|
+
"headers": headers,
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if body is not None:
|
|
294
|
+
self._assign_body(
|
|
295
|
+
request_kwargs,
|
|
296
|
+
body,
|
|
297
|
+
{
|
|
298
|
+
"model": "ExportKeyRequest",
|
|
299
|
+
},
|
|
300
|
+
body_media_types,
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
response = self._request_with_retry(request_kwargs)
|
|
304
|
+
if response.is_success:
|
|
305
|
+
return self._decode_response(response, model=models.ExportKeyResponse)
|
|
306
|
+
|
|
307
|
+
raise UnexpectedStatusError(response.status_code, response.text)
|
|
308
|
+
|
|
309
|
+
def export_master_key(
|
|
310
|
+
self,
|
|
311
|
+
body: models.ExportMasterKeyRequest,
|
|
312
|
+
) -> models.ExportMasterKeyResponse:
|
|
313
|
+
"""
|
|
314
|
+
Export the master derivation key from a wallet
|
|
315
|
+
"""
|
|
316
|
+
|
|
317
|
+
path = "/v1/master-key/export"
|
|
318
|
+
params: dict[str, Any] = {}
|
|
319
|
+
headers: Headers = self._config.resolve_headers()
|
|
320
|
+
|
|
321
|
+
accept_value: str | None = None
|
|
322
|
+
|
|
323
|
+
body_media_types = ["application/json"]
|
|
324
|
+
|
|
325
|
+
headers.setdefault("accept", accept_value or "application/json")
|
|
326
|
+
request_kwargs: dict[str, Any] = {
|
|
327
|
+
"method": "POST",
|
|
328
|
+
"url": path,
|
|
329
|
+
"params": params,
|
|
330
|
+
"headers": headers,
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
if body is not None:
|
|
334
|
+
self._assign_body(
|
|
335
|
+
request_kwargs,
|
|
336
|
+
body,
|
|
337
|
+
{
|
|
338
|
+
"model": "ExportMasterKeyRequest",
|
|
339
|
+
},
|
|
340
|
+
body_media_types,
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
response = self._request_with_retry(request_kwargs)
|
|
344
|
+
if response.is_success:
|
|
345
|
+
return self._decode_response(response, model=models.ExportMasterKeyResponse)
|
|
346
|
+
|
|
347
|
+
raise UnexpectedStatusError(response.status_code, response.text)
|
|
348
|
+
|
|
349
|
+
def export_multisig(
|
|
350
|
+
self,
|
|
351
|
+
body: models.ExportMultisigRequest,
|
|
352
|
+
) -> models.ExportMultisigResponse:
|
|
353
|
+
"""
|
|
354
|
+
Export multisig address metadata
|
|
355
|
+
"""
|
|
356
|
+
|
|
357
|
+
path = "/v1/multisig/export"
|
|
358
|
+
params: dict[str, Any] = {}
|
|
359
|
+
headers: Headers = self._config.resolve_headers()
|
|
360
|
+
|
|
361
|
+
accept_value: str | None = None
|
|
362
|
+
|
|
363
|
+
body_media_types = ["application/json"]
|
|
364
|
+
|
|
365
|
+
headers.setdefault("accept", accept_value or "application/json")
|
|
366
|
+
request_kwargs: dict[str, Any] = {
|
|
367
|
+
"method": "POST",
|
|
368
|
+
"url": path,
|
|
369
|
+
"params": params,
|
|
370
|
+
"headers": headers,
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
if body is not None:
|
|
374
|
+
self._assign_body(
|
|
375
|
+
request_kwargs,
|
|
376
|
+
body,
|
|
377
|
+
{
|
|
378
|
+
"model": "ExportMultisigRequest",
|
|
379
|
+
},
|
|
380
|
+
body_media_types,
|
|
381
|
+
)
|
|
382
|
+
|
|
383
|
+
response = self._request_with_retry(request_kwargs)
|
|
384
|
+
if response.is_success:
|
|
385
|
+
return self._decode_response(response, model=models.ExportMultisigResponse)
|
|
386
|
+
|
|
387
|
+
raise UnexpectedStatusError(response.status_code, response.text)
|
|
388
|
+
|
|
389
|
+
def generate_key(
|
|
390
|
+
self,
|
|
391
|
+
body: models.GenerateKeyRequest,
|
|
392
|
+
) -> models.GenerateKeyResponse:
|
|
393
|
+
"""
|
|
394
|
+
Generate a key
|
|
395
|
+
"""
|
|
396
|
+
|
|
397
|
+
path = "/v1/key"
|
|
398
|
+
params: dict[str, Any] = {}
|
|
399
|
+
headers: Headers = self._config.resolve_headers()
|
|
400
|
+
|
|
401
|
+
accept_value: str | None = None
|
|
402
|
+
|
|
403
|
+
body_media_types = ["application/json"]
|
|
404
|
+
|
|
405
|
+
headers.setdefault("accept", accept_value or "application/json")
|
|
406
|
+
request_kwargs: dict[str, Any] = {
|
|
407
|
+
"method": "POST",
|
|
408
|
+
"url": path,
|
|
409
|
+
"params": params,
|
|
410
|
+
"headers": headers,
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
if body is not None:
|
|
414
|
+
self._assign_body(
|
|
415
|
+
request_kwargs,
|
|
416
|
+
body,
|
|
417
|
+
{
|
|
418
|
+
"model": "GenerateKeyRequest",
|
|
419
|
+
},
|
|
420
|
+
body_media_types,
|
|
421
|
+
)
|
|
422
|
+
|
|
423
|
+
response = self._request_with_retry(request_kwargs)
|
|
424
|
+
if response.is_success:
|
|
425
|
+
return self._decode_response(response, model=models.GenerateKeyResponse)
|
|
426
|
+
|
|
427
|
+
raise UnexpectedStatusError(response.status_code, response.text)
|
|
428
|
+
|
|
429
|
+
def get_version(
|
|
430
|
+
self,
|
|
431
|
+
*,
|
|
432
|
+
body: models.VersionsRequest | None = None,
|
|
433
|
+
) -> models.VersionsResponse:
|
|
434
|
+
"""
|
|
435
|
+
Retrieves the current version
|
|
436
|
+
"""
|
|
437
|
+
|
|
438
|
+
path = "/versions"
|
|
439
|
+
params: dict[str, Any] = {}
|
|
440
|
+
headers: Headers = self._config.resolve_headers()
|
|
441
|
+
|
|
442
|
+
accept_value: str | None = None
|
|
443
|
+
|
|
444
|
+
body_media_types = ["application/json"]
|
|
445
|
+
|
|
446
|
+
headers.setdefault("accept", accept_value or "application/json")
|
|
447
|
+
request_kwargs: dict[str, Any] = {
|
|
448
|
+
"method": "GET",
|
|
449
|
+
"url": path,
|
|
450
|
+
"params": params,
|
|
451
|
+
"headers": headers,
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
if body is not None:
|
|
455
|
+
self._assign_body(
|
|
456
|
+
request_kwargs,
|
|
457
|
+
body,
|
|
458
|
+
{
|
|
459
|
+
"model": "VersionsRequest",
|
|
460
|
+
},
|
|
461
|
+
body_media_types,
|
|
462
|
+
)
|
|
463
|
+
|
|
464
|
+
response = self._request_with_retry(request_kwargs)
|
|
465
|
+
if response.is_success:
|
|
466
|
+
return self._decode_response(response, model=models.VersionsResponse)
|
|
467
|
+
|
|
468
|
+
raise UnexpectedStatusError(response.status_code, response.text)
|
|
469
|
+
|
|
470
|
+
def get_wallet_info(
|
|
471
|
+
self,
|
|
472
|
+
body: models.WalletInfoRequest,
|
|
473
|
+
) -> models.WalletInfoResponse:
|
|
474
|
+
"""
|
|
475
|
+
Get wallet info
|
|
476
|
+
"""
|
|
477
|
+
|
|
478
|
+
path = "/v1/wallet/info"
|
|
479
|
+
params: dict[str, Any] = {}
|
|
480
|
+
headers: Headers = self._config.resolve_headers()
|
|
481
|
+
|
|
482
|
+
accept_value: str | None = None
|
|
483
|
+
|
|
484
|
+
body_media_types = ["application/json"]
|
|
485
|
+
|
|
486
|
+
headers.setdefault("accept", accept_value or "application/json")
|
|
487
|
+
request_kwargs: dict[str, Any] = {
|
|
488
|
+
"method": "POST",
|
|
489
|
+
"url": path,
|
|
490
|
+
"params": params,
|
|
491
|
+
"headers": headers,
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
if body is not None:
|
|
495
|
+
self._assign_body(
|
|
496
|
+
request_kwargs,
|
|
497
|
+
body,
|
|
498
|
+
{
|
|
499
|
+
"model": "WalletInfoRequest",
|
|
500
|
+
},
|
|
501
|
+
body_media_types,
|
|
502
|
+
)
|
|
503
|
+
|
|
504
|
+
response = self._request_with_retry(request_kwargs)
|
|
505
|
+
if response.is_success:
|
|
506
|
+
return self._decode_response(response, model=models.WalletInfoResponse)
|
|
507
|
+
|
|
508
|
+
raise UnexpectedStatusError(response.status_code, response.text)
|
|
509
|
+
|
|
510
|
+
def import_key(
|
|
511
|
+
self,
|
|
512
|
+
body: models.ImportKeyRequest,
|
|
513
|
+
) -> models.ImportKeyResponse:
|
|
514
|
+
"""
|
|
515
|
+
Import a key
|
|
516
|
+
"""
|
|
517
|
+
|
|
518
|
+
path = "/v1/key/import"
|
|
519
|
+
params: dict[str, Any] = {}
|
|
520
|
+
headers: Headers = self._config.resolve_headers()
|
|
521
|
+
|
|
522
|
+
accept_value: str | None = None
|
|
523
|
+
|
|
524
|
+
body_media_types = ["application/json"]
|
|
525
|
+
|
|
526
|
+
headers.setdefault("accept", accept_value or "application/json")
|
|
527
|
+
request_kwargs: dict[str, Any] = {
|
|
528
|
+
"method": "POST",
|
|
529
|
+
"url": path,
|
|
530
|
+
"params": params,
|
|
531
|
+
"headers": headers,
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
if body is not None:
|
|
535
|
+
self._assign_body(
|
|
536
|
+
request_kwargs,
|
|
537
|
+
body,
|
|
538
|
+
{
|
|
539
|
+
"model": "ImportKeyRequest",
|
|
540
|
+
},
|
|
541
|
+
body_media_types,
|
|
542
|
+
)
|
|
543
|
+
|
|
544
|
+
response = self._request_with_retry(request_kwargs)
|
|
545
|
+
if response.is_success:
|
|
546
|
+
return self._decode_response(response, model=models.ImportKeyResponse)
|
|
547
|
+
|
|
548
|
+
raise UnexpectedStatusError(response.status_code, response.text)
|
|
549
|
+
|
|
550
|
+
def import_multisig(
|
|
551
|
+
self,
|
|
552
|
+
body: models.ImportMultisigRequest,
|
|
553
|
+
) -> models.ImportMultisigResponse:
|
|
554
|
+
"""
|
|
555
|
+
Import a multisig account
|
|
556
|
+
"""
|
|
557
|
+
|
|
558
|
+
path = "/v1/multisig/import"
|
|
559
|
+
params: dict[str, Any] = {}
|
|
560
|
+
headers: Headers = self._config.resolve_headers()
|
|
561
|
+
|
|
562
|
+
accept_value: str | None = None
|
|
563
|
+
|
|
564
|
+
body_media_types = ["application/json"]
|
|
565
|
+
|
|
566
|
+
headers.setdefault("accept", accept_value or "application/json")
|
|
567
|
+
request_kwargs: dict[str, Any] = {
|
|
568
|
+
"method": "POST",
|
|
569
|
+
"url": path,
|
|
570
|
+
"params": params,
|
|
571
|
+
"headers": headers,
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
if body is not None:
|
|
575
|
+
self._assign_body(
|
|
576
|
+
request_kwargs,
|
|
577
|
+
body,
|
|
578
|
+
{
|
|
579
|
+
"model": "ImportMultisigRequest",
|
|
580
|
+
},
|
|
581
|
+
body_media_types,
|
|
582
|
+
)
|
|
583
|
+
|
|
584
|
+
response = self._request_with_retry(request_kwargs)
|
|
585
|
+
if response.is_success:
|
|
586
|
+
return self._decode_response(response, model=models.ImportMultisigResponse)
|
|
587
|
+
|
|
588
|
+
raise UnexpectedStatusError(response.status_code, response.text)
|
|
589
|
+
|
|
590
|
+
def init_wallet_handle(
|
|
591
|
+
self,
|
|
592
|
+
body: models.InitWalletHandleTokenRequest,
|
|
593
|
+
) -> models.InitWalletHandleTokenResponse:
|
|
594
|
+
"""
|
|
595
|
+
Initialize a wallet handle token
|
|
596
|
+
"""
|
|
597
|
+
|
|
598
|
+
path = "/v1/wallet/init"
|
|
599
|
+
params: dict[str, Any] = {}
|
|
600
|
+
headers: Headers = self._config.resolve_headers()
|
|
601
|
+
|
|
602
|
+
accept_value: str | None = None
|
|
603
|
+
|
|
604
|
+
body_media_types = ["application/json"]
|
|
605
|
+
|
|
606
|
+
headers.setdefault("accept", accept_value or "application/json")
|
|
607
|
+
request_kwargs: dict[str, Any] = {
|
|
608
|
+
"method": "POST",
|
|
609
|
+
"url": path,
|
|
610
|
+
"params": params,
|
|
611
|
+
"headers": headers,
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
if body is not None:
|
|
615
|
+
self._assign_body(
|
|
616
|
+
request_kwargs,
|
|
617
|
+
body,
|
|
618
|
+
{
|
|
619
|
+
"model": "InitWalletHandleTokenRequest",
|
|
620
|
+
},
|
|
621
|
+
body_media_types,
|
|
622
|
+
)
|
|
623
|
+
|
|
624
|
+
response = self._request_with_retry(request_kwargs)
|
|
625
|
+
if response.is_success:
|
|
626
|
+
return self._decode_response(response, model=models.InitWalletHandleTokenResponse)
|
|
627
|
+
|
|
628
|
+
raise UnexpectedStatusError(response.status_code, response.text)
|
|
629
|
+
|
|
630
|
+
def list_keys_in_wallet(
|
|
631
|
+
self,
|
|
632
|
+
body: models.ListKeysRequest,
|
|
633
|
+
) -> models.ListKeysResponse:
|
|
634
|
+
"""
|
|
635
|
+
List keys in wallet
|
|
636
|
+
"""
|
|
637
|
+
|
|
638
|
+
path = "/v1/key/list"
|
|
639
|
+
params: dict[str, Any] = {}
|
|
640
|
+
headers: Headers = self._config.resolve_headers()
|
|
641
|
+
|
|
642
|
+
accept_value: str | None = None
|
|
643
|
+
|
|
644
|
+
body_media_types = ["application/json"]
|
|
645
|
+
|
|
646
|
+
headers.setdefault("accept", accept_value or "application/json")
|
|
647
|
+
request_kwargs: dict[str, Any] = {
|
|
648
|
+
"method": "POST",
|
|
649
|
+
"url": path,
|
|
650
|
+
"params": params,
|
|
651
|
+
"headers": headers,
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
if body is not None:
|
|
655
|
+
self._assign_body(
|
|
656
|
+
request_kwargs,
|
|
657
|
+
body,
|
|
658
|
+
{
|
|
659
|
+
"model": "ListKeysRequest",
|
|
660
|
+
},
|
|
661
|
+
body_media_types,
|
|
662
|
+
)
|
|
663
|
+
|
|
664
|
+
response = self._request_with_retry(request_kwargs)
|
|
665
|
+
if response.is_success:
|
|
666
|
+
return self._decode_response(response, model=models.ListKeysResponse)
|
|
667
|
+
|
|
668
|
+
raise UnexpectedStatusError(response.status_code, response.text)
|
|
669
|
+
|
|
670
|
+
def list_multisig(
|
|
671
|
+
self,
|
|
672
|
+
body: models.ListMultisigRequest,
|
|
673
|
+
) -> models.ListMultisigResponse:
|
|
674
|
+
"""
|
|
675
|
+
List multisig accounts
|
|
676
|
+
"""
|
|
677
|
+
|
|
678
|
+
path = "/v1/multisig/list"
|
|
679
|
+
params: dict[str, Any] = {}
|
|
680
|
+
headers: Headers = self._config.resolve_headers()
|
|
681
|
+
|
|
682
|
+
accept_value: str | None = None
|
|
683
|
+
|
|
684
|
+
body_media_types = ["application/json"]
|
|
685
|
+
|
|
686
|
+
headers.setdefault("accept", accept_value or "application/json")
|
|
687
|
+
request_kwargs: dict[str, Any] = {
|
|
688
|
+
"method": "POST",
|
|
689
|
+
"url": path,
|
|
690
|
+
"params": params,
|
|
691
|
+
"headers": headers,
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
if body is not None:
|
|
695
|
+
self._assign_body(
|
|
696
|
+
request_kwargs,
|
|
697
|
+
body,
|
|
698
|
+
{
|
|
699
|
+
"model": "ListMultisigRequest",
|
|
700
|
+
},
|
|
701
|
+
body_media_types,
|
|
702
|
+
)
|
|
703
|
+
|
|
704
|
+
response = self._request_with_retry(request_kwargs)
|
|
705
|
+
if response.is_success:
|
|
706
|
+
return self._decode_response(response, model=models.ListMultisigResponse)
|
|
707
|
+
|
|
708
|
+
raise UnexpectedStatusError(response.status_code, response.text)
|
|
709
|
+
|
|
710
|
+
def list_wallets(
|
|
711
|
+
self,
|
|
712
|
+
*,
|
|
713
|
+
body: models.ListWalletsRequest | None = None,
|
|
714
|
+
) -> models.ListWalletsResponse:
|
|
715
|
+
"""
|
|
716
|
+
List wallets
|
|
717
|
+
"""
|
|
718
|
+
|
|
719
|
+
path = "/v1/wallets"
|
|
720
|
+
params: dict[str, Any] = {}
|
|
721
|
+
headers: Headers = self._config.resolve_headers()
|
|
722
|
+
|
|
723
|
+
accept_value: str | None = None
|
|
724
|
+
|
|
725
|
+
body_media_types = ["application/json"]
|
|
726
|
+
|
|
727
|
+
headers.setdefault("accept", accept_value or "application/json")
|
|
728
|
+
request_kwargs: dict[str, Any] = {
|
|
729
|
+
"method": "GET",
|
|
730
|
+
"url": path,
|
|
731
|
+
"params": params,
|
|
732
|
+
"headers": headers,
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
if body is not None:
|
|
736
|
+
self._assign_body(
|
|
737
|
+
request_kwargs,
|
|
738
|
+
body,
|
|
739
|
+
{
|
|
740
|
+
"model": "ListWalletsRequest",
|
|
741
|
+
},
|
|
742
|
+
body_media_types,
|
|
743
|
+
)
|
|
744
|
+
|
|
745
|
+
response = self._request_with_retry(request_kwargs)
|
|
746
|
+
if response.is_success:
|
|
747
|
+
return self._decode_response(response, model=models.ListWalletsResponse)
|
|
748
|
+
|
|
749
|
+
raise UnexpectedStatusError(response.status_code, response.text)
|
|
750
|
+
|
|
751
|
+
def release_wallet_handle_token(
|
|
752
|
+
self,
|
|
753
|
+
body: models.ReleaseWalletHandleTokenRequest,
|
|
754
|
+
) -> None:
|
|
755
|
+
"""
|
|
756
|
+
Release a wallet handle token
|
|
757
|
+
"""
|
|
758
|
+
|
|
759
|
+
path = "/v1/wallet/release"
|
|
760
|
+
params: dict[str, Any] = {}
|
|
761
|
+
headers: Headers = self._config.resolve_headers()
|
|
762
|
+
|
|
763
|
+
accept_value: str | None = None
|
|
764
|
+
|
|
765
|
+
body_media_types = ["application/json"]
|
|
766
|
+
|
|
767
|
+
headers.setdefault("accept", accept_value or "application/json")
|
|
768
|
+
request_kwargs: dict[str, Any] = {
|
|
769
|
+
"method": "POST",
|
|
770
|
+
"url": path,
|
|
771
|
+
"params": params,
|
|
772
|
+
"headers": headers,
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
if body is not None:
|
|
776
|
+
self._assign_body(
|
|
777
|
+
request_kwargs,
|
|
778
|
+
body,
|
|
779
|
+
{
|
|
780
|
+
"model": "ReleaseWalletHandleTokenRequest",
|
|
781
|
+
},
|
|
782
|
+
body_media_types,
|
|
783
|
+
)
|
|
784
|
+
|
|
785
|
+
response = self._request_with_retry(request_kwargs)
|
|
786
|
+
if response.is_success:
|
|
787
|
+
return
|
|
788
|
+
|
|
789
|
+
raise UnexpectedStatusError(response.status_code, response.text)
|
|
790
|
+
|
|
791
|
+
def rename_wallet(
|
|
792
|
+
self,
|
|
793
|
+
body: models.RenameWalletRequest,
|
|
794
|
+
) -> models.RenameWalletResponse:
|
|
795
|
+
"""
|
|
796
|
+
Rename a wallet
|
|
797
|
+
"""
|
|
798
|
+
|
|
799
|
+
path = "/v1/wallet/rename"
|
|
800
|
+
params: dict[str, Any] = {}
|
|
801
|
+
headers: Headers = self._config.resolve_headers()
|
|
802
|
+
|
|
803
|
+
accept_value: str | None = None
|
|
804
|
+
|
|
805
|
+
body_media_types = ["application/json"]
|
|
806
|
+
|
|
807
|
+
headers.setdefault("accept", accept_value or "application/json")
|
|
808
|
+
request_kwargs: dict[str, Any] = {
|
|
809
|
+
"method": "POST",
|
|
810
|
+
"url": path,
|
|
811
|
+
"params": params,
|
|
812
|
+
"headers": headers,
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
if body is not None:
|
|
816
|
+
self._assign_body(
|
|
817
|
+
request_kwargs,
|
|
818
|
+
body,
|
|
819
|
+
{
|
|
820
|
+
"model": "RenameWalletRequest",
|
|
821
|
+
},
|
|
822
|
+
body_media_types,
|
|
823
|
+
)
|
|
824
|
+
|
|
825
|
+
response = self._request_with_retry(request_kwargs)
|
|
826
|
+
if response.is_success:
|
|
827
|
+
return self._decode_response(response, model=models.RenameWalletResponse)
|
|
828
|
+
|
|
829
|
+
raise UnexpectedStatusError(response.status_code, response.text)
|
|
830
|
+
|
|
831
|
+
def renew_wallet_handle_token(
|
|
832
|
+
self,
|
|
833
|
+
body: models.RenewWalletHandleTokenRequest,
|
|
834
|
+
) -> models.RenewWalletHandleTokenResponse:
|
|
835
|
+
"""
|
|
836
|
+
Renew a wallet handle token
|
|
837
|
+
"""
|
|
838
|
+
|
|
839
|
+
path = "/v1/wallet/renew"
|
|
840
|
+
params: dict[str, Any] = {}
|
|
841
|
+
headers: Headers = self._config.resolve_headers()
|
|
842
|
+
|
|
843
|
+
accept_value: str | None = None
|
|
844
|
+
|
|
845
|
+
body_media_types = ["application/json"]
|
|
846
|
+
|
|
847
|
+
headers.setdefault("accept", accept_value or "application/json")
|
|
848
|
+
request_kwargs: dict[str, Any] = {
|
|
849
|
+
"method": "POST",
|
|
850
|
+
"url": path,
|
|
851
|
+
"params": params,
|
|
852
|
+
"headers": headers,
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
if body is not None:
|
|
856
|
+
self._assign_body(
|
|
857
|
+
request_kwargs,
|
|
858
|
+
body,
|
|
859
|
+
{
|
|
860
|
+
"model": "RenewWalletHandleTokenRequest",
|
|
861
|
+
},
|
|
862
|
+
body_media_types,
|
|
863
|
+
)
|
|
864
|
+
|
|
865
|
+
response = self._request_with_retry(request_kwargs)
|
|
866
|
+
if response.is_success:
|
|
867
|
+
return self._decode_response(response, model=models.RenewWalletHandleTokenResponse)
|
|
868
|
+
|
|
869
|
+
raise UnexpectedStatusError(response.status_code, response.text)
|
|
870
|
+
|
|
871
|
+
def sign_multisig_program(
|
|
872
|
+
self,
|
|
873
|
+
body: models.SignProgramMultisigRequest,
|
|
874
|
+
) -> models.SignProgramMultisigResponse:
|
|
875
|
+
"""
|
|
876
|
+
Sign a program for a multisig account
|
|
877
|
+
"""
|
|
878
|
+
|
|
879
|
+
path = "/v1/multisig/signprogram"
|
|
880
|
+
params: dict[str, Any] = {}
|
|
881
|
+
headers: Headers = self._config.resolve_headers()
|
|
882
|
+
|
|
883
|
+
accept_value: str | None = None
|
|
884
|
+
|
|
885
|
+
body_media_types = ["application/json"]
|
|
886
|
+
|
|
887
|
+
headers.setdefault("accept", accept_value or "application/json")
|
|
888
|
+
request_kwargs: dict[str, Any] = {
|
|
889
|
+
"method": "POST",
|
|
890
|
+
"url": path,
|
|
891
|
+
"params": params,
|
|
892
|
+
"headers": headers,
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
if body is not None:
|
|
896
|
+
self._assign_body(
|
|
897
|
+
request_kwargs,
|
|
898
|
+
body,
|
|
899
|
+
{
|
|
900
|
+
"model": "SignProgramMultisigRequest",
|
|
901
|
+
},
|
|
902
|
+
body_media_types,
|
|
903
|
+
)
|
|
904
|
+
|
|
905
|
+
response = self._request_with_retry(request_kwargs)
|
|
906
|
+
if response.is_success:
|
|
907
|
+
return self._decode_response(response, model=models.SignProgramMultisigResponse)
|
|
908
|
+
|
|
909
|
+
raise UnexpectedStatusError(response.status_code, response.text)
|
|
910
|
+
|
|
911
|
+
def sign_multisig_transaction(
|
|
912
|
+
self,
|
|
913
|
+
body: models.SignMultisigTxnRequest,
|
|
914
|
+
) -> models.SignMultisigResponse:
|
|
915
|
+
"""
|
|
916
|
+
Sign a multisig transaction
|
|
917
|
+
"""
|
|
918
|
+
|
|
919
|
+
path = "/v1/multisig/sign"
|
|
920
|
+
params: dict[str, Any] = {}
|
|
921
|
+
headers: Headers = self._config.resolve_headers()
|
|
922
|
+
|
|
923
|
+
accept_value: str | None = None
|
|
924
|
+
|
|
925
|
+
body_media_types = ["application/json"]
|
|
926
|
+
|
|
927
|
+
headers.setdefault("accept", accept_value or "application/json")
|
|
928
|
+
request_kwargs: dict[str, Any] = {
|
|
929
|
+
"method": "POST",
|
|
930
|
+
"url": path,
|
|
931
|
+
"params": params,
|
|
932
|
+
"headers": headers,
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
if body is not None:
|
|
936
|
+
self._assign_body(
|
|
937
|
+
request_kwargs,
|
|
938
|
+
body,
|
|
939
|
+
{
|
|
940
|
+
"model": "SignMultisigTxnRequest",
|
|
941
|
+
},
|
|
942
|
+
body_media_types,
|
|
943
|
+
)
|
|
944
|
+
|
|
945
|
+
response = self._request_with_retry(request_kwargs)
|
|
946
|
+
if response.is_success:
|
|
947
|
+
return self._decode_response(response, model=models.SignMultisigResponse)
|
|
948
|
+
|
|
949
|
+
raise UnexpectedStatusError(response.status_code, response.text)
|
|
950
|
+
|
|
951
|
+
def sign_program(
|
|
952
|
+
self,
|
|
953
|
+
body: models.SignProgramRequest,
|
|
954
|
+
) -> models.SignProgramResponse:
|
|
955
|
+
"""
|
|
956
|
+
Sign program
|
|
957
|
+
"""
|
|
958
|
+
|
|
959
|
+
path = "/v1/program/sign"
|
|
960
|
+
params: dict[str, Any] = {}
|
|
961
|
+
headers: Headers = self._config.resolve_headers()
|
|
962
|
+
|
|
963
|
+
accept_value: str | None = None
|
|
964
|
+
|
|
965
|
+
body_media_types = ["application/json"]
|
|
966
|
+
|
|
967
|
+
headers.setdefault("accept", accept_value or "application/json")
|
|
968
|
+
request_kwargs: dict[str, Any] = {
|
|
969
|
+
"method": "POST",
|
|
970
|
+
"url": path,
|
|
971
|
+
"params": params,
|
|
972
|
+
"headers": headers,
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
if body is not None:
|
|
976
|
+
self._assign_body(
|
|
977
|
+
request_kwargs,
|
|
978
|
+
body,
|
|
979
|
+
{
|
|
980
|
+
"model": "SignProgramRequest",
|
|
981
|
+
},
|
|
982
|
+
body_media_types,
|
|
983
|
+
)
|
|
984
|
+
|
|
985
|
+
response = self._request_with_retry(request_kwargs)
|
|
986
|
+
if response.is_success:
|
|
987
|
+
return self._decode_response(response, model=models.SignProgramResponse)
|
|
988
|
+
|
|
989
|
+
raise UnexpectedStatusError(response.status_code, response.text)
|
|
990
|
+
|
|
991
|
+
def sign_transaction(
|
|
992
|
+
self,
|
|
993
|
+
body: models.SignTxnRequest,
|
|
994
|
+
) -> models.SignTransactionResponse:
|
|
995
|
+
"""
|
|
996
|
+
Sign a transaction
|
|
997
|
+
"""
|
|
998
|
+
|
|
999
|
+
path = "/v1/transaction/sign"
|
|
1000
|
+
params: dict[str, Any] = {}
|
|
1001
|
+
headers: Headers = self._config.resolve_headers()
|
|
1002
|
+
|
|
1003
|
+
accept_value: str | None = None
|
|
1004
|
+
|
|
1005
|
+
body_media_types = ["application/json"]
|
|
1006
|
+
|
|
1007
|
+
headers.setdefault("accept", accept_value or "application/json")
|
|
1008
|
+
request_kwargs: dict[str, Any] = {
|
|
1009
|
+
"method": "POST",
|
|
1010
|
+
"url": path,
|
|
1011
|
+
"params": params,
|
|
1012
|
+
"headers": headers,
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
if body is not None:
|
|
1016
|
+
self._assign_body(
|
|
1017
|
+
request_kwargs,
|
|
1018
|
+
body,
|
|
1019
|
+
{
|
|
1020
|
+
"model": "SignTxnRequest",
|
|
1021
|
+
},
|
|
1022
|
+
body_media_types,
|
|
1023
|
+
)
|
|
1024
|
+
|
|
1025
|
+
response = self._request_with_retry(request_kwargs)
|
|
1026
|
+
if response.is_success:
|
|
1027
|
+
return self._decode_response(response, model=models.SignTransactionResponse)
|
|
1028
|
+
|
|
1029
|
+
raise UnexpectedStatusError(response.status_code, response.text)
|
|
1030
|
+
|
|
1031
|
+
def _assign_body(
|
|
1032
|
+
self,
|
|
1033
|
+
request_kwargs: dict[str, Any],
|
|
1034
|
+
payload: object,
|
|
1035
|
+
descriptor: dict[str, object],
|
|
1036
|
+
media_types: list[str],
|
|
1037
|
+
) -> None:
|
|
1038
|
+
encoded = self._encode_payload(payload, descriptor)
|
|
1039
|
+
binary_types = {"application/x-binary", "application/octet-stream"}
|
|
1040
|
+
if bool(descriptor.get("is_binary")) or any(mt in binary_types for mt in media_types):
|
|
1041
|
+
if encoded is None:
|
|
1042
|
+
return
|
|
1043
|
+
request_kwargs["content"] = encoded
|
|
1044
|
+
if media_types:
|
|
1045
|
+
request_kwargs.setdefault("headers", {})["content-type"] = media_types[0]
|
|
1046
|
+
else:
|
|
1047
|
+
request_kwargs.setdefault("headers", {})["content-type"] = "application/octet-stream"
|
|
1048
|
+
elif "application/json" in media_types:
|
|
1049
|
+
request_kwargs["json"] = encoded
|
|
1050
|
+
elif "application/msgpack" in media_types:
|
|
1051
|
+
request_kwargs["content"] = msgpack.packb(encoded, use_bin_type=True)
|
|
1052
|
+
request_kwargs.setdefault("headers", {})["content-type"] = "application/msgpack"
|
|
1053
|
+
else:
|
|
1054
|
+
request_kwargs["json"] = encoded
|
|
1055
|
+
|
|
1056
|
+
def _encode_payload(self, payload: object, descriptor: dict[str, object]) -> object:
|
|
1057
|
+
if payload is None:
|
|
1058
|
+
return None
|
|
1059
|
+
if is_dataclass(payload):
|
|
1060
|
+
return to_wire(payload)
|
|
1061
|
+
list_model = descriptor.get("list_model")
|
|
1062
|
+
if list_model and isinstance(payload, list):
|
|
1063
|
+
return [to_wire(item) if is_dataclass(item) else item for item in payload]
|
|
1064
|
+
return payload
|
|
1065
|
+
|
|
1066
|
+
@overload
|
|
1067
|
+
def _decode_response(
|
|
1068
|
+
self,
|
|
1069
|
+
response: httpx.Response,
|
|
1070
|
+
*,
|
|
1071
|
+
model: type[ModelT],
|
|
1072
|
+
is_binary: bool = False,
|
|
1073
|
+
raw_msgpack: bool = False,
|
|
1074
|
+
) -> ModelT: ...
|
|
1075
|
+
|
|
1076
|
+
@overload
|
|
1077
|
+
def _decode_response(
|
|
1078
|
+
self,
|
|
1079
|
+
response: httpx.Response,
|
|
1080
|
+
*,
|
|
1081
|
+
list_model: type[ListModelT],
|
|
1082
|
+
is_binary: bool = False,
|
|
1083
|
+
raw_msgpack: bool = False,
|
|
1084
|
+
) -> list[ListModelT]: ...
|
|
1085
|
+
|
|
1086
|
+
@overload
|
|
1087
|
+
def _decode_response(
|
|
1088
|
+
self,
|
|
1089
|
+
response: httpx.Response,
|
|
1090
|
+
*,
|
|
1091
|
+
type_: type[PrimitiveT],
|
|
1092
|
+
is_binary: bool = False,
|
|
1093
|
+
raw_msgpack: bool = False,
|
|
1094
|
+
) -> PrimitiveT: ...
|
|
1095
|
+
|
|
1096
|
+
@overload
|
|
1097
|
+
def _decode_response(
|
|
1098
|
+
self,
|
|
1099
|
+
response: httpx.Response,
|
|
1100
|
+
*,
|
|
1101
|
+
is_binary: Literal[True],
|
|
1102
|
+
raw_msgpack: bool = False,
|
|
1103
|
+
) -> bytes: ...
|
|
1104
|
+
|
|
1105
|
+
@overload
|
|
1106
|
+
def _decode_response(
|
|
1107
|
+
self,
|
|
1108
|
+
response: httpx.Response,
|
|
1109
|
+
*,
|
|
1110
|
+
raw_msgpack: Literal[True],
|
|
1111
|
+
) -> bytes: ...
|
|
1112
|
+
|
|
1113
|
+
@overload
|
|
1114
|
+
def _decode_response(
|
|
1115
|
+
self,
|
|
1116
|
+
response: httpx.Response,
|
|
1117
|
+
*,
|
|
1118
|
+
type_: None = None,
|
|
1119
|
+
is_binary: bool = False,
|
|
1120
|
+
raw_msgpack: bool = False,
|
|
1121
|
+
) -> object: ...
|
|
1122
|
+
|
|
1123
|
+
def _decode_response(
|
|
1124
|
+
self,
|
|
1125
|
+
response: httpx.Response,
|
|
1126
|
+
*,
|
|
1127
|
+
model: type[Any] | None = None,
|
|
1128
|
+
list_model: type[Any] | None = None,
|
|
1129
|
+
type_: type[Any] | None = None,
|
|
1130
|
+
is_binary: bool = False,
|
|
1131
|
+
raw_msgpack: bool = False,
|
|
1132
|
+
) -> object:
|
|
1133
|
+
if is_binary or raw_msgpack:
|
|
1134
|
+
return response.content
|
|
1135
|
+
content_type = response.headers.get("content-type", "application/json")
|
|
1136
|
+
if "msgpack" in content_type:
|
|
1137
|
+
# Handle msgpack unpacking with support for unhashable keys
|
|
1138
|
+
# Use Unpacker for more control over the unpacking process
|
|
1139
|
+
unpacker = msgpack.Unpacker(
|
|
1140
|
+
raw=True,
|
|
1141
|
+
strict_map_key=False,
|
|
1142
|
+
object_pairs_hook=self._msgpack_pairs_hook,
|
|
1143
|
+
)
|
|
1144
|
+
unpacker.feed(response.content)
|
|
1145
|
+
try:
|
|
1146
|
+
data = unpacker.unpack()
|
|
1147
|
+
except TypeError:
|
|
1148
|
+
# If unpacking fails due to unhashable keys, try without the hook
|
|
1149
|
+
# and handle in normalization
|
|
1150
|
+
unpacker = msgpack.Unpacker(raw=True, strict_map_key=False)
|
|
1151
|
+
unpacker.feed(response.content)
|
|
1152
|
+
data = unpacker.unpack()
|
|
1153
|
+
data = self._normalize_msgpack(data)
|
|
1154
|
+
elif content_type.startswith("application/json"):
|
|
1155
|
+
data = response.json()
|
|
1156
|
+
else:
|
|
1157
|
+
data = response.text
|
|
1158
|
+
if model is not None:
|
|
1159
|
+
return from_wire(model, data)
|
|
1160
|
+
if list_model is not None:
|
|
1161
|
+
return [from_wire(list_model, item) for item in data]
|
|
1162
|
+
if type_ is not None:
|
|
1163
|
+
return data
|
|
1164
|
+
return data
|
|
1165
|
+
|
|
1166
|
+
def _normalize_msgpack(self, value: object) -> object:
|
|
1167
|
+
# Handle pairs returned from msgpack_pairs_hook when keys are unhashable
|
|
1168
|
+
_pair_length = 2
|
|
1169
|
+
if isinstance(value, list) and value and isinstance(value[0], tuple | list) and len(value[0]) == _pair_length:
|
|
1170
|
+
# Convert to dict with normalized keys
|
|
1171
|
+
pairs_dict: dict[object, object] = {}
|
|
1172
|
+
for pair in value:
|
|
1173
|
+
if isinstance(pair, tuple | list) and len(pair) == _pair_length:
|
|
1174
|
+
k, v = pair
|
|
1175
|
+
# For unhashable keys (like dict keys), use a tuple representation
|
|
1176
|
+
try:
|
|
1177
|
+
normalized_key = self._coerce_msgpack_key(k)
|
|
1178
|
+
pairs_dict[normalized_key] = self._normalize_msgpack(v)
|
|
1179
|
+
except TypeError:
|
|
1180
|
+
# Key is unhashable - use tuple representation
|
|
1181
|
+
normalized_key = ("__unhashable__", id(k), str(k))
|
|
1182
|
+
pairs_dict[normalized_key] = self._normalize_msgpack(v)
|
|
1183
|
+
return pairs_dict
|
|
1184
|
+
if isinstance(value, dict):
|
|
1185
|
+
# Safely normalize maps: coerce string/bytes keys, but tolerate complex/unhashable keys
|
|
1186
|
+
try:
|
|
1187
|
+
normalized_dict: dict[object, object] = {}
|
|
1188
|
+
for key, item in value.items():
|
|
1189
|
+
normalized_dict[self._coerce_msgpack_key(key)] = self._normalize_msgpack(item)
|
|
1190
|
+
return normalized_dict
|
|
1191
|
+
except TypeError:
|
|
1192
|
+
# Some maps can decode to object/dict keys; keep original keys and
|
|
1193
|
+
# only normalize values to avoid "unhashable type: 'dict'" errors.
|
|
1194
|
+
for k, item in list(value.items()):
|
|
1195
|
+
value[k] = self._normalize_msgpack(item)
|
|
1196
|
+
return value
|
|
1197
|
+
if isinstance(value, list):
|
|
1198
|
+
return [self._normalize_msgpack(item) for item in value]
|
|
1199
|
+
return value
|
|
1200
|
+
|
|
1201
|
+
def _coerce_msgpack_key(self, key: object) -> object:
|
|
1202
|
+
if isinstance(key, bytes):
|
|
1203
|
+
try:
|
|
1204
|
+
return key.decode("utf-8")
|
|
1205
|
+
except UnicodeDecodeError:
|
|
1206
|
+
return key
|
|
1207
|
+
return key
|
|
1208
|
+
|
|
1209
|
+
def _msgpack_pairs_hook(self, pairs: list[tuple[object, object]] | list[list[object]]) -> dict[object, object]:
|
|
1210
|
+
# Convert pairs to dict, handling unhashable keys by converting them to hashable tuples
|
|
1211
|
+
out: dict[object, object] = {}
|
|
1212
|
+
_hashable_type_tuple = (str, int, float, bool, type(None), bytes)
|
|
1213
|
+
|
|
1214
|
+
for k, v in pairs:
|
|
1215
|
+
if isinstance(k, dict | list | set):
|
|
1216
|
+
# Convert unhashable key to hashable tuple
|
|
1217
|
+
hashable_key: tuple[str, object]
|
|
1218
|
+
if isinstance(k, dict):
|
|
1219
|
+
try:
|
|
1220
|
+
hashable_key = (_UNHASHABLE_PREFIXES["dict"], tuple(sorted(k.items())))
|
|
1221
|
+
except TypeError:
|
|
1222
|
+
hashable_key = (_UNHASHABLE_PREFIXES["dict"], str(k))
|
|
1223
|
+
elif isinstance(k, list):
|
|
1224
|
+
prefix = _UNHASHABLE_PREFIXES["list"]
|
|
1225
|
+
hashable_key = (prefix, tuple(k) if all(isinstance(x, _hashable_type_tuple) for x in k) else str(k))
|
|
1226
|
+
else: # set
|
|
1227
|
+
prefix = _UNHASHABLE_PREFIXES["set"]
|
|
1228
|
+
if all(isinstance(x, _hashable_type_tuple) for x in k):
|
|
1229
|
+
hashable_key = (prefix, tuple(sorted(k)))
|
|
1230
|
+
else:
|
|
1231
|
+
hashable_key = (prefix, str(k))
|
|
1232
|
+
out[hashable_key] = v
|
|
1233
|
+
else:
|
|
1234
|
+
# Key should be hashable, use as-is
|
|
1235
|
+
try:
|
|
1236
|
+
out[k] = v
|
|
1237
|
+
except TypeError:
|
|
1238
|
+
# Unexpected unhashable type, convert to tuple
|
|
1239
|
+
out[(_UNHASHABLE_PREFIXES["generic"], str(type(k).__name__), str(k))] = v
|
|
1240
|
+
return out
|