Coverage for tests / test_pydantic / test_field.py: 99%

73 statements  

« prev     ^ index     » next       coverage.py v7.13.0, created at 2026-11-09 00:02 +0000

1from decimal import Decimal 1stuvwxyzA

2from typing import Annotated, Any, Literal 1stuvwxyzA

3 

4import pytest 1stuvwxyzA

5from pydantic import ValidationError 1stuvwxyzA

6from sqlmodel import Discriminator, Field, SQLModel, Tag 1stuvwxyzA

7 

8 

9def test_decimal(): 1stuvwxyzA

10 class Model(SQLModel): 1BCDEFGHIJ

11 dec: Decimal = Field(max_digits=4, decimal_places=2) 1BCDEFGHIJ

12 

13 Model(dec=Decimal("3.14")) 1BCDEFGHIJ

14 Model(dec=Decimal("69.42")) 1BCDEFGHIJ

15 

16 with pytest.raises(ValidationError): 1BCDEFGHIJ

17 Model(dec=Decimal("3.142")) 1BCDEFGHIJ

18 with pytest.raises(ValidationError): 1BCDEFGHIJ

19 Model(dec=Decimal("0.069")) 1BCDEFGHIJ

20 with pytest.raises(ValidationError): 1BCDEFGHIJ

21 Model(dec=Decimal("420")) 1BCDEFGHIJ

22 

23 

24def test_discriminator(): 1stuvwxyzA

25 # Example adapted from 

26 # [Pydantic docs](https://pydantic-docs.helpmanual.io/usage/types/#discriminated-unions-aka-tagged-unions): 

27 

28 class Cat(SQLModel): 1abcdefghi

29 pet_type: Literal["cat"] 1abcdefghi

30 meows: int 1abcdefghi

31 

32 class Dog(SQLModel): 1abcdefghi

33 pet_type: Literal["dog"] 1abcdefghi

34 barks: float 1abcdefghi

35 

36 class Lizard(SQLModel): 1abcdefghi

37 pet_type: Literal["reptile", "lizard"] 1abcdefghi

38 scales: bool 1abcdefghi

39 

40 class Model(SQLModel): 1abcdefghi

41 pet: Cat | Dog | Lizard = Field(..., discriminator="pet_type") 1abcdefghi

42 n: int 1abcdefghi

43 

44 Model(pet={"pet_type": "dog", "barks": 3.14}, n=1) # type: ignore[arg-type] 1abcdefghi

45 

46 with pytest.raises(ValidationError): 1abcdefghi

47 Model(pet={"pet_type": "dog"}, n=1) # type: ignore[arg-type] 1abcdefghi

48 

49 

50def test_discriminator_callable(): 1stuvwxyzA

51 # Example adapted from 

52 # [Pydantic docs](https://docs.pydantic.dev/latest/concepts/unions/#discriminated-unions-with-callable-discriminator): 

53 

54 class Pie(SQLModel): 1jklmnopqr

55 pass 1jklmnopqr

56 

57 class ApplePie(Pie): 1jklmnopqr

58 fruit: Literal["apple"] = "apple" 1jklmnopqr

59 

60 class PumpkinPie(Pie): 1jklmnopqr

61 filling: Literal["pumpkin"] = "pumpkin" 1jklmnopqr

62 

63 def get_discriminator_value(v: Any) -> str: 1jklmnopqr

64 if isinstance(v, dict): 1jklmnopqr

65 return v.get("fruit", v.get("filling")) 1jklmnopqr

66 return getattr(v, "fruit", getattr(v, "filling", None)) 

67 

68 class ThanksgivingDinner(SQLModel): 1jklmnopqr

69 dessert: ( 1jklmnopqr

70 Annotated[ApplePie, Tag("apple")] | Annotated[PumpkinPie, Tag("pumpkin")] 

71 ) = Field( 

72 discriminator=Discriminator(get_discriminator_value), 

73 ) 

74 

75 apple_pie = ThanksgivingDinner.model_validate({"dessert": {"fruit": "apple"}}) 1jklmnopqr

76 assert isinstance(apple_pie.dessert, ApplePie) 1jklmnopqr

77 

78 pumpkin_pie = ThanksgivingDinner.model_validate({"dessert": {"filling": "pumpkin"}}) 1jklmnopqr

79 assert isinstance(pumpkin_pie.dessert, PumpkinPie) 1jklmnopqr

80 

81 

82def test_repr(): 1stuvwxyzA

83 class Model(SQLModel): 123456789!

84 id: int | None = Field(primary_key=True) 123456789!

85 foo: str = Field(repr=False) 123456789!

86 

87 instance = Model(id=123, foo="bar") 123456789!

88 assert "foo=" not in repr(instance) 123456789!

89 

90 

91def test_min_items(): 1stuvwxyzA

92 with pytest.warns( 1KLMNOPQRS

93 DeprecationWarning, 

94 match="`min_items` is deprecated and will be removed, use `min_length` instead", 

95 ): 

96 

97 class Model(SQLModel): 1KLMNOPQRS

98 items: list[int] = Field(min_items=2) 1KLMNOPQRS

99 

100 Model(items=[1, 2]) 1KLMNOPQRS

101 

102 with pytest.raises(ValidationError) as exc_info: 1KLMNOPQRS

103 Model(items=[1]) 1KLMNOPQRS

104 assert len(exc_info.value.errors()) == 1 1KLMNOPQRS

105 assert exc_info.value.errors()[0]["type"] == "too_short" 1KLMNOPQRS

106 

107 

108def test_max_items(): 1stuvwxyzA

109 with pytest.warns( 1TUVWXYZ01

110 DeprecationWarning, 

111 match="`max_items` is deprecated and will be removed, use `max_length` instead", 

112 ): 

113 

114 class Model(SQLModel): 1TUVWXYZ01

115 items: list[int] = Field(max_items=2) 1TUVWXYZ01

116 

117 Model(items=[1, 2]) 1TUVWXYZ01

118 

119 with pytest.raises(ValidationError) as exc_info: 1TUVWXYZ01

120 Model(items=[1, 2, 3]) 1TUVWXYZ01

121 assert len(exc_info.value.errors()) == 1 1TUVWXYZ01

122 assert exc_info.value.errors()[0]["type"] == "too_long" 1TUVWXYZ01