Package: click-plugins
An extension module forclickto register
external CLI commands via setuptools entry-points.Why?Lets say you develop a commandline interface and someone requests a new feature
that is absolutely related to your project but would have negative consequences
like additional dependencies, major refactoring, or maybe its just too domain
specific to be supported directly.  Rather than developing a separate standalone
utility you could offer up asetuptools entry pointthat allows others to use your commandline utility as a home for their related
sub-commands.  You get to choose where these sub-commands or sub-groups CAN be
registered but the plugin developer gets to choose they ARE registered.  You
could have all plugins register alongside the core commands, in a special
sub-group, across multiple sub-groups, or some combination.Enabling PluginsFor a more detailed example see theexamplessection.The only requirement is decoratingclick.group()withclick_plugins.with_plugins()which handles attaching external commands and groups.  In this case the core CLI developer
registers CLI plugins fromcore_package.cli_plugins.frompkg_resourcesimportiter_entry_pointsimportclickfromclick_pluginsimportwith_plugins@with_plugins(iter_entry_points('core_package.cli_plugins'))@click.group()defcli():"""Commandline interface for yourpackage."""@cli.command()defsubcommand():"""Subcommand that does something."""Developing PluginsPlugin developers need to register their sub-commands or sub-groups to an
entry-point in theirsetup.pythat is loaded by the core package.fromsetuptoolsimportsetupsetup(name='yourscript',version='0.1',py_modules=['yourscript'],install_requires=['click',],entry_points='''
        [core_package.cli_plugins]
        cool_subcommand=yourscript.cli:cool_subcommand
        another_subcommand=yourscript.cli:another_subcommand
    ''',)Broken and Incompatible PluginsAny sub-command or sub-group that cannot be loaded is caught and converted to
aclick_plugins.core.BrokenCommand()rather than just crashing the entire
CLI.  The short-help is converted to a warning message like:Warning: could not load plugin. See ``<CLI> <command/group> --help``.and if the sub-command or group is executed the entire traceback is printed.Best Practices and Extra CreditOpening a CLI to plugins encourages other developers to independently extend
functionality independently but there is no guarantee these new features will
be “on brand”.  Plugin developers are almost certainly already using features
in the core package the CLI belongs to so defining commonly used arguments and
options in one place lets plugin developers reuse these flags to produce a more
cohesive CLI.  If the CLI is simple maybe just define them at the top ofyourpackage/cli.pyor for more complex packages something likeyourpackage/cli/options.py.  These common options need to be easy to find
and be well documented so that plugin developers know what variable to give to
their sub-command’s function and what object they can expect to receive.  Don’t
forget to document non-obvious callbacks.Keep in mind that plugin developers also have access to the parent group’sctx.obj, which is very useful for passing things like verbosity levels or
config values around to sub-commands.Here’s some code that sub-commands could re-use:frommultiprocessingimportcpu_countimportclickjobs_opt=click.option('-j','--jobs',metavar='CORES',type=click.IntRange(min=1,max=cpu_count()),default=1,show_default=True,help="Process data across N cores.")Plugin developers can access this with:importclickimportparent_cli_package.cli.options@click.command()@parent_cli_package.cli.options.jobs_optdefsubcommand(jobs):"""I do something domain specific."""InstallationWithpip:$pipinstallclick-pluginsFrom source:$gitclonehttps://github.com/click-contrib/click-plugins.git$cdclick-plugins$pythonsetup.pyinstallDeveloping$gitclonehttps://github.com/click-contrib/click-plugins.git$cdclick-plugins$pipinstall-e.\[dev\]$pytesttests--covclick_plugins--cov-reportterm-missingChangelogSeeCHANGES.txtAuthorsSeeAUTHORS.txtLicenseSeeLICENSE.txt