feat(sqlalchemy-factory): Add support for SQLAlchemy custom types
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:impltype 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)
Documentation preview will be available shortly at https://litestar-org.github.io/polyfactory-docs-preview/398
@adhtruong If you have time, could you take a look at this as well?
@bc291 any updates?
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