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:

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", "development", 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:development: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")