Skip to content

Commit 4a7422c

Browse files
committed
Only use deploy token for deploy command
1 parent 2036a4f commit 4a7422c

11 files changed

Lines changed: 146 additions & 74 deletions

File tree

src/fastapi_cloud_cli/commands/deploy.py

Lines changed: 41 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
TooManyRetriesError,
2929
)
3030
from fastapi_cloud_cli.utils.apps import AppConfig, get_app_config, write_app_config
31-
from fastapi_cloud_cli.utils.auth import Identity
31+
from fastapi_cloud_cli.utils.auth import AuthMode, Identity
3232
from fastapi_cloud_cli.utils.cli import get_rich_toolkit, handle_http_errors
3333
from fastapi_cloud_cli.utils.progress_file import ProgressFile
3434

@@ -72,7 +72,7 @@ def _cancel_upload(deployment_id: str) -> None:
7272
logger.debug("Cancelling upload for deployment: %s", deployment_id)
7373

7474
try:
75-
with APIClient() as client:
75+
with APIClient(use_deploy_token=True) as client:
7676
response = client.post(f"/deployments/{deployment_id}/upload-cancelled")
7777
response.raise_for_status()
7878

@@ -142,7 +142,7 @@ class Team(BaseModel):
142142

143143

144144
def _get_teams() -> list[Team]:
145-
with APIClient() as client:
145+
with APIClient(use_deploy_token=True) as client:
146146
response = client.get("/teams/")
147147
response.raise_for_status()
148148

@@ -158,7 +158,7 @@ class AppResponse(BaseModel):
158158

159159

160160
def _update_app(app_id: str, directory: str | None) -> AppResponse:
161-
with APIClient() as client:
161+
with APIClient(use_deploy_token=True) as client:
162162
response = client.patch(
163163
f"/apps/{app_id}",
164164
json={"directory": directory},
@@ -170,7 +170,7 @@ def _update_app(app_id: str, directory: str | None) -> AppResponse:
170170

171171

172172
def _create_app(team_id: str, app_name: str, directory: str | None) -> AppResponse:
173-
with APIClient() as client:
173+
with APIClient(use_deploy_token=True) as client:
174174
response = client.post(
175175
"/apps/",
176176
json={"name": app_name, "team_id": team_id, "directory": directory},
@@ -191,7 +191,7 @@ class CreateDeploymentResponse(BaseModel):
191191

192192

193193
def _create_deployment(app_id: str) -> CreateDeploymentResponse:
194-
with APIClient() as client:
194+
with APIClient(use_deploy_token=True) as client:
195195
response = client.post(f"/apps/{app_id}/deployments/")
196196
response.raise_for_status()
197197

@@ -230,7 +230,7 @@ def progress_callback(bytes_read: int) -> None:
230230
f"Uploading deployment ({_format_size(bytes_read)} of {archive_size_str})..."
231231
)
232232

233-
with APIClient() as fastapi_client, Client() as client:
233+
with APIClient(use_deploy_token=True) as fastapi_client, Client() as client:
234234
# Get the upload URL
235235
logger.debug("Requesting upload URL from API")
236236
response = fastapi_client.post(f"/deployments/{deployment_id}/upload")
@@ -264,7 +264,7 @@ def progress_callback(bytes_read: int) -> None:
264264

265265

266266
def _get_app(app_slug: str) -> AppResponse | None:
267-
with APIClient() as client:
267+
with APIClient(use_deploy_token=True) as client:
268268
response = client.get(f"/apps/{app_slug}")
269269

270270
if response.status_code == 404:
@@ -278,7 +278,7 @@ def _get_app(app_slug: str) -> AppResponse | None:
278278

279279

280280
def _get_apps(team_id: str) -> list[AppResponse]:
281-
with APIClient() as client:
281+
with APIClient(use_deploy_token=True) as client:
282282
response = client.get("/apps/", params={"team_id": team_id})
283283
response.raise_for_status()
284284

@@ -308,14 +308,20 @@ def _get_apps(team_id: str) -> list[AppResponse]:
308308
]
309309

310310

311-
def _configure_app(toolkit: RichToolkit, path_to_deploy: Path) -> AppConfig:
311+
def _configure_app(
312+
toolkit: RichToolkit,
313+
path_to_deploy: Path,
314+
auth_mode: AuthMode = "user",
315+
) -> AppConfig:
312316
toolkit.print(f"Setting up and deploying [blue]{path_to_deploy}[/blue]", tag="path")
313317

314318
toolkit.print_line()
315319

