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 <mike...@zzzcomputing.com> 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 <mike...@zzzcomputing.com> 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 sqlalch...@googlegroups.com.
>>>>>>> To unsubscribe from this group, send email to 
>>>>>>> sqlalchemy+unsubscr...@googlegroups.com.
>>>>>>> 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 sqlalch...@googlegroups.com.
>>>>> To unsubscribe from this group, send email to 
>>>>> sqlalchemy+unsubscr...@googlegroups.com.
>>>>> 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 sqlalch...@googlegroups.com.
>>>> To unsubscribe from this group, send email to 
>>>> sqlalchemy+unsubscr...@googlegroups.com.
>>>> 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 sqlalch...@googlegroups.com.
>>> To unsubscribe from this group, send email to 
>>> sqlalchemy+unsubscr...@googlegroups.com.
>>> 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 sqlalch...@googlegroups.com.
>> To unsubscribe from this group, send email to 
>> sqlalchemy+unsubscr...@googlegroups.com.
>> 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 sqlalch...@googlegroups.com.
> To unsubscribe from this group, send email to 
> sqlalchemy+unsubscr...@googlegroups.com.
> 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 sqlalch...@googlegroups.com.
To unsubscribe from this group, send email to 
sqlalchemy+unsubscr...@googlegroups.com.
For more options, visit this group at 
http://groups.google.com/group/sqlalchemy?hl=en.

Reply via email to