diff --git a/src/ape/api/providers.py b/src/ape/api/providers.py index abde585077..bc8ba5c041 100644 --- a/src/ape/api/providers.py +++ b/src/ape/api/providers.py @@ -16,7 +16,7 @@ from eth_pydantic_types import HexBytes from ethpm_types.abi import EventABI -from pydantic import Field, computed_field, model_validator +from pydantic import Field, computed_field, field_serializer, model_validator from ape.api.config import PluginConfig from ape.api.networks import NetworkAPI @@ -40,6 +40,7 @@ _create_raises_not_implemented_error, log_instead_of_fail, raises_not_implemented, + to_int, ) from ape.utils.rpc import RPCHeaders @@ -74,7 +75,7 @@ class BlockAPI(BaseInterfaceModel): default=EMPTY_BYTES32, alias="parentHash" ) # NOTE: genesis block has no parent hash """ - The preceeding block's hash. + The preceding block's hash. """ timestamp: HexInt @@ -83,7 +84,7 @@ class BlockAPI(BaseInterfaceModel): NOTE: The pending block uses the current timestamp. """ - _size: Optional[int] = None + _size: Optional[HexInt] = None @log_instead_of_fail(default="") def __repr__(self) -> str: @@ -111,7 +112,6 @@ def validate_size(cls, values, handler): Saves it to a private member on this class and gets returned in computed field "size". """ - if isinstance(values, BlockAPI): size = values.size @@ -125,10 +125,14 @@ def validate_size(cls, values, handler): model = handler(values) if size is not None: - model._size = size + model._size = to_int(size) return model + @field_serializer("size") + def serialize_size(self, value): + return to_int(value) + @computed_field() # type: ignore[misc] @cached_property def transactions(self) -> list[TransactionAPI]: @@ -145,15 +149,14 @@ def transactions(self) -> list[TransactionAPI]: @computed_field() # type: ignore[misc] @cached_property - def size(self) -> int: + def size(self) -> HexInt: """ The size of the block in gas. Most of the time, this field is passed to the model at validation time, - but occassionally it is missing (like in `eth_subscribe:newHeads`), + but occasionally it is missing (like in `eth_subscribe:newHeads`), in which case it gets calculated if and only if the user requests it (or during serialization of this model to disk). """ - if self._size is not None: # The size was provided with the rest of the model # (normal). diff --git a/tests/functional/test_block.py b/tests/functional/test_block.py index 9a597db0d0..75a059472d 100644 --- a/tests/functional/test_block.py +++ b/tests/functional/test_block.py @@ -18,8 +18,9 @@ def test_block(eth_tester_provider, vyper_contract_instance): assert actual.number == data["number"] -def test_block_dict(block): - actual = block.model_dump() +@pytest.mark.parametrize("mode", ("json", "python")) +def test_model_dump(block, mode): + actual = block.model_dump(mode=mode) expected = { "baseFeePerGas": 1000000000, "difficulty": 0, @@ -38,7 +39,7 @@ def test_block_dict(block): assert actual == expected -def test_block_json(block): +def test_model_dump_json(block): actual = block.model_dump_json() expected = ( '{"baseFeePerGas":1000000000,"difficulty":0,"gasLimit":30029122,"gasUsed":0,' @@ -46,11 +47,33 @@ def test_block_json(block): '"num_transactions":0,"number":0,' f'"parentHash":"{to_hex(block.parent_hash)}",' f'"size":{block.size},"timestamp":{block.timestamp},' - f'"totalDifficulty":0,"transactions":[],"uncles":[]}}' + '"totalDifficulty":0,"transactions":[],"uncles":[]}' ) assert actual == expected +@pytest.mark.parametrize("size", (123, HexBytes(123), to_hex(123))) +def test_size(block, size): + block._size = size + dictionary_python = block.model_dump(mode="python") + dictionary_json = block.model_dump(mode="json") + jons_str = block.model_dump_json() + assert dictionary_python["size"] == 123 + assert dictionary_json["size"] == 123 + assert '"size":123' in jons_str + + # Show the same when validated with size in the model. + data = block.model_dump() + data["size"] = size + new_block = Block.model_validate(data) + dictionary_python = new_block.model_dump(mode="python") + dictionary_json = new_block.model_dump(mode="json") + jons_str = new_block.model_dump_json() + assert dictionary_python["size"] == 123 + assert dictionary_json["size"] == 123 + assert '"size":123' in jons_str + + def test_block_calculate_size(block): original = block.model_dump(by_alias=True) size = original.pop("size")