Thanks for the suggestions and examples. On Sunday, December 13, 2015 at 9:04:17 AM UTC-8, Michael Bayer wrote: > > > > On 12/12/2015 09:56 PM, Gerald Thibault wrote: > > I am loading data from json files, and then creating instances via > > ObjClass(**data), loading them into a session, and committing. I do not > > see an easy way to go from having an integer foreign_key value to > > populating the relationship with the corresponding model in order to > > have post_update work. > > > > As an example, a fixture file might look like this: > > > > [ > > { > > "__model__": "Widget", > > "widget_id": 1, > > "favorite_entry_id": 1, > > "name": "somename" > > }, > > { > > "__model__": "Entry", > > "entry_id": 1, > > "widget_id": 1, > > "name": "somename" > > } > > ] > > > > The fixture loader deserialiizes the file contents, looks up the > > __model__ in the SQLA class registry, creates the instance via > > Model(**data), adds to the session, and once all instances have been > > added, it commits. Do you know a way I could have post_update take > > effect given the format of the data that I am starting with? > > You're already creating Model() objects in response to reading from a > JSON document, so the work of "translate from integer identifiers to > objects" is already what's going on. So extending this to some of the > related attributes in the JSON format should be straightforward. > > > > > > Would I > > need to introspect the relationships, determine which ones are > > post_update, get the remote class of the rel via introspection, > > > I'm not sure why this system needs to work based on introspection. > that's certainly possible, but I'm looking to challenge the narrative of > this is "hard" / "complicated" by default (not like the introspection > path is very difficult either but it is a little less trivial than not > going that path). > > Your JSON already contains the information it needs, e.g. "__model__", > which is the class. This is OOP, a "class" encapsulates data and > methods which work on that data, and the relationship() attributes on > these mapped classes is in fact part of that encapsulation. The most > simple way to build this out is just to add additional hints that are > consumable. If you wanted to dynamically construct these hints w/ > introspection, that's doable, but someone is already typing out > "relationship(...)", just type out some hints for deserialize as well. > Example follows which includes some of the "introspection" version as > well as the "static" version, maybe this can give you some ideas. > > from sqlalchemy import Column, Integer, ForeignKey, create_engine, String > from sqlalchemy.orm import relationship, Session > from sqlalchemy.ext.declarative import declarative_base > from sqlalchemy import inspect > from sqlalchemy.orm.interfaces import MANYTOONE > > Base = declarative_base() > > > class Entry(Base): > __tablename__ = 'entry' > entry_id = Column(Integer, primary_key=True) > widget_id = Column(Integer, ForeignKey('widget.widget_id')) > name = Column(String(50)) > > # static version > _deferred_attrs = { > 'widget_id': ('widget', "Widget") > } > > > class Widget(Base): > __tablename__ = 'widget' > > widget_id = Column(Integer, primary_key=True) > favorite_entry_id = Column( > Integer, > ForeignKey('entry.entry_id', name="fk_favorite_entry")) > name = Column(String(50)) > > entries = relationship( > Entry, primaryjoin=widget_id == Entry.widget_id, backref='widget') > > favorite_entry = relationship( > Entry, > primaryjoin=favorite_entry_id == Entry.entry_id, > post_update=True) > > # static version > # _deferred_attrs = { > # 'favorite_entry_id': ('favorite_entry', "Entry") > # } > > # introspection version > @property > def _deferred_attrs(self): > mapper = inspect(self).mapper > return dict( > ( > # the big assumption here is that we're dealing with > # only single-column primary / foreign keys. > accommodating > # more complicated kinds of relationships would require > more > # logic here to either skip them or handle them, > depending on > # your use case > list(rel.local_remote_pairs)[0][0].key, > (rel.key, rel.mapper.class_.__name__) > ) > for rel in mapper.relationships if rel.direction is MANYTOONE > ) > > > model_registry = {'Widget': Widget, 'Entry': Entry} > > e = create_engine("sqlite://", echo=True) > Base.metadata.create_all(e) > > data = [ > { > "__model__": "Widget", > "widget_id": 1, > "favorite_entry_id": 1, > "name": "somename" > }, > { > "__model__": "Entry", > "entry_id": 1, > "widget_id": 1, > "name": "somename" > } > ] > > # putting everything into a dictionary first. If your database model > # allows columns to be NULLABLE up front, you can instead use > # session.merge() to put rows straight into the database, it's just you > # might have more UPDATE statements in the end. > objs = {} > for rec in data: > cls = model_registry[rec['__model__']] > > our_rec = dict( > (k, v) for k, v in rec.items() if not k.startswith("__") > ) > > obj = cls(**our_rec) > > # this is a mini-identity map, since we aren't in the Session > # yet. A little bit of introspection here, but classes could > # also include a "get_my_primary_key()" method just as well. > objs[ > (cls.__name__, > # assuming non-composite primary keys. > inspect(obj).mapper.primary_key_from_instance(obj)[0]) > ] = obj > > # reconcile deferred attributes > for obj in objs.values(): > for fk_attr, (target_attr, target_cls) in obj._deferred_attrs.items(): > target_obj = objs[(target_cls, getattr(obj, fk_attr))] > delattr(obj, fk_attr) > setattr(obj, target_attr, target_obj) > > session = Session(e) > session.add_all(objs.values()) > 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+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.