Skip to content

Commit 953cfbd

Browse files
committed
More compat
1 parent 9e8fa41 commit 953cfbd

File tree

6 files changed

+60
-31
lines changed

6 files changed

+60
-31
lines changed

.github/workflows/test.yml

Lines changed: 5 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -25,33 +25,21 @@ jobs:
2525
test:
2626
strategy:
2727
matrix:
28-
os: [ ubuntu-latest, windows-latest, macos-latest ]
29-
python-version: ["3.14"]
28+
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
3029
pydantic-version: ["v2"]
3130
include:
3231
- python-version: "3.8"
3332
pydantic-version: "v1"
34-
os: windows-latest
3533
- python-version: "3.9"
36-
pydantic-version: "v2"
37-
os: macos-latest
34+
pydantic-version: "v1"
3835
- python-version: "3.10"
3936
pydantic-version: "v1"
40-
os: ubuntu-latest
4137
- python-version: "3.11"
42-
pydantic-version: "v2"
43-
os: windows-latest
44-
- python-version: "3.12"
4538
pydantic-version: "v1"
46-
os: macos-latest
47-
- python-version: "3.13"
39+
- python-version: "3.12"
4840
pydantic-version: "v1"
49-
os: ubuntu-latest
50-
- python-version: "3.13"
51-
pydantic-version: "v2"
52-
os: windows-latest
5341
fail-fast: false
54-
runs-on: ${{ matrix.os }}
42+
runs-on: ubuntu-latest
5543
steps:
5644
- name: Dump GitHub context
5745
env:
@@ -69,7 +57,7 @@ jobs:
6957
id: cache
7058
with:
7159
path: ${{ env.pythonLocation }}
72-
key: ${{ runner.os }}-python-${{ env.pythonLocation }}-${{ hashFiles('pyproject.toml', 'requirements-tests.txt') }}
60+
key: ${{ runner.os }}-python-${{ env.pythonLocation }}-${{ hashFiles('pyproject.toml', 'requirements-tests.txt') }}-{{ matrix.pydantic-version }}
7361
# Allow debugging with tmate
7462
- name: Setup tmate session
7563
uses: mxschmitt/action-tmate@v3

src/fastapi_cloud_cli/commands/deploy.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
import rignore
1414
import typer
1515
from httpx import Client
16-
from pydantic import BaseModel, EmailStr, TypeAdapter, ValidationError
16+
from pydantic import BaseModel, EmailStr, ValidationError
1717
from rich.text import Text
1818
from rich_toolkit import RichToolkit
1919
from rich_toolkit.menu import Option
@@ -24,7 +24,11 @@
2424
from fastapi_cloud_cli.utils.apps import AppConfig, get_app_config, write_app_config
2525
from fastapi_cloud_cli.utils.auth import is_logged_in
2626
from fastapi_cloud_cli.utils.cli import get_rich_toolkit, handle_http_errors
27-
from fastapi_cloud_cli.utils.pydantic_compat import model_validate
27+
from fastapi_cloud_cli.utils.pydantic_compat import (
28+
TypeAdapter,
29+
model_dump,
30+
model_validate,
31+
)
2832

2933
logger = logging.getLogger(__name__)
3034

