On 5/22/15 6:13 AM, Lie Ryan wrote:
I am currently stuck on creating association proxy.
The composite association_proxy
(http://docs.sqlalchemy.org/en/latest/orm/extensions/associationproxy.html#composite-association-proxies)
pattern pointed to using UniqueObject recipe
(https://bitbucket.org/zzzeek/sqlalchemy/wiki/UsageRecipes/UniqueObject)
to prevent creator from creating duplicate Keyword objects.
How do I obtain the session from inside the creator function when I
manage sessions explicitly (i.e. not using scoped_session)?
we need to look at the big picture. You have code like this:
obj1 = Object('foo')
obj2 = Object('bar')
obj1.tags['cheese'] = 'inspector'
obj2.tags['cheese'] = 'inspector'
you'd like this to work such that when these string names are assigned,
a database lookup is performed. To do database lookups, we need a
database connection.
Where would a system like the above most naturally get a database
connection from, assuming we don't want a global registry? It would
have to be explicit in some way. What way that is, I don't know, but
here are things that I'd imagine would be candidates:
# candidate #1
with using_session(session):
obj1 = Object('foo')
obj2 = Object('bar')
obj1.tags['cheese'] = 'inspector'
obj2.tags['cheese'] = 'inspector'
or maybe:
# candidate #2
obj1 = Object('foo')
obj2 = Object('bar')
obj1.tags['cheese'] = via_session(session, 'inspector')
obj2.tags['cheese'] = via_session(session, 'inspector')
or perhaps we want to be really slick, and have it work this way:
# candidate #3
obj1 = Object('foo')
obj1.tags['cheese'] = 'inspector' # <-- no SQL lookup
session.add(obj1) # <-- SQL lookup
e.g. "how do i obtain", from the pov of the receiver, is not really the
question here. It's "what am I asking for?" on the part of the caller
we have to decide. It's not reasonable to expect that we would be
able to say:
obj1 = Object('foo')
obj1.tags['cheese'] = 'inspector'
that we then would have a database-persisted Tag object on our Object.
Because we didn't tell it about a database. We shouldn't *want* it to
work that way (again, if we know there's no "global" database set up).
Not sure what approach appeals to you, here is a hint for each:
candidate #1: build a Python context manager that temporarily places a
Session in a global, thread-local registry, which is then consulted
within the "creator"
candidate #2: build a "via_session" "command" object that is received by
the "creator" and is interpreted to include both the session and the key.
candidate #3: build a brand new Tag() object that isn't database
persisted; then use the before_attach event
(http://docs.sqlalchemy.org/en/rel_1_0/orm/events.html?highlight=after_attach#sqlalchemy.orm.events.SessionEvents.before_attach)
to intercept when the parent Object is going into the Session, and then
swap out the Tag object that is present on it for the real persisted one.
Here is what I got to so far:
from sqlalchemy import Column, Integer, String, ForeignKey, Table,
create_engine, UniqueConstraint
from sqlalchemy.orm import relationship, sessionmaker, backref
from sqlalchemy.ext.associationproxy import association_proxy
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm.collections import attribute_mapped_collection
Base = declarative_base()
DBSession = sessionmaker()
# taken verbatim from
https://bitbucket.org/zzzeek/sqlalchemy/wiki/UsageRecipes/UniqueObject
def _unique(session, cls, hashfunc, queryfunc, constructor, arg, kw):
cache = getattr(session, '_unique_cache', None)
if cache is None:
session._unique_cache = cache = {}
key = (cls, hashfunc(*arg, **kw))
if key in cache:
return cache[key]
else:
with session.no_autoflush:
q = session.query(cls)
q = queryfunc(q, *arg, **kw)
obj = q.first()
if not obj:
obj = constructor(*arg, **kw)
session.add(obj)
cache[key] = obj
return obj
def creator(key, value):
#return Tag(key=key, value=value) # this creates a Unique
Constraint error on Tag table
return _unique(
session, # NameError: global name 'session' is not defined
Tag,
lambda key, value: (key, value),
lambda query, key, value: query.filter_by(key=key, value=value),
Tag,
arg=[],
kw={'key': key, 'value': value},
)
class Object(Base):
__tablename__ = 'obj'
id = Column(Integer, primary_key=True)
name = Column(String(64))
tags_obj = relationship(
'Tag',
secondary='obj_tag',
backref='objs',
collection_class=attribute_mapped_collection('key'),
)
tags = association_proxy(
'tags_obj',
'value',
creator=creator,
)
def __init__(self, name):
self.name = name
class ObjectTag(Base):
__tablename__ = 'obj_tag'
obj_id = Column(Integer, ForeignKey('obj.id'), primary_key=True)
tag_id = Column(Integer, ForeignKey('tag.id'), primary_key=True)
class Tag(Base):
__tablename__ = 'tag'
id = Column(Integer, primary_key=True)
key = Column('key', String(64))
value = Column('value', String(64))
__table_args__ = (
UniqueConstraint('key', 'value', name=tag_uq'),
)
def main():
engine = create_engine('sqlite://')
session = DBSession(bind=engine)
Base.metadata.drop_all(engine)
Base.metadata.create_all(engine)
obj1 = Object('foo')
obj2 = Object('bar')
obj1.tags['cheese'] = 'inspector'
obj2.tags['cheese'] = 'inspector'
session.add(obj1)
session.add(obj2)
assert session.query(Tag).count() == 1 # (IntegrityError) UNIQUE
constraint failed: tag.key, tag.value
session.commit()
main()
--
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]
<mailto:[email protected]>.
To post to this group, send email to [email protected]
<mailto:[email protected]>.
Visit this group at http://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 [email protected].
To post to this group, send email to [email protected].
Visit this group at http://groups.google.com/group/sqlalchemy.
For more options, visit https://groups.google.com/d/optout.