feat: add wutta
top-level command with make-uuid
subcommand
i think it only makes sense to have an "opinion" for command line interface in this project, and we probably need more `wutta` subcommands too but we'll see. main motivation for this currently is to allow poser apps to define their own CLI, in particular e.g. `poser install`
This commit is contained in:
parent
cb147c203d
commit
2deba45588
6
docs/api/wuttjamaican/cli.base.rst
Normal file
6
docs/api/wuttjamaican/cli.base.rst
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
|
||||||
|
``wuttjamaican.cli.base``
|
||||||
|
=========================
|
||||||
|
|
||||||
|
.. automodule:: wuttjamaican.cli.base
|
||||||
|
:members:
|
6
docs/api/wuttjamaican/cli.make_uuid.rst
Normal file
6
docs/api/wuttjamaican/cli.make_uuid.rst
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
|
||||||
|
``wuttjamaican.cli.make_uuid``
|
||||||
|
==============================
|
||||||
|
|
||||||
|
.. automodule:: wuttjamaican.cli.make_uuid
|
||||||
|
:members:
|
6
docs/api/wuttjamaican/cli.rst
Normal file
6
docs/api/wuttjamaican/cli.rst
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
|
||||||
|
``wuttjamaican.cli``
|
||||||
|
====================
|
||||||
|
|
||||||
|
.. automodule:: wuttjamaican.cli
|
||||||
|
:members:
|
|
@ -9,6 +9,9 @@
|
||||||
|
|
||||||
app
|
app
|
||||||
auth
|
auth
|
||||||
|
cli
|
||||||
|
cli.base
|
||||||
|
cli.make_uuid
|
||||||
conf
|
conf
|
||||||
db
|
db
|
||||||
db.conf
|
db.conf
|
||||||
|
|
|
@ -163,8 +163,8 @@ Glossary
|
||||||
subcommand
|
subcommand
|
||||||
A top-level :term:`command` may expose one or more subcommands,
|
A top-level :term:`command` may expose one or more subcommands,
|
||||||
for the overall command line interface. Subcommands are usually
|
for the overall command line interface. Subcommands are usually
|
||||||
the real workhorse; each can perform a different function. See
|
the real workhorse; each can perform a different function with a
|
||||||
also :doc:`narr/cli/index`.
|
custom arg set. See also :doc:`narr/cli/index`.
|
||||||
|
|
||||||
virtual environment
|
virtual environment
|
||||||
This term comes from the broader Python world and refers to an
|
This term comes from the broader Python world and refers to an
|
||||||
|
|
|
@ -22,8 +22,10 @@ Features
|
||||||
|
|
||||||
* flexible configuration, using config files and/or DB settings table
|
* flexible configuration, using config files and/or DB settings table
|
||||||
* flexible architecture, abstracting various portions of the overall app
|
* flexible architecture, abstracting various portions of the overall app
|
||||||
|
* flexible command line interface, using `Typer`_
|
||||||
* flexible database support, using `SQLAlchemy`_
|
* flexible database support, using `SQLAlchemy`_
|
||||||
|
|
||||||
|
.. _Typer: https://typer.tiangolo.com
|
||||||
.. _SQLAlchemy: https://www.sqlalchemy.org
|
.. _SQLAlchemy: https://www.sqlalchemy.org
|
||||||
|
|
||||||
See also these projects which build on WuttJamaican:
|
See also these projects which build on WuttJamaican:
|
||||||
|
|
41
docs/narr/cli/builtin.rst
Normal file
41
docs/narr/cli/builtin.rst
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
|
||||||
|
Built-in Commands
|
||||||
|
=================
|
||||||
|
|
||||||
|
WuttJamaican comes with one top-level :term:`command`, and some
|
||||||
|
:term:`subcommands<subcommand>`.
|
||||||
|
|
||||||
|
It uses `Typer`_ for the underlying CLI framework.
|
||||||
|
|
||||||
|
.. _Typer: https://typer.tiangolo.com/
|
||||||
|
|
||||||
|
|
||||||
|
``wutta``
|
||||||
|
---------
|
||||||
|
|
||||||
|
This is the top-level command. Its purpose is to expose subcommands
|
||||||
|
pertaining to WuttJamaican.
|
||||||
|
|
||||||
|
It is installed to the virtual environment in the ``bin`` folder (or
|
||||||
|
``Scripts`` on Windows):
|
||||||
|
|
||||||
|
.. code-block:: sh
|
||||||
|
|
||||||
|
cd /path/to/venv
|
||||||
|
bin/wutta --help
|
||||||
|
|
||||||
|
Defined in: :mod:`wuttjamaican.cli`
|
||||||
|
|
||||||
|
.. program-output:: wutta --help
|
||||||
|
|
||||||
|
|
||||||
|
.. _wutta-make-uuid:
|
||||||
|
|
||||||
|
``wutta make-uuid``
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
Print a new universally-unique identifier to standard output.
|
||||||
|
|
||||||
|
Defined in: :mod:`wuttjamaican.cli.make_uuid`
|
||||||
|
|
||||||
|
.. program-output:: wutta make-uuid --help
|
|
@ -1,23 +0,0 @@
|
||||||
|
|
||||||
Commands
|
|
||||||
========
|
|
||||||
|
|
||||||
WuttJamaican in fact does not directly provide a way to define a
|
|
||||||
command line interface for your app.
|
|
||||||
|
|
||||||
The reason is that several good frameworks exist already. You are
|
|
||||||
encouraged to use one of the following to define
|
|
||||||
:term:`commands<command>` and :term:`subcommands<subcommand>` as
|
|
||||||
needed:
|
|
||||||
|
|
||||||
* `Typer <https://typer.tiangolo.com/>`_
|
|
||||||
* `Click <https://click.palletsprojects.com/en/latest/>`_
|
|
||||||
* :mod:`python:argparse`
|
|
||||||
|
|
||||||
For even more options see:
|
|
||||||
|
|
||||||
* `awesome-cli-framework <https://github.com/shadawck/awesome-cli-frameworks/blob/master/README.md#python>`_
|
|
||||||
* `Hitchhiker’s Guide to Python <https://docs.python-guide.org/scenarios/cli/>`_
|
|
||||||
* `Python Wiki <https://wiki.python.org/moin/CommandlineTools>`_
|
|
||||||
|
|
||||||
Or if that is overkill you can always just use :doc:`scripts`.
|
|
105
docs/narr/cli/custom.rst
Normal file
105
docs/narr/cli/custom.rst
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
|
||||||
|
Custom Commands
|
||||||
|
===============
|
||||||
|
|
||||||
|
WuttJamaican comes with :doc:`/narr/cli/builtin`.
|
||||||
|
|
||||||
|
Using the same framework, each :term:`package` can define additional
|
||||||
|
top-level :term:`command(s)<command>` and
|
||||||
|
:term:`subcommands<subcommand>` as needed.
|
||||||
|
|
||||||
|
|
||||||
|
Top-Level Command
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
You must "define" *and* "register" your top-level command. Assuming a
|
||||||
|
basic Poser example:
|
||||||
|
|
||||||
|
.. code-block:: none
|
||||||
|
|
||||||
|
poser-project
|
||||||
|
├── poser
|
||||||
|
│ ├── __init__.py
|
||||||
|
│ └── cli.py
|
||||||
|
└── pyproject.toml
|
||||||
|
|
||||||
|
Add the command definition to the ``poser.cli`` module::
|
||||||
|
|
||||||
|
from wuttjamaican.cli import make_typer
|
||||||
|
|
||||||
|
poser_typer = make_typer(
|
||||||
|
name='poser',
|
||||||
|
help="Poser - the killer app"
|
||||||
|
)
|
||||||
|
|
||||||
|
Then register the command as script in ``pyproject.toml``:
|
||||||
|
|
||||||
|
.. code-block:: toml
|
||||||
|
|
||||||
|
[project.scripts]
|
||||||
|
poser = "poser.cli:poser_typer"
|
||||||
|
|
||||||
|
Then reinstall your project:
|
||||||
|
|
||||||
|
.. code-block:: sh
|
||||||
|
|
||||||
|
pip install -e ~/src/poser
|
||||||
|
|
||||||
|
And now you can run your command:
|
||||||
|
|
||||||
|
.. code-block:: sh
|
||||||
|
|
||||||
|
poser --help
|
||||||
|
|
||||||
|
But it won't really do anything until you add subcommands.
|
||||||
|
|
||||||
|
|
||||||
|
Subcommands
|
||||||
|
-----------
|
||||||
|
|
||||||
|
You must "define" the subcommand of course, but do not need to
|
||||||
|
"register" it. (That happens via function decorator; see below.)
|
||||||
|
|
||||||
|
However you *do* need to ensure all modules containing subcommands are
|
||||||
|
"eagerly imported" so the runtime discovery process finds everything.
|
||||||
|
|
||||||
|
Here we'll define the ``poser hello`` subcommand, by adding it to our
|
||||||
|
``poser.cli`` module (from example above)::
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import typer
|
||||||
|
from wuttjamaican.cli import make_typer
|
||||||
|
|
||||||
|
# top-level command
|
||||||
|
poser_typer = make_typer(
|
||||||
|
name='poser',
|
||||||
|
help="Poser - the killer app"
|
||||||
|
)
|
||||||
|
|
||||||
|
# nb. function decorator will auto-register the subcommand
|
||||||
|
@poser_typer.command()
|
||||||
|
def hello(
|
||||||
|
ctx: typer.Context,
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Hello world example
|
||||||
|
"""
|
||||||
|
config = ctx.parent.wutta_config
|
||||||
|
app = config.get_app()
|
||||||
|
|
||||||
|
name = config.get('hello.name', default="WhoAreYou")
|
||||||
|
sys.stdout.write(f'hello {name}\n')
|
||||||
|
|
||||||
|
title = app.get_title()
|
||||||
|
sys.stdout.write(f'from {title}\n')
|
||||||
|
|
||||||
|
# TODO: you may need to import other modules here, if they contain
|
||||||
|
# subcommands and would not be automatically imported otherwise.
|
||||||
|
# nb. *this* current module *is* automatically imported, only
|
||||||
|
# because of the top-level command registration in pyproject.toml
|
||||||
|
|
||||||
|
No need to re-install, you can now use the subcommand:
|
||||||
|
|
||||||
|
.. code-block:: sh
|
||||||
|
|
||||||
|
poser hello --help
|
|
@ -2,9 +2,24 @@
|
||||||
Command Line Interface
|
Command Line Interface
|
||||||
======================
|
======================
|
||||||
|
|
||||||
|
Most apps will need some sort of command line usage, via cron or
|
||||||
|
otherwise. There are two main aspects to it:
|
||||||
|
|
||||||
|
There is a proper CLI framework based on `Typer`_, with top-level
|
||||||
|
:term:`commands<command>` and :term:`subcommands<subcommand>`. The
|
||||||
|
``wutta`` command is built-in and includes some subcommands, but each
|
||||||
|
app can define more of either as needed. Such (sub)commands are
|
||||||
|
installed as part of a :term:`package`.
|
||||||
|
|
||||||
|
.. _Typer: https://typer.tiangolo.com
|
||||||
|
|
||||||
|
But sometimes you just need an :term:`ad hoc script` which is a single
|
||||||
|
file and can be placed anywhere, usually *not* installed as part of a
|
||||||
|
package.
|
||||||
|
|
||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 2
|
:maxdepth: 2
|
||||||
|
|
||||||
overview
|
builtin
|
||||||
commands
|
custom
|
||||||
scripts
|
scripts
|
||||||
|
|
|
@ -1,15 +0,0 @@
|
||||||
|
|
||||||
Overview
|
|
||||||
========
|
|
||||||
|
|
||||||
Many apps will need some sort of command line usage, via cron or
|
|
||||||
otherwise. There are two main aspects to it:
|
|
||||||
|
|
||||||
First there is the :term:`ad hoc script` which is a single file and
|
|
||||||
can be placed anywhere, but is not installed as part of a
|
|
||||||
:term:`package`. See :doc:`scripts`.
|
|
||||||
|
|
||||||
But a "true" command line interface may define
|
|
||||||
:term:`commands<command>` and :term:`subcommands<subcommand>`, which
|
|
||||||
are then installed as part of a package. See :doc:`commands` for more
|
|
||||||
about that.
|
|
|
@ -33,7 +33,7 @@ Run that like so:
|
||||||
|
|
||||||
|
|
||||||
Better Standards
|
Better Standards
|
||||||
----------------
|
~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
Keeping it simple, but improving that script per recommended patterns::
|
Keeping it simple, but improving that script per recommended patterns::
|
||||||
|
|
||||||
|
@ -69,7 +69,7 @@ that this also gives you access to the :term:`app handler`::
|
||||||
def hello(config):
|
def hello(config):
|
||||||
app = config.get_app()
|
app = config.get_app()
|
||||||
print('hello', config.get('hello.name'))
|
print('hello', config.get('hello.name'))
|
||||||
print('from', app.appname)
|
print('from', app.get_title())
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
config = make_config('my.conf')
|
config = make_config('my.conf')
|
||||||
|
@ -81,7 +81,7 @@ Output should now be different:
|
||||||
|
|
||||||
$ python hello.py
|
$ python hello.py
|
||||||
hello George
|
hello George
|
||||||
from wutta
|
from WuttJamaican
|
||||||
|
|
||||||
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
|
||||||
|
@ -97,7 +97,7 @@ before all packages are imported::
|
||||||
|
|
||||||
app = config.get_app()
|
app = config.get_app()
|
||||||
print('hello', config.get('hello.name'))
|
print('hello', config.get('hello.name'))
|
||||||
print('from', app.appname)
|
print('from', app.get_title())
|
||||||
|
|
||||||
something(config)
|
something(config)
|
||||||
|
|
||||||
|
@ -143,7 +143,7 @@ Here is the script with logging incorporated::
|
||||||
log.debug("saying hello")
|
log.debug("saying hello")
|
||||||
app = config.get_app()
|
app = config.get_app()
|
||||||
print('hello', config.get('hello.name'))
|
print('hello', config.get('hello.name'))
|
||||||
print('from', app.appname)
|
print('from', app.get_title())
|
||||||
|
|
||||||
log.debug("about to do something")
|
log.debug("about to do something")
|
||||||
if something(config):
|
if something(config):
|
||||||
|
|
|
@ -30,6 +30,7 @@ dependencies = [
|
||||||
"importlib_resources ; python_version < '3.9'",
|
"importlib_resources ; python_version < '3.9'",
|
||||||
"progress",
|
"progress",
|
||||||
"python-configuration",
|
"python-configuration",
|
||||||
|
"typer",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -40,6 +41,10 @@ docs = ["Sphinx", "sphinxcontrib-programoutput", "enum-tools[sphinx]", "furo"]
|
||||||
tests = ["pytest-cov", "tox"]
|
tests = ["pytest-cov", "tox"]
|
||||||
|
|
||||||
|
|
||||||
|
[project.scripts]
|
||||||
|
wutta = "wuttjamaican.cli:wutta_typer"
|
||||||
|
|
||||||
|
|
||||||
[project.urls]
|
[project.urls]
|
||||||
Homepage = "https://wuttaproject.org/"
|
Homepage = "https://wuttaproject.org/"
|
||||||
Repository = "https://forgejo.wuttaproject.org/wutta/wuttjamaican"
|
Repository = "https://forgejo.wuttaproject.org/wutta/wuttjamaican"
|
||||||
|
|
37
src/wuttjamaican/cli/__init__.py
Normal file
37
src/wuttjamaican/cli/__init__.py
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
# -*- coding: utf-8; -*-
|
||||||
|
################################################################################
|
||||||
|
#
|
||||||
|
# WuttJamaican -- Base package for Wutta Framework
|
||||||
|
# Copyright © 2023-2024 Lance Edgar
|
||||||
|
#
|
||||||
|
# This file is part of Wutta Framework.
|
||||||
|
#
|
||||||
|
# Wutta Framework is free software: you can redistribute it and/or modify it
|
||||||
|
# under the terms of the GNU General Public License as published by the Free
|
||||||
|
# Software Foundation, either version 3 of the License, or (at your option) any
|
||||||
|
# later version.
|
||||||
|
#
|
||||||
|
# Wutta Framework is distributed in the hope that it will be useful, but
|
||||||
|
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
# more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License along with
|
||||||
|
# Wutta Framework. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
################################################################################
|
||||||
|
"""
|
||||||
|
WuttJamaican - command line interface
|
||||||
|
|
||||||
|
See also :doc:`/narr/cli/index`.
|
||||||
|
|
||||||
|
This (``wuttjamaican.cli``) namespace exposes the following:
|
||||||
|
|
||||||
|
* :func:`~wuttjamaican.cli.base.make_typer`
|
||||||
|
* :data:`~wuttjamaican.cli.base.wutta_typer` (top-level command)
|
||||||
|
"""
|
||||||
|
|
||||||
|
from .base import wutta_typer, make_typer
|
||||||
|
|
||||||
|
# TODO: is this the best we can do, to register available commands?
|
||||||
|
from . import make_uuid
|
108
src/wuttjamaican/cli/base.py
Normal file
108
src/wuttjamaican/cli/base.py
Normal file
|
@ -0,0 +1,108 @@
|
||||||
|
# -*- coding: utf-8; -*-
|
||||||
|
################################################################################
|
||||||
|
#
|
||||||
|
# WuttJamaican -- Base package for Wutta Framework
|
||||||
|
# Copyright © 2023-2024 Lance Edgar
|
||||||
|
#
|
||||||
|
# This file is part of Wutta Framework.
|
||||||
|
#
|
||||||
|
# Wutta Framework is free software: you can redistribute it and/or modify it
|
||||||
|
# under the terms of the GNU General Public License as published by the Free
|
||||||
|
# Software Foundation, either version 3 of the License, or (at your option) any
|
||||||
|
# later version.
|
||||||
|
#
|
||||||
|
# Wutta Framework is distributed in the hope that it will be useful, but
|
||||||
|
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
# more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License along with
|
||||||
|
# Wutta Framework. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
################################################################################
|
||||||
|
"""
|
||||||
|
WuttJamaican - core command logic
|
||||||
|
|
||||||
|
See also :doc:`/narr/cli/index`.
|
||||||
|
|
||||||
|
.. data:: wutta_typer
|
||||||
|
|
||||||
|
This is the top-level ``wutta`` :term:`command`, using the Typer
|
||||||
|
framework.
|
||||||
|
|
||||||
|
See also :func:`make_typer()`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import List, Optional
|
||||||
|
|
||||||
|
import typer
|
||||||
|
from typing_extensions import Annotated
|
||||||
|
|
||||||
|
from wuttjamaican.conf import make_config
|
||||||
|
|
||||||
|
|
||||||
|
def make_cli_config(ctx: typer.Context):
|
||||||
|
"""
|
||||||
|
Make a :term:`config object` according to the command-line context
|
||||||
|
(params).
|
||||||
|
|
||||||
|
This function is normally called by :func:`typer_callback()`.
|
||||||
|
|
||||||
|
This function calls :func:`~wuttjamaican.conf.make_config()` using
|
||||||
|
config files specified via command line (if any).
|
||||||
|
|
||||||
|
:param ctx: ``typer.Context`` instance
|
||||||
|
|
||||||
|
:returns: :class:`~wuttjamaican.conf.WuttaConfig` instance
|
||||||
|
"""
|
||||||
|
logging.basicConfig()
|
||||||
|
return make_config(files=ctx.params.get('config_paths') or None)
|
||||||
|
|
||||||
|
|
||||||
|
def typer_callback(
|
||||||
|
ctx: typer.Context,
|
||||||
|
|
||||||
|
config_paths: Annotated[
|
||||||
|
Optional[List[Path]],
|
||||||
|
typer.Option('--config', '-c',
|
||||||
|
exists=True,
|
||||||
|
help="Config path (may be specified more than once)")] = None,
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Generic callback for use with top-level commands. This adds some
|
||||||
|
top-level args:
|
||||||
|
|
||||||
|
* ``--config`` (and ``-c``)
|
||||||
|
|
||||||
|
This callback is responsible for creating the :term:`config
|
||||||
|
object` for the command. (It calls :func:`make_cli_config()` for
|
||||||
|
that.) It then attaches it to the context as
|
||||||
|
``ctx.wutta_config``.
|
||||||
|
"""
|
||||||
|
ctx.wutta_config = make_cli_config(ctx)
|
||||||
|
|
||||||
|
|
||||||
|
def make_typer(**kwargs):
|
||||||
|
"""
|
||||||
|
Create a Typer command instance, per Wutta conventions.
|
||||||
|
|
||||||
|
This function is used to create the top-level ``wutta`` command,
|
||||||
|
:data:`wutta_typer`. You can use it to create additional
|
||||||
|
top-level commands for your app if needed. (And don't forget to
|
||||||
|
register; see :doc:`/narr/cli/custom`.)
|
||||||
|
|
||||||
|
:param callback: Override for the ``Typer.callback`` param. If
|
||||||
|
not specified, :func:`typer_callback` is used.
|
||||||
|
|
||||||
|
:returns: ``typer.Typer`` instance
|
||||||
|
"""
|
||||||
|
kwargs.setdefault('callback', typer_callback)
|
||||||
|
return typer.Typer(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
wutta_typer = make_typer(
|
||||||
|
name='wutta',
|
||||||
|
help="Wutta Software Framework"
|
||||||
|
)
|
44
src/wuttjamaican/cli/make_uuid.py
Normal file
44
src/wuttjamaican/cli/make_uuid.py
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
# -*- coding: utf-8; -*-
|
||||||
|
################################################################################
|
||||||
|
#
|
||||||
|
# WuttJamaican -- Base package for Wutta Framework
|
||||||
|
# Copyright © 2023-2024 Lance Edgar
|
||||||
|
#
|
||||||
|
# This file is part of Wutta Framework.
|
||||||
|
#
|
||||||
|
# Wutta Framework is free software: you can redistribute it and/or modify it
|
||||||
|
# under the terms of the GNU General Public License as published by the Free
|
||||||
|
# Software Foundation, either version 3 of the License, or (at your option) any
|
||||||
|
# later version.
|
||||||
|
#
|
||||||
|
# Wutta Framework is distributed in the hope that it will be useful, but
|
||||||
|
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
# more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License along with
|
||||||
|
# Wutta Framework. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
################################################################################
|
||||||
|
"""
|
||||||
|
See also: :ref:`wutta-make-uuid`
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import typer
|
||||||
|
|
||||||
|
from .base import wutta_typer
|
||||||
|
|
||||||
|
|
||||||
|
@wutta_typer.command()
|
||||||
|
def make_uuid(
|
||||||
|
ctx: typer.Context,
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Generate a new UUID
|
||||||
|
"""
|
||||||
|
config = ctx.parent.wutta_config
|
||||||
|
app = config.get_app()
|
||||||
|
uuid = app.make_uuid()
|
||||||
|
sys.stdout.write(f"{uuid}\n")
|
0
tests/cli/__init__.py
Normal file
0
tests/cli/__init__.py
Normal file
0
tests/cli/example.conf
Normal file
0
tests/cli/example.conf
Normal file
39
tests/cli/test_base.py
Normal file
39
tests/cli/test_base.py
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
# -*- coding: utf-8; -*-
|
||||||
|
|
||||||
|
import os
|
||||||
|
from unittest import TestCase
|
||||||
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
|
import typer
|
||||||
|
|
||||||
|
from wuttjamaican.conf import WuttaConfig
|
||||||
|
from wuttjamaican.cli import base as mod
|
||||||
|
|
||||||
|
|
||||||
|
here = os.path.dirname(__file__)
|
||||||
|
example_conf = os.path.join(here, 'example.conf')
|
||||||
|
|
||||||
|
|
||||||
|
class TestMakeCliConfig(TestCase):
|
||||||
|
|
||||||
|
def test_basic(self):
|
||||||
|
ctx = MagicMock(params={'config_paths': [example_conf]})
|
||||||
|
config = mod.make_cli_config(ctx)
|
||||||
|
self.assertIsInstance(config, WuttaConfig)
|
||||||
|
self.assertEqual(config.files_read, [example_conf])
|
||||||
|
|
||||||
|
|
||||||
|
class TestTyperCallback(TestCase):
|
||||||
|
|
||||||
|
def test_basic(self):
|
||||||
|
ctx = MagicMock(params={'config_paths': [example_conf]})
|
||||||
|
mod.typer_callback(ctx)
|
||||||
|
self.assertIsInstance(ctx.wutta_config, WuttaConfig)
|
||||||
|
self.assertEqual(ctx.wutta_config.files_read, [example_conf])
|
||||||
|
|
||||||
|
|
||||||
|
class TestMakeTyper(TestCase):
|
||||||
|
|
||||||
|
def test_basic(self):
|
||||||
|
typr = mod.make_typer()
|
||||||
|
self.assertIsInstance(typr, typer.Typer)
|
20
tests/cli/test_make_uuid.py
Normal file
20
tests/cli/test_make_uuid.py
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
# -*- coding: utf-8; -*-
|
||||||
|
|
||||||
|
import os
|
||||||
|
from unittest import TestCase
|
||||||
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
|
from wuttjamaican.cli import make_uuid as mod
|
||||||
|
|
||||||
|
|
||||||
|
here = os.path.dirname(__file__)
|
||||||
|
example_conf = os.path.join(here, 'example.conf')
|
||||||
|
|
||||||
|
|
||||||
|
class TestMakeUuid(TestCase):
|
||||||
|
|
||||||
|
def test_basic(self):
|
||||||
|
ctx = MagicMock(params={'config_paths': [example_conf]})
|
||||||
|
with patch.object(mod, 'sys') as sys:
|
||||||
|
mod.make_uuid(ctx)
|
||||||
|
sys.stdout.write.assert_called_once()
|
Loading…
Reference in a new issue