@@ -417,9 +421,7 @@ def _send_waitlist_form(
417421
with toolkit.progress("Sending your request...") as progress:
418422
with APIClient() as client:
419423
with handle_http_errors(progress):
420-
response = client.post(
421-
"/users/waiting-list", json=result.model_dump(mode="json")
422-
)
424+
response = client.post("/users/waiting-list", json=model_dump(result))
423425

424426
response.raise_for_status()
425427

@@ -444,7 +446,7 @@ def _waitlist_form(toolkit: RichToolkit) -> None:
444446

445447
toolkit.print_line()
446448

447-
result = SignupToWaitingList(email=email)
449+
result = model_validate(SignupToWaitingList, {"email": email})
448450

449451
if toolkit.confirm(
450452
"Do you want to get access faster by giving us more information?",

src/fastapi_cloud_cli/commands/login.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
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
19+
from fastapi_cloud_cli.utils.pydantic_compat import model_validate_json
2020

2121
logger = logging.getLogger(__name__)
2222

@@ -44,7 +44,7 @@ def _start_device_authorization(
4444

4545
response.raise_for_status()
4646

47-
return model_validate(AuthorizationData, response.json())
47+
return model_validate_json(AuthorizationData, response.text)
4848

4949

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

7575
time.sleep(interval)
7676

77-
response_data = model_validate(TokenResponse, response.json())
77+
response_data = model_validate_json(TokenResponse, response.text)
7878

7979
return response_data.access_token
8080

src/fastapi_cloud_cli/utils/apps.py

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

55
from pydantic import BaseModel
66

7-
from fastapi_cloud_cli.utils.pydantic_compat import model_validate_json
7+
from fastapi_cloud_cli.utils.pydantic_compat import model_dump_json, model_validate_json
88

99
logger = logging.getLogger("fastapi_cli")
1010

@@ -52,7 +52,7 @@ def write_app_config(path_to_deploy: Path, app_config: AppConfig) -> None:
5252
config_path.parent.mkdir(parents=True, exist_ok=True)
5353

5454
config_path.write_text(
55-
app_config.model_dump_json(),
55+
model_dump_json(app_config),
5656
encoding="utf-8",
5757
)
5858
readme_path.write_text(README, encoding="utf-8")

src/fastapi_cloud_cli/utils/auth.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
from pydantic import BaseModel
99

10-
from fastapi_cloud_cli.utils.pydantic_compat import model_validate_json
10+
from fastapi_cloud_cli.utils.pydantic_compat import model_dump_json, model_validate_json
1111

1212
from .config import get_auth_path
1313

@@ -22,7 +22,7 @@ def write_auth_config(auth_data: AuthConfig) -> None:
2222
auth_path = get_auth_path()
2323
logger.debug("Writing auth config to: %s", auth_path)
2424

25-
auth_path.write_text(auth_data.model_dump_json(), encoding="utf-8")
25+
auth_path.write_text(model_dump_json(auth_data), encoding="utf-8")
2626
logger.debug("Auth config written successfully")
2727

2828

src/fastapi_cloud_cli/utils/pydantic_compat.py

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import Any, Dict, Type, TypeVar
1+
from typing import Any, Dict, Generic, Type, TypeVar
22

33
from pydantic import BaseModel
44
from pydantic.version import VERSION as PYDANTIC_VERSION
@@ -7,6 +7,7 @@
77
PYDANTIC_V2 = PYDANTIC_VERSION_MINOR_TUPLE[0] == 2
88

99

10+
T = TypeVar("T")
1011
Model = TypeVar("Model", bound=BaseModel)
1112

1213

@@ -22,3 +23,41 @@ def model_validate_json(model_class: Type[Model], data: str) -> Model:
2223
return model_class.model_validate_json(data) # type: ignore[no-any-return, unused-ignore, attr-defined]
2324
else:
2425
return model_class.parse_raw(data) # type: ignore[no-any-return, unused-ignore, attr-defined]
26+
27+
28+
def model_dump(obj: BaseModel, **kwargs: Any) -> Dict[Any, Any]:
29+
if PYDANTIC_V2:
30+
return obj.model_dump(**kwargs) # type: ignore[no-any-return, unused-ignore, attr-defined]
31+
else:
32+
return obj.dict(**kwargs) # type: ignore[no-any-return, unused-ignore, attr-defined]
33+
34+
35+
def model_dump_json(obj: BaseModel) -> str:
36+
if PYDANTIC_V2:
37+
return obj.model_dump_json() # type: ignore[no-any-return, unused-ignore, attr-defined]
38+
else:
39+
# Use compact separators to match Pydantic v2's output format
40+
return obj.json(separators=(",", ":")) # type: ignore[no-any-return, unused-ignore, attr-defined]
41+
42+
43+
class TypeAdapter(Generic[T]):
44+
def __init__(self, type_: Type[T]) -> None:
45+
self.type_ = type_
46+
47+
if PYDANTIC_V2:
48+
from pydantic import ( # type: ignore[attr-defined, unused-ignore]
49+
TypeAdapter as PydanticTypeAdapter,
50+
)
51+
52+
self._adapter = PydanticTypeAdapter(type_)
53+
else:
54+
self._adapter = None # type: ignore[assignment, unused-ignore]
55+
56+
def validate_python(self, value: Any) -> T:
57+
"""Validate a Python object against the type."""
58+
if PYDANTIC_V2:
59+
return self._adapter.validate_python(value) # type: ignore[no-any-return, union-attr, unused-ignore]
60+
else:
61+
from pydantic import parse_obj_as
62+
63+
return parse_obj_as(self.type_, value) # type: ignore[no-any-return, unused-ignore]

0 commit comments

Comments
 (0)