Skip to content

Commit ec4e1a1

Browse files
authored
add enum support to pydantic_model_creator (#1798)
* add pydantic enum support
1 parent b9fda6c commit ec4e1a1

File tree

4 files changed

+133
-2
lines changed

4 files changed

+133
-2
lines changed

CHANGELOG.rst

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ Changed
2121
- Parametrizes UPDATE, DELETE, bulk update and create operations (#1785)
2222
- Parametrizes related field queries (#1797)
2323

24+
Added
25+
^^^^^
26+
- CharEnumField and IntEnumField is supported by pydantic_model_creator (#1798)
27+
2428
0.22.1
2529
------
2630
Fixed
@@ -52,7 +56,7 @@ Added
5256
Changed
5357
^^^^^^^
5458
- Change old pydantic docs link to new one (#1775).
55-
- Refactored pydantic_model_creator, interface not changed (#1763)
59+
- Refactored pydantic_model_creator, interface not changed (#1745)
5660
- Values are no longer validated to be right type upon loading from database (#1750)
5761
- Refactored private field names in queryset classes (#1751)
5862

CONTRIBUTORS.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ Contributors
6262
* Daniel Szucs ``@Quasar6X``
6363
* Rui Catarino ``@ruitcatarino``
6464
* Lance Moe ``@lancemoe``
65+
* Markus Beckschulte ``@markus-96``
6566

6667
Special Thanks
6768
==============

tests/contrib/test_pydantic.py

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
Tournament,
1616
User,
1717
json_pydantic_default,
18+
EnumFields,
1819
)
1920
from tortoise.contrib import test
2021
from tortoise.contrib.pydantic import (
@@ -1960,3 +1961,122 @@ def test_named_model_with_relations(self):
19601961
Pydantic2 = pydantic_model_creator(self.ModelWithRelations, name="Foo")
19611962

19621963
self.assertIs(Pydantic1, Pydantic2)
1964+
1965+
1966+
class TestPydanticEnum(test.TestCase):
1967+
def setUp(self) -> None:
1968+
self.EnumFields_Pydantic = pydantic_model_creator(EnumFields)
1969+
1970+
def test_int_enum(self):
1971+
with self.assertRaises(ValidationError) as cm:
1972+
self.EnumFields_Pydantic.model_validate({"id": 1, "service": 4, "currency": "HUF"})
1973+
self.assertEqual(
1974+
[
1975+
{
1976+
'type': 'enum',
1977+
'loc': ('service',),
1978+
'msg': 'Input should be 1, 2 or 3',
1979+
'input': 4,
1980+
'ctx': {'expected': '1, 2 or 3'}
1981+
}
1982+
],
1983+
cm.exception.errors(include_url=False)
1984+
)
1985+
with self.assertRaises(ValidationError) as cm:
1986+
self.EnumFields_Pydantic.model_validate({"id": 1, "service": "a string, not int", "currency": "HUF"})
1987+
self.assertEqual(
1988+
[
1989+
{
1990+
'type': 'enum',
1991+
'loc': ('service',),
1992+
'msg': 'Input should be 1, 2 or 3',
1993+
'input': "a string, not int",
1994+
'ctx': {'expected': '1, 2 or 3'}
1995+
}
1996+
],
1997+
cm.exception.errors(include_url=False)
1998+
)
1999+
2000+
def test_str_enum(self):
2001+
with self.assertRaises(ValidationError) as cm:
2002+
self.EnumFields_Pydantic.model_validate({"id": 1, "service": 3, "currency": "GoofyGooberDollar"})
2003+
self.assertEqual(
2004+
[
2005+
{
2006+
'type': 'enum',
2007+
'loc': ('currency',),
2008+
'msg': "Input should be 'HUF', 'EUR' or 'USD'",
2009+
'input': 'GoofyGooberDollar',
2010+
'ctx': {'expected': "'HUF', 'EUR' or 'USD'"}
2011+
}
2012+
],
2013+
cm.exception.errors(include_url=False)
2014+
)
2015+
with self.assertRaises(ValidationError) as cm:
2016+
self.EnumFields_Pydantic.model_validate({"id": 1, "service": 3, "currency": 1})
2017+
self.assertEqual(
2018+
[
2019+
{
2020+
'type': 'enum',
2021+
'loc': ('currency',),
2022+
'msg': "Input should be 'HUF', 'EUR' or 'USD'",
2023+
'input': 1,
2024+
'ctx': {'expected': "'HUF', 'EUR' or 'USD'"}
2025+
}
2026+
],
2027+
cm.exception.errors(include_url=False)
2028+
)
2029+
2030+
def test_enum(self):
2031+
with self.assertRaises(ValidationError) as cm:
2032+
self.EnumFields_Pydantic.model_validate({"id": 1, "service": 4, "currency": 1})
2033+
self.assertEqual(
2034+
[
2035+
{
2036+
'type': 'enum',
2037+
'loc': ('service',),
2038+
'msg': 'Input should be 1, 2 or 3',
2039+
'input': 4, 'ctx': {'expected': '1, 2 or 3'}
2040+
},
2041+
{
2042+
'type': 'enum',
2043+
'loc': ('currency',),
2044+
'msg': "Input should be 'HUF', 'EUR' or 'USD'",
2045+
'input': 1,
2046+
'ctx': {'expected': "'HUF', 'EUR' or 'USD'"}
2047+
}
2048+
],
2049+
cm.exception.errors(include_url=False)
2050+
)
2051+
2052+
# should simply not raise any error:
2053+
self.EnumFields_Pydantic.model_validate({"id": 1, "service": 3, "currency": "HUF"})
2054+
2055+
self.assertEqual(
2056+
{
2057+
'additionalProperties': False,
2058+
'properties': {
2059+
'id': {'maximum': 2147483647, 'minimum': -2147483648, 'title': 'Id', 'type': 'integer'},
2060+
'service': {
2061+
'description': 'python_programming: 1<br/>database_design: 2<br/>system_administration: 3',
2062+
'enum': [1, 2, 3],
2063+
'ge': -32768,
2064+
'le': 32767,
2065+
'title': 'Service',
2066+
'type': 'integer'
2067+
},
2068+
'currency': {
2069+
'default': 'HUF',
2070+
'description': 'HUF: HUF<br/>EUR: EUR<br/>USD: USD',
2071+
'enum': ['HUF', 'EUR', 'USD'],
2072+
'maxLength': 3,
2073+
'title': 'Currency',
2074+
'type': 'string'
2075+
}
2076+
},
2077+
'required': ['id', 'service'],
2078+
'title': 'EnumFields',
2079+
'type': 'object'
2080+
},
2081+
self.EnumFields_Pydantic.model_json_schema()
2082+
)

tortoise/contrib/pydantic/creator.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import inspect
22
from base64 import b32encode
33
from copy import copy
4+
from enum import IntEnum, Enum
45
from hashlib import sha3_224
56
from typing import (
67
TYPE_CHECKING,
@@ -33,6 +34,7 @@
3334
)
3435
from tortoise.contrib.pydantic.utils import get_annotations
3536
from tortoise.fields import Field, JSONField
37+
from tortoise.fields.data import IntEnumFieldInstance, CharEnumFieldInstance
3638

3739
if TYPE_CHECKING: # pragma: nocoverage
3840
from tortoise.models import Model
@@ -509,7 +511,11 @@ def _process_data_field(
509511
json_schema_extra["readOnly"] = constraints["readOnly"]
510512
del constraints["readOnly"]
511513
fconfig.update(constraints)
512-
python_type = getattr(field, "related_model", field.field_type)
514+
python_type: Union[Type[Enum], Type[IntEnum], Type]
515+
if isinstance(field, (IntEnumFieldInstance, CharEnumFieldInstance)):
516+
python_type = field.enum_type
517+
else:
518+
python_type = getattr(field, "related_model", field.field_type)
513519
ptype = python_type
514520
if field.null:
515521
json_schema_extra["nullable"] = True

0 commit comments

Comments
 (0)