Coverage for src/fastapi_cloud_cli/commands/env/set.py: 100%

64 statements  

« prev     ^ index     » next       coverage.py v7.16.0, created at 2026-11-09 00:25 +0000

1import sys 1JKLMNOP

2from pathlib import Path 1JKLMNOP

3from typing import Annotated, Any 1JKLMNOP

4 

5import typer 1JKLMNOP

6from pydantic import BaseModel, Field 1JKLMNOP

7from rich_toolkit import RichToolkit 1JKLMNOP

8 

9from fastapi_cloud_cli.api import APIClient, EnvironmentVariableCreatePayload 1JKLMNOP

10from fastapi_cloud_cli.commands._auth import UserCommand, get_user_command_context 1JKLMNOP

11from fastapi_cloud_cli.commands.env._app import env_app 1JKLMNOP

12from fastapi_cloud_cli.utils.apps import resolve_app_id_or_fail 1JKLMNOP

13from fastapi_cloud_cli.utils.cli import FastAPIRichToolkit 1JKLMNOP

14from fastapi_cloud_cli.utils.execution import JsonOutputOption 1JKLMNOP

15 

16 

17class EnvironmentVariableSetOutput(BaseModel): 1JKLMNOP

18 app_id: str 1JKLMNOP

19 name: str 1JKLMNOP

20 is_secret: bool 1JKLMNOP

21 show_tag: Annotated[bool, Field(exclude=True)] = True 1JKLMNOP

22 

23 

24def _render_environment_variable_set_output( 1JKoLMpNOPq

25 data: EnvironmentVariableSetOutput, toolkit: RichToolkit 

26) -> None: 

27 kind = "Secret environment variable" if data.is_secret else "Environment variable" 1abrstcduvoefwxyghzApijBCDklEFGmnHIq

28 message = f"{kind} [bold]{data.name}[/] set." 1abrstcduvoefwxyghzApijBCDklEFGmnHIq

29 

30 if data.show_tag: 1abrstcduvoefwxyghzApijBCDklEFGmnHIq

31 toolkit.print_title("environment variables") 1rstuvowxyzApBCDEFGHIq

32 

33 toolkit.print_line() 1abrstcduvoefwxyghzApijBCDklEFGmnHIq

34 toolkit.print(message, bullet=False) 1abrstcduvoefwxyghzApijBCDklEFGmnHIq

35 

36 

37def _input( 1JKLMNOP

38 toolkit: FastAPIRichToolkit, 

39 prompt: str, 

40 *, 

41 password: bool = False, 

42) -> str: 

43 return toolkit.input(prompt, password=password, bullet=False) 1abcdefghijklmn

44 

45 

46def _resolve_environment_variable_name( 1JKLMNOP

47 toolkit: FastAPIRichToolkit, *, name: str | None, secret: bool 

48) -> str: 

49 if name is not None: 2a b + jb` cbX Q Y r , s Z t c d - kb{ db0 R 1 u . v 2 o e f / lb| eb3 S 4 w : x 5 y g h ; mb} fb6 T 7 z = A 8 p i j ? nb~ gb9 U ! B @ C # D k l [ obabhb$ V % E ] F ' G m n ^ pbbbib( W ) H _ I * q

50 return name 2+ ` cbX Q Y r , s Z t - { db0 R 1 u . v 2 o / | eb3 S 4 w : x 5 y ; } fb6 T 7 z = A 8 p ? ~ gb9 U ! B @ C # D [ abhb$ V % E ] F ' G ^ bbib( W ) H _ I * q

51 

52 if toolkit.mode == "json": 2a b jbc d kbe f lbg h mbi j nbk l obm n pb

53 toolkit.fail( 2jbkblbmbnbobpb

54 "missing_required_input", 

55 "Environment variable name is required.", 

56 hint="Pass NAME to choose an environment variable.", 

57 ) 

58 

59 if secret: 1abcdefghijklmn

60 return _input(toolkit, "Enter the name of the secret to set:") 1bdfhjln

61 

62 return _input(toolkit, "Enter the name of the environment variable to set:") 1acegikm

63 

64 

65def _resolve_environment_variable_value( 1JKLMNOP

66 toolkit: FastAPIRichToolkit, 

67 *, 

68 value: str | None, 

69 value_stdin: bool, 

70 secret: bool, 

71) -> str: 

