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

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 

25 

26See also :doc:`/narr/cli/index`. 

27 

28.. data:: wutta_typer 

29 

30 This is the top-level ``wutta`` :term:`command`, using the Typer 

31 framework. 

32 

33 See also :func:`make_typer()`. 

34""" 

35 

36import logging 

37from pathlib import Path 

38from typing import List, Optional 

39 

40try: 

41 from typing import Annotated 

42except ImportError: # pragma: no cover 

43 from typing_extensions import Annotated 

44 

45import typer 

46from typer.core import TyperGroup 

47 

48from wuttjamaican.conf import make_config 

49from wuttjamaican.util import load_entry_points 

50 

51 

52def make_cli_config(ctx: typer.Context): 

53 """ 

54 Make a :term:`config object` according to the command-line context 

55 (params). 

56 

57 This function is normally called by :func:`typer_callback()`. 

58 

59 This function calls :func:`~wuttjamaican.conf.make_config()` using 

60 config files specified via command line (if any). 

61 

62 :param ctx: ``typer.Context`` instance 

63 

64 :returns: :class:`~wuttjamaican.conf.WuttaConfig` instance 

65 """ 

66 logging.basicConfig() 

67 return make_config(files=ctx.params.get("config_paths") or None) 

68 

69 

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: 

100 

101 * ``--config`` (and/or ``-c``) 

102 * ``--runas`` 

103 * ``--comment`` (or ``-m``) 

104 

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) 

111 

112 

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`). 

118 

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. 

123 

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. 

128 

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``): 

132 

133 .. code-block:: ini 

134 

135 [project.entry-points."wutta.typer_imports"] 

136 poser = "poser.commands" 

137 

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. 

141 

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") 

147 

148 

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. 

157 

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. 

161 

162 See also this `Typer doc 

163 <https://typer.tiangolo.com/tutorial/commands/#sorting-of-the-commands>`_. 

164 """ 

165 

166 def list_commands(self, ctx): # pylint: disable=empty-docstring 

167 """ """ 

168 return sorted(self.commands) 

169 

170 

171def make_typer(**kwargs): 

172 """ 

173 Create a Typer command instance, per Wutta conventions. 

174 

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`.) 

179 

180 :param cls: Override for the ``Typer.cls`` param. If not 

181 specified, :class:`OrderCommands` is used. 

182 

183 :param callback: Override for the ``Typer.callback`` param. If 

184 not specified, :func:`typer_callback` is used. 

185 

186 :returns: ``typer.Typer`` instance 

187 """ 

188 kwargs.setdefault("cls", OrderCommands) 

189 kwargs.setdefault("callback", typer_callback) 

190 return typer.Typer(**kwargs) 

191 

192 

193wutta_typer = make_typer(name="wutta", help="Wutta Software Framework")