Skip to content

Commit 7a2529e

Browse files
authored
➖ Drop support for Python 3.9 (#156)
* ➖ Drop support for Python 3.9 * 🎨 Update types for Python 3.10
1 parent 5021ec3 commit 7a2529e

12 files changed

Lines changed: 70 additions & 525 deletions

File tree

.github/workflows/test.yml

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,9 @@ jobs:
3030
uv-resolution:
3131
- highest
3232
include:
33-
- python-version: "3.9"
34-
os: macos-latest
35-
uv-resolution: lowest-direct
3633
- python-version: "3.10"
3734
os: ubuntu-latest
38-
uv-resolution: highest
35+
uv-resolution: lowest-direct
3936
- python-version: "3.11"
4037
os: macos-latest
4138
uv-resolution: lowest-direct

.python-version

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
3.9
1+
3.10

pyproject.toml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ name = "fastapi-cloud-cli"
33
dynamic = ["version"]
44
description = "Deploy and manage FastAPI Cloud apps from the command line 🚀"
55
authors = [{ name = "Patrick Arminio", email = "patrick@fastapilabs.com" }]
6-
requires-python = ">=3.9"
6+
requires-python = ">=3.10"
77
readme = "README.md"
88
license = { text = "MIT" }
99
classifiers = [
@@ -22,7 +22,6 @@ classifiers = [
2222
"Intended Audience :: Developers",
2323
"License :: OSI Approved :: MIT License",
2424
"Programming Language :: Python :: 3 :: Only",
25-
"Programming Language :: Python :: 3.9",
2625
"Programming Language :: Python :: 3.10",
2726
"Programming Language :: Python :: 3.11",
2827
"Programming Language :: Python :: 3.12",

src/fastapi_cloud_cli/commands/deploy.py

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from itertools import cycle
99
from pathlib import Path, PurePosixPath
1010
from textwrap import dedent
11-
from typing import Annotated, Any, Optional, Union
11+
from typing import Annotated, Any
1212

1313
import fastar
1414
import rignore
@@ -28,7 +28,7 @@
2828
logger = logging.getLogger(__name__)
2929

3030

31-
def validate_app_directory(v: Optional[str]) -> Optional[str]:
31+
def validate_app_directory(v: str | None) -> str | None:
3232
if v is None:
3333
return None
3434

@@ -58,7 +58,7 @@ def validate_app_directory(v: Optional[str]) -> Optional[str]:
5858
return normalized
5959

6060

61-
AppDirectory = Annotated[Optional[str], AfterValidator(validate_app_directory)]
61+
AppDirectory = Annotated[str | None, AfterValidator(validate_app_directory)]
6262

6363

6464
def _cancel_upload(deployment_id: str) -> None:
@@ -147,10 +147,10 @@ def _get_teams() -> list[Team]:
147147
class AppResponse(BaseModel):
148148
id: str
149149
slug: str
150-
directory: Optional[str]
150+
directory: str | None
151151

152152

153-
def _update_app(app_id: str, directory: Optional[str]) -> AppResponse:
153+
def _update_app(app_id: str, directory: str | None) -> AppResponse:
154154
with APIClient() as client:
155155
response = client.patch(
156156
f"/apps/{app_id}",
@@ -162,7 +162,7 @@ def _update_app(app_id: str, directory: Optional[str]) -> AppResponse:
162162
return AppResponse.model_validate(response.json())
163163

164164

165-
def _create_app(team_id: str, app_name: str, directory: Optional[str]) -> AppResponse:
165+
def _create_app(team_id: str, app_name: str, directory: str | None) -> AppResponse:
166166
with APIClient() as client:
167167
response = client.post(
168168
"/apps/",
@@ -273,7 +273,7 @@ def _upload_deployment(deployment_id: str, archive_path: Path) -> None:
273273
logger.debug("Upload notification sent successfully")
274274

275275

276-
def _get_app(app_slug: str) -> Optional[AppResponse]:
276+
def _get_app(app_slug: str) -> AppResponse | None:
277277
with APIClient() as client:
278278
response = client.get(f"/apps/{app_slug}")
279279

@@ -345,7 +345,7 @@ def _configure_app(toolkit: RichToolkit, path_to_deploy: Path) -> AppConfig:
345345

346346
toolkit.print_line()
347347

348-
selected_app: Optional[AppResponse] = None
348+
selected_app: AppResponse | None = None
349349

350350
if not create_new_app:
351351
with toolkit.progress("Fetching apps...") as progress:
@@ -389,7 +389,7 @@ def _configure_app(toolkit: RichToolkit, path_to_deploy: Path) -> AppConfig:
389389
validator=TypeAdapter(AppDirectory),
390390
)
391391

392-
directory: Optional[str] = directory_input if directory_input else None
392+
directory: str | None = directory_input if directory_input else None
393393

394394
toolkit.print_line()
395395

@@ -518,13 +518,13 @@ def _wait_for_deployment(
518518

519519
class SignupToWaitingList(BaseModel):
520520
email: EmailStr
521-
name: Optional[str] = None
522-
organization: Optional[str] = None
523-
role: Optional[str] = None
524-
team_size: Optional[str] = None
525-
location: Optional[str] = None
526-
use_case: Optional[str] = None
527-
secret_code: Optional[str] = None
521+
name: str | None = None
522+
organization: str | None = None
523+
role: str | None = None
524+
team_size: str | None = None
525+
location: str | None = None
526+
use_case: str | None = None
527+
secret_code: str | None = None
528528

529529

530530
def _send_waitlist_form(
@@ -624,7 +624,7 @@ def _waitlist_form(toolkit: RichToolkit) -> None:
624624

625625
def deploy(
626626
path: Annotated[
627-
Union[Path, None],
627+
Path | None,
628628
typer.Argument(
629629
help="A path to the folder containing the app you want to deploy"
630630
),
@@ -633,7 +633,7 @@ def deploy(
633633
bool, typer.Option("--no-wait", help="Skip waiting for deployment status")
634634
] = False,
635635
provided_app_id: Annotated[
636-
Union[str, None],
636+
str | None,
637637
typer.Option(
638638
"--app-id",
639639
help="Application ID to deploy to",

src/fastapi_cloud_cli/commands/env.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import logging
22
from pathlib import Path
3-
from typing import Annotated, Any, Optional, Union
3+
from typing import Annotated, Any
44

55
import typer
66
from pydantic import BaseModel
@@ -16,7 +16,7 @@
1616

1717
class EnvironmentVariable(BaseModel):
1818
name: str
19-
value: Optional[str] = None
19+
value: str | None = None
2020

2121

2222
class EnvironmentVariableResponse(BaseModel):
@@ -60,7 +60,7 @@ def _set_environment_variable(
6060
@env_app.command()
6161
def list(
6262
path: Annotated[
63-
Union[Path, None],
63+
Path | None,
6464
typer.Argument(
6565
help="A path to the folder containing the app you want to deploy"
6666
),
@@ -110,12 +110,12 @@ def list(
110110

111111
@env_app.command()
112112
def delete(
113-
name: Union[str, None] = typer.Argument(
113+
name: str | None = typer.Argument(
114114
None,
115115
help="The name of the environment variable to delete",
116116
),
117117
path: Annotated[
118-
Union[Path, None],
118+
Path | None,
119119
typer.Argument(
120120
help="A path to the folder containing the app you want to deploy"
121121
),
@@ -192,16 +192,16 @@ def delete(
192192

193193
@env_app.command()
194194
def set(
195-
name: Union[str, None] = typer.Argument(
195+
name: str | None = typer.Argument(
196196
None,
197197
help="The name of the environment variable to set",
198198
),
199-
value: Union[str, None] = typer.Argument(
199+
value: str | None = typer.Argument(
200200
None,
201201
help="The value of the environment variable to set",
202202
),
203203
path: Annotated[
204-
Union[Path, None],
204+
Path | None,
205205
typer.Argument(
206206
help="A path to the folder containing the app you want to deploy"
207207
),

src/fastapi_cloud_cli/commands/logs.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import re
33
from datetime import datetime
44
from pathlib import Path
5-
from typing import Annotated, Optional
5+
from typing import Annotated
66

77
import typer
88
from httpx import HTTPError
@@ -110,7 +110,7 @@ def _process_log_stream(
110110

111111
def logs(
112112
path: Annotated[
113-
Optional[Path],
113+
Path | None,
114114
typer.Argument(
115115
help="Path to the folder containing the app (defaults to current directory)"
116116
),

src/fastapi_cloud_cli/logging.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,11 @@
11
import logging
22
import os
3-
from typing import Union
43

54
from rich.console import Console
65
from rich.logging import RichHandler
76

87

9-
def setup_logging(
10-
terminal_width: Union[int, None] = None, level: Union[int, None] = None
11-
) -> None:
8+
def setup_logging(terminal_width: int | None = None, level: int | None = None) -> None:
129
if level is None:
1310
level = (
1411
logging.DEBUG if os.getenv("FASTAPI_CLOUD_DEBUG") == "1" else logging.INFO

src/fastapi_cloud_cli/utils/api.py

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,14 @@
11
import json
22
import logging
33
import time
4-
from collections.abc import Generator
4+
from collections.abc import Callable, Generator
55
from contextlib import contextmanager
66
from datetime import timedelta
77
from functools import wraps
88
from typing import (
99
Annotated,
10-
Callable,
1110
Literal,
12-
Optional,
1311
TypeVar,
14-
Union,
1512
)
1613

1714
import httpx
@@ -32,7 +29,7 @@
3229
class StreamLogError(Exception):
3330
"""Raised when there's an error streaming logs (build or app logs)."""
3431

35-
def __init__(self, message: str, *, status_code: Optional[int] = None) -> None:
32+
def __init__(self, message: str, *, status_code: int | None = None) -> None:
3633
super().__init__(message)
3734
self.status_code = status_code
3835

@@ -49,16 +46,16 @@ class AppLogEntry(BaseModel):
4946

5047
class BuildLogLineGeneric(BaseModel):
5148
type: Literal["complete", "failed", "timeout", "heartbeat"]
52-
id: Optional[str] = None
49+
id: str | None = None
5350

5451

5552
class BuildLogLineMessage(BaseModel):
5653
type: Literal["message"] = "message"
5754
message: str
58-
id: Optional[str] = None
55+
id: str | None = None
5956

6057

61-
BuildLogLine = Union[BuildLogLineMessage, BuildLogLineGeneric]
58+
BuildLogLine = BuildLogLineMessage | BuildLogLineGeneric
6259
BuildLogAdapter: TypeAdapter[BuildLogLine] = TypeAdapter(
6360
Annotated[BuildLogLine, Field(discriminator="type")]
6461
)
@@ -197,7 +194,7 @@ def stream_build_logs(
197194

198195
time.sleep(0.5)
199196

200-
def _parse_log_line(self, line: str) -> Optional[BuildLogLine]:
197+
def _parse_log_line(self, line: str) -> BuildLogLine | None:
201198
try:
202199
return BuildLogAdapter.validate_json(line)
203200
except (ValidationError, json.JSONDecodeError) as e:

src/fastapi_cloud_cli/utils/apps.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import logging
22
from pathlib import Path
3-
from typing import Optional
43

54
from pydantic import BaseModel
65

@@ -12,7 +11,7 @@ class AppConfig(BaseModel):
1211
team_id: str
1312

1413

15-
def get_app_config(path_to_deploy: Path) -> Optional[AppConfig]:
14+
def get_app_config(path_to_deploy: Path) -> AppConfig | None:
1615
config_path = path_to_deploy / ".fastapicloud/cloud.json"
1716
logger.debug("Looking for app config at: %s", config_path)
1817

src/fastapi_cloud_cli/utils/auth.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import logging
55
import os
66
import time
7-
from typing import Literal, Optional
7+
from typing import Literal
88

99
from pydantic import BaseModel
1010

@@ -36,7 +36,7 @@ def delete_auth_config() -> None:
3636
logger.debug("Auth config file doesn't exist, nothing to delete")
3737

3838

39-
def read_auth_config() -> Optional[AuthConfig]:
39+
def read_auth_config() -> AuthConfig | None:
4040
auth_path = get_auth_path()
4141
logger.debug("Reading auth config from: %s", auth_path)
4242

@@ -48,7 +48,7 @@ def read_auth_config() -> Optional[AuthConfig]:
4848
return AuthConfig.model_validate_json(auth_path.read_text(encoding="utf-8"))
4949

5050

51-
def _get_auth_token() -> Optional[str]:
51+
def _get_auth_token() -> str | None:
5252
logger.debug("Getting auth token")
5353
auth_data = read_auth_config()
5454

@@ -120,7 +120,7 @@ def __init__(self) -> None:
120120
self.token = env_token
121121
self.auth_mode = "token"
122122

123-
def _get_token_from_env(self) -> Optional[str]:
123+
def _get_token_from_env(self) -> str | None:
124124
return os.environ.get("FASTAPI_CLOUD_TOKEN")
125125

126126
def is_expired(self) -> bool:

0 commit comments

Comments
 (0)