316320
with toolkit.progress("Fetching teams...") as progress:
317321
with handle_http_errors(
318-
progress, default_message="Error fetching teams. Please try again later."
322+
progress,
323+
default_message="Error fetching teams. Please try again later.",
324+
auth_mode=auth_mode,
319325
):
320326
teams = _get_teams()
321327

@@ -341,7 +347,9 @@ def _configure_app(toolkit: RichToolkit, path_to_deploy: Path) -> AppConfig:
341347
if not create_new_app:
342348
with toolkit.progress("Fetching apps...") as progress:
343349
with handle_http_errors(
344-
progress, default_message="Error fetching apps. Please try again later."
350+
progress,
351+
default_message="Error fetching apps. Please try again later.",
352+
auth_mode=auth_mode,
345353
):
346354
apps = _get_apps(team.id)
347355

@@ -411,7 +419,7 @@ def _configure_app(toolkit: RichToolkit, path_to_deploy: Path) -> AppConfig:
411419
if directory != selected_app.directory:
412420
with (
413421
toolkit.progress(title="Updating app directory...") as progress,
414-
handle_http_errors(progress),
422+
handle_http_errors(progress, auth_mode=auth_mode),
415423
):
416424
app = _update_app(selected_app.id, directory=directory)
417425

@@ -420,7 +428,7 @@ def _configure_app(toolkit: RichToolkit, path_to_deploy: Path) -> AppConfig:
420428
app = selected_app
421429
else:
422430
with toolkit.progress(title="Creating app...") as progress:
423-
with handle_http_errors(progress):
431+
with handle_http_errors(progress, auth_mode=auth_mode):
424432
app = _create_app(team.id, app_name, directory=directory)
425433

