On Sep 6, 2010, at 12:01 PM, Kent Bower wrote:

> Fantastic, I will like to look into this change. 
> 
> Since you asked, consider a use case similar to this: we have a RESTfulish 
> web service that accepts a serialized version of a "transfer object" which is 
> passed to the server when a database save should take place.  In this case, 
> an "order" with select relations are serialized and passed.
> 
> For a database save, this will be added to the session after it is "cast" 
> into a sqlalchemy object. Nothing special there. 
> 
> Now for the use case:  the webservice needs to also be invoked while the user 
> is still working on the order. For example, taxes and delivery charges are 
> calculated by the server.  Again, the serialized version of the transfer 
> object is sent to the server and cast into a sqlalchemy object. In this case, 
> however, we have no intention on ever saving the object during this service 
> request. Rather, the sqlalchemy object is transient. Still, to calculate 
> taxes as an example there are a handful of relations that need to be loaded, 
> such as zipcode objects, tax authorities, tax tables, etc. 

So if it were me, I'd not be using HTTP in that way, i.e. the "big serialized 
bag of all kinds of stuff".    I'd have made it such that a new session key can 
be established with the web service which uses proper relational storage for 
the pending state.   In fact in my current project I am doing just that, where 
i have a base "OrderData" object that has two subclasses, "PendingOrder" and 
"Order", each of which are stored in distinct tables.  Its not concrete 
inheritance either - "OrderData" is a declarative mixin that uses 
@classproperty to establish the same relationship() objects on each subclass.   
However, I've certainly used HTTP sessions with disk state and such in the 
past, and while I don't prefer heavy reliance on serialization these days, I 
know that serialization patterns are very common.

> 
> The reason for not wanting to disable autoflush is that this same code is 
> (appropriately) invoked whether this object is persistent (from merge()) and 
> part of the save web service or transient and part of the calculate web 
> service (where the object is going to be thrown away).  In the case of being 
> the save, it is important for database consistency thar autoflush remain 
> enabled. 

So you have this transient object, and you want to use it to load various 
information about zipcodes and stuff, but its not database state.  *But*, you 
*have* populated individual foreign key and maybe primary key attributes on it, 
which most certainly represent persistent-centric concepts.    So there has 
been, at some point, some awareness of either this object's future primary key, 
or something has loaded up related many-to-ones and figured out their foreign 
keys and assigned them.   There's definitely no many-to-many collections at 
play since those aren't possible without persistence of the transient object's 
primary key information (unless you're working with totally unconstrained 
tables, in which case, good luck).

So the persistence information is already there.  If you are setting 
order.foreign_key_of_something, why would you not instead set order.something, 
so that order.something is already present in the transient state?  The rule 
here being, "how do i get foo.bar to be 'x'"? answer: "set foo.bar = 'x'" - 
simple right ?   The ORM would be left to do its normal job of worrying about 
foreign keys.

But instead, you're working in reverse.  The ORM has an opinion that if you 
work in reverse like that, it won't block you, but it also isn't going to make 
the guesses and assumptions that would be required for it to act "the same" 
(see the FAQ entry about "foo_id 7" for some rantage on this, you've probably 
already seen it).

I know the answer already to why you're populating 
order.foreign_key_of_something rather than order.something.  You're trying to 
reduce the serialization size, and/or the overhead of merging all that 
serialized data back into a session.   So you're trying to rig the ORM into a 
custom, optimized serialization scheme, a use case that is outside the scope of 
the very simple, single purpose that relationship() is designed for out of the 
box, which is to represent a linkage between classes and persist/restore a 
corresponding linkage between related database rows (since if one side is 
transient, there is only one database row in play).    

But there is good news, if we look at this for what it seems to be, which is an 
optimized serialization scheme.  You should build it that way.   Write custom 
serialization (__getstate__/__setstate__) for your class - if it detects as 
transient, expire all relationship()-based attributes upon __getstate__ - upon 
__setstate__, iterate through all relationship based-attrbutes (using 
mapper.iterate_properties()) and plug them all into query.with_parent() to 
again produce the "pending" attributes.    Basically, take advantage of the 
foreign key/primary key attributes already present to reduce the size of the 
serialization, and load the data back on the other end, transparently.   It's 
up to __setstate__ to figure out transactional context.   You could use 
scoped_session which is the easy way out, or write a custom pickler that 
accepts "session" (I'd go with the latter, more explicit).   Such a recipe 
could even look at the "local_remote_pairs" of each relationship to decide 
which related attributes should be expired, and which should not, based on 
whether or not the necessary FK attributes are present.   It would make a great 
recipe for the wiki.



> 
> 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.

Reply via email to