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 [email protected].
To post to this group, send email to [email protected].
Visit this group at https://groups.google.com/group/sqlalchemy.
For more options, visit https://groups.google.com/d/optout.