Coverage for src/fastapi_cloud_cli/commands/tokens/create.py: 100%

64 statements  

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

1from pathlib import Path 1abcdefg

2from typing import Annotated, Any, Literal 1abcdefg

3 

4import typer 1abcdefg

5from pydantic import BaseModel, Field 1abcdefg

6from rich_toolkit import RichToolkit 1abcdefg

7 

8from fastapi_cloud_cli.api import APIClient 1abcdefg

9from fastapi_cloud_cli.commands._auth import UserCommand, get_user_command_context 1abcdefg

10from fastapi_cloud_cli.commands.tokens._app import tokens_app 1abcdefg

11from fastapi_cloud_cli.utils.apps import resolve_app_id_or_fail 1abcdefg

12from fastapi_cloud_cli.utils.cli import FastAPIRichToolkit 1abcdefg

13from fastapi_cloud_cli.utils.execution import JsonOutputOption 1abcdefg

14 

15DEFAULT_EXPIRES_IN_DAYS = 365 1abcdefg

16 

17 

18class CreatedDeployToken(BaseModel): 1abcdefg

19 id: str 1abcdefg

20 name: str 1abcdefg

21 expired_at: str 1abcdefg

22 

23 

24class DeployTokenCreateAPIResponse(CreatedDeployToken): 1abcdefg

25 value: str 1abcdefg

26 

27 

28class StoredDeployTokenSecret(BaseModel): 1abcdefg

29 provider: Literal["file"] = "file" 1abcdefg

30 path: Path 1abcdefg

31 

32 

33class DeployTokenCreateOutput(BaseModel): 1abcdefg

34 app_id: str 1abcdefg

35 token: CreatedDeployToken 1abcdefg

36 stored_secret: StoredDeployTokenSecret 1abcdefg

37 output_file: Annotated[Path, Field(exclude=True)] 1abcdefg

38 

39 

40def _resolve_token_name(toolkit: FastAPIRichToolkit, *, name: str | None) -> str: 1abcdefg

41 if name is not None: 1oQkJvXwpRhKxYyqSlLzZArTiMB0CsUmND1EtVnOF2GuWjPH3I

42 return name 1oQvwpRxyqSzArTBCsUDEtVFGuWHI

43 

44 if toolkit.mode == "json": 1kJXhKYlLZiM0mN1nO2jP3

45 toolkit.fail( 1XYZ0123

46 "missing_required_input", 

47 "Deploy token name is required.", 

48 hint="Pass --name to choose a deploy token name.", 

49 ) 

50 

51 return toolkit.input( 1kJhKlLiMmNnOjP

52 "What's the deploy token name?", 

53 default="Deploy token", 

54 bullet=False, 

55 ) 

56 

57 

58def _resolve_output_file( 1abcdefg

59 toolkit: FastAPIRichToolkit, *, output_file: Path | None 

60) -> Path: 

61 if output_file is not None: 1oQk4JvX5wpRh6KxY7yqSl8LzZ9ArTi!MB0#CsUm$ND1%EtVn'OF2(GuWj)PH3*I

62 return output_file 1oQkJvXwpRhKxYyqSlLzZArTiMB0CsUmND1EtVnOF2GuWjPH3I

63 

64 toolkit.fail( 1456789!#$%'()*

65 "missing_required_input", 

66 "Output file is required.", 

67 hint="Pass --output-file to store the deploy token value.", 

68 ) 

69 

70 

71def _create_deploy_token( 1abcdefg

72 client: APIClient, *, app_id: str, name: str, expires_in_days: int 

73) -> DeployTokenCreateAPIResponse: 

74 response = client.post( 1oQkJvwpRhKxyqSlLzArTiMBCsUmNDEtVnOFGuWjPHI

75 f"/apps/{app_id}/tokens", 

76 json={"name": name, "expires_in_days": expires_in_days}, 

77 ) 

78 response.raise_for_status() 1oQkJvwpRhKxyqSlLzArTiMBCsUmNDEtVnOFGuWjPHI

79 

80 return DeployTokenCreateAPIResponse.model_validate(response.json()) 1okvwphxyqlzAriBCsmDEtnFGujHI

81 

82 

83def _write_token_value(output_file: Path, value: str) -> None: 1abcdefg

84 output_file.parent.mkdir(parents=True, exist_ok=True) 1okvwphxyqlzAriBCsmDEtnFGujHI

85 output_file.write_text(value, encoding="utf-8") 1okvwphxyqlzAriBCsmDEtnFGujHI

86 output_file.chmod(0o600) 1okvwphxyqlzAriBCsmDEtnFGujHI

87 

88 

89def _render_deploy_token_create_output( 1abhcdiefgj

90 data: DeployTokenCreateOutput, toolkit: RichToolkit 

91) -> None: 

