Skip to content

Commit 497724c

Browse files
authored
fix: Type checking of None assignment to nullable fields (tortoise#2089)
* fix: Type checking of None assignment to nullable fields * Update CHANGELOG
1 parent 1f70faa commit 497724c

File tree

3 files changed

+376
-12
lines changed

3 files changed

+376
-12
lines changed

CHANGELOG.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,13 @@ Changelog
1010
1.0
1111
===
1212

13+
1.0.1 (unreleased)
14+
------------------
15+
Fixed
16+
^^^^^
17+
- Type checking of None assignment to nullable fields (#2089)
18+
19+
1320
1.0.0
1421
-----
1522

tests/fields/type_checks.py

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
"""
2+
This file contains the code that is meant to be checked by a type checker, not to be run as a test.
3+
"""
4+
5+
from datetime import date, datetime, time, timedelta
6+
from decimal import Decimal
7+
from enum import Enum, IntEnum
8+
from uuid import UUID
9+
10+
from tortoise.fields.data import (
11+
BinaryField,
12+
BooleanField,
13+
CharEnumField,
14+
CharField,
15+
DateField,
16+
DatetimeField,
17+
DecimalField,
18+
FloatField,
19+
IntEnumField,
20+
IntField,
21+
TimeDeltaField,
22+
TimeField,
23+
UUIDField,
24+
)
25+
from tortoise.models import Model
26+
27+
28+
class Status(IntEnum):
29+
PENDING = 1
30+
ACTIVE = 2
31+
INACTIVE = 3
32+
33+
34+
class Color(str, Enum):
35+
RED = "red"
36+
GREEN = "green"
37+
BLUE = "blue"
38+
39+
40+
class InheretedFromIntField(IntField):
41+
pass
42+
43+
44+
class TypeTestModel(Model):
45+
# CharField fields
46+
char_non_null = CharField(max_length=100, null=False)
47+
char_nullable = CharField(max_length=100, null=True)
48+
49+
# IntField fields
50+
int_non_null = IntField(null=False)
51+
int_nullable = IntField(null=True)
52+
53+
# BooleanField fields
54+
bool_non_null = BooleanField(null=False)
55+
bool_nullable = BooleanField(null=True)
56+
57+
# DecimalField fields
58+
decimal_non_null = DecimalField(max_digits=10, decimal_places=2, null=False)
59+
decimal_nullable = DecimalField(max_digits=10, decimal_places=2, null=True)
60+
61+
# DatetimeField fields
62+
datetime_non_null = DatetimeField(null=False)
63+
datetime_nullable = DatetimeField(null=True)
64+
65+
# DateField fields
66+
date_non_null = DateField(null=False)
67+
date_nullable = DateField(null=True)
68+
69+
# TimeField fields
70+
time_non_null = TimeField(null=False)
71+
time_nullable = TimeField(null=True)
72+
73+
# TimeDeltaField fields
74+
timedelta_non_null = TimeDeltaField(null=False)
75+
timedelta_nullable = TimeDeltaField(null=True)
76+
77+
# FloatField fields
78+
float_non_null = FloatField(null=False)
79+
float_nullable = FloatField(null=True)
80+
81+
# UUIDField fields
82+
uuid_non_null = UUIDField(null=False)
83+
uuid_nullable = UUIDField(null=True)
84+
85+
# BinaryField fields
86+
binary_non_null = BinaryField(null=False)
87+
binary_nullable = BinaryField(null=True)
88+
89+
# Enum fields
90+
int_enum_field_non_null = IntEnumField(enum_type=Status, null=False)
91+
int_enum_field_nullable = IntEnumField(enum_type=Status, null=True)
92+
char_enum_field_non_null = CharEnumField(enum_type=Color, max_length=10, null=False)
93+
char_enum_field_nullable = CharEnumField(enum_type=Color, max_length=10, null=True)
94+
95+
# inhereted fields
96+
inhereted_int_field_non_null = InheretedFromIntField(null=False)
97+
inhereted_int_field_nullable = InheretedFromIntField(null=True)
98+
99+
100+
def test_char_field_nullability() -> None:
101+
o = TypeTestModel(char_non_null="test", char_nullable="test")
102+
o.char_nullable = None
103+
o.char_non_null = "another test"
104+
105+
106+
def test_int_field_nullability() -> None:
107+
o = TypeTestModel(char_non_null="test", int_non_null=42, int_nullable=42)
108+
o.int_nullable = None
109+
o.int_non_null = 100
110+
111+
112+
def test_bool_field_nullability() -> None:
113+
o = TypeTestModel(char_non_null="test", bool_non_null=True, bool_nullable=True)
114+
o.bool_nullable = None
115+
o.bool_non_null = False
116+
117+
118+
def test_decimal_field_nullability() -> None:
119+
o = TypeTestModel(
120+
char_non_null="test", decimal_non_null=Decimal("10.50"), decimal_nullable=Decimal("10.50")
121+
)
122+
o.decimal_nullable = None
123+
o.decimal_non_null = Decimal("20.75")
124+
125+
126+
def test_datetime_field_nullability() -> None:
127+
now = datetime.now()
128+
o = TypeTestModel(char_non_null="test", datetime_non_null=now, datetime_nullable=now)
129+
o.datetime_nullable = None
130+
o.datetime_non_null = datetime(2024, 1, 1, 12, 0, 0)
131+
132+
133+
def test_date_field_nullability() -> None:
134+
today = date.today()
135+
o = TypeTestModel(char_non_null="test", date_non_null=today, date_nullable=today)
136+
o.date_nullable = None
137+
o.date_non_null = date(2024, 1, 1)
138+
139+
140+
def test_time_field_nullability() -> None:
141+
now_time = time(12, 0, 0)
142+
o = TypeTestModel(char_non_null="test", time_non_null=now_time, time_nullable=now_time)
143+
o.time_nullable = None
144+
o.time_non_null = time(15, 30, 0)
145+
146+
147+
def test_timedelta_field_nullability() -> None:
148+
delta = timedelta(days=1)
149+
o = TypeTestModel(char_non_null="test", timedelta_non_null=delta, timedelta_nullable=delta)
150+
o.timedelta_nullable = None
151+
o.timedelta_non_null = timedelta(days=2)
152+
153+
154+
def test_float_field_nullability() -> None:
155+
o = TypeTestModel(char_non_null="test", float_non_null=1.5, float_nullable=1.5)
156+
o.float_nullable = None
157+
o.float_non_null = 3.14
158+
159+
160+
def test_uuid_field_nullability() -> None:
161+
test_uuid = UUID("12345678-1234-5678-1234-567812345678")
162+
o = TypeTestModel(char_non_null="test", uuid_non_null=test_uuid, uuid_nullable=test_uuid)
163+
o.uuid_nullable = None
164+
o.uuid_non_null = UUID("87654321-4321-8765-4321-876543218765")
165+
166+
167+
def test_binary_field_nullability() -> None:
168+
o = TypeTestModel(char_non_null="test", binary_non_null=b"data", binary_nullable=b"data")
169+
o.binary_nullable = None
170+
o.binary_non_null = b"new data"
171+
172+
173+
# TODO: add support for proper types when null=True for enum fields
174+
# def test_enum_fields() -> None:
175+
# o = TypeTestModel(char_non_null="test", int_non_null=1, bool_non_null=True, float_non_null=1.0, binary_non_null=b"test")
176+
177+
# o.int_enum_field_nullable = None
178+
# o.int_enum_field_non_null = Status.ACTIVE
179+
180+
# o.char_enum_field_nullable = None
181+
# o.char_enum_field_non_null = Color.GREEN
182+
183+
184+
def test_inhereted_int_field() -> None:
185+
o = TypeTestModel(
186+
char_non_null="test", inhereted_int_field_non_null=42, inhereted_int_field_nullable=42
187+
)
188+
o.inhereted_int_field_nullable = None
189+
o.inhereted_int_field_non_null = 100

0 commit comments

Comments
 (0)