Skip to content

Commit 5b3b59f

Browse files
committed
Merge branch 'main' into prek
2 parents 10f5e3f + 91ff85e commit 5b3b59f

File tree

19 files changed

+193
-1046
lines changed

19 files changed

+193
-1046
lines changed

.github/workflows/test.yml

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -25,19 +25,7 @@ jobs:
2525
test:
2626
strategy:
2727
matrix:
28-
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
29-
pydantic-version: ["v2"]
30-
include:
31-
- python-version: "3.8"
32-
pydantic-version: "v1"
33-
- python-version: "3.9"
34-
pydantic-version: "v1"
35-
- python-version: "3.10"
36-
pydantic-version: "v1"
37-
- python-version: "3.11"
38-
pydantic-version: "v1"
39-
- python-version: "3.12"
40-
pydantic-version: "v1"
28+
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
4129
fail-fast: false
4230
runs-on: ubuntu-latest
4331
env:
@@ -65,9 +53,6 @@ jobs:
6553
limit-access-to-actor: true
6654
- name: Install Dependencies
6755
run: uv sync --locked --all-extras --dev
68-
- name: Install Pydantic v1
69-
if: matrix.pydantic-version == 'v1'
70-
run: uv pip install "pydantic<2.0.0"
7156
- name: Lint
7257
run: uv run --no-sync scripts/lint.sh
7358
- run: mkdir coverage
@@ -79,7 +64,7 @@ jobs:
7964
- name: Store coverage files
8065
uses: actions/upload-artifact@v4
8166
with:
82-
name: coverage-${{ matrix.python-version }}-${{ matrix.pydantic-version }}
67+
name: coverage-${{ matrix.python-version }}
8368
path: coverage
8469
include-hidden-files: true
8570

.python-version

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

pyproject.toml

Lines changed: 2 additions & 3 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.8"
6+
requires-python = ">=3.9"
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.8",
2625
"Programming Language :: Python :: 3.9",
2726
"Programming Language :: Python :: 3.10",
2827
"Programming Language :: Python :: 3.11",
@@ -35,7 +34,7 @@ dependencies = [
3534
"rignore >= 0.5.1",
3635
"httpx >= 0.27.0",
3736
"rich-toolkit >= 0.14.5",
38-
"pydantic[email] >= 1.6.1",
37+
"pydantic[email] >= 2.0",
3938
"sentry-sdk >= 2.20.0",
4039
"fastar >= 0.8.0",
4140
]

release-notes.md

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

33
## Latest Changes
44

5+
## 0.8.0
6+
57
### Fixes
68

