Skip to content

Commit

Permalink
Check we can have Generics of types with no pydantic schema
Browse files Browse the repository at this point in the history
Signed-off-by: Nijat Khanbabayev <[email protected]>
  • Loading branch information
NeejWeej committed Jan 24, 2025
1 parent 9075a07 commit b756673
Show file tree
Hide file tree
Showing 2 changed files with 52 additions and 4 deletions.
9 changes: 7 additions & 2 deletions csp/impl/struct.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,13 @@ def _get_pydantic_core_schema(cls, _source_type, handler):
continue # we skip fields with underscore, like pydantic does
try:
field_schema = handler.generate_schema(field_type)
except PydanticSchemaGenerationError: # for classes we dont have a schema for
field_schema = core_schema.is_instance_schema(field_type)
except PydanticSchemaGenerationError:
# This logic allows for handling generic types with types we cant get a schema for, only 1 layer deep, same as csp
item_tp = typing.Any if typing.get_origin(field_type) is None else typing.get_args(field_type)[0]
try:
field_schema = handler.generate_schema(item_tp)
except PydanticSchemaGenerationError:
field_schema = core_schema.any_schema() # give up finally

if field_name in cls.__defaults__:
field_schema = core_schema.with_default_schema(
Expand Down
47 changes: 45 additions & 2 deletions csp/tests/impl/test_struct.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import unittest
from datetime import date, datetime, time, timedelta
from pydantic import TypeAdapter, ValidationError
from pydantic_core import PydanticSerializationError
from typing import Any, Dict, List, Literal, Optional, Set, Tuple, Union
from typing_extensions import Annotated

Expand Down Expand Up @@ -3079,16 +3080,58 @@ class EnumStruct(csp.Struct):
self.assertEqual(result.enum_field, MyEnum.A)
self.assertEqual(result.enum_list, [MyEnum.A, MyEnum.B, MyEnum.A])

# 6. test with arbitrary class
def test_non_serializable_pydantic(self):
class DummyBlankClass: ...

class StructWithDummy(csp.Struct):
x: int
y: DummyBlankClass
z: List[DummyBlankClass]
z1: Dict[DummyBlankClass, DummyBlankClass]
z2: Optional[Dict[DummyBlankClass, int]]
z3: List[List[DummyBlankClass]]

val = DummyBlankClass()
new_struct = TypeAdapter(StructWithDummy).validate_python(dict(x=12, y=val))
struct_as_dict = dict(x=12, y=val, z=[val], z1={val: val}, z2=None)
new_struct = TypeAdapter(StructWithDummy).validate_python(struct_as_dict)
self.assertTrue(new_struct.y is val)
self.assertTrue(new_struct.z[0] is val)
self.assertTrue(new_struct.z1[val] is val)
self.assertTrue(new_struct.z2 is None)
self.assertEqual(TypeAdapter(StructWithDummy).dump_python(new_struct), struct_as_dict)

for original_z2 in [None, {val: 12}]:
struct_as_dict = dict(
x=12,
y=val,
z=set([val]), # type is off
z1={val: val},
z2=original_z2,
z3=set([tuple([val, val])]),
)
new_struct = TypeAdapter(StructWithDummy).validate_python(struct_as_dict)
self.assertTrue(new_struct.y is val)
self.assertTrue(new_struct.z[0] is val)
self.assertTrue(new_struct.z1[val] is val)
self.assertTrue(new_struct.z2 is original_z2)
self.assertTrue(new_struct.z3[0][0] is val)
self.assertTrue(new_struct.z3[0][1] is val)

new_struct_as_dict = TypeAdapter(StructWithDummy).dump_python(new_struct)
self.assertEqual(new_struct_as_dict.pop("z"), [val]) # turned into a list!
self.assertEqual(struct_as_dict.pop("z"), set([val])) # remains a set

# turned into a list of tuples! Note that the inner type is wrong, we do not error since we
# pass an 'any_schema'. We maintain csp's behavior by passing the raw result to csp.
self.assertEqual(new_struct_as_dict.pop("z3"), [tuple([val, val])])
csp_struct = StructWithDummy(**struct_as_dict)
self.assertEqual(csp_struct.z3, [tuple([val, val])])

self.assertEqual(struct_as_dict.pop("z3"), set([tuple([val, val])])) # remains a set
self.assertEqual(new_struct_as_dict, struct_as_dict)

with self.assertRaises(PydanticSerializationError):
TypeAdapter(StructWithDummy).dump_json(new_struct)

def test_pydantic_validation_complex(self):
"""Test Pydantic validation with complex nested types and serialization"""
Expand Down

0 comments on commit b756673

Please sign in to comment.