fastapi-cloud-cli 0.10.1__tar.gz → 0.11.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.
- {fastapi_cloud_cli-0.10.1 → fastapi_cloud_cli-0.11.0}/PKG-INFO +1 -1
- {fastapi_cloud_cli-0.10.1 → fastapi_cloud_cli-0.11.0}/pyproject.toml +1 -1
- fastapi_cloud_cli-0.11.0/src/fastapi_cloud_cli/__init__.py +1 -0
- {fastapi_cloud_cli-0.10.1 → fastapi_cloud_cli-0.11.0}/src/fastapi_cloud_cli/cli.py +2 -0
- {fastapi_cloud_cli-0.10.1 → fastapi_cloud_cli-0.11.0}/src/fastapi_cloud_cli/commands/deploy.py +3 -0
- fastapi_cloud_cli-0.11.0/src/fastapi_cloud_cli/commands/link.py +117 -0
- {fastapi_cloud_cli-0.10.1 → fastapi_cloud_cli-0.11.0}/tests/test_archive.py +3 -3
- fastapi_cloud_cli-0.11.0/tests/test_cli_link.py +191 -0
- {fastapi_cloud_cli-0.10.1 → fastapi_cloud_cli-0.11.0}/tests/test_deploy_utils.py +7 -0
- fastapi_cloud_cli-0.10.1/src/fastapi_cloud_cli/__init__.py +0 -1
- {fastapi_cloud_cli-0.10.1 → fastapi_cloud_cli-0.11.0}/LICENSE +0 -0
- {fastapi_cloud_cli-0.10.1 → fastapi_cloud_cli-0.11.0}/README.md +0 -0
- {fastapi_cloud_cli-0.10.1 → fastapi_cloud_cli-0.11.0}/scripts/format.sh +0 -0
- {fastapi_cloud_cli-0.10.1 → fastapi_cloud_cli-0.11.0}/scripts/lint.sh +0 -0
- {fastapi_cloud_cli-0.10.1 → fastapi_cloud_cli-0.11.0}/scripts/test-cov-html.sh +0 -0
- {fastapi_cloud_cli-0.10.1 → fastapi_cloud_cli-0.11.0}/scripts/test.sh +0 -0
- {fastapi_cloud_cli-0.10.1 → fastapi_cloud_cli-0.11.0}/src/fastapi_cloud_cli/__main__.py +0 -0
- {fastapi_cloud_cli-0.10.1 → fastapi_cloud_cli-0.11.0}/src/fastapi_cloud_cli/commands/__init__.py +0 -0
- {fastapi_cloud_cli-0.10.1 → fastapi_cloud_cli-0.11.0}/src/fastapi_cloud_cli/commands/env.py +0 -0
- {fastapi_cloud_cli-0.10.1 → fastapi_cloud_cli-0.11.0}/src/fastapi_cloud_cli/commands/login.py +0 -0
- {fastapi_cloud_cli-0.10.1 → fastapi_cloud_cli-0.11.0}/src/fastapi_cloud_cli/commands/logout.py +0 -0
- {fastapi_cloud_cli-0.10.1 → fastapi_cloud_cli-0.11.0}/src/fastapi_cloud_cli/commands/logs.py +0 -0
- {fastapi_cloud_cli-0.10.1 → fastapi_cloud_cli-0.11.0}/src/fastapi_cloud_cli/commands/unlink.py +0 -0
- {fastapi_cloud_cli-0.10.1 → fastapi_cloud_cli-0.11.0}/src/fastapi_cloud_cli/commands/whoami.py +0 -0
- {fastapi_cloud_cli-0.10.1 → fastapi_cloud_cli-0.11.0}/src/fastapi_cloud_cli/config.py +0 -0
- {fastapi_cloud_cli-0.10.1 → fastapi_cloud_cli-0.11.0}/src/fastapi_cloud_cli/logging.py +0 -0
- {fastapi_cloud_cli-0.10.1 → fastapi_cloud_cli-0.11.0}/src/fastapi_cloud_cli/py.typed +0 -0
- {fastapi_cloud_cli-0.10.1 → fastapi_cloud_cli-0.11.0}/src/fastapi_cloud_cli/utils/__init__.py +0 -0
- {fastapi_cloud_cli-0.10.1 → fastapi_cloud_cli-0.11.0}/src/fastapi_cloud_cli/utils/api.py +0 -0
- {fastapi_cloud_cli-0.10.1 → fastapi_cloud_cli-0.11.0}/src/fastapi_cloud_cli/utils/apps.py +0 -0
- {fastapi_cloud_cli-0.10.1 → fastapi_cloud_cli-0.11.0}/src/fastapi_cloud_cli/utils/auth.py +0 -0
- {fastapi_cloud_cli-0.10.1 → fastapi_cloud_cli-0.11.0}/src/fastapi_cloud_cli/utils/cli.py +0 -0
- {fastapi_cloud_cli-0.10.1 → fastapi_cloud_cli-0.11.0}/src/fastapi_cloud_cli/utils/config.py +0 -0
- {fastapi_cloud_cli-0.10.1 → fastapi_cloud_cli-0.11.0}/src/fastapi_cloud_cli/utils/env.py +0 -0
- {fastapi_cloud_cli-0.10.1 → fastapi_cloud_cli-0.11.0}/src/fastapi_cloud_cli/utils/sentry.py +0 -0
- {fastapi_cloud_cli-0.10.1 → fastapi_cloud_cli-0.11.0}/tests/__init__.py +0 -0
- {fastapi_cloud_cli-0.10.1 → fastapi_cloud_cli-0.11.0}/tests/assets/broken_package/mod/__init__.py +0 -0
- {fastapi_cloud_cli-0.10.1 → fastapi_cloud_cli-0.11.0}/tests/assets/broken_package/mod/app.py +0 -0
- {fastapi_cloud_cli-0.10.1 → fastapi_cloud_cli-0.11.0}/tests/assets/broken_package/utils.py +0 -0
- {fastapi_cloud_cli-0.10.1 → fastapi_cloud_cli-0.11.0}/tests/assets/default_files/default_api/api.py +0 -0
- {fastapi_cloud_cli-0.10.1 → fastapi_cloud_cli-0.11.0}/tests/assets/default_files/default_app/api.py +0 -0
- {fastapi_cloud_cli-0.10.1 → fastapi_cloud_cli-0.11.0}/tests/assets/default_files/default_app/app.py +0 -0
- {fastapi_cloud_cli-0.10.1 → fastapi_cloud_cli-0.11.0}/tests/assets/default_files/default_app_dir_api/app/__init__.py +0 -0
- {fastapi_cloud_cli-0.10.1 → fastapi_cloud_cli-0.11.0}/tests/assets/default_files/default_app_dir_api/app/api.py +0 -0
- {fastapi_cloud_cli-0.10.1 → fastapi_cloud_cli-0.11.0}/tests/assets/default_files/default_app_dir_app/app/__init__.py +0 -0
- {fastapi_cloud_cli-0.10.1 → fastapi_cloud_cli-0.11.0}/tests/assets/default_files/default_app_dir_app/app/api.py +0 -0
- {fastapi_cloud_cli-0.10.1 → fastapi_cloud_cli-0.11.0}/tests/assets/default_files/default_app_dir_app/app/app.py +0 -0
- {fastapi_cloud_cli-0.10.1 → fastapi_cloud_cli-0.11.0}/tests/assets/default_files/default_app_dir_main/app/__init__.py +0 -0
- {fastapi_cloud_cli-0.10.1 → fastapi_cloud_cli-0.11.0}/tests/assets/default_files/default_app_dir_main/app/api.py +0 -0
- {fastapi_cloud_cli-0.10.1 → fastapi_cloud_cli-0.11.0}/tests/assets/default_files/default_app_dir_main/app/app.py +0 -0
- {fastapi_cloud_cli-0.10.1 → fastapi_cloud_cli-0.11.0}/tests/assets/default_files/default_app_dir_main/app/main.py +0 -0
- {fastapi_cloud_cli-0.10.1 → fastapi_cloud_cli-0.11.0}/tests/assets/default_files/default_app_dir_non_default/app/__init__.py +0 -0
- {fastapi_cloud_cli-0.10.1 → fastapi_cloud_cli-0.11.0}/tests/assets/default_files/default_app_dir_non_default/app/nondefault.py +0 -0
- {fastapi_cloud_cli-0.10.1 → fastapi_cloud_cli-0.11.0}/tests/assets/default_files/default_main/api.py +0 -0
- {fastapi_cloud_cli-0.10.1 → fastapi_cloud_cli-0.11.0}/tests/assets/default_files/default_main/app.py +0 -0
- {fastapi_cloud_cli-0.10.1 → fastapi_cloud_cli-0.11.0}/tests/assets/default_files/default_main/main.py +0 -0
- {fastapi_cloud_cli-0.10.1 → fastapi_cloud_cli-0.11.0}/tests/assets/default_files/non_default/nonstandard.py +0 -0
- {fastapi_cloud_cli-0.10.1 → fastapi_cloud_cli-0.11.0}/tests/assets/package/__init__.py +0 -0
- {fastapi_cloud_cli-0.10.1 → fastapi_cloud_cli-0.11.0}/tests/assets/package/core/__init__.py +0 -0
- {fastapi_cloud_cli-0.10.1 → fastapi_cloud_cli-0.11.0}/tests/assets/package/core/utils.py +0 -0
- {fastapi_cloud_cli-0.10.1 → fastapi_cloud_cli-0.11.0}/tests/assets/package/mod/__init__.py +0 -0
- {fastapi_cloud_cli-0.10.1 → fastapi_cloud_cli-0.11.0}/tests/assets/package/mod/api.py +0 -0
- {fastapi_cloud_cli-0.10.1 → fastapi_cloud_cli-0.11.0}/tests/assets/package/mod/app.py +0 -0
- {fastapi_cloud_cli-0.10.1 → fastapi_cloud_cli-0.11.0}/tests/assets/package/mod/other.py +0 -0
- {fastapi_cloud_cli-0.10.1 → fastapi_cloud_cli-0.11.0}/tests/assets/single_file_api.py +0 -0
- {fastapi_cloud_cli-0.10.1 → fastapi_cloud_cli-0.11.0}/tests/assets/single_file_app.py +0 -0
- {fastapi_cloud_cli-0.10.1 → fastapi_cloud_cli-0.11.0}/tests/assets/single_file_other.py +0 -0
- {fastapi_cloud_cli-0.10.1 → fastapi_cloud_cli-0.11.0}/tests/conftest.py +0 -0
- {fastapi_cloud_cli-0.10.1 → fastapi_cloud_cli-0.11.0}/tests/test_api_client.py +0 -0
- {fastapi_cloud_cli-0.10.1 → fastapi_cloud_cli-0.11.0}/tests/test_auth.py +0 -0
- {fastapi_cloud_cli-0.10.1 → fastapi_cloud_cli-0.11.0}/tests/test_cli.py +0 -0
- {fastapi_cloud_cli-0.10.1 → fastapi_cloud_cli-0.11.0}/tests/test_cli_deploy.py +0 -0
- {fastapi_cloud_cli-0.10.1 → fastapi_cloud_cli-0.11.0}/tests/test_cli_login.py +0 -0
- {fastapi_cloud_cli-0.10.1 → fastapi_cloud_cli-0.11.0}/tests/test_cli_logout.py +0 -0
- {fastapi_cloud_cli-0.10.1 → fastapi_cloud_cli-0.11.0}/tests/test_cli_unlink.py +0 -0
- {fastapi_cloud_cli-0.10.1 → fastapi_cloud_cli-0.11.0}/tests/test_cli_whoami.py +0 -0
- {fastapi_cloud_cli-0.10.1 → fastapi_cloud_cli-0.11.0}/tests/test_config.py +0 -0
- {fastapi_cloud_cli-0.10.1 → fastapi_cloud_cli-0.11.0}/tests/test_env_delete.py +0 -0
- {fastapi_cloud_cli-0.10.1 → fastapi_cloud_cli-0.11.0}/tests/test_env_list.py +0 -0
- {fastapi_cloud_cli-0.10.1 → fastapi_cloud_cli-0.11.0}/tests/test_env_set.py +0 -0
- {fastapi_cloud_cli-0.10.1 → fastapi_cloud_cli-0.11.0}/tests/test_logs.py +0 -0
- {fastapi_cloud_cli-0.10.1 → fastapi_cloud_cli-0.11.0}/tests/test_sentry.py +0 -0
- {fastapi_cloud_cli-0.10.1 → fastapi_cloud_cli-0.11.0}/tests/utils.py +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.11.0"
|
|
@@ -2,6 +2,7 @@ import typer
|
|
|
2
2
|
|
|
3
3
|
from .commands.deploy import deploy
|
|
4
4
|
from .commands.env import env_app
|
|
5
|
+
from .commands.link import link
|
|
5
6
|
from .commands.login import login
|
|
6
7
|
from .commands.logout import logout
|
|
7
8
|
from .commands.logs import logs
|
|
@@ -25,6 +26,7 @@ cloud_app = typer.Typer(
|
|
|
25
26
|
|
|
26
27
|
# fastapi cloud [command]
|
|
27
28
|
cloud_app.command()(deploy)
|
|
29
|
+
cloud_app.command()(link)
|
|
28
30
|
cloud_app.command()(login)
|
|
29
31
|
cloud_app.command()(logs)
|
|
30
32
|
cloud_app.command()(logout)
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
from rich_toolkit.menu import Option
|
|
7
|
+
|
|
8
|
+
from fastapi_cloud_cli.utils.api import APIClient
|
|
9
|
+
from fastapi_cloud_cli.utils.apps import AppConfig, get_app_config, write_app_config
|
|
10
|
+
from fastapi_cloud_cli.utils.auth import Identity
|
|
11
|
+
from fastapi_cloud_cli.utils.cli import get_rich_toolkit, handle_http_errors
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def link() -> Any:
|
|
17
|
+
"""
|
|
18
|
+
Link a local directory to an existing FastAPI Cloud app.
|
|
19
|
+
"""
|
|
20
|
+
identity = Identity()
|
|
21
|
+
|
|
22
|
+
with get_rich_toolkit() as toolkit:
|
|
23
|
+
if not identity.is_logged_in():
|
|
24
|
+
toolkit.print(
|
|
25
|
+
"[error]You need to be logged in to link an app.[/]",
|
|
26
|
+
)
|
|
27
|
+
toolkit.print_line()
|
|
28
|
+
toolkit.print(
|
|
29
|
+
"Run [bold]fastapi cloud login[/] to authenticate.",
|
|
30
|
+
tag="tip",
|
|
31
|
+
)
|
|
32
|
+
raise typer.Exit(1)
|
|
33
|
+
|
|
34
|
+
path_to_link = Path.cwd()
|
|
35
|
+
|
|
36
|
+
if get_app_config(path_to_link):
|
|
37
|
+
toolkit.print(
|
|
38
|
+
"[error]This directory is already linked to an app.[/]",
|
|
39
|
+
)
|
|
40
|
+
toolkit.print_line()
|
|
41
|
+
toolkit.print(
|
|
42
|
+
"Run [bold]fastapi cloud unlink[/] first to remove the existing configuration.",
|
|
43
|
+
tag="tip",
|
|
44
|
+
)
|
|
45
|
+
raise typer.Exit(1)
|
|
46
|
+
|
|
47
|
+
toolkit.print_title("Link to FastAPI Cloud", tag="FastAPI")
|
|
48
|
+
toolkit.print_line()
|
|
49
|
+
|
|
50
|
+
with toolkit.progress("Fetching teams...") as progress:
|
|
51
|
+
with handle_http_errors(
|
|
52
|
+
progress, message="Error fetching teams. Please try again later."
|
|
53
|
+
):
|
|
54
|
+
with APIClient() as client:
|
|
55
|
+
response = client.get("/teams/")
|
|
56
|
+
response.raise_for_status()
|
|
57
|
+
teams_data = response.json()["data"]
|
|
58
|
+
|
|
59
|
+
if not teams_data:
|
|
60
|
+
toolkit.print(
|
|
61
|
+
"[error]No teams found. Please create a team first.[/]",
|
|
62
|
+
)
|
|
63
|
+
raise typer.Exit(1)
|
|
64
|
+
|
|
65
|
+
toolkit.print_line()
|
|
66
|
+
|
|
67
|
+
team = toolkit.ask(
|
|
68
|
+
"Select the team:",
|
|
69
|
+
tag="team",
|
|
70
|
+
options=[
|
|
71
|
+
Option({"name": t["name"], "value": {"id": t["id"], "name": t["name"]}})
|
|
72
|
+
for t in teams_data
|
|
73
|
+
],
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
toolkit.print_line()
|
|
77
|
+
|
|
78
|
+
with toolkit.progress("Fetching apps...") as progress:
|
|
79
|
+
with handle_http_errors(
|
|
80
|
+
progress, message="Error fetching apps. Please try again later."
|
|
81
|
+
):
|
|
82
|
+
with APIClient() as client:
|
|
83
|
+
response = client.get("/apps/", params={"team_id": team["id"]})
|
|
84
|
+
response.raise_for_status()
|
|
85
|
+
apps_data = response.json()["data"]
|
|
86
|
+
|
|
87
|
+
if not apps_data:
|
|
88
|
+
toolkit.print(
|
|
89
|
+
"[error]No apps found in this team.[/]",
|
|
90
|
+
)
|
|
91
|
+
toolkit.print_line()
|
|
92
|
+
toolkit.print(
|
|
93
|
+
"Run [bold]fastapi cloud deploy[/] to create and deploy a new app.",
|
|
94
|
+
tag="tip",
|
|
95
|
+
)
|
|
96
|
+
raise typer.Exit(1)
|
|
97
|
+
|
|
98
|
+
toolkit.print_line()
|
|
99
|
+
|
|
100
|
+
app = toolkit.ask(
|
|
101
|
+
"Select the app to link:",
|
|
102
|
+
tag="app",
|
|
103
|
+
options=[
|
|
104
|
+
Option({"name": a["slug"], "value": {"id": a["id"], "slug": a["slug"]}})
|
|
105
|
+
for a in apps_data
|
|
106
|
+
],
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
toolkit.print_line()
|
|
110
|
+
|
|
111
|
+
app_config = AppConfig(app_id=app["id"], team_id=team["id"])
|
|
112
|
+
write_app_config(path_to_link, app_config)
|
|
113
|
+
|
|
114
|
+
toolkit.print(
|
|
115
|
+
f"Successfully linked to app [bold]{app['slug']}[/bold]! 🔗",
|
|
116
|
+
)
|
|
117
|
+
logger.debug(f"Linked to app: {app['id']} in team: {team['id']}")
|
|
@@ -143,12 +143,13 @@ def test_archive_respects_fastapicloudignore_unignore(
|
|
|
143
143
|
}
|
|
144
144
|
|
|
145
145
|
|
|
146
|
-
def
|
|
146
|
+
def test_archive_includes_hidden_files_but_excludes_env(
|
|
147
147
|
src_path: Path, tar_path: Path, dst_path: Path
|
|
148
148
|
) -> None:
|
|
149
|
-
"""Should include hidden files
|
|
149
|
+
"""Should include hidden files but exclude .env files."""
|
|
150
150
|
(src_path / "main.py").write_text("print('hello')")
|
|
151
151
|
(src_path / ".env").write_text("SECRET_KEY=xyz")
|
|
152
|
+
(src_path / ".env.local").write_text("LOCAL_KEY=abc")
|
|
152
153
|
(src_path / ".config").mkdir()
|
|
153
154
|
(src_path / ".config" / "settings.json").write_text('{"setting": "value"}')
|
|
154
155
|
|
|
@@ -159,7 +160,6 @@ def test_archive_includes_hidden_files(
|
|
|
159
160
|
|
|
160
161
|
assert set(dst_path.glob("**/*")) == {
|
|
161
162
|
dst_path / "main.py",
|
|
162
|
-
dst_path / ".env",
|
|
163
163
|
dst_path / ".config",
|
|
164
164
|
dst_path / ".config" / "settings.json",
|
|
165
165
|
}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from unittest.mock import patch
|
|
3
|
+
|
|
4
|
+
import pytest
|
|
5
|
+
import respx
|
|
6
|
+
from httpx import Response
|
|
7
|
+
from typer.testing import CliRunner
|
|
8
|
+
|
|
9
|
+
from fastapi_cloud_cli.cli import cloud_app as app
|
|
10
|
+
from fastapi_cloud_cli.config import Settings
|
|
11
|
+
from fastapi_cloud_cli.utils.apps import AppConfig
|
|
12
|
+
from tests.conftest import ConfiguredApp
|
|
13
|
+
from tests.utils import Keys, changing_dir
|
|
14
|
+
|
|
15
|
+
runner = CliRunner()
|
|
16
|
+
settings = Settings.get()
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def test_shows_a_message_if_not_logged_in(logged_out_cli: None) -> None:
|
|
20
|
+
result = runner.invoke(app, ["link"])
|
|
21
|
+
|
|
22
|
+
assert result.exit_code == 1
|
|
23
|
+
assert "You need to be logged in to link an app." in result.output
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def test_shows_a_message_if_already_linked(
|
|
27
|
+
logged_in_cli: None, configured_app: ConfiguredApp
|
|
28
|
+
) -> None:
|
|
29
|
+
with changing_dir(configured_app.path):
|
|
30
|
+
result = runner.invoke(app, ["link"])
|
|
31
|
+
|
|
32
|
+
assert result.exit_code == 1
|
|
33
|
+
assert "This directory is already linked to an app." in result.output
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@pytest.mark.respx(base_url=settings.base_api_url)
|
|
37
|
+
def test_shows_a_message_if_no_teams(
|
|
38
|
+
logged_in_cli: None, respx_mock: respx.MockRouter, tmp_path: Path
|
|
39
|
+
) -> None:
|
|
40
|
+
respx_mock.get("/teams/").mock(return_value=Response(200, json={"data": []}))
|
|
41
|
+
|
|
42
|
+
with changing_dir(tmp_path):
|
|
43
|
+
result = runner.invoke(app, ["link"])
|
|
44
|
+
|
|
45
|
+
assert result.exit_code == 1
|
|
46
|
+
assert "No teams found" in result.output
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@pytest.mark.respx(base_url=settings.base_api_url)
|
|
50
|
+
def test_shows_a_message_if_no_apps(
|
|
51
|
+
logged_in_cli: None, respx_mock: respx.MockRouter, tmp_path: Path
|
|
52
|
+
) -> None:
|
|
53
|
+
steps = [Keys.ENTER]
|
|
54
|
+
|
|
55
|
+
respx_mock.get("/teams/").mock(
|
|
56
|
+
return_value=Response(
|
|
57
|
+
200, json={"data": [{"id": "team-1", "name": "My Team", "slug": "my-team"}]}
|
|
58
|
+
)
|
|
59
|
+
)
|
|
60
|
+
respx_mock.get("/apps/", params={"team_id": "team-1"}).mock(
|
|
61
|
+
return_value=Response(200, json={"data": []})
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
with (
|
|
65
|
+
changing_dir(tmp_path),
|
|
66
|
+
patch("rich_toolkit.container.getchar") as mock_getchar,
|
|
67
|
+
):
|
|
68
|
+
mock_getchar.side_effect = steps
|
|
69
|
+
result = runner.invoke(app, ["link"])
|
|
70
|
+
|
|
71
|
+
assert result.exit_code == 1
|
|
72
|
+
assert "No apps found in this team." in result.output
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@pytest.mark.respx(base_url=settings.base_api_url)
|
|
76
|
+
def test_links_successfully(
|
|
77
|
+
logged_in_cli: None, respx_mock: respx.MockRouter, tmp_path: Path
|
|
78
|
+
) -> None:
|
|
79
|
+
steps = [Keys.ENTER, Keys.ENTER]
|
|
80
|
+
|
|
81
|
+
respx_mock.get("/teams/").mock(
|
|
82
|
+
return_value=Response(
|
|
83
|
+
200, json={"data": [{"id": "team-1", "name": "My Team", "slug": "my-team"}]}
|
|
84
|
+
)
|
|
85
|
+
)
|
|
86
|
+
respx_mock.get("/apps/", params={"team_id": "team-1"}).mock(
|
|
87
|
+
return_value=Response(200, json={"data": [{"id": "app-1", "slug": "my-app"}]})
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
with (
|
|
91
|
+
changing_dir(tmp_path),
|
|
92
|
+
patch("rich_toolkit.container.getchar") as mock_getchar,
|
|
93
|
+
):
|
|
94
|
+
mock_getchar.side_effect = steps
|
|
95
|
+
result = runner.invoke(app, ["link"])
|
|
96
|
+
|
|
97
|
+
assert result.exit_code == 0
|
|
98
|
+
assert "Successfully linked to app" in result.output
|
|
99
|
+
assert "my-app" in result.output
|
|
100
|
+
|
|
101
|
+
config_path = tmp_path / ".fastapicloud" / "cloud.json"
|
|
102
|
+
assert config_path.exists()
|
|
103
|
+
config = AppConfig.model_validate_json(config_path.read_text())
|
|
104
|
+
assert config.app_id == "app-1"
|
|
105
|
+
assert config.team_id == "team-1"
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
@pytest.mark.respx(base_url=settings.base_api_url)
|
|
109
|
+
def test_shows_error_on_teams_api_failure(
|
|
110
|
+
logged_in_cli: None, respx_mock: respx.MockRouter, tmp_path: Path
|
|
111
|
+
) -> None:
|
|
112
|
+
respx_mock.get("/teams/").mock(return_value=Response(500))
|
|
113
|
+
|
|
114
|
+
with changing_dir(tmp_path):
|
|
115
|
+
result = runner.invoke(app, ["link"])
|
|
116
|
+
|
|
117
|
+
assert result.exit_code == 1
|
|
118
|
+
assert "Error fetching teams" in result.output
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
@pytest.mark.respx(base_url=settings.base_api_url)
|
|
122
|
+
def test_shows_error_on_apps_api_failure(
|
|
123
|
+
logged_in_cli: None, respx_mock: respx.MockRouter, tmp_path: Path
|
|
124
|
+
) -> None:
|
|
125
|
+
steps = [Keys.ENTER]
|
|
126
|
+
|
|
127
|
+
respx_mock.get("/teams/").mock(
|
|
128
|
+
return_value=Response(
|
|
129
|
+
200, json={"data": [{"id": "team-1", "name": "My Team", "slug": "my-team"}]}
|
|
130
|
+
)
|
|
131
|
+
)
|
|
132
|
+
respx_mock.get("/apps/", params={"team_id": "team-1"}).mock(
|
|
133
|
+
return_value=Response(500)
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
with (
|
|
137
|
+
changing_dir(tmp_path),
|
|
138
|
+
patch("rich_toolkit.container.getchar") as mock_getchar,
|
|
139
|
+
):
|
|
140
|
+
mock_getchar.side_effect = steps
|
|
141
|
+
result = runner.invoke(app, ["link"])
|
|
142
|
+
|
|
143
|
+
assert result.exit_code == 1
|
|
144
|
+
assert "Error fetching apps" in result.output
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
@pytest.mark.respx(base_url=settings.base_api_url)
|
|
148
|
+
def test_links_with_multiple_teams_and_apps(
|
|
149
|
+
logged_in_cli: None, respx_mock: respx.MockRouter, tmp_path: Path
|
|
150
|
+
) -> None:
|
|
151
|
+
steps = [Keys.DOWN_ARROW, Keys.ENTER, Keys.DOWN_ARROW, Keys.ENTER]
|
|
152
|
+
|
|
153
|
+
respx_mock.get("/teams/").mock(
|
|
154
|
+
return_value=Response(
|
|
155
|
+
200,
|
|
156
|
+
json={
|
|
157
|
+
"data": [
|
|
158
|
+
{"id": "team-1", "name": "Team One", "slug": "team-one"},
|
|
159
|
+
{"id": "team-2", "name": "Team Two", "slug": "team-two"},
|
|
160
|
+
]
|
|
161
|
+
},
|
|
162
|
+
)
|
|
163
|
+
)
|
|
164
|
+
respx_mock.get("/apps/", params={"team_id": "team-2"}).mock(
|
|
165
|
+
return_value=Response(
|
|
166
|
+
200,
|
|
167
|
+
json={
|
|
168
|
+
"data": [
|
|
169
|
+
{"id": "app-1", "slug": "first-app"},
|
|
170
|
+
{"id": "app-2", "slug": "second-app"},
|
|
171
|
+
]
|
|
172
|
+
},
|
|
173
|
+
)
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
with (
|
|
177
|
+
changing_dir(tmp_path),
|
|
178
|
+
patch("rich_toolkit.container.getchar") as mock_getchar,
|
|
179
|
+
):
|
|
180
|
+
mock_getchar.side_effect = steps
|
|
181
|
+
result = runner.invoke(app, ["link"])
|
|
182
|
+
|
|
183
|
+
assert result.exit_code == 0
|
|
184
|
+
assert "Successfully linked to app" in result.output
|
|
185
|
+
assert "second-app" in result.output
|
|
186
|
+
|
|
187
|
+
config_path = tmp_path / ".fastapicloud" / "cloud.json"
|
|
188
|
+
assert config_path.exists()
|
|
189
|
+
config = AppConfig.model_validate_json(config_path.read_text())
|
|
190
|
+
assert config.app_id == "app-2"
|
|
191
|
+
assert config.team_id == "team-2"
|
|
@@ -19,6 +19,11 @@ from fastapi_cloud_cli.commands.deploy import DeploymentStatus, _should_exclude_
|
|
|
19
19
|
Path(".venv"),
|
|
20
20
|
Path("__pycache__"),
|
|
21
21
|
Path("module.pyc"),
|
|
22
|
+
Path("/project/.env"),
|
|
23
|
+
Path("/project/.env.local"),
|
|
24
|
+
Path("/project/.env.production"),
|
|
25
|
+
Path(".env"),
|
|
26
|
+
Path(".env.development"),
|
|
22
27
|
],
|
|
23
28
|
)
|
|
24
29
|
def test_excludes_paths(path: Path) -> None:
|
|
@@ -37,6 +42,8 @@ def test_excludes_paths(path: Path) -> None:
|
|
|
37
42
|
Path("/project/src/module.pyx"), # similar to .pyc but different
|
|
38
43
|
Path("/project/config.json"),
|
|
39
44
|
Path("/project/README.md"),
|
|
45
|
+
Path("/project/.envrc"), # not a .env file
|
|
46
|
+
Path("/project/env.py"), # not a .env file
|
|
40
47
|
],
|
|
41
48
|
)
|
|
42
49
|
def test_includes_paths(path: Path) -> None:
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.10.1"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fastapi_cloud_cli-0.10.1 → fastapi_cloud_cli-0.11.0}/src/fastapi_cloud_cli/commands/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{fastapi_cloud_cli-0.10.1 → fastapi_cloud_cli-0.11.0}/src/fastapi_cloud_cli/commands/login.py
RENAMED
|
File without changes
|
{fastapi_cloud_cli-0.10.1 → fastapi_cloud_cli-0.11.0}/src/fastapi_cloud_cli/commands/logout.py
RENAMED
|
File without changes
|
{fastapi_cloud_cli-0.10.1 → fastapi_cloud_cli-0.11.0}/src/fastapi_cloud_cli/commands/logs.py
RENAMED
|
File without changes
|
{fastapi_cloud_cli-0.10.1 → fastapi_cloud_cli-0.11.0}/src/fastapi_cloud_cli/commands/unlink.py
RENAMED
|
File without changes
|
{fastapi_cloud_cli-0.10.1 → fastapi_cloud_cli-0.11.0}/src/fastapi_cloud_cli/commands/whoami.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fastapi_cloud_cli-0.10.1 → fastapi_cloud_cli-0.11.0}/src/fastapi_cloud_cli/utils/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fastapi_cloud_cli-0.10.1 → fastapi_cloud_cli-0.11.0}/tests/assets/broken_package/mod/__init__.py
RENAMED
|
File without changes
|
{fastapi_cloud_cli-0.10.1 → fastapi_cloud_cli-0.11.0}/tests/assets/broken_package/mod/app.py
RENAMED
|
File without changes
|
|
File without changes
|
{fastapi_cloud_cli-0.10.1 → fastapi_cloud_cli-0.11.0}/tests/assets/default_files/default_api/api.py
RENAMED
|
File without changes
|
{fastapi_cloud_cli-0.10.1 → fastapi_cloud_cli-0.11.0}/tests/assets/default_files/default_app/api.py
RENAMED
|
File without changes
|
{fastapi_cloud_cli-0.10.1 → fastapi_cloud_cli-0.11.0}/tests/assets/default_files/default_app/app.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fastapi_cloud_cli-0.10.1 → fastapi_cloud_cli-0.11.0}/tests/assets/default_files/default_main/api.py
RENAMED
|
File without changes
|
{fastapi_cloud_cli-0.10.1 → fastapi_cloud_cli-0.11.0}/tests/assets/default_files/default_main/app.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|