Coverage for .tox/coverage/lib/python3.13/site-packages/wuttjamaican/cli/base.py: 100%
26 statements
« prev ^ index » next coverage.py v7.16.1, created at 2026-10-02 15:01 -0500
« prev ^ index » next coverage.py v7.16.1, created at 2026-10-02 15:01 -0500
1# -*- coding: utf-8; -*-
2################################################################################
3#
4# WuttJamaican -- Base package for Wutta Framework
5# Copyright © 2023-2026 Lance Edgar
6#
7# This file is part of Wutta Framework.
8#
9# Wutta Framework is free software: you can redistribute it and/or modify it
10# under the terms of the GNU General Public License as published by the Free
11# Software Foundation, either version 3 of the License, or (at your option) any
12# later version.
13#
14# Wutta Framework is distributed in the hope that it will be useful, but
15# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
16# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
17# more details.
18#
19# You should have received a copy of the GNU General Public License along with
20# Wutta Framework. If not, see <http://www.gnu.org/licenses/>.
21#
22################################################################################
23"""
24WuttJamaican - core command logic
26See also :doc:`/narr/cli/index`.
28.. data:: wutta_typer
30 This is the top-level ``wutta`` :term:`command`, using the Typer
31 framework.
33 See also :func:`make_typer()`.
34"""
36import logging
37from pathlib import Path
38from typing import List, Optional
40try:
41 from typing import Annotated
42except ImportError: # pragma: no cover
43 from typing_extensions import Annotated
45import typer
46from typer.core import TyperGroup
48from wuttjamaican.conf import make_config
49from wuttjamaican.util import load_entry_points
52def make_cli_config(ctx: typer.Context):
53 """
54 Make a :term:`config object` according to the command-line context
55 (params).
57 This function is normally called by :func:`typer_callback()`.
59 This function calls :func:`~wuttjamaican.conf.make_config()` using
60 config files specified via command line (if any).
62 :param ctx: ``typer.Context`` instance
64 :returns: :class:`~wuttjamaican.conf.WuttaConfig` instance
65 """
66 logging.basicConfig()
67 return make_config(files=ctx.params.get("config_paths") or None)
70def typer_callback( # pylint: disable=unused-argument
71 ctx: typer.Context,
72 config_paths: Annotated[
73 Optional[List[Path]],
74 typer.Option(
75 "--config",
76 "-c",
77 exists=True,
78 help="Config path (may be specified more than once)",
79 ),
80 ] = None,
81 runas_username: Annotated[
82 str,
83 typer.Option(
84 "--runas",
85 help="Username responsible for running the command (where applicable).",
86 ),
87 ] = None,
88 comment: Annotated[
89 str,
90 typer.Option(
91 "--comment",
92 "-m",
93 help="Comment to apply to the transaction (where applicable).",
94 ),
95 ] = None,
96):
97 """
98 Generic callback for use with top-level commands. This adds some
99 top-level args:
101 * ``--config`` (and/or ``-c``)
102 * ``--runas``
103 * ``--comment`` (or ``-m``)
105 This callback is responsible for creating the :term:`config
106 object` for the command. (It calls :func:`make_cli_config()` for
107 that.) It then attaches it to the context as
108 ``ctx.wutta_config``.
109 """
110 ctx.wutta_config = make_cli_config(ctx)
113def typer_eager_imports(group: [typer.Typer, str]):
114 """
115 Eagerly import all modules which are registered as having
116 :term:`subcommands <subcommand>` belonging to the given group
117 (i.e. top-level :term:`command`).
119 This is used to locate subcommands which may be defined by
120 multiple different packages. It is mostly needed for the main
121 ``wutta`` command, since e.g. various extension packages may
122 define additional subcommands for it.
124 Most custom apps will define their own top-level command and some
125 subcommands, but will have no need to "discover" additional
126 subcommands defined elsewhere. Hence you normally would not need
127 to call this function.
129 However if you wish to define a ``wutta`` subcommand(s), you
130 *would* need to register the entry point for your module(s)
131 containing the subcommand(s) like so (in ``pyproject.toml``):
133 .. code-block:: ini
135 [project.entry-points."wutta.typer_imports"]
136 poser = "poser.commands"
138 Note that the ``wutta.typer_imports`` above indicates you are
139 registering a module which defines ``wutta`` subcommands. The
140 ``poser`` name is arbitrary but should match your package name.
142 :param group: Typer group command, or the name of one.
143 """
144 if isinstance(group, typer.Typer):
145 group = group.info.name
146 load_entry_points(f"{group}.typer_imports")
149# nb. typer "by design" will not sort the commands listing, but we
150# definitely want that (until someone says otherwise).
151# nb. thanks to the following, for pointers
152# https://stackoverflow.com/a/78351533
153# https://github.com/fastapi/typer/issues/428#issuecomment-1238866548
154class OrderCommands(TyperGroup):
155 """
156 Custom base class for top-level Typer command.
158 This exists only to ensure the commands listing is sorted
159 alphabetically when displayed with ``--help`` param, since Typer
160 "by design" will not sort them.
162 See also this `Typer doc
163 <https://typer.tiangolo.com/tutorial/commands/#sorting-of-the-commands>`_.
164 """
166 def list_commands(self, ctx): # pylint: disable=empty-docstring
167 """ """
168 return sorted(self.commands)
171def make_typer(**kwargs):
172 """
173 Create a Typer command instance, per Wutta conventions.
175 This function is used to create the top-level ``wutta`` command,
176 :data:`wutta_typer`. You can use it to create additional
177 top-level commands for your app if needed. (And don't forget to
178 register; see :doc:`/narr/cli/custom`.)
180 :param cls: Override for the ``Typer.cls`` param. If not
181 specified, :class:`OrderCommands` is used.
183 :param callback: Override for the ``Typer.callback`` param. If
184 not specified, :func:`typer_callback` is used.
186 :returns: ``typer.Typer`` instance
187 """
188 kwargs.setdefault("cls", OrderCommands)
189 kwargs.setdefault("callback", typer_callback)
190 return typer.Typer(**kwargs)
193wutta_typer = make_typer(name="wutta", help="Wutta Software Framework")