diff --git a/mypy.ini b/mypy.ini new file mode 100644 index 0000000000..04cf2e47fa --- /dev/null +++ b/mypy.ini @@ -0,0 +1,13 @@ +[mypy] +warn_unused_ignores = True +ignore_missing_imports = True +strict_optional = False +check_untyped_defs = True +disallow_incomplete_defs = True +disallow_untyped_defs = True +disallow_any_generics = True +; todo: disallow untyped calls after entire web3 codebase has type hints +; disallow_untyped_calls = True +warn_redundant_casts = True +warn_unused_configs = True +strict_equality = True diff --git a/newsfragments/1488.misc.rst b/newsfragments/1488.misc.rst new file mode 100644 index 0000000000..0a41c84e8c --- /dev/null +++ b/newsfragments/1488.misc.rst @@ -0,0 +1 @@ +Start adding type hints to web3, beginning with web3.main diff --git a/setup.py b/setup.py index bea2079133..a5ccc36057 100644 --- a/setup.py +++ b/setup.py @@ -13,6 +13,7 @@ 'linter': [ "flake8==3.4.1", "isort>=4.2.15,<4.3.5", + "mypy==0.730", ], 'docs': [ "mock", diff --git a/tox.ini b/tox.ini index 2d538a2e1f..dca0fff869 100644 --- a/tox.ini +++ b/tox.ini @@ -62,3 +62,4 @@ extras=linter commands= flake8 {toxinidir}/web3 {toxinidir}/ens {toxinidir}/ethpm {toxinidir}/tests isort --recursive --check-only --diff {toxinidir}/web3/ {toxinidir}/ens/ {toxinidir}/ethpm/ {toxinidir}/tests/ + mypy -p web3.main --config-file {toxinidir}/mypy.ini diff --git a/web3/main.py b/web3/main.py index 2e30befd9e..7dfc45b806 100644 --- a/web3/main.py +++ b/web3/main.py @@ -15,6 +15,10 @@ from hexbytes import ( HexBytes, ) +from typing import Any, cast, Dict, List, Sequence, TYPE_CHECKING + +from eth_typing import HexStr, Primitives +from eth_typing.abi import TypeStr from ens import ENS from web3._utils.abi import ( @@ -43,6 +47,9 @@ from web3._utils.normalizers import ( abi_ens_resolver, ) +from web3.datastructures import ( + NamedElementOnion, +) from web3.eth import ( Eth, ) @@ -68,6 +75,9 @@ ParityPersonal, ParityShh, ) +from web3.providers import ( + BaseProvider, +) from web3.providers.eth_tester import ( EthereumTesterProvider, ) @@ -87,8 +97,11 @@ Version, ) +if TYPE_CHECKING: + from web3.pm import PM # noqa: F401 + -def get_default_modules(): +def get_default_modules() -> Dict[str, Sequence[Any]]: return { "eth": (Eth,), "net": (Net,), @@ -137,7 +150,13 @@ class Web3: isChecksumAddress = staticmethod(is_checksum_address) toChecksumAddress = staticmethod(to_checksum_address) - def __init__(self, provider=None, middlewares=None, modules=None, ens=empty): + def __init__( + self, + provider: BaseProvider=None, + middlewares: Sequence[Any]=None, + modules: Dict[str, Sequence[Any]]=None, + ens: ENS=cast(ENS, empty) + ) -> None: self.manager = self.RequestManager(self, provider, middlewares) if modules is None: @@ -150,35 +169,35 @@ def __init__(self, provider=None, middlewares=None, modules=None, ens=empty): self.ens = ens @property - def middleware_onion(self): + def middleware_onion(self) -> NamedElementOnion: return self.manager.middleware_onion @property - def provider(self): + def provider(self) -> BaseProvider: return self.manager.provider @provider.setter - def provider(self, provider): + def provider(self, provider: BaseProvider) -> None: self.manager.provider = provider @property - def clientVersion(self): + def clientVersion(self) -> int: return self.manager.request_blocking("web3_clientVersion", []) @property - def api(self): + def api(self) -> str: from web3 import __version__ return __version__ @staticmethod @deprecated_for("keccak") @apply_to_return_value(HexBytes) - def sha3(primitive=None, text=None, hexstr=None): + def sha3(primitive: Primitives=None, text: str=None, hexstr: HexStr=None) -> bytes: return Web3.keccak(primitive, text, hexstr) @staticmethod @apply_to_return_value(HexBytes) - def keccak(primitive=None, text=None, hexstr=None): + def keccak(primitive: Primitives=None, text: str=None, hexstr: HexStr=None) -> bytes: if isinstance(primitive, (bytes, int, type(None))): input_bytes = to_bytes(primitive, hexstr=hexstr, text=text) return eth_utils_keccak(input_bytes) @@ -194,11 +213,11 @@ def keccak(primitive=None, text=None, hexstr=None): @combomethod @deprecated_for("solidityKeccak") - def soliditySha3(cls, abi_types, values): + def soliditySha3(cls, abi_types: List[TypeStr], values: List[Any]) -> bytes: return cls.solidityKeccak(abi_types, values) @combomethod - def solidityKeccak(cls, abi_types, values): + def solidityKeccak(cls, abi_types: List[TypeStr], values: List[Any]) -> bytes: """ Executes keccak256 exactly as Solidity does. Takes list of abi_types as inputs -- `[uint24, int8[], bool]` @@ -223,27 +242,28 @@ def solidityKeccak(cls, abi_types, values): )) return cls.keccak(hexstr=hex_string) - def isConnected(self): + def isConnected(self) -> bool: return self.provider.isConnected() - def is_encodable(self, _type, value): + def is_encodable(self, _type: TypeStr, value: Any) -> bool: return self.codec.is_encodable(_type, value) @property - def ens(self): - if self._ens is empty: + def ens(self) -> ENS: + if self._ens is cast(ENS, empty): return ENS.fromWeb3(self) else: return self._ens @ens.setter - def ens(self, new_ens): + def ens(self, new_ens: ENS) -> None: self._ens = new_ens @property - def pm(self): + def pm(self) -> "PM": if hasattr(self, '_pm'): - return self._pm + # ignored b/c property is dynamically set via enable_unstable_package_management_api + return self._pm # type: ignore else: raise AttributeError( "The Package Management feature is disabled by default until " @@ -251,10 +271,10 @@ def pm(self): "`w3.enable_unstable_package_management_api()` and try again." ) - def enable_unstable_package_management_api(self): - from web3.pm import PM + def enable_unstable_package_management_api(self) -> None: + from web3.pm import PM # noqa: F811 if not hasattr(self, '_pm'): PM.attach(self, '_pm') - def enable_strict_bytes_type_checking(self): + def enable_strict_bytes_type_checking(self) -> None: self.codec = ABICodec(build_strict_registry())