Coverage for src/footman/__init__.py: 100%

44 statements  

« prev     ^ index     » next       coverage.py v7.15.2, created at 2026-07-24 13:38 +0000

1"""footman — a task runner with typed commands and instant completion. 

2 

3Typed function signatures become real flags and positionals, modules become 

4nested command groups, and shell completion answers from a cached manifest 

5without importing your code. 

6 

7The console-script entry lives here and is deliberately thin: completion must 

8dispatch to the stdlib-only hot path before importing the framework or the 

9user's tasks, so `main` checks `--complete` first and everything else is 

10imported lazily. A bare `import footman` pays for nothing but this module. 

11""" 

12 

13from __future__ import annotations 

14 

15from typing import TYPE_CHECKING 

16 

17if TYPE_CHECKING: 

18 # Give type-checkers the real types for the lazily re-exported names below; 

19 # at runtime these are served by `__getattr__` without importing registry 

20 # on a bare `import footman` (the completion hot path). 

21 from footman import tools as tools 

22 from footman._fetch import FetchError as FetchError 

23 from footman._fetch import fetch as fetch 

24 from footman.app import App as App 

25 from footman.app import Brand as Brand 

26 from footman.compose import include as include 

27 from footman.compose import plugin as plugin 

28 from footman.context import Context as Context 

29 from footman.context import Failed as Failed 

30 from footman.context import Result as Result 

31 from footman.context import RunFailed as RunFailed 

32 from footman.context import confirm as confirm 

33 from footman.context import fail as fail 

34 from footman.context import inherited as inherited 

35 from footman.context import parallel as parallel 

36 from footman.context import passthrough as passthrough 

37 from footman.context import progress as progress 

38 from footman.context import prompt as prompt 

39 from footman.context import run as run 

40 from footman.context import select as select 

41 from footman.context import track as track 

42 from footman.context import use_context as use_context 

43 from footman.params import Exists as Exists 

44 from footman.params import Forward as Forward 

45 from footman.params import IsDir as IsDir 

46 from footman.params import IsFile as IsFile 

47 from footman.params import Many as Many 

48 from footman.params import NoSplit as NoSplit 

49 from footman.params import ask as ask 

50 from footman.params import between as between 

51 from footman.params import check as check 

52 from footman.params import doc as doc 

53 from footman.params import env as env 

54 from footman.params import exists as exists 

55 from footman.params import forward as forward 

56 from footman.params import isdir as isdir 

57 from footman.params import isfile as isfile 

58 from footman.params import nosplit as nosplit 

59 from footman.params import suggest as suggest 

60 from footman.registry import Group as Group 

61 from footman.registry import Tasks as Tasks 

62 from footman.registry import TaskView as TaskView 

63 from footman.registry import capture as capture 

64 from footman.registry import finalize as finalize 

65 from footman.registry import group as group 

66 from footman.registry import requires as requires 

67 from footman.registry import requires_dep as requires_dep 

68 from footman.registry import requires_env as requires_env 

69 from footman.registry import requires_tool as requires_tool 

70 from footman.registry import task as task 

71 from footman.testing import Runner as Runner 

72 from footman.testing import recording as recording 

73 

74__version__ = "0.19.0" 

75__all__ = [ 

76 "App", 

77 "Brand", 

78 "Context", 

79 "Exists", 

80 "Failed", 

81 "FetchError", 

82 "Forward", 

83 "Group", 

84 "IsDir", 

85 "IsFile", 

86 "Many", 

87 "NoSplit", 

88 "Result", 

89 "RunFailed", 

90 "Runner", 

91 "TaskView", 

92 "Tasks", 

93 "__version__", 

94 "ask", 

95 "between", 

96 "capture", 

97 "check", 

98 "confirm", 

99 "doc", 

100 "docstrings", 

101 "env", 

102 "exists", 

103 "fail", 

104 "fetch", 

105 "finalize", 

106 "forward", 

107 "group", 

108 "include", 

109 "inherited", 

110 "isdir", 

111 "isfile", 

112 "main", 

113 "markdown", 

114 "nosplit", 

115 "parallel", 

116 "passthrough", 

117 "plugin", 

118 "progress", 

119 "prompt", 

120 "recording", 

121 "requires", 

122 "requires_dep", 

123 "requires_env", 

124 "requires_tool", 

125 "run", 

126 "select", 

127 "suggest", 

128 "task", 

129 "tools", 

130 "track", 

131 "use_context", 

132] 

133 

134 

135def main() -> None: 

136 """Console-script entry for `footman` and `fm`.""" 

137 import sys 

138 

139 argv = sys.argv[1:] 

140 if argv and argv[0] == "--complete": 

141 from footman._complete import complete_cli 

142 

143 raise SystemExit(complete_cli(argv[1:])) 

144 from footman.app import App 

145 

146 raise SystemExit(App().run(argv)) 

147 

148 

149def __getattr__(name: str) -> object: 

150 # Lazy re-export: `from footman import task, group` works without paying the 

151 # registry import on a bare `import footman` (the completion hot path). 

152 if name in ( 

153 "task", 

154 "group", 

155 "Group", 

156 "capture", 

157 "finalize", 

158 "Tasks", 

159 "TaskView", 

160 "requires", 

161 "requires_dep", 

162 "requires_env", 

163 "requires_tool", 

164 ): 

165 from footman import registry 

166 

167 return getattr(registry, name) 

168 if name in ("Runner", "recording"): 

169 from footman import testing 

170 

171 return getattr(testing, name) 

172 if name == "tools": 

173 import footman.tools 

174 

175 return footman.tools 

176 if name == "docstrings": 

177 import footman.docstrings 

178 

179 return footman.docstrings 

180 if name == "markdown": 

181 import footman.markdown 

182 

183 return footman.markdown 

184 if name in ("fetch", "FetchError"): 

185 from footman import _fetch 

186 

187 return getattr(_fetch, name) 

188 if name in ("include", "plugin"): 

189 from footman import compose 

190 

191 return getattr(compose, name) 

192 if name in ( 

193 "suggest", 

194 "Many", 

195 "nosplit", 

196 "exists", 

197 "isfile", 

198 "isdir", 

199 "between", 

200 "env", 

201 "check", 

202 "doc", 

203 "ask", 

204 "forward", 

205 "Forward", 

206 "NoSplit", 

207 "Exists", 

208 "IsFile", 

209 "IsDir", 

210 ): 

211 from footman import params 

212 

213 return getattr(params, name) 

214 if name in ( 

215 "run", 

216 "parallel", 

217 "Context", 

218 "Result", 

219 "inherited", 

220 "passthrough", 

221 "progress", 

222 "prompt", 

223 "confirm", 

224 "select", 

225 "track", 

226 "RunFailed", 

227 "Failed", 

228 "fail", 

229 "use_context", 

230 ): 

231 from footman import context 

232 

233 return getattr(context, name) 

234 if name in ("App", "Brand"): 

235 from footman import app 

236 

237 return getattr(app, name) 

238 raise AttributeError(f"module 'footman' has no attribute {name!r}")