Add docs for subcommands
This commit is contained in:
parent
8759fb8d37
commit
6b110e567a
|
@ -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`.
|
||||||
|
|
|
@ -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
|
||||||
-------
|
-------
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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')
|
||||||
"""
|
"""
|
||||||
|
|
Loading…
Reference in a new issue