Skip to content

Complete Type Hinting for eth.tools #1420

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

Merged
merged 6 commits into from
Oct 26, 2018
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
2 changes: 1 addition & 1 deletion eth/tools/_utils/git.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@
from eth_utils import to_text


def get_version_from_git():
def get_version_from_git() -> str:
version = subprocess.check_output(["git", "describe"]).strip()
return to_text(version)
11 changes: 10 additions & 1 deletion eth/tools/_utils/hashing.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,19 @@

import rlp

from typing import (
Iterable,
Tuple,
)

from eth_typing import (
Hash32,
)

from eth.rlp.logs import Log


def hash_log_entries(log_entries):
def hash_log_entries(log_entries: Iterable[Tuple[bytes, bytes, bytes]]) -> Hash32:
"""
Helper function for computing the RLP hash of the logs from transaction
execution.
Expand Down
12 changes: 9 additions & 3 deletions eth/tools/_utils/mappings.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,27 @@
from collections.abc import Mapping
import itertools

from typing import (
Any,
Dict,
Sequence,
)

from cytoolz import merge_with


def merge_if_dicts(values):
def merge_if_dicts(values: Sequence[Dict[Any, Any]]) -> Any:
if all(isinstance(item, Mapping) for item in values):
return merge_with(merge_if_dicts, *values)
else:
return values[-1]


def deep_merge(*dicts):
def deep_merge(*dicts: Dict[Any, Any]) -> Dict[Any, Any]:
return merge_with(merge_if_dicts, *dicts)


def is_cleanly_mergable(*dicts):
def is_cleanly_mergable(*dicts: Dict[Any, Any]) -> bool:
"""Check that nothing will be overwritten when dictionaries are merged using `deep_merge`.

Examples:
Expand Down
114 changes: 74 additions & 40 deletions eth/tools/_utils/normalization.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,19 @@
import binascii
from collections.abc import (
import functools

from typing import (
Any,
AnyStr,
Callable,
cast,
Dict,
Iterable,
List,
Mapping,
Sequence,
Tuple,
Union,
)
import functools

from cytoolz import (
assoc_in,
Expand All @@ -15,6 +25,10 @@
)
import cytoolz.curried

from eth_typing import (
Address,
)

from eth_utils import (
apply_formatters_to_dict,
big_endian_to_int,
Expand All @@ -41,21 +55,31 @@
is_cleanly_mergable,
)

from eth.typing import (
AccountState,
GeneralState,
IntConvertible,
Normalizer,
TransactionDict,
TransactionNormalizer,
)


#
# Primitives
#
@functools.lru_cache(maxsize=1024)
def normalize_int(value):
def normalize_int(value: IntConvertible) -> int:
"""
Robust to integer conversion, handling hex values, string representations,
and special cases like `0x`.
"""
if is_integer(value):
return value
return cast(int, value)
elif is_bytes(value):
return big_endian_to_int(value)
elif is_hex(value) and is_0x_prefixed(value):
value = cast(str, value)
if len(value) == 2:
return 0
else:
Expand All @@ -66,9 +90,9 @@ def normalize_int(value):
raise TypeError("Unsupported type: Got `{0}`".format(type(value)))


def normalize_bytes(value):
def normalize_bytes(value: Union[bytes, str]) -> bytes:
if is_bytes(value):
return value
return cast(bytes, value)
elif is_text(value) and is_hex(value):
return decode_hex(value)
elif is_text(value):
Expand All @@ -78,7 +102,7 @@ def normalize_bytes(value):


@functools.lru_cache(maxsize=1024)
def to_int(value):
def to_int(value: str) -> int:
"""
Robust to integer conversion, handling hex values, string representations,
and special cases like `0x`.
Expand All @@ -93,7 +117,7 @@ def to_int(value):


@functools.lru_cache(maxsize=128)
def normalize_to_address(value):
def normalize_to_address(value: AnyStr) -> Address:
if value:
return to_canonical_address(value)
else:
Expand All @@ -106,21 +130,24 @@ def normalize_to_address(value):
#
# Containers
#
def dict_normalizer(formatters, required=None, optional=None):
def dict_normalizer(formatters: Dict[Any, Callable[..., Any]],
required: Iterable[Any]=None,
optional: Iterable[Any]=None) -> Normalizer:

all_keys = set(formatters.keys())

if required is None and optional is None:
required = all_keys
required_set_form = all_keys
elif required is not None and optional is not None:
raise ValueError("Both required and optional keys specified")
elif required is not None:
required = set(required)
required_set_form = set(required)
elif optional is not None:
required = all_keys - set(optional)
else:
raise ValueError("Both required and optional keys specified")
required_set_form = all_keys - set(optional)

def normalizer(d):
def normalizer(d: Dict[Any, Any]) -> Dict[str, Any]:
keys = set(d.keys())
missing_keys = required - keys
missing_keys = required_set_form - keys
superfluous_keys = keys - all_keys
if missing_keys:
raise KeyError("Missing required keys: {}".format(", ".join(missing_keys)))
Expand All @@ -132,9 +159,9 @@ def normalizer(d):
return normalizer


def dict_options_normalizer(normalizers):
def dict_options_normalizer(normalizers: Iterable[Normalizer]) -> Normalizer:

def normalize(d):
def normalize(d: Dict[Any, Any]) -> Dict[str, Any]:
first_exception = None
for normalizer in normalizers:
try:
Expand All @@ -153,7 +180,7 @@ def normalize(d):
#
# Composition
#
def state_definition_to_dict(state_definition):
def state_definition_to_dict(state_definition: GeneralState) -> AccountState:
"""Convert a state definition to the canonical dict form.

State can either be defined in the canonical form, or as a list of sub states that are then
Expand Down Expand Up @@ -225,36 +252,36 @@ def state_definition_to_dict(state_definition):
)


