sqlalchemy / sqlalchemy

The Database Toolkit for Python
https://www.sqlalchemy.org
MIT License
9.53k stars 1.42k forks source link

Support lambdas in `column_property` #6120

Open CaselIT opened 3 years ago

CaselIT commented 3 years ago

Is your feature request related to a problem? Please describe. It would be nice to be able to use lambdas to define column_properties inline with the model definition.

Describe the solution you'd like Example:

class X(Base):
    __tablename__ = 'foo'

    id = sa.Column(sa.Integer, primary_key=True)
    cp = sa.orm.column_property(lambda: sa.select(sa.exists(1).where(Y.xid == id)).scalar_subquery())

class Y(Base):
    __tablename__='bar'
    xid = sa.Column(sa.ForeignKey('foo.id'), primary_key=True)

The lambda could be evaluated once when configuring mappers in a similar way to relationship, or it could be evaluated on first access of the expression statement.

A more advanced alternative may also be to support an inline function in the class definition to allow resolving imports:

class X(Base):
    __tablename__ = 'foo'

    id = sa.Column(sa.Integer, primary_key=True)
    cp = sa.orm.column_property()
    @cp
    def go():
        from .y import Y
        return sa.select(sa.exists(1).where(Y.xid == id)).scalar_subquery()

the @cp there could also be @cp.expression or some other attribute. Another alternative is to use the name of the function as the property name.

class X(Base):
    __tablename__ = 'foo'

    id = sa.Column(sa.Integer, primary_key=True)
    @sa.orm.column_property
    def cp():
        from .y import Y
        return sa.select(sa.exists(1).where(Y.xid == id)).scalar_subquery()

With this option we would need to support the case x = @sa.orm.column_property(my_function) to make the property name x

Have a nice day!

zzzeek commented 1 year ago

I originally wanted to do this in #3050, which got marked as a dupe of #3150, which allowed column_property to work with declared_attr, however, never added the lambda part of it. there was some reason that using lambdas here was either difficult or infeasible in some way but it looks like I've never logged it.

I think the idea was that this should be in a @declared_attr, but that doesn't solve the problem of setting it up later, because declared_attrs necessarily get called at class creation time. maybe that's where I stopped on it. evaluating a lambda would have to be part of column_property() / ColumnProperty, and it would need to have init set up to it which resembles what goes on in Relationship.