Coverage for src/fastapi_cloud_cli/commands/deployments.py: 100%
189 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
1import json 1abcdefg
2import logging 1abcdefg
3from typing import Annotated, Any 1abcdefg
5import typer 1abcdefg
6from httpx import HTTPError 1abcdefg
7from pydantic import BaseModel 1abcdefg
8from rich.markup import escape 1abcdefg
9from rich.table import Table 1abcdefg
10from rich.text import Text 1abcdefg
11from rich_toolkit import RichToolkit 1abcdefg
13from fastapi_cloud_cli.api import ( 1abcdefg
14 BUILD_FAILED_STATUSES,
15 TERMINAL_STATUSES,
16 APIClient,
17 BuildFailure,
18 BuildLogLineGeneric,
19 BuildLogLineMessage,
20 Deployment,
21 StreamLogError,
22 TooManyRetriesError,
23 get_http_error_code,
24 get_http_error_hint,
25 handle_http_error,
26)
27from fastapi_cloud_cli.commands._auth import UserCommand, get_user_command_context 1abcdefg
28from fastapi_cloud_cli.utils.apps import resolve_app_id_or_fail 1abcdefg
29from fastapi_cloud_cli.utils.cli import ( 1abcdefg
30 FastAPIRichToolkit,
31 get_details_table,
32)
33from fastapi_cloud_cli.utils.dates import format_last_updated 1abcdefg
34from fastapi_cloud_cli.utils.errors import ErrorCode 1abcdefg
35from fastapi_cloud_cli.utils.execution import JsonOutputOption 1abcdefg
37logger = logging.getLogger(__name__) 1abcdefg
39DEFAULT_LIMIT = 100 1abcdefg
40DEFAULT_OFFSET = 0 1abcdefg
43class DeploymentsListAPIResponse(BaseModel): 1abcdefg
44 data: list[Deployment] 1abcdefg
45 count: int 1abcdefg
48class DeploymentsListOutput(BaseModel): 1abcdefg
49 deployments: list[Deployment] 1abcdefg
50 total_count: int 1abcdefg
51 limit: int 1abcdefg
52 offset: int 1abcdefg
55class DeploymentGetOutput(BaseModel): 1abcdefg
56 deployment: Deployment 1abcdefg
59class BuildLogOutput(BaseModel): 1abcdefg
60 id: str | None = None 1abcdefg
61 message: str 1abcdefg
64class BuildLogsOutput(BaseModel): 1abcdefg
65 deployment_id: str 1abcdefg
66 failed: bool 1abcdefg
67 logs: list[BuildLogOutput] 1abcdefg
68 failure: BuildFailure | None = None 1abcdefg
71def _get_deployments( 1abcdefg
72 client: APIClient, *, app_id: str, limit: int, offset: int
73) -> DeploymentsListOutput:
74 response = client.get( 2lcmcmb-bncocjb.bpcqcnb/brcsckb:btcucob;bvcwcpb=bxcyclb?b
75 f"/apps/{app_id}/deployments/",
76 params={
77 "limit": limit,
78 "skip": offset,
79 },
80 )
81 response.raise_for_status() 2lcmcmb-bncocjb.bpcqcnb/brcsckb:btcucob;bvcwcpb=bxcyclb?b
83 data = DeploymentsListAPIResponse.model_validate(response.json()) 2lcmcmb-bncocjb.bpcqcnb/brcsckb:btcucob;bvcwcpb=bxcyclb?b
85 return DeploymentsListOutput( 2lcmcmb-bncocjb.bpcqcnb/brcsckb:btcucob;bvcwcpb=bxcyclb?b
86 deployments=data.data,
87 total_count=data.count,
88 limit=limit,
89 offset=offset,
90 )
93def _render_deployments_list_output( 2a b jbc d kbe f g lb
94 data: DeploymentsListOutput, toolkit: RichToolkit
95) -> None:
96 toolkit.print_title("deployments") 2mb-bjb.bnb/bkb:bob;bpb=blb?b
97 toolkit.print_line() 2mb-bjb.bnb/bkb:bob;bpb=blb?b
99 if not data.deployments: 2mb-bjb.bnb/bkb:bob;bpb=blb?b
100 toolkit.print("No deployments found.", bullet=False) 2-b.b/b:b;b=b?b
101 return 2-b.b/b:b;b=b?b
103 table = Table.grid(padding=(0, 2), pad_edge=False) 2mbjbnbkbobpblb
104 table.add_column("ID", no_wrap=True) 2mbjbnbkbobpblb
105 table.add_column("Status", no_wrap=True) 2mbjbnbkbobpblb
106 table.add_column("Created", style="dim", no_wrap=True) 2mbjbnbkbobpblb
107 table.add_row("[bold]ID[/bold]", "[bold]Status[/bold]", "[bold]Created[/bold]") 2mbjbnbkbobpblb
108 table.add_row("", "", "") 2mbjbnbkbobpblb
110 for deployment in data.deployments: 2mbjbnbkbobpblb
111 table.add_row( 2mbjbnbkbobpblb
112 deployment.id,
113 deployment.status.value,
114 Text(format_last_updated(deployment.created_at)),
115 )
117 toolkit.print(table, bullet=False) 2mbjbnbkbobpblb
120def _render_deployment_get_output( 2a b ecc d fce f g gc
121 data: DeploymentGetOutput, toolkit: FastAPIRichToolkit
122) -> None:
123 deployment = data.deployment 2qbhcsbectbjcubfcrbicvbkcwbgc
125 toolkit.print_title("deployment") 2qbhcsbectbjcubfcrbicvbkcwbgc
126 toolkit.print_line() 2qbhcsbectbjcubfcrbicvbkcwbgc
128 toolkit.print(f"[bold]{deployment.id}[/bold]", emoji="🚀") 2qbhcsbectbjcubfcrbicvbkcwbgc
129 toolkit.print_line() 2qbhcsbectbjcubfcrbicvbkcwbgc
130 toolkit.print( 2qbhcsbectbjcubfcrbicvbkcwbgc
131 get_details_table(
132 [
133 ("app id", deployment.app_id),
134 ("slug", deployment.slug),
135 ("status", deployment.status.value),
136 ("created", format_last_updated(deployment.created_at)),
137 (
138 "url",
139 deployment.url
140 if deployment.url is not None
141 else Text("-", style="dim"),
142 ),
143 (
144 "dashboard",
145 Text(
146 deployment.dashboard_url,
147 style=f"link {deployment.dashboard_url}",
148 )
149 if deployment.dashboard_url is not None
150 else Text("-", style="dim"),
151 ),
152 ]
153 )
154 )
156 if deployment.failure is not None: 2qbhcsbectbjcubfcrbicvbkcwbgc
157 toolkit.print_line() 2qbsbtbubrbvbwb
158 _print_deployment_failure(toolkit, deployment.failure) 2qbsbtbubrbvbwb
161def _print_deployment_failure( 1abcdefg
162 toolkit: FastAPIRichToolkit, failure: BuildFailure
163) -> None:
164 toolkit.print("This deployment failed with the following error:", emoji="🚨") 2v qb0 h w sb1 i x tb2 j y ub3 k z rb4 l A vb5 m B wb6 n
165 toolkit.print_line() 2v qb0 h w sb1 i x tb2 j y ub3 k z rb4 l A vb5 m B wb6 n
166 toolkit.print(Text(failure.error_title, style="bold")) 2v qb0 h w sb1 i x tb2 j y ub3 k z rb4 l A vb5 m B wb6 n
167 toolkit.print_line() 2v qb0 h w sb1 i x tb2 j y ub3 k z rb4 l A vb5 m B wb6 n
168 toolkit.print(Text(failure.error_message)) 2v qb0 h w sb1 i x tb2 j y ub3 k z rb4 l A vb5 m B wb6 n
169 if failure.error_hint: 2v qb0 h w sb1 i x tb2 j y ub3 k z rb4 l A vb5 m B wb6 n
170 toolkit.print_line() 2v qbh w sbi x tbj y ubk z rbl A vbm B wbn
171 toolkit.print_hint(escape(failure.error_hint)) 2v qbh w sbi x tbj y ubk z rbl A vbm B wbn
174def _print_build_log_json( 1abcdefg
175 deployment_id: str,
176 record_type: str,
177 *,
178 log_id: str | None,
179 message: str | None = None,
180 failure: BuildFailure | None = None,
181) -> None:
182 record = { 2= ? # @ [ $ ] ^ % _ ` ' { | ( } ~ ) abbb*
183 "type": record_type,
184 "deployment_id": deployment_id,
185 "id": log_id,
186 "message": message,
187 "failure": failure.model_dump(mode="json") if failure is not None else None,
188 }
190 typer.echo( 2= ? # @ [ $ ] ^ % _ ` ' { | ( } ~ ) abbb*
191 json.dumps(
192 {key: value for key, value in record.items() if value is not None},
193 separators=(",", ":"),
194 )
195 )
198BUILD_LOG_BULLET = "[dim]▕[/dim]" 1abcdefg
201def _print_build_log_line(toolkit: RichToolkit, message: str) -> None: 1abcdefg
202 text = Text.from_ansi(message.rstrip()) 1o7Jh+QpXKi,Rq8Lj-SrYMk.Ts9Nl/Ut!Om:VuZPn;W
204 if not text.plain.strip(): 1o7Jh+QpXKi,Rq8Lj-SrYMk.Ts9Nl/Ut!Om:VuZPn;W
205 # Keep the log marker when the style would otherwise omit an empty line.
206 text = Text("\u200b") 1hijklmn
208 toolkit.print(text, emoji=BUILD_LOG_BULLET) 1o7Jh+QpXKi,Rq8Lj-SrYMk.Ts9Nl/Ut!Om:VuZPn;W
211def _render_build_logs_output( 1abXcdYefgZ
212 data: BuildLogsOutput, toolkit: FastAPIRichToolkit
213) -> None:
214 for log in data.logs: 2C v o 0 7 EbJ h D w p 1 X FbK i E x q 2 8 GbL j F y r 3 Y HbM k G z s 4 9 IbN l H A t 5 ! JbO m I B u 6 Z KbP n
215 _print_build_log_line(toolkit, log.message) 1o7JhpXKiq8LjrYMks9Nlt!OmuZPn
217 if data.failure: 2C v o 0 7 EbJ h D w p 1 X FbK i E x q 2 8 GbL j F y r 3 Y HbM k G z s 4 9 IbN l H A t 5 ! JbO m I B u 6 Z KbP n
218 if data.logs: 1v0hw1ix2jy3kz4lA5mB6n
219 toolkit.print_line() 1hijklmn
221 _print_deployment_failure(toolkit, data.failure) 1v0hw1ix2jy3kz4lA5mB6n
222 elif data.failed: 2C o 7 EbJ D p X FbK E q 8 GbL F r Y HbM G s 9 IbN H t ! JbO I u Z KbP
223 toolkit.print_line() 1oJpKqLrMsNtOuP
224 toolkit.print_error("Build failed.") 1oJpKqLrMsNtOuP
225 elif not data.logs: 2C 7 EbD X FbE 8 GbF Y HbG 9 IbH ! JbI Z Kb
226 toolkit.print("No build logs found.") 2C EbD FbE GbF HbG IbH JbI Kb
229def _stream_build_logs( 1abcdefg
230 toolkit: FastAPIRichToolkit,
231 client: APIClient,
232 deployment_id: str,
233) -> bool:
234 deployment = client.get_deployment(deployment_id) 2C Lb@bxbcbMbv o h [b= ? + # Q D Nb]bybdbObw p i ^b@ [ , $ R E Pb_bzbebQbx q j `b] ^ - % S F Rb{bAbfbSby r k |b_ ` . ' T G Tb}bBbgbUbz s l ~b{ | / ( U H VbacCbhbWbA t m bc} ~ : ) V I XbccDbibYbB u n dcabbb; * W
235 terminal_log: BuildLogLineGeneric | None = None 2C LbxbcbMbv o h [b= ? + # Q D NbybdbObw p i ^b@ [ , $ R E PbzbebQbx q j `b] ^ - % S F RbAbfbSby r k |b_ ` . ' T G TbBbgbUbz s l ~b{ | / ( U H VbCbhbWbA t m bc} ~ : ) V I XbDbibYbB u n dcabbb; * W
237 for log in client.stream_build_logs( 2C LbxbcbMbv o h [b= ? + # Q D NbybdbObw p i ^b@ [ , $ R E PbzbebQbx q j `b] ^ - % S F RbAbfbSby r k |b_ ` . ' T G TbBbgbUbz s l ~b{ | / ( U H VbCbhbWbA t m bc} ~ : ) V I XbDbibYbB u n dcabbb; * W
238 deployment_id, follow=deployment.status not in TERMINAL_STATUSES
239 ):
240 if isinstance(log, BuildLogLineMessage): 2C o h = ? + # Q D p i @ [ , $ R E q j ] ^ - % S F r k _ ` . ' T G s l { | / ( U H t m } ~ : ) V I u n abbb; * W
241 if toolkit.mode == "json": 2o h = ? + # Q p i @ [ , $ R q j ] ^ - % S r k _ ` . ' T s l { | / ( U t m } ~ : ) V u n abbb; * W
242 _print_build_log_json( 2= ? # @ [ $ ] ^ % _ ` ' { | ( } ~ ) abbb*
243 deployment_id,
244 "log",
245 log_id=log.id,
246 message=log.message,
247 )
248 else:
249 _print_build_log_line(toolkit, log.message) 1oh+Qpi,Rqj-Srk.Tsl/Utm:Vun;W
250 else:
251 terminal_log = log 2C o h = ? + # Q D p i @ [ , $ R E q j ] ^ - % S F r k _ ` . ' T G s l { | / ( U H t m } ~ : ) V I u n abbb; * W
253 if deployment.status not in TERMINAL_STATUSES: 2C v o h = ? + # Q D w p i @ [ , $ R E x q j ] ^ - % S F y r k _ ` . ' T G z s l { | / ( U H A t m } ~ : ) V I B u n abbb; * W
254 deployment = client.get_deployment(deployment_id) 2o h ? + # Q p i [ , $ R q j ^ - % S r k ` . ' T s l | / ( U t m ~ : ) V u n bb; * W
256 failed = ( 2C v o h = ? + # Q D w p i @ [ , $ R E x q j ] ^ - % S F y r k _ ` . ' T G z s l { | / ( U H A t m } ~ : ) V I B u n abbb; * W
257 terminal_log is not None and terminal_log.type == "failed"
258 ) or deployment.status in BUILD_FAILED_STATUSES
260 if toolkit.mode == "json": 2C v o h = ? + # Q D w p i @ [ , $ R E x q j ] ^ - % S F y r k _ ` . ' T G z s l { | / ( U H A t m } ~ : ) V I B u n abbb; * W
261 if failed or terminal_log is not None or deployment.status in TERMINAL_STATUSES: 2= ? # @ [ $ ] ^ % _ ` ' { | ( } ~ ) abbb*
262 _print_build_log_json( 2= ? # @ [ $ ] ^ % _ ` ' { | ( } ~ ) abbb*
263 deployment_id,
264 "failed" if failed else "complete",
265 log_id=terminal_log.id if terminal_log is not None else None,
266 failure=deployment.failure,
267 )
268 elif deployment.failure is not None: 1Cvoh+QDwpi,RExqj-SFyrk.TGzsl/UHAtm:VIBun;W
269 toolkit.print_line() 1vhwixjykzlAmBn
270 _print_deployment_failure(toolkit, deployment.failure) 1vhwixjykzlAmBn
271 elif failed: 1Co+QDp,REq-SFr.TGs/UHt:VIu;W
272 toolkit.print_line() 1oQpRqSrTsUtVuW
273 toolkit.print_error("Build failed.") 1oQpRqSrTsUtVuW
275 return failed 2C v o h = ? + # Q D w p i @ [ , $ R E x q j ] ^ - % S F y r k _ ` . ' T G z s l { | / ( U H A t m } ~ : ) V I B u n abbb; * W
278def _fetch_build_logs(client: APIClient, deployment_id: str) -> BuildLogsOutput: 1abcdefg
279 logs: list[BuildLogOutput] = [] 2C v o Zb0 %b7 Eb0bJ h D w p 1b1 'bX Fb2bK i E x q 3b2 (b8 Gb4bL j F y r 5b3 )bY Hb6bM k G z s 7b4 *b9 Ib8bN l H A t 9b5 +b! Jb!bO m I B u #b6 ,bZ Kb$bP n
280 failed = False 2C v o Zb0 %b7 Eb0bJ h D w p 1b1 'bX Fb2bK i E x q 3b2 (b8 Gb4bL j F y r 5b3 )bY Hb6bM k G z s 7b4 *b9 Ib8bN l H A t 9b5 +b! Jb!bO m I B u #b6 ,bZ Kb$bP n
282 for log in client.stream_build_logs(deployment_id, follow=False): 2C v o Zb0 %b7 Eb0bJ h D w p 1b1 'bX Fb2bK i E x q 3b2 (b8 Gb4bL j F y r 5b3 )bY Hb6bM k G z s 7b4 *b9 Ib8bN l H A t 9b5 +b! Jb!bO m I B u #b6 ,bZ Kb$bP n
283 if isinstance(log, BuildLogLineMessage): 2C o Zb%b7 0bJ h D p 1b'bX 2bK i E q 3b(b8 4bL j F r 5b)bY 6bM k G s 7b*b9 8bN l H t 9b+b! !bO m I u #b,bZ $bP n
284 logs.append(BuildLogOutput(id=log.id, message=log.message)) 2o Zb%b7 0bJ h p 1b'bX 2bK i q 3b(b8 4bL j r 5b)bY 6bM k s 7b*b9 8bN l t 9b+b! !bO m u #b,bZ $bP n
286 elif log.type == "failed": 2C o Zb7 0bJ h D p 1bX 2bK i E q 3b8 4bL j F r 5bY 6bM k G s 7b9 8bN l H t 9b! !bO m I u #bZ $bP n
287 failed = True 2o Zb0bJ h p 1b2bK i q 3b4bL j r 5b6bM k s 7b8bN l t 9b!bO m u #b$bP n
289 deployment = client.get_deployment(deployment_id) 2C v o Zb0 %b7 Eb0bJ h D w p 1b1 'bX Fb2bK i E x q 3b2 (b8 Gb4bL j F y r 5b3 )bY Hb6bM k G z s 7b4 *b9 Ib8bN l H A t 9b5 +b! Jb!bO m I B u #b6 ,bZ Kb$bP n
290 return BuildLogsOutput( 2C v o Zb0 %b7 Eb0bJ h D w p 1b1 'bX Fb2bK i E x q 3b2 (b8 Gb4bL j F y r 5b3 )bY Hb6bM k G z s 7b4 *b9 Ib8bN l H A t 9b5 +b! Jb!bO m I B u #b6 ,bZ Kb$bP n
291 deployment_id=deployment_id,
292 failed=failed or deployment.status in BUILD_FAILED_STATUSES,
293 logs=logs,
294 failure=deployment.failure,
295 )
298def _handle_build_log_error( 1abcdefg
299 toolkit: FastAPIRichToolkit,
300 error: StreamLogError,
301) -> None:
302 hint: str | None = None 2xbcbMbybdbObzbebQbAbfbSbBbgbUbCbhbWbDbibYb
304 if error.status_code == 404: 2xbcbMbybdbObzbebQbAbfbSbBbgbUbCbhbWbDbibYb
305 code: ErrorCode = "not_found" 2MbObQbSbUbWbYb
306 message = "Deployment not found." 2MbObQbSbUbWbYb
308 elif isinstance(error.__cause__, HTTPError): 2xbcbybdbzbebAbfbBbgbCbhbDbib
309 code = get_http_error_code(error.__cause__) 2cbdbebfbgbhbib
310 message = handle_http_error(error.__cause__) 2cbdbebfbgbhbib
311 hint = get_http_error_hint(code) 2cbdbebfbgbhbib
313 else:
314 code = "api_error" 2xbybzbAbBbCbDb
315 message = f"Error streaming build logs: {error}" 2xbybzbAbBbCbDb
317 toolkit.fail( 2xbcbMbybdbObzbebQbAbfbSbBbgbUbCbhbWbDbibYb
318 code,
319 message,
320 hint=hint,
321 render_output=_render_build_log_error,
322 )
325def _render_build_log_error( 1abcdefg
326 toolkit: FastAPIRichToolkit,
327 *,
328 code: ErrorCode,
329 message: str,
330 hint: str,
331) -> None:
332 toolkit.print_error(message) 2LbxbcbMbNbybdbObPbzbebQbRbAbfbSbTbBbgbUbVbCbhbWbXbDbibYb
333 if hint: 2LbxbcbMbNbybdbObPbzbebQbRbAbfbSbTbBbgbUbVbCbhbWbXbDbibYb
334 toolkit.print_line() 2LbcbNbdbPbebRbfbTbgbVbhbXbib
335 toolkit.print_hint(hint) 2LbcbNbdbPbebRbfbTbgbVbhbXbib
338deployments_app = typer.Typer( 1abcdefg
339 no_args_is_help=True,
340 help="Manage the deployments of your app.",
341)
344@deployments_app.command("get", cls=UserCommand) 1abcdefg
345def get_deployment( 2a b Ucc d Vce f g Wc
346 ctx: typer.Context,
347 deployment_id: Annotated[
348 str,
349 typer.Argument(
350 help="ID of the deployment to return.",
351 ),
352 ],
353 app_id: Annotated[
354 str | None,
355 typer.Option(
356 "--app-id",
357 help="ID of the app that owns the deployment.",
358 ),
359 ] = None,
360 json_output: JsonOutputOption = False,
361) -> Any:
362 """
363 Get a FastAPI Cloud deployment by ID.
364 """
366 toolkit = get_user_command_context(ctx).toolkit 2zcqbAcBchcXcFcsbGcHcecYcIctbJcKcjcZcLcubMcNcfc0cCcrbDcEcic1cOcvbPcQckc2cRcwbScTcgc3c
368 resolve_app_id_or_fail(toolkit, app_id=app_id) 2zcqbAcBchcXcFcsbGcHcecYcIctbJcKcjcZcLcubMcNcfc0cCcrbDcEcic1cOcvbPcQckc2cRcwbScTcgc3c
370 with APIClient() as client: 2zcqbAcBchcFcsbGcHcecIctbJcKcjcLcubMcNcfcCcrbDcEcicOcvbPcQckcRcwbScTcgc
371 with ( 2zcqbAcBchcCcrbDcEcic
372 toolkit.progress(
373 title="Fetching deployment",
374 transient=True,
375 ) as progress,
376 client.handle_http_errors(
377 progress,
378 default_message="Error fetching deployment. Please try again later.",
379 not_found_message="Deployment not found.",
380 toolkit=toolkit,
381 ),
382 ):
383 deployment = client.get_deployment(deployment_id) 2zcqbAcBchcFcsbGcHcecIctbJcKcjcLcubMcNcfcCcrbDcEcicOcvbPcQckcRcwbScTcgc
385 toolkit.success( 2zcqbAcBchcFcsbGcHcecIctbJcKcjcLcubMcNcfcCcrbDcEcicOcvbPcQckcRcwbScTcgc
386 DeploymentGetOutput(deployment=deployment),
387 render_output=_render_deployment_get_output,
388 )
391@deployments_app.command("build-logs", cls=UserCommand) 1abcdefg
392def build_logs( 2a b Ucc d Vce f g Wc
393 ctx: typer.Context,
394 deployment_id: Annotated[
395 str,
396 typer.Argument(
397 help="ID of the deployment whose build logs should be returned.",
398 ),
399 ],
400 follow: Annotated[
401 bool,
402 typer.Option(
403 "--follow/--no-follow",
404 "-f",
405 help="Stream build logs until the build reaches a terminal state.",
406 ),
407 ] = True,
408 json_output: JsonOutputOption = False,
409) -> None:
410 """
411 Stream or fetch build logs for a FastAPI Cloud deployment.
412 """
414 toolkit = get_user_command_context(ctx).toolkit 2C Lb@bxbcbMbv o Zb0 %b7 Eb0bJ h [b= ? + # Q D Nb]bybdbObw p 1b1 'bX Fb2bK i ^b@ [ , $ R E Pb_bzbebQbx q 3b2 (b8 Gb4bL j `b] ^ - % S F Rb{bAbfbSby r 5b3 )bY Hb6bM k |b_ ` . ' T G Tb}bBbgbUbz s 7b4 *b9 Ib8bN l ~b{ | / ( U H VbacCbhbWbA t 9b5 +b! Jb!bO m bc} ~ : ) V I XbccDbibYbB u #b6 ,bZ Kb$bP n dcabbb; * W
416 if follow: 2C Lb@bxbcbMbv o Zb0 %b7 Eb0bJ h [b= ? + # Q D Nb]bybdbObw p 1b1 'bX Fb2bK i ^b@ [ , $ R E Pb_bzbebQbx q 3b2 (b8 Gb4bL j `b] ^ - % S F Rb{bAbfbSby r 5b3 )bY Hb6bM k |b_ ` . ' T G Tb}bBbgbUbz s 7b4 *b9 Ib8bN l ~b{ | / ( U H VbacCbhbWbA t 9b5 +b! Jb!bO m bc} ~ : ) V I XbccDbibYbB u #b6 ,bZ Kb$bP n dcabbb; * W
417 toolkit.print( 2C Lb@bxbcbMbv o h [b= ? + # Q D Nb]bybdbObw p i ^b@ [ , $ R E Pb_bzbebQbx q j `b] ^ - % S F Rb{bAbfbSby r k |b_ ` . ' T G Tb}bBbgbUbz s l ~b{ | / ( U H VbacCbhbWbA t m bc} ~ : ) V I XbccDbibYbB u n dcabbb; * W
418 f"Streaming build logs for [bold]{deployment_id}[/bold]...",
419 emoji="📡",
420 )
421 else:
422 toolkit.print( 2C v o Zb0 %b7 Eb0bJ h D w p 1b1 'bX Fb2bK i E x q 3b2 (b8 Gb4bL j F y r 5b3 )bY Hb6bM k G z s 7b4 *b9 Ib8bN l H A t 9b5 +b! Jb!bO m I B u #b6 ,bZ Kb$bP n
423 f"Fetching build logs for [bold]{deployment_id}[/bold]...",
424 emoji="📜",
425 )
426 toolkit.print_line() 2C Lb@bxbcbMbv o Zb0 %b7 Eb0bJ h [b= ? + # Q D Nb]bybdbObw p 1b1 'bX Fb2bK i ^b@ [ , $ R E Pb_bzbebQbx q 3b2 (b8 Gb4bL j `b] ^ - % S F Rb{bAbfbSby r 5b3 )bY Hb6bM k |b_ ` . ' T G Tb}bBbgbUbz s 7b4 *b9 Ib8bN l ~b{ | / ( U H VbacCbhbWbA t 9b5 +b! Jb!bO m bc} ~ : ) V I XbccDbibYbB u #b6 ,bZ Kb$bP n dcabbb; * W
428 try: 2C Lb@bxbcbMbv o Zb0 %b7 Eb0bJ h [b= ? + # Q D Nb]bybdbObw p 1b1 'bX Fb2bK i ^b@ [ , $ R E Pb_bzbebQbx q 3b2 (b8 Gb4bL j `b] ^ - % S F Rb{bAbfbSby r 5b3 )bY Hb6bM k |b_ ` . ' T G Tb}bBbgbUbz s 7b4 *b9 Ib8bN l ~b{ | / ( U H VbacCbhbWbA t 9b5 +b! Jb!bO m bc} ~ : ) V I XbccDbibYbB u #b6 ,bZ Kb$bP n dcabbb; * W
429 with APIClient() as client: 2C Lb@bxbcbMbv o Zb0 %b7 Eb0bJ h [b= ? + # Q D Nb]bybdbObw p 1b1 'bX Fb2bK i ^b@ [ , $ R E Pb_bzbebQbx q 3b2 (b8 Gb4bL j `b] ^ - % S F Rb{bAbfbSby r 5b3 )bY Hb6bM k |b_ ` . ' T G Tb}bBbgbUbz s 7b4 *b9 Ib8bN l ~b{ | / ( U H VbacCbhbWbA t 9b5 +b! Jb!bO m bc} ~ : ) V I XbccDbibYbB u #b6 ,bZ Kb$bP n dcabbb; * W
430 if follow: 2C Lb@bxbcbMbv o Zb0 %b7 Eb0bJ h [b= ? + # Q D Nb]bybdbObw p 1b1 'bX Fb2bK i ^b@ [ , $ R E Pb_bzbebQbx q 3b2 (b8 Gb4bL j `b] ^ - % S F Rb{bAbfbSby r 5b3 )bY Hb6bM k |b_ ` . ' T G Tb}bBbgbUbz s 7b4 *b9 Ib8bN l ~b{ | / ( U H VbacCbhbWbA t 9b5 +b! Jb!bO m bc} ~ : ) V I XbccDbibYbB u #b6 ,bZ Kb$bP n dcabbb; * W
431 failed = _stream_build_logs(toolkit, client, deployment_id) 2C Lb@bxbcbMbv o h [b= ? + # Q D Nb]bybdbObw p i ^b@ [ , $ R E Pb_bzbebQbx q j `b] ^ - % S F Rb{bAbfbSby r k |b_ ` . ' T G Tb}bBbgbUbz s l ~b{ | / ( U H VbacCbhbWbA t m bc} ~ : ) V I XbccDbibYbB u n dcabbb; * W
432 else:
433 result = _fetch_build_logs(client, deployment_id) 2C v o Zb0 %b7 Eb0bJ h D w p 1b1 'bX Fb2bK i E x q 3b2 (b8 Gb4bL j F y r 5b3 )bY Hb6bM k G z s 7b4 *b9 Ib8bN l H A t 9b5 +b! Jb!bO m I B u #b6 ,bZ Kb$bP n
434 toolkit.success(result, render_output=_render_build_logs_output) 2C v o Zb0 %b7 Eb0bJ h D w p 1b1 'bX Fb2bK i E x q 3b2 (b8 Gb4bL j F y r 5b3 )bY Hb6bM k G z s 7b4 *b9 Ib8bN l H A t 9b5 +b! Jb!bO m I B u #b6 ,bZ Kb$bP n
435 failed = result.failed 2C v o Zb0 %b7 Eb0bJ h D w p 1b1 'bX Fb2bK i E x q 3b2 (b8 Gb4bL j F y r 5b3 )bY Hb6bM k G z s 7b4 *b9 Ib8bN l H A t 9b5 +b! Jb!bO m I B u #b6 ,bZ Kb$bP n
437 except KeyboardInterrupt: # pragma: no cover 2Lb@bxbcbMb[bNb]bybdbOb^bPb_bzbebQb`bRb{bAbfbSb|bTb}bBbgbUb~bVbacCbhbWbbcXbccDbibYbdc
438 toolkit.print_line() 2[b^b`b|b~bbcdc
439 return 2[b^b`b|b~bbcdc
440 except StreamLogError as e: 2Lb@bxbcbMbNb]bybdbObPb_bzbebQbRb{bAbfbSbTb}bBbgbUbVbacCbhbWbXbccDbibYb
441 _handle_build_log_error(toolkit, e) 2xbcbMbybdbObzbebQbAbfbSbBbgbUbCbhbWbDbibYb
442 except HTTPError as e: 2Lb@bNb]bPb_bRb{bTb}bVbacXbcc
443 code = get_http_error_code(e) 2@b]b_b{b}baccc
444 toolkit.fail( 2@b]b_b{b}baccc
445 code,
446 handle_http_error(e, not_found_message="Deployment not found."),
447 hint=get_http_error_hint(code),
448 render_output=_render_build_log_error,
449 )
451 except (TooManyRetriesError, TimeoutError): 2LbNbPbRbTbVbXb
452 message = "Lost connection to build log stream. Please try again later." 2LbNbPbRbTbVbXb
453 toolkit.fail( 2LbNbPbRbTbVbXb
454 "network_error",
455 message,
456 hint="Please try again later.",
457 render_output=_render_build_log_error,
458 )
460 if failed: 2C v o Zb0 %b7 Eb0bJ h = ? + # Q D w p 1b1 'bX Fb2bK i @ [ , $ R E x q 3b2 (b8 Gb4bL j ] ^ - % S F y r 5b3 )bY Hb6bM k _ ` . ' T G z s 7b4 *b9 Ib8bN l { | / ( U H A t 9b5 +b! Jb!bO m } ~ : ) V I B u #b6 ,bZ Kb$bP n abbb; * W
461 raise typer.Exit(1) 2v o Zb0 0bJ h = # Q w p 1b1 2bK i @ $ R x q 3b2 4bL j ] % S y r 5b3 6bM k _ ' T z s 7b4 8bN l { ( U A t 9b5 !bO m } ) V B u #b6 $bP n ab* W
464@deployments_app.command("list", cls=UserCommand) 1abcdefg
465def list_deployments( 2a b Ucc d Vce f g Wc
466 ctx: typer.Context,
467 app_id: Annotated[
468 str | None,
469 typer.Option(
470 "--app-id",
471 help="ID of the app whose deployments should be listed.",
472 ),
473 ] = None,
474 limit: Annotated[
475 int,
476 typer.Option(
477 "--limit",
478 help="Maximum number of deployments to return.",
479 min=1,
480 ),
481 ] = DEFAULT_LIMIT,
482 offset: Annotated[
483 int,
484 typer.Option(
485 "--offset",
486 help="Offset into the deployment result set.",
487 min=0,
488 ),
489 ] = DEFAULT_OFFSET,
490 json_output: JsonOutputOption = False,
491) -> Any:
492 """
493 List FastAPI Cloud deployments for an app.
494 """
496 toolkit = get_user_command_context(ctx).toolkit 2lcmcmb-b4cncocjb.b5cpcqcnb/b6crcsckb:b7ctcucob;b8cvcwcpb=b9cxcyclb?b!c
498 target_app_id = resolve_app_id_or_fail(toolkit, app_id=app_id) 2lcmcmb-b4cncocjb.b5cpcqcnb/b6crcsckb:b7ctcucob;b8cvcwcpb=b9cxcyclb?b!c
500 with APIClient() as client: 2lcmcmb-bncocjb.bpcqcnb/brcsckb:btcucob;bvcwcpb=bxcyclb?b
501 with toolkit.progress( 2lcmcmb-bncocjb.bpcqcnb/brcsckb:btcucob;bvcwcpb=bxcyclb?b
502 title="Fetching deployments",
503 transient=True,
504 ) as progress:
505 with client.handle_http_errors( 2lcmcmb-bncocjb.bpcqcnb/brcsckb:btcucob;bvcwcpb=bxcyclb?b
506 progress,
507 default_message="Error fetching deployments. Please try again later.",
508 not_found_message="App not found.",
509 toolkit=toolkit,
510 ):
511 result = _get_deployments( 2lcmcmb-bncocjb.bpcqcnb/brcsckb:btcucob;bvcwcpb=bxcyclb?b
512 client,
513 app_id=target_app_id,
514 limit=limit,
515 offset=offset,
516 )
518 toolkit.success(result, render_output=_render_deployments_list_output) 2lcmcmb-bncocjb.bpcqcnb/brcsckb:btcucob;bvcwcpb=bxcyclb?b