Skip to content

Commit 18b4a43

Browse files
✨ Show if there's a new fastapi-cloud-cli version available (#201)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
1 parent ce2110b commit 18b4a43

10 files changed

Lines changed: 744 additions & 46 deletions

File tree

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ dependencies = [
3838
"pydantic[email] >= 2.12.0; python_version >= '3.14'",
3939
"sentry-sdk >= 2.20.0",
4040
"fastar >= 0.10.0",
41+
"detect-installer>=0.1.0",
4142
]
4243

4344
[project.optional-dependencies]

src/fastapi_cloud_cli/commands/login.py

Lines changed: 24 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -77,48 +77,50 @@ def login() -> Any:
7777
Login to FastAPI Cloud. 🚀
7878
"""
7979
identity = Identity()
80+
is_logged_in = identity.is_logged_in()
8081

81-
if identity.is_logged_in():
82-
with get_rich_toolkit(minimal=True) as toolkit:
82+
with get_rich_toolkit(minimal=is_logged_in) as toolkit:
83+
if is_logged_in:
8384
toolkit.print("You are already logged in.")
8485
toolkit.print(
8586
"Run [bold]fastapi cloud logout[/bold] first if you want to switch accounts."
8687
)
8788

88-
return
89+
return
8990

90-
if identity.has_deploy_token():
91-
with get_rich_toolkit() as toolkit:
91+
if identity.has_deploy_token():
9292
toolkit.print(
9393
"You have [bold blue]FASTAPI_CLOUD_TOKEN[/] environment variable set.\n"
9494
"This token will take precedence over the user token for "
9595
"[blue]`fastapi deploy`[/] command.",
9696
tag="Warning",
9797
)
9898

99-
with get_rich_toolkit() as toolkit, APIClient() as client:
100-
toolkit.print_title("Login to FastAPI Cloud", tag="FastAPI")
99+
with APIClient() as client:
100+
toolkit.print_title("Login to FastAPI Cloud", tag="FastAPI")
101101

102-
toolkit.print_line()
102+
toolkit.print_line()
103103

104-
with toolkit.progress("Starting authorization") as progress:
105-
with client.handle_http_errors(progress):
106-
authorization_data = _start_device_authorization(client)
104+
with toolkit.progress("Starting authorization") as progress:
105+
with client.handle_http_errors(progress):
106+
authorization_data = _start_device_authorization(client)
107107

108-
url = authorization_data.verification_uri_complete
108+
url = authorization_data.verification_uri_complete
109109

110-
progress.log(f"Opening [link={url}]{url}[/link]")
110+
progress.log(f"Opening [link={url}]{url}[/link]")
111111

112-
toolkit.print_line()
112+
toolkit.print_line()
113113

114-
with toolkit.progress("Waiting for user to authorize...") as progress:
115-
typer.launch(url)
114+
with toolkit.progress("Waiting for user to authorize...") as progress:
115+
typer.launch(url)
116116

117-
with client.handle_http_errors(progress):
118-
access_token = _fetch_access_token(
119-
client, authorization_data.device_code, authorization_data.interval
120-
)
117+
with client.handle_http_errors(progress):
118+
access_token = _fetch_access_token(
119+
client,
120+
authorization_data.device_code,
121+
authorization_data.interval,
122+
)
121123

122-
write_auth_config(AuthConfig(access_token=access_token))
124+
write_auth_config(AuthConfig(access_token=access_token))
123125

124-
progress.log("Now you are logged in! 🚀")
126+
progress.log("Now you are logged in! 🚀")
Lines changed: 25 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,37 @@
11
import logging
22
from typing import Any
33

4-
from rich import print
5-
from rich_toolkit.progress import Progress
6-
74
from fastapi_cloud_cli.utils.api import APIClient
85
from fastapi_cloud_cli.utils.auth import Identity
6+
from fastapi_cloud_cli.utils.cli import get_rich_toolkit
97

108
logger = logging.getLogger(__name__)
119

1210

1311
def whoami() -> Any:
1412
identity = Identity()
1513

16-
if not identity.is_logged_in():
17-
print("No credentials found. Use [blue]`fastapi login`[/] to login.")
18-
else:
19-
with APIClient() as client:
20-
with Progress(title="⚡ Fetching profile", transient=True) as progress:
21-
with client.handle_http_errors(progress, default_message=""):
22-
response = client.get("/users/me")
23-
response.raise_for_status()
24-
25-
data = response.json()
26-
27-
print(f"⚡ [bold]{data['email']}[/bold]")
28-
29-
if identity.has_deploy_token():
30-
print(
31-
"⚡ [bold]Using API token from environment variable for "
32-
"[blue]`fastapi deploy`[/blue] command.[/bold]"
33-
)
14+
with get_rich_toolkit(minimal=True) as toolkit:
15+
if not identity.is_logged_in():
16+
toolkit.print(
17+
"No credentials found. Use [blue]`fastapi login`[/] to login."
18+
)
19+
else:
20+
with APIClient() as client:
21+
with toolkit.progress(
22+
title="⚡ Fetching profile",
23+
transient=True,
24+
) as progress:
25+
with client.handle_http_errors(progress, default_message=""):
26+
response = client.get("/users/me")
27+
response.raise_for_status()
28+
29+
data = response.json()
30+
31+
toolkit.print(f"⚡ [bold]{data['email']}[/bold]")
32+
33+
if identity.has_deploy_token():
34+
toolkit.print(
35+
"⚡ [bold]Using API token from environment variable for "
36+
"[blue]`fastapi deploy`[/blue] command.[/bold]"
37+
)

src/fastapi_cloud_cli/utils/cli.py

Lines changed: 63 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,18 @@
11
import logging
2+
import os
3+
from types import TracebackType
24
from typing import Any, Literal
35

46
from rich.segment import Segment
7+
from rich.style import Style
8+
from rich.text import Text
59
from rich_toolkit import RichToolkit, RichToolkitTheme
6-
from rich_toolkit.styles import MinimalStyle, TaggedStyle
10+
from rich_toolkit.styles import BaseStyle, MinimalStyle, TaggedStyle
11+
12+
from fastapi_cloud_cli.utils.version_check import (
13+
DISABLE_VERSION_CHECK_ENV,
14+
BackgroundVersionCheck,
15+
)
716

817
logger = logging.getLogger(__name__)
918

@@ -20,10 +29,20 @@ def _get_tag_segments(
2029
animation_status: Literal["started", "stopped", "error"] | None = None,
2130
) -> tuple[list[Segment], int]:
2231
if not is_animated:
23-
return super()._get_tag_segments(
32+
tag_segments, left_padding = super()._get_tag_segments(
2433
metadata, is_animated, done, animation_status=animation_status
2534
)
2635

36+
tag_style = metadata.get("tag_style")
37+
38+
if isinstance(tag_style, (str, Style)):
39+
style = self.console.get_style(tag_style)
40+
tag_segments = [
41+
Segment(segment.text, style=style) for segment in tag_segments
42+
]
43+
44+
return tag_segments, left_padding
45+
2746
emojis = [
2847
"🥚",
2948
"🐣",
@@ -47,6 +66,46 @@ def _get_tag_segments(
4766
return [Segment(tag)], left_padding
4867

4968

69+
class FastAPIRichToolkit(RichToolkit):
70+
def __init__(
71+
self,
72+
style: BaseStyle | None = None,
73+
theme: RichToolkitTheme | None = None,
74+
) -> None:
75+
super().__init__(style=style, theme=theme)
76+
self._version_check = self._get_version_check()
77+
78+
def __exit__(
79+
self,
80+
exc_type: type[BaseException] | None,
81+
exc_value: BaseException | None,
82+
traceback: TracebackType | None,
83+
) -> bool | None:
84+
self._print_update_message()
85+
86+
return super().__exit__(
87+
exc_type,
88+
exc_value,
89+
traceback,
90+
)
91+
92+
def _get_version_check(self) -> BackgroundVersionCheck | None:
93+
if os.environ.get(DISABLE_VERSION_CHECK_ENV) == "1":
94+
return None
95+
96+
version_check = BackgroundVersionCheck()
97+
version_check.start()
98+
99+
return version_check
100+
101+
def _print_update_message(self) -> None:
102+
if self._version_check is None:
103+
return
104+
105+
if message := self._version_check.get_update_message():
106+
self.print(Text.from_markup(message), tag="update", tag_style="tag.update")
107+
108+
50109
def get_rich_toolkit(minimal: bool = False) -> RichToolkit:
51110
style = MinimalStyle() if minimal else FastAPIStyle(tag_width=11)
52111

@@ -55,6 +114,7 @@ def get_rich_toolkit(minimal: bool = False) -> RichToolkit:
55114
theme={
56115
"tag.title": "white on #009485",
57116
"tag": "white on #007166",
117+
"tag.update": "black on yellow",
58118
"placeholder": "grey62",
59119
"text": "white",
60120
"selected": "#007166",
@@ -65,4 +125,4 @@ def get_rich_toolkit(minimal: bool = False) -> RichToolkit:
65125
},
66126
)
67127

68-
return RichToolkit(theme=theme)
128+
return FastAPIRichToolkit(theme=theme)

src/fastapi_cloud_cli/utils/config.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,7 @@ def get_cli_config_path() -> Path:
2525
cli_config_path.parent.mkdir(parents=True, exist_ok=True)
2626

2727
return cli_config_path
28+
29+
30+
def get_version_check_cache_path() -> Path:
31+
return get_config_folder() / "version-check.json"

0 commit comments

Comments
 (0)