Coverage for fastagency/cli/cli.py: 73%

42 statements  

« prev     ^ index     » next       coverage.py v7.8.0, created at 2025-04-19 12:16 +0000

1from logging import getLogger 1afghbicde

2from pathlib import Path 1afghbicde

3from typing import Annotated, Any, Optional 1afghbicde

4 

5import typer 1afghbicde

6 

7from .. import __version__ 1afghbicde

8from ..exceptions import FastAgencyCLIError, FastAgencyCLIPythonVersionError 1afghbicde

9from .discover import get_import_string 1afghbicde

10from .docker_cli import docker_app 1afghbicde

11from .logging import setup_logging 1afghbicde

12 

13app = typer.Typer(rich_markup_mode="rich") 1afghbicde

14app.add_typer( 1afghbicde

15 docker_app, 

16 name="docker", 

17 help="[bold]Docker[/bold] commands for [bold]FastAgency[/bold]", 

18) 

19 

20setup_logging() 1afghbicde

21logger = getLogger(__name__) 1afghbicde

22 

23 

24def version_callback(value: bool) -> None: 1afghbicde

25 if value: 25 ↛ 26line 25 didn't jump to line 26 because the condition on line 25 was never true1abcde

26 typer.echo(f"{__version__}") 

27 raise typer.Exit() 

28 

29 

30@app.callback() 1afghbicde

31def callback( 1afghbicde

32 version: Annotated[ 

33 Optional[bool], 

34 typer.Option( 

35 "--version", help="Show the version and exit.", callback=version_callback 

36 ), 

37 ] = None, 

38) -> None: 

39 """FastAgency CLI - The [bold]fastapi[/bold] command line app. 😎 

40 

41 Manage your [bold]FastAgency[/bold] projects, run your FastAgency apps, and more. 

42 

43 Read more in the docs: [link]https://fastagency.ai/latest/[/link]. 

44 """ # noqa: D415 

45 

46 

47def _run_app( 1afghbicde

48 *, 

49 path: Optional[Path], 

50 app: Optional[str], 

51 workflow: Optional[str], 

52 params: dict[str, Any], 

53 dev_mode: bool = False, 

54 single_run: bool = False, 

55) -> None: 

56 try: 1ab

57 import_string, fa_app = get_import_string(path=path, app_name=app) 1ab

58 

59 with fa_app.create(import_string=import_string): 1b

60 fa_app.start( 1b

61 import_string=import_string, 

62 name=workflow, 

63 params=params, 

64 single_run=single_run, 

65 ) 

66 except FastAgencyCLIPythonVersionError as e: 1a

67 msg = e.args[0] 1a

68 typer.echo(msg, err=True) 1a

69 raise typer.Exit(code=1) # noqa: B904 1a

70 except FastAgencyCLIError as e: 

71 msg = e.args[0] 

72 typer.echo(msg, err=True) 

73 raise typer.Exit(code=1) from None 

74 

75 

76def _get_help_messages(dev_mode: bool = False) -> dict[str, str]: 1afghbicde

77 help = f"""Run a [bold]FastAgency[/bold] app in [yellow]{"development" if dev_mode else "production"}[/yellow] mode. 🚀 1afghbicde

78 

79{"This is similar to the [bold]fastagency run[/bold] command but with [bold]reload[/bold] enabled and listening on the [blue]127.0.0.1[/blue] address." if dev_mode else "This is similar to the [bold]fastagency dev[/bold] command, but optimized for production environments."} 

80 

81It automatically detects the Python module or package that needs to be imported based on the file or directory path passed. 

82 

83If no path is passed, it tries with: 

84 

85- [blue]main.py[/blue] 

86- [blue]app.py[/blue] 

87- [blue]api.py[/blue] 

88- [blue]app/main.py[/blue] 

89- [blue]app/app.py[/blue] 

90- [blue]app/api.py[/blue] 

91 

92It also detects the directory that needs to be added to the [bold]PYTHONPATH[/bold] to make the app importable and adds it. 

93 

94It detects the [bold]FastAgency[/bold] app object to use. By default it looks in the module or package for an object named: 

95 

96- [blue]app[/blue] 

97- [blue]api[/blue] 

98 

99Otherwise, it uses the first [bold]FastAgency[/bold] app found in the imported module or package. 

100""" 

