Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bug: SQLAlchemy instance build fails for models with nullable constraints #601

Open
1 of 4 tasks
MetaHG opened this issue Nov 6, 2024 · 0 comments · May be fixed by #602
Open
1 of 4 tasks

Bug: SQLAlchemy instance build fails for models with nullable constraints #601

MetaHG opened this issue Nov 6, 2024 · 0 comments · May be fixed by #602
Labels
bug Something isn't working

Comments

@MetaHG
Copy link

MetaHG commented Nov 6, 2024

Description

ModelSQLAlchemyFactory.build() fails on SQLAlchemy models with a constrained, optional field, e.g., mapped_column(String(10), nullable=True).

With nullable=False, no exception is raised.

The bug may have been introduced by #594 that has just been released (2024.11.06 - v2.18.0).

URL to code causing the issue

No response

MCVE

from polyfactory.factories.sqlalchemy_factory import SQLAlchemyFactory
from sqlalchemy import Integer, String
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column


class Base(DeclarativeBase):
    pass


class MyModel(Base):
    __tablename__ = "my_model"
    id: Mapped[int] = mapped_column(Integer, primary_key=True)
    name: Mapped[str | None] = mapped_column(String(10), nullable=True)


class MyModelFactory(SQLAlchemyFactory[MyModel]):
    __model__ = MyModel


MyModelFactory.build()

Steps to reproduce

No response

Screenshots

No response

Logs

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[5], line 20
     16 class MyModelFactory(SQLAlchemyFactory[MyModel]):
     17     __model__ = MyModel
---> 20 MyModelFactory.build()

File ~/toy-project/.venv/lib/python3.12/site-packages/polyfactory/factories/base.py:1080, in BaseFactory.build(cls, **kwargs)
   1071 @classmethod
   1072 def build(cls, **kwargs: Any) -> T:
   1073     """Build an instance of the factory's __model__
   1074 
   1075     :param kwargs: Any kwargs. If field names are set in kwargs, their values will be used.
   (...)
   1078 
   1079     """
-> 1080     return cast("T", cls.__model__(**cls.process_kwargs(**kwargs)))

File ~/toy-project/.venv/lib/python3.12/site-packages/polyfactory/factories/base.py:980, in BaseFactory.process_kwargs(cls, **kwargs)
    977 result: dict[str, Any] = {**kwargs}
    978 generate_post: dict[str, PostGenerated] = {}
--> 980 for field_meta in cls.get_model_fields():
    981     field_build_parameters = cls.extract_field_build_parameters(field_meta=field_meta, build_args=kwargs)
    982     if cls.should_set_field_value(field_meta, **kwargs) and not cls.should_use_default_value(field_meta):

File ~/toy-project/.venv/lib/python3.12/site-packages/polyfactory/factories/sqlalchemy_factory.py:196, in SQLAlchemyFactory.get_model_fields(cls)
    193 fields_meta: list[FieldMeta] = []
    195 table: Mapper = inspect(cls.__model__)  # type: ignore[assignment]
--> 196 fields_meta.extend(
    197     FieldMeta.from_type(
    198         annotation=cls.get_type_from_column(column),
    199         name=name,
    200         random=cls.__random__,
    201     )
    202     for name, column in table.columns.items()
    203     if cls.should_column_be_set(column)
    204 )
    205 if cls.__set_relationships__:
    206     for name, relationship in table.relationships.items():

File ~/toy-project/.venv/lib/python3.12/site-packages/polyfactory/factories/sqlalchemy_factory.py:197, in <genexpr>(.0)
    193 fields_meta: list[FieldMeta] = []
    195 table: Mapper = inspect(cls.__model__)  # type: ignore[assignment]
    196 fields_meta.extend(
--> 197     FieldMeta.from_type(
    198         annotation=cls.get_type_from_column(column),
    199         name=name,
    200         random=cls.__random__,
    201     )
    202     for name, column in table.columns.items()
    203     if cls.should_column_be_set(column)
    204 )
    205 if cls.__set_relationships__:
    206     for name, relationship in table.relationships.items():

File ~/toy-project/.venv/lib/python3.12/site-packages/polyfactory/field_meta.py:153, in FieldMeta.from_type(cls, annotation, random, name, default, constraints, randomize_collection_length, min_collection_length, max_collection_length, children)
    142     annotation = container[get_args(annotation)]  # type: ignore[index]
    144 field = cls(
    145     annotation=annotation,
    146     random=random,
   (...)
    150     constraints=constraints,
    151 )
--> 153 if field.type_args and not field.children:
    154     field.children = [
    155         cls.from_type(
    156             annotation=unwrap_new_type(arg),
   (...)
    160         if arg is not NoneType
    161     ]
    162 return field

File ~/toy-project/.venv/lib/python3.12/site-packages/polyfactory/field_meta.py:96, in FieldMeta.type_args(self)
     90 @property
     91 def type_args(self) -> tuple[Any, ...]:
     92     """Return the normalized type args of the annotation, if any.
     93 
     94     :returns: a tuple of types.
     95     """
---> 96     return tuple(TYPE_MAPPING.get(arg, arg) for arg in get_args(self.annotation))

File ~/toy-project/.venv/lib/python3.12/site-packages/polyfactory/field_meta.py:96, in <genexpr>(.0)
     90 @property
     91 def type_args(self) -> tuple[Any, ...]:
     92     """Return the normalized type args of the annotation, if any.
     93 
     94     :returns: a tuple of types.
     95     """
---> 96     return tuple(TYPE_MAPPING.get(arg, arg) for arg in get_args(self.annotation))

File /usr/lib/python3.12/typing.py:2023, in _AnnotatedAlias.__hash__(self)
   2022 def __hash__(self):
-> 2023     return hash((self.__origin__, self.__metadata__))

TypeError: unhashable type: 'dict'

Release Version

v2.18.0

Platform

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

Note

While we are open for sponsoring on GitHub Sponsors and
OpenCollective, we also utilize Polar.sh to engage in pledge-based sponsorship.

Check out all issues funded or available for funding on our Polar.sh dashboard

  • If you would like to see an issue prioritized, make a pledge towards it!
  • We receive the pledge once the issue is completed & verified
  • This, along with engagement in the community, helps us know which features are a priority to our users.
Fund with Polar
@MetaHG MetaHG added the bug Something isn't working label Nov 6, 2024
@adhtruong adhtruong linked a pull request Nov 6, 2024 that will close this issue
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

Successfully merging a pull request may close this issue.

1 participant