polyfactory icon indicating copy to clipboard operation
polyfactory copied to clipboard

feat(sqlalchemy-factory): Add support for SQLAlchemy custom types

Open bc291 opened this issue 2 years ago • 3 comments

Pull Request Checklist

  • [x] New code has 100% test coverage
  • [x] (If applicable) The prose documentation has been updated to reflect the changes introduced by this PR
  • [ ] (If applicable) The reference documentation has been updated to reflect the changes introduced by this PR
  • [x] Pre-Commit Checks were ran and passed
  • [x] Tests were ran and passed

Description

Currently custom SQLAlchemy types are not supported which results in:

polyfactory/factories/base.py:715: in build
    return cast("T", cls.__model__(**cls.process_kwargs(**kwargs)))
polyfactory/factories/base.py:676: in process_kwargs
    for field_meta in cls.get_model_fields():
polyfactory/factories/sqlalchemy_factory.py:145: in get_model_fields
    fields_meta.extend(
polyfactory/factories/sqlalchemy_factory.py:147: in <genexpr>
    annotation=cls.get_type_from_column(column),
polyfactory/factories/sqlalchemy_factory.py:133: in get_type_from_column
    annotation = column.type.python_type
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = CustomType()

    @property
    def python_type(self) -> Type[Any]:
        """Return the Python type object expected to be returned
        by instances of this type, if known.
    
        Basically, for those types which enforce a return type,
        or are known across the board to do such for all common
        DBAPIs (like ``int`` for example), will return that type.
    
        If a return type is not defined, raises
        ``NotImplementedError``.
    
        Note that any type also accommodates NULL in SQL which
        means you can also get back ``None`` from any type
        in practice.
    
        """
>       raise NotImplementedError()
E       NotImplementedError

This PR adds support for custom types created by:

  • subclassing sqlalchemy.types.TypeDecorator; note: impl type needs to be python-mappable
class CustomType(types.TypeDecorator):
    impl = DateTime(timezone=True)

class Model(Base):
    __tablename__ = "example_table"

    id: orm.Mapped[int] = orm.mapped_column(primary_key=True)
    custom_type: orm.Mapped[Any] = orm.mapped_column(type_=CustomType(), nullable=False)

instance = ModelFactory.build()
assert isinstance(instance.custom_type, datetime.datetime)
  • subclassing sqlalchemy.types.UserDefinedType:
class CustomType(types.UserDefinedType):
    ...

class Model(Base):
    __tablename__ = "example_table"

    id: orm.Mapped[int] = orm.mapped_column(primary_key=True)
    custom_type: orm.Mapped[Any] = orm.mapped_column(type_=CustomType())

In this case, ParameterException is raised which encourages user to override get_sqlalchemy_types class method in the factory:

User defined type detected (subclass of {types.UserDefinedType}). Override get_sqlalchemy_types to provide factory function.

class ModelFactory(SQLAlchemyFactory[Model]):
    __model__ = Model

    @classmethod
    def get_sqlalchemy_types(cls) -> dict[Any, Callable[[], Any]]:
         return super().get_sqlalchemy_types() | {CustomType: lambda: cls.__faker__.date_time()}

...
instance = ModelFactory.build()
assert isinstance(instance.custom_type, datetime.datetime)

Close Issue(s)

bc291 avatar Oct 06 '23 22:10 bc291

Documentation preview will be available shortly at https://litestar-org.github.io/polyfactory-docs-preview/398

github-actions[bot] avatar Oct 07 '23 12:10 github-actions[bot]

@adhtruong If you have time, could you take a look at this as well?

vkcku avatar Oct 08 '23 09:10 vkcku

@bc291 any updates?

vkcku avatar Oct 14 '23 11:10 vkcku

Thanks for the work on this @bc291 . I think this use case is now covered by https://github.com/litestar-org/polyfactory/pull/513/files. Please reopen if missed off a use case here. You will need to sync with main

adhtruong avatar Apr 28 '24 12:04 adhtruong