72 if value is not None and value_stdin: 2a b + ` cbX Q Y r , s Z t c d - { db0 R 1 u . v 2 o e f / | eb3 S 4 w : x 5 y g h ; } fb6 T 7 z = A 8 p i j ? ~ gb9 U ! B @ C # D k l [ abhb$ V % E ] F ' G m n ^ bbib( W ) H _ I * q

73 toolkit.fail( 2cbdbebfbgbhbib

74 "invalid_input", 

75 "Only one environment variable value source can be used.", 

76 hint="Pass either VALUE or --value-stdin.", 

77 ) 

78 

79 if value is not None: 2a b + ` X Q Y r , s Z t c d - { 0 R 1 u . v 2 o e f / | 3 S 4 w : x 5 y g h ; } 6 T 7 z = A 8 p i j ? ~ 9 U ! B @ C # D k l [ ab$ V % E ] F ' G m n ^ bb( W ) H _ I * q

80 return value 1+XYr,sZt-01u.v2o/34w:x5y;67z=A8p?9!B@C#D[$%E]F'G^()H_I*q

81 

82 if value_stdin: 2a b ` Q c d { R e f | S g h } T i j ~ U k l abV m n bbW

83 return sys.stdin.read().rstrip("\r\n") 1QRSTUVW

84 

85 if toolkit.mode == "json": 2a b ` c d { e f | g h } i j ~ k l abm n bb

