From 6728c25bec264152e84818b8c42fa3d480540cfc Mon Sep 17 00:00:00 2001 From: Daniil Fajnberg Date: Wed, 7 Sep 2022 11:47:57 +0200 Subject: [PATCH 1/4] =?UTF-8?q?=E2=9C=A8=20Add=20support=20for=20all=20`Fi?= =?UTF-8?q?eld`=20parameters=20from=20Pydantic=20version=20`1.9.0`=20(incl?= =?UTF-8?q?.=20`max=5Fdigits`,=20`decimal=5Fplaces`,=20`unique=5Fitems`,?= =?UTF-8?q?=20`discriminator`,=20and=20`repr`);=20add=20tests=20for=20Pyda?= =?UTF-8?q?ntic=20capabilities;=20bump=20Pydantic=20dependency=20to=20`1.9?= =?UTF-8?q?.0`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyproject.toml | 2 +- sqlmodel/main.py | 16 ++++++- tests/test_pydantic/__init__.py | 0 tests/test_pydantic/test_field.py | 69 +++++++++++++++++++++++++++++++ 4 files changed, 85 insertions(+), 2 deletions(-) create mode 100644 tests/test_pydantic/__init__.py create mode 100644 tests/test_pydantic/test_field.py diff --git a/pyproject.toml b/pyproject.toml index 7f5e7f803..435aed141 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,7 +32,7 @@ classifiers = [ [tool.poetry.dependencies] python = "^3.6.1" SQLAlchemy = ">=1.4.17,<=1.4.41" -pydantic = "^1.8.2" +pydantic = "^1.9.0" sqlalchemy2-stubs = {version = "*", allow-prereleases = true} [tool.poetry.dev-dependencies] diff --git a/sqlmodel/main.py b/sqlmodel/main.py index d343c698e..ed1a2c07a 100644 --- a/sqlmodel/main.py +++ b/sqlmodel/main.py @@ -135,12 +135,17 @@ def Field( lt: Optional[float] = None, le: Optional[float] = None, multiple_of: Optional[float] = None, + max_digits: Optional[int] = None, + decimal_places: Optional[int] = None, min_items: Optional[int] = None, max_items: Optional[int] = None, + unique_items: Optional[bool] = None, min_length: Optional[int] = None, max_length: Optional[int] = None, allow_mutation: bool = True, regex: Optional[str] = None, + discriminator: Optional[str] = None, + repr: bool = True, primary_key: bool = False, foreign_key: Optional[Any] = None, unique: bool = False, @@ -166,12 +171,17 @@ def Field( lt=lt, le=le, multiple_of=multiple_of, + max_digits=max_digits, + decimal_places=decimal_places, min_items=min_items, max_items=max_items, + unique_items=unique_items, min_length=min_length, max_length=max_length, allow_mutation=allow_mutation, regex=regex, + discriminator=discriminator, + repr=repr, primary_key=primary_key, foreign_key=foreign_key, unique=unique, @@ -576,7 +586,11 @@ def parse_obj( def __repr_args__(self) -> Sequence[Tuple[Optional[str], Any]]: # Don't show SQLAlchemy private attributes - return [(k, v) for k, v in self.__dict__.items() if not k.startswith("_sa_")] + return [ + (k, v) + for k, v in super().__repr_args__() + if not (isinstance(k, str) and k.startswith("_sa_")) + ] # From Pydantic, override to enforce validation with dict @classmethod diff --git a/tests/test_pydantic/__init__.py b/tests/test_pydantic/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/test_pydantic/test_field.py b/tests/test_pydantic/test_field.py new file mode 100644 index 000000000..07c9a78c3 --- /dev/null +++ b/tests/test_pydantic/test_field.py @@ -0,0 +1,69 @@ +""" +Tests to ensure that the SQLModel `Field` constructor works as +expected from the Pydantic `Field` function. +""" +from decimal import Decimal +from typing import List, Literal, Optional, Union + +import pytest +from pydantic import ValidationError +from sqlmodel import Field, SQLModel + + +def test_decimal(): + class Model(SQLModel): + dec: Decimal = Field(max_digits=4, decimal_places=2) + + Model(dec=Decimal("3.14")) + Model(dec=Decimal("69.42")) + + with pytest.raises(ValidationError): + Model(dec=Decimal("3.142")) + with pytest.raises(ValidationError): + Model(dec=Decimal("0.069")) + with pytest.raises(ValidationError): + Model(dec=Decimal("420")) + + +def test_unique_items(): + class Model(SQLModel): + unique_strings: List[str] = Field(unique_items=True) + + Model(unique_strings=["x", "y"]) + + with pytest.raises(ValidationError): + Model(unique_strings=["x", "y", "x"]) + + +def test_discriminator(): + # Example adapted from + # [Pydantic docs](https://pydantic-docs.helpmanual.io/usage/types/#discriminated-unions-aka-tagged-unions): + class Cat(SQLModel): + pet_type: Literal["cat"] + meows: int + + class Dog(SQLModel): + pet_type: Literal["dog"] + barks: float + + class Lizard(SQLModel): + pet_type: Literal["reptile", "lizard"] + scales: bool + + class Model(SQLModel): + pet: Union[Cat, Dog, Lizard] = Field(..., discriminator="pet_type") + n: int + + Model(pet={"pet_type": "dog", "barks": 3.14}, n=1) # type: ignore[arg-type] + + with pytest.raises(ValidationError): + Model(pet={"pet_type": "dog"}, n=1) # type: ignore[arg-type] + + +def test_repr(): + class Model(SQLModel): + id: Optional[int] = Field(primary_key=True) + foo: str = Field(repr=False) + + instance = Model(id=123, foo="bar") + assert "foo=" not in repr(instance) From 57e05abeda76179b96f4af629a3677c72d1a7e50 Mon Sep 17 00:00:00 2001 From: Daniil Fajnberg Date: Wed, 7 Sep 2022 13:20:35 +0200 Subject: [PATCH 2/4] =?UTF-8?q?=F0=9F=94=A7=20Skip=20tests=20for=20`discri?= =?UTF-8?q?minator`=20param=20(needs=20Python=20`3.8+`)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/test_pydantic/test_field.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/tests/test_pydantic/test_field.py b/tests/test_pydantic/test_field.py index 07c9a78c3..fb6586330 100644 --- a/tests/test_pydantic/test_field.py +++ b/tests/test_pydantic/test_field.py @@ -2,8 +2,9 @@ Tests to ensure that the SQLModel `Field` constructor works as expected from the Pydantic `Field` function. """ +import sys from decimal import Decimal -from typing import List, Literal, Optional, Union +from typing import List, Optional, Union import pytest from pydantic import ValidationError @@ -35,9 +36,15 @@ class Model(SQLModel): Model(unique_strings=["x", "y", "x"]) +@pytest.mark.skipif( + sys.version_info < (3, 8), reason="requires Python 3.8+ (for `typing.Literal`)" +) def test_discriminator(): + from typing import Literal + # Example adapted from # [Pydantic docs](https://pydantic-docs.helpmanual.io/usage/types/#discriminated-unions-aka-tagged-unions): + class Cat(SQLModel): pet_type: Literal["cat"] meows: int From 470e81354e8b0b1d2c569aaec285ce3ef10eae0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Thu, 26 Oct 2023 13:59:13 +0400 Subject: [PATCH 3/4] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Import=20from=20typing?= =?UTF-8?q?=5Fextensions=20to=20support=20Python=203.7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/test_pydantic/test_field.py | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/tests/test_pydantic/test_field.py b/tests/test_pydantic/test_field.py index fb6586330..02df42b50 100644 --- a/tests/test_pydantic/test_field.py +++ b/tests/test_pydantic/test_field.py @@ -1,14 +1,10 @@ -""" -Tests to ensure that the SQLModel `Field` constructor works as -expected from the Pydantic `Field` function. -""" -import sys from decimal import Decimal from typing import List, Optional, Union import pytest from pydantic import ValidationError from sqlmodel import Field, SQLModel +from typing_extensions import Literal def test_decimal(): @@ -36,12 +32,7 @@ class Model(SQLModel): Model(unique_strings=["x", "y", "x"]) -@pytest.mark.skipif( - sys.version_info < (3, 8), reason="requires Python 3.8+ (for `typing.Literal`)" -) def test_discriminator(): - from typing import Literal - # Example adapted from # [Pydantic docs](https://pydantic-docs.helpmanual.io/usage/types/#discriminated-unions-aka-tagged-unions): From 8bc6450c0a445fac8f305e4ef06b6708e2a5e8c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Thu, 26 Oct 2023 14:01:51 +0400 Subject: [PATCH 4/4] =?UTF-8?q?=F0=9F=94=A5=20Remove=20test=20for=20unique?= =?UTF-8?q?=5Fitems=20as=20it=20was=20removed=20from=20Pydantic=20v2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/test_pydantic/test_field.py | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/tests/test_pydantic/test_field.py b/tests/test_pydantic/test_field.py index 02df42b50..9d7bc7762 100644 --- a/tests/test_pydantic/test_field.py +++ b/tests/test_pydantic/test_field.py @@ -1,5 +1,5 @@ from decimal import Decimal -from typing import List, Optional, Union +from typing import Optional, Union import pytest from pydantic import ValidationError @@ -22,16 +22,6 @@ class Model(SQLModel): Model(dec=Decimal("420")) -def test_unique_items(): - class Model(SQLModel): - unique_strings: List[str] = Field(unique_items=True) - - Model(unique_strings=["x", "y"]) - - with pytest.raises(ValidationError): - Model(unique_strings=["x", "y", "x"]) - - def test_discriminator(): # Example adapted from # [Pydantic docs](https://pydantic-docs.helpmanual.io/usage/types/#discriminated-unions-aka-tagged-unions):