Bug: Python 3.13 Types are not supported
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)
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])
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())
I would like to solve this problem.
Good news - the fix for this bug has already been added! ✅ #711