92 toolkit.print(f"Created deploy token [bold]{data.token.name}[/bold]", bullet=False) 1okphqlrismtnuj

93 toolkit.print( 1okphqlrismtnuj

94 f"Stored deploy token value in [bold]{data.output_file}[/bold]", 

95 bullet=False, 

96 ) 

97 

98 

99@tokens_app.command("create", cls=UserCommand) 1abcdefg

100def create_token( 1ab+cd,efg-

101 ctx: typer.Context, 

102 app_id: Annotated[ 

103 str | None, 

104 typer.Option( 

105 "--app-id", 

106 help="ID of the app whose deploy token should be created.", 

107 ), 

108 ] = None, 

109 name: Annotated[ 

110 str | None, 

111 typer.Option( 

112 "--name", 

113 help="Name of the deploy token to create.", 

114 ), 

115 ] = None, 

116 expires_in_days: Annotated[ 

117 int, 

118 typer.Option( 

119 "--expires-in-days", 

120 help="Number of days before the deploy token expires.", 

121 min=1, 

122 ), 

123 ] = DEFAULT_EXPIRES_IN_DAYS, 

124 output_file: Annotated[ 

125 Path | None, 

126 typer.Option( 

127 "--output-file", 

128 help="File path where the deploy token value should be stored.", 

129 ), 

130 ] = None, 

131 json_output: JsonOutputOption = False, 

132) -> Any: 

133 """ 

134 Create a deploy token for an app. 

135 """ 

136 

137 toolkit = get_user_command_context(ctx).toolkit 1oQk4JvX5wpRh6KxY7yqSl8LzZ9ArTi!MB0#CsUm$ND1%EtVn'OF2(GuWj)PH3*I

138 

139 target_app_id = resolve_app_id_or_fail(toolkit, app_id=app_id) 1oQk4JvX5wpRh6KxY7yqSl8LzZ9ArTi!MB0#CsUm$ND1%EtVn'OF2(GuWj)PH3*I

140 

141 toolkit.print_title("deploy tokens") 1oQk4JvX5wpRh6KxY7yqSl8LzZ9ArTi!MB0#CsUm$ND1%EtVn'OF2(GuWj)PH3*I

142 toolkit.print_line() 1oQk4JvX5wpRh6KxY7yqSl8LzZ9ArTi!MB0#CsUm$ND1%EtVn'OF2(GuWj)PH3*I

143 

144 output_file = _resolve_output_file(toolkit, output_file=output_file) 1oQk4JvX5wpRh6KxY7yqSl8LzZ9ArTi!MB0#CsUm$ND1%EtVn'OF2(GuWj)PH3*I

145 name_needs_prompt = name is None 1oQkJvXwpRhKxYyqSlLzZArTiMB0CsUmND1EtVnOF2GuWjPH3I

146 name = _resolve_token_name(toolkit, name=name) 1oQkJvXwpRhKxYyqSlLzZArTiMB0CsUmND1EtVnOF2GuWjPH3I

147 if name_needs_prompt: 1oQkJvwpRhKxyqSlLzArTiMBCsUmNDEtVnOFGuWjPHI

148 toolkit.print_line() 1kJhKlLiMmNnOjP

149 

150 with APIClient() as client: 1oQkJvwpRhKxyqSlLzArTiMBCsUmNDEtVnOFGuWjPHI

151 with toolkit.progress( 1oQkJvwpRhKxyqSlLzArTiMBCsUmNDEtVnOFGuWjPHI

152 title="Creating deploy token", 

153 transient=True, 

154 ) as progress: 

155 with client.handle_http_errors( 1oQkJvwpRhKxyqSlLzArTiMBCsUmNDEtVnOFGuWjPHI

156 progress, 

157 default_message="Error creating deploy token. Please try again later.", 

158 not_found_message="App not found.", 

159 toolkit=toolkit, 

160 ): 

161 token = _create_deploy_token( 1oQkJvwpRhKxyqSlLzArTiMBCsUmNDEtVnOFGuWjPHI

162 client, 

163 app_id=target_app_id, 

164 name=name, 

165 expires_in_days=expires_in_days, 

166 ) 

167 

168 _write_token_value(output_file, token.value) 1okvwphxyqlzAriBCsmDEtnFGujHI

169 

170 toolkit.success( 1okvwphxyqlzAriBCsmDEtnFGujHI

171 DeployTokenCreateOutput( 

172 app_id=target_app_id, 

173 token=CreatedDeployToken.model_validate(token), 

174 stored_secret=StoredDeployTokenSecret(path=output_file), 

175 output_file=output_file, 

176 ), 

177 render_output=_render_deploy_token_create_output, 

178 )