On Jan 13, 2014, at 1:19 PM, Alessandro Molina <[email protected]> 
wrote:

> Just noticed that SQLAlchemy 0.9 broke a LazyForeignKey class that was 
> provided by TurboGears tgext.pluggable to make possible to declare foreign 
> keys to models not yet defined at the time the ForeignKey was declared.
> 
> The main use was something like:
> 
> class User(DeclarativeBase):
>     __tablename__ = 'users'
> 
>     registration_id = Column(Integer, LazyForeignKey(lambda: 
> app_model.Registration.uid))
> 
> where app_model.Registration was available only at runtime and not when User 
> class was declared.
> Also app_model.Registration table name was not known (as it dynamically 
> generated to avoid collision between tables of multiple apps plugged at 
> runtime), so it was not possible to declare the foreign key as a string with 
> the "tablename.column_name" syntax.
> 
> LazyForeignKey was simply implemented as a subclass of ForeignKey which 
> provided a custom _colspec property which would resolve the given function 
> whenever it was first accessed: 
> https://github.com/TurboGears/tgext.pluggable/blob/master/tgext/pluggable/sqla/relations.py#L4
> 
> This approach doesn't work anymore in SQLA 0.9 as _colspec gets accessed by 
> the ForeignKey constructor itself, calling the lambda at Column declaration 
> time.
> 
> It seems to me that adapting the LazyForeignKey class to SQLA 0.9 would 
> require messing a lot more with SQLAlchemy internal code, which is something 
> I would like to avoid. 
> 
> What would be the suggested way to achieve the same feature on SQLA 0.9?

Well if the table name were known, then you wouldn’t need such a feature in the 
first place as you can just put a string name in ForeignKey().

However, as this seems to be a case where a table + declared class is to 
eventually refer to some other Table whose name is not known yet, the 
ForeignKey() should not exist on the Column at all until that other Table is 
available.    A simple call to 
User.__table__.append_constraint(ForeignKeyConstraint(<now you know what to put 
here>)) at the time this information is known would be the cleanest.   

to get at this you’d need to use events.  First a way to make an XYZ() object 
that can be placed inside of Column and know about it:


from sqlalchemy.schema import SchemaItem
from sqlalchemy import event

class MyThing(SchemaItem):
    def _set_parent(self, parent):
        pass

@event.listens_for(MyThing, "after_parent_attach")
def my_thing_attached(target, parent):
    print("Attaching target %s to parent %s", target, parent)

from sqlalchemy import Table, MetaData, Column, Integer

Table('t', MetaData(), Column('x', Integer, MyThing()))

Then you want to register your LazyForeignKey object in a registry that will 
allow the callable to be invoked once your “table setup” system has completed - 
you’d call the callable, construct a ForeignKeyConstraint, then append it to 
the constraints of the parent column.table.

the only private API in use here is the _set_parent() override.   If I change 
that API it would be something simple.

Attachment: signature.asc
Description: Message signed with OpenPGP using GPGMail

Reply via email to