Coverage for src/pydal2sql/cli.py: 100%

56 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-11-13 13:42 +0100

1import sys 

2import typing 

3from typing import Annotated, Optional 

4 

5import typer 

6from configuraptor import Singleton 

7from pydal2sql_core import get_typing_args 

8from pydal2sql_core.cli_support import core_alter, core_create 

9from pydal2sql_core.types import DEFAULT_OUTPUT_FORMAT, SUPPORTED_OUTPUT_FORMATS 

10from rich import print 

11from typer import Argument 

12from typing_extensions import Never 

13 

14from .__about__ import __version__ 

15from .typer_support import ( 

16 DEFAULT_VERBOSITY, 

17 IS_DEBUG, 

18 ApplicationState, 

19 DB_Types, 

20 Verbosity, 

21 with_exit_code, 

22) 

23 

24## type fuckery: 

25T = typing.TypeVar("T") 

26 

27OptionalArgument = Annotated[Optional[T], Argument()] 

28# usage: (myparam: OptionalArgument[some_type]) 

29 

30OptionalOption = Annotated[Optional[T], typer.Option()] 

31# usage: (myparam: OptionalOption[some_type]) 

32 

33DBType_Option = Annotated[DB_Types, typer.Option("--db-type", "--dialect", "-d")] 

34 

35Tables_Option = Annotated[ 

36 Optional[list[str]], 

37 typer.Option("--table", "--tables", "-t", help="One or more table names, default is all tables."), 

38] 

39 

40OutputFormat_Option = Annotated[ 

41 # Optional[SUPPORTED_OUTPUT_FORMATS], 

42 Optional[str], 

43 typer.Option("--format", "--fmt", help=f"One of {get_typing_args(SUPPORTED_OUTPUT_FORMATS)}"), 

44] 

45 

46### end typing stuff, start app: 

47 

48app = typer.Typer( 

49 no_args_is_help=True, 

50) 

51state = ApplicationState() 

52 

53 

54def info(*args: str) -> None: # pragma: no cover 

55 """ 

56 'print' but with blue text. 

57 """ 

58 print(f"[blue]{' '.join(args)}[/blue]", file=sys.stderr) 

59 

60 

61def warn(*args: str) -> None: # pragma: no cover 

62 """ 

63 'print' but with yellow text. 

64 """ 

65 print(f"[yellow]{' '.join(args)}[/yellow]", file=sys.stderr) 

66 

67 

68def danger(*args: str) -> None: # pragma: no cover 

69 """ 

70 'print' but with red text. 

71 """ 

72 print(f"[red]{' '.join(args)}[/red]", file=sys.stderr) 

73 

74 

75@app.command() 

76@with_exit_code(hide_tb=not IS_DEBUG) 

77def create( 

78 filename: OptionalArgument[str] = None, 

79 tables: Tables_Option = None, 

80 db_type: DBType_Option = None, 

81 magic: Optional[bool] = None, 

82 noop: Optional[bool] = None, 

83 function: Optional[str] = None, 

84 output_format: OutputFormat_Option = DEFAULT_OUTPUT_FORMAT, 

85 output_file: Optional[str] = None, 

86) -> bool: 

87 """ 

88 todo: docs 

89 

90 Examples: 

91 pydal2sql create models.py 

92 cat models.py | pydal2sql 

93 pydal2sql # output from stdin 

94 """ 

95 config = state.update_config( 

96 magic=magic, noop=noop, tables=tables, db_type=db_type.value if db_type else None, function=function 

97 ) 

98 

99 if core_create( 

100 filename=filename, 

101 db_type=config.db_type, 

102 tables=config.tables, 

103 verbose=state.verbosity > Verbosity.normal, 

104 noop=config.noop, 

105 magic=config.magic, 

106 function=config.function, 

107 output_format=typing.cast(SUPPORTED_OUTPUT_FORMATS, output_format), 

108 output_file=output_file, 

109 ): 

110 print("[green] success! [/green]", file=sys.stderr) 

111 return True 

112 else: 

113 print("[red] create failed! [/red]", file=sys.stderr) 

114 return False 

115 

116 

117@app.command() 

118@with_exit_code(hide_tb=not IS_DEBUG) 

