Developing CLI plugins
New in version 0.18.0.
This is an incubating feature. API adjustments are still possible.
0.18.0, pyHanko’s CLI can load
from external sources with minimal configuration.
If you develop an integration for a remote signing service or hardware device that isn’t already supported by the pyHanko CLI out of the box, you can make your implementation available to CLI users as a separate package. If you set things up the right way, all your users have to do is install it, and pyHanko will automagically detect the plugin.
This page aims to provide you with some pointers to upgrade your
Signer implementation into a CLI-integrated plugin.
Plugins are only supported on Python 3.8 and up.
Throughout, we assume that you have a
Signer implementation that you
want to hook into the CLI. This could be an
integration that you developed yourself,
or simply a wrapper around an existing
Signer to facilitate integration
with some third-party service or a particular hardware device.
In order to help you write the necessary glue code to patch things into the CLI, we’ll go over the following:
how to provide the mapping between CLI arguments and instances of your
how to get access to other parts of the CLI context (e.g. configuration settings);
how to ensure that the
pyhankoexecutable picks up your plugin.
The plugin API
This is what the basic skeleton looks like.
class MySigningCommand(SigningCommandPlugin): subcommand_name = 'mysigner' help_summary = 'a short line about the plugin' def click_options(self) -> List[click.Option]: ... def create_signer( self, context: CLIContext, **kwargs ) -> ContextManager[Signer]: ...
As an example, the options for a simplified version of the
in pyHanko’s CLI could’ve been defined as follows.
def click_options(self) -> List[click.Option]: return [ click.Option( ('--lib',), help='path to PKCS#11 module', type=readable_file, required=False, ), click.Option( ('--token-label',), help='PKCS#11 token label', type=str, required=False, ), click.Option( ('--cert-label',), help='certificate label', type=str, required=False, ), click.Option( ('--key-label',), help='key label', type=str, required=False ), ]
The core plumbing for your plugin will be supplied in the
Here’s a brief rundown of what the arguments mean.
Note that the return type of
not just a
Signer, but a context manager wrapping a
This allows pyHanko to easily return control to the plugin after signing or
when errors are thrown, so that the plugin code can run its own
The plugin class must have a no-arguments
Plugin discovery and registration
Using a package entry points
The easiest way to make your plugin discoverable is to package it
with a package entry point
for pyHanko CLI plugins. The entry point group ID is
If you manage your plugin’s packaging metadata with
this is all you have to add:
[project.entry-points."pyhanko.cli_plugin.signing"] your_plugin = "some_package.path.to.module:SomePluginClass"
With entry points set up, pyHanko will automatically discover your plugin if it’s
installed (i.e. if
importlib can find it).
From the configuration file
If you don’t want to use packages or can’t for some reason, you also have the option to reference them from pyHanko’s configuration file, like so:
plugins: - some_package.path.to.module:SomePluginClass