I want to dynamically derive columns with the server_default
set to Computed(...)
.
The statement going into Computed(...)
has to be derived from other columns, and therefore needs to have access to the class after the relationship
s have been mapped:
from uuid import uuid4
from sqlalchemy import func, Computed, ForeignKey, Column
from sqlalchemy.orm import relationship
from sqlalchemy.sql.base import Executable
from sqlalchemy.ext.declarative import as_declarative
from sqlalchemy.dialects.postgresql import INTEGER, UUID
def aggregate_computed(relationship: str, column: Column, expr: Executable):
...
@as_declarative()
class Base: pass
class Other(Base):
__tablename__ = "other"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid4)
this = relationship("This", back_populates="other")
this_id = Column(UUID(as_uuid=True), ForeignKey("this.id"))
class This(Base, IDMixin):
__tablename__ = "this"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid4)
other = relationship("Other", back_populates="this")
other_count = aggregate_computed("species", Column(INTEGER), func.count(1))
assert isinstance(This.other_count.server_default, Computed)
assert This.other_count.server_default.sqltext == """\
SELECT count(1) AS out
FROM other
WHERE this.id = other.this_id
"""
Where the query is constructed as such:
-- this is the decorated function
SELECT count(1) AS out
-- this is derived from the relationships.
-- for a m-to-n relation, it would make use of the `secondary` table
FROM other WHERE this.id = other.this_id
I know how to construct the query from the relationships, but I don’t know how to tell SQLAlchemy how to map everything except for my properties, then finish mapping while allowing those properties access to the class’s relationships.
The answer here is NOT:
- “Simply use a
column_property
”: Those accept only finished columns, and don’t allow to construct them with access to the mapped class - “Use
sqlalchemy_utils.aggregate
”: That one is implemented viaafter_flush
trigger, notGENERATED ALWAYS AS
.