101 short_help = f"Run a [bold]FastAgency[/bold] app in [yellow]{'development' if dev_mode else 'production'}[/yellow] mode." 1afghbicde

102 return {"help": help, "short_help": short_help} 1afghbicde

103 

104 

105@app.command( 1afghbicde

106 context_settings={"allow_extra_args": True, "ignore_unknown_options": True}, 

107 **_get_help_messages(False), # type: ignore[arg-type] 

108) 

109def run( 1afghbicde

110 path: Annotated[ 

111 Optional[Path], 

112 typer.Argument( 

113 help="A path to a Python file or package directory (with [blue]__init__.py[/blue] files) containing a [bold]FastAgency[/bold] app. If not provided, a default set of paths will be tried." 

114 ), 

115 ] = None, 

116 *, 

117 app: Annotated[ 

118 Optional[str], 

119 typer.Option( 

120 help="The name of the variable that contains the [bold][/bold] app in the imported module or package. If not provided, it is detected automatically." 

121 ), 

122 ] = None, 

123 workflow: Annotated[ 

124 Optional[str], 

125 typer.Option( 

126 "--workflow", 

127 "-w", 

128 help="The name of the workflow to run. If not provided, the default workflow will be run.", 

129 ), 

130 ] = None, 

131 single_run: Annotated[ 

132 bool, 

133 typer.Option( 

134 "--single-run", help="If set, only a single workflow will be executed." 

135 ), 

136 ] = False, 

137 ctx: typer.Context, 

138) -> None: 

139 if len(ctx.args) > 0: 1ab

140 raise NotImplementedError("Extra arguments are not supported in this command.") 

141 else: 

142 params: dict[str, Any] = {} 1ab

143 

144 dev_mode = False 1ab

145 _run_app( 1ab

146 path=path, 

147 app=app, 

148 workflow=workflow, 

149 params=params, 

150 dev_mode=dev_mode, 

151 single_run=single_run, 

152 ) 

153 

154 

155@app.command( 1afghbicde

156 context_settings={"allow_extra_args": True, "ignore_unknown_options": True}, 

157 **_get_help_messages(True), # type: ignore[arg-type] 

158) 

159def dev( 1afghbicde

160 path: Annotated[ 

161 Optional[Path], 

162 typer.Argument( 

163 help="A path to a Python file or package directory (with [blue]__init__.py[/blue] files) containing a [bold]FastAgency[/bold] app. If not provided, a default set of paths will be tried." 

164 ), 

165 ] = None, 

166 *, 

167 app: Annotated[ 

168 Optional[str], 

169 typer.Option( 

170 help="The name of the variable that contains the [bold][/bold] app in the imported module or package. If not provided, it is detected automatically." 

171 ), 

172 ] = None, 

173 workflow: Annotated[ 

174 Optional[str], 

175 typer.Option( 

176 "--workflow", 

177 "-w", 

178 help="The name of the workflow to run. If not provided, the default workflow will be run.", 

179 ), 

180 ] = None, 

181 single_run: Annotated[ 

182 bool, 

183 typer.Option( 

184 "--single-run", help="If set, only a single workflow will be executed." 

185 ), 

186 ] = False, 

187 ctx: typer.Context, 

188) -> None: 

189 dev_mode = True 

190 if len(ctx.args) > 0: 

191 raise NotImplementedError("Extra arguments are not supported in this command.") 

192 else: 

193 params: dict[str, Any] = {} 

194 _run_app( 

195 path=path, 

196 app=app, 

197 workflow=workflow, 

198 params=params, 

199 dev_mode=dev_mode, 

200 single_run=single_run, 

201 ) 

202 

203 

204@app.command(help="Display the version of FastAgency") 1afghbicde

205def version() -> None: 1afghbicde

206 typer.echo(__version__) 

207 

208 

209def main() -> None: 1afghbicde

210 app()