Coverage for src/fastapi_cloud_cli/commands/domains/remove.py: 100%

55 statements  

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

1from typing import Annotated, Any 1stuvwxy

2 

3import typer 1stuvwxy

4from pydantic import BaseModel 1stuvwxy

5from rich_toolkit import RichToolkit 1stuvwxy

6 

7from fastapi_cloud_cli.api import APIClient, CustomDomain 1stuvwxy

8from fastapi_cloud_cli.commands._auth import UserCommand, get_user_command_context 1stuvwxy

9from fastapi_cloud_cli.commands.domains._app import domains_app 1stuvwxy

10from fastapi_cloud_cli.commands.domains._shared import ( 1stuvwxy

11 _find_custom_domain, 

12 _select_custom_domain, 

13) 

14from fastapi_cloud_cli.utils.apps import resolve_app_id_or_fail 1stuvwxy

15from fastapi_cloud_cli.utils.execution import JsonOutputOption 1stuvwxy

16 

17 

18class CustomDomainRemoveOutput(BaseModel): 1stuvwxy

19 app_id: str 1stuvwxy

20 domain_id: str 1stuvwxy

21 name: str 1stuvwxy

22 removed: bool = True 1stuvwxy

23 

24 

25def _render_custom_domain_remove_output( 1stcuvdwxye

26 data: CustomDomainRemoveOutput, 

27 toolkit: RichToolkit, 

28) -> None: 

29 toolkit.print(f"Removed [bold]{data.name}[/bold]", emoji="🐔") 1fazcAgBdhbCiDe

30 

31 

32def _print_removal_warning(toolkit: RichToolkit, domain: CustomDomain) -> None: 1stuvwxy

33 toolkit.print( 1jfkanzEcoAFgpBGdlhmbqCHirDIe

34 f"FastAPI Cloud resources for [bold]{domain.name}[/bold] will be removed. " 

35 "DNS records at your provider will not be changed.", 

36 emoji="⚠️", 

37 ) 

38 

39 

40@domains_app.command("remove", cls=UserCommand) 1stuvwxy

41def remove_domain( 1st#uv$wxy%

42 ctx: typer.Context, 

43 domain: Annotated[ 

44 str | None, 

45 typer.Argument( 

46 help="Hostname or ID of the custom domain to remove.", 

47 ), 

48 ] = None, 

49 app_id: Annotated[ 

50 str | None, 

51 typer.Option( 

52 "--app-id", 

53 help="ID of the app that owns the custom domain.", 

54 ), 

55 ] = None, 

56 yes: Annotated[ 

57 bool, 

58 typer.Option( 

59 "--yes", 

60 "-y", 

61 help="Confirm removal without prompting.", 

62 ), 

63 ] = False, 

64 json_output: JsonOutputOption = False, 

65) -> Any: 

66 """ 

67 Remove a custom domain from an app. 

68 

69 DNS records at your provider are not changed. 

70 """ 

71 toolkit = get_user_command_context(ctx).toolkit 1jfX4JkQanzY5LEScoAZ6MFTgpB07NGUdlh18KmRbqC29OHVirD3!PIWe

72 app_id = resolve_app_id_or_fail(toolkit, app_id=app_id) 1jfX4JkQanzY5LEScoAZ6MFTgpB07NGUdlh18KmRbqC29OHVirD3!PIWe

73 

74 if toolkit.mode == "json": 1jfX4JkQanzY5LEScoAZ6MFTgpB07NGUdlh18KmRbqC29OHVirD3!PIWe

75 if domain is None: 1X4JkY5LEZ6MF07NG18Km29OH3!PI

76 toolkit.fail( 1456789!

77 "missing_required_input", 

78 "Custom domain is required.", 

79 hint="Pass DOMAIN to choose a custom domain.", 

80 ) 

81 if not yes: 1XJkYLEZMF0NG1Km2OH3PI

82 toolkit.fail( 1XYZ0123

83 "missing_required_input", 

84 "Removal confirmation is required.", 

85 hint="Pass --yes to confirm removal.", 

86 ) 

