Skip to content

Commit d4ec2cd

Browse files
authored
Merge pull request fastapi#36 from fastapilabs/09-17-_allow_to_use_an_existing_app
✨ Allow to use an existing app
2 parents 020a92f + 62e562d commit d4ec2cd

File tree

2 files changed

+95
-15
lines changed

2 files changed

+95
-15
lines changed

src/fastapi_cli/commands/deploy.py

+47-10
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,16 @@ def _get_app(app_slug: str) -> Optional[AppResponse]:
153153
return AppResponse.model_validate(data)
154154

155155

156+
def _get_apps(team_slug: str) -> List[AppResponse]:
157+
with APIClient() as client:
158+
response = client.get("/apps/", params={"team_slug": team_slug})
159+
response.raise_for_status()
160+
161+
data = response.json()["data"]
162+
163+
return [AppResponse.model_validate(app) for app in data]
164+
165+
156166
class DeploymentResponse(BaseModel):
157167
id: str
158168
app_id: str
@@ -216,25 +226,52 @@ def _configure_app(
216226

217227
toolkit.print_line()
218228

219-
app_name = toolkit.input(
220-
title="What's your app name?",
221-
tag="app",
222-
default=_get_app_name(path_to_deploy),
229+
create_new_app = toolkit.confirm(
230+
"Do you want to create a new app?", tag="app", default=True
223231
)
224232

225233
toolkit.print_line()
226234

227-
with toolkit.progress(title="Creating app...") as progress:
228-
with handle_http_errors(progress):
229-
app_data = _create_app(team.id, app_name)
235+
if not create_new_app:
236+
with toolkit.progress("Fetching apps...") as progress:
237+
with handle_http_errors(
238+
progress, message="Error fetching apps. Please try again later."
239+
):
240+
apps = _get_apps(team.slug)
241+
242+
toolkit.print_line()
243+
244+
if not apps:
245+
toolkit.print(
246+
"No apps found in this team. You can create a new app instead.",
247+
)
248+
249+
raise typer.Exit(1)
250+
251+
app = toolkit.ask(
252+
"Select the app you want to deploy to:",
253+
tag="app",
254+
options=[Option({"name": app.slug, "value": app}) for app in apps],
255+
)
256+
else:
257+
app_name = toolkit.input(
258+
title="What's your app name?",
259+
default=_get_app_name(path_to_deploy),
260+
)
261+
262+
toolkit.print_line()
263+
264+
with toolkit.progress(title="Creating app...") as progress:
265+
with handle_http_errors(progress):
266+
app = _create_app(team.id, app_name)
230267

231-
progress.log(f"App created successfully! App slug: {app_data.slug}")
268+
progress.log(f"App created successfully! App slug: {app.slug}")
232269

233-
app_config = AppConfig(app_id=app_data.id, team_id=team.id)
270+
app_config = AppConfig(app_id=app.id, team_id=team.id)
234271

235272
write_app_config(path_to_deploy, app_config)
236273

237-
return app_config, app_data
274+
return app_config, app
238275

239276

240277
def _wait_for_deployment(

tests/test_cli_deploy.py

+48-5
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ def test_shows_teams(
123123
def test_asks_for_app_name_after_team(
124124
logged_in_cli: None, tmp_path: Path, respx_mock: respx.MockRouter
125125
) -> None:
126-
steps = [ENTER, ENTER, CTRL_C]
126+
steps = [ENTER, ENTER, ENTER, CTRL_C]
127127

128128
respx_mock.get("/teams/").mock(
129129
return_value=Response(
@@ -151,7 +151,7 @@ def test_asks_for_app_name_after_team(
151151
def test_creates_app_on_backend(
152152
logged_in_cli: None, tmp_path: Path, respx_mock: respx.MockRouter
153153
) -> None:
154-
steps = [ENTER, ENTER, *"demo", ENTER]
154+
steps = [ENTER, ENTER, ENTER, *"demo", ENTER]
155155

156156
respx_mock.get("/teams/").mock(
157157
return_value=Response(
@@ -181,11 +181,54 @@ def test_creates_app_on_backend(
181181
assert "App created successfully" in result.output
182182

183183

184+
@pytest.mark.respx(base_url=settings.base_api_url)
185+
def test_uses_existing_app(
186+
logged_in_cli: None, tmp_path: Path, respx_mock: respx.MockRouter
187+
) -> None:
188+
steps = [ENTER, ENTER, RIGHT_ARROW, ENTER, *"demo", ENTER]
189+
190+
respx_mock.get("/teams/").mock(
191+
return_value=Response(
192+
200,
193+
json={
194+
"data": [
195+
{"name": "team1", "slug": "team1", "id": "123"},
196+
]
197+
},
198+
)
199+
)
200+
201+
respx_mock.get("/apps/", params={"team_slug": "team1"}).mock(
202+
return_value=Response(
203+
200,
204+
json={
205+
"data": [
206+
{
207+
"name": "App called demo",
208+
"slug": "app-called-demo",
209+
"id": "1234",
210+
},
211+
]
212+
},
213+
)
214+
)
215+
216+
with changing_dir(tmp_path), patch("click.getchar") as mock_getchar:
217+
mock_getchar.side_effect = steps
218+
219+
result = runner.invoke(app, ["deploy"])
220+
221+
assert result.exit_code == 1
222+
223+
assert "Select the app you want to deploy to:" in result.output
224+
assert "app-called-demo" in result.output
225+
226+
184227
@pytest.mark.respx(base_url=settings.base_api_url)
185228
def test_creates_and_uploads_deployment_then_fails(
186229
logged_in_cli: None, tmp_path: Path, respx_mock: respx.MockRouter
187230
) -> None:
188-
steps = [ENTER, ENTER, *"demo", ENTER]
231+
steps = [ENTER, ENTER, ENTER, *"demo", ENTER]
189232

190233
respx_mock.get("/teams/").mock(
191234
return_value=Response(
@@ -264,7 +307,7 @@ def test_creates_and_uploads_deployment_then_fails(
264307
def test_exists_successfully_when_deployment_is_done(
265308
logged_in_cli: None, tmp_path: Path, respx_mock: respx.MockRouter
266309
) -> None:
267-
steps = [ENTER, ENTER, *"demo", ENTER]
310+
steps = [ENTER, ENTER, ENTER, *"demo", ENTER]
268311

269312
respx_mock.get("/teams/").mock(
270313
return_value=Response(
@@ -429,7 +472,7 @@ def test_shows_error_when_app_does_not_exist(
429472
def test_can_skip_waiting(
430473
logged_in_cli: None, tmp_path: Path, respx_mock: respx.MockRouter
431474
) -> None:
432-
steps = [ENTER, ENTER, *"demo", ENTER]
475+
steps = [ENTER, ENTER, ENTER, *"demo", ENTER]
433476

434477
respx_mock.get("/teams/").mock(
435478
return_value=Response(

0 commit comments

Comments
 (0)