polyfactory icon indicating copy to clipboard operation
polyfactory copied to clipboard

Enhancement: Support Ignoring SQLAlchemy Computed Columns When Generating Inserts (Build/Sync)

Open brandon-sorrow opened this issue 3 months ago • 1 comments

Summary

It would be awesome if there was a config value we could pass to the BaseFactory instantiation that would detect SQLAlchemy Computed columns and exclude them from the INSERT statement on create_sync, create_async and build calls to the factory.

Setting a computed column to Ignore() in the factory still results in the generated INSERT trying to insert a None value to the generated column.

Basic Example

from sqlalchemy import Computed
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
from polyfactory.factories.sqlalchemy_factory import SQLAlchemyFactory, T

class Some(DeclarativeBase):
    id: int = mapped_column(...)
    other_col: int = mapped_column(...)
    computed_col: int = mapped_column(
        Computed("id + other_col" , persisted=True),
        nullable=True,
    )

# Base Class
class BaseFactory(SQLAlchemyFactory[T]):
    __is_base_factory__ = True
    __session__ = SomeSession
    __async_session__ = SomeAsyncSession
    __min_collection_length__ = 3
    __set_relationships__ = False
    __set_association_proxy__ = False
    __check_model__ = False

class SomeFactory(BaseFactory[Some]): ...


SomeFactory.create_sync(id=10, other_col=10)

Doing this currently results in an error like:

def execute(
        self,
        query: Query,
        params: Params | None = None,
        *,
        prepare: bool | None = None,
        binary: bool | None = None,
    ) -> Self:
        """
        Execute a query or command to the database.
        """
        try:
            with self._conn.lock:
                self._conn.wait(
                    self._execute_gen(query, params, prepare=prepare, binary=binary)
                )
        except e._NO_TRACEBACK as ex:
>           raise ex.with_traceback(None)
E           psycopg.errors.GeneratedAlways: cannot insert a non-DEFAULT value into column "computed_col"
E           DETAIL:  Column "computed_col" is a generated column.

When using Postgres as the DB backend

Drawbacks and Impact

I'm not sure of any drawbacks, but it would make using computed columns possible when using PolyFactory.

Unresolved questions

Is there a workaround to accomplish this currently?

brandon-sorrow avatar Oct 03 '25 14:10 brandon-sorrow

PRs welcome to add this flag.

Setting a computed column to Ignore() in the factory still results in the generated INSERT trying to insert a None value to the generated column.

I am not able to reproduce this. Updating the example omits the column correctly. Are you able to confirm?

from sqlalchemy import Computed, create_engine
from sqlalchemy.orm import DeclarativeBase, Mapped, Session, mapped_column

from polyfactory.factories.sqlalchemy_factory import SQLAlchemyFactory, T
from polyfactory.fields import Ignore


class Base(DeclarativeBase):
    pass


class Some(Base):
    __tablename__ = "some_table"

    id: Mapped[int] = mapped_column(primary_key=True)
    other_col: Mapped[int]
    computed_col: Mapped[int] = mapped_column(
        Computed("id + other_col", persisted=True),
        nullable=True,
    )


engine = create_engine("sqlite+pysqlite:///:memory:", echo=True)
Some.metadata.create_all(engine)
session = Session(bind=engine)


# Base Class
class BaseFactory(SQLAlchemyFactory[T]):
    __is_base_factory__ = True
    __session__ = session
    __set_relationships__ = False
    __set_association_proxy__ = False
    __check_model__ = False


class SomeFactory(BaseFactory[Some]):
    computed_col = Ignore()


SomeFactory.create_sync()

adhtruong avatar Oct 05 '25 17:10 adhtruong