Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 1 addition & 4 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,9 @@ jobs:
uv-resolution:
- highest
include:
- python-version: "3.9"
os: macos-latest
uv-resolution: lowest-direct
- python-version: "3.10"
os: ubuntu-latest
uv-resolution: highest
uv-resolution: lowest-direct
- python-version: "3.11"
os: macos-latest
uv-resolution: lowest-direct
Expand Down
2 changes: 1 addition & 1 deletion .python-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
3.9
3.10
3 changes: 1 addition & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name = "fastapi-cloud-cli"
dynamic = ["version"]
description = "Deploy and manage FastAPI Cloud apps from the command line 🚀"
authors = [{ name = "Patrick Arminio", email = "patrick@fastapilabs.com" }]
requires-python = ">=3.9"
requires-python = ">=3.10"
readme = "README.md"
license = { text = "MIT" }
classifiers = [
Expand All @@ -22,7 +22,6 @@ classifiers = [
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
Expand Down
36 changes: 18 additions & 18 deletions src/fastapi_cloud_cli/commands/deploy.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from itertools import cycle
from pathlib import Path, PurePosixPath
from textwrap import dedent
from typing import Annotated, Any, Optional, Union
from typing import Annotated, Any

import fastar
import rignore
Expand All @@ -28,7 +28,7 @@
logger = logging.getLogger(__name__)


def validate_app_directory(v: Optional[str]) -> Optional[str]:
def validate_app_directory(v: str | None) -> str | None:
if v is None:
return None

Expand Down Expand Up @@ -58,7 +58,7 @@ def validate_app_directory(v: Optional[str]) -> Optional[str]:
return normalized


AppDirectory = Annotated[Optional[str], AfterValidator(validate_app_directory)]
AppDirectory = Annotated[str | None, AfterValidator(validate_app_directory)]


def _cancel_upload(deployment_id: str) -> None:
Expand Down Expand Up @@ -147,10 +147,10 @@ def _get_teams() -> list[Team]:
class AppResponse(BaseModel):
id: str
slug: str
directory: Optional[str]
directory: str | None


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


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


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

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

toolkit.print_line()

selected_app: Optional[AppResponse] = None
selected_app: AppResponse | None = None

if not create_new_app:
with toolkit.progress("Fetching apps...") as progress:
Expand Down Expand Up @@ -389,7 +389,7 @@ def _configure_app(toolkit: RichToolkit, path_to_deploy: Path) -> AppConfig:
validator=TypeAdapter(AppDirectory),
)

directory: Optional[str] = directory_input if directory_input else None
directory: str | None = directory_input if directory_input else None

toolkit.print_line()

Expand Down Expand Up @@ -518,13 +518,13 @@ def _wait_for_deployment(

class SignupToWaitingList(BaseModel):
email: EmailStr
name: Optional[str] = None
organization: Optional[str] = None
role: Optional[str] = None
team_size: Optional[str] = None
location: Optional[str] = None
use_case: Optional[str] = None
secret_code: Optional[str] = None
name: str | None = None
organization: str | None = None
role: str | None = None
team_size: str | None = None
location: str | None = None
use_case: str | None = None
secret_code: str | None = None


def _send_waitlist_form(
Expand Down Expand Up @@ -624,7 +624,7 @@ def _waitlist_form(toolkit: RichToolkit) -> None:

def deploy(
path: Annotated[
Union[Path, None],
Path | None,
typer.Argument(
help="A path to the folder containing the app you want to deploy"
),
Expand All @@ -633,7 +633,7 @@ def deploy(
bool, typer.Option("--no-wait", help="Skip waiting for deployment status")
] = False,
provided_app_id: Annotated[
Union[str, None],
str | None,
typer.Option(
"--app-id",
help="Application ID to deploy to",
Expand Down
16 changes: 8 additions & 8 deletions src/fastapi_cloud_cli/commands/env.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import logging
from pathlib import Path
from typing import Annotated, Any, Optional, Union
from typing import Annotated, Any

import typer
from pydantic import BaseModel
Expand All @@ -16,7 +16,7 @@

class EnvironmentVariable(BaseModel):
name: str
value: Optional[str] = None
value: str | None = None


class EnvironmentVariableResponse(BaseModel):
Expand Down Expand Up @@ -60,7 +60,7 @@ def _set_environment_variable(
@env_app.command()
def list(
path: Annotated[
Union[Path, None],
Path | None,
typer.Argument(
help="A path to the folder containing the app you want to deploy"
),
Expand Down Expand Up @@ -110,12 +110,12 @@ def list(

@env_app.command()
def delete(
name: Union[str, None] = typer.Argument(
name: str | None = typer.Argument(
None,
help="The name of the environment variable to delete",
),
path: Annotated[
Union[Path, None],
Path | None,
typer.Argument(
help="A path to the folder containing the app you want to deploy"
),
Expand Down Expand Up @@ -192,16 +192,16 @@ def delete(

@env_app.command()
def set(
name: Union[str, None] = typer.Argument(
name: str | None = typer.Argument(
None,
help="The name of the environment variable to set",
),
value: Union[str, None] = typer.Argument(
value: str | None = typer.Argument(
None,
help="The value of the environment variable to set",
),
path: Annotated[
Union[Path, None],
Path | None,
typer.Argument(
help="A path to the folder containing the app you want to deploy"
),
Expand Down
4 changes: 2 additions & 2 deletions src/fastapi_cloud_cli/commands/logs.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import re
from datetime import datetime
from pathlib import Path
from typing import Annotated, Optional
from typing import Annotated

import typer
from httpx import HTTPError
Expand Down Expand Up @@ -110,7 +110,7 @@ def _process_log_stream(

def logs(
path: Annotated[
Optional[Path],
Path | None,
typer.Argument(
help="Path to the folder containing the app (defaults to current directory)"
),
Expand Down
5 changes: 1 addition & 4 deletions src/fastapi_cloud_cli/logging.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
import logging
import os
from typing import Union

from rich.console import Console
from rich.logging import RichHandler


def setup_logging(
terminal_width: Union[int, None] = None, level: Union[int, None] = None
) -> None:
def setup_logging(terminal_width: int | None = None, level: int | None = None) -> None:
if level is None:
level = (
logging.DEBUG if os.getenv("FASTAPI_CLOUD_DEBUG") == "1" else logging.INFO
Expand Down
15 changes: 6 additions & 9 deletions src/fastapi_cloud_cli/utils/api.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
import json
import logging
import time
from collections.abc import Generator
from collections.abc import Callable, Generator
from contextlib import contextmanager
from datetime import timedelta
from functools import wraps
from typing import (
Annotated,
Callable,
Literal,
Optional,
TypeVar,
Union,
)

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

def __init__(self, message: str, *, status_code: Optional[int] = None) -> None:
def __init__(self, message: str, *, status_code: int | None = None) -> None:
super().__init__(message)
self.status_code = status_code

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

class BuildLogLineGeneric(BaseModel):
type: Literal["complete", "failed", "timeout", "heartbeat"]
id: Optional[str] = None
id: str | None = None


class BuildLogLineMessage(BaseModel):
type: Literal["message"] = "message"
message: str
id: Optional[str] = None
id: str | None = None


BuildLogLine = Union[BuildLogLineMessage, BuildLogLineGeneric]
BuildLogLine = BuildLogLineMessage | BuildLogLineGeneric
BuildLogAdapter: TypeAdapter[BuildLogLine] = TypeAdapter(
Annotated[BuildLogLine, Field(discriminator="type")]
)
Expand Down Expand Up @@ -197,7 +194,7 @@ def stream_build_logs(

time.sleep(0.5)

def _parse_log_line(self, line: str) -> Optional[BuildLogLine]:
def _parse_log_line(self, line: str) -> BuildLogLine | None:
try:
return BuildLogAdapter.validate_json(line)
except (ValidationError, json.JSONDecodeError) as e:
Expand Down
3 changes: 1 addition & 2 deletions src/fastapi_cloud_cli/utils/apps.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import logging
from pathlib import Path
from typing import Optional

from pydantic import BaseModel

Expand All @@ -12,7 +11,7 @@ class AppConfig(BaseModel):
team_id: str


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

Expand Down
8 changes: 4 additions & 4 deletions src/fastapi_cloud_cli/utils/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import logging
import os
import time
from typing import Literal, Optional
from typing import Literal

from pydantic import BaseModel

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


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

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


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

Expand Down Expand Up @@ -120,7 +120,7 @@ def __init__(self) -> None:
self.token = env_token
self.auth_mode = "token"

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

def is_expired(self) -> bool:
Expand Down
8 changes: 4 additions & 4 deletions src/fastapi_cloud_cli/utils/cli.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import contextlib
import logging
from collections.abc import Generator
from typing import Any, Optional
from typing import Any

import typer
from httpx import HTTPError, HTTPStatusError, ReadTimeout
Expand Down Expand Up @@ -84,8 +84,8 @@ def handle_unauthorized() -> str:
return message


def handle_http_error(error: HTTPError, default_message: Optional[str] = None) -> str:
message: Optional[str] = None
def handle_http_error(error: HTTPError, default_message: str | None = None) -> str:
message: str | None = None

if isinstance(error, HTTPStatusError):
status_code = error.response.status_code
Expand All @@ -112,7 +112,7 @@ def handle_http_error(error: HTTPError, default_message: Optional[str] = None) -
@contextlib.contextmanager
def handle_http_errors(
progress: Progress,
default_message: Optional[str] = None,
default_message: str | None = None,
) -> Generator[None, None, None]:
try:
yield
Expand Down
Loading
Loading