Skip to content

Commit 12b817e

Browse files
Merge branch 'main' into logs_command
2 parents e839475 + 6f68eb5 commit 12b817e

15 files changed

Lines changed: 577 additions & 87 deletions

File tree

.github/workflows/lint.yml

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
name: Lint
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
pull_request:
8+
types:
9+
- opened
10+
- synchronize
11+
12+
jobs:
13+
lint:
14+
runs-on: ubuntu-latest
15+
steps:
16+
- uses: actions/checkout@v5
17+
- name: Set up Python
18+
uses: actions/setup-python@v6
19+
with:
20+
python-version-file: "pyproject.toml"
21+
- name: Install uv
22+
uses: astral-sh/setup-uv@v7
23+
- name: Install Dependencies
24+
run: uv sync --locked --all-extras --dev
25+
- name: Lint
26+
run: uv run --no-sync scripts/lint.sh
27+
28+
# https://github.com/marketplace/actions/alls-green#why
29+
lint-alls-green: # This job does nothing and is only used for the branch protection
30+
if: always()
31+
needs:
32+
- lint
33+
runs-on: ubuntu-latest
34+
steps:
35+
- name: Decide whether the needed jobs succeeded or failed
36+
uses: re-actors/alls-green@release/v1
37+
with:
38+
jobs: ${{ toJSON(needs) }}

release-notes.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,25 @@
22

33
## Latest Changes
44

