Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FOR REVIEW ONLY: py-algorand-sdk v2.0.0 #430

Merged
merged 8 commits into from
Jan 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ jobs:
- checkout
- run: pip install -r requirements.txt
- run: black --check .
- run: mypy algosdk
- run: pytest tests/unit_tests
integration-test:
parameters:
Expand Down
23 changes: 23 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,28 @@
# Changelog

# v2.0.0

## What's Changed
### Breaking Changes

* Remove v1 algod API (`algosdk/algod.py`) due to API end-of-life (2022-12-01). Instead, use v2 algod API (`algosdk/v2client/algod.py`).
* Remove `algosdk.future` package. Move package contents to `algosdk`.
* Remove `encoding.future_msgpack_decode` method in favor of `encoding.msgpack_decode` method.
* Remove `cost` field in `DryrunTxnResult` in favor of 2 fields: `budget-added` and `budget-consumed`. `cost` can be derived by `budget-consumed - budget-added`.
* Remove `mnemonic.to_public_key` in favor of `account.address_from_private_key`.
* Remove logicsig templates, `algosdk/data/langspec.json` and all methods in `logic` depending on it.

### Bugfixes
* Fix: populate_foreign_array offset logic by @jgomezst in https://github.com/algorand/py-algorand-sdk/pull/406

### Enhancements
* v2: Breaking changes from v1 to v2.0.0 by @ahangsu in https://github.com/algorand/py-algorand-sdk/pull/415
* v2: Delete more references to `langspec` by @algochoi in https://github.com/algorand/py-algorand-sdk/pull/426
* LogicSig: Add LogicSig usage disclaimer by @michaeldiamant in https://github.com/algorand/py-algorand-sdk/pull/424
* Infrastructure: Only package `algosdk` in `setup.py` by @algochoi in https://github.com/algorand/py-algorand-sdk/pull/428
* Tests: Introduce type linting with mypy by @jdtzmn in https://github.com/algorand/py-algorand-sdk/pull/397


# v1.20.2

## What's Changed
Expand Down
3 changes: 2 additions & 1 deletion MANIFEST.in
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
include algosdk/data/langspec.json
global-include *.pyi
global-include *.typed
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ Format code:

* `black .`

Lint types:

* `mypy algosdk`

## Quick start

Here's a simple example you can run without a node.
Expand Down
3 changes: 0 additions & 3 deletions algosdk/__init__.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
from . import abi
from . import account
from . import algod
from . import auction
from . import constants
from . import dryrun_results
from . import encoding
from . import error
from . import future
from . import kmd
from . import logic
from . import mnemonic
from . import template
from . import transaction
from . import util
from . import v2client
Expand Down
11 changes: 5 additions & 6 deletions algosdk/abi/address_type.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Union
from typing import Union, cast

from algosdk.abi.base_type import ABIType
from algosdk.abi.byte_type import ByteType
Expand Down Expand Up @@ -53,15 +53,16 @@ def encode(self, value: Union[str, bytes]) -> bytes:
value = encoding.decode_address(value)
except Exception as e:
raise error.ABIEncodingError(
"cannot encode the following address: {}".format(value)
f"cannot encode the following address: {value!r}"
) from e
elif (
not (isinstance(value, bytes) or isinstance(value, bytearray))
or len(value) != 32
):
raise error.ABIEncodingError(
"cannot encode the following public key: {}".format(value)
f"cannot encode the following public key: {value!r}"
)
value = cast(bytes, value)
return bytes(value)

