Skip to content

Commit cf5c9da

Browse files
committed
✨ Improve error handling
1 parent a5e9879 commit cf5c9da

File tree

2 files changed

+80
-98
lines changed

2 files changed

+80
-98
lines changed

src/fastapi_cli/commands/deploy.py

Lines changed: 43 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@
66
from enum import Enum
77
from itertools import cycle
88
from pathlib import Path
9-
from typing import Any, Dict, List, Optional, Union
9+
from typing import Any, Dict, List, Union
1010

1111
import rignore
1212
import typer
13-
from httpx import Client, HTTPError, ReadTimeout
13+
from httpx import Client
1414
from pydantic import BaseModel
1515
from rich_toolkit import RichToolkit
1616
from rich_toolkit.menu import Option
@@ -20,7 +20,7 @@
2020
from fastapi_cli.utils.api import APIClient
2121
from fastapi_cli.utils.apps import AppConfig, get_app_config, write_app_config
2222
from fastapi_cli.utils.auth import is_logged_in
23-
from fastapi_cli.utils.cli import get_rich_toolkit
23+
from fastapi_cli.utils.cli import get_rich_toolkit, handle_http_errors
2424

2525
logger = logging.getLogger(__name__)
2626

@@ -66,20 +66,14 @@ class Team(BaseModel):
6666
name: str
6767

6868

69-
def _get_teams() -> Optional[List[Team]]:
69+
def _get_teams() -> List[Team]:
7070
with APIClient() as client:
71-
try:
72-
response = client.get("/teams/")
73-
response.raise_for_status()
74-
except (HTTPError, ReadTimeout) as e:
75-
logger.debug(e)
76-
return None
71+
response = client.get("/teams/")
72+
response.raise_for_status()
7773

7874
data = response.json()["data"]
7975

80-
teams = [Team.model_validate(team) for team in data]
81-
82-
return teams
76+
return [Team.model_validate(team) for team in data]
8377

8478

8579
class CreateAppResponse(BaseModel):
@@ -89,19 +83,14 @@ class CreateAppResponse(BaseModel):
8983
slug: str
9084

9185

92-
def _create_app(team_slug: str, app_name: str) -> Optional[CreateAppResponse]:
86+
def _create_app(team_slug: str, app_name: str) -> CreateAppResponse:
9387
with APIClient() as client:
94-
try:
95-
response = client.post(
96-
"/apps/",
97-
json={"name": app_name, "team_slug": team_slug},
98-
)
99-
except (HTTPError, ReadTimeout) as e:
100-
logger.debug(e)
101-
return None
88+
response = client.post(
89+
"/apps/",
90+
json={"name": app_name, "team_slug": team_slug},
91+
)
10292

103-
if response.status_code != 201:
104-
return None
93+
response.raise_for_status()
10594

10695
return CreateAppResponse.model_validate(response.json())
10796

@@ -121,16 +110,10 @@ class CreateDeploymentResponse(BaseModel):
121110
status: DeploymentStatus
122111

123112

124-
def _create_deployment(app_id: str) -> Optional[CreateDeploymentResponse]:
113+
def _create_deployment(app_id: str) -> CreateDeploymentResponse:
125114
with APIClient() as client:
126-
try:
127-
response = client.post(f"/apps/{app_id}/deployments/")
128-
except (HTTPError, ReadTimeout) as e:
129-
logger.debug(e)
130-
return None
131-
132-
if response.status_code != 201:
133-
return None
115+
response = client.post(f"/apps/{app_id}/deployments/")
116+
response.raise_for_status()
134117

135118
return CreateDeploymentResponse.model_validate(response.json())
136119

@@ -140,32 +123,21 @@ class RequestUploadResponse(BaseModel):
140123
fields: Dict[str, str]
141124

142125

143-
def _upload_deployment(deployment_id: str, archive_path: Path) -> bool:
126+
def _upload_deployment(deployment_id: str, archive_path: Path) -> None:
144127
with APIClient() as client:
145-
try:
146-
response = client.post(f"/deployments/{deployment_id}/upload")
147-
148-
response.raise_for_status()
149-
except (HTTPError, ReadTimeout) as e:
150-
logger.debug(e)
151-
return False
128+
response = client.post(f"/deployments/{deployment_id}/upload")
129+
response.raise_for_status()
152130

153131
upload_data = RequestUploadResponse.model_validate(response.json())
154132

155133
with Client() as client:
156-
try:
157-
response = client.post(
158-
upload_data.url,
159-
data=upload_data.fields,
160-
files={"file": archive_path.open("rb")},
161-
)
162-
163-
response.raise_for_status()
164-
except (HTTPError, ReadTimeout) as e:
165-
logger.debug(e)
166-
return False
134+
response = client.post(
135+
upload_data.url,
136+
data=upload_data.fields,
137+
files={"file": archive_path.open("rb")},
138+
)
167139

168-
return True
140+
response.raise_for_status()
169141

170142

171143
class DeploymentResponse(BaseModel):
@@ -176,19 +148,13 @@ class DeploymentResponse(BaseModel):
176148
url: str
177149

178150

