Developing Plugins
Your plugin project can be any type of python project, so long as its package name starts with ape_
(such as
ape_ethereum
). To create an ape
plugin, implement one or more API classes from the the ape.api
namespace or add
key ape_cli_subcommands
to your entry-points list in your project’s setup.py
, depending on what type of plugin you
want to create. This guide is intended to assist in both of those use-cases.
The following is a list of example plugins to use as a reference when developing plugins:
the Solidity plugin, an example
CompilerAPI
the Infura plugin, an example
ProviderAPI
the Trezor plugin, an example
AccountAPI
the Tokenlists plugin, an example CLI Extension
Initialize a Plugin Project
As previously mentioned, a plugin project is merely a python project. However, you can optionally use this project template for initializing your plugin. NOTE: this template is primarily designed for plugins built within the ApeWorX team organization; not everything may apply. It is okay to delete anything that does not work or that you don’t find helpful. The template may be good to follow if you want to keep your plugin of similar quality to plugins developed by the ApeWorX team.
Implementing API Classes
API classes (classes from the ape.api
namespace) are primary composed of abstract methods and properties that plugins
must implement. A benefit of the plugin system is that each plugin can implement these however they need, so long as
they conform to the API interface. Two plugins with the same API may do entirely different things and yet be
interchangeable in their usage.
To implement an API, import its class and use it as a base-class in your implementation class. WARNING: The plugin will fail to work properly if you do not implement all the abstract methods.
from ape.api import ProviderAPI
from web3 import Web3, HTTPProvider
class MyProvider(ProviderAPI):
_web3: Web3 = None # type: ignore
def connect(self):
self._web3 = Web3(HTTPProvider(str("https://localhost:1337")))
"""Implement rest of abstract methods"""
Registering API Classes
Once you have finished implementing your API classes, you need to register them using the @plugins.register method decorator.
from ape import plugins
# Here, we register our provider plugin so we can use it in 'ape'.
@plugins.register(plugins.ProviderPlugin)
def providers():
# NOTE: 'MyProvider' defined in a prior code-block.
yield "ethereum", "local", MyProvider
This decorator hooks into ape core and ties everything together by looking for all local installed site-packages that
start with ape_
. Then, it will loop through these potential ape
plugins and see which ones have created a plugin
type registration. If the plugin type registration is found, then ape
knows this package is a plugin and attempts to
process it according to its registration interface.
CLI Plugins
The ape
CLI is built using the python package click. To create a CLI plugin,
create any type of click
command (such as a click.group
or a click.command
).
_cli.py
:
import click
@click.group
def cli():
"""My custom commands."""
@cli.command()
def my_sub_cmd():
"""My subcommand."""
Then, register it using entrypoints
, which is a built-in python registry of items declared in setup.py
.
setup.py
:
...
entry_points={
"ape_cli_subcommands": [
"ape_myplugin=ape_myplugin._cli:cli",
],
},
...
NOTE: Typically, a _cli.py
module is used instead of a __init__.py
module for the location of the Click CLI
group because it is logically separate from the Python module loading process. If you try to define them together and
use ape
as a library as well, there is a race condition in the loading process that will prevent the CLI plugin from
working.
For common ape-click
usages, use the ape.cli
namespace. For example, use the
@existing_alias_argument() decorator) when you need
a CLI argument for specifying an existing account alias:
import click
from ape.cli import existing_alias_argument
@click.command()
@existing_alias_argument()
def my_cmd(alias):
click.echo(f"{alias} is an existing account!")
Using Plugins
Once you have finished implementing and registering your API classes, they will now be part of ape
. For example,
if you implemented the AccountAPI
, you can now use accounts created from this plugin. The top-level ape
manager
classes are indifferent about the source of the plugin.
from ape import accounts
# The manager can load accounts from any account-based plugin.
my_ledger_account = accounts.load("ledger_0") # Created using the 'ape-ledger' plugin
my_trezor_account = accounts.load("trezor_0") # Created using the 'ape-trezor' plugin
Similarly, if you implemented a ProviderAPI
, that provider is now accessible in the CLI via the --network
option:
ape run my_script --network ethereum:local:my_provider_plugin
NOTE: The --network
option is available on the commands run
, test
, and console
or any CLI command that
uses the network option decorator.
When creating the CLI-based plugins, you should see your CLI command as a top-level command in the ape --help
output:
Commands:
...
my-plugin Utilities for my plugin
...
Logging
Use Ape’s logger in your plugins or scripts by importing it from the ape.logging
module or
by using it off the CLI context (from using the @ape_cli_context
decorator).
Import the logger from the logging module
from ape.logging import logger
logger.info("This is a log message")
Use the logger from the @ape_cli_context
from ape.cli import ape_cli_context
@ape_cli_context()
def my_command(cli_ctx)
cli_ctx.logger.info("my log message")