fastapi-cloud-cli 0.22.2__tar.gz → 0.24.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 (118) hide show
  1. {fastapi_cloud_cli-0.22.2 → fastapi_cloud_cli-0.24.0}/PKG-INFO +2 -1
  2. {fastapi_cloud_cli-0.22.2 → fastapi_cloud_cli-0.24.0}/pyproject.toml +3 -2
  3. fastapi_cloud_cli-0.24.0/src/fastapi_cloud_cli/__init__.py +1 -0
  4. {fastapi_cloud_cli-0.22.2 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/cli.py +2 -0
  5. {fastapi_cloud_cli-0.22.2 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/commands/apps/create.py +6 -2
  6. {fastapi_cloud_cli-0.22.2 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/commands/apps/list.py +7 -48
  7. {fastapi_cloud_cli-0.22.2 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/commands/deploy/cloud.py +23 -3
  8. {fastapi_cloud_cli-0.22.2 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/commands/deploy/command.py +19 -1
  9. {fastapi_cloud_cli-0.22.2 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/commands/deploy/upload.py +4 -1
  10. fastapi_cloud_cli-0.24.0/src/fastapi_cloud_cli/commands/integrations/__init__.py +13 -0
  11. fastapi_cloud_cli-0.24.0/src/fastapi_cloud_cli/commands/integrations/providers/__init__.py +11 -0
  12. fastapi_cloud_cli-0.24.0/src/fastapi_cloud_cli/commands/integrations/providers/list.py +144 -0
  13. fastapi_cloud_cli-0.24.0/src/fastapi_cloud_cli/commands/integrations/resources/__init__.py +19 -0
  14. fastapi_cloud_cli-0.24.0/src/fastapi_cloud_cli/commands/integrations/resources/connect.py +155 -0
  15. fastapi_cloud_cli-0.24.0/src/fastapi_cloud_cli/commands/integrations/resources/disconnect.py +250 -0
  16. fastapi_cloud_cli-0.24.0/src/fastapi_cloud_cli/commands/integrations/resources/get.py +230 -0
  17. fastapi_cloud_cli-0.24.0/src/fastapi_cloud_cli/commands/integrations/resources/list.py +123 -0
  18. fastapi_cloud_cli-0.24.0/src/fastapi_cloud_cli/commands/integrations/resources/providers.py +10 -0
  19. {fastapi_cloud_cli-0.22.2 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/utils/api.py +27 -1
  20. fastapi_cloud_cli-0.24.0/src/fastapi_cloud_cli/utils/teams.py +83 -0
  21. fastapi_cloud_cli-0.24.0/tests/integrations/__init__.py +0 -0
  22. fastapi_cloud_cli-0.24.0/tests/integrations/providers/__init__.py +0 -0
  23. fastapi_cloud_cli-0.24.0/tests/integrations/providers/test_list.py +284 -0
  24. fastapi_cloud_cli-0.24.0/tests/integrations/resources/__init__.py +0 -0
  25. fastapi_cloud_cli-0.24.0/tests/integrations/resources/test_connect.py +221 -0
  26. fastapi_cloud_cli-0.24.0/tests/integrations/resources/test_disconnect.py +363 -0
  27. fastapi_cloud_cli-0.24.0/tests/integrations/resources/test_get.py +297 -0
  28. fastapi_cloud_cli-0.24.0/tests/integrations/resources/test_list.py +257 -0
  29. {fastapi_cloud_cli-0.22.2 → fastapi_cloud_cli-0.24.0}/tests/test_api_client.py +26 -0
  30. {fastapi_cloud_cli-0.22.2 → fastapi_cloud_cli-0.24.0}/tests/test_cli_deploy.py +136 -0
  31. {fastapi_cloud_cli-0.22.2 → fastapi_cloud_cli-0.24.0}/tests/test_deploy_utils.py +12 -0
  32. fastapi_cloud_cli-0.22.2/src/fastapi_cloud_cli/__init__.py +0 -1
  33. {fastapi_cloud_cli-0.22.2 → fastapi_cloud_cli-0.24.0}/LICENSE +0 -0
  34. {fastapi_cloud_cli-0.22.2 → fastapi_cloud_cli-0.24.0}/README.md +0 -0
  35. {fastapi_cloud_cli-0.22.2 → fastapi_cloud_cli-0.24.0}/scripts/add_latest_release_date.py +0 -0
  36. {fastapi_cloud_cli-0.22.2 → fastapi_cloud_cli-0.24.0}/scripts/format.sh +0 -0
  37. {fastapi_cloud_cli-0.22.2 → fastapi_cloud_cli-0.24.0}/scripts/lint.sh +0 -0
  38. {fastapi_cloud_cli-0.22.2 → fastapi_cloud_cli-0.24.0}/scripts/prepare_release.py +0 -0
  39. {fastapi_cloud_cli-0.22.2 → fastapi_cloud_cli-0.24.0}/scripts/test-cov-html.sh +0 -0
  40. {fastapi_cloud_cli-0.22.2 → fastapi_cloud_cli-0.24.0}/scripts/test.sh +0 -0
  41. {fastapi_cloud_cli-0.22.2 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/__main__.py +0 -0
  42. {fastapi_cloud_cli-0.22.2 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/commands/__init__.py +0 -0
  43. {fastapi_cloud_cli-0.22.2 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/commands/_flow.py +0 -0
  44. {fastapi_cloud_cli-0.22.2 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/commands/apps/__init__.py +0 -0
  45. {fastapi_cloud_cli-0.22.2 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/commands/apps/get.py +0 -0
  46. {fastapi_cloud_cli-0.22.2 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/commands/apps/link.py +0 -0
  47. {fastapi_cloud_cli-0.22.2 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/commands/apps/unlink.py +0 -0
  48. {fastapi_cloud_cli-0.22.2 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/commands/apps/update.py +0 -0
  49. {fastapi_cloud_cli-0.22.2 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/commands/auth/__init__.py +0 -0
  50. {fastapi_cloud_cli-0.22.2 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/commands/auth/wait.py +0 -0
  51. {fastapi_cloud_cli-0.22.2 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/commands/ci/__init__.py +0 -0
  52. {fastapi_cloud_cli-0.22.2 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/commands/ci/print_workflow.py +0 -0
  53. {fastapi_cloud_cli-0.22.2 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/commands/deploy/__init__.py +0 -0
  54. {fastapi_cloud_cli-0.22.2 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/commands/deploy/archive.py +0 -0
  55. {fastapi_cloud_cli-0.22.2 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/commands/deploy/configure.py +0 -0
  56. {fastapi_cloud_cli-0.22.2 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/commands/deploy/wait.py +0 -0
  57. {fastapi_cloud_cli-0.22.2 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/commands/deployments.py +0 -0
  58. {fastapi_cloud_cli-0.22.2 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/commands/env/__init__.py +0 -0
  59. {fastapi_cloud_cli-0.22.2 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/commands/env/_shared.py +0 -0
  60. {fastapi_cloud_cli-0.22.2 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/commands/env/delete.py +0 -0
  61. {fastapi_cloud_cli-0.22.2 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/commands/env/get.py +0 -0
  62. {fastapi_cloud_cli-0.22.2 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/commands/env/list.py +0 -0
  63. {fastapi_cloud_cli-0.22.2 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/commands/env/set.py +0 -0
  64. {fastapi_cloud_cli-0.22.2 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/commands/login.py +0 -0
  65. {fastapi_cloud_cli-0.22.2 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/commands/logout.py +0 -0
  66. {fastapi_cloud_cli-0.22.2 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/commands/logs.py +0 -0
  67. {fastapi_cloud_cli-0.22.2 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/commands/setup_ci.py +0 -0
  68. {fastapi_cloud_cli-0.22.2 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/commands/teams/__init__.py +0 -0
  69. {fastapi_cloud_cli-0.22.2 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/commands/teams/get.py +0 -0
  70. {fastapi_cloud_cli-0.22.2 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/commands/tokens/__init__.py +0 -0
  71. {fastapi_cloud_cli-0.22.2 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/commands/tokens/create.py +0 -0
  72. {fastapi_cloud_cli-0.22.2 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/commands/tokens/delete.py +0 -0
  73. {fastapi_cloud_cli-0.22.2 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/commands/tokens/list.py +0 -0
  74. {fastapi_cloud_cli-0.22.2 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/commands/whoami.py +0 -0
  75. {fastapi_cloud_cli-0.22.2 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/config.py +0 -0
  76. {fastapi_cloud_cli-0.22.2 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/logging.py +0 -0
  77. {fastapi_cloud_cli-0.22.2 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/py.typed +0 -0
  78. {fastapi_cloud_cli-0.22.2 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/utils/__init__.py +0 -0
  79. {fastapi_cloud_cli-0.22.2 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/utils/apps.py +0 -0
  80. {fastapi_cloud_cli-0.22.2 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/utils/auth.py +0 -0
  81. {fastapi_cloud_cli-0.22.2 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/utils/cli.py +0 -0
  82. {fastapi_cloud_cli-0.22.2 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/utils/config.py +0 -0
  83. {fastapi_cloud_cli-0.22.2 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/utils/dates.py +0 -0
  84. {fastapi_cloud_cli-0.22.2 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/utils/env.py +0 -0
  85. {fastapi_cloud_cli-0.22.2 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/utils/errors.py +0 -0
  86. {fastapi_cloud_cli-0.22.2 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/utils/execution.py +0 -0
  87. {fastapi_cloud_cli-0.22.2 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/utils/progress_file.py +0 -0
  88. {fastapi_cloud_cli-0.22.2 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/utils/sentry.py +0 -0
  89. {fastapi_cloud_cli-0.22.2 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/utils/version_check.py +0 -0
  90. {fastapi_cloud_cli-0.22.2 → fastapi_cloud_cli-0.24.0}/tests/__init__.py +0 -0
  91. {fastapi_cloud_cli-0.22.2 → fastapi_cloud_cli-0.24.0}/tests/conftest.py +0 -0
  92. {fastapi_cloud_cli-0.22.2 → fastapi_cloud_cli-0.24.0}/tests/test_archive.py +0 -0
  93. {fastapi_cloud_cli-0.22.2 → fastapi_cloud_cli-0.24.0}/tests/test_auth.py +0 -0
  94. {fastapi_cloud_cli-0.22.2 → fastapi_cloud_cli-0.24.0}/tests/test_cli.py +0 -0
  95. {fastapi_cloud_cli-0.22.2 → fastapi_cloud_cli-0.24.0}/tests/test_cli_apps.py +0 -0
  96. {fastapi_cloud_cli-0.22.2 → fastapi_cloud_cli-0.24.0}/tests/test_cli_ci.py +0 -0
  97. {fastapi_cloud_cli-0.22.2 → fastapi_cloud_cli-0.24.0}/tests/test_cli_deployments.py +0 -0
  98. {fastapi_cloud_cli-0.22.2 → fastapi_cloud_cli-0.24.0}/tests/test_cli_link.py +0 -0
  99. {fastapi_cloud_cli-0.22.2 → fastapi_cloud_cli-0.24.0}/tests/test_cli_login.py +0 -0
  100. {fastapi_cloud_cli-0.22.2 → fastapi_cloud_cli-0.24.0}/tests/test_cli_logout.py +0 -0
  101. {fastapi_cloud_cli-0.22.2 → fastapi_cloud_cli-0.24.0}/tests/test_cli_setup_ci.py +0 -0
  102. {fastapi_cloud_cli-0.22.2 → fastapi_cloud_cli-0.24.0}/tests/test_cli_teams.py +0 -0
  103. {fastapi_cloud_cli-0.22.2 → fastapi_cloud_cli-0.24.0}/tests/test_cli_tokens.py +0 -0
  104. {fastapi_cloud_cli-0.22.2 → fastapi_cloud_cli-0.24.0}/tests/test_cli_unlink.py +0 -0
  105. {fastapi_cloud_cli-0.22.2 → fastapi_cloud_cli-0.24.0}/tests/test_cli_whoami.py +0 -0
  106. {fastapi_cloud_cli-0.22.2 → fastapi_cloud_cli-0.24.0}/tests/test_config.py +0 -0
  107. {fastapi_cloud_cli-0.22.2 → fastapi_cloud_cli-0.24.0}/tests/test_dates.py +0 -0
  108. {fastapi_cloud_cli-0.22.2 → fastapi_cloud_cli-0.24.0}/tests/test_env_delete.py +0 -0
  109. {fastapi_cloud_cli-0.22.2 → fastapi_cloud_cli-0.24.0}/tests/test_env_get.py +0 -0
  110. {fastapi_cloud_cli-0.22.2 → fastapi_cloud_cli-0.24.0}/tests/test_env_list.py +0 -0
  111. {fastapi_cloud_cli-0.22.2 → fastapi_cloud_cli-0.24.0}/tests/test_env_set.py +0 -0
  112. {fastapi_cloud_cli-0.22.2 → fastapi_cloud_cli-0.24.0}/tests/test_logs.py +0 -0
  113. {fastapi_cloud_cli-0.22.2 → fastapi_cloud_cli-0.24.0}/tests/test_prepare_release.py +0 -0
  114. {fastapi_cloud_cli-0.22.2 → fastapi_cloud_cli-0.24.0}/tests/test_progress_file.py +0 -0
  115. {fastapi_cloud_cli-0.22.2 → fastapi_cloud_cli-0.24.0}/tests/test_sentry.py +0 -0
  116. {fastapi_cloud_cli-0.22.2 → fastapi_cloud_cli-0.24.0}/tests/test_utils_apps.py +0 -0
  117. {fastapi_cloud_cli-0.22.2 → fastapi_cloud_cli-0.24.0}/tests/test_version_check.py +0 -0
  118. {fastapi_cloud_cli-0.22.2 → fastapi_cloud_cli-0.24.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.2
3
+ Version: 0.24.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
@@ -40,6 +40,7 @@ Requires-Dist: pydantic[email]>=2.12.0; python_version >= "3.14"
40
40
  Requires-Dist: sentry-sdk>=2.20.0
41
41
  Requires-Dist: fastar>=0.10.0
42
42
  Requires-Dist: detect-installer>=0.1.0
43
+ Requires-Dist: agent-detector>=1.1.0
43
44
  Provides-Extra: standard
44
45
  Requires-Dist: uvicorn[standard]>=0.15.0; extra == "standard"
45
46
  Description-Content-Type: text/markdown
@@ -40,8 +40,9 @@ dependencies = [
40
40
  "sentry-sdk >= 2.20.0",
41
41
  "fastar >= 0.10.0",
42
42
  "detect-installer>=0.1.0",
43
+ "agent-detector>=1.1.0",
43
44
  ]
44
- version = "0.22.2"
45
+ version = "0.24.0"
45
46
 
46
47
  [project.license]
47
48
  text = "MIT"
@@ -63,7 +64,7 @@ dev = [
63
64
  "prek>=0.2.24,<1.0.0",
64
65
  "pytest>=7.0.0,<10.0.0",
65
66
  "coverage[toml]>=7.2,<8.0",
66
- "mypy==2.1.0",
67
+ "mypy==2.3.0",
67
68
  "ruff>=0.15.16",
68
69
  "respx==0.23.1",
69
70
  "time-machine>=2.19.0",
@@ -0,0 +1 @@
1
+ __version__ = "0.24.0"
@@ -12,6 +12,7 @@ from .commands.ci import ci_app
12
12
  from .commands.deploy import deploy
13
13
  from .commands.deployments import deployments_app
14
14
  from .commands.env import env_app
15
+ from .commands.integrations import integrations_app
15
16
  from .commands.login import login
16
17
  from .commands.logout import logout
17
18
  from .commands.logs import logs
@@ -73,6 +74,7 @@ cloud_app.add_typer(auth_app, name="auth")
73
74
  cloud_app.add_typer(apps_app, name="apps")
74
75
  cloud_app.add_typer(ci_app, name="ci")
75
76
  cloud_app.add_typer(deployments_app, name="deployments")
77
+ cloud_app.add_typer(integrations_app, name="integrations")
76
78
  cloud_app.add_typer(teams_app, name="teams")
77
79
  cloud_app.add_typer(tokens_app, name="tokens")
78
80
 
@@ -6,7 +6,6 @@ import typer
6
6
  from pydantic import BaseModel, Field
7
7
  from rich_toolkit import RichToolkit
8
8
 
9
- from fastapi_cloud_cli.commands.apps.list import _prompt_for_team
10
9
  from fastapi_cloud_cli.commands.deploy.archive import (
11
10
  _get_app_name,
12
11
  validate_app_directory,
@@ -16,6 +15,7 @@ from fastapi_cloud_cli.utils.apps import AppConfig, write_app_config
16
15
  from fastapi_cloud_cli.utils.auth import Identity
17
16
  from fastapi_cloud_cli.utils.cli import get_rich_toolkit
18
17
  from fastapi_cloud_cli.utils.execution import JsonOutputOption
18
+ from fastapi_cloud_cli.utils.teams import select_team
19
19
 
20
20
  logger = logging.getLogger(__name__)
21
21
 
@@ -130,7 +130,11 @@ def create_app(
130
130
  hint="Pass --team-id to choose a team.",
131
131
  )
132
132
 
133
- team = _prompt_for_team(toolkit, client)
133
+ team = select_team(
134
+ toolkit,
135
+ client,
136
+ empty_hint="Create a team before listing apps.",
137
+ )
134
138
  team_id = team.id
135
139
  toolkit.print_line()
136
140
 
@@ -6,13 +6,13 @@ from pydantic import BaseModel, Field
6
6
  from rich.table import Table
7
7
  from rich.text import Text
8
8
  from rich_toolkit import RichToolkit
9
- from rich_toolkit.menu import Option
10
9
 
11
10
  from fastapi_cloud_cli.config import Settings
12
11
  from fastapi_cloud_cli.utils.api import APIClient
13
12
  from fastapi_cloud_cli.utils.auth import Identity
14
- from fastapi_cloud_cli.utils.cli import FastAPIRichToolkit, get_rich_toolkit
13
+ from fastapi_cloud_cli.utils.cli import get_rich_toolkit
15
14
  from fastapi_cloud_cli.utils.execution import JsonOutputOption
15
+ from fastapi_cloud_cli.utils.teams import Team, select_team
16
16
 
17
17
  logger = logging.getLogger(__name__)
18
18
 
@@ -31,12 +31,6 @@ class App(BaseModel):
31
31
  updated_at: str | None = None
32
32
 
33
33
 
34
- class Team(BaseModel):
35
- id: str
36
- slug: str
37
- name: str
38
-
39
-
40
34
  class AppsListAPIResponse(BaseModel):
41
35
  data: list[App]
42
36
  count: int
@@ -84,15 +78,6 @@ def _get_apps_list_table(
84
78
  return table
85
79
 
86
80
 
87
- def _get_teams(client: APIClient) -> list[Team]:
88
- response = client.get("/teams/")
89
- response.raise_for_status()
90
-
91
- data = response.json()["data"]
92
-
93
- return [Team.model_validate(team) for team in data]
94
-
95
-
96
81
  def _get_team(client: APIClient, team_id: str) -> Team:
97
82
  response = client.get(f"/teams/{team_id}")
98
83
  response.raise_for_status()
@@ -149,36 +134,6 @@ def _render_apps_list_output(data: AppsListOutput, toolkit: RichToolkit) -> None
149
134
  )
150
135
 
151
136
 
152
- def _prompt_for_team(toolkit: FastAPIRichToolkit, client: APIClient) -> Team:
153
- with toolkit.progress(
154
- title="Fetching teams",
155
- transient=True,
156
- ) as progress:
157
- with client.handle_http_errors(
158
- progress,
159
- default_message="Error fetching teams. Please try again later.",
160
- toolkit=toolkit,
161
- ):
162
- teams = _get_teams(client)
163
-
164
- if not teams:
165
- toolkit.fail(
166
- "missing_required_input",
167
- "No teams found.",
168
- hint="Create a team before listing apps.",
169
- )
170
-
171
- return toolkit.ask(
172
- "Select the team:",
173
- options=[
174
- Option({"name": team.name, "value": team})
175
- for team in sorted(teams, key=lambda team: team.name.lower())
176
- ],
177
- allow_filtering=True,
178
- bullet=False,
179
- )
180
-
181
-
182
137
  def list_apps(
183
138
  team_id: Annotated[
184
139
  str | None,
@@ -229,7 +184,11 @@ def list_apps(
229
184
  hint="Pass --team-id to choose a team.",
230
185
  )
231
186
 
232
- team = _prompt_for_team(toolkit, client)
187
+ team = select_team(
188
+ toolkit,
189
+ client,
190
+ empty_hint="Create a team before listing apps.",
191
+ )
233
192
  team_id = team.id
234
193
  team_slug = team.slug
235
194
 
@@ -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
 
@@ -0,0 +1,13 @@
1
+ import typer
2
+
3
+ from fastapi_cloud_cli.commands.integrations.providers import providers_app
4
+ from fastapi_cloud_cli.commands.integrations.resources import resources_app
5
+
6
+ integrations_app = typer.Typer(
7
+ no_args_is_help=True,
8
+ help="Manage third-party integrations.",
9
+ )
10
+ integrations_app.add_typer(providers_app, name="providers")
11
+ integrations_app.add_typer(resources_app, name="resources")
12
+
13
+ __all__ = ["integrations_app"]
@@ -0,0 +1,11 @@
1
+ import typer
2
+
3
+ from fastapi_cloud_cli.commands.integrations.providers.list import list_providers
4
+
5
+ providers_app = typer.Typer(
6
+ no_args_is_help=True,
7
+ help="Manage integration providers for a team.",
8
+ )
9
+ providers_app.command("list")(list_providers)
10
+
11
+ __all__ = ["providers_app"]
@@ -0,0 +1,144 @@
1
+ from typing import Annotated, Any, Literal
2
+
3
+ import typer
4
+ from pydantic import BaseModel
5
+ from rich.table import Table
6
+ from rich.text import Text
7
+ from rich_toolkit import RichToolkit
8
+
9
+ from fastapi_cloud_cli.utils.api import APIClient
10
+ from fastapi_cloud_cli.utils.auth import Identity
11
+ from fastapi_cloud_cli.utils.cli import get_rich_toolkit
12
+ from fastapi_cloud_cli.utils.execution import JsonOutputOption
13
+ from fastapi_cloud_cli.utils.teams import resolve_team_id
14
+
15
+ ProviderStatus = Literal["available", "coming_soon", "connected"]
16
+
17
+ STATUS_LABELS: dict[ProviderStatus, str] = {
18
+ "available": "not connected",
19
+ "coming_soon": "coming soon",
20
+ "connected": "connected",
21
+ }
22
+
23
+
24
+ class ConnectedIntegration(BaseModel):
25
+ id: str
26
+
27
+
28
+ class IntegrationProvider(BaseModel):
29
+ id: str
30
+ name: str
31
+ status: ProviderStatus
32
+ connected_integration: ConnectedIntegration | None = None
33
+
34
+
35
+ class ProvidersListAPIResponse(BaseModel):
36
+ data: list[IntegrationProvider]
37
+
38
+
39
+ class ProvidersListOutput(BaseModel):
40
+ team_id: str
41
+ providers: list[IntegrationProvider]
42
+
43
+
44
+ def _get_providers(client: APIClient, *, team_id: str) -> list[IntegrationProvider]:
45
+ response = client.get(f"/teams/{team_id}/integrations")
46
+ response.raise_for_status()
47
+
48
+ return ProvidersListAPIResponse.model_validate(response.json()).data
49
+
50
+
51
+ def _get_providers_table(providers: list[IntegrationProvider]) -> Table:
52
+ table = Table.grid(padding=(0, 2), pad_edge=False)
53
+ table.add_column("Name", no_wrap=True)
54
+ table.add_column("Status", no_wrap=True)
55
+ table.add_column("Integration ID", no_wrap=True, overflow="ignore")
56
+ table.add_row(
57
+ Text("Name", style="bold"),
58
+ Text("Status", style="bold"),
59
+ Text("Integration ID", style="bold"),
60
+ )
61
+ table.add_row("", "", "")
62
+
63
+ for provider in providers:
64
+ connected_integration = provider.connected_integration
65
+ table.add_row(
66
+ Text(provider.name),
67
+ Text(
68
+ STATUS_LABELS[provider.status],
69
+ style="green" if provider.status == "connected" else "dim",
70
+ ),
71
+ Text(connected_integration.id if connected_integration else "-"),
72
+ )
73
+
74
+ return table
75
+
76
+
77
+ def _render_providers_list_output(
78
+ data: ProvidersListOutput,
79
+ toolkit: RichToolkit,
80
+ ) -> None:
81
+ toolkit.print_title("integrations")
82
+ toolkit.print_line()
83
+
84
+ if not data.providers:
85
+ toolkit.print("No integration providers available.", bullet=False)
86
+ return
87
+
88
+ toolkit.print(_get_providers_table(data.providers), bullet=False)
89
+
90
+
91
+ def list_providers(
92
+ team_id: Annotated[
93
+ str | None,
94
+ typer.Option(
95
+ "--team-id",
96
+ help=(
97
+ "ID of the team whose integration providers should be listed. "
98
+ "Defaults to the linked app's team."
99
+ ),
100
+ ),
101
+ ] = None,
102
+ json_output: JsonOutputOption = False,
103
+ ) -> Any:
104
+ """
105
+ List integration providers and their connection status for a team.
106
+ """
107
+ identity = Identity()
108
+
109
+ with get_rich_toolkit(json_output=json_output) as toolkit:
110
+ if not identity.is_logged_in():
111
+ toolkit.fail(
112
+ "not_logged_in",
113
+ "No credentials found.",
114
+ hint="Run `fastapi cloud login` or set FASTAPI_CLOUD_TOKEN.",
115
+ )
116
+
117
+ with APIClient() as client:
118
+ team_id = resolve_team_id(
119
+ toolkit,
120
+ client,
121
+ team_id=team_id,
122
+ empty_hint="Create a team before listing integration providers.",
123
+ )
124
+
125
+ with (
126
+ toolkit.progress(
127
+ title="Fetching integration providers",
128
+ transient=True,
129
+ ) as progress,
130
+ client.handle_http_errors(
131
+ progress,
132
+ default_message=(
133
+ "Error fetching integration providers. Please try again later."
134
+ ),
135
+ not_found_message="Team not found.",
136
+ toolkit=toolkit,
137
+ ),
138
+ ):
139
+ providers = _get_providers(client, team_id=team_id)
140
+
141
+ toolkit.success(
142
+ ProvidersListOutput(team_id=team_id, providers=providers),
143
+ render_output=_render_providers_list_output,
144
+ )
@@ -0,0 +1,19 @@
1
+ import typer
2
+
3
+ from fastapi_cloud_cli.commands.integrations.resources.connect import connect_resource
4
+ from fastapi_cloud_cli.commands.integrations.resources.disconnect import (
5
+ disconnect_resource,
6
+ )
7
+ from fastapi_cloud_cli.commands.integrations.resources.get import get_resource
8
+ from fastapi_cloud_cli.commands.integrations.resources.list import list_resources
9
+
10
+ resources_app = typer.Typer(
11
+ no_args_is_help=True,
12
+ help="Manage resources connected to an app.",
13
+ )
14
+ resources_app.command("connect")(connect_resource)
15
+ resources_app.command("disconnect")(disconnect_resource)
16
+ resources_app.command("get")(get_resource)
17
+ resources_app.command("list")(list_resources)
18
+
19
+ __all__ = ["resources_app"]
@@ -0,0 +1,155 @@
1
+ import logging
2
+ from typing import Annotated, Any
3
+
4
+ import typer
5
+ from pydantic import BaseModel, Field
6
+ from rich.text import Text
7
+ from rich_toolkit import RichToolkit
8
+
9
+ from fastapi_cloud_cli.commands.apps.list import (
10
+ App,
11
+ _get_app,
12
+ _get_app_dashboard_url,
13
+ _get_team,
14
+ )
15
+ from fastapi_cloud_cli.config import Settings
16
+ from fastapi_cloud_cli.utils.api import APIClient
17
+ from fastapi_cloud_cli.utils.apps import resolve_app_id_or_fail
18
+ from fastapi_cloud_cli.utils.auth import Identity
19
+ from fastapi_cloud_cli.utils.cli import get_rich_toolkit
20
+ from fastapi_cloud_cli.utils.execution import JsonOutputOption
21
+
22
+ logger = logging.getLogger(__name__)
23
+
24
+
25
+ class ResourceConnectOutput(BaseModel):
26
+ app_id: str
27
+ connect_url: str
28
+ app_name: Annotated[str, Field(exclude=True)]
29
+ browser_opened: Annotated[bool, Field(exclude=True)]
30
+ app_id_was_provided: Annotated[bool, Field(exclude=True)]
31
+
32
+
33
+ def _get_resource_connect_url(
34
+ app: App,
35
+ *,
36
+ team_slug: str,
37
+ settings: Settings,
38
+ ) -> str:
39
+ app_dashboard_url = _get_app_dashboard_url(
40
+ app,
41
+ team_slug=team_slug,
42
+ settings=settings,
43
+ )
44
+ return f"{app_dashboard_url}/integrations/connect"
45
+
46
+
47
+ def _render_resource_connect_output(
48
+ data: ResourceConnectOutput,
49
+ toolkit: RichToolkit,
50
+ ) -> None:
51
+ toolkit.print_title("connect resource")
52
+ toolkit.print_line()
53
+
54
+ if data.browser_opened:
55
+ toolkit.print(
56
+ f"Opened the integration setup for [bold]{data.app_name}[/bold] in your browser.",
57
+ emoji="🔌",
58
+ )
59
+ else:
60
+ toolkit.print(
61
+ f"Open the integration setup for [bold]{data.app_name}[/bold] in your browser:",
62
+ emoji="🔌",
63
+ )
64
+
65
+ toolkit.print(Text(data.connect_url, style=f"link {data.connect_url}"))
66
+ toolkit.print_line()
67
+
68
+ list_command = "fastapi cloud integrations resources list"
69
+ if data.app_id_was_provided:
70
+ list_command = f"{list_command} --app-id {data.app_id}"
71
+
72
+ toolkit.print(
73
+ f"When the connection is complete, run [bold]{list_command}[/bold] to view it.",
74
+ emoji="💡",
75
+ )
76
+
77
+
78
+ def connect_resource(
79
+ app_id: Annotated[
80
+ str | None,
81
+ typer.Option(
82
+ "--app-id",
83
+ help="ID of the app to connect a provider resource to.",
84
+ ),
85
+ ] = None,
86
+ no_open: Annotated[
87
+ bool,
88
+ typer.Option(
89
+ "--no-open",
90
+ help="Do not open the browser automatically.",
91
+ ),
92
+ ] = False,
93
+ json_output: JsonOutputOption = False,
94
+ ) -> Any:
95
+ """
96
+ Open FastAPI Cloud to connect a provider resource to an app.
97
+ """
98
+ identity = Identity()
99
+
100
+ with get_rich_toolkit(json_output=json_output) as toolkit:
101
+ if not identity.is_logged_in():
102
+ toolkit.fail(
103
+ "not_logged_in",
104
+ "No credentials found.",
105
+ hint="Run `fastapi cloud login` or set FASTAPI_CLOUD_TOKEN.",
106
+ )
107
+
108
+ app_id_was_provided = app_id is not None
109
+ app_id = resolve_app_id_or_fail(toolkit, app_id=app_id)
110
+
111
+ with APIClient() as client:
112
+ with (
113
+ toolkit.progress(title="Fetching app", transient=True) as progress,
114
+ client.handle_http_errors(
115
+ progress,
116
+ default_message="Error fetching app. Please try again later.",
117
+ not_found_message="App not found.",
118
+ toolkit=toolkit,
119
+ ),
120
+ ):
121
+ app = _get_app(client, app_id)
122
+
123
+ with (
124
+ toolkit.progress(title="Fetching team", transient=True) as progress,
125
+ client.handle_http_errors(
126
+ progress,
127
+ default_message="Error fetching team. Please try again later.",
128
+ not_found_message="Team not found.",
129
+ toolkit=toolkit,
130
+ ),
131
+ ):
132
+ team = _get_team(client, app.team_id)
133
+
134
+ connect_url = _get_resource_connect_url(
135
+ app,
136
+ team_slug=team.slug,
137
+ settings=Settings.get(),
138
+ )
139
+ browser_opened = False
140
+
141
+ if not json_output and not no_open:
142
+ launch_result = typer.launch(connect_url)
143
+ logger.debug("Launch command result: %s", launch_result)
144
+ browser_opened = launch_result == 0
145
+
146
+ toolkit.success(
147
+ ResourceConnectOutput(
148
+ app_id=app.id,
149
+ app_name=app.name,
150
+ connect_url=connect_url,
151
+ browser_opened=browser_opened,
152
+ app_id_was_provided=app_id_was_provided,
153
+ ),
154
+ render_output=_render_resource_connect_output,
155
+ )