(Note: In line with Stack Overflow's encouragement to enrich the community by answering one's own questions, especially after investing significant effort in troubleshooting, I am providing an answer below. I eagerly welcome further comments, additional insights, or alternative approaches! I would also like to acknowledge the valuable assistance from F. Caselli on a SQLAlchemy forum, which helped me to narrow down the search for a solution.)
Hello, I'm trying to create a COMPUTED column directly in the ORM definition using mapped_column()
in SqlAlchemy 2.0, but I haven't been successful (I've checked Google and tried to find the answer in the documentation).
I know that for the core model, it's enough to declare a table in the following way (Toy example where "area"
is a COMPUTED column):
square = Table(
"square",
metadata_obj,
Column("id", Integer, primary_key=True),
Column("side", Integer),
Column("area", Integer, Computed("side * side")),
)
This is equivalent to the following SQL DDL statement (Postgresql >12):
CREATE TABLE square (
id SERIAL NOT NULL,
side INTEGER,
area INTEGER GENERATED ALWAYS AS (side * side) STORED,
PRIMARY KEY (id)
)
However, when trying to do the equivalent in ORM using Mapped[]
and mapped_column()
, I can't seem to find the solution.
I imagined something like the following might work, but it didn’t:
class Square(Base):
...
area: Mapped[int] = mapped_column(Computed("side*side"))
Before that, I also tried other configurations, such as passing Computed()
as server_default=
, and using FetchedValue
, but in all cases, I encountered errors.
I also came to @hybrid_property
, but I understand that it's not the same (since I need the computed column to be part of the table's DDL definition and @hybrid_property
seems to leave the computation to be done during selects but not as DDL).
I would appreciate if someone could guide me on the correct use of mapped_column()
with Computed()
!
We discovered that the missing part was only the parameter:
init=False
So, in order to make the following work:
area: Mapped[int] = mapped_column(Computed("side*side"))
it must be changed to:
area: Mapped[int] = mapped_column(Computed("side*side"), init=False)
It turns out that my Base
class is derived from MappedAsDataclass
, and the creation of the __init__()
method of the dataclass was not handled properly as required by this type of Base
class.
Apparently, the MappedAsDataclass
machinery does not automatically exclude the Computed()
column from the __init__()
method, so we must explicitly set init=False
in the mapped_column()
definition.
To summarize, the solution in my case was to declare the area
column as follows:
class Base(MappedAsDataclass, DeclarativeBase):
pass
class Square(Base):
...
# for computed column, use init=False if Base is a MappedAsDataclass
area: Mapped[int] = mapped_column(Computed("side*side"), init=False)
...
Thanks to F. Caselli for helping narrow the search for a solution in the sqlalchemy forum!!