Skip to content

Commit 80d6297

Browse files
committed
✨ Allow to specify application directory
Shortcake-Parent: improve-how-we-handle-invalid-tokens
1 parent ba30f49 commit 80d6297

5 files changed

Lines changed: 63 additions & 13 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: 16 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,23 @@ 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+
default=None,
339+
placeholder="[italic]Leave empty if it's the current directory[/italic]",
340+
)
341+
342+
directory: Optional[str] = directory_input if directory_input else None
343+
344+
toolkit.print_line()
345+
335346
toolkit.print("Deployment configuration:", tag="summary")
336347
toolkit.print_line()
337348
toolkit.print(f"Team: [bold]{team.name}[/bold]")
338349
toolkit.print(f"App name: [bold]{app_name}[/bold]")
350+
toolkit.print(f"Directory: [bold]{directory or '.'}[/bold]")
351+
339352
toolkit.print_line()
340353

341354
choice = toolkit.ask(
@@ -357,7 +370,7 @@ def _configure_app(toolkit: RichToolkit, path_to_deploy: Path) -> AppConfig:
357370
else:
358371
with toolkit.progress(title="Creating app...") as progress:
359372
with handle_http_errors(progress):
360-
app = _create_app(team.id, app_name)
373+
app = _create_app(team.id, app_name, directory=directory)
361374

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

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: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -318,7 +318,7 @@ def test_asks_for_app_name_after_team(
318318
def test_creates_app_on_backend(
319319
logged_in_cli: None, tmp_path: Path, respx_mock: respx.MockRouter
320320
) -> None:
321-
steps = [Keys.ENTER, Keys.ENTER, *"demo", Keys.ENTER, Keys.ENTER]
321+
steps = [Keys.ENTER, Keys.ENTER, *"demo", Keys.ENTER, Keys.ENTER, Keys.ENTER]
322322

323323
team = _get_random_team()
324324

@@ -329,7 +329,7 @@ def test_creates_app_on_backend(
329329
)
330330
)
331331

332-
respx_mock.post("/apps/", json={"name": "demo", "team_id": team["id"]}).mock(
332+
respx_mock.post("/apps/", json={"name": "demo", "team_id": team["id"], "directory": None}).mock(
333333
return_value=Response(201, json=_get_random_app(team_id=team["id"]))
334334
)
335335

@@ -346,6 +346,39 @@ def test_creates_app_on_backend(
346346
assert "App created successfully" in result.output
347347

348348

349+
@pytest.mark.respx
350+
def test_creates_app_with_directory(
351+
logged_in_cli: None, tmp_path: Path, respx_mock: respx.MockRouter
352+
) -> None:
353+
steps = [Keys.ENTER, Keys.ENTER, *"demo", Keys.ENTER, *"src", Keys.ENTER, Keys.ENTER]
354+
355+
team = _get_random_team()
356+
357+
respx_mock.get("/teams/").mock(
358+
return_value=Response(
359+
200,
360+
json={"data": [team]},
361+
)
362+
)
363+
364+
respx_mock.post("/apps/", json={"name": "demo", "team_id": team["id"], "directory": "src"}).mock(
365+
return_value=Response(201, json=_get_random_app(team_id=team["id"]))
366+
)
367+
368+
with (
369+
changing_dir(tmp_path),
370+
patch("rich_toolkit.container.getchar") as mock_getchar,
371+
):
372+
mock_getchar.side_effect = steps
373+
374+
result = runner.invoke(app, ["deploy"])
375+
376+
assert result.exit_code == 1
377+
378+
assert "App created successfully" in result.output
379+
assert "Directory: src" in result.output
380+
381+
349382
@pytest.mark.respx
350383
def test_cancels_deployment_when_user_selects_no(
351384
logged_in_cli: None, tmp_path: Path, respx_mock: respx.MockRouter
@@ -355,6 +388,7 @@ def test_cancels_deployment_when_user_selects_no(
355388
Keys.ENTER,
356389
*"demo",
357390
Keys.ENTER,
391+
Keys.ENTER,
358392
Keys.DOWN_ARROW,
359393
Keys.ENTER,
360394
]
@@ -420,6 +454,7 @@ def test_exits_successfully_when_deployment_is_done(
420454
*"demo",
421455
Keys.ENTER,
422456
Keys.ENTER,
457+
Keys.ENTER,
423458
]
424459

425460
team_data = _get_random_team()
@@ -429,7 +464,7 @@ def test_exits_successfully_when_deployment_is_done(
429464
return_value=Response(200, json={"data": [team_data]})
430465
)
431466

432-
respx_mock.post("/apps/", json={"name": "demo", "team_id": team_data["id"]}).mock(
467+
respx_mock.post("/apps/", json={"name": "demo", "team_id": team_data["id"], "directory": None}).mock(
433468
return_value=Response(201, json=app_data)
434469
)
435470

@@ -671,6 +706,7 @@ def _deploy_without_waiting(respx_mock: respx.MockRouter, tmp_path: Path) -> Res
671706
*"demo",
672707
Keys.ENTER,
673708
Keys.ENTER,
709+
Keys.ENTER,
674710
]
675711

676712
team_data = _get_random_team()
@@ -684,7 +720,7 @@ def _deploy_without_waiting(respx_mock: respx.MockRouter, tmp_path: Path) -> Res
684720
)
685721
)
686722

687-
respx_mock.post("/apps/", json={"name": "demo", "team_id": team_data["id"]}).mock(
723+
respx_mock.post("/apps/", json={"name": "demo", "team_id": team_data["id"], "directory": None}).mock(
688724
return_value=Response(201, json=app_data)
689725
)
690726

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)