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

16 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-07-20 17:05 +0200

1""" 

2CLI tool to generate SQL from PyDAL code. 

3""" 

4 

5import argparse 

6import select 

7import string 

8import sys 

9import textwrap 

10from typing import IO, Optional 

11 

12import rich 

13from rich.prompt import Prompt 

14 

15from .helpers import flatten 

16 

17 

18class PrettyParser(argparse.ArgumentParser): # pragma: no cover 

19 """ 

20 Add 'rich' to the argparse output. 

21 """ 

22 

23 def _print_message(self, message: str, file: Optional[IO[str]] = None) -> None: 

24 rich.print(message, file=file) 

25 

26 

27def has_stdin_data() -> bool: # pragma: no cover 

28 """ 

29 Check if the program starts with cli data (pipe | or redirect ><). 

30 

31 See Also: 

32 https://stackoverflow.com/questions/3762881/how-do-i-check-if-stdin-has-some-data 

33 """ 

34 return any( 

35 select.select( 

36 [ 

37 sys.stdin, 

38 ], 

39 [], 

40 [], 

41 0.0, 

42 )[0] 

43 ) 

44 

45 

46def handle_cli( 

47 code: str, 

48 db_type: str = None, 

49 tables: list[list[str]] = None, 

50 verbose: bool = False, 

51 noop: bool = False, 

52) -> None: 

53 """ 

54 Handle user input. 

55 """ 

56 to_execute = string.Template( 

57 textwrap.dedent( 

58 """ 

59 from pydal import * 

60 from pydal.objects import * 

61 from pydal.validators import * 

62 

63 from pydal2sql import generate_sql 

64 

65 db = database = DAL(None, migrate=False) 

66 

67 tables = $tables 

68 db_type = '$db_type' 

69 

70 $code 

71 

72 if not tables: 

73 tables = db._tables 

74 

75 for table in tables: 

76 print(generate_sql(db[table], db_type)) 

77 """ 

78 ) 

79 ) 

80 

81 generated_code = to_execute.substitute( 

82 { 

83 "tables": flatten(tables or []), 

84 "db_type": db_type or "", 

85 "code": textwrap.dedent(code), 

86 } 

87 ) 

88 if verbose or noop: 

89 rich.print(generated_code, file=sys.stderr) 

90 

91 if not noop: 

92 exec(generated_code) # nosec: B102 

93 

94 

95def app() -> None: # pragma: no cover 

96 """ 

97 Entrypoint for the pydal2sql cli command. 

98 """ 

99 parser = PrettyParser( 

100 prog="pydal2sql", 

101 formatter_class=argparse.RawDescriptionHelpFormatter, 

102 description="""[green]CLI tool to generate SQL from PyDAL code.[/green]\n 

103 Aside from using cli arguments, you can also configure the tool in your code. 

104 You can set the following variables: 

105 

106 db_type: str = 'sqlite' # your desired database type; 

107 tables: list[str] = [] # your desired tables to generate SQL for;""", 

108 epilog="Example: [i]cat models.py | pydal2sql sqlite[/i]", 

109 ) 

110 

111 parser.add_argument( 

112 "db_type", nargs="?", help="Which database dialect to generate ([blue]postgres, sqlite, mysql[/blue])" 

113 ) 

114 

115 parser.add_argument("--verbose", "-v", help="Show more info", action=argparse.BooleanOptionalAction, default=False) 

116 

117 parser.add_argument( 

118 "--noop", "-n", help="Only show code, don't run it.", action=argparse.BooleanOptionalAction, default=False 

119 ) 

120 

121 parser.add_argument( 

122 "-t", 

123 "--table", 

124 "--tables", 

125 action="append", 

126 nargs="+", 

127 help="One or more tables to generate. By default, all tables in the file will be used.", 

128 ) 

129 

130 args = parser.parse_args() 

131 

132 db_type = args.db_type 

133 

134 if not has_stdin_data(): 

135 if not db_type: 

136 db_type = Prompt.ask("Which database type do you want to use?", choices=["sqlite", "postgres", "mysql"]) 

137 

138 rich.print("Please paste your define tables code below and press ctrl-D when finished.", file=sys.stderr) 

139 

140 # else: data from stdin 

141 # py code or cli args should define settings. 

142 

143 text = sys.stdin.read() 

144 return handle_cli(text, db_type, args.table, verbose=args.verbose, noop=args.noop)