Skip to content

Commit 956e07c

Browse files
committed
✨ Add upload cancellation notification on keyboard interrupt
1 parent e602669 commit 956e07c

File tree

2 files changed

+88
-6
lines changed

2 files changed

+88
-6
lines changed

src/fastapi_cloud_cli/commands/deploy.py

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,19 @@
3232
logger = logging.getLogger(__name__)
3333

3434

35+
def _cancel_upload(deployment_id: str) -> None:
36+
logger.debug("Cancelling upload for deployment: %s", deployment_id)
37+
38+
try:
39+
with APIClient() as client:
40+
response = client.post(f"/deployments/{deployment_id}/upload-cancelled")
41+
response.raise_for_status()
42+
43+
logger.debug("Upload cancellation notification sent successfully")
44+
except Exception as e:
45+
logger.debug("Failed to notify server about upload cancellation: %s", e)
46+
47+
3548
def _get_app_name(path: Path) -> str:
3649
# TODO: use pyproject.toml to get the app name
3750
return path.name
@@ -598,15 +611,19 @@ def deploy(
598611
logger.debug("Creating deployment for app: %s", app.id)
599612
deployment = _create_deployment(app.id)
600613

601-
progress.log(
602-
f"Deployment created successfully! Deployment slug: {deployment.slug}"
603-
)
614+
try:
615+
progress.log(
616+
f"Deployment created successfully! Deployment slug: {deployment.slug}"
617+
)
604618

605-
progress.log("Uploading deployment...")
619+
progress.log("Uploading deployment...")
606620

607-
_upload_deployment(deployment.id, archive_path)
621+
_upload_deployment(deployment.id, archive_path)
608622

609-
progress.log("Deployment uploaded successfully!")
623+
progress.log("Deployment uploaded successfully!")
624+
except KeyboardInterrupt:
625+
_cancel_upload(deployment.id)
626+
raise
610627

611628
toolkit.print_line()
612629

tests/test_cli_deploy.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -990,3 +990,68 @@ def build_logs_handler(request: httpx.Request, route: respx.Route) -> Response:
990990
result = runner.invoke(app, ["deploy"])
991991

992992
assert "long wait message" in result.output
993+
994+
995+
@pytest.mark.respx(base_url=settings.base_api_url)
996+
def test_calls_upload_cancelled_when_user_interrupts(
997+
logged_in_cli: None, tmp_path: Path, respx_mock: respx.MockRouter
998+
) -> None:
999+
app_data = _get_random_app()
1000+
team_data = _get_random_team()
1001+
app_id = app_data["id"]
1002+
team_id = team_data["id"]
1003+
deployment_data = _get_random_deployment(app_id=app_id)
1004+
1005+
config_path = tmp_path / ".fastapicloud" / "cloud.json"
1006+
config_path.parent.mkdir(parents=True, exist_ok=True)
1007+
config_path.write_text(f'{{"app_id": "{app_id}", "team_id": "{team_id}"}}')
1008+
1009+
respx_mock.get(f"/apps/{app_id}").mock(return_value=Response(200, json=app_data))
1010+
respx_mock.post(f"/apps/{app_id}/deployments/").mock(
1011+
return_value=Response(201, json=deployment_data)
1012+
)
1013+
1014+
upload_cancelled_route = respx_mock.post(
1015+
f"/deployments/{deployment_data['id']}/upload-cancelled"
1016+
).mock(return_value=Response(200))
1017+
1018+
with changing_dir(tmp_path), patch(
1019+
"fastapi_cloud_cli.commands.deploy._upload_deployment",
1020+
side_effect=KeyboardInterrupt(),
1021+
):
1022+
runner.invoke(app, ["deploy"])
1023+
1024+
assert upload_cancelled_route.called
1025+
1026+
1027+
@pytest.mark.respx(base_url=settings.base_api_url)
1028+
def test_cancel_upload_swallows_exceptions(
1029+
logged_in_cli: None, tmp_path: Path, respx_mock: respx.MockRouter
1030+
) -> None:
1031+
app_data = _get_random_app()
1032+
team_data = _get_random_team()
1033+
app_id = app_data["id"]
1034+
team_id = team_data["id"]
1035+
deployment_data = _get_random_deployment(app_id=app_id)
1036+
1037+
config_path = tmp_path / ".fastapicloud" / "cloud.json"
1038+
config_path.parent.mkdir(parents=True, exist_ok=True)
1039+
config_path.write_text(f'{{"app_id": "{app_id}", "team_id": "{team_id}"}}')
1040+
1041+
respx_mock.get(f"/apps/{app_id}").mock(return_value=Response(200, json=app_data))
1042+
respx_mock.post(f"/apps/{app_id}/deployments/").mock(
1043+
return_value=Response(201, json=deployment_data)
1044+
)
1045+
1046+
upload_cancelled_route = respx_mock.post(
1047+
f"/deployments/{deployment_data['id']}/upload-cancelled"
1048+
).mock(return_value=Response(500))
1049+
1050+
with changing_dir(tmp_path), patch(
1051+
"fastapi_cloud_cli.commands.deploy._upload_deployment",
1052+
side_effect=KeyboardInterrupt(),
1053+
):
1054+
result = runner.invoke(app, ["deploy"])
1055+
1056+
assert upload_cancelled_route.called
1057+
assert "HTTPStatusError" not in result.output

0 commit comments

Comments
 (0)