normalize_main_transaction = dict_normalizer({
normalize_main_transaction = cast(Normalizer, dict_normalizer({
"data": normalize_bytes,
"gasLimit": normalize_int,
"gasPrice": normalize_int,
"nonce": normalize_int,
"secretKey": normalize_bytes,
"to": normalize_to_address,
"value": normalize_int,
})
}))


normalize_transaction = dict_options_normalizer([
normalize_transaction = cast(TransactionNormalizer, dict_options_normalizer([
normalize_main_transaction,
])
]))


normalize_main_transaction_group = dict_normalizer({
normalize_main_transaction_group = cast(Normalizer, dict_normalizer({
"data": eth_utils.curried.apply_formatter_to_array(normalize_bytes),
"gasLimit": eth_utils.curried.apply_formatter_to_array(normalize_int),
"gasPrice": normalize_int,
"nonce": normalize_int,
"secretKey": normalize_bytes,
"to": normalize_to_address,
"value": eth_utils.curried.apply_formatter_to_array(normalize_int),
})
}))


normalize_transaction_group = dict_options_normalizer([
normalize_transaction_group = cast(TransactionNormalizer, dict_options_normalizer([
normalize_main_transaction_group,
])
]))


normalize_execution = dict_normalizer({
Expand Down Expand Up @@ -305,10 +332,14 @@ def state_definition_to_dict(state_definition):
#
# Fixture Normalizers
#
def normalize_unsigned_transaction(transaction, indexes):
def normalize_unsigned_transaction(transaction: TransactionDict,
indexes: Dict[str, Any]) -> TransactionDict:

normalized = normalize_transaction_group(transaction)
return merge(normalized, {
transaction_key: normalized[transaction_key][indexes[index_key]]
# Dynamic key access not yet allowed with TypedDict
# https://github.com/python/mypy/issues/5359
transaction_key: normalized[transaction_key][indexes[index_key]] # type: ignore
for transaction_key, index_key in [
("gasLimit", "gas"),
("value", "value"),
Expand All @@ -318,7 +349,7 @@ def normalize_unsigned_transaction(transaction, indexes):
})


def normalize_account_state(account_state):
def normalize_account_state(account_state: AccountState) -> AccountState:
return {
to_canonical_address(address): {
'balance': to_int(state['balance']),
Expand All @@ -333,14 +364,17 @@ def normalize_account_state(account_state):


@to_dict
def normalize_post_state(post_state):
def normalize_post_state(post_state: Dict[str, Any]) -> Iterable[Tuple[str, bytes]]:
yield 'hash', decode_hex(post_state['hash'])
if 'logs' in post_state:
yield 'logs', decode_hex(post_state['logs'])


@curry
def normalize_statetest_fixture(fixture, fork, post_state_index):
def normalize_statetest_fixture(fixture: Dict[str, Any],
fork: str,
post_state_index: int) -> Dict[str, Any]:

post_state = fixture['post'][fork][post_state_index]

normalized_fixture = {
Expand All @@ -356,7 +390,7 @@ def normalize_statetest_fixture(fixture, fork, post_state_index):
return normalized_fixture


def normalize_exec(exec_params):
def normalize_exec(exec_params: Dict[str, Any]) -> Dict[str, Any]:
return {
'origin': to_canonical_address(exec_params['origin']),
'address': to_canonical_address(exec_params['address']),
Expand All @@ -368,7 +402,7 @@ def normalize_exec(exec_params):
}


def normalize_callcreates(callcreates):
def normalize_callcreates(callcreates: Sequence[Dict[str, Any]]) -> List[Dict[str, Any]]:
return [
{
'data': decode_hex(created_call['data']),
Expand All @@ -384,7 +418,7 @@ def normalize_callcreates(callcreates):


@to_dict
def normalize_vmtest_fixture(fixture):
def normalize_vmtest_fixture(fixture: Dict[str, Any]) -> Iterable[Tuple[str, Any]]:
yield 'env', normalize_environment(fixture['env'])
yield 'exec', normalize_exec(fixture['exec'])
yield 'pre', normalize_account_state(fixture['pre'])
Expand All @@ -405,7 +439,7 @@ def normalize_vmtest_fixture(fixture):
yield 'logs', decode_hex(fixture['logs'])


def normalize_signed_transaction(transaction):
def normalize_signed_transaction(transaction: Dict[str, Any]) -> Dict[str, Any]:
return {
'data': robust_decode_hex(transaction['data']),
'gasLimit': to_int(transaction['gasLimit']),
Expand All @@ -420,7 +454,7 @@ def normalize_signed_transaction(transaction):


@curry
def normalize_transactiontest_fixture(fixture, fork):
def normalize_transactiontest_fixture(fixture: Dict[str, Any], fork: str) -> Dict[str, Any]:

normalized_fixture = {}

Expand All @@ -440,7 +474,7 @@ def normalize_transactiontest_fixture(fixture, fork):
return normalized_fixture


def normalize_block_header(header):
def normalize_block_header(header: Dict[str, Any]) -> Dict[str, Any]:
normalized_header = {
'bloom': big_endian_to_int(decode_hex(header['bloom'])),
'coinbase': to_canonical_address(header['coinbase']),
Expand Down Expand Up @@ -468,7 +502,7 @@ def normalize_block_header(header):
return normalized_header


def normalize_block(block):
def normalize_block(block: Dict[str, Any]) -> Dict[str, Any]:
normalized_block = {}

try:
Expand All @@ -487,7 +521,7 @@ def normalize_block(block):
return normalized_block


def normalize_blockchain_fixtures(fixture):
def normalize_blockchain_fixtures(fixture: Dict[str, Any]) -> Dict[str, Any]:
normalized_fixture = {
'blocks': [normalize_block(block_fixture) for block_fixture in fixture['blocks']],
'genesisBlockHeader': normalize_block_header(fixture['genesisBlockHeader']),
Expand Down
Loading