polyfactory icon indicating copy to clipboard operation
polyfactory copied to clipboard

Bug: Random values from a base Enum

Open sander76 opened this issue 10 months ago • 5 comments

Description

I don't think it's a bug, but not sure:

Consider this example:

from dataclasses import dataclass
from enum import Enum

from polyfactory.factories import DataclassFactory

class BaseEnum(Enum): ...


class OtherEnum(BaseEnum):  # <--- inherits from BaseEnum
    VALUE_1 = 1
    VALUE_2 = 2


@dataclass
class MyModel:
    values: dict[BaseEnum, int]  # <--- a dict with keys from the BaseEnum


class MyModelFactory(DataclassFactory[MyModel]): ...


if __name__ == "__main__":
    my_model = MyModelFactory.build()

This will raise an IndexError: IndexError: Cannot choose from an empty sequence. Which is obvious as Litestar will try to populate the keys in the values dict by doing this: __random__(list(BaseEnum)). That results in an empty list which gives the error.

But the BaseEnum (which I don't have control over) is only the base enum for the OtherEnum which does contain values.

Is there any way in my factory to tell when BaseEnum is encountered it should pick values from OtherEnum ?

URL to code causing the issue

No response

MCVE


Steps to reproduce

Run the above example.

Screenshots

Logs


Release Version

2.19.0

Platform

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

sander76 avatar Feb 07 '25 13:02 sander76

Hi! If you can not do something like this:

class MyModel:
    values: dict[OtherEnum, int]

Maybe this decision will suit you? Any of Use(), lambda or @classmethod.

nisemenov avatar Feb 07 '25 14:02 nisemenov

I think you mean I explicity/manually fill the values dictionary?. I could do that, but my problem is that my real data is really nested and that would require me to do the manual filling in a lot of places.

I have indeed no control of the shape of my model. So

class MyModel:
    values: dict[OtherEnum, int]

is not possible unfortunately.

sander76 avatar Feb 07 '25 16:02 sander76

I'm struggling with the same or a similar issue.

from enum import Enum

from polyfactory.factories.pydantic_factory import ModelFactory
from pydantic import Field
from pydantic_settings import BaseSettings


class SubSettings(BaseSettings):
    enum: Enum = Field(..., exclude=True)


class Settings(BaseSettings):
    sub_settings: SubSettings


class DummyEnum(str, Enum):
    DUMMY = "dummy"


class SubSettingsFactory(ModelFactory[SubSettings]):
    @classmethod
    def enum(cls) -> DummyEnum:
        return DummyEnum.DUMMY


class SettingsFactory(ModelFactory[Settings]):
    @classmethod
    def sub_settings(cls) -> SubSettings:
        return SubSettingsFactory.build()


if __name__ == "__main__":
    SubSettingsFactory.build()
    SettingsFactory.build()

This works. However, I didn't think that this part was necessary.

class SettingsFactory(ModelFactory[Settings]):
    @classmethod
    def sub_settings(cls) -> SubSettings:
        return SubSettingsFactory.build()

Based on the docs, it seems like this should be good enough.

class SettingsFactory(ModelFactory[Settings]): ...

However, when I use this, SettingsFactory.build() fails.

ericchansen avatar May 07 '25 01:05 ericchansen

@ericchansen this case is supported if configure this as the default factory https://polyfactory.litestar.dev/latest/usage/configuration.html#defining-default-factories

adhtruong avatar May 15 '25 07:05 adhtruong

For the original case, this is particularly well supported and current behaviour is correct.

As workaround, could override get_field_value and then check FieldMeta.annnotation and map this type explicitly. Some thing like

class MyBaseFactory(ModelFactory):
      @classmethod
      def get_field_value(
        field_meta: FieldMeta,
        field_build_parameters: Any | None = None,
        build_context: BuildContext | None = None,
     ) -> Any:
           if field_meta.annotation is  BaseEnum:
                    # Logic for mapping this

          return super().get_field_value(....)

PRs welcome to have this more generalised, maybe as a configuration on factory to map one type to another.

adhtruong avatar May 15 '25 07:05 adhtruong