def decode(self, bytestring: Union[bytearray, bytes]) -> str:
Expand All @@ -82,9 +83,7 @@ def decode(self, bytestring: Union[bytearray, bytes]) -> str:
or len(bytestring) != 32
):
raise error.ABIEncodingError(
"address string must be in bytes and correspond to a byte[32]: {}".format(
bytestring
)
f"address string must be in bytes and correspond to a byte[32]: {bytestring!r}"
)
# Return the base32 encoded address string
return encoding.encode_address(bytestring)
4 changes: 1 addition & 3 deletions algosdk/abi/array_dynamic_type.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,7 @@ def encode(self, value_array: Union[List[Any], bytes, bytearray]) -> bytes:
or isinstance(value_array, bytearray)
) and not isinstance(self.child_type, ByteType):
raise error.ABIEncodingError(
"cannot pass in bytes when the type of the array is not ByteType: {}".format(
value_array
)
f"cannot pass in bytes when the type of the array is not ByteType: {value_array!r}"
)
converted_tuple = self._to_tuple_type(len(value_array))
length_to_encode = len(converted_tuple.child_types).to_bytes(
Expand Down
4 changes: 1 addition & 3 deletions algosdk/abi/array_static_type.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,7 @@ def encode(self, value_array: Union[List[Any], bytes, bytearray]) -> bytes:
or isinstance(value_array, bytearray)
) and not isinstance(self.child_type, ByteType):
raise error.ABIEncodingError(
"cannot pass in bytes when the type of the array is not ByteType: {}".format(
value_array
)
f"cannot pass in bytes when the type of the array is not ByteType: {value_array!r}"
)
converted_tuple = self._to_tuple_type()
return converted_tuple.encode(value_array)
Expand Down
11 changes: 4 additions & 7 deletions algosdk/abi/base_type.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,8 @@ def from_string(s: str) -> "ABIType":
elif s.endswith("]"):
matches = re.search(STATIC_ARRAY_REGEX, s)
try:
static_length = int(matches.group(2))
array_type = ABIType.from_string(matches.group(1))
static_length = int(matches.group(2)) # type: ignore[union-attr] # we allow attribute errors to be caught
array_type = ABIType.from_string(matches.group(1)) # type: ignore[union-attr] # we allow attribute errors to be caught
return ArrayStaticType(array_type, static_length)
except Exception as e:
raise error.ABITypeError(
Expand All @@ -103,8 +103,8 @@ def from_string(s: str) -> "ABIType":
elif s.startswith("ufixed"):
matches = re.search(UFIXED_REGEX, s)
try:
bit_size = int(matches.group(1))
precision = int(matches.group(2))
bit_size = int(matches.group(1)) # type: ignore[union-attr] # we allow attribute errors to be caught
precision = int(matches.group(2)) # type: ignore[union-attr] # we allow attribute errors to be caught
return UfixedType(bit_size, precision)
except Exception as e:
raise error.ABITypeError(
Expand All @@ -124,9 +124,6 @@ def from_string(s: str) -> "ABIType":
if isinstance(tup, str):
tt = ABIType.from_string(tup)
tuple_list.append(tt)
elif isinstance(tup, list):
tts = [ABIType.from_string(t_) for t_ in tup]
tuple_list.append(tts)
else:
raise error.ABITypeError(
"cannot convert {} to an ABI type".format(tup)
Expand Down
6 changes: 2 additions & 4 deletions algosdk/abi/bool_type.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,15 +60,13 @@ def decode(self, bytestring: Union[bytes, bytearray]) -> bool:
or len(bytestring) != 1
):
raise error.ABIEncodingError(
"value string must be in bytes and correspond to a bool: {}".format(
bytestring
)
f"value string must be in bytes and correspond to a bool: {bytestring!r}"
)
if bytestring == b"\x80":
return True
elif bytestring == b"\x00":
return False
else:
raise error.ABIEncodingError(
"boolean value could not be decoded: {}".format(bytestring)
f"boolean value could not be decoded: {bytestring!r}"
)
8 changes: 3 additions & 5 deletions algosdk/abi/byte_type.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,15 @@ def encode(self, value: int) -> bytes:
)
return bytes([value])

def decode(self, bytestring: Union[bytes, bytearray]) -> bytes:
def decode(self, bytestring: Union[bytes, bytearray]) -> int:
"""
Decodes a bytestring to a single byte.

Args:
bytestring (bytes | bytearray): bytestring to be decoded

Returns:
bytes: byte of the encoded bytestring
int: byte value of the encoded bytestring
"""
if (
not (
Expand All @@ -60,8 +60,6 @@ def decode(self, bytestring: Union[bytes, bytearray]) -> bytes:
or len(bytestring) != 1
):
raise error.ABIEncodingError(
"value string must be in bytes and correspond to a byte: {}".format(
bytestring
)
f"value string must be in bytes and correspond to a byte: {bytestring!r}"
)
return bytestring[0]
36 changes: 26 additions & 10 deletions algosdk/abi/contract.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,22 @@
import json
from typing import Dict, List, Union
from typing import Dict, List, Union, Optional, TypedDict

from algosdk.abi.method import Method, get_method_by_name
from algosdk.abi.method import Method, MethodDict, get_method_by_name


class NetworkInfoDict(TypedDict):
appID: int


# In Python 3.11+ the following classes should be combined using `NotRequired`
class ContractDict_Optional(TypedDict, total=False):
desc: str


class ContractDict(ContractDict_Optional):
name: str
methods: List[MethodDict]
networks: Dict[str, NetworkInfoDict]


class Contract:
Expand All @@ -20,8 +35,8 @@ def __init__(
self,
name: str,
methods: List[Method],
desc: str = None,
networks: Dict[str, "NetworkInfo"] = None,
desc: Optional[str] = None,
networks: Optional[Dict[str, "NetworkInfo"]] = None,
) -> None:
self.name = name
self.methods = methods
Expand All @@ -43,11 +58,12 @@ def from_json(resp: Union[str, bytes, bytearray]) -> "Contract":
d = json.loads(resp)
return Contract.undictify(d)

def dictify(self) -> dict:
d = {}
d["name"] = self.name
d["methods"] = [m.dictify() for m in self.methods]
d["networks"] = {k: v.dictify() for k, v in self.networks.items()}
def dictify(self) -> ContractDict:
d: ContractDict = {
"name": self.name,
"methods": [m.dictify() for m in self.methods],
"networks": {k: v.dictify() for k, v in self.networks.items()},
}
if self.desc is not None:
d["desc"] = self.desc
return d
Expand Down Expand Up @@ -84,7 +100,7 @@ def __eq__(self, o: object) -> bool:
return False
return self.app_id == o.app_id

def dictify(self) -> dict:
def dictify(self) -> NetworkInfoDict:
return {"appID": self.app_id}

@staticmethod
Expand Down
24 changes: 17 additions & 7 deletions algosdk/abi/interface.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
import json
from typing import List, Union
from typing import List, Union, Optional, TypedDict

from algosdk.abi.method import Method, get_method_by_name
from algosdk.abi.method import Method, MethodDict, get_method_by_name

# In Python 3.11+ the following classes should be combined using `NotRequired`
class InterfaceDict_Optional(TypedDict, total=False):
desc: str


class InterfaceDict(InterfaceDict_Optional):
name: str
methods: List[MethodDict]


class Interface:
Expand All @@ -15,7 +24,7 @@ class Interface:
"""

def __init__(
self, name: str, methods: List[Method], desc: str = None
self, name: str, methods: List[Method], desc: Optional[str] = None
) -> None:
self.name = name
self.methods = methods
Expand All @@ -35,10 +44,11 @@ def from_json(resp: Union[str, bytes, bytearray]) -> "Interface":
d = json.loads(resp)
return Interface.undictify(d)

def dictify(self) -> dict:
d = {}
d["name"] = self.name
d["methods"] = [m.dictify() for m in self.methods]
def dictify(self) -> InterfaceDict:
d: InterfaceDict = {
"name": self.name,
"methods": [m.dictify() for m in self.methods],
}
if self.desc:
d["desc"] = self.desc
return d
Expand Down
36 changes: 25 additions & 11 deletions algosdk/abi/method.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,20 @@
import json
from typing import List, Union
from typing import List, Union, Optional, TypedDict

from Cryptodome.Hash import SHA512

from algosdk import abi, constants, error

# In Python 3.11+ the following classes should be combined using `NotRequired`
class MethodDict_Optional(TypedDict, total=False):
desc: str


class MethodDict(MethodDict_Optional):
name: str
args: List[dict]
returns: dict


class Method:
"""
Expand All @@ -23,7 +33,7 @@ def __init__(
name: str,
args: List["Argument"],
returns: "Returns",
desc: str = None,
desc: Optional[str] = None,
) -> None:
self.name = name
self.args = args
Expand Down Expand Up @@ -108,11 +118,12 @@ def from_signature(s: str) -> "Method":
return_type = Returns(tokens[-1])
return Method(name=tokens[0], args=argument_list, returns=return_type)

def dictify(self) -> dict:
d = {}
d["name"] = self.name
d["args"] = [arg.dictify() for arg in self.args]
d["returns"] = self.returns.dictify()
def dictify(self) -> MethodDict:
d: MethodDict = {
"name": self.name,
"args": [arg.dictify() for arg in self.args],
"returns": self.returns.dictify(),
}
if self.desc:
d["desc"] = self.desc
return d
Expand Down Expand Up @@ -156,12 +167,15 @@ class Argument:
"""

def __init__(
self, arg_type: str, name: str = None, desc: str = None
self,
arg_type: str,
name: Optional[str] = None,
desc: Optional[str] = None,
) -> None:
if abi.is_abi_transaction_type(arg_type) or abi.is_abi_reference_type(
arg_type
):
self.type = arg_type
self.type: Union[str, abi.ABIType] = arg_type
else:
# If the type cannot be parsed into an ABI type, it will error
self.type = abi.ABIType.from_string(arg_type)
Expand Down Expand Up @@ -208,9 +222,9 @@ class Returns:
# Represents a void return.
VOID = "void"

def __init__(self, arg_type: str, desc: str = None) -> None:
def __init__(self, arg_type: str, desc: Optional[str] = None) -> None:
if arg_type == "void":
self.type = self.VOID
self.type: Union[str, abi.ABIType] = self.VOID
else:
# If the type cannot be parsed into an ABI type, it will error.
self.type = abi.ABIType.from_string(arg_type)
Expand Down
2 changes: 1 addition & 1 deletion algosdk/abi/transaction.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from typing import Any

from algosdk import constants
from algosdk.future.transaction import Transaction
from algosdk.transaction import Transaction


class ABITransactionType:
Expand Down
Loading