litestar-org / polyfactory

Simple and powerful factories for mock data generation
https://polyfactory.litestar.dev/
MIT License
1.05k stars 81 forks source link

Union types with __allow_none_optionals__ causing maximum recursion depth exceeded errors #35

Closed hozn closed 2 years ago

hozn commented 2 years ago

When I have

  1. A model defined where some fields are Union of types and None and
  2. __allow_none_optionals__= True

I'm getting an error: RecursionError: maximum recursion depth exceeded while getting the repr of an object

# snip
File "/Users/hans/workspace/example/.venv/lib/python3.10/site-packages/pydantic_factories/value_generators/complex_types.py", line 94, in handle_complex_type
  return model_factory.get_field_value(model_field=model_field)
File "/Users/hans/workspace/example/.venv/lib/python3.10/site-packages/pydantic_factories/factory.py", line 431, in get_field_value
  return handle_complex_type(model_field=model_field, model_factory=cls)
File "/Users/hans/workspace/example/.venv/lib/python3.10/site-packages/pydantic_factories/value_generators/complex_types.py", line 94, in handle_complex_type
  return model_factory.get_field_value(model_field=model_field)
File "/Users/hans/workspace/example/.venv/lib/python3.10/site-packages/pydantic_factories/factory.py", line 431, in get_field_value
  return handle_complex_type(model_field=model_field, model_factory=cls)
File "/Users/hans/workspace/example/.venv/lib/python3.10/site-packages/pydantic_factories/value_generators/complex_types.py", line 88, in handle_complex_type
  if is_union(model_field=model_field) and model_field.sub_fields:
File "/Users/hans/workspace/example/.venv/lib/python3.10/site-packages/pydantic_factories/utils.py", line 74, in is_union
  return repr(model_field.outer_type_).split("[")[0] == "typing.Union"
RecursionError: maximum recursion depth exceeded while getting the repr of an object`

Here's a simple (python 3.10) reproduce script:

import uuid

from pydantic import BaseModel
from pydantic_factories import ModelFactory

class Location(BaseModel):
    id: uuid.UUID | str | None
    name: str | None

class LocationFactory(ModelFactory):
    __model__ = Location
    __allow_none_optionals__ = False

if __name__ == "__main__":
    LocationFactory.build()

Version info:

❯ pip freeze | grep pydantic
pydantic==1.9.0
pydantic-factories==1.2.6

❯ python --version
Python 3.10.2
Goldziher commented 2 years ago

Hi there.

This seems to be a bug with how we determine if a given value is a Union type and the 3.10 union syntax. The error is here: repr(model_field.outer_type_).split("[")[0] == "typing.Union", where I would assume that either pydantic changed its logic respective of union typings, or that python itself has a different repr for these types.

I will have time to check this during the weekend, unless you or someone else would like to give it a go.

DaanRademaker commented 2 years ago

It seems that the repr method is different for the 3.10 union syntax. uuid.UUID | str | None

so I guess the is_union function in utils.py could be changed into something like this.

def is_union(model_field: ModelField) -> bool:
    """Determines whether the given model_field is type Union"""

    if repr(model_field.outer_type_).split("[")[0] == "typing.Union":
        return True
    elif "|" in repr(model_field.outer_type_):
        return True
    else:
        False
Goldziher commented 2 years ago

If you add a PR with some test cases for this it would be great.

DaanRademaker commented 2 years ago

Will add PR today.

Goldziher commented 2 years ago

v1.2.7 released, thanks to @DaanRademaker for the PR! please confirm this issue is resolved and if it is - close this issue @hozn

hozn commented 2 years ago

Thank you both! I will test this out.

hozn commented 2 years ago

Confirmed fixed; thanks again.