Change entry point group naming for subcommands
and use fallback to find subcommands registered via legacy naming
This commit is contained in:
parent
d8252f029d
commit
ea9a9ade57
|
@ -50,5 +50,5 @@ tests = pytest-cov; tox
|
|||
console_scripts =
|
||||
wutta = wuttjamaican.commands.base:main
|
||||
|
||||
wutta.commands =
|
||||
wutta.subcommands =
|
||||
setup = wuttjamaican.commands.setup:Setup
|
||||
|
|
|
@ -27,6 +27,7 @@ WuttJamaican - command framework
|
|||
import argparse
|
||||
import logging
|
||||
import sys
|
||||
import warnings
|
||||
|
||||
from wuttjamaican import __version__
|
||||
from wuttjamaican.util import load_entry_points
|
||||
|
@ -75,7 +76,7 @@ class Command:
|
|||
top-level command, and create subcommands as needed.
|
||||
|
||||
To do that, first create your Command class, and a ``main()``
|
||||
entry point (e.g. in ``poser/commands.py``)::
|
||||
entry point function for it (e.g. in ``poser/commands.py``)::
|
||||
|
||||
import sys
|
||||
from wuttjamaican.commands import Command
|
||||
|
@ -85,18 +86,25 @@ class Command:
|
|||
description = 'my custom top-level command'
|
||||
version = '0.1'
|
||||
|
||||
def main(*args):
|
||||
def poser_main(*args):
|
||||
args = list(args) or sys.argv[1:]
|
||||
cmd = Poser()
|
||||
cmd.run(*args)
|
||||
|
||||
Then register the ``main()`` entry point (in your ``setup.cfg``):
|
||||
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:main
|
||||
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.:
|
||||
|
@ -105,12 +113,10 @@ class Command:
|
|||
|
||||
cd /path/to/venv
|
||||
bin/poser --help
|
||||
bin/wutta-poser --help
|
||||
|
||||
And see :class:`Subcommand` for info about adding those.
|
||||
|
||||
Note that you can add as many top-level commands as needed. Most
|
||||
apps only need one of course, but there is no actual limit.
|
||||
|
||||
.. attribute:: name
|
||||
|
||||
Name of the primary command, e.g. ``wutta``
|
||||
|
@ -154,7 +160,19 @@ class Command:
|
|||
self.name = name or self.name
|
||||
self.stdout = stdout or sys.stdout
|
||||
self.stderr = stderr or sys.stderr
|
||||
self.subcommands = subcommands or load_entry_points(f'{self.name}.commands')
|
||||
|
||||
# nb. default entry point is like 'wutta-poser.subcommands'
|
||||
self.subcommands = subcommands or load_entry_points(f'{self.name}.subcommands')
|
||||
if not self.subcommands:
|
||||
|
||||
# nb. legacy entry point is like 'wutta_poser.commands'
|
||||
safe_name = self.name.replace('-', '_')
|
||||
self.subcommands = load_entry_points(f'{safe_name}.commands')
|
||||
if self.subcommands:
|
||||
msg = (f"entry point group '{safe_name}.commands' uses deprecated name; "
|
||||
f"please define '{self.name}.subcommands' instead")
|
||||
warnings.warn(msg, DeprecationWarning, stacklevel=2)
|
||||
log.warning(msg)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
@ -316,12 +334,17 @@ class Subcommand:
|
|||
|
||||
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:
|
||||
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:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[options.entry_points]
|
||||
poser.commands =
|
||||
poser.subcommands =
|
||||
hello = poser.commands:Hello
|
||||
wutta-poser.subcommands =
|
||||
hello = poser.commands:Hello
|
||||
|
||||
Next time your (``poser``) package is installed, the subcommand
|
||||
|
@ -331,27 +354,7 @@ class Subcommand:
|
|||
|
||||
cd /path/to/venv
|
||||
bin/poser hello --help
|
||||
|
||||
Since the connection between command and subcommand is only "real"
|
||||
if there is an entry point, this means a) you can add the
|
||||
subcommand under *any* top-level command, but also b) you could
|
||||
technically add it to multiple top-level commands. So for
|
||||
instance in addition to the above entry point you might also do:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[options.entry_points]
|
||||
wutta.commands =
|
||||
hello = poser.commands:Hello
|
||||
|
||||
After re-installing your package then these commands would do the
|
||||
same thing:
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
cd /path/to/venv
|
||||
bin/poser hello
|
||||
bin/wutta hello
|
||||
bin/wutta-poser hello --help
|
||||
"""
|
||||
name = 'UNDEFINED'
|
||||
description = "TODO: not defined"
|
||||
|
|
|
@ -6,6 +6,7 @@ from unittest import TestCase
|
|||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from wuttjamaican.commands import base
|
||||
from wuttjamaican.commands.setup import Setup
|
||||
|
||||
|
||||
class TestCommand(TestCase):
|
||||
|
@ -17,6 +18,27 @@ class TestCommand(TestCase):
|
|||
self.assertIn('setup', cmd.subcommands)
|
||||
self.assertEqual(str(cmd), 'wutta')
|
||||
|
||||
def test_subcommand_entry_points(self):
|
||||
with patch('wuttjamaican.commands.base.load_entry_points') as load_entry_points:
|
||||
|
||||
# empty entry points
|
||||
load_entry_points.side_effect = lambda group: {}
|
||||
cmd = base.Command()
|
||||
self.assertEqual(cmd.subcommands, {})
|
||||
|
||||
# typical entry points
|
||||
load_entry_points.side_effect = lambda group: {'setup': Setup}
|
||||
cmd = base.Command()
|
||||
self.assertEqual(cmd.subcommands, {'setup': Setup})
|
||||
self.assertEqual(cmd.subcommands['setup'].name, 'setup')
|
||||
|
||||
# legacy entry points
|
||||
# nb. mock returns entry points only when legacy name is used
|
||||
load_entry_points.side_effect = lambda group: {} if 'subcommands' in group else {'setup': Setup}
|
||||
cmd = base.Command()
|
||||
self.assertEqual(cmd.subcommands, {'setup': Setup})
|
||||
self.assertEqual(cmd.subcommands['setup'].name, 'setup')
|
||||
|
||||
def test_sorted_subcommands(self):
|
||||
cmd = base.Command(subcommands={'foo': 'FooSubcommand',
|
||||
'bar': 'BarSubcommand'})
|
||||
|
|
Loading…
Reference in a new issue