On 12/12/2015 06:31 PM, Gerald Thibault wrote:
> I ended up with the following code to preprocess the session and collect
> the problematic updates into a collection, which I then issue after the
> initial flush. It looks really ugly, and I'm wondering if any of this
> could be more easily handled via a hook somewhere in SQLA? Particularly
> the section where I am generating the list of post_update_keys, I found
> references in the source code to 'post_update_cols', and it looked to
> have the info I was looking for, but I'm having trouble finding a way to
> access it. I tried writing my own DependencyProcessor, but it didn't go
> so well.
> 
> If you could suggest any way to improve this by using SQLA functionality
> instead of reinventing the wheel, I'd love to hear it. This works, but
> it seems like a lot of code for something that already has a working
> base (post_update).

It's not clear why actually using the feature that was designed to do
this (e.g. relationship with post_update) is not sufficient.    The
SQLAlchemy ORM provides automation of primary key / foreign key
population and querying using the relationship() construct.  That's the
API.   Getting all kinds of things that relationship() does to work
without actually using the relationship() API seems like a difficult
case to define.




> 
> 
> def get_post_update_attrs(cls):
>     insp = inspect(cls)
>     rels = [r for r in insp.relationships if r.post_update]
>     if not rels:
>         return None
>     keys = []
>     for rel in rels:
>         for c in rel.local_columns:
>             attr = insp.get_property_by_column(c)
>             keys.append(attr.key)
>     return keys
> 
> def pk_as_query_filter(obj):
>     insp = inspect(type(obj))
>     pk_values = insp.primary_key_from_instance(obj)
>     if None in pk_values:
>         return None
>     pk_keys = [key.name for key in insp.primary_key]
>     return dict(zip(pk_keys, pk_values))
> 
> def get_deferred_updates(session):
>     deferred = []
>     post_update_keys = {}
> 
>     for obj in session:
>         cls = type(obj)
>         if cls not in post_update_keys:
>             post_update_keys[cls] = get_post_update_attrs(cls)
>         keys = post_update_keys[cls]
>         if not keys:
>             continue
> 
>         pk = pk_as_query_filter(obj)
>         if pk is None:
>             continue
> 
>         update = {}
>         for key in keys:
>             update[key] = getattr(obj, key)
>             setattr(obj, key, None)
> 
>         deferred.append((cls, pk, update))
>     return deferred
> 
> # this doesn't
> w1 = Widget(widget_id=1, favorite_entry_id=1, name='somewidget')
> e1 = Entry(entry_id=1, widget_id=1, name='someentry')
> session.add_all([w1, e1])
> deferred = get_deferred_updates(session)
> session.flush()
> 
> for cls, pk, update in deferred:
>     session.query(cls).filter_by(**pk).update(update)
> session.flush()
> 
> 
> On Friday, December 11, 2015 at 5:49:02 PM UTC-8, Michael Bayer wrote:
> 
> 
> 
>     On 12/11/2015 07:47 PM, Gerald Thibault wrote:
>     > Is there a way to perform a Metadata.create_all() but have it only
>     > create the tables, without any of the FKs? And then create the FKs in
>     > one go after the fixture data has been loaded into the DB?
> 
>     first off, this is unnecessary because the DDL system can now create
>     mutually-dependent foreign key constraints without any use of the
>     use_alter flag; ALTER TABLE ADD CONSTRAINT is used automatically when a
>     cycle is detected and this flag is legacy.
> 
>     However, if you want to manually control this you can use the rules at
>     
> http://docs.sqlalchemy.org/en/latest/core/ddl.html#controlling-ddl-sequences
>     
> <http://docs.sqlalchemy.org/en/latest/core/ddl.html#controlling-ddl-sequences>
> 
>     to achieve this effect.  You'd apply the AddConstraint object to all of
>     the ForeignKeyConstraint objects and then set them up with events.
>      Here
>     is an example:
> 
> 
>     from sqlalchemy import Column, MetaData, Integer, create_engine,
>     ForeignKey, ForeignKeyConstraint
>     from sqlalchemy.ext.declarative import declarative_base
>     from sqlalchemy import event
> 
>     convention = {
>       "fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s",
>     }
> 
>     metadata = MetaData(naming_convention=convention)
> 
>     Base = declarative_base(metadata=metadata)
> 
> 
>     class A(Base):
>         __tablename__ = 'a'
>         id = Column(Integer, primary_key=True)
> 
> 
>     class B(Base):
>         __tablename__ = 'b'
>         id = Column(Integer, primary_key=True)
>         a_id = Column(ForeignKey('a.id <http://a.id>'))
> 
>     from sqlalchemy.schema import AddConstraint, DropConstraint
> 
>     for table in Base.metadata.tables.values():
>         for constraint in table.constraints:
>             if isinstance(constraint, ForeignKeyConstraint):
>                 event.listen(Base.metadata, "after_create",
>     AddConstraint(constraint))
>                 event.listen(Base.metadata, "before_drop",
>     DropConstraint(constraint))
> 
> 
>     e = create_engine("postgresql://scott:tiger@localhost/test", echo=True)
> 
> 
>     Base.metadata.create_all(e)
> 
>     Base.metadata.drop_all(e)
> 
> 
> 
> 
> 
> 
>     >
>     > On Friday, December 11, 2015 at 3:38:16 PM UTC-8, Michael Bayer
>     wrote:
>     >
>     >
>     >
>     >     On 12/11/2015 05:25 PM, Gerald Thibault wrote:
>     >     > I am basing my question off the code
>     >     > at
>     >    
>     
> http://docs.sqlalchemy.org/en/latest/orm/relationship_persistence.html#rows-that-point-to-themselves-mutually-dependent-rows
>     
> <http://docs.sqlalchemy.org/en/latest/orm/relationship_persistence.html#rows-that-point-to-themselves-mutually-dependent-rows>
> 
>     >    
>     
> <http://docs.sqlalchemy.org/en/latest/orm/relationship_persistence.html#rows-that-point-to-themselves-mutually-dependent-rows
>     
> <http://docs.sqlalchemy.org/en/latest/orm/relationship_persistence.html#rows-that-point-to-themselves-mutually-dependent-rows>>,
> 
>     >
>     >     > with a few changes.
>     >     >
>     >     > I am trying to handle a situation very similar to the one in
>     that
>     >     > example, with 2 classes having the same relationship types as
>     >     those in
>     >     > the example. However, I would like to create the instances
>     without
>     >     using
>     >     > the relationships, and instead populate the fk values
>     directly. The
>     >     > example uses this, and it works.
>     >     >
>     >     > w1 = Widget(name='somewidget')
>     >     > e1 = Entry(name='someentry')
>     >     > w1.favorite_entry = e1
>     >     > w1.entries = [e1]
>     >     > session.add_all([w1, e1])
>     >     > session.commit()
>     >     >
>     >     > I would like to do this:
>     >     >
>     >     > w1 = Widget(widget_id=1, favorite_entry_id=1,
>     name='somewidget')
>     >     > e1 = Entry(entry_id=1, widget_id=1, name='someentry')
>     >     > session.add_all([w1, e1])
>     >     > session.commit()
>     >     >
>     >     > The reason I am doing it this way is because I am operating
>     from a
>     >     JSON
>     >     > fixture file, and trying to populate a database for unit
>     testing. The
>     >     > method used in the example works perfectly, but trying to do
>     it my
>     >     way
>     >     > yields:
>     >     >
>     >     > sqlalchemy.exc.IntegrityError: (IntegrityError) (1452,
>     'Cannot add or
>     >     > update a child row: a foreign key constraint fails
>     (`test`.`widget`,
>     >     > CONSTRAINT `fk_favorite_entry` FOREIGN KEY
>     (`favorite_entry_id`)
>     >     > REFERENCES `entry` (`entry_id`))') 'INSERT INTO widget
>     (widget_id,
>     >     > favorite_entry_id, name) VALUES (%s, %s, %s)' (1, 1,
>     'somewidget')
>     >     >
>     >     > I understand the post_update option is on the relationship, and
>     >     not the
>     >     > column, so it has no effect on column population. Is there an
>     >     > alternative method to have that column populated separately
>     via a
>     >     second
>     >     > statement, similar to the post_update functionality?
>     >
>     >     Sure, you have to do it manually:
>     >
>     >     w1 = Widget(widget_id=1, favorite_entry_id=None,
>     name='somewidget')
>     >     e1 = Entry(entry_id=1, widget_id=1, name='someentry')
>     >     session.add_all([w1, e1])
>     >     session.flush()  # optional if you're on autoflush
>     >    
>     session.query(Widget).filter_by(widget_id=1).update(favorite_entry_id=1)
> 
>     >
>     >     session.commit()
>     >
>     >
>     >
>     >
>     >
>     >
>     >
>     >     >
>     >     > --
>     >     > You received this message because you are subscribed to the
>     Google
>     >     > Groups "sqlalchemy" group.
>     >     > To unsubscribe from this group and stop receiving emails
>     from it,
>     >     send
>     >     > an email to sqlalchemy+...@googlegroups.com <javascript:>
>     >     > <mailto:sqlalchemy+unsubscr...@googlegroups.com
>     <javascript:> <javascript:>>.
>     >     > To post to this group, send email to sqlal...@googlegroups.com
>     >     <javascript:>
>     >     > <mailto:sqlal...@googlegroups.com <javascript:>>.
>     >     > Visit this group at
>     http://groups.google.com/group/sqlalchemy
>     <http://groups.google.com/group/sqlalchemy>
>     >     <http://groups.google.com/group/sqlalchemy
>     <http://groups.google.com/group/sqlalchemy>>.
>     >     > For more options, visit https://groups.google.com/d/optout
>     <https://groups.google.com/d/optout>
>     >     <https://groups.google.com/d/optout
>     <https://groups.google.com/d/optout>>.
>     >
>     > --
>     > You received this message because you are subscribed to the Google
>     > Groups "sqlalchemy" group.
>     > To unsubscribe from this group and stop receiving emails from it,
>     send
>     > an email to sqlalchemy+...@googlegroups.com <javascript:>
>     > <mailto:sqlalchemy+unsubscr...@googlegroups.com <javascript:>>.
>     > To post to this group, send email to sqlal...@googlegroups.com
>     <javascript:>
>     > <mailto:sqlal...@googlegroups.com <javascript:>>.
>     > Visit this group at http://groups.google.com/group/sqlalchemy
>     <http://groups.google.com/group/sqlalchemy>.
>     > For more options, visit https://groups.google.com/d/optout
>     <https://groups.google.com/d/optout>.
> 
> -- 
> You received this message because you are subscribed to the Google
> Groups "sqlalchemy" group.
> To unsubscribe from this group and stop receiving emails from it, send
> an email to sqlalchemy+unsubscr...@googlegroups.com
> <mailto:sqlalchemy+unsubscr...@googlegroups.com>.
> To post to this group, send email to sqlalchemy@googlegroups.com
> <mailto:sqlalchemy@googlegroups.com>.
> Visit this group at https://groups.google.com/group/sqlalchemy.
> For more options, visit https://groups.google.com/d/optout.

-- 
You received this message because you are subscribed to the Google Groups 
"sqlalchemy" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to sqlalchemy+unsubscr...@googlegroups.com.
To post to this group, send email to sqlalchemy@googlegroups.com.
Visit this group at https://groups.google.com/group/sqlalchemy.
For more options, visit https://groups.google.com/d/optout.

Reply via email to