Skip to content

Commit 6728c25

Browse files
committed
✨ Add support for all Field parameters from Pydantic version 1.9.0
(incl. `max_digits`, `decimal_places`, `unique_items`, `discriminator`, and `repr`); add tests for Pydantic capabilities; bump Pydantic dependency to `1.9.0`
1 parent 75ce455 commit 6728c25

File tree

4 files changed

+85
-2
lines changed

4 files changed

+85
-2
lines changed

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ classifiers = [
3232
[tool.poetry.dependencies]
3333
python = "^3.6.1"
3434
SQLAlchemy = ">=1.4.17,<=1.4.41"
35-
pydantic = "^1.8.2"
35+
pydantic = "^1.9.0"
3636
sqlalchemy2-stubs = {version = "*", allow-prereleases = true}
3737

3838
[tool.poetry.dev-dependencies]

sqlmodel/main.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,12 +135,17 @@ def Field(
135135
lt: Optional[float] = None,
136136
le: Optional[float] = None,
137137
multiple_of: Optional[float] = None,
138+
max_digits: Optional[int] = None,
139+
decimal_places: Optional[int] = None,
138140
min_items: Optional[int] = None,
139141
max_items: Optional[int] = None,
142+
unique_items: Optional[bool] = None,
140143
min_length: Optional[int] = None,
141144
max_length: Optional[int] = None,
142145
allow_mutation: bool = True,
143146
regex: Optional[str] = None,
147+
discriminator: Optional[str] = None,
148+
repr: bool = True,
144149
primary_key: bool = False,
145150
foreign_key: Optional[Any] = None,
146151
unique: bool = False,
@@ -166,12 +171,17 @@ def Field(
166171
lt=lt,
167172
le=le,
168173
multiple_of=multiple_of,
174+
max_digits=max_digits,
175+
decimal_places=decimal_places,
169176
min_items=min_items,
170177
max_items=max_items,
178+
unique_items=unique_items,
171179
min_length=min_length,
172180
max_length=max_length,
173181
allow_mutation=allow_mutation,
174182
regex=regex,
183+
discriminator=discriminator,
184+
repr=repr,
175185
primary_key=primary_key,
176186
foreign_key=foreign_key,
177187
unique=unique,
@@ -576,7 +586,11 @@ def parse_obj(
576586

577587
def __repr_args__(self) -> Sequence[Tuple[Optional[str], Any]]:
578588
# Don't show SQLAlchemy private attributes
579-
return [(k, v) for k, v in self.__dict__.items() if not k.startswith("_sa_")]
589+
return [
590+
(k, v)
591+
for k, v in super().__repr_args__()
592+
if not (isinstance(k, str) and k.startswith("_sa_"))
593+
]
580594

581595
# From Pydantic, override to enforce validation with dict
582596
@classmethod

tests/test_pydantic/__init__.py

Whitespace-only changes.

tests/test_pydantic/test_field.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
"""
2+
Tests to ensure that the SQLModel `Field` constructor works as
3+
expected from the Pydantic `Field` function.
4+
"""
5+
from decimal import Decimal
6+
from typing import List, Literal, Optional, Union
7+
8+
import pytest
9+
from pydantic import ValidationError
10+
from sqlmodel import Field, SQLModel
11+
12+
13+
def test_decimal():
14+
class Model(SQLModel):
15+
dec: Decimal = Field(max_digits=4, decimal_places=2)
16+
17+
Model(dec=Decimal("3.14"))
18+
Model(dec=Decimal("69.42"))
19+
20+
with pytest.raises(ValidationError):
21+
Model(dec=Decimal("3.142"))
22+
with pytest.raises(ValidationError):
23+
Model(dec=Decimal("0.069"))
24+
with pytest.raises(ValidationError):
25+
Model(dec=Decimal("420"))
26+
27+
28+
def test_unique_items():
29+
class Model(SQLModel):
30+
unique_strings: List[str] = Field(unique_items=True)
31+
32+
Model(unique_strings=["x", "y"])
33+
34+
with pytest.raises(ValidationError):
35+
Model(unique_strings=["x", "y", "x"])
36+
37+
38+
def test_discriminator():
39+
# Example adapted from
40+
# [Pydantic docs](https://pydantic-docs.helpmanual.io/usage/types/#discriminated-unions-aka-tagged-unions):
41+
class Cat(SQLModel):
42+
pet_type: Literal["cat"]
43+
meows: int
44+
45+
class Dog(SQLModel):
46+
pet_type: Literal["dog"]
47+
barks: float
48+
49+
class Lizard(SQLModel):
50+
pet_type: Literal["reptile", "lizard"]
51+
scales: bool
52+
53+
class Model(SQLModel):
54+
pet: Union[Cat, Dog, Lizard] = Field(..., discriminator="pet_type")
55+
n: int
56+
57+
Model(pet={"pet_type": "dog", "barks": 3.14}, n=1) # type: ignore[arg-type]
58+
59+
with pytest.raises(ValidationError):
60+
Model(pet={"pet_type": "dog"}, n=1) # type: ignore[arg-type]
61+
62+
63+
def test_repr():
64+
class Model(SQLModel):
65+
id: Optional[int] = Field(primary_key=True)
66+
foo: str = Field(repr=False)
67+
68+
instance = Model(id=123, foo="bar")
69+
assert "foo=" not in repr(instance)

0 commit comments

Comments
 (0)