119def alter( 

120 filename_before: OptionalArgument[str] = None, 

121 filename_after: OptionalArgument[str] = None, 

122 db_type: DBType_Option = None, 

123 tables: Tables_Option = None, 

124 magic: Optional[bool] = None, 

125 noop: Optional[bool] = None, 

126 function: Optional[str] = None, 

127 output_format: OutputFormat_Option = DEFAULT_OUTPUT_FORMAT, 

128 output_file: Optional[str] = None, 

129) -> bool: 

130 """ 

131 Todo: docs 

132 

133 Examples: 

134 > pydal2sql alter @b3f24091a9201d6 examples/magic.py 

135 compare magic.py at commit b3f... to current (= as in workdir). 

136 

137 > pydal2sql alter examples/magic.py@@b3f24091a9201d6 examples/magic_after_rename.py@latest 

138 compare magic.py (which was renamed to magic_after_rename.py), 

139 at a specific commit to the latest version in git (ignore workdir version). 

140 

141 Todo: 

142 alter myfile.py # only one arg 

143 # = alter myfile.py@latest myfile.py@current 

144 # != alter myfile.py - # with - for cli 

145 # != alter - myfile.py 

146 """ 

147 config = state.update_config( 

148 magic=magic, noop=noop, tables=tables, db_type=db_type.value if db_type else None, function=function 

149 ) 

150 

151 if core_alter( 

152 filename_before, 

153 filename_after, 

154 db_type=config.db_type, 

155 tables=config.tables, 

156 verbose=state.verbosity > Verbosity.normal, 

157 noop=config.noop, 

158 magic=config.magic, 

159 function=config.function, 

160 output_format=typing.cast(SUPPORTED_OUTPUT_FORMATS, output_format), 

161 output_file=output_file, 

162 ): 

163 print("[green] success! [/green]", file=sys.stderr) 

164 return True 

165 else: 

166 print("[red] alter failed! [/red]", file=sys.stderr) 

167 return False 

168 

169 

170""" 

171todo: 

172- db type in config 

173- models.py with db import or define_tables method. 

174- `public.` prefix 

175""" 

176 

177""" 

178def pin: 

179pydal2sql pin 96de5b37b586e75b8ac053b9bef7647f544fe502 # -> default pin created 

180pydal2sql alter myfile.py # should compare between pin/@latest and @current 

181 # replaces @current for Before, not for After in case of ALTER. 

182pydal2sql pin --remove # -> default pin removed 

183 

184pydal2sql pin 96de5b37b586e75b8ac053b9bef7647f544fe502 --name my_custom_name # -> pin '@my_custom_name' created 

185pydal2sql pin 96de5b37b586e75b8ac053b9bef7647f544fe503 --name my_custom_name #-> pin '@my_custom_name' overwritten 

186pydal2sql create myfile.py@my_custom_name 

187pydal2sql pin 96de5b37b586e75b8ac053b9bef7647f544fe502 --remove -> pin '@my_custom_name' removed 

188 

189pydal2sql pins 

190# lists hash with name 

191""" 

192 

193 

194def show_config_callback() -> Never: 

195 """ 

196 --show-config requested! 

197 """ 

198 print(state) 

199 raise typer.Exit(0) 

200 

201 

202def version_callback() -> Never: 

203 """ 

204 --version requested! 

205 """ 

206 print(f"pydal2sql Version: {__version__}") 

207 

208 raise typer.Exit(0) 

209 

210 

211@app.callback(invoke_without_command=True) 

212def main( 

213 _: typer.Context, 

214 config: str = None, 

215 verbosity: Verbosity = DEFAULT_VERBOSITY, 

216 # stops the program: 

217 show_config: bool = False, 

218 version: bool = False, 

219) -> None: 

220 """ 

221 Todo: docs 

222 

223 Args: 

224 _: context to determine if a subcommand is passed, etc 

225 config: path to a different config toml file 

226 verbosity: level of detail to print out (1 - 3) 

227 

228 show_config: display current configuration? 

229 version: display current version? 

230 

231 """ 

232 if state.config: 

233 # if a config already exists, it's outdated, so we clear it. 

234 # only really applicable in Pytest scenarios where multiple commands are executed after eachother 

235 Singleton.clear(state.config) 

236 

237 state.load_config(config_file=config, verbosity=verbosity) 

238 

239 if show_config: 

240 show_config_callback() 

241 elif version: 

242 version_callback() 

243 # else: just continue 

244 

245 

246# if __name__ == "__main__": 

247# app()