5+
## 0.9.0
6+
7+
### Features
8+
9+
* ✨ Add support for app ID in `fastapi deploy`. PR [#144](https://github.com/fastapilabs/fastapi-cloud-cli/pull/144) by [@buurro](https://github.com/buurro).
10+
* ✨ Add support for FASTAPI_CLOUD_TOKEN environment variable auth. PR [#142](https://github.com/fastapilabs/fastapi-cloud-cli/pull/142) by [@patrick91](https://github.com/patrick91).
11+
* ✨ Add a confirmation step to the first CLI deployment . PR [#140](https://github.com/fastapilabs/fastapi-cloud-cli/pull/140) by [@alejsdev](https://github.com/alejsdev).
12+
13+
### Fixes
14+
15+
* 🐛 Avoid archiving `.git` by default. PR [#143](https://github.com/fastapilabs/fastapi-cloud-cli/pull/143) by [@buurro](https://github.com/buurro).
16+
17+
### Refactors
18+
19+
* ♻️ Refactor authentication. PR [#141](https://github.com/fastapilabs/fastapi-cloud-cli/pull/141) by [@patrick91](https://github.com/patrick91).
20+
521
### Internal
622

23+
* 🔧 Add GitHub Actions workflow for linting. PR [#145](https://github.com/fastapilabs/fastapi-cloud-cli/pull/145) by [@patrick91](https://github.com/patrick91).
724
* 👷 Add pre-commit workflow. PR [#135](https://github.com/fastapilabs/fastapi-cloud-cli/pull/135) by [@YuriiMotov](https://github.com/YuriiMotov).
825

926
## 0.8.0

src/fastapi_cloud_cli/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "0.8.0"
1+
__version__ = "0.9.0"

src/fastapi_cloud_cli/commands/deploy.py

Lines changed: 84 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
from fastapi_cloud_cli.commands.login import login
2222
from fastapi_cloud_cli.utils.api import APIClient, StreamLogError, TooManyRetriesError
2323
from fastapi_cloud_cli.utils.apps import AppConfig, get_app_config, write_app_config
24-
from fastapi_cloud_cli.utils.auth import is_logged_in
24+
from fastapi_cloud_cli.utils.auth import Identity
2525
from fastapi_cloud_cli.utils.cli import get_rich_toolkit, handle_http_errors
2626

2727
logger = logging.getLogger(__name__)
@@ -51,6 +51,7 @@ def _should_exclude_entry(path: Path) -> bool:
5151
"__pycache__",
5252
".mypy_cache",
5353
".pytest_cache",
54+
".git",
5455
".gitignore",
5556
".fastapicloudignore",
5657
]
@@ -294,6 +295,8 @@ def _configure_app(toolkit: RichToolkit, path_to_deploy: Path) -> AppConfig:
294295

295296
toolkit.print_line()
296297

298+
selected_app: Optional[AppResponse] = None
299+
297300
if not create_new_app:
298301
with toolkit.progress("Fetching apps...") as progress:
299302
with handle_http_errors(
@@ -310,18 +313,45 @@ def _configure_app(toolkit: RichToolkit, path_to_deploy: Path) -> AppConfig:
310313

311314
raise typer.Exit(1)
312315

313-
app = toolkit.ask(
316+
selected_app = toolkit.ask(
314317
"Select the app you want to deploy to:",
315318
options=[Option({"name": app.slug, "value": app}) for app in apps],
316319
)
317-
else:
318-
app_name = toolkit.input(
320+
321+
app_name = (
322+
selected_app.slug
323+
if selected_app
324+
else toolkit.input(
319325
title="What's your app name?",
320326
default=_get_app_name(path_to_deploy),
321327
)
328+
)
322329

323-
toolkit.print_line()
330+
toolkit.print_line()
331+
332+
toolkit.print("Deployment configuration:", tag="summary")
333+
toolkit.print_line()
334+
toolkit.print(f"Team: [bold]{team.name}[/bold]")
335+
toolkit.print(f"App name: [bold]{app_name}[/bold]")
336+
toolkit.print_line()
337+
338+
choice = toolkit.ask(
339+
"Does everything look right?",
340+
tag="confirm",
341+
options=[
342+
Option({"name": "Yes, start the deployment!", "value": "deploy"}),
343+
Option({"name": "No, let me start over", "value": "cancel"}),
344+
],
345+
)
346+
toolkit.print_line()
347+
348+
if choice == "cancel":
349+
toolkit.print("Deployment cancelled.")
350+
raise typer.Exit(0)
324351

352+
if selected_app: # pragma: no cover
353+
app = selected_app
354+
else:
325355
with toolkit.progress(title="Creating app...") as progress:
326356
with handle_http_errors(progress):
327357
app = _create_app(team.id, app_name)
@@ -527,15 +557,27 @@ def deploy(
527557
skip_wait: Annotated[
528558
bool, typer.Option("--no-wait", help="Skip waiting for deployment status")
529559
] = False,
560+
provided_app_id: Annotated[
561+
Union[str, None],
562+
typer.Option(
563+
"--app-id",
564+
help="Application ID to deploy to",
565+
envvar="FASTAPI_CLOUD_APP_ID",
566+
),
567+
] = None,
530568
) -> Any:
531569
"""
532570
Deploy a [bold]FastAPI[/bold] app to FastAPI Cloud. 🚀
533571
"""
534572
logger.debug("Deploy command started")
535-
logger.debug("Deploy path: %s, skip_wait: %s", path, skip_wait)
573+
logger.debug(
574+
"Deploy path: %s, skip_wait: %s, app_id: %s", path, skip_wait, provided_app_id
575+
)
576+
577+
identity = Identity()
536578

537579
with get_rich_toolkit() as toolkit:
538-
if not is_logged_in():
580+
if not identity.is_logged_in():
539581
logger.debug("User not logged in, prompting for login or waitlist")
540582

541583
toolkit.print_title("Welcome to FastAPI Cloud!", tag="FastAPI")
@@ -572,19 +614,43 @@ def deploy(
572614

573615
app_config = get_app_config(path_to_deploy)
574616

575-
if not app_config:
617+
if app_config and provided_app_id and app_config.app_id != provided_app_id:
618+
toolkit.print(
619+
f"[error]Error: Provided app ID ({provided_app_id}) does not match the local "
620+
f"config ({app_config.app_id}).[/]"
621+
)
622+
toolkit.print_line()
623+
toolkit.print(
624+
"Run [bold]fastapi cloud unlink[/] to remove the local config, "
625+
"or remove --app-id / unset FASTAPI_CLOUD_APP_ID to use the configured app.",
626+
tag="tip",
627+
)
628+
629+
raise typer.Exit(1) from None
630+
631+
if provided_app_id:
632+
target_app_id = provided_app_id
633+
elif app_config:
634+
target_app_id = app_config.app_id
635+
else:
576636
logger.debug("No app config found, configuring new app")
637+
577638
app_config = _configure_app(toolkit, path_to_deploy=path_to_deploy)
578639
toolkit.print_line()
640+
641+
target_app_id = app_config.app_id
642+
643+
if provided_app_id:
644+
toolkit.print(f"Deploying to app [blue]{target_app_id}[/blue]...")
579645
else:
580-
logger.debug("Existing app config found, proceeding with deployment")
581646
toolkit.print("Deploying app...")
582-
toolkit.print_line()
647+
648+
toolkit.print_line()
583649

584650
with toolkit.progress("Checking app...", transient=True) as progress:
585651
with handle_http_errors(progress):
586-
logger.debug("Checking app with ID: %s", app_config.app_id)
587-
app = _get_app(app_config.app_id)
652+
logger.debug("Checking app with ID: %s", target_app_id)
653+
app = _get_app(target_app_id)
588654

589655
if not app:
590656
logger.debug("App not found in API")
@@ -594,10 +660,12 @@ def deploy(
594660

595661
if not app:
596662
toolkit.print_line()
597-
toolkit.print(
598-
"If you deleted this app, you can run [bold]fastapi cloud unlink[/] to unlink the local configuration.",
599-
tag="tip",
600-
)
663+
664+
if not provided_app_id:
665+
toolkit.print(
666+
"If you deleted this app, you can run [bold]fastapi cloud unlink[/] to unlink the local configuration.",
667+
tag="tip",
668+
)
601669
raise typer.Exit(1)
602670

603671
with tempfile.TemporaryDirectory() as temp_dir:

src/fastapi_cloud_cli/commands/env.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
from fastapi_cloud_cli.utils.api import APIClient
99
from fastapi_cloud_cli.utils.apps import get_app_config
10-
from fastapi_cloud_cli.utils.auth import is_logged_in
10+
from fastapi_cloud_cli.utils.auth import Identity
1111
from fastapi_cloud_cli.utils.cli import get_rich_toolkit, handle_http_errors
1212
from fastapi_cloud_cli.utils.env import validate_environment_variable_name
1313

@@ -70,8 +70,10 @@ def list(
7070
List the environment variables for the app.
7171
"""
7272

73+
identity = Identity()
74+
7375
with get_rich_toolkit(minimal=True) as toolkit:
74-
if not is_logged_in():
76+
if not identity.is_logged_in():
7577
toolkit.print(
7678
"No credentials found. Use [blue]`fastapi login`[/] to login.",
7779
tag="auth",
@@ -123,9 +125,10 @@ def delete(
123125
Delete an environment variable from the app.
124126
"""
125127

128+
identity = Identity()
129+
126130
with get_rich_toolkit(minimal=True) as toolkit:
127-
# TODO: maybe this logic can be extracted to a function
128-
if not is_logged_in():
131+
if not identity.is_logged_in():
129132
toolkit.print(
130133
"No credentials found. Use [blue]`fastapi login`[/] to login.",
131134
tag="auth",
@@ -208,8 +211,10 @@ def set(
208211
Set an environment variable for the app.
209212
"""
210213

214+
identity = Identity()
215+
211216
with get_rich_toolkit(minimal=True) as toolkit:
212-
if not is_logged_in():
217+
if not identity.is_logged_in():
213218
toolkit.print(
214219
"No credentials found. Use [blue]`fastapi login`[/] to login.",
215220
tag="auth",

src/fastapi_cloud_cli/commands/login.py

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,7 @@
88

99
from fastapi_cloud_cli.config import Settings
1010
from fastapi_cloud_cli.utils.api import APIClient
11-
from fastapi_cloud_cli.utils.auth import (
12-
AuthConfig,
13-
get_auth_token,
14-
is_logged_in,
15-
is_token_expired,
16-
write_auth_config,
17-
)
11+
from fastapi_cloud_cli.utils.auth import AuthConfig, Identity, write_auth_config
1812
from fastapi_cloud_cli.utils.cli import get_rich_toolkit, handle_http_errors
1913

2014
logger = logging.getLogger(__name__)
@@ -82,13 +76,14 @@ def login() -> Any:
8276
"""
8377
Login to FastAPI Cloud. 🚀
8478
"""
85-
token = get_auth_token()
86-
if token is not None and is_token_expired(token):
79+
identity = Identity()
80+
81+
if identity.is_expired():
8782
with get_rich_toolkit(minimal=True) as toolkit:
8883
toolkit.print("Your session has expired. Logging in again...")
8984
toolkit.print_line()
9085

91-
if is_logged_in():
86+
if identity.is_logged_in():
9287
with get_rich_toolkit(minimal=True) as toolkit:
9388
toolkit.print("You are already logged in.")
9489
toolkit.print(

src/fastapi_cloud_cli/commands/whoami.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,20 @@
55
from rich_toolkit.progress import Progress
66

77
from fastapi_cloud_cli.utils.api import APIClient
8-
from fastapi_cloud_cli.utils.auth import is_logged_in
8+
from fastapi_cloud_cli.utils.auth import Identity
99
from fastapi_cloud_cli.utils.cli import handle_http_errors
1010

1111
logger = logging.getLogger(__name__)
1212

1313

1414
def whoami() -> Any:
15-
if not is_logged_in():
15+
identity = Identity()
16+
17+
if identity.auth_mode == "token":
18+
print("⚡ [bold]Using API token from environment variable[/bold]")
19+
return
20+
21+
if not identity.is_logged_in():
1622
print("No credentials found. Use [blue]`fastapi login`[/] to login.")
1723
return
1824

src/fastapi_cloud_cli/utils/api.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@
2020

2121
from fastapi_cloud_cli import __version__
2222
from fastapi_cloud_cli.config import Settings
23-
from fastapi_cloud_cli.utils.auth import get_auth_token
23+
24+
from .auth import Identity
2425

2526
logger = logging.getLogger(__name__)
2627

@@ -140,14 +141,13 @@ def wrapper(*args: P.args, **kwargs: P.kwargs) -> Generator[T, None, None]:
140141
class APIClient(httpx.Client):
141142
def __init__(self) -> None:
142143
settings = Settings.get()
143-
144-
token = get_auth_token()
144+
identity = Identity()
145145

146146
super().__init__(
147147
base_url=settings.base_api_url,
148148
timeout=httpx.Timeout(20),
149149
headers={
150-
"Authorization": f"Bearer {token}",
150+
"Authorization": f"Bearer {identity.token}",
151151
"User-Agent": f"fastapi-cloud-cli/{__version__}",
152152
},
153153
)

0 commit comments

Comments
 (0)