Coverage for src/fastapi_cloud_cli/commands/deploy/configure.py: 100%

57 statements  

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

1from pathlib import Path 2EbFbGbHbIbJbKb

2 

3import typer 2EbFbGbHbIbJbKb

4from pydantic import TypeAdapter 2EbFbGbHbIbJbKb

5from rich_toolkit.menu import Option 2EbFbGbHbIbJbKb

6 

7from fastapi_cloud_cli.api import APIClient 2EbFbGbHbIbJbKb

8from fastapi_cloud_cli.commands.deploy.archive import AppDirectory, _get_app_name 2EbFbGbHbIbJbKb

9from fastapi_cloud_cli.commands.deploy.cloud import ( 2EbFbGbHbIbJbKb

10 AppResponse, 

11 _create_app, 

12 _get_apps, 

13 _get_teams, 

14 _update_app, 

15) 

16from fastapi_cloud_cli.utils.apps import AppConfig, write_app_config 2EbFbGbHbIbJbKb

17from fastapi_cloud_cli.utils.cli import FastAPIRichToolkit 2EbFbGbHbIbJbKb

18 

19 

20def _configure_app( 2EbFbGbHbIbJbKb

21 toolkit: FastAPIRichToolkit, 

22 client: APIClient, 

23 path_to_deploy: Path, 

24) -> AppConfig: 

