diff --git a/.test-env b/.test-env index a321a7f1..049289d7 100644 --- a/.test-env +++ b/.test-env @@ -1,6 +1,6 @@ # Configs for testing repo download: SDK_TESTING_URL="https://github.com/algorand/algorand-sdk-testing" -SDK_TESTING_BRANCH="V2" +SDK_TESTING_BRANCH="master" SDK_TESTING_HARNESS="test-harness" INSTALL_ONLY=0 diff --git a/CHANGELOG.md b/CHANGELOG.md index 26e29c14..412efb7e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Changelog +# v2.6.1 + + + +## What's Changed +### Bugfixes +* algod: Even in the face of urllib.error.HTTPError, return the json by @jannotti in https://github.com/algorand/py-algorand-sdk/pull/529 +* Fix: Pass args to underlying `kmd_request` function, including timeout by @jasonpaulos in https://github.com/algorand/py-algorand-sdk/pull/545 + + +**Full Changelog**: https://github.com/algorand/py-algorand-sdk/compare/v2.6.0...v2.6.1 + # v2.6.0 diff --git a/algosdk/error.py b/algosdk/error.py index 6de6afee..78ae7562 100644 --- a/algosdk/error.py +++ b/algosdk/error.py @@ -178,9 +178,10 @@ def __init__(self, msg): class AlgodHTTPError(Exception): - def __init__(self, msg, code=None): + def __init__(self, msg, code=None, data=None): super().__init__(msg) self.code = code + self.data = data class AlgodResponseError(Exception): diff --git a/algosdk/kmd.py b/algosdk/kmd.py index 70dc0edd..1f6848c3 100644 --- a/algosdk/kmd.py +++ b/algosdk/kmd.py @@ -1,5 +1,6 @@ import base64 import json +from typing import Any import urllib.error from urllib import parse from urllib.request import Request, urlopen @@ -66,7 +67,7 @@ def kmd_request(self, method, requrl, params=None, data=None, timeout=30): raise error.KMDHTTPError(e) return json.loads(resp.read().decode("utf-8")) - def versions(self): + def versions(self, **kwargs: Any): """ Get kmd versions. @@ -74,9 +75,9 @@ def versions(self): str[]: list of versions """ req = "/versions" - return self.kmd_request("GET", req)["versions"] + return self.kmd_request("GET", req, **kwargs)["versions"] - def list_wallets(self): + def list_wallets(self, **kwargs: Any): """ List all wallets hosted on node. @@ -84,14 +85,19 @@ def list_wallets(self): dict[]: list of dictionaries containing wallet information """ req = "/wallets" - res = self.kmd_request("GET", req) + res = self.kmd_request("GET", req, **kwargs) if "wallets" in res: return res["wallets"] else: return [] def create_wallet( - self, name, pswd, driver_name="sqlite", master_deriv_key=None + self, + name, + pswd, + driver_name="sqlite", + master_deriv_key=None, + **kwargs: Any ): """ Create a new wallet. @@ -113,9 +119,9 @@ def create_wallet( } if master_deriv_key: query["master_derivation_key"] = master_deriv_key - return self.kmd_request("POST", req, data=query)["wallet"] + return self.kmd_request("POST", req, data=query, **kwargs)["wallet"] - def get_wallet(self, handle): + def get_wallet(self, handle, **kwargs: Any): """ Get wallet information. @@ -127,9 +133,11 @@ def get_wallet(self, handle): """ req = "/wallet/info" query = {"wallet_handle_token": handle} - return self.kmd_request("POST", req, data=query)["wallet_handle"] + return self.kmd_request("POST", req, data=query, **kwargs)[ + "wallet_handle" + ] - def init_wallet_handle(self, id, password): + def init_wallet_handle(self, id, password, **kwargs: Any): """ Initialize a handle for the wallet. @@ -142,9 +150,11 @@ def init_wallet_handle(self, id, password): """ req = "/wallet/init" query = {"wallet_id": id, "wallet_password": password} - return self.kmd_request("POST", req, data=query)["wallet_handle_token"] + return self.kmd_request("POST", req, data=query, **kwargs)[ + "wallet_handle_token" + ] - def release_wallet_handle(self, handle): + def release_wallet_handle(self, handle, **kwargs: Any): """ Deactivate the handle for the wallet. @@ -156,10 +166,10 @@ def release_wallet_handle(self, handle): """ req = "/wallet/release" query = {"wallet_handle_token": handle} - result = self.kmd_request("POST", req, data=query) + result = self.kmd_request("POST", req, data=query, **kwargs) return result == {} - def renew_wallet_handle(self, handle): + def renew_wallet_handle(self, handle, **kwargs: Any): """ Renew the wallet handle. @@ -171,9 +181,11 @@ def renew_wallet_handle(self, handle): """ req = "/wallet/renew" query = {"wallet_handle_token": handle} - return self.kmd_request("POST", req, data=query)["wallet_handle"] + return self.kmd_request("POST", req, data=query, **kwargs)[ + "wallet_handle" + ] - def rename_wallet(self, id, password, new_name): + def rename_wallet(self, id, password, new_name, **kwargs: Any): """ Rename the wallet. @@ -191,9 +203,9 @@ def rename_wallet(self, id, password, new_name): "wallet_password": password, "wallet_name": new_name, } - return self.kmd_request("POST", req, data=query)["wallet"] + return self.kmd_request("POST", req, data=query, **kwargs)["wallet"] - def export_master_derivation_key(self, handle, password): + def export_master_derivation_key(self, handle, password, **kwargs: Any): """ Get the wallet's master derivation key. @@ -206,10 +218,10 @@ def export_master_derivation_key(self, handle, password): """ req = "/master-key/export" query = {"wallet_handle_token": handle, "wallet_password": password} - result = self.kmd_request("POST", req, data=query) + result = self.kmd_request("POST", req, data=query, **kwargs) return result["master_derivation_key"] - def import_key(self, handle, private_key): + def import_key(self, handle, private_key, **kwargs: Any): """ Import an account into the wallet. @@ -222,9 +234,9 @@ def import_key(self, handle, private_key): """ req = "/key/import" query = {"wallet_handle_token": handle, "private_key": private_key} - return self.kmd_request("POST", req, data=query)["address"] + return self.kmd_request("POST", req, data=query, **kwargs)["address"] - def export_key(self, handle, password, address): + def export_key(self, handle, password, address, **kwargs: Any): """ Return an account private key. @@ -242,9 +254,11 @@ def export_key(self, handle, password, address): "wallet_password": password, "address": address, } - return self.kmd_request("POST", req, data=query)["private_key"] + return self.kmd_request("POST", req, data=query, **kwargs)[ + "private_key" + ] - def generate_key(self, handle, display_mnemonic=True): + def generate_key(self, handle, display_mnemonic=True, **kwargs: Any): """ Generate a key in the wallet. @@ -258,9 +272,9 @@ def generate_key(self, handle, display_mnemonic=True): """ req = "/key" query = {"wallet_handle_token": handle} - return self.kmd_request("POST", req, data=query)["address"] + return self.kmd_request("POST", req, data=query, **kwargs)["address"] - def delete_key(self, handle, password, address): + def delete_key(self, handle, password, address, **kwargs: Any): """ Delete a key in the wallet. @@ -278,10 +292,10 @@ def delete_key(self, handle, password, address): "wallet_password": password, "address": address, } - result = self.kmd_request("DELETE", req, data=query) + result = self.kmd_request("DELETE", req, data=query, **kwargs) return result == {} - def list_keys(self, handle): + def list_keys(self, handle, **kwargs: Any): """ List all keys in the wallet. @@ -294,12 +308,14 @@ def list_keys(self, handle): req = "/key/list" query = {"wallet_handle_token": handle} - result = self.kmd_request("POST", req, data=query) + result = self.kmd_request("POST", req, data=query, **kwargs) if result: return result["addresses"] return [] - def sign_transaction(self, handle, password, txn, signing_address=None): + def sign_transaction( + self, handle, password, txn, signing_address=None, **kwargs: Any + ): """ Sign a transaction. @@ -322,11 +338,11 @@ def sign_transaction(self, handle, password, txn, signing_address=None): } if signing_address: query["public_key"] = signing_address - result = self.kmd_request("POST", req, data=query) + result = self.kmd_request("POST", req, data=query, **kwargs) result = result["signed_transaction"] return encoding.msgpack_decode(result) - def list_multisig(self, handle): + def list_multisig(self, handle, **kwargs: Any): """ List all multisig accounts in the wallet. @@ -338,12 +354,12 @@ def list_multisig(self, handle): """ req = "/multisig/list" query = {"wallet_handle_token": handle} - result = self.kmd_request("POST", req, data=query) + result = self.kmd_request("POST", req, data=query, **kwargs) if result == {}: return [] return result["addresses"] - def import_multisig(self, handle, multisig): + def import_multisig(self, handle, multisig, **kwargs: Any): """ Import a multisig account into the wallet. @@ -364,9 +380,9 @@ def import_multisig(self, handle, multisig): for s in multisig.subsigs ], } - return self.kmd_request("POST", req, data=query)["address"] + return self.kmd_request("POST", req, data=query, **kwargs)["address"] - def export_multisig(self, handle, address): + def export_multisig(self, handle, address, **kwargs: Any): """ Export a multisig account. @@ -379,7 +395,7 @@ def export_multisig(self, handle, address): """ req = "/multisig/export" query = {"wallet_handle_token": handle, "address": address} - result = self.kmd_request("POST", req, data=query) + result = self.kmd_request("POST", req, data=query, **kwargs) pks = result["pks"] pks = [encoding.encode_address(base64.b64decode(p)) for p in pks] msig = transaction.Multisig( @@ -387,7 +403,7 @@ def export_multisig(self, handle, address): ) return msig - def delete_multisig(self, handle, password, address): + def delete_multisig(self, handle, password, address, **kwargs: Any): """ Delete a multisig account. @@ -405,10 +421,12 @@ def delete_multisig(self, handle, password, address): "wallet_password": password, "address": address, } - result = self.kmd_request("DELETE", req, data=query) + result = self.kmd_request("DELETE", req, data=query, **kwargs) return result == {} - def sign_multisig_transaction(self, handle, password, public_key, mtx): + def sign_multisig_transaction( + self, handle, password, public_key, mtx, **kwargs: Any + ): """ Sign a multisig transaction for the given public key. @@ -439,7 +457,9 @@ def sign_multisig_transaction(self, handle, password, public_key, mtx): signer = base64.b64encode(encoding.decode_address(mtx.auth_addr)) query["signer"] = signer.decode() - result = self.kmd_request("POST", req, data=query)["multisig"] + result = self.kmd_request("POST", req, data=query, **kwargs)[ + "multisig" + ] msig = encoding.msgpack_decode(result) mtx.multisig = msig return mtx diff --git a/algosdk/v2client/algod.py b/algosdk/v2client/algod.py index 431f7715..f39aa77e 100644 --- a/algosdk/v2client/algod.py +++ b/algosdk/v2client/algod.py @@ -107,10 +107,13 @@ def algod_request( except urllib.error.HTTPError as e: code = e.code es = e.read().decode("utf-8") + m = e # If json.loads() fails, we'll return e itself + j = {} try: - e = json.loads(es)["message"] + j = json.loads(es) + m = j["message"] finally: - raise error.AlgodHTTPError(e, code) + raise error.AlgodHTTPError(m, code, j.get("data")) if response_format == "json": try: return json.load(resp) diff --git a/examples/inspect-error.py b/examples/inspect-error.py new file mode 100755 index 00000000..427db724 --- /dev/null +++ b/examples/inspect-error.py @@ -0,0 +1,20 @@ +from algosdk import error, transaction + +from utils import get_algod_client, algod_env + +algod = get_algod_client(*algod_env()) + +# This program is "#pragma version 5, +". It will fail because no arguments are on the stack. +lsig = transaction.LogicSigAccount(b"\x05\x08") +sender = lsig.address() +# Get suggested parameters +params = algod.suggested_params() + +amount = 10000 +txn = transaction.PaymentTxn(sender, params, sender, amount) +lstx = transaction.LogicSigTransaction(txn, lsig) +try: + txid = algod.send_transaction(lstx) + print("Impossible! Exception will have been thrown") +except error.AlgodHTTPError as e: + print(e.data) diff --git a/examples/utils.py b/examples/utils.py index d5c3a8bc..d760ec21 100644 --- a/examples/utils.py +++ b/examples/utils.py @@ -93,6 +93,23 @@ class SandboxAccount: signer: AccountTransactionSigner +def algod_env(): + algodata = os.environ.get("ALGORAND_DATA") + if not algodata: + return () + try: + token = ( + open(os.path.join(algodata, "algod.token"), "rt").read().strip() + ) + net = ( + "http://" + + open(os.path.join(algodata, "algod.net"), "rt").read().strip() + ) + return (net, token) + except FileNotFoundError: + return () + + def get_accounts( kmd_address: str = KMD_URL, kmd_token: str = KMD_TOKEN, diff --git a/setup.py b/setup.py index f7fb11b3..a30301cf 100644 --- a/setup.py +++ b/setup.py @@ -9,7 +9,7 @@ description="Algorand SDK in Python", author="Algorand", author_email="pypiservice@algorand.com", - version="2.6.0", + version="2.6.1", long_description=long_description, long_description_content_type="text/markdown", url="https://github.com/algorand/py-algorand-sdk",