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

Tests: Introduce type linting with mypy #397

Merged
merged 27 commits into from
Dec 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
b285255
Add mypy dependency and include it in CI
jdtzmn Nov 8, 2022
2bbc8a3
Include documentation for type linting
jdtzmn Nov 8, 2022
ea663f1
Fix type errors in `constants.py`
jdtzmn Nov 10, 2022
8df4213
Fix type errors in `source_map.py`
jdtzmn Nov 10, 2022
1dac040
Fix type errors in `dryrun_results.py`
jdtzmn Nov 10, 2022
80f367f
Add msgpack-types package
jdtzmn Nov 10, 2022
217a022
Fix type errors in `template.py`
jdtzmn Nov 10, 2022
ab70c7d
Fix type errors in `future/template.py`
jdtzmn Nov 10, 2022
c1e6504
Fix type errors in `future/transaction.py`
jdtzmn Nov 10, 2022
6714597
Fix type errors in `base_type.py`
jdtzmn Nov 10, 2022
1559a62
Fix string format errors with bytestrings
jdtzmn Nov 15, 2022
3d19951
Fix type errors in `tuple_type.py`
jdtzmn Nov 15, 2022
748db3d
Fix type errors in `method.py`
jdtzmn Nov 15, 2022
56659b4
Fix type errors in `interface.py`
jdtzmn Nov 15, 2022
4f40018
Fix type errors in `contract.py`
jdtzmn Nov 15, 2022
6acce21
Fix type errors in `dryrun.py`
jdtzmn Nov 15, 2022
8cf2b06
Fix type errors in `atomic_transaction_composer.py`
jdtzmn Nov 17, 2022
8dcd5a0
Fix formatter errors
jdtzmn Nov 17, 2022
34593aa
Fix unsupported types before Python 3.9
jdtzmn Nov 17, 2022
d82af55
Use `cast` instead of `hasattr` when inside an `issubclass` conditional
jdtzmn Nov 22, 2022
ef693d8
Specify ignored errors
jdtzmn Nov 22, 2022
6225ff7
Include mypy configuration
jdtzmn Nov 22, 2022
c082b1c
Add py.typed marker so mypy knows we include types
jdtzmn Nov 22, 2022
41b5a97
Add return type to TransactionSigner.sign_transactions (#3)
michaeldiamant Nov 29, 2022
1b643b0
Rename `SignedTransaction` to `GenericSignedTransaction`
jdtzmn Nov 29, 2022
afe769c
Some changes based on mypy: (#1)
algochoi Nov 29, 2022
24175d3
Add url metadata to `setup.py`
jdtzmn Dec 5, 2022
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
2 changes: 2 additions & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
include algosdk/data/langspec.json
global-include *.pyi
michaeldiamant marked this conversation as resolved.
Show resolved Hide resolved
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
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:
jdtzmn marked this conversation as resolved.
Show resolved Hide resolved
"""
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
Loading