fastapi-cloud-cli 0.22.1__tar.gz → 0.23.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. {fastapi_cloud_cli-0.22.1 → fastapi_cloud_cli-0.23.0}/PKG-INFO +2 -2
  2. {fastapi_cloud_cli-0.22.1 → fastapi_cloud_cli-0.23.0}/pyproject.toml +2 -2
  3. fastapi_cloud_cli-0.23.0/src/fastapi_cloud_cli/__init__.py +1 -0
  4. {fastapi_cloud_cli-0.22.1 → fastapi_cloud_cli-0.23.0}/src/fastapi_cloud_cli/commands/deploy/cloud.py +23 -3
  5. {fastapi_cloud_cli-0.22.1 → fastapi_cloud_cli-0.23.0}/src/fastapi_cloud_cli/commands/deploy/command.py +19 -1
  6. {fastapi_cloud_cli-0.22.1 → fastapi_cloud_cli-0.23.0}/src/fastapi_cloud_cli/commands/deploy/upload.py +4 -1
  7. {fastapi_cloud_cli-0.22.1 → fastapi_cloud_cli-0.23.0}/src/fastapi_cloud_cli/utils/api.py +3 -0
  8. {fastapi_cloud_cli-0.22.1 → fastapi_cloud_cli-0.23.0}/tests/test_cli_deploy.py +136 -0
  9. fastapi_cloud_cli-0.22.1/src/fastapi_cloud_cli/__init__.py +0 -1
  10. {fastapi_cloud_cli-0.22.1 → fastapi_cloud_cli-0.23.0}/LICENSE +0 -0
  11. {fastapi_cloud_cli-0.22.1 → fastapi_cloud_cli-0.23.0}/README.md +0 -0
  12. {fastapi_cloud_cli-0.22.1 → fastapi_cloud_cli-0.23.0}/scripts/add_latest_release_date.py +0 -0
  13. {fastapi_cloud_cli-0.22.1 → fastapi_cloud_cli-0.23.0}/scripts/format.sh +0 -0
  14. {fastapi_cloud_cli-0.22.1 → fastapi_cloud_cli-0.23.0}/scripts/lint.sh +0 -0
  15. {fastapi_cloud_cli-0.22.1 → fastapi_cloud_cli-0.23.0}/scripts/prepare_release.py +0 -0
  16. {fastapi_cloud_cli-0.22.1 → fastapi_cloud_cli-0.23.0}/scripts/test-cov-html.sh +0 -0
  17. {fastapi_cloud_cli-0.22.1 → fastapi_cloud_cli-0.23.0}/scripts/test.sh +0 -0
  18. {fastapi_cloud_cli-0.22.1 → fastapi_cloud_cli-0.23.0}/src/fastapi_cloud_cli/__main__.py +0 -0
  19. {fastapi_cloud_cli-0.22.1 → fastapi_cloud_cli-0.23.0}/src/fastapi_cloud_cli/cli.py +0 -0
  20. {fastapi_cloud_cli-0.22.1 → fastapi_cloud_cli-0.23.0}/src/fastapi_cloud_cli/commands/__init__.py +0 -0
  21. {fastapi_cloud_cli-0.22.1 → fastapi_cloud_cli-0.23.0}/src/fastapi_cloud_cli/commands/_flow.py +0 -0
  22. {fastapi_cloud_cli-0.22.1 → fastapi_cloud_cli-0.23.0}/src/fastapi_cloud_cli/commands/apps/__init__.py +0 -0
  23. {fastapi_cloud_cli-0.22.1 → fastapi_cloud_cli-0.23.0}/src/fastapi_cloud_cli/commands/apps/create.py +0 -0
  24. {fastapi_cloud_cli-0.22.1 → fastapi_cloud_cli-0.23.0}/src/fastapi_cloud_cli/commands/apps/get.py +0 -0
  25. {fastapi_cloud_cli-0.22.1 → fastapi_cloud_cli-0.23.0}/src/fastapi_cloud_cli/commands/apps/link.py +0 -0
  26. {fastapi_cloud_cli-0.22.1 → fastapi_cloud_cli-0.23.0}/src/fastapi_cloud_cli/commands/apps/list.py +0 -0
  27. {fastapi_cloud_cli-0.22.1 → fastapi_cloud_cli-0.23.0}/src/fastapi_cloud_cli/commands/apps/unlink.py +0 -0
  28. {fastapi_cloud_cli-0.22.1 → fastapi_cloud_cli-0.23.0}/src/fastapi_cloud_cli/commands/apps/update.py +0 -0
  29. {fastapi_cloud_cli-0.22.1 → fastapi_cloud_cli-0.23.0}/src/fastapi_cloud_cli/commands/auth/__init__.py +0 -0
  30. {fastapi_cloud_cli-0.22.1 → fastapi_cloud_cli-0.23.0}/src/fastapi_cloud_cli/commands/auth/wait.py +0 -0
  31. {fastapi_cloud_cli-0.22.1 → fastapi_cloud_cli-0.23.0}/src/fastapi_cloud_cli/commands/ci/__init__.py +0 -0
  32. {fastapi_cloud_cli-0.22.1 → fastapi_cloud_cli-0.23.0}/src/fastapi_cloud_cli/commands/ci/print_workflow.py +0 -0
  33. {fastapi_cloud_cli-0.22.1 → fastapi_cloud_cli-0.23.0}/src/fastapi_cloud_cli/commands/deploy/__init__.py +0 -0
  34. {fastapi_cloud_cli-0.22.1 → fastapi_cloud_cli-0.23.0}/src/fastapi_cloud_cli/commands/deploy/archive.py +0 -0
  35. {fastapi_cloud_cli-0.22.1 → fastapi_cloud_cli-0.23.0}/src/fastapi_cloud_cli/commands/deploy/configure.py +0 -0
  36. {fastapi_cloud_cli-0.22.1 → fastapi_cloud_cli-0.23.0}/src/fastapi_cloud_cli/commands/deploy/wait.py +0 -0
  37. {fastapi_cloud_cli-0.22.1 → fastapi_cloud_cli-0.23.0}/src/fastapi_cloud_cli/commands/deployments.py +0 -0
  38. {fastapi_cloud_cli-0.22.1 → fastapi_cloud_cli-0.23.0}/src/fastapi_cloud_cli/commands/env/__init__.py +0 -0
  39. {fastapi_cloud_cli-0.22.1 → fastapi_cloud_cli-0.23.0}/src/fastapi_cloud_cli/commands/env/_shared.py +0 -0
  40. {fastapi_cloud_cli-0.22.1 → fastapi_cloud_cli-0.23.0}/src/fastapi_cloud_cli/commands/env/delete.py +0 -0
  41. {fastapi_cloud_cli-0.22.1 → fastapi_cloud_cli-0.23.0}/src/fastapi_cloud_cli/commands/env/get.py +0 -0
  42. {fastapi_cloud_cli-0.22.1 → fastapi_cloud_cli-0.23.0}/src/fastapi_cloud_cli/commands/env/list.py +0 -0
  43. {fastapi_cloud_cli-0.22.1 → fastapi_cloud_cli-0.23.0}/src/fastapi_cloud_cli/commands/env/set.py +0 -0
  44. {fastapi_cloud_cli-0.22.1 → fastapi_cloud_cli-0.23.0}/src/fastapi_cloud_cli/commands/login.py +0 -0
  45. {fastapi_cloud_cli-0.22.1 → fastapi_cloud_cli-0.23.0}/src/fastapi_cloud_cli/commands/logout.py +0 -0
  46. {fastapi_cloud_cli-0.22.1 → fastapi_cloud_cli-0.23.0}/src/fastapi_cloud_cli/commands/logs.py +0 -0
  47. {fastapi_cloud_cli-0.22.1 → fastapi_cloud_cli-0.23.0}/src/fastapi_cloud_cli/commands/setup_ci.py +0 -0
  48. {fastapi_cloud_cli-0.22.1 → fastapi_cloud_cli-0.23.0}/src/fastapi_cloud_cli/commands/teams/__init__.py +0 -0
  49. {fastapi_cloud_cli-0.22.1 → fastapi_cloud_cli-0.23.0}/src/fastapi_cloud_cli/commands/teams/get.py +0 -0
  50. {fastapi_cloud_cli-0.22.1 → fastapi_cloud_cli-0.23.0}/src/fastapi_cloud_cli/commands/tokens/__init__.py +0 -0
  51. {fastapi_cloud_cli-0.22.1 → fastapi_cloud_cli-0.23.0}/src/fastapi_cloud_cli/commands/tokens/create.py +0 -0
  52. {fastapi_cloud_cli-0.22.1 → fastapi_cloud_cli-0.23.0}/src/fastapi_cloud_cli/commands/tokens/delete.py +0 -0
  53. {fastapi_cloud_cli-0.22.1 → fastapi_cloud_cli-0.23.0}/src/fastapi_cloud_cli/commands/tokens/list.py +0 -0
  54. {fastapi_cloud_cli-0.22.1 → fastapi_cloud_cli-0.23.0}/src/fastapi_cloud_cli/commands/whoami.py +0 -0
  55. {fastapi_cloud_cli-0.22.1 → fastapi_cloud_cli-0.23.0}/src/fastapi_cloud_cli/config.py +0 -0
  56. {fastapi_cloud_cli-0.22.1 → fastapi_cloud_cli-0.23.0}/src/fastapi_cloud_cli/logging.py +0 -0
  57. {fastapi_cloud_cli-0.22.1 → fastapi_cloud_cli-0.23.0}/src/fastapi_cloud_cli/py.typed +0 -0
  58. {fastapi_cloud_cli-0.22.1 → fastapi_cloud_cli-0.23.0}/src/fastapi_cloud_cli/utils/__init__.py +0 -0
  59. {fastapi_cloud_cli-0.22.1 → fastapi_cloud_cli-0.23.0}/src/fastapi_cloud_cli/utils/apps.py +0 -0
  60. {fastapi_cloud_cli-0.22.1 → fastapi_cloud_cli-0.23.0}/src/fastapi_cloud_cli/utils/auth.py +0 -0
  61. {fastapi_cloud_cli-0.22.1 → fastapi_cloud_cli-0.23.0}/src/fastapi_cloud_cli/utils/cli.py +0 -0
  62. {fastapi_cloud_cli-0.22.1 → fastapi_cloud_cli-0.23.0}/src/fastapi_cloud_cli/utils/config.py +0 -0
  63. {fastapi_cloud_cli-0.22.1 → fastapi_cloud_cli-0.23.0}/src/fastapi_cloud_cli/utils/dates.py +0 -0
  64. {fastapi_cloud_cli-0.22.1 → fastapi_cloud_cli-0.23.0}/src/fastapi_cloud_cli/utils/env.py +0 -0
  65. {fastapi_cloud_cli-0.22.1 → fastapi_cloud_cli-0.23.0}/src/fastapi_cloud_cli/utils/errors.py +0 -0
  66. {fastapi_cloud_cli-0.22.1 → fastapi_cloud_cli-0.23.0}/src/fastapi_cloud_cli/utils/execution.py +0 -0
  67. {fastapi_cloud_cli-0.22.1 → fastapi_cloud_cli-0.23.0}/src/fastapi_cloud_cli/utils/progress_file.py +0 -0
  68. {fastapi_cloud_cli-0.22.1 → fastapi_cloud_cli-0.23.0}/src/fastapi_cloud_cli/utils/sentry.py +0 -0
  69. {fastapi_cloud_cli-0.22.1 → fastapi_cloud_cli-0.23.0}/src/fastapi_cloud_cli/utils/version_check.py +0 -0
  70. {fastapi_cloud_cli-0.22.1 → fastapi_cloud_cli-0.23.0}/tests/__init__.py +0 -0
  71. {fastapi_cloud_cli-0.22.1 → fastapi_cloud_cli-0.23.0}/tests/conftest.py +0 -0
  72. {fastapi_cloud_cli-0.22.1 → fastapi_cloud_cli-0.23.0}/tests/test_api_client.py +0 -0
  73. {fastapi_cloud_cli-0.22.1 → fastapi_cloud_cli-0.23.0}/tests/test_archive.py +0 -0
  74. {fastapi_cloud_cli-0.22.1 → fastapi_cloud_cli-0.23.0}/tests/test_auth.py +0 -0
  75. {fastapi_cloud_cli-0.22.1 → fastapi_cloud_cli-0.23.0}/tests/test_cli.py +0 -0
  76. {fastapi_cloud_cli-0.22.1 → fastapi_cloud_cli-0.23.0}/tests/test_cli_apps.py +0 -0
  77. {fastapi_cloud_cli-0.22.1 → fastapi_cloud_cli-0.23.0}/tests/test_cli_ci.py +0 -0
  78. {fastapi_cloud_cli-0.22.1 → fastapi_cloud_cli-0.23.0}/tests/test_cli_deployments.py +0 -0
  79. {fastapi_cloud_cli-0.22.1 → fastapi_cloud_cli-0.23.0}/tests/test_cli_link.py +0 -0
  80. {fastapi_cloud_cli-0.22.1 → fastapi_cloud_cli-0.23.0}/tests/test_cli_login.py +0 -0
  81. {fastapi_cloud_cli-0.22.1 → fastapi_cloud_cli-0.23.0}/tests/test_cli_logout.py +0 -0
  82. {fastapi_cloud_cli-0.22.1 → fastapi_cloud_cli-0.23.0}/tests/test_cli_setup_ci.py +0 -0
  83. {fastapi_cloud_cli-0.22.1 → fastapi_cloud_cli-0.23.0}/tests/test_cli_teams.py +0 -0
  84. {fastapi_cloud_cli-0.22.1 → fastapi_cloud_cli-0.23.0}/tests/test_cli_tokens.py +0 -0
  85. {fastapi_cloud_cli-0.22.1 → fastapi_cloud_cli-0.23.0}/tests/test_cli_unlink.py +0 -0
  86. {fastapi_cloud_cli-0.22.1 → fastapi_cloud_cli-0.23.0}/tests/test_cli_whoami.py +0 -0
  87. {fastapi_cloud_cli-0.22.1 → fastapi_cloud_cli-0.23.0}/tests/test_config.py +0 -0
  88. {fastapi_cloud_cli-0.22.1 → fastapi_cloud_cli-0.23.0}/tests/test_dates.py +0 -0
  89. {fastapi_cloud_cli-0.22.1 → fastapi_cloud_cli-0.23.0}/tests/test_deploy_utils.py +0 -0
  90. {fastapi_cloud_cli-0.22.1 → fastapi_cloud_cli-0.23.0}/tests/test_env_delete.py +0 -0
  91. {fastapi_cloud_cli-0.22.1 → fastapi_cloud_cli-0.23.0}/tests/test_env_get.py +0 -0
  92. {fastapi_cloud_cli-0.22.1 → fastapi_cloud_cli-0.23.0}/tests/test_env_list.py +0 -0
  93. {fastapi_cloud_cli-0.22.1 → fastapi_cloud_cli-0.23.0}/tests/test_env_set.py +0 -0
  94. {fastapi_cloud_cli-0.22.1 → fastapi_cloud_cli-0.23.0}/tests/test_logs.py +0 -0
  95. {fastapi_cloud_cli-0.22.1 → fastapi_cloud_cli-0.23.0}/tests/test_prepare_release.py +0 -0
  96. {fastapi_cloud_cli-0.22.1 → fastapi_cloud_cli-0.23.0}/tests/test_progress_file.py +0 -0
  97. {fastapi_cloud_cli-0.22.1 → fastapi_cloud_cli-0.23.0}/tests/test_sentry.py +0 -0
  98. {fastapi_cloud_cli-0.22.1 → fastapi_cloud_cli-0.23.0}/tests/test_utils_apps.py +0 -0
  99. {fastapi_cloud_cli-0.22.1 → fastapi_cloud_cli-0.23.0}/tests/test_version_check.py +0 -0
  100. {fastapi_cloud_cli-0.22.1 → fastapi_cloud_cli-0.23.0}/tests/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: fastapi-cloud-cli