25 toolkit.print(f"Setting up and deploying [blue]{path_to_deploy}[/blue]", emoji="📁") 2qbo = p q r s h t ` LbZb+ 0bcbMbrb1ba db4 5 sbu ? v w x y i z { Nb2b, 3bebObtb4bc fb6 7 ubA @ B C D E j F | Pb5b- 6bgbQbvb7bd hb8 9 wbG [ H I J K k L } Rb8b. 9bibSbxb!be jb! # ybM ] N O P Q l R ~ Tb#b/ $bkbUbzb%bb lb$ % AbS ^ T U V W m X abVb'b: (bmbWbBb)bf nb' ( CbY _ Z 0 1 2 n 3 bbXb*b; +bobYbDb,bg pb) *

26 

27 toolkit.print_line() 2qbo = p q r s h t ` LbZb+ 0bcbMbrb1ba db4 5 sbu ? v w x y i z { Nb2b, 3bebObtb4bc fb6 7 ubA @ B C D E j F | Pb5b- 6bgbQbvb7bd hb8 9 wbG [ H I J K k L } Rb8b. 9bibSbxb!be jb! # ybM ] N O P Q l R ~ Tb#b/ $bkbUbzb%bb lb$ % AbS ^ T U V W m X abVb'b: (bmbWbBb)bf nb' ( CbY _ Z 0 1 2 n 3 bbXb*b; +bobYbDb,bg pb) *

28 

29 with toolkit.progress("Fetching teams...", transient=True) as progress: 2qbo = p q r s h t ` LbZb+ 0bcbMbrb1ba db4 5 sbu ? v w x y i z { Nb2b, 3bebObtb4bc fb6 7 ubA @ B C D E j F | Pb5b- 6bgbQbvb7bd hb8 9 wbG [ H I J K k L } Rb8b. 9bibSbxb!be jb! # ybM ] N O P Q l R ~ Tb#b/ $bkbUbzb%bb lb$ % AbS ^ T U V W m X abVb'b: (bmbWbBb)bf nb' ( CbY _ Z 0 1 2 n 3 bbXb*b; +bobYbDb,bg pb) *

30 with client.handle_http_errors( 2qbo = p q r s h t ` LbZb+ 0bcbMbrb1ba db4 5 sbu ? v w x y i z { Nb2b, 3bebObtb4bc fb6 7 ubA @ B C D E j F | Pb5b- 6bgbQbvb7bd hb8 9 wbG [ H I J K k L } Rb8b. 9bibSbxb!be jb! # ybM ] N O P Q l R ~ Tb#b/ $bkbUbzb%bb lb$ % AbS ^ T U V W m X abVb'b: (bmbWbBb)bf nb' ( CbY _ Z 0 1 2 n 3 bbXb*b; +bobYbDb,bg pb) *

31 progress, 

32 default_message="Error fetching teams. Please try again later.", 

33 ): 

34 teams = _get_teams(client) 2qbo = p q r s h t ` LbZb+ 0bcbMbrb1ba db4 5 sbu ? v w x y i z { Nb2b, 3bebObtb4bc fb6 7 ubA @ B C D E j F | Pb5b- 6bgbQbvb7bd hb8 9 wbG [ H I J K k L } Rb8b. 9bibSbxb!be jb! # ybM ] N O P Q l R ~ Tb#b/ $bkbUbzb%bb lb$ % AbS ^ T U V W m X abVb'b: (bmbWbBb)bf nb' ( CbY _ Z 0 1 2 n 3 bbXb*b; +bobYbDb,bg pb) *

35 

36 team = toolkit.ask( 2qbo = p q r s h t ` Lb+ cbMbrba db4 5 sbu ? v w x y i z { Nb, ebObtbc fb6 7 ubA @ B C D E j F | Pb- gbQbvbd hb8 9 wbG [ H I J K k L } Rb. ibSbxbe jb! # ybM ] N O P Q l R ~ Tb/ kbUbzbb lb$ % AbS ^ T U V W m X abVb: mbWbBbf nb' ( CbY _ Z 0 1 2 n 3 bbXb; obYbDbg pb) *

37 "Select the team you want to deploy to:", 

38 options=[ 

39 Option({"name": team.name, "value": team}) 

40 for team in sorted(teams, key=lambda team: team.name.lower()) 

41 ], 

42 allow_filtering=True, 

43 emoji="🏢", 

44 ) 

45 

46 toolkit.print_line() 2qbo = p q r s h t ` Lb+ cbMbrba db4 5 sbu ? v w x y i z { Nb, ebObtbc fb6 7 ubA @ B C D E j F | Pb- gbQbvbd hb8 9 wbG [ H I J K k L } Rb. ibSbxbe jb! # ybM ] N O P Q l R ~ Tb/ kbUbzbb lb$ % AbS ^ T U V W m X abVb: mbWbBbf nb' ( CbY _ Z 0 1 2 n 3 bbXb; obYbDbg pb) *

47 

48 create_new_app = toolkit.confirm( 2qbo = p q r s h t ` Lb+ cbMbrba db4 5 sbu ? v w x y i z { Nb, ebObtbc fb6 7 ubA @ B C D E j F | Pb- gbQbvbd hb8 9 wbG [ H I J K k L } Rb. ibSbxbe jb! # ybM ] N O P Q l R ~ Tb/ kbUbzbb lb$ % AbS ^ T U V W m X abVb: mbWbBbf nb' ( CbY _ Z 0 1 2 n 3 bbXb; obYbDbg pb) *

49 "Do you want to create a new app?", default=True, emoji="📦" 

50 ) 

51 

52 toolkit.print_line() 2qbo = p q r s h t ` + cbrba db4 5 sbu ? v w x y i z { , ebtbc fb6 7 ubA @ B C D E j F | - gbvbd hb8 9 wbG [ H I J K k L } . ibxbe jb! # ybM ] N O P Q l R ~ / kbzbb lb$ % AbS ^ T U V W m X ab: mbBbf nb' ( CbY _ Z 0 1 2 n 3 bb; obDbg pb) *

53 

54 selected_app: AppResponse | None = None 2qbo = p q r s h t ` + cbrba db4 5 sbu ? v w x y i z { , ebtbc fb6 7 ubA @ B C D E j F | - gbvbd hb8 9 wbG [ H I J K k L } . ibxbe jb! # ybM ] N O P Q l R ~ / kbzbb lb$ % AbS ^ T U V W m X ab: mbBbf nb' ( CbY _ Z 0 1 2 n 3 bb; obDbg pb) *

55 

56 if not create_new_app: 2qbo = p q r s h t ` + cbrba db4 5 sbu ? v w x y i z { , ebtbc fb6 7 ubA @ B C D E j F | - gbvbd hb8 9 wbG [ H I J K k L } . ibxbe jb! # ybM ] N O P Q l R ~ / kbzbb lb$ % AbS ^ T U V W m X ab: mbBbf nb' ( CbY _ Z 0 1 2 n 3 bb; obDbg pb) *

57 with toolkit.progress("Fetching apps...", transient=True) as progress: 2h ` cba db4 5 i { ebc fb6 7 j | gbd hb8 9 k } ibe jb! # l ~ kbb lb$ % m abmbf nb' ( n bbobg pb) *

58 with client.handle_http_errors( 2h ` cba db4 5 i { ebc fb6 7 j | gbd hb8 9 k } ibe jb! # l ~ kbb lb$ % m abmbf nb' ( n bbobg pb) *

59 progress, 

60 default_message="Error fetching apps. Please try again later.", 

61 ): 

62 apps = _get_apps(client=client, team_id=team.id) 2h ` cba db4 5 i { ebc fb6 7 j | gbd hb8 9 k } ibe jb! # l ~ kbb lb$ % m abmbf nb' ( n bbobg pb) *

63 

64 if not apps: 2h ` cba db4 5 i { ebc fb6 7 j | gbd hb8 9 k } ibe jb! # l ~ kbb lb$ % m abmbf nb' ( n bbobg pb) *

65 toolkit.fail( 2cbebgbibkbmbob

66 "not_found", 

67 "No apps found in this team. You can create a new app instead.", 

68 ) 

69 

70 selected_app = toolkit.ask( 2h ` a db4 5 i { c fb6 7 j | d hb8 9 k } e jb! # l ~ b lb$ % m abf nb' ( n bbg pb) *

71 "Select the app you want to deploy to:", 

72 options=[ 

73 Option({"name": app.slug, "value": app}) 

74 for app in sorted(apps, key=lambda app: app.slug.lower()) 

75 ], 

76 allow_filtering=True, 

77 emoji="📦", 

78 ) 

79 

80 app_name = ( 2qbo = p q r s h t ` + rba 4 5 sbu ? v w x y i z { , tbc 6 7 ubA @ B C D E j F | - vbd 8 9 wbG [ H I J K k L } . xbe ! # ybM ] N O P Q l R ~ / zbb $ % AbS ^ T U V W m X ab: Bbf ' ( CbY _ Z 0 1 2 n 3 bb; Dbg ) *

81 selected_app.slug 

82 if selected_app 

83 else toolkit.input( 

84 title="What's your app name?", 

85 default=_get_app_name(path_to_deploy), 

86 emoji="✏️", 

87 ) 

88 ) 

89 

90 toolkit.print_line() 2qbo = p q r s h t ` + rba 4 5 sbu ? v w x y i z { , tbc 6 7 ubA @ B C D E j F | - vbd 8 9 wbG [ H I J K k L } . xbe ! # ybM ] N O P Q l R ~ / zbb $ % AbS ^ T U V W m X ab: Bbf ' ( CbY _ Z 0 1 2 n 3 bb; Dbg ) *

91 

92 initial_directory = selected_app.directory if selected_app else "" 2qbo = p q r s h t ` + rba 4 5 sbu ? v w x y i z { , tbc 6 7 ubA @ B C D E j F | - vbd 8 9 wbG [ H I J K k L } . xbe ! # ybM ] N O P Q l R ~ / zbb $ % AbS ^ T U V W m X ab: Bbf ' ( CbY _ Z 0 1 2 n 3 bb; Dbg ) *

93 

94 directory_input = toolkit.input( 2qbo = p q r s h t ` + rba 4 5 sbu ? v w x y i z { , tbc 6 7 ubA @ B C D E j F | - vbd 8 9 wbG [ H I J K k L } . xbe ! # ybM ] N O P Q l R ~ / zbb $ % AbS ^ T U V W m X ab: Bbf ' ( CbY _ Z 0 1 2 n 3 bb; Dbg ) *

95 title=( 

96 "Directory where your app's pyproject.toml file lives (e.g. src, backend):" 

97 ), 

98 value=initial_directory or "", 

99 placeholder=( 

100 "[italic]Leave empty if pyproject.toml is in the current directory[/italic]" 

101 ), 

102 validator=TypeAdapter(AppDirectory), 

103 emoji="📂", 

104 ) 

105 

106 directory: str | None = directory_input if directory_input else None 1o=pqrsht+a45u?vwxyiz,c67A@BCDEjF-d89G[HIJKkL.e!#M]NOPQlR/b$%S^TUVWmX:f'(Y_Z012n3;g)*

107 

108 toolkit.print_line() 1o=pqrsht+a45u?vwxyiz,c67A@BCDEjF-d89G[HIJKkL.e!#M]NOPQlR/b$%S^TUVWmX:f'(Y_Z012n3;g)*

109 

110 toolkit.print("Deployment configuration:", emoji="📋") 1o=pqrsht+a45u?vwxyiz,c67A@BCDEjF-d89G[HIJKkL.e!#M]NOPQlR/b$%S^TUVWmX:f'(Y_Z012n3;g)*

111 toolkit.print_line() 1o=pqrsht+a45u?vwxyiz,c67A@BCDEjF-d89G[HIJKkL.e!#M]NOPQlR/b$%S^TUVWmX:f'(Y_Z012n3;g)*

112 toolkit.print(f"Team: [bold]{team.name}[/bold]") 1o=pqrsht+a45u?vwxyiz,c67A@BCDEjF-d89G[HIJKkL.e!#M]NOPQlR/b$%S^TUVWmX:f'(Y_Z012n3;g)*

113 toolkit.print(f"App name: [bold]{app_name}[/bold]") 1o=pqrsht+a45u?vwxyiz,c67A@BCDEjF-d89G[HIJKkL.e!#M]NOPQlR/b$%S^TUVWmX:f'(Y_Z012n3;g)*

114 toolkit.print(f"Directory: [bold]{directory or '.'}[/bold]") 1o=pqrsht+a45u?vwxyiz,c67A@BCDEjF-d89G[HIJKkL.e!#M]NOPQlR/b$%S^TUVWmX:f'(Y_Z012n3;g)*

115 

116 toolkit.print_line() 1o=pqrsht+a45u?vwxyiz,c67A@BCDEjF-d89G[HIJKkL.e!#M]NOPQlR/b$%S^TUVWmX:f'(Y_Z012n3;g)*

117 

118 choice = toolkit.ask( 1o=pqrsht+a45u?vwxyiz,c67A@BCDEjF-d89G[HIJKkL.e!#M]NOPQlR/b$%S^TUVWmX:f'(Y_Z012n3;g)*

119 "Does everything look right?", 

120 options=[ 

121 Option({"name": "Yes, start the deployment!", "value": "deploy"}), 

122 Option({"name": "No, let me start over", "value": "cancel"}), 

123 ], 

124 emoji="👀", 

125 ) 

126 toolkit.print_line() 1o=pqrsht+a45u?vwxyiz,c67A@BCDEjF-d89G[HIJKkL.e!#M]NOPQlR/b$%S^TUVWmX:f'(Y_Z012n3;g)*

127 

128 if choice == "cancel": 1o=pqrsht+a45u?vwxyiz,c67A@BCDEjF-d89G[HIJKkL.e!#M]NOPQlR/b$%S^TUVWmX:f'(Y_Z012n3;g)*

129 toolkit.print("Deployment cancelled.") 1=45?67@89[!#]$%^'(_)*

130 raise typer.Exit(0) 1=45?67@89[!#]$%^'(_)*

131 

132 if selected_app: 1opqrsht+auvwxyiz,cABCDEjF-dGHIJKkL.eMNOPQlR/bSTUVWmX:fYZ012n3;g

133 if directory != selected_app.directory: 1haicjdkelbmfng

134 with ( 1ab

135 toolkit.progress(title="Updating app directory...") as progress, 

136 client.handle_http_errors(progress), 

137 ): 

138 app = _update_app( 1acdebfg

139 client=client, app_id=selected_app.id, directory=directory 

140 ) 

141 

142 progress.log(f"App directory updated to '{directory or '.'}'") 1acdebfg

143 else: 

144 app = selected_app 1hijklmn

145 else: 

146 with toolkit.progress(title="Creating app...") as progress: 1opqrst+uvwxyz,ABCDEF-GHIJKL.MNOPQR/STUVWX:YZ0123;

147 with client.handle_http_errors(progress): 1opqrst+uvwxyz,ABCDEF-GHIJKL.MNOPQR/STUVWX:YZ0123;

148 app = _create_app( 1opqrst+uvwxyz,ABCDEF-GHIJKL.MNOPQR/STUVWX:YZ0123;

149 client=client, 

150 team_id=team.id, 

151 app_name=app_name, 

152 directory=directory, 

153 ) 

154 

155 progress.log(f"App created successfully! App slug: {app.slug}") 1opqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123

156 

157 app_config = AppConfig(app_id=app.id, team_id=team.id) 1opqrshtauvwxyizcABCDEjFdGHIJKkLeMNOPQlRbSTUVWmXfYZ012n3g

158 

159 write_app_config(path_to_deploy, app_config) 1opqrshtauvwxyizcABCDEjFdGHIJKkLeMNOPQlRbSTUVWmXfYZ012n3g

160 

161 return app_config 1opqrshtauvwxyizcABCDEjFdGHIJKkLeMNOPQlRbSTUVWmXfYZ012n3g