polyfactory icon indicating copy to clipboard operation
polyfactory copied to clipboard

Bug: Python 3.13 Types are not supported

Open dickermoshe opened this issue 7 months ago • 4 comments

Description

I'm using an Annotated Type throughout my codebase to enforce the minimum length of a list:

type NonEmptyList[T] = Annotated[list[T], annotated_types.Len(1)]

However it seems that this is not supported. I had to do the following as a work around for now:

from typing import Annotated, TypeVar

import annotated_types
T = TypeVar("T")
NonEmptyList = Annotated[list[T], annotated_types.Len(1)]

URL to code causing the issue

No response

MCVE

from typing import Annotated, Any, Dict, Type

import annotated_types
from polyfactory import BaseFactory
from pydantic import BaseModel

type NonEmptyList[T] = Annotated[list[T], annotated_types.Len(1)]


class CustomModelFactory[T: BaseModel](BaseFactory[T]):
    __is_base_factory__ = True

    @classmethod
    def get_provider_map(cls) -> Dict[Type, Any]:
        providers_map = super().get_provider_map()

        return {
            NonEmptyList: BaseFactory.__faker__.pylist,
            **providers_map,
        }


class Foo(BaseModel):
    bar: NonEmptyList[int]


class FooFactory(CustomModelFactory[Foo]): ...


print(FooFactory.build())

Steps to reproduce

1. Install `pydantic`, `annotated_types` and `polyfactory`
2. Run the above code

Screenshots

Logs

Traceback (most recent call last):
  File "c:\Users\dicke\DickerSystems\chaimlegal2\qqq.py", line 31, in <module>
    print(FooFactory.build())
          ~~~~~~~~~~~~~~~~^^
  File "C:\Users\dicke\DickerSystems\chaimlegal2\.venv\Lib\site-packages\polyfactory\factories\pydantic_factory.py", line 518, in build
    processed_kwargs = cls.process_kwargs(**kwargs)
  File "C:\Users\dicke\DickerSystems\chaimlegal2\.venv\Lib\site-packages\polyfactory\factories\base.py", line 1064, in process_kwargs
    field_result = cls.get_field_value(
        field_meta,
        field_build_parameters=field_build_parameters,
        build_context=_build_context,
    )
  File "C:\Users\dicke\DickerSystems\chaimlegal2\.venv\Lib\site-packages\polyfactory\factories\pydantic_factory.py", line 491, in get_field_value
    result = super().get_field_value(
        field_meta=field_meta, field_build_parameters=field_build_parameters, build_context=build_context
    )
  File "C:\Users\dicke\DickerSystems\chaimlegal2\.venv\Lib\site-packages\polyfactory\factories\base.py", line 859, in get_field_value
    raise ParameterException(
        msg,
    )
polyfactory.exceptions.ParameterException: Unsupported type: NonEmptyList[int] on field 'bar' from class FooFactory.

Either use 'add_provider', extend the providers map, or add a factory function for the field on the model.

Release Version

2.21.0

Platform

  • [ ] Linux
  • [ ] Mac
  • [x] Windows
  • [ ] Other (Please specify in the description above)

dickermoshe avatar May 29 '25 00:05 dickermoshe

Hi @dickermoshe.

Probably your example is incorrect, since you need to inherit from Factory for a specific library, for example, if we are talking about Pydantic, then this is ModelFactory.

Так что ваш пример должен выглядеть следующим образом:

from typing import Annotated, Any, Dict, TypeAliasType

import annotated_types
from polyfactory.factories.pydantic_factory import ModelFactory
from pydantic import BaseModel

type NonEmptyList[T] = Annotated[list[T], annotated_types.Len(1)]


class CustomModelFactory[T: BaseModel](ModelFactory[T]):
    __is_base_factory__ = True

    @classmethod
    def get_provider_map(cls) -> Dict[TypeAliasType, Any]:
        providers_map = super().get_provider_map()

        return {
            NonEmptyList: ModelFactory.__faker__.pylist,
            **providers_map,
        }


class Foo(BaseModel):
    bar: NonEmptyList[int]


class FooFactory(CustomModelFactory[Foo]): ...


print(FooFactory.build())

This looks like a bug. The main issue lies in the method ModelFactory.get_model_fields. During the retrieval of field_name and field_info, there is no meta-information in field_info that could exist if we specified bar: Annotated[list[int], annotated_types.Len(1)] instead of bar: NonEmptyList[int]. Apparently, this is directly a problem with Pydantic V2, as it is strange why this information is not found in FieldInfo.

There is only one way to solve this problem in ModelFactory: to use pydantic.TypeAdapter, which allows extracting data from the type annotation. Next, all this information needs to be placed in polyfactory.factory.pydantic_factory.PydanticFieldMeta, and then generate the data. This is just one of the solutions to the problem within the Polyfactory library.

In your case, I can suggest writing it like this:


class FooFactory(ModelFactory[Foo]):
    
    @classmethod
    def bar(cls) -> list[int]:
        return cls.__faker__.pylist(1, value_types=[int])

Or this solution:

from polyfactory import Use


class FooFactory(ModelFactory[Foo]):
    bar = Use(ModelFactory.__faker__.pylist, nb_elements=1, value_types=[int])

Rub1kCube avatar May 30 '25 10:05 Rub1kCube

Thanks for reporting! I'd recommend your workaround used to not use new syntax for now.

PRs welcome to fix. Internally this is not being properly unwrapped in all places yet but is available. Here is a minimal example of this

from typing import Annotated, TypeVar, get_origin

import annotated_types

from pydantic import BaseModel

from polyfactory.factories.pydantic_factory import ModelFactory

T = TypeVar("T")
type NonEmptyList[T] = Annotated[list[T], annotated_types.Len(1)]


class Foo(BaseModel):
    bar: NonEmptyList[int]


class FooFactory(ModelFactory[Foo]): ...


print(FooFactory.get_model_fields())
print(get_origin(FooFactory.get_model_fields()[0].annotation).__value__)
print(FooFactory.build())

adhtruong avatar May 31 '25 12:05 adhtruong

I would like to solve this problem.

Rub1kCube avatar May 31 '25 13:05 Rub1kCube

Good news - the fix for this bug has already been added! ✅ #711

Rub1kCube avatar Jun 10 '25 16:06 Rub1kCube