fastapi-cloud-cli 0.23.0__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.23.0 → fastapi_cloud_cli-0.24.0}/PKG-INFO +2 -1
  2. {fastapi_cloud_cli-0.23.0 → 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.23.0 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/cli.py +2 -0
  5. {fastapi_cloud_cli-0.23.0 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/commands/apps/create.py +6 -2
  6. {fastapi_cloud_cli-0.23.0 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/commands/apps/list.py +7 -48
  7. fastapi_cloud_cli-0.24.0/src/fastapi_cloud_cli/commands/integrations/__init__.py +13 -0
  8. fastapi_cloud_cli-0.24.0/src/fastapi_cloud_cli/commands/integrations/providers/__init__.py +11 -0
  9. fastapi_cloud_cli-0.24.0/src/fastapi_cloud_cli/commands/integrations/providers/list.py +144 -0
  10. fastapi_cloud_cli-0.24.0/src/fastapi_cloud_cli/commands/integrations/resources/__init__.py +19 -0
  11. fastapi_cloud_cli-0.24.0/src/fastapi_cloud_cli/commands/integrations/resources/connect.py +155 -0
  12. fastapi_cloud_cli-0.24.0/src/fastapi_cloud_cli/commands/integrations/resources/disconnect.py +250 -0
  13. fastapi_cloud_cli-0.24.0/src/fastapi_cloud_cli/commands/integrations/resources/get.py +230 -0
  14. fastapi_cloud_cli-0.24.0/src/fastapi_cloud_cli/commands/integrations/resources/list.py +123 -0
  15. fastapi_cloud_cli-0.24.0/src/fastapi_cloud_cli/commands/integrations/resources/providers.py +10 -0
  16. {fastapi_cloud_cli-0.23.0 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/utils/api.py +24 -1
  17. fastapi_cloud_cli-0.24.0/src/fastapi_cloud_cli/utils/teams.py +83 -0
  18. fastapi_cloud_cli-0.24.0/tests/integrations/__init__.py +0 -0
  19. fastapi_cloud_cli-0.24.0/tests/integrations/providers/__init__.py +0 -0
  20. fastapi_cloud_cli-0.24.0/tests/integrations/providers/test_list.py +284 -0
  21. fastapi_cloud_cli-0.24.0/tests/integrations/resources/__init__.py +0 -0
  22. fastapi_cloud_cli-0.24.0/tests/integrations/resources/test_connect.py +221 -0
  23. fastapi_cloud_cli-0.24.0/tests/integrations/resources/test_disconnect.py +363 -0
  24. fastapi_cloud_cli-0.24.0/tests/integrations/resources/test_get.py +297 -0
  25. fastapi_cloud_cli-0.24.0/tests/integrations/resources/test_list.py +257 -0
  26. {fastapi_cloud_cli-0.23.0 → fastapi_cloud_cli-0.24.0}/tests/test_api_client.py +26 -0
  27. {fastapi_cloud_cli-0.23.0 → fastapi_cloud_cli-0.24.0}/tests/test_deploy_utils.py +12 -0
  28. fastapi_cloud_cli-0.23.0/src/fastapi_cloud_cli/__init__.py +0 -1
  29. {fastapi_cloud_cli-0.23.0 → fastapi_cloud_cli-0.24.0}/LICENSE +0 -0
  30. {fastapi_cloud_cli-0.23.0 → fastapi_cloud_cli-0.24.0}/README.md +0 -0
  31. {fastapi_cloud_cli-0.23.0 → fastapi_cloud_cli-0.24.0}/scripts/add_latest_release_date.py +0 -0
  32. {fastapi_cloud_cli-0.23.0 → fastapi_cloud_cli-0.24.0}/scripts/format.sh +0 -0
  33. {fastapi_cloud_cli-0.23.0 → fastapi_cloud_cli-0.24.0}/scripts/lint.sh +0 -0
  34. {fastapi_cloud_cli-0.23.0 → fastapi_cloud_cli-0.24.0}/scripts/prepare_release.py +0 -0
  35. {fastapi_cloud_cli-0.23.0 → fastapi_cloud_cli-0.24.0}/scripts/test-cov-html.sh +0 -0
  36. {fastapi_cloud_cli-0.23.0 → fastapi_cloud_cli-0.24.0}/scripts/test.sh +0 -0
  37. {fastapi_cloud_cli-0.23.0 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/__main__.py +0 -0
  38. {fastapi_cloud_cli-0.23.0 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/commands/__init__.py +0 -0
  39. {fastapi_cloud_cli-0.23.0 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/commands/_flow.py +0 -0
  40. {fastapi_cloud_cli-0.23.0 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/commands/apps/__init__.py +0 -0
  41. {fastapi_cloud_cli-0.23.0 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/commands/apps/get.py +0 -0
  42. {fastapi_cloud_cli-0.23.0 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/commands/apps/link.py +0 -0
  43. {fastapi_cloud_cli-0.23.0 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/commands/apps/unlink.py +0 -0
  44. {fastapi_cloud_cli-0.23.0 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/commands/apps/update.py +0 -0
  45. {fastapi_cloud_cli-0.23.0 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/commands/auth/__init__.py +0 -0
  46. {fastapi_cloud_cli-0.23.0 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/commands/auth/wait.py +0 -0
  47. {fastapi_cloud_cli-0.23.0 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/commands/ci/__init__.py +0 -0
  48. {fastapi_cloud_cli-0.23.0 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/commands/ci/print_workflow.py +0 -0
  49. {fastapi_cloud_cli-0.23.0 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/commands/deploy/__init__.py +0 -0
  50. {fastapi_cloud_cli-0.23.0 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/commands/deploy/archive.py +0 -0
  51. {fastapi_cloud_cli-0.23.0 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/commands/deploy/cloud.py +0 -0
  52. {fastapi_cloud_cli-0.23.0 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/commands/deploy/command.py +0 -0
  53. {fastapi_cloud_cli-0.23.0 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/commands/deploy/configure.py +0 -0
  54. {fastapi_cloud_cli-0.23.0 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/commands/deploy/upload.py +0 -0
  55. {fastapi_cloud_cli-0.23.0 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/commands/deploy/wait.py +0 -0
  56. {fastapi_cloud_cli-0.23.0 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/commands/deployments.py +0 -0
  57. {fastapi_cloud_cli-0.23.0 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/commands/env/__init__.py +0 -0
  58. {fastapi_cloud_cli-0.23.0 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/commands/env/_shared.py +0 -0
  59. {fastapi_cloud_cli-0.23.0 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/commands/env/delete.py +0 -0
  60. {fastapi_cloud_cli-0.23.0 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/commands/env/get.py +0 -0
  61. {fastapi_cloud_cli-0.23.0 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/commands/env/list.py +0 -0
  62. {fastapi_cloud_cli-0.23.0 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/commands/env/set.py +0 -0
  63. {fastapi_cloud_cli-0.23.0 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/commands/login.py +0 -0
  64. {fastapi_cloud_cli-0.23.0 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/commands/logout.py +0 -0
  65. {fastapi_cloud_cli-0.23.0 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/commands/logs.py +0 -0
  66. {fastapi_cloud_cli-0.23.0 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/commands/setup_ci.py +0 -0
  67. {fastapi_cloud_cli-0.23.0 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/commands/teams/__init__.py +0 -0
  68. {fastapi_cloud_cli-0.23.0 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/commands/teams/get.py +0 -0
  69. {fastapi_cloud_cli-0.23.0 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/commands/tokens/__init__.py +0 -0
  70. {fastapi_cloud_cli-0.23.0 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/commands/tokens/create.py +0 -0
  71. {fastapi_cloud_cli-0.23.0 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/commands/tokens/delete.py +0 -0
  72. {fastapi_cloud_cli-0.23.0 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/commands/tokens/list.py +0 -0
  73. {fastapi_cloud_cli-0.23.0 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/commands/whoami.py +0 -0
  74. {fastapi_cloud_cli-0.23.0 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/config.py +0 -0
  75. {fastapi_cloud_cli-0.23.0 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/logging.py +0 -0
  76. {fastapi_cloud_cli-0.23.0 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/py.typed +0 -0
  77. {fastapi_cloud_cli-0.23.0 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/utils/__init__.py +0 -0
  78. {fastapi_cloud_cli-0.23.0 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/utils/apps.py +0 -0
  79. {fastapi_cloud_cli-0.23.0 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/utils/auth.py +0 -0
  80. {fastapi_cloud_cli-0.23.0 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/utils/cli.py +0 -0
  81. {fastapi_cloud_cli-0.23.0 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/utils/config.py +0 -0
  82. {fastapi_cloud_cli-0.23.0 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/utils/dates.py +0 -0
  83. {fastapi_cloud_cli-0.23.0 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/utils/env.py +0 -0
  84. {fastapi_cloud_cli-0.23.0 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/utils/errors.py +0 -0
  85. {fastapi_cloud_cli-0.23.0 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/utils/execution.py +0 -0
  86. {fastapi_cloud_cli-0.23.0 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/utils/progress_file.py +0 -0
  87. {fastapi_cloud_cli-0.23.0 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/utils/sentry.py +0 -0
  88. {fastapi_cloud_cli-0.23.0 → fastapi_cloud_cli-0.24.0}/src/fastapi_cloud_cli/utils/version_check.py +0 -0
  89. {fastapi_cloud_cli-0.23.0 → fastapi_cloud_cli-0.24.0}/tests/__init__.py +0 -0
  90. {fastapi_cloud_cli-0.23.0 → fastapi_cloud_cli-0.24.0}/tests/conftest.py +0 -0
  91. {fastapi_cloud_cli-0.23.0 → fastapi_cloud_cli-0.24.0}/tests/test_archive.py +0 -0
  92. {fastapi_cloud_cli-0.23.0 → fastapi_cloud_cli-0.24.0}/tests/test_auth.py +0 -0
  93. {fastapi_cloud_cli-0.23.0 → fastapi_cloud_cli-0.24.0}/tests/test_cli.py +0 -0
  94. {fastapi_cloud_cli-0.23.0 → fastapi_cloud_cli-0.24.0}/tests/test_cli_apps.py +0 -0
  95. {fastapi_cloud_cli-0.23.0 → fastapi_cloud_cli-0.24.0}/tests/test_cli_ci.py +0 -0
  96. {fastapi_cloud_cli-0.23.0 → fastapi_cloud_cli-0.24.0}/tests/test_cli_deploy.py +0 -0
  97. {fastapi_cloud_cli-0.23.0 → fastapi_cloud_cli-0.24.0}/tests/test_cli_deployments.py +0 -0
  98. {fastapi_cloud_cli-0.23.0 → fastapi_cloud_cli-0.24.0}/tests/test_cli_link.py +0 -0
  99. {fastapi_cloud_cli-0.23.0 → fastapi_cloud_cli-0.24.0}/tests/test_cli_login.py +0 -0
  100. {fastapi_cloud_cli-0.23.0 → fastapi_cloud_cli-0.24.0}/tests/test_cli_logout.py +0 -0
  101. {fastapi_cloud_cli-0.23.0 → fastapi_cloud_cli-0.24.0}/tests/test_cli_setup_ci.py +0 -0
  102. {fastapi_cloud_cli-0.23.0 → fastapi_cloud_cli-0.24.0}/tests/test_cli_teams.py +0 -0
  103. {fastapi_cloud_cli-0.23.0 → fastapi_cloud_cli-0.24.0}/tests/test_cli_tokens.py +0 -0
  104. {fastapi_cloud_cli-0.23.0 → fastapi_cloud_cli-0.24.0}/tests/test_cli_unlink.py +0 -0
  105. {fastapi_cloud_cli-0.23.0 → fastapi_cloud_cli-0.24.0}/tests/test_cli_whoami.py +0 -0
  106. {fastapi_cloud_cli-0.23.0 → fastapi_cloud_cli-0.24.0}/tests/test_config.py +0 -0
  107. {fastapi_cloud_cli-0.23.0 → fastapi_cloud_cli-0.24.0}/tests/test_dates.py +0 -0
  108. {fastapi_cloud_cli-0.23.0 → fastapi_cloud_cli-0.24.0}/tests/test_env_delete.py +0 -0
  109. {fastapi_cloud_cli-0.23.0 → fastapi_cloud_cli-0.24.0}/tests/test_env_get.py +0 -0
  110. {fastapi_cloud_cli-0.23.0 → fastapi_cloud_cli-0.24.0}/tests/test_env_list.py +0 -0
  111. {fastapi_cloud_cli-0.23.0 → fastapi_cloud_cli-0.24.0}/tests/test_env_set.py +0 -0
  112. {fastapi_cloud_cli-0.23.0 → fastapi_cloud_cli-0.24.0}/tests/test_logs.py +0 -0
  113. {fastapi_cloud_cli-0.23.0 → fastapi_cloud_cli-0.24.0}/tests/test_prepare_release.py +0 -0
  114. {fastapi_cloud_cli-0.23.0 → fastapi_cloud_cli-0.24.0}/tests/test_progress_file.py +0 -0
  115. {fastapi_cloud_cli-0.23.0 → fastapi_cloud_cli-0.24.0}/tests/test_sentry.py +0 -0
  116. {fastapi_cloud_cli-0.23.0 → fastapi_cloud_cli-0.24.0}/tests/test_utils_apps.py +0 -0
  117. {fastapi_cloud_cli-0.23.0 → fastapi_cloud_cli-0.24.0}/tests/test_version_check.py +0 -0
  118. {fastapi_cloud_cli-0.23.0 → 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.23.0
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.23.0"
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
 
@@ -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
+ )