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
46 changes: 46 additions & 0 deletions fquery/pydantic.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import dataclasses
from dataclasses import dataclass, fields
from typing import Type

from pydantic import BaseModel, ConfigDict, Field


def pydantic(cls):
return model(dataclass(kw_only=True)(cls))


def validator(self) -> BaseModel:
attrs = {name: getattr(self, name) for name in self.__pydantic__.model_fields}
return self.__pydantic__(**attrs)


def get_field_def(cls, field):
# if the dataclass has a default_factory, or a default value, use it in pydantic Field
kwargs = {}
if not isinstance(field.default, dataclasses._MISSING_TYPE):
kwargs["default"] = field.default
if not isinstance(field.default_factory, dataclasses._MISSING_TYPE):
kwargs["default_factory"] = field.default_factory
return Field(**kwargs)


def model(cls: Type) -> Type:
"""
Decorator to convert a dataclass to a Pydantic model.
"""
# Generate the SQLModel class
pydantic_cls = type(
cls.__name__ + "Model",
(BaseModel,),
{
# Add type annotations to the generated fields
"__annotations__": {**{field.name: field.type for field in fields(cls)}},
# Actual field defs
**{field.name: get_field_def(cls, field) for field in fields(cls)},
},
)
cls.__pydantic__ = pydantic_cls
cls.model_config = ConfigDict(extra="ignore")
cls.validator = validator

return cls
34 changes: 34 additions & 0 deletions tests/test_pydantic.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
from dataclasses import is_dataclass

import pytest
from pydantic import BaseModel, ValidationError

from fquery.pydantic import pydantic


@pydantic
class User:
name: str
age: int
is_active: bool = True


def test_pydantic():
u1 = User(name="John Doe", age=42)
u2 = User(name="John Doe", age=42, is_active=False)
assert is_dataclass(u1)
assert is_dataclass(u2)

v1 = u1.validator()
v2 = u2.validator()
assert isinstance(v1, BaseModel)
assert isinstance(v2, BaseModel)

assert v1.model_dump() == u1.__dict__
assert v2.model_dump() == u2.__dict__


def test_pydantic_fail():
u1 = User(name="John Doe", age=42.3)
with pytest.raises(ValidationError):
_ = u1.validator()