From 512e82eefb9a44f1bf3d26f33501c804cd95ff27 Mon Sep 17 00:00:00 2001 From: Sheng Lundquist Date: Mon, 7 Oct 2024 10:58:14 -0700 Subject: [PATCH] Adding typing to `process_receipts` via `process_receipts_typed`. Version bump to v0.0.41 (#133) Major updates: - Adding `process_receipts_typed` that returns a dataclass typed event when given a transaction receipt. - Adding `combomethod_typed` that allows for combomethods that preserve types, and propagating the change throughout. - Similar to solution in eth-utils here: https://github.com/ethereum/eth-utils/pull/264 - Renaming `get_typed_logs` to `get_logs_typed` for consistency. - Adding `BaseEventArgs` and frozen dataclass attribute for arg subclassing. This also ensures the `arg` field exists in all event types. --- example/types/ExampleContract.py | 139 ++++-------------- example/types/ExampleTypes.py | 16 +- example/types/__init__.py | 2 +- example/types/pypechain.version | 2 +- pypechain/core/__init__.py | 3 +- pypechain/core/base_event.py | 10 +- pypechain/core/combomethod_typed.py | 39 +++++ .../templates/contract.py/base.py.jinja2 | 11 ++ .../templates/contract.py/errors.py.jinja2 | 23 +-- .../templates/contract.py/events.py.jinja2 | 49 +++--- pypechain/templates/types.py.jinja2 | 8 +- .../deploy_linking/types/ContractContract.py | 2 +- .../deploy_linking/types/MyLibraryContract.py | 2 +- .../test/deploy_linking/types/__init__.py | 2 +- .../deploy_linking/types/pypechain.version | 2 +- .../types/ConstructorNoArgsContract.py | 2 +- .../types/ConstructorWithArgsContract.py | 2 +- .../ConstructorWithStructArgsContract.py | 2 +- .../types/ConstructorWithStructArgsTypes.py | 2 +- .../deployment/types/NoConstructorContract.py | 2 +- pypechain/test/deployment/types/__init__.py | 2 +- .../test/deployment/types/pypechain.version | 2 +- pypechain/test/errors/types/ErrorsContract.py | 73 ++------- pypechain/test/errors/types/__init__.py | 2 +- pypechain/test/errors/types/pypechain.version | 2 +- pypechain/test/events/test_events.py | 41 ++++-- pypechain/test/events/types/EventsContract.py | 118 ++++----------- pypechain/test/events/types/EventsTypes.py | 12 +- pypechain/test/events/types/__init__.py | 2 +- pypechain/test/events/types/pypechain.version | 2 +- .../types/OverloadedMethodsContract.py | 2 +- pypechain/test/overloading/types/__init__.py | 2 +- .../test/overloading/types/pypechain.version | 2 +- .../return_types/types/ReturnTypesContract.py | 2 +- .../return_types/types/ReturnTypesTypes.py | 2 +- pypechain/test/return_types/types/__init__.py | 2 +- .../test/return_types/types/pypechain.version | 2 +- pypechain/test/structs/types/IStructsTypes.py | 2 +- .../test/structs/types/StructsAContract.py | 2 +- pypechain/test/structs/types/StructsATypes.py | 2 +- .../test/structs/types/StructsBContract.py | 2 +- .../test/structs/types/StructsCContract.py | 2 +- pypechain/test/structs/types/StructsCTypes.py | 2 +- pypechain/test/structs/types/__init__.py | 2 +- .../test/structs/types/pypechain.version | 2 +- pyproject.toml | 2 +- snapshots/has_events.py | 90 ++++-------- 47 files changed, 258 insertions(+), 440 deletions(-) create mode 100644 pypechain/core/combomethod_typed.py diff --git a/example/types/ExampleContract.py b/example/types/ExampleContract.py index a4a529e0..c56ef50e 100644 --- a/example/types/ExampleContract.py +++ b/example/types/ExampleContract.py @@ -1,6 +1,6 @@ """A web3.py Contract class for the Example contract. -DO NOT EDIT. This file was generated by pypechain v0.0.40. +DO NOT EDIT. This file was generated by pypechain v0.0.41. See documentation at https://github.com/delvtech/pypechain """ # contracts have PascalCase names @@ -35,6 +35,7 @@ from hexbytes import HexBytes from typing_extensions import Self from web3 import Web3 +from web3._utils.events import EventLogErrorFlags from web3._utils.filters import LogFilter from web3.contract.contract import ( Contract, @@ -44,9 +45,10 @@ ContractFunction, ContractFunctions, ) -from web3.types import BlockIdentifier, StateOverride, TxParams +from web3.logs import WARN +from web3.types import BlockIdentifier, StateOverride, TxParams, TxReceipt -from pypechain.core import dataclass_to_tuple, get_abi_input_types, rename_returned_types +from pypechain.core import combomethod_typed, dataclass_to_tuple, get_abi_input_types, rename_returned_types from .ExampleTypes import FlipEvent, FlopEvent, InnerStruct, NestedStruct, SimpleStruct @@ -474,16 +476,13 @@ class ExampleFlipContractEvent(ContractEvent): # super() get_logs and create_filter methods are generic, while our version adds values & types # pylint: disable=arguments-differ - # @combomethod destroys return types, so we are redefining functions as both class and instance - # pylint: disable=function-redefined - # pylint: disable=useless-parent-delegation def __init__(self, *argument_names: tuple[str]) -> None: super().__init__(*argument_names) - # We ignore types here for function redefinition - def get_typed_logs( # type: ignore - self: "ExampleFlipContractEvent", + @combomethod_typed + def get_logs_typed( + self, argument_filters: dict[str, Any] | None = None, from_block: BlockIdentifier | None = None, to_block: BlockIdentifier | None = None, @@ -509,19 +508,10 @@ def get_typed_logs( # type: ignore for abi_event in abi_events ] - @classmethod - # We ignore types here for function redefinition - def get_typed_logs( # type: ignore - cls: Type["ExampleFlipContractEvent"], - argument_filters: dict[str, Any] | None = None, - from_block: BlockIdentifier | None = None, - to_block: BlockIdentifier | None = None, - block_hash: HexBytes | None = None, - ) -> Iterable[FlipEvent]: - """Extension of `get_logs` that return a typed dataclass of the event.""" - abi_events = super().get_logs( - argument_filters=argument_filters, from_block=from_block, to_block=to_block, block_hash=block_hash - ) + @combomethod_typed + def process_receipt_typed(self, txn_receipt: TxReceipt, errors: EventLogErrorFlags = WARN) -> Iterable[FlipEvent]: + """Extension of `process_receipt` that return a typed dataclass of the event.""" + abi_events = super().process_receipt(txn_receipt, errors) # TODO there may be issues with this function if the user uses a middleware that changes event structure. return [ FlipEvent( @@ -538,29 +528,9 @@ def get_typed_logs( # type: ignore for abi_event in abi_events ] + @combomethod_typed def create_filter( # type: ignore - self: "ExampleFlipContractEvent", - *, # PEP 3102 - argument_filters: dict[str, Any] | None = None, - from_block: BlockIdentifier | None = None, - to_block: BlockIdentifier = "latest", - address: ChecksumAddress | None = None, - topics: Sequence[Any] | None = None, - ) -> LogFilter: - return cast( - LogFilter, - super().create_filter( - argument_filters=argument_filters, - from_block=from_block, - to_block=to_block, - address=address, - topics=topics, - ), - ) - - @classmethod - def create_filter( # type: ignore - cls: Type["ExampleFlipContractEvent"], + self, *, # PEP 3102 argument_filters: dict[str, Any] | None = None, from_block: BlockIdentifier | None = None, @@ -586,16 +556,13 @@ class ExampleFlopContractEvent(ContractEvent): # super() get_logs and create_filter methods are generic, while our version adds values & types # pylint: disable=arguments-differ - # @combomethod destroys return types, so we are redefining functions as both class and instance - # pylint: disable=function-redefined - # pylint: disable=useless-parent-delegation def __init__(self, *argument_names: tuple[str]) -> None: super().__init__(*argument_names) - # We ignore types here for function redefinition - def get_typed_logs( # type: ignore - self: "ExampleFlopContractEvent", + @combomethod_typed + def get_logs_typed( + self, argument_filters: dict[str, Any] | None = None, from_block: BlockIdentifier | None = None, to_block: BlockIdentifier | None = None, @@ -621,19 +588,10 @@ def get_typed_logs( # type: ignore for abi_event in abi_events ] - @classmethod - # We ignore types here for function redefinition - def get_typed_logs( # type: ignore - cls: Type["ExampleFlopContractEvent"], - argument_filters: dict[str, Any] | None = None, - from_block: BlockIdentifier | None = None, - to_block: BlockIdentifier | None = None, - block_hash: HexBytes | None = None, - ) -> Iterable[FlopEvent]: - """Extension of `get_logs` that return a typed dataclass of the event.""" - abi_events = super().get_logs( - argument_filters=argument_filters, from_block=from_block, to_block=to_block, block_hash=block_hash - ) + @combomethod_typed + def process_receipt_typed(self, txn_receipt: TxReceipt, errors: EventLogErrorFlags = WARN) -> Iterable[FlopEvent]: + """Extension of `process_receipt` that return a typed dataclass of the event.""" + abi_events = super().process_receipt(txn_receipt, errors) # TODO there may be issues with this function if the user uses a middleware that changes event structure. return [ FlopEvent( @@ -650,29 +608,9 @@ def get_typed_logs( # type: ignore for abi_event in abi_events ] + @combomethod_typed def create_filter( # type: ignore - self: "ExampleFlopContractEvent", - *, # PEP 3102 - argument_filters: dict[str, Any] | None = None, - from_block: BlockIdentifier | None = None, - to_block: BlockIdentifier = "latest", - address: ChecksumAddress | None = None, - topics: Sequence[Any] | None = None, - ) -> LogFilter: - return cast( - LogFilter, - super().create_filter( - argument_filters=argument_filters, - from_block=from_block, - to_block=to_block, - address=address, - topics=topics, - ), - ) - - @classmethod - def create_filter( # type: ignore - cls: Type["ExampleFlopContractEvent"], + self, *, # PEP 3102 argument_filters: dict[str, Any] | None = None, from_block: BlockIdentifier | None = None, @@ -695,9 +633,9 @@ def create_filter( # type: ignore class ExampleContractEvents(ContractEvents): """ContractEvents for the Example contract.""" - Flip: ExampleFlipContractEvent + Flip: Type[ExampleFlipContractEvent] - Flop: ExampleFlopContractEvent + Flop: Type[ExampleFlopContractEvent] def __init__( self, @@ -707,11 +645,11 @@ def __init__( ) -> None: super().__init__(abi, w3, address) self.Flip = cast( - ExampleFlipContractEvent, + Type[ExampleFlipContractEvent], ExampleFlipContractEvent.factory("Flip", w3=w3, contract_abi=abi, address=address, event_name="Flip"), ) self.Flop = cast( - ExampleFlopContractEvent, + Type[ExampleFlopContractEvent], ExampleFlopContractEvent.factory("Flop", w3=w3, contract_abi=abi, address=address, event_name="Flop"), ) @@ -719,9 +657,6 @@ def __init__( class ExampleWrongChoiceContractError: """ContractError for WrongChoice.""" - # @combomethod destroys return types, so we are redefining functions as both class and instance - # pylint: disable=function-redefined - # 4 byte error selector selector: str # error signature, i.e. CustomError(uint256,bool) @@ -734,8 +669,9 @@ def __init__( self.selector = "0xc13b30d4" self.signature = "WrongChoice(uint8,string)" - def decode_error_data( # type: ignore - self: "ExampleWrongChoiceContractError", + @combomethod_typed + def decode_error_data( + self, data: HexBytes, # TODO: instead of returning a tuple, return a dataclass with the input names and types just like we do for functions ) -> tuple[Any, ...]: @@ -749,21 +685,6 @@ def decode_error_data( # type: ignore decoded = abi_codec.decode(types, data) return decoded - @classmethod - def decode_error_data( # type: ignore - cls: Type["ExampleWrongChoiceContractError"], - data: HexBytes, - ) -> tuple[Any, ...]: - """Decodes error data returns from a smart contract.""" - error_abi = cast( - ABIFunction, - [item for item in example_abi if item.get("name") == "WrongChoice" and item.get("type") == "error"][0], - ) - types = get_abi_input_types(error_abi) - abi_codec = ABICodec(default_registry) - decoded = abi_codec.decode(types, data) - return decoded - class ExampleContractErrors: """ContractErrors for the Example contract.""" diff --git a/example/types/ExampleTypes.py b/example/types/ExampleTypes.py index 12190148..def6e545 100644 --- a/example/types/ExampleTypes.py +++ b/example/types/ExampleTypes.py @@ -1,6 +1,6 @@ """Dataclasses for all structs in the Example contract. -DO NOT EDIT. This file was generated by pypechain v0.0.40. +DO NOT EDIT. This file was generated by pypechain v0.0.41. See documentation at https://github.com/delvtech/pypechain """ # super() call methods are generic, while our version adds values & types @@ -20,15 +20,15 @@ from dataclasses import dataclass -from pypechain.core import BaseEvent, ErrorInfo, ErrorParams +from pypechain.core import BaseEvent, BaseEventArgs, ErrorInfo, ErrorParams -@dataclass(kw_only=True) +@dataclass(kw_only=True, frozen=True) class FlipEvent(BaseEvent): """The event type for event Flip""" - @dataclass - class FlipEventArgs: + @dataclass(kw_only=True, frozen=True) + class FlipEventArgs(BaseEventArgs): """The args to the event Flip""" flip: int @@ -38,12 +38,12 @@ class FlipEventArgs: __name__: str = "Flip" -@dataclass(kw_only=True) +@dataclass(kw_only=True, frozen=True) class FlopEvent(BaseEvent): """The event type for event Flop""" - @dataclass - class FlopEventArgs: + @dataclass(kw_only=True, frozen=True) + class FlopEventArgs(BaseEventArgs): """The args to the event Flop""" flop: int diff --git a/example/types/__init__.py b/example/types/__init__.py index 4bed9d8d..3666cf66 100644 --- a/example/types/__init__.py +++ b/example/types/__init__.py @@ -1,6 +1,6 @@ """Export all types from generated files. -DO NOT EDIT. This file was generated by pypechain v0.0.40. +DO NOT EDIT. This file was generated by pypechain v0.0.41. See documentation at https://github.com/delvtech/pypechain """ from .ExampleContract import ExampleContract diff --git a/example/types/pypechain.version b/example/types/pypechain.version index eb6a2aae..85e555e5 100644 --- a/example/types/pypechain.version +++ b/example/types/pypechain.version @@ -1 +1 @@ -pypechain == 0.0.40 \ No newline at end of file +pypechain == 0.0.41 \ No newline at end of file diff --git a/pypechain/core/__init__.py b/pypechain/core/__init__.py index ec86f109..3c7469f6 100644 --- a/pypechain/core/__init__.py +++ b/pypechain/core/__init__.py @@ -1,5 +1,6 @@ """Core pypechain functions used by generated files""" -from .base_event import BaseEvent +from .base_event import BaseEvent, BaseEventArgs +from .combomethod_typed import combomethod_typed from .error import ErrorInfo, ErrorParams from .utilities import dataclass_to_tuple, get_abi_input_types, rename_returned_types, tuple_to_dataclass diff --git a/pypechain/core/base_event.py b/pypechain/core/base_event.py index a4b517d9..7649a0dd 100644 --- a/pypechain/core/base_event.py +++ b/pypechain/core/base_event.py @@ -1,12 +1,19 @@ """Defines the base event class for all subclasses of events.""" +from __future__ import annotations + from dataclasses import dataclass from eth_typing import ChecksumAddress from hexbytes import HexBytes -@dataclass(kw_only=True) +@dataclass(kw_only=True, frozen=True) +class BaseEventArgs: + """The base event argument class for all subclasses of event args.""" + + +@dataclass(kw_only=True, frozen=True) class BaseEvent: """The base event class for all subclasses of events.""" @@ -19,4 +26,5 @@ class BaseEvent: address: ChecksumAddress block_hash: HexBytes block_number: int + args: BaseEventArgs __name__: str = "BaseEvent" diff --git a/pypechain/core/combomethod_typed.py b/pypechain/core/combomethod_typed.py new file mode 100644 index 00000000..3a46a29c --- /dev/null +++ b/pypechain/core/combomethod_typed.py @@ -0,0 +1,39 @@ +"""A typed version of combomethod from eth_utils.""" + +import functools +from typing import Any, Callable, Concatenate, Generic, Optional, ParamSpec, Type, TypeVar + +# TODO remove this file once https://github.com/ethereum/eth-utils/pull/264 gets merged + +# We use generics to define the structure of the wrapped method +# Here, `T` is the `self` or `cls` object, `P` is the parameter of the wrapped function, and +# `R` is the return type + +T = TypeVar("T") +P = ParamSpec("P") +R = TypeVar("R") + + +# We define the generic that attaches to the function we're decorating +# so here, P and R are the types of the parameters and return of the decorated function +class combomethod_typed(Generic[P, R]): # pylint: disable=invalid-name + """A typed version of combomethod from eth_utils.""" + + # The callable `Any` takes place of the obj or class + method: Callable[Concatenate[Any, P], R] + + # The method passed in has a spot for obj or class in `Any` + def __init__(self, method: Callable[Concatenate[Any, P], R]) -> None: + self.method = method + + # The getter allows for logic to call either cls or obj method + # with the original type decorators in the output wrapper function + def __get__(self, obj: Optional[T] = None, objtype: Optional[Type[T]] = None) -> Callable[P, R]: + # The _wrapper function is unchanged from eth-utils + @functools.wraps(self.method) + def _wrapper(*args: P.args, **kwargs: P.kwargs) -> R: + if obj is not None: + return self.method(obj, *args, **kwargs) + return self.method(objtype, *args, **kwargs) + + return _wrapper diff --git a/pypechain/templates/contract.py/base.py.jinja2 b/pypechain/templates/contract.py/base.py.jinja2 index d7b3e7b3..13a69b44 100644 --- a/pypechain/templates/contract.py/base.py.jinja2 +++ b/pypechain/templates/contract.py/base.py.jinja2 @@ -49,6 +49,17 @@ from .{{struct_info.contract_name}}Types import {{struct_info.name}} from .{{contract_type}} import {{contract_type}} {% endfor %} +{% if has_events %} +from web3.types import TxReceipt +from web3.logs import WARN +from web3._utils.events import EventLogErrorFlags +from pypechain.core import BaseEventArgs +{% endif %} + +{% if has_errors or has_events %} +from pypechain.core import combomethod_typed +{% endif %} + {% for event in events %} from .{{contract_name}}Types import {{event.capitalized_name}}Event {% endfor %} diff --git a/pypechain/templates/contract.py/errors.py.jinja2 b/pypechain/templates/contract.py/errors.py.jinja2 index f8a99a1c..a5d4eb7a 100644 --- a/pypechain/templates/contract.py/errors.py.jinja2 +++ b/pypechain/templates/contract.py/errors.py.jinja2 @@ -2,9 +2,6 @@ {%- for error_info in errors -%} class {{contract_name}}{{error_info.name}}ContractError: """ContractError for {{error_info.name}}.""" - # @combomethod destroys return types, so we are redefining functions as both class and instance - # pylint: disable=function-redefined - # 4 byte error selector selector: str # error signature, i.e. CustomError(uint256,bool) @@ -18,8 +15,9 @@ class {{contract_name}}{{error_info.name}}ContractError: self.selector = "{{error_info.selector}}" self.signature = "{{error_info.signature}}" - def decode_error_data( # type: ignore - self: "{{contract_name}}{{error_info.name}}ContractError", + @combomethod_typed + def decode_error_data( + self, data: HexBytes, # TODO: instead of returning a tuple, return a dataclass with the input names and types just like we do for functions ) -> tuple[Any, ...]: @@ -32,21 +30,6 @@ class {{contract_name}}{{error_info.name}}ContractError: abi_codec = ABICodec(default_registry) decoded = abi_codec.decode(types, data) return decoded - - @classmethod - def decode_error_data( # type: ignore - cls: Type["{{contract_name}}{{error_info.name}}ContractError"], - data: HexBytes, - ) -> tuple[Any, ...]: - """Decodes error data returns from a smart contract.""" - error_abi = cast( - ABIFunction, - [item for item in {{contract_name | lower}}_abi if item.get("name") == "{{error_info.name}}" and item.get("type") == "error"][0], - ) - types = get_abi_input_types(error_abi) - abi_codec = ABICodec(default_registry) - decoded = abi_codec.decode(types, data) - return decoded {% endfor %} class {{contract_name}}ContractErrors: diff --git a/pypechain/templates/contract.py/events.py.jinja2 b/pypechain/templates/contract.py/events.py.jinja2 index edac81cb..46c012ae 100644 --- a/pypechain/templates/contract.py/events.py.jinja2 +++ b/pypechain/templates/contract.py/events.py.jinja2 @@ -8,9 +8,6 @@ class {{contract_name}}{{event.capitalized_name}}ContractEvent(ContractEvent): # super() get_logs and create_filter methods are generic, while our version adds values & types # pylint: disable=arguments-differ - # @combomethod destroys return types, so we are redefining functions as both class and instance - # pylint: disable=function-redefined - {# TODO: add type-hints for argument_names #} {# TODO: remove pylint disable when we add a type-hint for argument_names #} # pylint: disable=useless-parent-delegation @@ -18,9 +15,9 @@ class {{contract_name}}{{event.capitalized_name}}ContractEvent(ContractEvent): super().__init__(*argument_names) {# TODO: add type-hints for argument_filters -#} - # We ignore types here for function redefinition - def get_typed_logs( # type: ignore - self: "{{contract_name}}{{event.capitalized_name}}ContractEvent", + @combomethod_typed + def get_logs_typed( + self, argument_filters: dict[str, Any] | None = None, from_block: BlockIdentifier | None = None, to_block: BlockIdentifier | None = None, @@ -43,21 +40,18 @@ class {{contract_name}}{{event.capitalized_name}}ContractEvent(ContractEvent): {{event_input.name}} = abi_event.args["{{event_input.name}}"], {%- endfor %} ), +{% else %} + args = BaseEventArgs(), {% endif %} ) for abi_event in abi_events ] - - @classmethod - # We ignore types here for function redefinition - def get_typed_logs( # type: ignore - cls: Type["{{contract_name}}{{event.capitalized_name}}ContractEvent"], - argument_filters: dict[str, Any] | None = None, - from_block: BlockIdentifier | None = None, - to_block: BlockIdentifier | None = None, - block_hash: HexBytes | None = None, + + @combomethod_typed + def process_receipt_typed( + self, txn_receipt: TxReceipt, errors: EventLogErrorFlags = WARN ) -> Iterable[{{event.capitalized_name}}Event]: - """Extension of `get_logs` that return a typed dataclass of the event.""" - abi_events = super().get_logs(argument_filters=argument_filters, from_block=from_block, to_block=to_block, block_hash=block_hash) + """Extension of `process_receipt` that return a typed dataclass of the event.""" + abi_events = super().process_receipt(txn_receipt, errors) # TODO there may be issues with this function if the user uses a middleware that changes event structure. return [ {{event.capitalized_name}}Event( @@ -73,25 +67,16 @@ class {{contract_name}}{{event.capitalized_name}}ContractEvent(ContractEvent): {{event_input.name}} = abi_event.args["{{event_input.name}}"], {%- endfor %} ), +{% else %} + args = BaseEventArgs(), {% endif %} ) for abi_event in abi_events ] {# TODO: add type-hints for argument_filters -#} + @combomethod_typed def create_filter( # type: ignore - self: "{{contract_name}}{{event.capitalized_name}}ContractEvent", - *, # PEP 3102 - argument_filters: dict[str, Any] | None = None, - from_block: BlockIdentifier | None = None, - to_block: BlockIdentifier = "latest", - address: ChecksumAddress | None = None, - topics: Sequence[Any] | None = None, - ) -> LogFilter: - return cast(LogFilter, super().create_filter(argument_filters=argument_filters, from_block=from_block, to_block=to_block, address=address, topics=topics)) - - @classmethod - def create_filter( # type: ignore - cls: Type["{{contract_name}}{{event.capitalized_name}}ContractEvent"], + self, *, # PEP 3102 argument_filters: dict[str, Any] | None = None, from_block: BlockIdentifier | None = None, @@ -106,7 +91,7 @@ class {{contract_name}}{{event.capitalized_name}}ContractEvent(ContractEvent): class {{contract_name}}ContractEvents(ContractEvents): """ContractEvents for the {{contract_name}} contract.""" {% for event in events %} - {{event.name}}: {{contract_name}}{{event.capitalized_name}}ContractEvent + {{event.name}}: Type[{{contract_name}}{{event.capitalized_name}}ContractEvent] {% endfor %} def __init__( @@ -117,7 +102,7 @@ class {{contract_name}}ContractEvents(ContractEvents): ) -> None: super().__init__(abi, w3, address) {% for event in events -%} - self.{{event.name}} = cast({{contract_name}}{{event.capitalized_name}}ContractEvent, {{contract_name}}{{event.capitalized_name}}ContractEvent.factory( + self.{{event.name}} = cast(Type[{{contract_name}}{{event.capitalized_name}}ContractEvent], {{contract_name}}{{event.capitalized_name}}ContractEvent.factory( "{{event.name}}", w3=w3, contract_abi=abi, diff --git a/pypechain/templates/types.py.jinja2 b/pypechain/templates/types.py.jinja2 index 4337ce61..548732ab 100644 --- a/pypechain/templates/types.py.jinja2 +++ b/pypechain/templates/types.py.jinja2 @@ -24,7 +24,7 @@ from . import {{imported_contract_name}}Types as {{imported_contract_name}} {% endfor %} {% if has_events %} -from pypechain.core import BaseEvent +from pypechain.core import BaseEvent, BaseEventArgs {% endif %} {% if errors|length > 0 %} @@ -36,13 +36,13 @@ from pypechain.core import ErrorInfo, ErrorParams {% set has_event_args = event.inputs|length > 0 %} -@dataclass(kw_only=True) +@dataclass(kw_only=True, frozen=True) class {{event.capitalized_name}}Event(BaseEvent): """The event type for event {{event.name}}""" {% if has_event_args %} - @dataclass - class {{event.capitalized_name}}EventArgs: + @dataclass(kw_only=True, frozen=True) + class {{event.capitalized_name}}EventArgs(BaseEventArgs): """The args to the event {{event.name}}""" {%- for event_input in event.inputs %} {{event_input.name}}: {{event_input.python_type}} diff --git a/pypechain/test/deploy_linking/types/ContractContract.py b/pypechain/test/deploy_linking/types/ContractContract.py index d80c2f02..41b22ea0 100644 --- a/pypechain/test/deploy_linking/types/ContractContract.py +++ b/pypechain/test/deploy_linking/types/ContractContract.py @@ -1,6 +1,6 @@ """A web3.py Contract class for the Contract contract. -DO NOT EDIT. This file was generated by pypechain v0.0.40. +DO NOT EDIT. This file was generated by pypechain v0.0.41. See documentation at https://github.com/delvtech/pypechain """ # contracts have PascalCase names diff --git a/pypechain/test/deploy_linking/types/MyLibraryContract.py b/pypechain/test/deploy_linking/types/MyLibraryContract.py index f9aedca6..8a75d319 100644 --- a/pypechain/test/deploy_linking/types/MyLibraryContract.py +++ b/pypechain/test/deploy_linking/types/MyLibraryContract.py @@ -1,6 +1,6 @@ """A web3.py Contract class for the MyLibrary contract. -DO NOT EDIT. This file was generated by pypechain v0.0.40. +DO NOT EDIT. This file was generated by pypechain v0.0.41. See documentation at https://github.com/delvtech/pypechain """ # contracts have PascalCase names diff --git a/pypechain/test/deploy_linking/types/__init__.py b/pypechain/test/deploy_linking/types/__init__.py index b76f8ce0..0e33ea68 100644 --- a/pypechain/test/deploy_linking/types/__init__.py +++ b/pypechain/test/deploy_linking/types/__init__.py @@ -1,6 +1,6 @@ """Export all types from generated files. -DO NOT EDIT. This file was generated by pypechain v0.0.40. +DO NOT EDIT. This file was generated by pypechain v0.0.41. See documentation at https://github.com/delvtech/pypechain """ from .MyLibraryContract import MyLibraryContract diff --git a/pypechain/test/deploy_linking/types/pypechain.version b/pypechain/test/deploy_linking/types/pypechain.version index eb6a2aae..85e555e5 100644 --- a/pypechain/test/deploy_linking/types/pypechain.version +++ b/pypechain/test/deploy_linking/types/pypechain.version @@ -1 +1 @@ -pypechain == 0.0.40 \ No newline at end of file +pypechain == 0.0.41 \ No newline at end of file diff --git a/pypechain/test/deployment/types/ConstructorNoArgsContract.py b/pypechain/test/deployment/types/ConstructorNoArgsContract.py index dc2acb5c..0f332973 100644 --- a/pypechain/test/deployment/types/ConstructorNoArgsContract.py +++ b/pypechain/test/deployment/types/ConstructorNoArgsContract.py @@ -1,6 +1,6 @@ """A web3.py Contract class for the ConstructorNoArgs contract. -DO NOT EDIT. This file was generated by pypechain v0.0.40. +DO NOT EDIT. This file was generated by pypechain v0.0.41. See documentation at https://github.com/delvtech/pypechain """ # contracts have PascalCase names diff --git a/pypechain/test/deployment/types/ConstructorWithArgsContract.py b/pypechain/test/deployment/types/ConstructorWithArgsContract.py index 079930a2..544b26b8 100644 --- a/pypechain/test/deployment/types/ConstructorWithArgsContract.py +++ b/pypechain/test/deployment/types/ConstructorWithArgsContract.py @@ -1,6 +1,6 @@ """A web3.py Contract class for the ConstructorWithArgs contract. -DO NOT EDIT. This file was generated by pypechain v0.0.40. +DO NOT EDIT. This file was generated by pypechain v0.0.41. See documentation at https://github.com/delvtech/pypechain """ # contracts have PascalCase names diff --git a/pypechain/test/deployment/types/ConstructorWithStructArgsContract.py b/pypechain/test/deployment/types/ConstructorWithStructArgsContract.py index 3b529216..2709460f 100644 --- a/pypechain/test/deployment/types/ConstructorWithStructArgsContract.py +++ b/pypechain/test/deployment/types/ConstructorWithStructArgsContract.py @@ -1,6 +1,6 @@ """A web3.py Contract class for the ConstructorWithStructArgs contract. -DO NOT EDIT. This file was generated by pypechain v0.0.40. +DO NOT EDIT. This file was generated by pypechain v0.0.41. See documentation at https://github.com/delvtech/pypechain """ # contracts have PascalCase names diff --git a/pypechain/test/deployment/types/ConstructorWithStructArgsTypes.py b/pypechain/test/deployment/types/ConstructorWithStructArgsTypes.py index 2674d923..91ea68ef 100644 --- a/pypechain/test/deployment/types/ConstructorWithStructArgsTypes.py +++ b/pypechain/test/deployment/types/ConstructorWithStructArgsTypes.py @@ -1,6 +1,6 @@ """Dataclasses for all structs in the ConstructorWithStructArgs contract. -DO NOT EDIT. This file was generated by pypechain v0.0.40. +DO NOT EDIT. This file was generated by pypechain v0.0.41. See documentation at https://github.com/delvtech/pypechain """ # super() call methods are generic, while our version adds values & types diff --git a/pypechain/test/deployment/types/NoConstructorContract.py b/pypechain/test/deployment/types/NoConstructorContract.py index aaedaddf..7bd8bf79 100644 --- a/pypechain/test/deployment/types/NoConstructorContract.py +++ b/pypechain/test/deployment/types/NoConstructorContract.py @@ -1,6 +1,6 @@ """A web3.py Contract class for the NoConstructor contract. -DO NOT EDIT. This file was generated by pypechain v0.0.40. +DO NOT EDIT. This file was generated by pypechain v0.0.41. See documentation at https://github.com/delvtech/pypechain """ # contracts have PascalCase names diff --git a/pypechain/test/deployment/types/__init__.py b/pypechain/test/deployment/types/__init__.py index c1738304..19ab6298 100644 --- a/pypechain/test/deployment/types/__init__.py +++ b/pypechain/test/deployment/types/__init__.py @@ -1,6 +1,6 @@ """Export all types from generated files. -DO NOT EDIT. This file was generated by pypechain v0.0.40. +DO NOT EDIT. This file was generated by pypechain v0.0.41. See documentation at https://github.com/delvtech/pypechain """ from .ConstructorNoArgsContract import ConstructorNoArgsContract diff --git a/pypechain/test/deployment/types/pypechain.version b/pypechain/test/deployment/types/pypechain.version index eb6a2aae..85e555e5 100644 --- a/pypechain/test/deployment/types/pypechain.version +++ b/pypechain/test/deployment/types/pypechain.version @@ -1 +1 @@ -pypechain == 0.0.40 \ No newline at end of file +pypechain == 0.0.41 \ No newline at end of file diff --git a/pypechain/test/errors/types/ErrorsContract.py b/pypechain/test/errors/types/ErrorsContract.py index 0b32f793..83de4a90 100644 --- a/pypechain/test/errors/types/ErrorsContract.py +++ b/pypechain/test/errors/types/ErrorsContract.py @@ -1,6 +1,6 @@ """A web3.py Contract class for the Errors contract. -DO NOT EDIT. This file was generated by pypechain v0.0.40. +DO NOT EDIT. This file was generated by pypechain v0.0.41. See documentation at https://github.com/delvtech/pypechain """ # contracts have PascalCase names @@ -38,7 +38,7 @@ from web3.contract.contract import Contract, ContractConstructor, ContractFunction, ContractFunctions from web3.types import BlockIdentifier, StateOverride, TxParams -from pypechain.core import get_abi_input_types +from pypechain.core import combomethod_typed, get_abi_input_types structs = {} @@ -155,9 +155,6 @@ def __init__( class ErrorsOneContractError: """ContractError for One.""" - # @combomethod destroys return types, so we are redefining functions as both class and instance - # pylint: disable=function-redefined - # 4 byte error selector selector: str # error signature, i.e. CustomError(uint256,bool) @@ -170,8 +167,9 @@ def __init__( self.selector = "0xbe0c2110" self.signature = "One()" - def decode_error_data( # type: ignore - self: "ErrorsOneContractError", + @combomethod_typed + def decode_error_data( + self, data: HexBytes, # TODO: instead of returning a tuple, return a dataclass with the input names and types just like we do for functions ) -> tuple[Any, ...]: @@ -185,28 +183,10 @@ def decode_error_data( # type: ignore decoded = abi_codec.decode(types, data) return decoded - @classmethod - def decode_error_data( # type: ignore - cls: Type["ErrorsOneContractError"], - data: HexBytes, - ) -> tuple[Any, ...]: - """Decodes error data returns from a smart contract.""" - error_abi = cast( - ABIFunction, - [item for item in errors_abi if item.get("name") == "One" and item.get("type") == "error"][0], - ) - types = get_abi_input_types(error_abi) - abi_codec = ABICodec(default_registry) - decoded = abi_codec.decode(types, data) - return decoded - class ErrorsThreeContractError: """ContractError for Three.""" - # @combomethod destroys return types, so we are redefining functions as both class and instance - # pylint: disable=function-redefined - # 4 byte error selector selector: str # error signature, i.e. CustomError(uint256,bool) @@ -219,8 +199,9 @@ def __init__( self.selector = "0x09b8b989" self.signature = "Three(bool,(uint256,uint256,uint256,uint256),uint8)" - def decode_error_data( # type: ignore - self: "ErrorsThreeContractError", + @combomethod_typed + def decode_error_data( + self, data: HexBytes, # TODO: instead of returning a tuple, return a dataclass with the input names and types just like we do for functions ) -> tuple[Any, ...]: @@ -234,28 +215,10 @@ def decode_error_data( # type: ignore decoded = abi_codec.decode(types, data) return decoded - @classmethod - def decode_error_data( # type: ignore - cls: Type["ErrorsThreeContractError"], - data: HexBytes, - ) -> tuple[Any, ...]: - """Decodes error data returns from a smart contract.""" - error_abi = cast( - ABIFunction, - [item for item in errors_abi if item.get("name") == "Three" and item.get("type") == "error"][0], - ) - types = get_abi_input_types(error_abi) - abi_codec = ABICodec(default_registry) - decoded = abi_codec.decode(types, data) - return decoded - class ErrorsTwoContractError: """ContractError for Two.""" - # @combomethod destroys return types, so we are redefining functions as both class and instance - # pylint: disable=function-redefined - # 4 byte error selector selector: str # error signature, i.e. CustomError(uint256,bool) @@ -268,8 +231,9 @@ def __init__( self.selector = "0x01e3e2f6" self.signature = "Two(string,address,uint8)" - def decode_error_data( # type: ignore - self: "ErrorsTwoContractError", + @combomethod_typed + def decode_error_data( + self, data: HexBytes, # TODO: instead of returning a tuple, return a dataclass with the input names and types just like we do for functions ) -> tuple[Any, ...]: @@ -283,21 +247,6 @@ def decode_error_data( # type: ignore decoded = abi_codec.decode(types, data) return decoded - @classmethod - def decode_error_data( # type: ignore - cls: Type["ErrorsTwoContractError"], - data: HexBytes, - ) -> tuple[Any, ...]: - """Decodes error data returns from a smart contract.""" - error_abi = cast( - ABIFunction, - [item for item in errors_abi if item.get("name") == "Two" and item.get("type") == "error"][0], - ) - types = get_abi_input_types(error_abi) - abi_codec = ABICodec(default_registry) - decoded = abi_codec.decode(types, data) - return decoded - class ErrorsContractErrors: """ContractErrors for the Errors contract.""" diff --git a/pypechain/test/errors/types/__init__.py b/pypechain/test/errors/types/__init__.py index b98a7dcc..79f87e39 100644 --- a/pypechain/test/errors/types/__init__.py +++ b/pypechain/test/errors/types/__init__.py @@ -1,6 +1,6 @@ """Export all types from generated files. -DO NOT EDIT. This file was generated by pypechain v0.0.40. +DO NOT EDIT. This file was generated by pypechain v0.0.41. See documentation at https://github.com/delvtech/pypechain """ from .ErrorsContract import ErrorsContract diff --git a/pypechain/test/errors/types/pypechain.version b/pypechain/test/errors/types/pypechain.version index eb6a2aae..85e555e5 100644 --- a/pypechain/test/errors/types/pypechain.version +++ b/pypechain/test/errors/types/pypechain.version @@ -1 +1 @@ -pypechain == 0.0.40 \ No newline at end of file +pypechain == 0.0.41 \ No newline at end of file diff --git a/pypechain/test/events/test_events.py b/pypechain/test/events/test_events.py index 873aa83b..236972c3 100644 --- a/pypechain/test/events/test_events.py +++ b/pypechain/test/events/test_events.py @@ -4,9 +4,8 @@ import os -import pytest from web3 import Web3 -from web3.logs import WARN +from web3.logs import DISCARD from pypechain.test.events.types import EventsContract from pypechain.test.events.types.EventsTypes import EventAEvent, EventBEvent @@ -15,36 +14,56 @@ project_root = os.path.dirname(os.path.dirname(current_path)) -@pytest.mark.usefixtures("process_contracts") class TestEvents: """Tests events emitted from the contracts.""" - @pytest.mark.skip() - def test_process_receipt(self, w3: Web3): + def test_process_receipt_typed(self, w3: Web3): """Test that we can use event filters.""" deployed_contract = EventsContract.deploy(w3=w3, account=w3.eth.accounts[0]) hash_a = deployed_contract.functions.emitOneEvent(0, "0x0000000000000000000000000000000000000000").transact() receipt_a = w3.eth.get_transaction_receipt(hash_a) - hash_b = deployed_contract.functions.emitTwoEvents(0, "0x0000000000000000000000000000000000000000").transact() + hash_b = deployed_contract.functions.emitTwoEvents(1, "0x0000000000000000000000000000000000000001").transact() receipt_b = w3.eth.get_transaction_receipt(hash_b) - deployed_contract.events.EventA.process_receipt(receipt_a, errors=WARN) - deployed_contract.events.EventB.process_receipt(receipt_b, errors=WARN) + # Web3 requires instantiated events (i.e., `EventA()`) for process_receipts + # NOTE uses of `process_receipts` with an actual event will throw warnings + # if the receipt has multiple events. This is expected, as web3 under the hood + # loops through all events, regardless of the event attached. Hence, we discard + # any events, as we handle looping over known events here. + event_aa = list(deployed_contract.events.EventA().process_receipt_typed(receipt_a, errors=DISCARD)) + event_ab = list(deployed_contract.events.EventA().process_receipt_typed(receipt_b, errors=DISCARD)) + event_ba = list(deployed_contract.events.EventB().process_receipt_typed(receipt_a, errors=DISCARD)) + event_bb = list(deployed_contract.events.EventB().process_receipt_typed(receipt_b, errors=DISCARD)) - def test_get_typed_logs(self, w3): + assert len(event_aa) == 1 + assert isinstance(event_aa[0], EventAEvent) + assert event_aa[0].args.who == "0x0000000000000000000000000000000000000000" + assert event_aa[0].args.value == 0 + + assert len(event_ab) == 1 + assert isinstance(event_ab[0], EventAEvent) + assert event_ab[0].args.who == "0x0000000000000000000000000000000000000001" + assert event_ab[0].args.value == 1 + + assert len(event_ba) == 0 + + assert len(event_bb) == 1 + assert isinstance(event_bb[0], EventBEvent) + + def test_get_logs_typed(self, w3): """Test that we can get logs and the return is the type we expect.""" deployed_contract = EventsContract.deploy(w3=w3, account=w3.eth.accounts[0]) deployed_contract.functions.emitOneEvent(0, "0x0000000000000000000000000000000000000000").transact() deployed_contract.functions.emitTwoEvents(1, "0x0000000000000000000000000000000000000000").transact() event_a_logs = list( - deployed_contract.events.EventA.get_typed_logs( + deployed_contract.events.EventA.get_logs_typed( argument_filters={"who": "0x0000000000000000000000000000000000000000"}, from_block=0, ) ) event_b_logs = list( - deployed_contract.events.EventB.get_typed_logs( + deployed_contract.events.EventB.get_logs_typed( from_block=0, ) ) diff --git a/pypechain/test/events/types/EventsContract.py b/pypechain/test/events/types/EventsContract.py index 9511f0d4..4649e5d8 100644 --- a/pypechain/test/events/types/EventsContract.py +++ b/pypechain/test/events/types/EventsContract.py @@ -1,6 +1,6 @@ """A web3.py Contract class for the Events contract. -DO NOT EDIT. This file was generated by pypechain v0.0.40. +DO NOT EDIT. This file was generated by pypechain v0.0.41. See documentation at https://github.com/delvtech/pypechain """ # contracts have PascalCase names @@ -33,6 +33,7 @@ from hexbytes import HexBytes from typing_extensions import Self from web3 import Web3 +from web3._utils.events import EventLogErrorFlags from web3._utils.filters import LogFilter from web3.contract.contract import ( Contract, @@ -42,9 +43,10 @@ ContractFunction, ContractFunctions, ) -from web3.types import BlockIdentifier, StateOverride, TxParams +from web3.logs import WARN +from web3.types import BlockIdentifier, StateOverride, TxParams, TxReceipt -from pypechain.core import dataclass_to_tuple, rename_returned_types +from pypechain.core import BaseEventArgs, combomethod_typed, dataclass_to_tuple, rename_returned_types from .EventsTypes import EventAEvent, EventBEvent @@ -171,16 +173,13 @@ class EventsEventAContractEvent(ContractEvent): # super() get_logs and create_filter methods are generic, while our version adds values & types # pylint: disable=arguments-differ - # @combomethod destroys return types, so we are redefining functions as both class and instance - # pylint: disable=function-redefined - # pylint: disable=useless-parent-delegation def __init__(self, *argument_names: tuple[str]) -> None: super().__init__(*argument_names) - # We ignore types here for function redefinition - def get_typed_logs( # type: ignore - self: "EventsEventAContractEvent", + @combomethod_typed + def get_logs_typed( + self, argument_filters: dict[str, Any] | None = None, from_block: BlockIdentifier | None = None, to_block: BlockIdentifier | None = None, @@ -207,19 +206,10 @@ def get_typed_logs( # type: ignore for abi_event in abi_events ] - @classmethod - # We ignore types here for function redefinition - def get_typed_logs( # type: ignore - cls: Type["EventsEventAContractEvent"], - argument_filters: dict[str, Any] | None = None, - from_block: BlockIdentifier | None = None, - to_block: BlockIdentifier | None = None, - block_hash: HexBytes | None = None, - ) -> Iterable[EventAEvent]: - """Extension of `get_logs` that return a typed dataclass of the event.""" - abi_events = super().get_logs( - argument_filters=argument_filters, from_block=from_block, to_block=to_block, block_hash=block_hash - ) + @combomethod_typed + def process_receipt_typed(self, txn_receipt: TxReceipt, errors: EventLogErrorFlags = WARN) -> Iterable[EventAEvent]: + """Extension of `process_receipt` that return a typed dataclass of the event.""" + abi_events = super().process_receipt(txn_receipt, errors) # TODO there may be issues with this function if the user uses a middleware that changes event structure. return [ EventAEvent( @@ -237,29 +227,9 @@ def get_typed_logs( # type: ignore for abi_event in abi_events ] + @combomethod_typed def create_filter( # type: ignore - self: "EventsEventAContractEvent", - *, # PEP 3102 - argument_filters: dict[str, Any] | None = None, - from_block: BlockIdentifier | None = None, - to_block: BlockIdentifier = "latest", - address: ChecksumAddress | None = None, - topics: Sequence[Any] | None = None, - ) -> LogFilter: - return cast( - LogFilter, - super().create_filter( - argument_filters=argument_filters, - from_block=from_block, - to_block=to_block, - address=address, - topics=topics, - ), - ) - - @classmethod - def create_filter( # type: ignore - cls: Type["EventsEventAContractEvent"], + self, *, # PEP 3102 argument_filters: dict[str, Any] | None = None, from_block: BlockIdentifier | None = None, @@ -285,16 +255,13 @@ class EventsEventBContractEvent(ContractEvent): # super() get_logs and create_filter methods are generic, while our version adds values & types # pylint: disable=arguments-differ - # @combomethod destroys return types, so we are redefining functions as both class and instance - # pylint: disable=function-redefined - # pylint: disable=useless-parent-delegation def __init__(self, *argument_names: tuple[str]) -> None: super().__init__(*argument_names) - # We ignore types here for function redefinition - def get_typed_logs( # type: ignore - self: "EventsEventBContractEvent", + @combomethod_typed + def get_logs_typed( + self, argument_filters: dict[str, Any] | None = None, from_block: BlockIdentifier | None = None, to_block: BlockIdentifier | None = None, @@ -313,23 +280,15 @@ def get_typed_logs( # type: ignore address=abi_event.address, block_hash=abi_event.blockHash, block_number=abi_event.blockNumber, + args=BaseEventArgs(), ) for abi_event in abi_events ] - @classmethod - # We ignore types here for function redefinition - def get_typed_logs( # type: ignore - cls: Type["EventsEventBContractEvent"], - argument_filters: dict[str, Any] | None = None, - from_block: BlockIdentifier | None = None, - to_block: BlockIdentifier | None = None, - block_hash: HexBytes | None = None, - ) -> Iterable[EventBEvent]: - """Extension of `get_logs` that return a typed dataclass of the event.""" - abi_events = super().get_logs( - argument_filters=argument_filters, from_block=from_block, to_block=to_block, block_hash=block_hash - ) + @combomethod_typed + def process_receipt_typed(self, txn_receipt: TxReceipt, errors: EventLogErrorFlags = WARN) -> Iterable[EventBEvent]: + """Extension of `process_receipt` that return a typed dataclass of the event.""" + abi_events = super().process_receipt(txn_receipt, errors) # TODO there may be issues with this function if the user uses a middleware that changes event structure. return [ EventBEvent( @@ -339,33 +298,14 @@ def get_typed_logs( # type: ignore address=abi_event.address, block_hash=abi_event.blockHash, block_number=abi_event.blockNumber, + args=BaseEventArgs(), ) for abi_event in abi_events ] + @combomethod_typed def create_filter( # type: ignore - self: "EventsEventBContractEvent", - *, # PEP 3102 - argument_filters: dict[str, Any] | None = None, - from_block: BlockIdentifier | None = None, - to_block: BlockIdentifier = "latest", - address: ChecksumAddress | None = None, - topics: Sequence[Any] | None = None, - ) -> LogFilter: - return cast( - LogFilter, - super().create_filter( - argument_filters=argument_filters, - from_block=from_block, - to_block=to_block, - address=address, - topics=topics, - ), - ) - - @classmethod - def create_filter( # type: ignore - cls: Type["EventsEventBContractEvent"], + self, *, # PEP 3102 argument_filters: dict[str, Any] | None = None, from_block: BlockIdentifier | None = None, @@ -388,9 +328,9 @@ def create_filter( # type: ignore class EventsContractEvents(ContractEvents): """ContractEvents for the Events contract.""" - EventA: EventsEventAContractEvent + EventA: Type[EventsEventAContractEvent] - EventB: EventsEventBContractEvent + EventB: Type[EventsEventBContractEvent] def __init__( self, @@ -400,11 +340,11 @@ def __init__( ) -> None: super().__init__(abi, w3, address) self.EventA = cast( - EventsEventAContractEvent, + Type[EventsEventAContractEvent], EventsEventAContractEvent.factory("EventA", w3=w3, contract_abi=abi, address=address, event_name="EventA"), ) self.EventB = cast( - EventsEventBContractEvent, + Type[EventsEventBContractEvent], EventsEventBContractEvent.factory("EventB", w3=w3, contract_abi=abi, address=address, event_name="EventB"), ) diff --git a/pypechain/test/events/types/EventsTypes.py b/pypechain/test/events/types/EventsTypes.py index 80ca4a97..584afbb6 100644 --- a/pypechain/test/events/types/EventsTypes.py +++ b/pypechain/test/events/types/EventsTypes.py @@ -1,6 +1,6 @@ """Dataclasses for all structs in the Events contract. -DO NOT EDIT. This file was generated by pypechain v0.0.40. +DO NOT EDIT. This file was generated by pypechain v0.0.41. See documentation at https://github.com/delvtech/pypechain """ # super() call methods are generic, while our version adds values & types @@ -20,15 +20,15 @@ from dataclasses import dataclass -from pypechain.core import BaseEvent +from pypechain.core import BaseEvent, BaseEventArgs -@dataclass(kw_only=True) +@dataclass(kw_only=True, frozen=True) class EventAEvent(BaseEvent): """The event type for event EventA""" - @dataclass - class EventAEventArgs: + @dataclass(kw_only=True, frozen=True) + class EventAEventArgs(BaseEventArgs): """The args to the event EventA""" who: str @@ -39,7 +39,7 @@ class EventAEventArgs: __name__: str = "EventA" -@dataclass(kw_only=True) +@dataclass(kw_only=True, frozen=True) class EventBEvent(BaseEvent): """The event type for event EventB""" diff --git a/pypechain/test/events/types/__init__.py b/pypechain/test/events/types/__init__.py index 6979a4d5..f519f592 100644 --- a/pypechain/test/events/types/__init__.py +++ b/pypechain/test/events/types/__init__.py @@ -1,6 +1,6 @@ """Export all types from generated files. -DO NOT EDIT. This file was generated by pypechain v0.0.40. +DO NOT EDIT. This file was generated by pypechain v0.0.41. See documentation at https://github.com/delvtech/pypechain """ from .EventsContract import EventsContract diff --git a/pypechain/test/events/types/pypechain.version b/pypechain/test/events/types/pypechain.version index eb6a2aae..85e555e5 100644 --- a/pypechain/test/events/types/pypechain.version +++ b/pypechain/test/events/types/pypechain.version @@ -1 +1 @@ -pypechain == 0.0.40 \ No newline at end of file +pypechain == 0.0.41 \ No newline at end of file diff --git a/pypechain/test/overloading/types/OverloadedMethodsContract.py b/pypechain/test/overloading/types/OverloadedMethodsContract.py index 0b707d27..a1be7c28 100644 --- a/pypechain/test/overloading/types/OverloadedMethodsContract.py +++ b/pypechain/test/overloading/types/OverloadedMethodsContract.py @@ -1,6 +1,6 @@ """A web3.py Contract class for the OverloadedMethods contract. -DO NOT EDIT. This file was generated by pypechain v0.0.40. +DO NOT EDIT. This file was generated by pypechain v0.0.41. See documentation at https://github.com/delvtech/pypechain """ # contracts have PascalCase names diff --git a/pypechain/test/overloading/types/__init__.py b/pypechain/test/overloading/types/__init__.py index f212be19..1537d5f4 100644 --- a/pypechain/test/overloading/types/__init__.py +++ b/pypechain/test/overloading/types/__init__.py @@ -1,6 +1,6 @@ """Export all types from generated files. -DO NOT EDIT. This file was generated by pypechain v0.0.40. +DO NOT EDIT. This file was generated by pypechain v0.0.41. See documentation at https://github.com/delvtech/pypechain """ from .OverloadedMethodsContract import OverloadedMethodsContract diff --git a/pypechain/test/overloading/types/pypechain.version b/pypechain/test/overloading/types/pypechain.version index eb6a2aae..85e555e5 100644 --- a/pypechain/test/overloading/types/pypechain.version +++ b/pypechain/test/overloading/types/pypechain.version @@ -1 +1 @@ -pypechain == 0.0.40 \ No newline at end of file +pypechain == 0.0.41 \ No newline at end of file diff --git a/pypechain/test/return_types/types/ReturnTypesContract.py b/pypechain/test/return_types/types/ReturnTypesContract.py index b6ee83d6..b880601c 100644 --- a/pypechain/test/return_types/types/ReturnTypesContract.py +++ b/pypechain/test/return_types/types/ReturnTypesContract.py @@ -1,6 +1,6 @@ """A web3.py Contract class for the ReturnTypes contract. -DO NOT EDIT. This file was generated by pypechain v0.0.40. +DO NOT EDIT. This file was generated by pypechain v0.0.41. See documentation at https://github.com/delvtech/pypechain """ # contracts have PascalCase names diff --git a/pypechain/test/return_types/types/ReturnTypesTypes.py b/pypechain/test/return_types/types/ReturnTypesTypes.py index 150895ab..649604fd 100644 --- a/pypechain/test/return_types/types/ReturnTypesTypes.py +++ b/pypechain/test/return_types/types/ReturnTypesTypes.py @@ -1,6 +1,6 @@ """Dataclasses for all structs in the ReturnTypes contract. -DO NOT EDIT. This file was generated by pypechain v0.0.40. +DO NOT EDIT. This file was generated by pypechain v0.0.41. See documentation at https://github.com/delvtech/pypechain """ # super() call methods are generic, while our version adds values & types diff --git a/pypechain/test/return_types/types/__init__.py b/pypechain/test/return_types/types/__init__.py index 8dd09102..adc28dad 100644 --- a/pypechain/test/return_types/types/__init__.py +++ b/pypechain/test/return_types/types/__init__.py @@ -1,6 +1,6 @@ """Export all types from generated files. -DO NOT EDIT. This file was generated by pypechain v0.0.40. +DO NOT EDIT. This file was generated by pypechain v0.0.41. See documentation at https://github.com/delvtech/pypechain """ from .ReturnTypesContract import ReturnTypesContract diff --git a/pypechain/test/return_types/types/pypechain.version b/pypechain/test/return_types/types/pypechain.version index eb6a2aae..85e555e5 100644 --- a/pypechain/test/return_types/types/pypechain.version +++ b/pypechain/test/return_types/types/pypechain.version @@ -1 +1 @@ -pypechain == 0.0.40 \ No newline at end of file +pypechain == 0.0.41 \ No newline at end of file diff --git a/pypechain/test/structs/types/IStructsTypes.py b/pypechain/test/structs/types/IStructsTypes.py index 70671eb8..48a689cc 100644 --- a/pypechain/test/structs/types/IStructsTypes.py +++ b/pypechain/test/structs/types/IStructsTypes.py @@ -1,6 +1,6 @@ """Dataclasses for all structs in the IStructs contract. -DO NOT EDIT. This file was generated by pypechain v0.0.40. +DO NOT EDIT. This file was generated by pypechain v0.0.41. See documentation at https://github.com/delvtech/pypechain """ # super() call methods are generic, while our version adds values & types diff --git a/pypechain/test/structs/types/StructsAContract.py b/pypechain/test/structs/types/StructsAContract.py index 5377a10c..67b0c284 100644 --- a/pypechain/test/structs/types/StructsAContract.py +++ b/pypechain/test/structs/types/StructsAContract.py @@ -1,6 +1,6 @@ """A web3.py Contract class for the StructsA contract. -DO NOT EDIT. This file was generated by pypechain v0.0.40. +DO NOT EDIT. This file was generated by pypechain v0.0.41. See documentation at https://github.com/delvtech/pypechain """ # contracts have PascalCase names diff --git a/pypechain/test/structs/types/StructsATypes.py b/pypechain/test/structs/types/StructsATypes.py index c5bcd7e4..320b5d4c 100644 --- a/pypechain/test/structs/types/StructsATypes.py +++ b/pypechain/test/structs/types/StructsATypes.py @@ -1,6 +1,6 @@ """Dataclasses for all structs in the StructsA contract. -DO NOT EDIT. This file was generated by pypechain v0.0.40. +DO NOT EDIT. This file was generated by pypechain v0.0.41. See documentation at https://github.com/delvtech/pypechain """ # super() call methods are generic, while our version adds values & types diff --git a/pypechain/test/structs/types/StructsBContract.py b/pypechain/test/structs/types/StructsBContract.py index 728ea411..04459ab9 100644 --- a/pypechain/test/structs/types/StructsBContract.py +++ b/pypechain/test/structs/types/StructsBContract.py @@ -1,6 +1,6 @@ """A web3.py Contract class for the StructsB contract. -DO NOT EDIT. This file was generated by pypechain v0.0.40. +DO NOT EDIT. This file was generated by pypechain v0.0.41. See documentation at https://github.com/delvtech/pypechain """ # contracts have PascalCase names diff --git a/pypechain/test/structs/types/StructsCContract.py b/pypechain/test/structs/types/StructsCContract.py index 9a67ef78..28f7534b 100644 --- a/pypechain/test/structs/types/StructsCContract.py +++ b/pypechain/test/structs/types/StructsCContract.py @@ -1,6 +1,6 @@ """A web3.py Contract class for the StructsC contract. -DO NOT EDIT. This file was generated by pypechain v0.0.40. +DO NOT EDIT. This file was generated by pypechain v0.0.41. See documentation at https://github.com/delvtech/pypechain """ # contracts have PascalCase names diff --git a/pypechain/test/structs/types/StructsCTypes.py b/pypechain/test/structs/types/StructsCTypes.py index 2eaf56f7..4e696da3 100644 --- a/pypechain/test/structs/types/StructsCTypes.py +++ b/pypechain/test/structs/types/StructsCTypes.py @@ -1,6 +1,6 @@ """Dataclasses for all structs in the StructsC contract. -DO NOT EDIT. This file was generated by pypechain v0.0.40. +DO NOT EDIT. This file was generated by pypechain v0.0.41. See documentation at https://github.com/delvtech/pypechain """ # super() call methods are generic, while our version adds values & types diff --git a/pypechain/test/structs/types/__init__.py b/pypechain/test/structs/types/__init__.py index 8784e01d..4ffdc012 100644 --- a/pypechain/test/structs/types/__init__.py +++ b/pypechain/test/structs/types/__init__.py @@ -1,6 +1,6 @@ """Export all types from generated files. -DO NOT EDIT. This file was generated by pypechain v0.0.40. +DO NOT EDIT. This file was generated by pypechain v0.0.41. See documentation at https://github.com/delvtech/pypechain """ from .StructsCContract import StructsCContract diff --git a/pypechain/test/structs/types/pypechain.version b/pypechain/test/structs/types/pypechain.version index eb6a2aae..85e555e5 100644 --- a/pypechain/test/structs/types/pypechain.version +++ b/pypechain/test/structs/types/pypechain.version @@ -1 +1 @@ -pypechain == 0.0.40 \ No newline at end of file +pypechain == 0.0.41 \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index d6da4ee3..cc667342 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "pypechain" -version = "0.0.40" +version = "0.0.41" authors = [ { name = "Matthew Brown", email = "matt@delv.tech" }, { name = "Dylan Paiton", email = "dylan@delv.tech" }, diff --git a/snapshots/has_events.py b/snapshots/has_events.py index 1bfab1f9..a65f286b 100644 --- a/snapshots/has_events.py +++ b/snapshots/has_events.py @@ -5,18 +5,15 @@ class OverloadedTransferContractEvent(ContractEvent): # super() get_logs and create_filter methods are generic, while our version adds values & types # pylint: disable=arguments-differ - # @combomethod destroys return types, so we are redefining functions as both class and instance - # pylint: disable=function-redefined - # pylint: disable=useless-parent-delegation def __init__(self, *argument_names: tuple[str]) -> None: super().__init__(*argument_names) - # We ignore types here for function redefinition - def get_typed_logs( # type: ignore - self: "OverloadedTransferContractEvent", + @combomethod_typed + def get_logs_typed( + self, argument_filters: dict[str, Any] | None = None, from_block: BlockIdentifier | None = None, to_block: BlockIdentifier | None = None, @@ -42,18 +39,13 @@ def get_typed_logs( # type: ignore ) for abi_event in abi_events ] - - @classmethod - # We ignore types here for function redefinition - def get_typed_logs( # type: ignore - cls: Type["OverloadedTransferContractEvent"], - argument_filters: dict[str, Any] | None = None, - from_block: BlockIdentifier | None = None, - to_block: BlockIdentifier | None = None, - block_hash: HexBytes | None = None, + + @combomethod_typed + def process_receipt_typed( + self, txn_receipt: TxReceipt, errors: EventLogErrorFlags = WARN ) -> Iterable[TransferEvent]: - """Extension of `get_logs` that return a typed dataclass of the event.""" - abi_events = super().get_logs(argument_filters=argument_filters, from_block=from_block, to_block=to_block, block_hash=block_hash) + """Extension of `process_receipt` that return a typed dataclass of the event.""" + abi_events = super().process_receipt(txn_receipt, errors) # TODO there may be issues with this function if the user uses a middleware that changes event structure. return [ TransferEvent( @@ -73,20 +65,9 @@ def get_typed_logs( # type: ignore ) for abi_event in abi_events ] + @combomethod_typed def create_filter( # type: ignore - self: "OverloadedTransferContractEvent", - *, # PEP 3102 - argument_filters: dict[str, Any] | None = None, - from_block: BlockIdentifier | None = None, - to_block: BlockIdentifier = "latest", - address: ChecksumAddress | None = None, - topics: Sequence[Any] | None = None, - ) -> LogFilter: - return cast(LogFilter, super().create_filter(argument_filters=argument_filters, from_block=from_block, to_block=to_block, address=address, topics=topics)) - - @classmethod - def create_filter( # type: ignore - cls: Type["OverloadedTransferContractEvent"], + self, *, # PEP 3102 argument_filters: dict[str, Any] | None = None, from_block: BlockIdentifier | None = None, @@ -103,18 +84,15 @@ class OverloadedApprovalContractEvent(ContractEvent): # super() get_logs and create_filter methods are generic, while our version adds values & types # pylint: disable=arguments-differ - # @combomethod destroys return types, so we are redefining functions as both class and instance - # pylint: disable=function-redefined - # pylint: disable=useless-parent-delegation def __init__(self, *argument_names: tuple[str]) -> None: super().__init__(*argument_names) - # We ignore types here for function redefinition - def get_typed_logs( # type: ignore - self: "OverloadedApprovalContractEvent", + @combomethod_typed + def get_logs_typed( + self, argument_filters: dict[str, Any] | None = None, from_block: BlockIdentifier | None = None, to_block: BlockIdentifier | None = None, @@ -140,18 +118,13 @@ def get_typed_logs( # type: ignore ) for abi_event in abi_events ] - - @classmethod - # We ignore types here for function redefinition - def get_typed_logs( # type: ignore - cls: Type["OverloadedApprovalContractEvent"], - argument_filters: dict[str, Any] | None = None, - from_block: BlockIdentifier | None = None, - to_block: BlockIdentifier | None = None, - block_hash: HexBytes | None = None, + + @combomethod_typed + def process_receipt_typed( + self, txn_receipt: TxReceipt, errors: EventLogErrorFlags = WARN ) -> Iterable[ApprovalEvent]: - """Extension of `get_logs` that return a typed dataclass of the event.""" - abi_events = super().get_logs(argument_filters=argument_filters, from_block=from_block, to_block=to_block, block_hash=block_hash) + """Extension of `process_receipt` that return a typed dataclass of the event.""" + abi_events = super().process_receipt(txn_receipt, errors) # TODO there may be issues with this function if the user uses a middleware that changes event structure. return [ ApprovalEvent( @@ -171,20 +144,9 @@ def get_typed_logs( # type: ignore ) for abi_event in abi_events ] + @combomethod_typed def create_filter( # type: ignore - self: "OverloadedApprovalContractEvent", - *, # PEP 3102 - argument_filters: dict[str, Any] | None = None, - from_block: BlockIdentifier | None = None, - to_block: BlockIdentifier = "latest", - address: ChecksumAddress | None = None, - topics: Sequence[Any] | None = None, - ) -> LogFilter: - return cast(LogFilter, super().create_filter(argument_filters=argument_filters, from_block=from_block, to_block=to_block, address=address, topics=topics)) - - @classmethod - def create_filter( # type: ignore - cls: Type["OverloadedApprovalContractEvent"], + self, *, # PEP 3102 argument_filters: dict[str, Any] | None = None, from_block: BlockIdentifier | None = None, @@ -199,9 +161,9 @@ def create_filter( # type: ignore class OverloadedContractEvents(ContractEvents): """ContractEvents for the Overloaded contract.""" - Transfer: OverloadedTransferContractEvent + Transfer: Type[OverloadedTransferContractEvent] - Approval: OverloadedApprovalContractEvent + Approval: Type[OverloadedApprovalContractEvent] def __init__( @@ -211,14 +173,14 @@ def __init__( address: ChecksumAddress | None = None, ) -> None: super().__init__(abi, w3, address) - self.Transfer = cast(OverloadedTransferContractEvent, OverloadedTransferContractEvent.factory( + self.Transfer = cast(Type[OverloadedTransferContractEvent], OverloadedTransferContractEvent.factory( "Transfer", w3=w3, contract_abi=abi, address=address, event_name="Transfer" )) - self.Approval = cast(OverloadedApprovalContractEvent, OverloadedApprovalContractEvent.factory( + self.Approval = cast(Type[OverloadedApprovalContractEvent], OverloadedApprovalContractEvent.factory( "Approval", w3=w3, contract_abi=abi,