Skip to content

Commit 6b5b4f4

Browse files
committed
✨ Allow to specify application directory
Shortcake-Parent: improve-how-we-handle-invalid-tokens
1 parent 4b7b2cf commit 6b5b4f4

5 files changed

Lines changed: 75 additions & 18 deletions

File tree

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ dependencies = [
3333
"uvicorn[standard] >= 0.15.0",
3434
"rignore >= 0.5.1",
3535
"httpx >= 0.27.0",
36-
"rich-toolkit >= 0.14.5",
36+
"rich-toolkit >= 0.19.3",
3737
"pydantic[email] >= 2.0",
3838
"sentry-sdk >= 2.20.0",
3939
"fastar >= 0.8.0",

src/fastapi_cloud_cli/commands/deploy.py

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -115,11 +115,11 @@ class AppResponse(BaseModel):
115115
slug: str
116116

117117

118-
def _create_app(team_id: str, app_name: str) -> AppResponse:
118+
def _create_app(team_id: str, app_name: str, directory: Optional[str]) -> AppResponse:
119119
with APIClient() as client:
120120
response = client.post(
121121
"/apps/",
122-
json={"name": app_name, "team_id": team_id},
122+
json={"name": app_name, "team_id": team_id, "directory": directory},
123123
)
124124

125125
response.raise_for_status()
@@ -332,10 +332,22 @@ def _configure_app(toolkit: RichToolkit, path_to_deploy: Path) -> AppConfig:
332332

333333
toolkit.print_line()
334334

335+
directory_input = toolkit.input(
336+
title="Path to the directory containing your app:",
337+
tag="dir",
338+
placeholder="[italic]Leave empty if it's the current directory[/italic]",
339+
)
340+
341+
directory: Optional[str] = directory_input if directory_input else None
342+
343+
toolkit.print_line()
344+
335345
toolkit.print("Deployment configuration:", tag="summary")
336346
toolkit.print_line()
337347
toolkit.print(f"Team: [bold]{team.name}[/bold]")
338348
toolkit.print(f"App name: [bold]{app_name}[/bold]")
349+
toolkit.print(f"Directory: [bold]{directory or '.'}[/bold]")
350+
339351
toolkit.print_line()
340352

341353
choice = toolkit.ask(
@@ -357,7 +369,7 @@ def _configure_app(toolkit: RichToolkit, path_to_deploy: Path) -> AppConfig:
357369
else:
358370
with toolkit.progress(title="Creating app...") as progress:
359371
with handle_http_errors(progress):
360-
app = _create_app(team.id, app_name)
372+
app = _create_app(team.id, app_name, directory=directory)
361373

362374
progress.log(f"App created successfully! App slug: {app.slug}")
363375

src/fastapi_cloud_cli/utils/cli.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,12 +56,13 @@ def get_rich_toolkit(minimal: bool = False) -> RichToolkit:
5656
theme={
5757
"tag.title": "white on #009485",
5858
"tag": "white on #007166",
59-
"placeholder": "grey85",
59+
"placeholder": "grey62",
6060
"text": "white",
6161
"selected": "#007166",
6262
"result": "grey85",
6363
"progress": "on #007166",
6464
"error": "red",
65+
"cancelled": "indian_red italic",
6566
},
6667
)
6768

tests/test_cli_deploy.py

Lines changed: 53 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -332,7 +332,7 @@ def test_asks_for_app_name_after_team(
332332
def test_creates_app_on_backend(
333333
logged_in_cli: None, tmp_path: Path, respx_mock: respx.MockRouter
334334
) -> None:
335-
steps = [Keys.ENTER, Keys.ENTER, *"demo", Keys.ENTER, Keys.ENTER]
335+
steps = [Keys.ENTER, Keys.ENTER, *"demo", Keys.ENTER, Keys.ENTER, Keys.ENTER]
336336

337337
team = _get_random_team()
338338

@@ -343,10 +343,50 @@ def test_creates_app_on_backend(
343343
)
344344
)
345345

346-
respx_mock.post("/apps/", json={"name": "demo", "team_id": team["id"]}).mock(
347-
return_value=Response(201, json=_get_random_app(team_id=team["id"]))
346+
respx_mock.post(
347+
"/apps/", json={"name": "demo", "team_id": team["id"], "directory": None}
348+
).mock(return_value=Response(201, json=_get_random_app(team_id=team["id"])))
349+
350+
with (
351+
changing_dir(tmp_path),
352+
patch("rich_toolkit.container.getchar") as mock_getchar,
353+
):
354+
mock_getchar.side_effect = steps
355+
356+
result = runner.invoke(app, ["deploy"])
357+
358+
assert result.exit_code == 1
359+
360+
assert "App created successfully" in result.output
361+
362+
363+
@pytest.mark.respx
364+
def test_creates_app_with_directory(
365+
logged_in_cli: None, tmp_path: Path, respx_mock: respx.MockRouter
366+
) -> None:
367+
steps = [
368+
Keys.ENTER,
369+
Keys.ENTER,
370+
*"demo",
371+
Keys.ENTER,
372+
*"src",
373+
Keys.ENTER,
374+
Keys.ENTER,
375+
]
376+
377+
team = _get_random_team()
378+
379+
respx_mock.get("/teams/").mock(
380+
return_value=Response(
381+
200,
382+
json={"data": [team]},
383+
)
348384
)
349385

386+
respx_mock.post(
387+
"/apps/", json={"name": "demo", "team_id": team["id"], "directory": "src"}
388+
).mock(return_value=Response(201, json=_get_random_app(team_id=team["id"])))
389+
350390
with (
351391
changing_dir(tmp_path),
352392
patch("rich_toolkit.container.getchar") as mock_getchar,
@@ -358,6 +398,7 @@ def test_creates_app_on_backend(
358398
assert result.exit_code == 1
359399

360400
assert "App created successfully" in result.output
401+
assert "Directory: src" in result.output
361402

362403

363404
@pytest.mark.respx
@@ -369,6 +410,7 @@ def test_cancels_deployment_when_user_selects_no(
369410
Keys.ENTER,
370411
*"demo",
371412
Keys.ENTER,
413+
Keys.ENTER,
372414
Keys.DOWN_ARROW,
373415
Keys.ENTER,
374416
]
@@ -434,6 +476,7 @@ def test_exits_successfully_when_deployment_is_done(
434476
*"demo",
435477
Keys.ENTER,
436478
Keys.ENTER,
479+
Keys.ENTER,
437480
]
438481

439482
team_data = _get_random_team()
@@ -443,9 +486,9 @@ def test_exits_successfully_when_deployment_is_done(
443486
return_value=Response(200, json={"data": [team_data]})
444487
)
445488

446-
respx_mock.post("/apps/", json={"name": "demo", "team_id": team_data["id"]}).mock(
447-
return_value=Response(201, json=app_data)
448-
)
489+
respx_mock.post(
490+
"/apps/", json={"name": "demo", "team_id": team_data["id"], "directory": None}
491+
).mock(return_value=Response(201, json=app_data))
449492

450493
respx_mock.get(f"/apps/{app_data['id']}").mock(
451494
return_value=Response(200, json=app_data)
@@ -685,6 +728,7 @@ def _deploy_without_waiting(respx_mock: respx.MockRouter, tmp_path: Path) -> Res
685728
*"demo",
686729
Keys.ENTER,
687730
Keys.ENTER,
731+
Keys.ENTER,
688732
]
689733

690734
team_data = _get_random_team()
@@ -698,9 +742,9 @@ def _deploy_without_waiting(respx_mock: respx.MockRouter, tmp_path: Path) -> Res
698742
)
699743
)
700744

701-
respx_mock.post("/apps/", json={"name": "demo", "team_id": team_data["id"]}).mock(
702-
return_value=Response(201, json=app_data)
703-
)
745+
respx_mock.post(
746+
"/apps/", json={"name": "demo", "team_id": team_data["id"], "directory": None}
747+
).mock(return_value=Response(201, json=app_data))
704748

705749
respx_mock.get(f"/apps/{app_data['id']}").mock(
706750
return_value=Response(200, json=app_data)

uv.lock

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)