3
- Version: 0.22.1
3
+ Version: 0.23.0
4
4
  Summary: Deploy and manage FastAPI Cloud apps from the command line 🚀
5
5
  Author-Email: Patrick Arminio <patrick@fastapilabs.com>
6
6
  License: MIT
@@ -33,7 +33,7 @@ Requires-Dist: typer>=0.16.0
33
33
  Requires-Dist: uvicorn[standard]>=0.17.6
34
34
  Requires-Dist: rignore>=0.5.1
35
35
  Requires-Dist: httpx>=0.27.0
36
- Requires-Dist: rich-toolkit>=0.20.1
36
+ Requires-Dist: rich-toolkit>=0.20.3
37
37
  Requires-Dist: pydantic[email]>=2.7.4; python_version < "3.13"
38
38
  Requires-Dist: pydantic[email]>=2.8.0; python_version == "3.13"
39
39
  Requires-Dist: pydantic[email]>=2.12.0; python_version >= "3.14"
@@ -33,7 +33,7 @@ dependencies = [
33
33
  "uvicorn[standard] >= 0.17.6",
34
34
  "rignore >= 0.5.1",
35
35
  "httpx >= 0.27.0",
36
- "rich-toolkit>=0.20.1",
36
+ "rich-toolkit>=0.20.3",
37
37
  "pydantic[email] >= 2.7.4; python_version < '3.13'",
38
38
  "pydantic[email] >= 2.8.0; python_version == '3.13'",
39
39
  "pydantic[email] >= 2.12.0; python_version >= '3.14'",
@@ -41,7 +41,7 @@ dependencies = [
41
41
  "fastar >= 0.10.0",
42
42
  "detect-installer>=0.1.0",
43
43
  ]
44
- version = "0.22.1"
44
+ version = "0.23.0"
45
45
 
46
46
  [project.license]
47
47
  text = "MIT"
@@ -0,0 +1 @@
1
+ __version__ = "0.23.0"
@@ -1,6 +1,14 @@
1
1
  from pydantic import BaseModel
2
2
 
3
- from fastapi_cloud_cli.utils.api import APIClient, DeploymentStatus
3
+ from fastapi_cloud_cli.utils.api import (
4
+ APIClient,
5
+ DeploymentStatus,
6
+ _get_response_error_message,
7
+ )
8
+
9
+
10
+ class ArchiveTooLargeError(Exception):
11
+ pass
4
12
 
5
13
 
6
14
  class Team(BaseModel):
@@ -57,8 +65,20 @@ def _create_app(
57
65
  return AppResponse.model_validate(response.json())
58
66
 
59
67
 
60
- def _create_deployment(client: APIClient, app_id: str) -> CreateDeploymentResponse:
61
- response = client.post(f"/apps/{app_id}/deployments/")
68
+ def _create_deployment(
69
+ client: APIClient, app_id: str, archive_size_bytes: int
70
+ ) -> CreateDeploymentResponse:
71
+ response = client.post(
72
+ f"/apps/{app_id}/deployments/",
73
+ json={"archive_size_bytes": archive_size_bytes},
74
+ )
75
+
76
+ if response.status_code == 413:
77
+ raise ArchiveTooLargeError(
78
+ _get_response_error_message(response)
79
+ or "The app source code exceeds the maximum allowed size."
80
+ )
81
+
62
82
  response.raise_for_status()
63
83
 
64
84
  return CreateDeploymentResponse.model_validate(response.json())
@@ -9,6 +9,7 @@ from pydantic import BaseModel
9
9
  from fastapi_cloud_cli.commands.deploy.archive import _get_large_files, archive
10
10
  from fastapi_cloud_cli.commands.deploy.cloud import (
11
11
  AppResponse,
12
+ ArchiveTooLargeError,
12
13
  CreateDeploymentResponse,
13
14
  _create_deployment,
14
15
  _get_app,
@@ -313,6 +314,7 @@ def deploy(
313
314
  logger.debug("Creating archive for deployment")
314
315
  archive_path = Path(temp_dir) / "archive.tar"
315
316
  archive(path_to_deploy, archive_path)
317
+ archive_size = archive_path.stat().st_size
316
318
 
317
319
  with (
318
320
  toolkit.progress(
@@ -324,7 +326,22 @@ def deploy(
324
326
  client.handle_http_errors(progress, toolkit=toolkit),
325
327
  ):
326
328
  logger.debug("Creating deployment for app: %s", app.id)
327
- deployment = _create_deployment(client=client, app_id=app.id)
329
+
330
+ try:
331
+ deployment = _create_deployment(
332
+ client=client,
333
+ app_id=app.id,
334
+ archive_size_bytes=archive_size,
335
+ )
336
+ except ArchiveTooLargeError as e:
337
+ toolkit.fail(
338
+ "invalid_input",
339
+ str(e),
340
+ hint=(
341
+ "You can exclude files from the deployment "
342
+ "with a .fastapicloudignore file."
343
+ ),
344
+ )
328
345
 
329
346
  try:
330
347
  progress.log(
@@ -335,6 +352,7 @@ def deploy(
335
352
  fastapi_client=client,
336
353
  deployment_id=deployment.id,
337
354
  archive_path=archive_path,
355
+ archive_size=archive_size,
338
356
  progress=progress,
339
357
  )
340
358
 
@@ -43,9 +43,9 @@ def _upload_deployment(
43
43
  fastapi_client: APIClient,
44
44
  deployment_id: str,
45
45
  archive_path: Path,
46
+ archive_size: int,
46
47
  progress: Progress,
47
48
  ) -> CreateDeploymentResponse:
48
- archive_size = archive_path.stat().st_size
49
49
  archive_size_str = _format_size(archive_size)
50
50
 
51
51
  progress.log(f"Uploading deployment ({archive_size_str})...")
@@ -79,6 +79,9 @@ def _upload_deployment(
79
79
  files={"file": cast(BinaryIO, archive_file_with_progress)},
80
80
  )
81
81
 
82
+ if upload_response.is_error:
83
+ logger.debug("File upload failed with response: %s", upload_response.text)
84
+
82
85
  upload_response.raise_for_status()
83
86
  logger.debug("File upload completed successfully")
84
87
 
@@ -146,6 +146,7 @@ class DeploymentStatus(str, Enum):
146
146
  building = "building"
147
147
  extracting = "extracting"
148
148
  extracting_failed = "extracting_failed"
149
+ extracting_failed_archive_too_large = "extracting_failed_archive_too_large"
149
150
  building_image = "building_image"
150
151
  building_image_failed = "building_image_failed"
151
152
  deploying = "deploying"
@@ -166,6 +167,7 @@ class DeploymentStatus(str, Enum):
166
167
  cls.building: "Building",
167
168
  cls.extracting: "Extracting Upload",
168
169
  cls.extracting_failed: "Extraction Failed",
170
+ cls.extracting_failed_archive_too_large: "Archive Too Large",
169
171
  cls.building_image: "Building Image",
170
172
  cls.building_image_failed: "Build Failed",
171
173
  cls.deploying: "Deploying Image",
@@ -186,6 +188,7 @@ FAILED_STATUSES = {
186
188
  DeploymentStatus.deploying_failed,
187
189
  DeploymentStatus.building_image_failed,
188
190
  DeploymentStatus.extracting_failed,
191
+ DeploymentStatus.extracting_failed_archive_too_large,
189
192
  }
190
193
  TERMINAL_STATUSES = SUCCESSFUL_STATUSES | FAILED_STATUSES
191
194
 
@@ -1672,6 +1672,142 @@ def test_cancel_upload_swallows_exceptions(
1672
1672
  assert "HTTPStatusError" not in result.output
1673
1673
 
1674
1674
 
1675
+ def _mock_deploy_until_upload(
1676
+ respx_mock: respx.MockRouter,
1677
+ tmp_path: Path,
1678
+ app_data: RandomApp,
1679
+ deployment_data: dict[str, str],
1680
+ upload_response: Response,
1681
+ ) -> None:
1682
+ app_id = app_data["id"]
1683
+
1684
+ config_path = tmp_path / ".fastapicloud" / "cloud.json"
1685
+ config_path.parent.mkdir(parents=True, exist_ok=True)
1686
+ config_path.write_text(f'{{"app_id": "{app_id}", "team_id": "some-team-id"}}')
1687
+
1688
+ respx_mock.get(f"/apps/{app_id}").mock(return_value=Response(200, json=app_data))
1689
+ respx_mock.post(f"/apps/{app_id}/deployments/").mock(
1690
+ return_value=Response(201, json=deployment_data)
1691
+ )
1692
+ respx_mock.post(f"/deployments/{deployment_data['id']}/upload").mock(
1693
+ return_value=Response(
1694
+ 200,
1695
+ json={"url": "http://test.com", "fields": {"key": "value"}},
1696
+ )
1697
+ )
1698
+ respx_mock.post("http://test.com", data={"key": "value"}).mock(
1699
+ return_value=upload_response
1700
+ )
1701
+
1702
+
1703
+ def _mock_deploy_until_deployment_creation(
1704
+ respx_mock: respx.MockRouter,
1705
+ tmp_path: Path,
1706
+ app_data: RandomApp,
1707
+ creation_response: Response,
1708
+ ) -> respx.Route:
1709
+ app_id = app_data["id"]
1710
+
1711
+ config_path = tmp_path / ".fastapicloud" / "cloud.json"
1712
+ config_path.parent.mkdir(parents=True, exist_ok=True)
1713
+ config_path.write_text(f'{{"app_id": "{app_id}", "team_id": "some-team-id"}}')
1714
+
1715
+ respx_mock.get(f"/apps/{app_id}").mock(return_value=Response(200, json=app_data))
1716
+
1717
+ return respx_mock.post(f"/apps/{app_id}/deployments/").mock(
1718
+ return_value=creation_response
1719
+ )
1720
+
1721
+
1722
+ @pytest.mark.respx
1723
+ def test_deploy_shows_error_when_creation_rejects_archive_size(
1724
+ logged_in_cli: None, tmp_path: Path, respx_mock: respx.MockRouter
1725
+ ) -> None:
1726
+ app_data = _get_random_app()
1727
+
1728
+ # no upload routes are mocked, any upload attempt would fail the test
1729
+ create_deployment_route = _mock_deploy_until_deployment_creation(
1730
+ respx_mock,
1731
+ tmp_path,
1732
+ app_data,
1733
+ Response(
1734
+ 413,
1735
+ json={
1736
+ "detail": "App source code exceeds the maximum allowed size of 1000.0 MB"
1737
+ },
1738
+ ),
1739
+ )
1740
+
1741
+ with changing_dir(tmp_path):
1742
+ result = runner.invoke(app, ["deploy"])
1743
+
1744
+ output = " ".join(result.output.split())
1745
+
1746
+ assert result.exit_code == 1
1747
+ assert "App source code exceeds the maximum allowed size of 1000.0 MB" in output
1748
+ assert ".fastapicloudignore" in output
1749
+ assert "Something went wrong" not in output
1750
+
1751
+ request_body = json.loads(create_deployment_route.calls.last.request.content)
1752
+ assert request_body["archive_size_bytes"] > 0
1753
+
1754
+
1755
+ @pytest.mark.respx
1756
+ def test_deploy_json_shows_error_when_creation_rejects_archive_size(
1757
+ logged_in_cli: None, tmp_path: Path, respx_mock: respx.MockRouter
1758
+ ) -> None:
1759
+ app_data = _get_random_app()
1760
+
1761
+ _mock_deploy_until_deployment_creation(
1762
+ respx_mock,
1763
+ tmp_path,
1764
+ app_data,
1765
+ Response(
1766
+ 413,
1767
+ json={
1768
+ "detail": "App source code exceeds the maximum allowed size of 1000.0 MB"
1769
+ },
1770
+ ),
1771
+ )
1772
+
1773
+ with changing_dir(tmp_path):
1774
+ result = runner.invoke(app, ["deploy", "--json"])
1775
+
1776
+ assert result.exit_code == 1
1777
+
1778
+ error = json.loads(result.stdout)["error"]
1779
+ assert error["code"] == "invalid_input"
1780
+ assert (
1781
+ error["message"]
1782
+ == "App source code exceeds the maximum allowed size of 1000.0 MB"
1783
+ )
1784
+ assert error["hint"] == (
1785
+ "You can exclude files from the deployment with a .fastapicloudignore file."
1786
+ )
1787
+
1788
+
1789
+ @pytest.mark.respx
1790
+ def test_deploy_shows_error_when_upload_fails(
1791
+ logged_in_cli: None, tmp_path: Path, respx_mock: respx.MockRouter
1792
+ ) -> None:
1793
+ app_data = _get_random_app()
1794
+ deployment_data = _get_random_deployment(app_id=app_data["id"])
1795
+
1796
+ _mock_deploy_until_upload(
1797
+ respx_mock,
1798
+ tmp_path,
1799
+ app_data,
1800
+ deployment_data,
1801
+ Response(400, text="not an xml body"),
1802
+ )
1803
+
1804
+ with changing_dir(tmp_path):
1805
+ result = runner.invoke(app, ["deploy"])
1806
+
1807
+ assert result.exit_code == 1
1808
+ assert "Something went wrong" in result.output
1809
+
1810
+
1675
1811
  @pytest.mark.respx
1676
1812
  def test_deploy_successfully_with_token(
1677
1813
  logged_out_cli: None, tmp_path: Path, respx_mock: respx.MockRouter
@@ -1 +0,0 @@
1
- __version__ = "0.22.1"