|
| 1 | +<!DOCTYPE html> |
| 2 | +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> |
| 3 | +<head> |
| 4 | +<meta charset="utf-8" /> |
| 5 | +<meta name="generator" content="Docutils 0.21.2: https://docutils.sourceforge.io/" /> |
| 6 | +<meta name="viewport" content="width=device-width, initial-scale=1" /> |
| 7 | +<title>click-plugins</title> |
| 8 | + |
| 9 | +</head> |
| 10 | +<body class="with-toc"> |
| 11 | +<main id="click-plugins"> |
| 12 | +<h1 class="title"><span class="docutils literal"><span class="pre">click-plugins</span></span></h1> |
| 13 | + |
| 14 | +<!-- This file is part of 'click-plugins' version 2.0: https://github.com/click-contrib/click-plugins --> |
| 15 | +<p>Load <a class="reference external" href="https://click.palletsprojects.com/">click</a> commands from |
| 16 | +<a class="reference external" href="https://docs.python.org/3/library/importlib.metadata.html#entry-points">entry points</a>. |
| 17 | +Allows a click-based command line interface to load commands from external |
| 18 | +packages.</p> |
| 19 | +<nav class="contents" id="table-of-contents" role="doc-toc"> |
| 20 | +<p class="topic-title"><a class="reference internal" href="#top">Table of Contents</a></p> |
| 21 | +<ul class="simple"> |
| 22 | +<li><p><a class="reference internal" href="#what-is-a-plugin" id="toc-entry-1">What is a plugin?</a></p></li> |
| 23 | +<li><p><a class="reference internal" href="#why-would-i-want-a-plugin" id="toc-entry-2">Why would I want a plugin?</a></p></li> |
| 24 | +<li><p><a class="reference internal" href="#i-am-a-developer-wanting-to-support-plugins-on-my-cli" id="toc-entry-3">I am a developer wanting to support plugins on my CLI</a></p> |
| 25 | +<ul> |
| 26 | +<li><p><a class="reference internal" href="#support" id="toc-entry-4">Support</a></p></li> |
| 27 | +</ul> |
| 28 | +</li> |
| 29 | +<li><p><a class="reference internal" href="#i-am-a-plugin-author" id="toc-entry-5">I am a plugin author</a></p></li> |
| 30 | +<li><p><a class="reference internal" href="#license" id="toc-entry-6">License</a></p></li> |
| 31 | +</ul> |
| 32 | +</nav> |
| 33 | +<section id="what-is-a-plugin"> |
| 34 | +<h2><a class="toc-backref" href="#toc-entry-1" role="doc-backlink">What is a plugin?</a></h2> |
| 35 | +<p>A plugin is similar to installing and importing a Python package, except the |
| 36 | +code conforms to a specific protocol, and is loaded through other means.</p> |
| 37 | +</section> |
| 38 | +<section id="why-would-i-want-a-plugin"> |
| 39 | +<h2><a class="toc-backref" href="#toc-entry-2" role="doc-backlink">Why would I want a plugin?</a></h2> |
| 40 | +<p>Library developers providing a command line interface can load plugins to |
| 41 | +extend its features by allowing other developers to register additional |
| 42 | +commands or groups. This allows for an extensible command line, and a natural |
| 43 | +home for commands that aren't a great fit for the primary CLI, but belong in |
| 44 | +the broader ecosystem. For example, a plugin might provide a more advanced set |
| 45 | +of features, but require additional dependencies.</p> |
| 46 | +</section> |
| 47 | +<section id="i-am-a-developer-wanting-to-support-plugins-on-my-cli"> |
| 48 | +<h2><a class="toc-backref" href="#toc-entry-3" role="doc-backlink">I am a developer wanting to support plugins on my CLI</a></h2> |
| 49 | +<p>A <a class="reference external" href="https://pypi.org/project/click-plugins/">click-plugins</a> package exists on |
| 50 | +the Python Package Index. This is an older version that is no longer supported |
| 51 | +and will not be updated. Instead, developers should vendor |
| 52 | +<span class="docutils literal">click_plugins.py</span>, and consider vendoring <span class="docutils literal">click_plugins_tests.py</span>, and |
| 53 | +<span class="docutils literal">click_plugins.rst</span>. Alternatively, developers are free to use this project |
| 54 | +as a reference for their own implementation, or make modifications in |
| 55 | +accordance with the license.</p> |
| 56 | +<p>Some considerations for vendoring are speed, and packaging. Entrypoints are |
| 57 | +known to be slow to load, and some alternative approaches exist at the cost of |
| 58 | +additional dependencies, or assumptions about what happens when a plugin fails |
| 59 | +to load. Vendoring <span class="docutils literal"><span class="pre">click-plugins</span></span> might include changing the entry point |
| 60 | +loading mechanism to one that is more appropriate for your use. Python |
| 61 | +packaging can be quite complicated in some cases, and vendoring may require |
| 62 | +adjustments for your specific packaging setup.</p> |
| 63 | +<p>In order to support loading plugins, developers must document where their |
| 64 | +library is looking for entry points. Exactly how to do this varies based on |
| 65 | +packaging tooling, but it is supported by <a class="reference external" href="https://setuptools.pypa.io/en/latest/userguide/entry_point.html">setuptools</a>. |
| 66 | +A project may offer several entry points allowing plugins to choose where they |
| 67 | +are registered in the CLI. Including the package name in the entrypoint is |
| 68 | +good, so an example might look like <span class="docutils literal">package.plugins</span> or |
| 69 | +<span class="docutils literal">package.subcommand.plugins</span>. If <span class="docutils literal"><span class="pre">click-plugins</span></span> offered plugins, it might |
| 70 | +want to register them at <span class="docutils literal">click_plugins.plugins</span>.</p> |
| 71 | +<p>This entry point should be associated with a <span class="docutils literal">click.Group()</span> where the |
| 72 | +plugins will live:</p> |
| 73 | +<pre class="code python literal-block"><code><span class="keyword namespace">from</span><span class="whitespace"> </span><span class="name namespace">click</span><span class="whitespace"> |
| 74 | +</span><span class="keyword namespace">from</span><span class="whitespace"> </span><span class="name namespace">click_plugins</span><span class="whitespace"> </span><span class="keyword namespace">import</span> <span class="name">with_plugins</span><span class="whitespace"> |
| 75 | + |
| 76 | +</span><span class="name decorator">@with_plugins</span><span class="punctuation">(</span><span class="literal string single">'example.entry.point'</span><span class="punctuation">)</span><span class="whitespace"> |
| 77 | +</span><span class="name decorator">@click</span><span class="operator">.</span><span class="name">group</span><span class="punctuation">(</span><span class="literal string single">'External plugins'</span><span class="punctuation">)</span><span class="whitespace"> |
| 78 | +</span><span class="keyword">def</span><span class="whitespace"> </span><span class="name function">group</span><span class="punctuation">():</span><span class="whitespace"> |
| 79 | +</span> <span class="operator">...</span></code></pre> |
| 80 | +<p><span class="docutils literal">click_plugins.with_plugins()</span> has a docstring describing alternate |
| 81 | +invocations.</p> |
| 82 | +<p>Some developers use <span class="docutils literal"><span class="pre">click-plugins</span></span> as an easy way to assemble the CLI for |
| 83 | +their project in addition to supporting plugins. This approach does work, but |
| 84 | +can cause CLI startup to be slow. Developers taking this approach might |
| 85 | +consider entry point for the primary CLI, and one for plugins.</p> |
| 86 | +<p>Packages offering plugins of the same name will experience collisions.</p> |
| 87 | +<section id="support"> |
| 88 | +<h3><a class="toc-backref" href="#toc-entry-4" role="doc-backlink">Support</a></h3> |
| 89 | +<p>Offering a home for plugins comes with a certain amount of support. The primary |
| 90 | +CLI author is likely to sometimes receive bug reports or feature requests for |
| 91 | +plugins that are not part of the core project. <span class="docutils literal"><span class="pre">click-plugins</span></span> attempts to |
| 92 | +gracefully handle plugins that fail to load, and nudges the user towards the |
| 93 | +plugin author, but the plugin origin may at times not be clear. Consider that |
| 94 | +your users are primarily interacting with your CLI, but may be experiencing |
| 95 | +problems with a plugin, or even a bad interaction between plugins. It may be |
| 96 | +worth including a brief description about this in your documentation to help |
| 97 | +users report issues to the correct location.</p> |
| 98 | +</section> |
| 99 | +</section> |
| 100 | +<section id="i-am-a-plugin-author"> |
| 101 | +<h2><a class="toc-backref" href="#toc-entry-5" role="doc-backlink">I am a plugin author</a></h2> |
| 102 | +<p>Register your <span class="docutils literal">click.Command()</span> or <span class="docutils literal">click.Group()</span> as an |
| 103 | +<a class="reference external" href="https://setuptools.pypa.io/en/latest/userguide/entry_point.html">entry point</a>. |
| 104 | +The exact mechanism depends on your packaging choices, but for a |
| 105 | +<span class="docutils literal">pyproject.toml</span> with <span class="docutils literal">setuptools</span> as a backend, it looks like:</p> |
| 106 | +<pre class="code toml literal-block"><code><span class="keyword">[tool.setuptools.dynamic]</span><span class="whitespace"> |
| 107 | +</span><span class="name">entry-points</span><span class="whitespace"> </span><span class="operator">=</span><span class="whitespace"> |
| 108 | + </span><span class="error">name = library.submodule:object</span></code></pre> |
| 109 | +<p>If <span class="docutils literal">click_plugins</span> had a <span class="docutils literal">plugins.py</span> submodule, it might contain a |
| 110 | +plugin structured as the <span class="docutils literal">click.Command()</span> below:</p> |
| 111 | +<pre class="code python literal-block"><code><span class="keyword namespace">import</span><span class="whitespace"> </span><span class="name namespace">click</span><span class="whitespace"> |
| 112 | + |
| 113 | +</span><span class="name decorator">@click</span><span class="operator">.</span><span class="name">command</span><span class="punctuation">(</span><span class="literal string single">'uppercase'</span><span class="punctuation">)</span><span class="whitespace"> |
| 114 | +</span><span class="keyword">def</span><span class="whitespace"> </span><span class="name function">uppercase</span><span class="punctuation">():</span><span class="whitespace"> |
| 115 | + </span><span class="literal string doc">"""Echo stdin in uppercase."""</span><span class="whitespace"> |
| 116 | +</span> <span class="keyword">with</span> <span class="name">click</span><span class="operator">.</span><span class="name">get_text_stream</span><span class="punctuation">(</span><span class="literal string single">'stdin'</span><span class="punctuation">)</span> <span class="keyword">as</span> <span class="name">f</span><span class="punctuation">:</span><span class="whitespace"> |
| 117 | +</span> <span class="keyword">for</span> <span class="name">line</span> <span class="operator word">in</span> <span class="name">f</span><span class="punctuation">:</span><span class="whitespace"> |
| 118 | +</span> <span class="name">click</span><span class="operator">.</span><span class="name">echo</span><span class="punctuation">(</span><span class="name">f</span><span class="operator">.</span><span class="name">upper</span><span class="punctuation">())</span></code></pre> |
| 119 | +<p>This would be attached to an entry point like:</p> |
| 120 | +<pre class="code toml literal-block"><code><span class="keyword">[tool.setuptools.dynamic]</span><span class="whitespace"> |
| 121 | +</span><span class="name">entry-points</span><span class="whitespace"> </span><span class="operator">=</span><span class="whitespace"> |
| 122 | + </span><span class="error">bold = click</span><span class="literal number integer">_</span><span class="name">plugins</span><span class="punctuation">.</span><span class="name">plugins</span><span class="error">:</span><span class="name">bold</span></code></pre> |
| 123 | +</section> |
| 124 | +<section id="license"> |
| 125 | +<h2><a class="toc-backref" href="#toc-entry-6" role="doc-backlink">License</a></h2> |
| 126 | +<p>New BSD License</p> |
| 127 | +<p>Copyright (c) 2015-2025, Kevin D. Wurster, Sean C. Gillies |
| 128 | +All rights reserved.</p> |
| 129 | +<p>Redistribution and use in source and binary forms, with or without |
| 130 | +modification, are permitted provided that the following conditions are met:</p> |
| 131 | +<ul class="simple"> |
| 132 | +<li><p>Redistributions of source code must retain the above copyright notice, this |
| 133 | +list of conditions and the following disclaimer.</p></li> |
| 134 | +<li><p>Redistributions in binary form must reproduce the above copyright notice, |
| 135 | +this list of conditions and the following disclaimer in the documentation |
| 136 | +and/or other materials provided with the distribution.</p></li> |
| 137 | +<li><p>Neither click-plugins nor the names of its contributors may not be used to |
| 138 | +endorse or promote products derived from this software without specific prior |
| 139 | +written permission.</p></li> |
| 140 | +</ul> |
| 141 | +<p>THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
| 142 | +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| 143 | +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| 144 | +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE |
| 145 | +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
| 146 | +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR |
| 147 | +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
| 148 | +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, |
| 149 | +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| 150 | +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.</p> |
| 151 | +</section> |
| 152 | +</main> |
| 153 | +<footer> |
| 154 | +<p>Generated on: 2025-06-09. |
| 155 | +</p> |
| 156 | +</footer> |
| 157 | +</body> |
| 158 | +</html> |
0 commit comments