Web3.py: Add API to iterate through all events in a contract

Created on 16 Jul 2018  路  7Comments  路  Source: ethereum/web3.py

What was wrong?

No easy way to get all events for a contract (while still parsing the results). See this StackExchange question. One option is to iterate over all the events, but it's a bit awkward right now. I think the easiest way is:

    from web3.contract import ContractEvent

    filters = [
      event.createFilter(fromBlock='latest')
      for event in myContract.events
      if isinstance(event, ContractEvent)
    ]

How can it be fixed?

Some options:

  • Implement __iter__ on Contract.events to iterate through all events in the ABI (my favorite option, except that it's inconsistent with contract.functions, which is doing the wrong thing IMO)
  • Add a new Contract.all_events() equivalent to Contract.all_functions()

Then the example changes to:

    filters = [
      event.createFilter(fromBlock='latest')
      for event in myContract.events
    ]

Of course, we could also implement contract.create_filter() like web3.js's contract.events.allEvents. I kind of like that the filters are event specific right now, though. I don't think it's too big a deal to require callers to write a filter loop on events.

Most helpful comment

It might be better to just steer users towards instantiating filter object with just the contract address rather than providing a helper function to do the same.

The key difference is that they want the automatic argument decoding, which they don't get with the generic filter on an address. I like the recipe idea.

All 7 comments

+1 for implementing __iter__. I need to think about all_events. While I see the use, it would be a simple filter with just the contract address, and not allow one to filter on arguments or topics. It might be better to just steer users towards instantiating filter object with just the contract address rather than providing a helper function to do the same.

Maybe we could publish a recipe on combining filters into a single object that can be polled.

It might be better to just steer users towards instantiating filter object with just the contract address rather than providing a helper function to do the same.

The key difference is that they want the automatic argument decoding, which they don't get with the generic filter on an address. I like the recipe idea.

@carver

Implement __iter__ on Contract.events to iterate through all events in the ABI (my favorite option, except that it's inconsistent with contract.functions, which is doing the wrong thing IMO)

__iter__() has been implemented in ContractFunctions. Is there something specific about that implementation that you would like to see changed? This might be a good time to figure out a consistent approach for both functions and events.

Is there something specific about that implementation that you would like to see changed?

Yeah, it's only returning the name (str) of the function. I think it should return the full ContractFunction object.

Wow, this ticket is still open! Here is a snippet that emits all decode-able events for a specific contract. I too found this was an annoyance @carver

from web3.contract import Contract
from web3._utils.events import get_event_data
from eth_utils import event_abi_to_log_topic


def fetch_events(contract: Contract, fromBlock, toBlock=None):
    topic2abi = {event_abi_to_log_topic(_): _
                 for _ in contract.abi if _['type'] == 'event'}

    logs = w3.eth.getLogs(dict(
        address=contract.address,
        fromBlock=fromBlock,
        toBlock=toBlock
    ))

    for entry in logs:
        topic0 = entry['topics'][0]
        if topic0 in topic2abi:
            yield get_event_data(w3.codec, topic2abi[topic0], entry)

Ping @kclowes for an old issue that could use some love.

Was this page helpful?
0 / 5 - 0 ratings