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.

Reply via email to