On Sep 6, 2010, at 2:11 PM, Kent Bower wrote:
> Also, I was hoping you would tell me whether this would be a candidate for
> subclassing InstrumentedAttribute? Would that make more sense or providing
> custom __getstate__ & __setstate__ ?
__getstate__ / __setstate__ are pretty much what I like to use for pickle
stuff, unless some exotic situation makes me have to use __reduce__. One
problem with the recipe is that theres no 'deferred' loading of attributes.
So in that sense playing with InstrumentedAttribute would give you a chance to
put a callable in there that does what you want.
There is also the possibility that __setstate__ can load up callables into the
instance_state using state.set_callable(). This is a callable that triggers
when you access the attribute that is otherwise None. There's a little bit of
fanfare required to get that callable to assign to the attribute in the right
way. Attached is an example of that. This is all a little more shaky since
the state/callable API isn't really public. Hasn't changed for awhile but
there's no guarantee.
>
> Thanks for your help, hopefully I'll be able to contribute such a recipe.
>
> Kent
>
>
>
>>
>>>
>>> Since sqla won't load that for me in the case of transient, I need to load
>>> the relation manually (unless you feel like enhancing that as well).
>>
>> its not an enhancement - it was a broken behavior that was specifically
>> removed. The transient object has no session, so therefore no SQL can be
>> emitted - there's no context established.
>>
>>
>>
>>>
>>> Now I can manually emulate the obj being persistent with your changes for
>>>
>>> On Sep 6, 2010, at 10:58 AM, Michael Bayer <[email protected]> wrote:
>>>
>>>>
>>>> On Sep 6, 2010, at 9:06 AM, Kent wrote:
>>>>
>>>>> with_parent seems to add a join condition.
>>>>
>>>> OK, so I guess you read the docs which is why you thought it joined and
>>>> why you didn't realize it doesn't work for transient. r20b6ce05f194
>>>> changes all that so that with_parent() accepts transient objects and will
>>>> do the "look at the attributes" thing. The docs are updated as this
>>>> method does use the lazy loader SQL mechanism, not a join.
>>>>
>>>>
>>>>
>>>>> Is there a way to get at
>>>>> the query object that would be rendered from a lazy load (or what
>>>>> "subqueryload" would render on the subsequent load), but on a
>>>>> transient object, if i supply the session?
>>>>>
>>>>> even though not "recommended", can it make sqla believe my transient
>>>>> object is detached by setting its state key?
>>>>>
>>>>> There are reasons i do not want to add this to the session and
>>>>> disabling autoflush would also cause problems.
>>>>>
>>>>>
>>>>>
>>>>> On Sep 3, 9:58 am, Michael Bayer <[email protected]> wrote:
>>>>>> On Sep 3, 2010, at 9:36 AM, Kent wrote:
>>>>>>
>>>>>>> For the case of customerid = '7', that is a simple problem, but when
>>>>>>> it is a more complex join condition, we only wanted to define this
>>>>>>> condition in one single place in our application (namely, the orm).
>>>>>>> That way, if or when that changes, developers don't need to search for
>>>>>>> other places in the app that needed to manually duplicate the logic of
>>>>>>> the orm join condition.
>>>>>>
>>>>>>> If I supplied the DBSession to sqla, it would know how to create the
>>>>>>> proper Query object for this lazyload. Can you point me in the right
>>>>>>> direction (even if where you point me is not currently part of the
>>>>>>> public API)?
>>>>>>
>>>>>> Query has the with_parent() method for this use case.
>>>>>>
>>>>>>
>>>>>>
>>>>>>
>>>>>>
>>>>>>> Thanks again,
>>>>>>> Kent
>>>>>>
>>>>>>> --
>>>>>>> You received this message because you are subscribed to the Google
>>>>>>> Groups "sqlalchemy" group.
>>>>>>> To post to this group, send email to [email protected].
>>>>>>> To unsubscribe from this group, send email to
>>>>>>> [email protected].
>>>>>>> For more options, visit this group
>>>>>>> athttp://groups.google.com/group/sqlalchemy?hl=en.
>>>>>
>>>>> --
>>>>> You received this message because you are subscribed to the Google Groups
>>>>> "sqlalchemy" group.
>>>>> To post to this group, send email to [email protected].
>>>>> To unsubscribe from this group, send email to
>>>>> [email protected].
>>>>> For more options, visit this group at
>>>>> http://groups.google.com/group/sqlalchemy?hl=en.
>>>>>
>>>>
>>>> --
>>>> You received this message because you are subscribed to the Google Groups
>>>> "sqlalchemy" group.
>>>> To post to this group, send email to [email protected].
>>>> To unsubscribe from this group, send email to
>>>> [email protected].
>>>> For more options, visit this group at
>>>> http://groups.google.com/group/sqlalchemy?hl=en.
>>>>
>>>
>>> --
>>> You received this message because you are subscribed to the Google Groups
>>> "sqlalchemy" group.
>>> To post to this group, send email to [email protected].
>>> To unsubscribe from this group, send email to
>>> [email protected].
>>> For more options, visit this group at
>>> http://groups.google.com/group/sqlalchemy?hl=en.
>>>
>>
>> --
>> You received this message because you are subscribed to the Google Groups
>> "sqlalchemy" group.
>> To post to this group, send email to [email protected].
>> To unsubscribe from this group, send email to
>> [email protected].
>> For more options, visit this group at
>> http://groups.google.com/group/sqlalchemy?hl=en.
>>
>
> --
> You received this message because you are subscribed to the Google Groups
> "sqlalchemy" group.
> To post to this group, send email to [email protected].
> To unsubscribe from this group, send email to
> [email protected].
> For more options, visit this group at
> http://groups.google.com/group/sqlalchemy?hl=en.
>
from sqlalchemy import *
from sqlalchemy.orm import *
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm.properties import RelationshipProperty, MANYTOONE
import pickle
from sqlalchemy.orm.util import has_identity, with_parent
from sqlalchemy.ext import serializer
from sqlalchemy.orm.attributes import instance_state, ATTR_WAS_SET
Base = declarative_base()
def many_to_one_props(mapper):
for prop in mapper.iterate_properties:
if isinstance(prop, RelationshipProperty) and \
prop.direction == MANYTOONE:
yield prop
def deferred_transient_callable(prop, crit, state, dict_):
def go(**kw):
result = session.query(prop.mapper).filter(crit).one()
# pop out the callable...
state.reset(dict_, prop.key)
# populate the result via dict. this produces no
# net change during flush, relying upon the FK
dict_[prop.key] = result
# or, populate the result with a "change" event.
# the UOW picks it up during flush.
# state.get_impl(prop.key).set(state, dict_, result, None)
# tell the InstrumentedAttribute "its handled"
return ATTR_WAS_SET
return go
class Parent(Base):
__tablename__ = 'parent'
id = Column(Integer, primary_key=True)
related_id = Column(Integer, ForeignKey('related.id'))
some_other_id = Column(Integer, ForeignKey('some_other.id'))
related = relationship("Related")
some_other = relationship("SomeOther")
def __getstate__(self):
if not has_identity(self):
d = self.__dict__.copy()
mapper = object_mapper(self)
for prop in many_to_one_props(mapper):
for l, r in prop.local_remote_pairs:
if mapper.get_property_by_column(l).key not in d:
break
else:
d[prop.key] = serializer.dumps(with_parent(self, prop))
return d
else:
return self.__dict__
def __setstate__(self, d):
self.__dict__.update(d)
if not has_identity(self):
state = instance_state(self)
for prop in many_to_one_props(object_mapper(self)):
if prop.key in self.__dict__:
crit = serializer.loads(
self.__dict__[prop.key],
metadata = self.metadata,
)
state.set_callable(self.__dict__, prop.key, deferred_transient_callable(
prop, crit, state, self.__dict__
))
class Related(Base):
__tablename__ = 'related'
id = Column(Integer, primary_key=True)
class SomeOther(Base):
__tablename__ = 'some_other'
id = Column(Integer, primary_key=True)
engine = create_engine('sqlite://', echo='debug')
Base.metadata.create_all(engine)
session = Session(engine)
rel = Related()
session.add(rel)
session.commit()
p1 = Parent(related_id=rel.id)
dump = pickle.dumps(p1)
load= pickle.loads(dump)
# not present
assert 'related' not in load.__dict__
# load
print load.related
# present !
assert 'related' in load.__dict__
assert load.related is rel
session.add(load)
session.commit()
print load.related
assert load.related is rel
dump = pickle.dumps(load)
load2 = pickle.loads(dump)
print load2.related
assert load2.related is not rel # since it was serialized normally
load2 = session.merge(load2)
assert load2.related is rel
--
You received this message because you are subscribed to the Google Groups
"sqlalchemy" group.
To post to this group, send email to [email protected].
To unsubscribe from this group, send email to
[email protected].
For more options, visit this group at
http://groups.google.com/group/sqlalchemy?hl=en.