86 toolkit.fail( 2` { | } ~ abbb

87 "missing_required_input", 

88 "Environment variable value is required.", 

89 hint="Pass VALUE or --value-stdin to set the environment variable.", 

90 ) 

91 

92 if secret: 1abcdefghijklmn

93 return _input(toolkit, "Enter the secret value:", password=True) 1bdfhjln

94 

95 return _input(toolkit, "Enter the value of the environment variable:") 1acegikm

96 

97 

98@env_app.command(cls=UserCommand) 1JKLMNOP

99def set( 2J K xbL M ybN O P zb

100 ctx: typer.Context, 

101 name: str | None = typer.Argument( 

102 None, 

103 help="The name of the environment variable to set", 

104 ), 

105 value: str | None = typer.Argument( 

106 None, 

107 help="The value of the environment variable to set", 

108 ), 

109 path_arg: Annotated[ 

110 Path | None, 

111 typer.Argument( 

112 help=( 

113 "Path to the directory with your app's pyproject.toml " 

114 "(defaults to current directory)" 

115 ) 

116 ), 

117 ] = None, 

118 value_stdin: Annotated[ 

119 bool, 

120 typer.Option( 

121 "--value-stdin", 

122 help="Read the environment variable value from stdin.", 

123 ), 

124 ] = False, 

125 path: Annotated[ 

126 Path | None, 

127 typer.Option( 

128 "--path", 

129 help=( 

130 "Path to the directory with your app's pyproject.toml " 

131 "(defaults to current directory)" 

132 ), 

133 ), 

134 ] = None, 

135 app_id: Annotated[ 

136 str | None, 

137 typer.Option( 

138 "--app-id", 

139 help="ID of the app whose environment variable should be set.", 

140 ), 

141 ] = None, 

142 secret: Annotated[ 

143 bool, 

144 typer.Option( 

145 "--secret", 

146 help="Mark a new environment variable as secret. Existing variables keep their secret status.", 

147 ), 

148 ] = False, 

149 no_redeploy: Annotated[ 

150 bool, 

151 typer.Option( 

152 "--no-redeploy", 

153 help="Save the environment variable without redeploying the app.", 

154 ), 

155 ] = False, 

156 json_output: JsonOutputOption = False, 

157) -> Any: 

158 """ 

159 Create or update an environment variable and redeploy the app by default. 

160 """ 

161 

162 toolkit = get_user_command_context(ctx).toolkit 2a b + jb` cbX Q Y r qb, s Z t c d - kb{ db0 R 1 u rb. v 2 o e f / lb| eb3 S 4 w sb: x 5 y g h ; mb} fb6 T 7 z tb= A 8 p i j ? nb~ gb9 U ! B ub@ C # D k l [ obabhb$ V % E vb] F ' G m n ^ pbbbib( W ) H wb_ I * q

163 

164 target_app_id = resolve_app_id_or_fail( 2a b + jb` cbX Q Y r qb, s Z t c d - kb{ db0 R 1 u rb. v 2 o e f / lb| eb3 S 4 w sb: x 5 y g h ; mb} fb6 T 7 z tb= A 8 p i j ? nb~ gb9 U ! B ub@ C # D k l [ obabhb$ V % E vb] F ' G m n ^ pbbbib( W ) H wb_ I * q

165 toolkit, app_id=app_id, path=path or path_arg 

166 ) 

167 name_needs_prompt = name is None 2a b + jb` cbX Q Y r , s Z t c d - kb{ db0 R 1 u . v 2 o e f / lb| eb3 S 4 w : x 5 y g h ; mb} fb6 T 7 z = A 8 p i j ? nb~ gb9 U ! B @ C # D k l [ obabhb$ V % E ] F ' G m n ^ pbbbib( W ) H _ I * q

168 value_needs_prompt = value is None and not value_stdin 2a b + jb` cbX Q Y r , s Z t c d - kb{ db0 R 1 u . v 2 o e f / lb| eb3 S 4 w : x 5 y g h ; mb} fb6 T 7 z = A 8 p i j ? nb~ gb9 U ! B @ C # D k l [ obabhb$ V % E ] F ' G m n ^ pbbbib( W ) H _ I * q

169 prompts_user = name_needs_prompt or value_needs_prompt 2a b + jb` cbX Q Y r , s Z t c d - kb{ db0 R 1 u . v 2 o e f / lb| eb3 S 4 w : x 5 y g h ; mb} fb6 T 7 z = A 8 p i j ? nb~ gb9 U ! B @ C # D k l [ obabhb$ V % E ] F ' G m n ^ pbbbib( W ) H _ I * q

170 if prompts_user and toolkit.mode != "json": 2a b + jb` cbX Q Y r , s Z t c d - kb{ db0 R 1 u . v 2 o e f / lb| eb3 S 4 w : x 5 y g h ; mb} fb6 T 7 z = A 8 p i j ? nb~ gb9 U ! B @ C # D k l [ obabhb$ V % E ] F ' G m n ^ pbbbib( W ) H _ I * q

171 toolkit.print_title("environment variables") 1abcdefghijklmn

172 toolkit.print_line() 1abcdefghijklmn

173 

174 name = _resolve_environment_variable_name( 2a b + jb` cbX Q Y r , s Z t c d - kb{ db0 R 1 u . v 2 o e f / lb| eb3 S 4 w : x 5 y g h ; mb} fb6 T 7 z = A 8 p i j ? nb~ gb9 U ! B @ C # D k l [ obabhb$ V % E ] F ' G m n ^ pbbbib( W ) H _ I * q

175 toolkit, 

176 name=name, 

177 secret=secret, 

178 ) 

179 value = _resolve_environment_variable_value( 2a b + ` cbX Q Y r , s Z t c d - { db0 R 1 u . v 2 o e f / | eb3 S 4 w : x 5 y g h ; } fb6 T 7 z = A 8 p i j ? ~ gb9 U ! B @ C # D k l [ abhb$ V % E ] F ' G m n ^ bbib( W ) H _ I * q

180 toolkit, 

181 value=value, 

182 value_stdin=value_stdin, 

183 secret=secret, 

184 ) 

185 

186 with APIClient() as client: 1ab+XQYr,sZtcd-0R1u.v2oef/3S4w:x5ygh;6T7z=A8pij?9U!B@C#Dkl[$V%E]F'Gmn^(W)H_I*q

187 with toolkit.progress( 1ab+XQYr,sZtcd-0R1u.v2oef/3S4w:x5ygh;6T7z=A8pij?9U!B@C#Dkl[$V%E]F'Gmn^(W)H_I*q

188 "Setting environment variable", transient=True 

189 ) as progress: 

190 with client.handle_http_errors(progress, toolkit=toolkit): 1ab+XQYr,sZtcd-0R1u.v2oef/3S4w:x5ygh;6T7z=A8pij?9U!B@C#Dkl[$V%E]F'Gmn^(W)H_I*q

191 environment_variables = client.batch_environment_variables( 1ab+XQYr,sZtcd-0R1u.v2oef/3S4w:x5ygh;6T7z=A8pij?9U!B@C#Dkl[$V%E]F'Gmn^(W)H_I*q

192 app_id=target_app_id, 

193 upsert={ 

194 name: EnvironmentVariableCreatePayload( 

195 value=value, is_secret=secret 

196 ) 

197 }, 

198 delete=[], 

199 redeploy=not no_redeploy, 

200 ) 

201 

202 variable = next( 1abXQYrsZtcd0R1uv2oef3S4wx5ygh6T7zA8pij9U!BC#Dkl$V%EF'Gmn(W)HI*q

203 variable for variable in environment_variables.data if variable.name == name 

204 ) 

205 

206 toolkit.success( 1abXQYrsZtcd0R1uv2oef3S4wx5ygh6T7zA8pij9U!BC#Dkl$V%EF'Gmn(W)HI*q

207 EnvironmentVariableSetOutput( 

208 app_id=target_app_id, 

209 name=name, 

210 is_secret=variable.is_secret, 

211 show_tag=not prompts_user, 

212 ), 

213 render_output=_render_environment_variable_set_output, 

214 )