87 

88 with APIClient() as client: 1jfJkQanzLEScoAMFTgpBNGUdlhKmRbqCOHVirDPIWe

89 with ( 1jfJkQalhKmRb

90 toolkit.progress( 

91 title="Fetching custom domains", 

92 transient=True, 

93 ) as progress, 

94 client.handle_http_errors( 

95 progress, 

96 default_message=( 

97 "Error fetching custom domains. Please try again later." 

98 ), 

99 not_found_message="App not found.", 

100 toolkit=toolkit, 

101 ), 

102 ): 

103 domains = client.get_custom_domains(app_id=app_id).data 1jfJkQanzLEScoAMFTgpBNGUdlhKmRbqCOHVirDPIWe

104 

105 toolkit.print_title("custom domains") 1jfJkQanzLEScoAMFTgpBNGUdlhKmRbqCOHVirDPIWe

106 toolkit.print_line() 1jfJkQanzLEScoAMFTgpBNGUdlhKmRbqCOHVirDPIWe

107 

108 selected_domain: CustomDomain | None 

109 if domain is None: 1jfJkQanzLEScoAMFTgpBNGUdlhKmRbqCOHVirDPIWe

110 if not domains: 1fQzSATBUhRCVDW

111 toolkit.print("No custom domains found.", bullet=False) 1QSTURVW

112 return 1QSTURVW

113 

114 selected_domain = _select_custom_domain( 1fzABhCD

115 toolkit, 

116 domains, 

117 prompt="Select the custom domain to remove:", 

118 ) 

119 toolkit.print_line() 1fzABhCD

120 else: 

121 selected_domain = _find_custom_domain(domains, domain) 1jJkanLEcoMFgpNGdlKmbqOHirPIe

122 if selected_domain is None: 1jJkanLEcoMFgpNGdlKmbqOHirPIe

123 toolkit.fail( 1JLMNKOP

124 "not_found", 

125 f"Custom domain {domain} not found.", 

126 hint=( 

127 "Run `fastapi cloud domains list` to see available " 

128 "custom domains." 

129 ), 

130 ) 

131 

132 assert selected_domain is not None 1jfkanzEcoAFgpBGdlhmbqCHirDIe

133 _print_removal_warning(toolkit, selected_domain) 1jfkanzEcoAFgpBGdlhmbqCHirDIe

134 

135 if not yes: 1jfkanzEcoAFgpBGdlhmbqCHirDIe

136 toolkit.print_line() 1jancogpdlbqire

137 should_remove = toolkit.confirm( 1jancogpdlbqire

138 f"Remove [bold]{selected_domain.name}[/bold]?", 

139 default=False, 

140 bullet=False, 

141 ) 

142 if not should_remove: 1jancogpdlbqire

143 toolkit.print_line() 1jnoplqr

144 toolkit.print("Removal cancelled.", bullet=False) 1jnoplqr

145 raise typer.Exit(0) 1jnoplqr

146 

147 toolkit.print_line() 1fkazEcAFgBGdhmbCHiDIe

148 with ( 1fkahmb

149 toolkit.progress( 

150 title="Removing custom domain", 

151 transient=True, 

152 ) as progress, 

153 client.handle_http_errors( 

154 progress, 

155 default_message=( 

156 "Error removing custom domain. Please try again later." 

157 ), 

158 not_found_message="Custom domain not found.", 

159 toolkit=toolkit, 

160 ), 

161 ): 

162 client.remove_custom_domain( 1fkazEcAFgBGdhmbCHiDIe

163 app_id=app_id, 

164 domain_id=selected_domain.id, 

165 ) 

166 

167 toolkit.success( 1fkazEcAFgBGdhmbCHiDIe

168 CustomDomainRemoveOutput( 

169 app_id=app_id, 

170 domain_id=selected_domain.id, 

171 name=selected_domain.name, 

172 ), 

173 render_output=_render_custom_domain_remove_output, 

174 )