426434
progress.log(f"App created successfully! App slug: {app.slug}")
@@ -485,7 +493,7 @@ def _wait_for_deployment(
485493

486494
last_message_changed_at = time.monotonic()
487495

488-
with APIClient() as client:
496+
with APIClient(use_deploy_token=True) as client:
489497
with (
490498
toolkit.progress(
491499
next(messages),
@@ -556,7 +564,7 @@ def _send_waitlist_form(
556564
toolkit: RichToolkit,
557565
) -> None:
558566
with toolkit.progress("Sending your request...") as progress:
559-
with APIClient() as client:
567+
with APIClient(use_deploy_token=True) as client:
560568
with handle_http_errors(progress):
561569
response = client.post("/users/waiting-list", json=result.model_dump())
562570

@@ -674,15 +682,18 @@ def deploy(
674682
)
675683

676684
identity = Identity()
685+
use_deploy = identity.has_deploy_token()
686+
has_auth = use_deploy or identity.is_logged_in()
687+
auth_mode: AuthMode = "token" if use_deploy else "user"
677688

678689
with get_rich_toolkit() as toolkit:
679-
if not identity.is_logged_in():
690+
if not has_auth:
680691
logger.debug("User not logged in, prompting for login or waitlist")
681692

682693
toolkit.print_title("Welcome to FastAPI Cloud!", tag="FastAPI")
683694
toolkit.print_line()
684695

685-
if identity.token and identity.is_expired():
696+
if identity.user_token and identity.is_user_token_expired():
686697
toolkit.print(
687698
"Your session has expired. Please log in again.",
688699
tag="info",
@@ -709,6 +720,13 @@ def deploy(
709720
_waitlist_form(toolkit)
710721
raise typer.Exit(1)
711722

723+
if use_deploy:
724+
toolkit.print(
725+
"Using token from [bold blue]FASTAPI_CLOUD_TOKEN[/] environment variable",
726+
tag="info",
727+
)
728+
toolkit.print_line()
729+
712730
toolkit.print_title("Starting deployment", tag="FastAPI")
713731
toolkit.print_line()
714732

@@ -738,7 +756,9 @@ def deploy(
738756
else:
739757
logger.debug("No app config found, configuring new app")
740758

741-
app_config = _configure_app(toolkit, path_to_deploy=path_to_deploy)
759+
app_config = _configure_app(
760+
toolkit, path_to_deploy=path_to_deploy, auth_mode=auth_mode
761+
)
742762
toolkit.print_line()
743763

744764
target_app_id = app_config.app_id
@@ -751,7 +771,7 @@ def deploy(
751771
toolkit.print_line()
752772

753773
with toolkit.progress("Checking app...", transient=True) as progress:
754-
with handle_http_errors(progress):
774+
with handle_http_errors(progress, auth_mode=auth_mode):
755775
logger.debug("Checking app with ID: %s", target_app_id)
756776
app = _get_app(target_app_id)
757777

@@ -780,7 +800,7 @@ def deploy(
780800
toolkit.progress(
781801
title="Creating deployment", done_emoji="📦"
782802
) as progress,
783-
handle_http_errors(progress),
803+
handle_http_errors(progress, auth_mode=auth_mode),
784804
):
785805
logger.debug("Creating deployment for app: %s", app.id)
786806
deployment = _create_deployment(app.id)

src/fastapi_cloud_cli/commands/login.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,15 @@ def login() -> Any:
8787

8888
return
8989

90+
if identity.has_deploy_token():
91+
with get_rich_toolkit(minimal=True) as toolkit:
92+
toolkit.print(
93+
"You have [bold blue]FASTAPI_CLOUD_TOKEN[/] environment variable set.\n"
94+
"This token will take precedence over the user token for "
95+
"[blue]`fastapi deploy`[/] command.",
96+
tag="Warning",
97+
)
98+
9099
with get_rich_toolkit() as toolkit, APIClient() as client:
91100
toolkit.print_title("Login to FastAPI Cloud", tag="FastAPI")
92101

src/fastapi_cloud_cli/commands/whoami.py

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -14,20 +14,21 @@
1414
def whoami() -> Any:
1515
identity = Identity()
1616

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

src/fastapi_cloud_cli/utils/api.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
from fastapi_cloud_cli import __version__
2020
from fastapi_cloud_cli.config import Settings
2121

22-
from .auth import Identity
22+
from .auth import AuthMode, Identity
2323

2424
logger = logging.getLogger(__name__)
2525

@@ -195,15 +195,24 @@ def to_human_readable(cls, status: "DeploymentStatus") -> str:
195195

196196

197197
class APIClient(httpx.Client):
198-
def __init__(self) -> None:
198+
auth_mode: AuthMode
199+
200+
def __init__(self, use_deploy_token: bool = False) -> None:
199201
settings = Settings.get()
200202
identity = Identity()
201203

204+
if use_deploy_token and identity.deploy_token:
205+
token = identity.deploy_token
206+
self.auth_mode = "token"
207+
else:
208+
token = identity.user_token
209+
self.auth_mode = "user"
210+
202211
super().__init__(
203212
base_url=settings.base_api_url,
204213
timeout=httpx.Timeout(20),
205214
headers={
206-
"Authorization": f"Bearer {identity.token}",
215+
"Authorization": f"Bearer {token}",
207216
"User-Agent": f"fastapi-cloud-cli/{__version__}",
208217
},
209218
)

src/fastapi_cloud_cli/utils/auth.py

Lines changed: 18 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212

1313
logger = logging.getLogger("fastapi_cli")
1414

15+
AuthMode = Literal["token", "user"]
16+
1517

1618
class AuthConfig(BaseModel):
1719
access_token: str
@@ -109,34 +111,28 @@ def _is_jwt_expired(token: str) -> bool:
109111

110112

111113
class Identity:
112-
auth_mode: Literal["token", "user"]
113-
114114
def __init__(self) -> None:
115-
self.token = _get_auth_token()
116-
self.auth_mode = "user"
115+
self._user_token = _get_auth_token()
116+
self._deploy_token: str | None = os.environ.get("FASTAPI_CLOUD_TOKEN")
117117

118-
# users using `FASTAPI_CLOUD_TOKEN`
119-
if env_token := self._get_token_from_env():
120-
self.token = env_token
121-
self.auth_mode = "token"
118+
@property
119+
def user_token(self) -> str | None:
120+
return self._user_token
122121

123-
def _get_token_from_env(self) -> str | None:
124-
return os.environ.get("FASTAPI_CLOUD_TOKEN")
122+
@property
123+
def deploy_token(self) -> str | None:
124+
return self._deploy_token
125125

126-
def is_expired(self) -> bool:
127-
if not self.token:
126+
def is_user_token_expired(self) -> bool:
127+
if not self._user_token:
128128
return True
129129

130-
return _is_jwt_expired(self.token)
130+
return _is_jwt_expired(self._user_token)
131131

132132
def is_logged_in(self) -> bool:
133-
if self.token is None:
134-
logger.debug("Login status: False (no token)")
135-
return False
133+
"""Whether there is a valid user token"""
134+
return self._user_token is not None and not self.is_user_token_expired()
136135

137-
if self.auth_mode == "user" and self.is_expired():
138-
logger.debug("Login status: False (token expired)")
139-
return False
140-
141-
logger.debug("Login status: True")
142-
return True
136+
def has_deploy_token(self) -> bool:
137+
"""Whether there is a deploy token"""
138+
return self._deploy_token is not None

0 commit comments

Comments
 (0)