179-
def _get_deployment(app_id: str, deployment_id: str) -> Optional[DeploymentResponse]:
151+
def _get_deployment(app_id: str, deployment_id: str) -> DeploymentResponse:
180152
with APIClient() as client:
181-
try:
182-
response = client.get(f"/apps/{app_id}/deployments/{deployment_id}")
183-
except (HTTPError, ReadTimeout) as e:
184-
logger.debug(e)
185-
return None
153+
response = client.get(f"/apps/{app_id}/deployments/{deployment_id}")
154+
response.raise_for_status()
186155

187156
data = response.json()
188157

189-
if response.status_code != 200:
190-
return None
191-
192158
return DeploymentResponse.model_validate(data)
193159

194160

@@ -220,12 +186,10 @@ def _configure_app(toolkit: RichToolkit, path_to_deploy: Path) -> AppConfig:
220186
toolkit.print_line()
221187

222188
with toolkit.progress("Fetching teams...") as progress:
223-
teams = _get_teams()
224-
225-
if teams is None:
226-
progress.set_error("Error fetching teams. Please try again later.")
227-
228-
raise typer.Exit(1)
189+
with handle_http_errors(
190+
progress, message="Error fetching teams. Please try again later."
191+
):
192+
teams = _get_teams()
229193

230194
toolkit.print_line()
231195

@@ -246,12 +210,8 @@ def _configure_app(toolkit: RichToolkit, path_to_deploy: Path) -> AppConfig:
246210
toolkit.print_line()
247211

248212
with toolkit.progress(title="Creating app...") as progress:
249-
app_data = _create_app(team_slug, app_name)
250-
251-
if app_data is None:
252-
progress.set_error("Error creating app. Please try again later.")
253-
254-
raise typer.Exit(1)
213+
with handle_http_errors(progress):
214+
app_data = _create_app(team_slug, app_name)
255215

256216
progress.log(f"App created successfully! App slug: {app_data.slug}")
257217

@@ -278,14 +238,8 @@ def _wait_for_deployment(
278238

279239
with toolkit.progress("Deploying...") as progress:
280240
while True:
281-
deployment = _get_deployment(app_id, deployment_id)
282-
283-
if deployment is None:
284-
progress.set_error(
285-
"[error]Error fetching deployment status.[/]\n\nPlease try again later."
286-
)
287-
288-
raise typer.Exit(1) from None
241+
with handle_http_errors(progress):
242+
deployment = _get_deployment(app_id, deployment_id)
289243

290244
if deployment.status == DeploymentStatus.success:
291245
progress.log(
@@ -354,25 +308,16 @@ def deploy(
354308
archive_path = archive(path or Path.cwd()) # noqa: F841
355309

356310
with toolkit.progress(title="Creating deployment") as progress:
357-
deployment = _create_deployment(app_config.id)
358-
359-
if deployment is None:
360-
progress.set_error("Error creating deployment. Please try again later.")
361-
362-
raise typer.Exit(1)
363-
364-
progress.log(
365-
f"Deployment created successfully! Deployment slug: {deployment.slug}"
366-
)
367-
368-
progress.log("Uploading deployment...")
311+
with handle_http_errors(progress):
312+
deployment = _create_deployment(app_config.id)
369313

370-
if not _upload_deployment(deployment.id, archive_path):
371-
progress.set_error(
372-
"[error]Something went while upload the archive[/]\n\nPlease try again later."
314+
progress.log(
315+
f"Deployment created successfully! Deployment slug: {deployment.slug}"
373316
)
374317

375-
raise typer.Exit(1) from None
318+
progress.log("Uploading deployment...")
319+
320+
_upload_deployment(deployment.id, archive_path)
376321

377322
progress.log("Deployment uploaded successfully!")
378323

src/fastapi_cli/utils/cli.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
1+
import contextlib
2+
import logging
3+
from typing import Generator, Optional
4+
5+
import typer
6+
from httpx import HTTPError, ReadTimeout
17
from rich_toolkit import RichToolkit, RichToolkitTheme
8+
from rich_toolkit.progress import Progress
29
from rich_toolkit.styles import TaggedStyle
310

11+
logger = logging.getLogger(__name__)
12+
413

514
def get_rich_toolkit() -> RichToolkit:
615
theme = RichToolkitTheme(
@@ -18,3 +27,31 @@ def get_rich_toolkit() -> RichToolkit:
1827
)
1928

2029
return RichToolkit(theme=theme)
30+
31+
32+
@contextlib.contextmanager
33+
def handle_http_errors(
34+
progress: Progress,
35+
message: Optional[str] = None,
36+
) -> Generator[None, None, None]:
37+
try:
38+
yield
39+
except ReadTimeout as e:
40+
logger.debug(e)
41+
42+
progress.set_error(
43+
"The request to the FastAPI Cloud server timed out. Please try again later."
44+
)
45+
46+
raise typer.Exit(1) from None
47+
except HTTPError as e:
48+
logger.debug(e)
49+
50+
message = (
51+
message
52+
or f"Something went wrong while contacting the FastAPI Cloud server. Please try again later. \n\n{e}"
53+
)
54+
55+
progress.set_error(message)
56+
57+
raise typer.Exit(1) from None

0 commit comments

Comments
 (0)