Bug: Infinite recursion error with discriminated unions
Description
This seems to occur when a Discriminated Union model has a Discriminated Union field. The first level of this problem was fixed in #76 but it is still broken for the next level down.
URL to code causing the issue
No response
MCVE
from typing import Literal, Union
from typing_extensions import Annotated
from pydantic import BaseModel, Field
class BlackCat(BaseModel):
pet_type: Literal['cat']
color: Literal['black']
black_name: str
pet: 'Pet'
class WhiteCat(BaseModel):
pet_type: Literal['cat']
color: Literal['white']
white_name: str
pets: 'Pet'
Cat = Annotated[Union[BlackCat, WhiteCat], Field(discriminator='color')]
class Dog(BaseModel):
pet_type: Literal['dog']
name: str
pet: 'Pet'
Pet = Annotated[Union[Cat, Dog], Field(discriminator='pet_type')]
BlackCat.update_forward_refs()
WhiteCat.update_forward_refs()
Dog.update_forward_refs()
class Model(BaseModel):
pet: Pet
n: int
from polyfactory.factories.pydantic_factory import ModelFactory
class MyModelFactory(ModelFactory):
__model__ = Model
# This will create a max recursion error
obj = MyModelFactory.build()
print(obj)
Steps to reproduce
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
Screenshots
"In the format of: ``"
Logs
No response
Litestar Version
polyfactory==2.0.1
Platform
- [ ] Linux
- [ ] Mac
- [ ] Windows
- [ ] Other (Please specify in the description above)
I've got some fairly complex models and haven't identified the root cause of this, but I'm also getting infinite recursion errors with pydantic factories. The models themselves definitely don't recurse though.
Python 3.10, pydantic 1.10.7, polyfactory 2.1.0
Stack trace if it's useful:
.../test_ThingView.py:24: in <listcomp>
things = [thing_factory.build() for i in range(10)]
/var/virtualenv/lib/python3.10/site-packages/polyfactory/factories/pydantic_factory.py:166: in build
processed_kwargs = cls.process_kwargs(**kwargs)
/var/virtualenv/lib/python3.10/site-packages/polyfactory/factories/base.py:681: in process_kwargs
result[field_meta.name] = cls.get_field_value(field_meta, field_build_parameters=field_build_parameters)
/var/virtualenv/lib/python3.10/site-packages/polyfactory/factories/base.py:600: in get_field_value
return handle_complex_type(field_meta=field_meta, factory=cls)
/var/virtualenv/lib/python3.10/site-packages/polyfactory/value_generators/complex_types.py:72: in handle_complex_type
return factory.get_field_value(field_meta)
/var/virtualenv/lib/python3.10/site-packages/polyfactory/factories/base.py:600: in get_field_value
return handle_complex_type(field_meta=field_meta, factory=cls)
E RecursionError: maximum recursion depth exceeded in comparison
!!! Recursion detected (same locals & position)
=============================================================================== short test summary info ===============================================================================
FAILED .../tests/test_ThingView.py::test_ThingView_does_right_thing[2] - RecursionError: maximum recursion depth exceeded in comparison
================================================================================== 1 failed in 1.34s ============================================================================
I've got some fairly complex models and haven't identified the root cause of this, but I'm also getting infinite recursion errors with pydantic factories. The models themselves definitely don't recurse though.
Python 3.10, pydantic 1.10.7, polyfactory 2.1.0
Stack trace if it's useful:
.../test_ThingView.py:24: in <listcomp> things = [thing_factory.build() for i in range(10)] /var/virtualenv/lib/python3.10/site-packages/polyfactory/factories/pydantic_factory.py:166: in build processed_kwargs = cls.process_kwargs(**kwargs) /var/virtualenv/lib/python3.10/site-packages/polyfactory/factories/base.py:681: in process_kwargs result[field_meta.name] = cls.get_field_value(field_meta, field_build_parameters=field_build_parameters) /var/virtualenv/lib/python3.10/site-packages/polyfactory/factories/base.py:600: in get_field_value return handle_complex_type(field_meta=field_meta, factory=cls) /var/virtualenv/lib/python3.10/site-packages/polyfactory/value_generators/complex_types.py:72: in handle_complex_type return factory.get_field_value(field_meta) /var/virtualenv/lib/python3.10/site-packages/polyfactory/factories/base.py:600: in get_field_value return handle_complex_type(field_meta=field_meta, factory=cls) E RecursionError: maximum recursion depth exceeded in comparison !!! Recursion detected (same locals & position) =============================================================================== short test summary info =============================================================================== FAILED .../tests/test_ThingView.py::test_ThingView_does_right_thing[2] - RecursionError: maximum recursion depth exceeded in comparison ================================================================================== 1 failed in 1.34s ============================================================================
Do you have an MCVE? Is it also related to nested discriminated unions?
Do you have an MCVE?
Not yet, models are a bit complex and I'll have to slowly remove things until I can identify source of the problem.
Not sure if it's the same issue but I ran into RecursionError with recursive types. MCVE:
from typing import TypeAlias, Literal
from polyfactory.factories.pydantic_factory import ModelFactory
from pydantic import BaseModel
Unit: TypeAlias = "UnitProduct | UnitRatio | AtomicUnit"
class UnitProduct(BaseModel):
"""A product of two units"""
unit1: Unit
unit2: Unit
class UnitRatio(BaseModel):
"""A ratio of two units"""
numerator: Unit
denominator: Unit
class AtomicUnit(BaseModel):
"""A base unit that cannot be further reduced"""
unit: Literal["m", "s", "kg", "A", "K", "mol", "cd"]
class Quantity(BaseModel):
value: float
unit: Unit
class QuantityFactory(ModelFactory):
__model__ = Quantity
@classmethod
def value(cls):
return round(cls.__random__.uniform(0, 10), 2)
if __name__ == '__main__':
print(QuantityFactory.build())
This may fixed now with the changes from https://github.com/litestar-org/polyfactory/pull/468.
- The original MVC has no way to be constructed easily as fully recursive. If a type in one of the models is changed to
Pet | Nonethen this works. - https://github.com/litestar-org/polyfactory/issues/205#issuecomment-1814654295 works with main with no changes
@adhtruong thanks for pointing this out. I think you're right that the original MCVE is always going to result in an infinite recursion unless we have a base case we can use.
Also, @adhtruong could you hop in on the Discord channel?