2
0
Fork 0

Add docs for subcommands

This commit is contained in:
Lance Edgar 2023-11-24 14:22:22 -06:00
parent 8759fb8d37
commit 6b110e567a
4 changed files with 127 additions and 110 deletions

View file

@ -6,12 +6,19 @@ Top-level :term:`commands<command>` are primarily a way to group
:term:`subcommands<subcommand>`. :term:`subcommands<subcommand>`.
.. _running-commands:
Running a Command Running a Command
----------------- -----------------
Top-level commands are installed in such a way that they are available Top-level commands are installed in such a way that they are available
within the ``bin`` folder of the virtual environment. (Or the within the ``bin`` folder of the virtual environment. (Or the
``Scripts`` folder if on Windows.) ``Scripts`` folder if on Windows.) For instance:
.. code-block:: sh
cd /path/to/venv
bin/wutta --help
This folder should be in the ``PATH`` when the virtual environment is This folder should be in the ``PATH`` when the virtual environment is
activated, in which case you can just run the command by name, e.g.: activated, in which case you can just run the command by name, e.g.:
@ -64,6 +71,8 @@ subcommands.
:returncode: 1 :returncode: 1
.. _adding-commands:
Adding a New Command Adding a New Command
-------------------- --------------------
@ -87,8 +96,8 @@ First create your :class:`~wuttjamaican.cmd.base.Command` class, and a
cmd.run(*args) cmd.run(*args)
Then register the :term:`entry point(s)<entry point>` in your Then register the :term:`entry point(s)<entry point>` in your
``setup.cfg``. The command name should not contain spaces but may ``setup.cfg``. The command name should *not* contain spaces but *may*
include hyphens or underscore etc. include hyphen or underscore.
You can register more than one top-level command if needed; these You can register more than one top-level command if needed; these
could refer to the same ``main()`` function (in which case they could refer to the same ``main()`` function (in which case they
@ -109,3 +118,6 @@ available:
cd /path/to/venv cd /path/to/venv
bin/poser --help bin/poser --help
bin/wutta-poser --help bin/wutta-poser --help
You will then likely want to add subcommand(s) for this to be useful;
see :ref:`adding-subcommands`.

View file

@ -10,6 +10,9 @@ A script is just a text file with Python code. To run it you
generally must invoke the Python interpreter somehow and explicitly generally must invoke the Python interpreter somehow and explicitly
tell it the path to your script. tell it the path to your script.
Note that a script is (usually) not installed as part of a package.
They can live anywhere.
Below we'll walk through creating a script. Below we'll walk through creating a script.
@ -72,6 +75,14 @@ that this also gives you access to the :term:`app handler`::
config = make_config('my.conf') config = make_config('my.conf')
hello(config) hello(config)
Output should now be different:
.. code-block:: sh
$ python hello.py
hello George
from wutta
You are likely to need more imports; it is generally wise to do those You are likely to need more imports; it is generally wise to do those
*within the function* as opposed to the top of the module. This is to *within the function* as opposed to the top of the module. This is to
ensure the :func:`~wuttjamaican.conf.make_config()` call happens ensure the :func:`~wuttjamaican.conf.make_config()` call happens
@ -94,14 +105,6 @@ before all packages are imported::
config = make_config('my.conf') config = make_config('my.conf')
hello(config) hello(config)
Output should now be different:
.. code-block:: sh
$ python hello.py
hello George
from wutta
Logging Logging
------- -------

View file

@ -2,4 +2,91 @@
Subcommands Subcommands
=========== ===========
TODO A top-level :term:`command` may have multiple
:term:`subcommands<subcommand>`.
The top-level command is responsible for invoking the subcommand, but
the subcommand is responsible for performing some action(s).
There is no restriction on what sort of action that might be, but for
sake of clarity it is best to make a distinct subcommand for each
"type" of action needed by the app.
Running a Subcommand
--------------------
You cannot run a subcommand directly; you must run a top-level command
and specify the subcommand as part of the command line arguments. See
:ref:`running-commands`.
This restriction holds true even when running a subcommand "natively"
from within Python code. For more info see
:meth:`wuttjamaican.cmd.base.Subcommand.run()`.
Built-in Subcommands
--------------------
WuttJamaican comes with one top-level command named ``wutta`` as well
as a few subcommands under that.
See :mod:`wuttjamaican.cmd` for more on the built-in ``wutta``
subcommands.
.. _adding-subcommands:
Adding a New Subcommand
-----------------------
There are two steps for this:
* define the subcommand
* register it under top-level command(s)
First create a Subcommand class (e.g. by adding to
``poser/commands.py``)::
from wuttjamaican.cmd import Subcommand
class Hello(Subcommand):
"""
Say hello to the user
"""
name = 'hello'
description = __doc__.strip()
def add_args(self):
self.parser.add_argument('--foo', default='bar', help="Foo value")
def run(self, args):
print("hello, foo value is:", args.foo)
You may notice there is nothing in that subcommand definition which
ties it to the ``poser`` top-level command. That is done by way of
another :term:`entry point` in your ``setup.cfg`` file.
As with top-level commands, you can "alias" the same subcommand so
it appears under multiple top-level commands. Note that if the
top-level command name contains a hyphen, that must be replaced
with underscore for sake of the subcommand entry point:
.. code-block:: ini
[options.entry_points]
poser.subcommands =
hello = poser.commands:Hello
wutta_poser.subcommands =
hello = poser.commands:Hello
Next time your ``poser`` package is installed, the subcommand will be
available, so you can e.g.:
.. code-block:: sh
cd /path/to/venv
bin/poser hello --help
bin/wutta-poser hello --help

View file

@ -72,51 +72,11 @@ class Command:
:param subcommands: Optional dictionary to use for :param subcommands: Optional dictionary to use for
:attr:`subcommands`, instead of loading those via entry points. :attr:`subcommands`, instead of loading those via entry points.
The base class serves as the primary ``wutta`` command for This base class also serves as the primary ``wutta`` command for
WuttJamaican. Most apps will subclass this and register their own WuttJamaican. Most apps will subclass this and register their own
top-level command, and create subcommands as needed. top-level command, then create subcommands as needed.
To do that, first create your Command class, and a ``main()`` For more info see :doc:`/narr/cli/commands`.
entry point function for it (e.g. in ``poser/commands.py``)::
import sys
from wuttjamaican.commands import Command
class Poser(Command):
name = 'poser'
description = 'my custom top-level command'
version = '0.1'
def poser_main(*args):
args = list(args) or sys.argv[1:]
cmd = Poser()
cmd.run(*args)
Then register the ``main()`` entry point(s) in your ``setup.cfg``.
The command name should be "one word" (no spaces) but may include
hyphens or underscore etc.
You can register more than one top-level command if needed; these
could refer to the same ``main()`` function (in which case they
are really aliases) or can use different functions:
.. code-block:: ini
[options.entry_points]
console_scripts =
poser = poser.commands:poser_main
wutta-poser = poser.commands:wutta_poser_main
Next time your (``poser``) package is installed, the command will be
available, so you can e.g.:
.. code-block:: sh
cd /path/to/venv
bin/poser --help
bin/wutta-poser --help
And see :class:`Subcommand` for info about adding those.
.. attribute:: name .. attribute:: name
@ -211,7 +171,7 @@ class Command:
You could do this in Python:: You could do this in Python::
from wuttjamaican.commands import Command from wuttjamaican.cmd import Command
cmd = Command() cmd = Command()
assert cmd.name == 'wutta' assert cmd.name == 'wutta'
@ -341,13 +301,14 @@ class CommandArgumentParser(argparse.ArgumentParser):
""" """
Custom argument parser for use with :class:`Command`. Custom argument parser for use with :class:`Command`.
This overrides some of the parsing logic which is specific to the This is based on standard :class:`python:argparse.ArgumentParser`
but overrides some of the parsing logic which is specific to the
primary command object, to separate command options from primary command object, to separate command options from
subcommand options. subcommand options.
This is documented as FYI but you probably should not need to know This is documented as FYI but you probably should not need to know
about or try to use this yourself. It will be used automatically about or try to use this yourself. It will be used automatically
by ``Command`` or a subclass thereof. by :class:`Command` or a subclass thereof.
""" """
def parse_args(self, args=None, namespace=None): def parse_args(self, args=None, namespace=None):
@ -365,63 +326,17 @@ class Subcommand:
also define :meth:`add_args()` to expose options. also define :meth:`add_args()` to expose options.
Subcommands always belong to a top-level command - the association Subcommands always belong to a top-level command - the association
is made by way of entry point registration, and the constructor is made by way of :term:`entry point` registration, and the
for this class. constructor for this class.
:param command: Reference to top-level :class:`Command` object. :param command: Reference to top-level :class:`Command` object.
Note that unlike :class:`Command`, the base ``Subcommand`` does Note that unlike :class:`Command`, the base ``Subcommand`` does
not correspond to any real subcommand for WuttJamaican. (It's not correspond to any real subcommand for WuttJamaican. (It's
*only* a base class.) For a real example see *only* a base class.) For a real example see
:class:`~wuttjamaican.commands.setup.Setup`. :class:`~wuttjamaican.cmd.make_appdir.MakeAppDir`.
In your project you can define new subcommands for any top-level For more info see :doc:`/narr/cli/subcommands`.
command. For instance to add a ``hello`` subcommand to the
``poser`` command example (cf. :class:`Command` docs):
First create a Subcommand class (e.g. by adding to
``poser/commands.py``)::
from wuttjamaican.commands import Subcommand
class Hello(Subcommand):
\"""
Say hello to the user
\"""
name = 'hello'
description = __doc__.strip()
def add_args(self):
self.parser.add_argument('--foo', default='bar', help="Foo value")
def run(self, args):
print("hello, foo value is:", args.foo)
You may notice there is nothing in that subcommand definition
which ties it to the ``poser`` top-level command. That is done by
way of another entry point in your ``setup.cfg`` file.
As with top-level commands, you can "alias" the same subcommand so
it appears under multiple top-level commands. Note that if the
top-level command name contains a hyphen, that must be replaced
with underscore for sake of the subcommand entry point:
.. code-block:: ini
[options.entry_points]
poser.subcommands =
hello = poser.commands:Hello
wutta_poser.subcommands =
hello = poser.commands:Hello
Next time your (``poser``) package is installed, the subcommand
will be available, so you can e.g.:
.. code-block:: sh
cd /path/to/venv
bin/poser hello --help
bin/wutta-poser hello --help
.. attribute:: stdout .. attribute:: stdout
@ -509,9 +424,9 @@ class Subcommand:
For a command line like ``bin/poser hello --foo=baz`` then, For a command line like ``bin/poser hello --foo=baz`` then,
you might do this:: you might do this::
from poser.commands import Poser from poser.commands import PoserCommand
cmd = Poser() cmd = PoserCommand()
assert cmd.name == 'poser' assert cmd.name == 'poser'
cmd.run('hello', '--foo=baz') cmd.run('hello', '--foo=baz')
""" """