79
* 🐛 Fix CI ran all tests on oldest supported Python. PR [#136](https://github.com/fastapilabs/fastapi-cloud-cli/pull/136) by [@DoctorJohn](https://github.com/DoctorJohn).
@@ -10,6 +12,11 @@
1012

1113
* ♻️ Reorder log messages for deployment readiness. PR [#137](https://github.com/fastapilabs/fastapi-cloud-cli/pull/137) by [@alejsdev](https://github.com/alejsdev).
1214

15+
### Upgrades
16+
17+
* ➖ Drop support for Pydantic v1. PR [#139](https://github.com/fastapilabs/fastapi-cloud-cli/pull/139) by [@patrick91](https://github.com/patrick91).
18+
* 🔧 Drop support for Python 3.8. PR [#138](https://github.com/fastapilabs/fastapi-cloud-cli/pull/138) by [@patrick91](https://github.com/patrick91).
19+
1320
### Internal
1421

1522
* 👷 Configure coverage, error on main tests, don't wait for Smokeshow. PR [#134](https://github.com/fastapilabs/fastapi-cloud-cli/pull/134) by [@YuriiMotov](https://github.com/YuriiMotov).

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.7.0"
1+
__version__ = "0.8.0"

src/fastapi_cloud_cli/commands/deploy.py

Lines changed: 24 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -7,28 +7,22 @@
77
from itertools import cycle
88
from pathlib import Path
99
from textwrap import dedent
10-
from typing import Any, Dict, List, Optional, Union
10+
from typing import Annotated, Any, Optional, Union
1111

1212
import fastar
1313
import rignore
1414
import typer
1515
from httpx import Client
16-
from pydantic import BaseModel, EmailStr, ValidationError
16+
from pydantic import BaseModel, EmailStr, TypeAdapter, ValidationError
1717
from rich.text import Text
1818
from rich_toolkit import RichToolkit
1919
from rich_toolkit.menu import Option
20-
from typing_extensions import Annotated
2120

2221
from fastapi_cloud_cli.commands.login import login
2322
from fastapi_cloud_cli.utils.api import APIClient, BuildLogError, TooManyRetriesError
2423
from fastapi_cloud_cli.utils.apps import AppConfig, get_app_config, write_app_config
2524
from fastapi_cloud_cli.utils.auth import is_logged_in
2625
from fastapi_cloud_cli.utils.cli import get_rich_toolkit, handle_http_errors
27-
from fastapi_cloud_cli.utils.pydantic_compat import (
28-
TypeAdapter,
29-
model_dump,
30-
model_validate,
31-
)
3226

3327
logger = logging.getLogger(__name__)
3428

@@ -102,14 +96,14 @@ class Team(BaseModel):
10296
name: str
10397

10498

105-
def _get_teams() -> List[Team]:
99+
def _get_teams() -> list[Team]:
106100
with APIClient() as client:
107101
response = client.get("/teams/")
108102
response.raise_for_status()
109103

110104
data = response.json()["data"]
111105

112-
return [model_validate(Team, team) for team in data]
106+
return [Team.model_validate(team) for team in data]
113107

114108

115109
class AppResponse(BaseModel):
@@ -126,7 +120,7 @@ def _create_app(team_id: str, app_name: str) -> AppResponse:
126120

127121
response.raise_for_status()
128122

129-
return model_validate(AppResponse, response.json())
123+
return AppResponse.model_validate(response.json())
130124

131125

132126
class DeploymentStatus(str, Enum):
@@ -179,12 +173,12 @@ def _create_deployment(app_id: str) -> CreateDeploymentResponse:
179173
response = client.post(f"/apps/{app_id}/deployments/")
180174
response.raise_for_status()
181175

182-
return model_validate(CreateDeploymentResponse, response.json())
176+
return CreateDeploymentResponse.model_validate(response.json())
183177

184178

185179
class RequestUploadResponse(BaseModel):
186180
url: str
187-
fields: Dict[str, str]
181+
fields: dict[str, str]
188182

189183

190184
def _upload_deployment(deployment_id: str, archive_path: Path) -> None:
@@ -204,7 +198,7 @@ def _upload_deployment(deployment_id: str, archive_path: Path) -> None:
204198
response = fastapi_client.post(f"/deployments/{deployment_id}/upload")
205199
response.raise_for_status()
206200

207-
upload_data = model_validate(RequestUploadResponse, response.json())
201+
upload_data = RequestUploadResponse.model_validate(response.json())
208202
logger.debug("Received upload URL: %s", upload_data.url)
209203

210204
logger.debug("Starting file upload to S3")
@@ -239,17 +233,17 @@ def _get_app(app_slug: str) -> Optional[AppResponse]:
239233

240234
data = response.json()
241235

242-
return model_validate(AppResponse, data)
236+
return AppResponse.model_validate(data)
243237

244238

245-
def _get_apps(team_id: str) -> List[AppResponse]:
239+
def _get_apps(team_id: str) -> list[AppResponse]:
246240
with APIClient() as client:
247241
response = client.get("/apps/", params={"team_id": team_id})
248242
response.raise_for_status()
249243

250244
data = response.json()["data"]
251245

252-
return [model_validate(AppResponse, app) for app in data]
246+
return [AppResponse.model_validate(app) for app in data]
253247

254248

255249
WAITING_MESSAGES = [
@@ -363,9 +357,12 @@ def _wait_for_deployment(
363357

364358
last_message_changed_at = time.monotonic()
365359

366-
with toolkit.progress(
367-
next(messages), inline_logs=True, lines_to_show=20
368-
) as progress, APIClient() as client:
360+
with (
361+
toolkit.progress(
362+
next(messages), inline_logs=True, lines_to_show=20
363+
) as progress,
364+
APIClient() as client,
365+
):
369366
try:
370367
for log in client.stream_build_logs(deployment.id):
371368
time_elapsed = time.monotonic() - started_at
@@ -432,7 +429,7 @@ def _send_waitlist_form(
432429
with toolkit.progress("Sending your request...") as progress:
433430
with APIClient() as client:
434431
with handle_http_errors(progress):
435-
response = client.post("/users/waiting-list", json=model_dump(result))
432+
response = client.post("/users/waiting-list", json=result.model_dump())
436433

437434
response.raise_for_status()
438435

@@ -457,7 +454,7 @@ def _waitlist_form(toolkit: RichToolkit) -> None:
457454

458455
toolkit.print_line()
459456

460-
result = model_validate(SignupToWaitingList, {"email": email})
457+
result = SignupToWaitingList.model_validate({"email": email})
461458

462459
if toolkit.confirm(
463460
"Do you want to get access faster by giving us more information?",
@@ -481,8 +478,7 @@ def _waitlist_form(toolkit: RichToolkit) -> None:
481478
result = form.run() # type: ignore
482479

483480
try:
484-
result = model_validate(
485-
SignupToWaitingList,
481+
result = SignupToWaitingList.model_validate(
486482
{
487483
"email": email,
488484
**result, # type: ignore
@@ -609,9 +605,10 @@ def deploy(
609605
archive_path = Path(temp_dir) / "archive.tar"
610606
archive(path or Path.cwd(), archive_path)
611607

612-
with toolkit.progress(
613-
title="Creating deployment"
614-
) as progress, handle_http_errors(progress):
608+
with (
609+
toolkit.progress(title="Creating deployment") as progress,
610+
handle_http_errors(progress),
611+
):
615612
logger.debug("Creating deployment for app: %s", app.id)
616613
deployment = _create_deployment(app.id)
617614

src/fastapi_cloud_cli/commands/env.py

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

55
import typer
66
from pydantic import BaseModel
7-
from typing_extensions import Annotated
87

98
from fastapi_cloud_cli.utils.api import APIClient
109
from fastapi_cloud_cli.utils.apps import get_app_config
1110
from fastapi_cloud_cli.utils.auth import is_logged_in
1211
from fastapi_cloud_cli.utils.cli import get_rich_toolkit, handle_http_errors
1312
from fastapi_cloud_cli.utils.env import validate_environment_variable_name
14-
from fastapi_cloud_cli.utils.pydantic_compat import model_validate
1513

1614
logger = logging.getLogger(__name__)
1715

@@ -22,15 +20,15 @@ class EnvironmentVariable(BaseModel):
2220

2321

2422
class EnvironmentVariableResponse(BaseModel):
25-
data: List[EnvironmentVariable]
23+
data: list[EnvironmentVariable]
2624

2725

2826
def _get_environment_variables(app_id: str) -> EnvironmentVariableResponse:
2927
with APIClient() as client:
3028
response = client.get(f"/apps/{app_id}/environment-variables/")
3129
response.raise_for_status()
3230

33-
return model_validate(EnvironmentVariableResponse, response.json())
31+
return EnvironmentVariableResponse.model_validate(response.json())
3432

3533

3634
def _delete_environment_variable(app_id: str, name: str) -> bool:

src/fastapi_cloud_cli/commands/login.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
write_auth_config,
1717
)
1818
from fastapi_cloud_cli.utils.cli import get_rich_toolkit, handle_http_errors
19-
from fastapi_cloud_cli.utils.pydantic_compat import model_validate_json
2019

2120
logger = logging.getLogger(__name__)
2221

@@ -44,7 +43,7 @@ def _start_device_authorization(
4443

4544
response.raise_for_status()
4645

47-
return model_validate_json(AuthorizationData, response.text)
46+
return AuthorizationData.model_validate_json(response.text)
4847

4948

5049
def _fetch_access_token(client: httpx.Client, device_code: str, interval: int) -> str:
@@ -74,7 +73,7 @@ def _fetch_access_token(client: httpx.Client, device_code: str, interval: int) -
7473

7574
time.sleep(interval)
7675

77-
response_data = model_validate_json(TokenResponse, response.text)
76+
response_data = TokenResponse.model_validate_json(response.text)
7877

7978
return response_data.access_token
8079

src/fastapi_cloud_cli/utils/api.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,26 @@
11
import json
22
import logging
33
import time
4+
from collections.abc import Generator
45
from contextlib import contextmanager
56
from datetime import timedelta
67
from functools import wraps
78
from typing import (
9+
Annotated,
810
Callable,
9-
Generator,
1011
Literal,
1112
Optional,
1213
TypeVar,
1314
Union,
1415
)
1516

1617
import httpx
17-
from pydantic import BaseModel, Field, ValidationError
18-
from typing_extensions import Annotated, ParamSpec
18+
from pydantic import BaseModel, Field, TypeAdapter, ValidationError
19+
from typing_extensions import ParamSpec
1920

2021
from fastapi_cloud_cli import __version__
2122
from fastapi_cloud_cli.config import Settings
2223
from fastapi_cloud_cli.utils.auth import get_auth_token
23-
from fastapi_cloud_cli.utils.pydantic_compat import TypeAdapter
2424

2525
logger = logging.getLogger(__name__)
2626

@@ -48,8 +48,8 @@ class BuildLogLineMessage(BaseModel):
4848

4949

5050
BuildLogLine = Union[BuildLogLineMessage, BuildLogLineGeneric]
51-
BuildLogAdapter = TypeAdapter[BuildLogLine](
52-
Annotated[BuildLogLine, Field(discriminator="type")] # type: ignore
51+
BuildLogAdapter: TypeAdapter[BuildLogLine] = TypeAdapter(
52+
Annotated[BuildLogLine, Field(discriminator="type")]
5353
)
5454

5555

src/fastapi_cloud_cli/utils/apps.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@
44

55
from pydantic import BaseModel
66

7-
from fastapi_cloud_cli.utils.pydantic_compat import model_dump_json, model_validate_json
8-
97
logger = logging.getLogger("fastapi_cli")
108

119

@@ -23,7 +21,7 @@ def get_app_config(path_to_deploy: Path) -> Optional[AppConfig]:
2321
return None
2422

2523
logger.debug("App config loaded successfully")
26-
return model_validate_json(AppConfig, config_path.read_text(encoding="utf-8"))
24+
return AppConfig.model_validate_json(config_path.read_text(encoding="utf-8"))
2725

2826

2927
README = """
@@ -52,7 +50,7 @@ def write_app_config(path_to_deploy: Path, app_config: AppConfig) -> None:
5250
config_path.parent.mkdir(parents=True, exist_ok=True)
5351

5452
config_path.write_text(
55-
model_dump_json(app_config),
53+
app_config.model_dump_json(),
5654
encoding="utf-8",
5755
)
5856
readme_path.write_text(README, encoding="utf-8")

0 commit comments

Comments
 (0)