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
« prev ^ index » next coverage.py v7.16.0, created at 2026-11-09 00:25 +0000
1from typing import Annotated, Any 1stuvwxy
3import typer 1stuvwxy
4from pydantic import BaseModel 1stuvwxy
5from rich_toolkit import RichToolkit 1stuvwxy
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
18class CustomDomainRemoveOutput(BaseModel): 1stuvwxy
19 app_id: str 1stuvwxy
20 domain_id: str 1stuvwxy
21 name: str 1stuvwxy
22 removed: bool = True 1stuvwxy
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
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 )
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.
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
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 )
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
105 toolkit.print_title("custom domains") 1jfJkQanzLEScoAMFTgpBNGUdlhKmRbqCOHVirDPIWe
106 toolkit.print_line() 1jfJkQanzLEScoAMFTgpBNGUdlhKmRbqCOHVirDPIWe
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
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 )
132 assert selected_domain is not None 1jfkanzEcoAFgpBGdlhmbqCHirDIe
133 _print_removal_warning(toolkit, selected_domain) 1jfkanzEcoAFgpBGdlhmbqCHirDIe
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
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 )
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 )