Web3.py Patterns: Customization
[Last update: 6/25/24]
If you're looking to make Web3.py do something outside of its native functionality, you've got at least a few options: middleware, custom methods, external modules, and custom providers. This post will walk through what each of those are, when you might reach for them, and how to get started.
1. Middleware
What
Middleware allows you to add some behavior to existing methods prior to making a request or after a result is received.
When
Reach for middleware when you want something to happen every time a certain RPC call or set of calls are performed, e.g., logging, data visualization, data conversion, etc.
How
A set of default middleware comes standard in Web3.py, along with more optional middleware to pull in. However, If you need to write some custom middleware, you have a couple of syntax options: functions or classes. The function syntax is more typical for straightforward use cases.
Middleware is executed in a particular order, so the API allows you to add
your new middleware to the end of the list, inject
, replace
or remove
a layer, or clear
the whole middleware stack.
2. Custom Methods
What
Arbitrary RPC methods may be added to existing modules.
When
Registering custom methods can be handy if you're working with a client with nonstandard RPC commands or are testing some custom functionality within a forked client.
Custom methods can also be used to overwrite existing methods, if you want to apply your own request or result formatter.
How
The attach_methods
function is available on every module and accepts a dictionary with method names and corresponding Method
:
from web3.method import Method
w3.eth.attach_methods({"create_access_list": Method("eth_createAccessList")})
w3.eth.create_access_list(...)
You may optionally include custom handlers for input munging, request, and result formatters.
If you prefer, you can add a property instead of a method by setting is_property
to True
.
3. External Modules
What
External modules offer still more flexibility by allowing you to import groups of APIs under one banner. Think: plugins.
When
External modules may be useful for introducing an entire L2 API or several nonstandard RPC methods supported by one client, e.g., Erigon-specific methods like erigon_getHeaderByHash
, erigon_getHeaderByNumber
, and so on.
How
Modules need only be classes and can reference the parent Web3
instance. Configure your external modules at the time of Web3
instantiation using the external_modules
keyword argument, or at any point via the attach_modules
method:
# add modules at instantiation:
w3 = Web3(
HTTPProvider(...),
external_modules={"example": ExampleModule}
)
# add modules after instantiation:
w3.attach_modules({"example": ExampleModule})
# invoking external modules:
w3.example.example_method()
More context, including a nested module example, available here.
4. Custom Providers
What
At its core, a provider defines how requests are performed.
When
Building a custom provider is only relevant for the rare occasions that you're plugging into a custom testing framework, or something in that vein. If you're just looking to connect to another EVM blockchain, sidechain, or rollup, typically you can just configure one of the existing options: HTTPProvider, IPCProvider, or WebsocketProvider.
How
Providers only require two methods, make_request
and isConnected
, and a definition of middlewares
.
from web3.providers.base import BaseProvider
class CustomProvider(BaseProvider):
middlewares = ()
def make_request(self, method, params):
return {"result": {"welp": "lol"}}
def is_connected(self):
print(True)
w3 = Web3(CustomProvider())
w3.eth.get_block("latest")
# AttributeDict({"welp": "lol"})
Wrapping up
The options above are ordered roughly from least to most flexibility they offer. In practice, I expect middleware and external modules to get the most mileage, particularly once trusted external modules become commonplace.
Deliberately not included in this post is monkey patching. If you've gone down that path... is everything okay? Seriously though, open an issue if you have another vector you'd like to customize that requires monkey patching today.