[sqlalchemy] Re: [PATCH] Filtered one_to_many relationships (Experimental)

2007-04-17 Thread Gaetan de Menten

On 4/17/07, Michael Bayer [EMAIL PROTECTED] wrote:

 On Apr 10, 2007, at 10:22 AM, Gaetan de Menten wrote:

  By the way, should I create a ticket for this?

 ive created ticket #541 for this,

I had already created ticket #531 for this. Sorry for not mentioning
it here (I thought you'd see it). Anyway, it can be closed too now :).

 and implemented a slightly refined
 version of the patch you provided, including the classmethod as well
 as a generative method + unit tests for both.  I added docs and
 examples for the generative version, so you can see the use cases i
 was getting at.  it will throw an error if you give it input that
 makes no sense (i.e. two classes that are unrelated).

 the pattern looks like (assuming the user-address paradigm) :

 # without specifying the property
 session.query(Address).with_parent(someuser).list()

 # specifying the property
 session.query(Address).with_parent(someuser,
 property='addresses').filter_by(email_address='[EMAIL PROTECTED]').list()

That's just great! I was going to do it eventually but it seems like
you beat me to it ;-).
Thanks a lot.
-- 
Gaëtan de Menten
http://openhex.org

--~--~-~--~~~---~--~~
You received this message because you are subscribed to the Google Groups 
sqlalchemy group.
To post to this group, send email to sqlalchemy@googlegroups.com
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
-~--~~~~--~~--~--~---



[sqlalchemy] Re: [PATCH] Filtered one_to_many relationships (Experimental)

2007-04-17 Thread Michael Bayer


On Apr 17, 2007, at 2:49 PM, Gaetan de Menten wrote:


 I had already created ticket #531 for this. Sorry for not mentioning
 it here (I thought you'd see it). Anyway, it can be closed too now :).


oh crap, sorry, i did a quick search thru track for filtered and  
nothing came up.  yeah im starting to lose track of tickets now...

--~--~-~--~~~---~--~~
You received this message because you are subscribed to the Google Groups 
sqlalchemy group.
To post to this group, send email to sqlalchemy@googlegroups.com
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
-~--~~~~--~~--~--~---



[sqlalchemy] Re: [PATCH] Filtered one_to_many relationships (Experimental)

2007-04-13 Thread Michael Bayer


On Apr 13, 2007, at 12:51 PM, [EMAIL PROTECTED] wrote:
 haven't thought yet of where/how to hack this...
 i may have to abandon *-to-many-relations() alltogether, as i don't
 want/need them loaded - only filtered at view-time.
 or can i make some super- (or sub-) relation thing (propertyLoader?)
 that does the above?

I currently do it using a property that returns a Query object, which  
works very well.  the extra method with_parent() i want to add to  
Query would make it easier to implement also.


class MyClass(object):
 def _get_children(self):
  return object_session(self).query 
(SomeChildClass).with_parent(self)
 children = property(_get_children)


then:

m = session.query(MyClass).get(1)
somechildren = m.children[3:5]
someotherchildren = m.children.filter_by(foo='bar').filter_by 
(x==y).list()



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



[sqlalchemy] Re: [PATCH] Filtered one_to_many relationships (Experimental)

2007-04-11 Thread Gaetan de Menten

On 4/10/07, Michael Bayer [EMAIL PROTECTED] wrote:

 hm, why is from_attr a classmethod ?

Because that way, you don't have to specify the related class at all,
and you can specify the parameters as args not kwargs. See my first
initial remark:

* I've implemented Query.from_attr, instead of adding new keywords to
the Query constructor, because I think:

   Query.from_attr(someuser, 'addresses')

looks better, is shorter and is more readable than:

   Query('Address', instance=someuser, attr_name='addresses')

 not very consistent with all the other generative methods ?

True, but I think it makes more sense this way (see below).

 can we have a regular generative method as well ?

If you really want one, I'll gladly do it, but I don't think it makes
sense because that method can possibly change the mapper.

So, first I'd need to duplicate part of what is in the __init__
method, which doesn't feel right.

And second, I think it could be quite confusing for a user. Imagine
that Query(A).from_attr(inst, 'rel') could return instances of B (or
whatever class is attached to the relation)... Probably not what you'd
expect. So, if we go down that route, it would probably be a good idea
to check that the mapper in the cloned query is the same than the one
we get from the relation.

And by the way, from_attr doesn't sound like a generative method, so
if you want it, what about filter_from_attr, or something similar?

-- 
Gaëtan de Menten
http://openhex.org

--~--~-~--~~~---~--~~
You received this message because you are subscribed to the Google Groups 
sqlalchemy group.
To post to this group, send email to sqlalchemy@googlegroups.com
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
-~--~~~~--~~--~--~---



[sqlalchemy] Re: [PATCH] Filtered one_to_many relationships (Experimental)

2007-04-11 Thread svilen

is from_attr makeing sense for plain atributes, e.g. integers ot 
whatever?

if no, maybe choose something like from_relation or filter_relation or 
filter_relation_tomany or similar if it is expected to only work over 
relations - and multiple-instances relations; i.e. it is useless over 
single pointer-like 1:1 relation - so the name would suggest the 
proper target-type.

On Wednesday 11 April 2007 11:50:53 Gaetan de Menten wrote:
 On 4/10/07, Michael Bayer [EMAIL PROTECTED] wrote:
  hm, why is from_attr a classmethod ?

 Because that way, you don't have to specify the related class at
 all, and you can specify the parameters as args not kwargs. See my
 first initial remark:

 * I've implemented Query.from_attr, instead of adding new keywords
 to the Query constructor, because I think:

Query.from_attr(someuser, 'addresses')

 looks better, is shorter and is more readable than:

Query('Address', instance=someuser, attr_name='addresses')

  not very consistent with all the other generative methods ?

 True, but I think it makes more sense this way (see below).

  can we have a regular generative method as well ?

 If you really want one, I'll gladly do it, but I don't think it
 makes sense because that method can possibly change the mapper.

 So, first I'd need to duplicate part of what is in the __init__
 method, which doesn't feel right.

 And second, I think it could be quite confusing for a user. Imagine
 that Query(A).from_attr(inst, 'rel') could return instances of B
 (or whatever class is attached to the relation)... Probably not
 what you'd expect. So, if we go down that route, it would probably
 be a good idea to check that the mapper in the cloned query is the
 same than the one we get from the relation.

 And by the way, from_attr doesn't sound like a generative method,
 so if you want it, what about filter_from_attr, or something
 similar?



--~--~-~--~~~---~--~~
You received this message because you are subscribed to the Google Groups 
sqlalchemy group.
To post to this group, send email to sqlalchemy@googlegroups.com
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
-~--~~~~--~~--~--~---



[sqlalchemy] Re: [PATCH] Filtered one_to_many relationships (Experimental)

2007-04-11 Thread Gaetan de Menten

On 4/11/07, svilen [EMAIL PROTECTED] wrote:

 is from_attr makeing sense for plain atributes, e.g. integers ot
 whatever?

You got a point here. It doesn't work (or even make sense) on plain attributes.

 if no, maybe choose something like from_relation

Fine with me.

 or filter_relation or
 filter_relation_tomany or similar if it is expected to only work over
 relations - and multiple-instances relations; i.e. it is useless over
 single pointer-like 1:1 relation - so the name would suggest the
 proper target-type.

It works fine for to_one relations. I'm not sure if anybody will
ever use it on such relations, but since it works as expected, I see
no reason to artificially constrain the thing to to_many
relationships.


 On Wednesday 11 April 2007 11:50:53 Gaetan de Menten wrote:
  On 4/10/07, Michael Bayer [EMAIL PROTECTED] wrote:
   hm, why is from_attr a classmethod ?
 
  Because that way, you don't have to specify the related class at
  all, and you can specify the parameters as args not kwargs. See my
  first initial remark:
 
  * I've implemented Query.from_attr, instead of adding new keywords
  to the Query constructor, because I think:
 
 Query.from_attr(someuser, 'addresses')
 
  looks better, is shorter and is more readable than:
 
 Query('Address', instance=someuser, attr_name='addresses')
 
   not very consistent with all the other generative methods ?
 
  True, but I think it makes more sense this way (see below).
 
   can we have a regular generative method as well ?
 
  If you really want one, I'll gladly do it, but I don't think it
  makes sense because that method can possibly change the mapper.
 
  So, first I'd need to duplicate part of what is in the __init__
  method, which doesn't feel right.
 
  And second, I think it could be quite confusing for a user. Imagine
  that Query(A).from_attr(inst, 'rel') could return instances of B
  (or whatever class is attached to the relation)... Probably not
  what you'd expect. So, if we go down that route, it would probably
  be a good idea to check that the mapper in the cloned query is the
  same than the one we get from the relation.
 
  And by the way, from_attr doesn't sound like a generative method,
  so if you want it, what about filter_from_attr, or something
  similar?



-- 
Gaëtan de Menten
http://openhex.org

--~--~-~--~~~---~--~~
You received this message because you are subscribed to the Google Groups 
sqlalchemy group.
To post to this group, send email to sqlalchemy@googlegroups.com
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
-~--~~~~--~~--~--~---



[sqlalchemy] Re: [PATCH] Filtered one_to_many relationships (Experimental)

2007-04-10 Thread Gaetan de Menten
On 3/31/07, Michael Bayer [EMAIL PROTECTED] wrote:

 On Mar 31, 2007, at 1:17 PM, Gaetan de Menten wrote:

  That's approximately what I did in my patch with the new params
  keyword argument, except I only implemented the set operation, not
  the add operation on the params. Anyway, what can/should I do to get
  this included? Do you have any advice/pointers on how to do the same
  for eager attributes? (or will you implement it yourself?)

 im totally into a series of engine/execution patches/refactorings
 right now, so for properties that have lazy=False, there is still a
 LazyLoader strategy there...you should just call
 property._get_strategy(LazyLoader) in all cases to get at it.

In case anybody is interested, here is my patch slightly modified with
what you suggest above. Now it works wonders for both lazy and eager
relationships. There is something ugly about it though: imports. I
have to import the LazyLoader class from the orm.strategies module,
but that module imports query, so what I did is import the LazyLoader
class inside the from_attr method to avoid a circular import
problem.

By the way, should I create a ticket for this?
-- 
Gaëtan de Menten
http://openhex.org

--~--~-~--~~~---~--~~
You received this message because you are subscribed to the Google Groups 
sqlalchemy group.
To post to this group, send email to sqlalchemy@googlegroups.com
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
-~--~~~~--~~--~--~---

Index: orm/query.py
===
--- orm/query.py(revision 2493)
+++ orm/query.py(working copy)
@@ -8,6 +8,7 @@
 from sqlalchemy.orm import mapper, class_mapper
 from sqlalchemy.orm.interfaces import OperationContext, SynonymProperty
 
+
 __all__ = ['Query', 'QueryContext', 'SelectionContext']
 
 class Query(object):
@@ -42,12 +43,36 @@
 self._distinct = kwargs.pop('distinct', False)
 self._offset = kwargs.pop('offset', None)
 self._limit = kwargs.pop('limit', None)
-self._criterion = None
+self._criterion = kwargs.pop('criterion', None)
+self._params = kwargs.pop('params', {})
 self._joinpoint = self.mapper
 self._from_obj = [self.table]
 
 for opt in util.flatten_iterator(self.with_options):
 opt.process_query(self)
+
+def from_attr(cls, instance, attr_name):
+from sqlalchemy.orm.strategies import LazyLoader
+
+prop = instance.mapper.props[attr_name]
+loader = prop._get_strategy(LazyLoader)
+
+# the following code is taken from strategies.py
+# this gets the values of the columns referenced by the property
+# for this specific instance
+params = {}
+allparams = True
+for col, bind in loader.lazybinds.iteritems():
+params[bind.key] = loader.parent.get_attr_by_column(instance, col)
+if params[bind.key] is None:
+allparams = False
+break
+
+if not allparams:
+return None
+
+return Query(prop.mapper, criterion=loader.lazywhere, params=params)
+from_attr = classmethod(from_attr)
 
 def _clone(self):
 q = Query.__new__(Query)
@@ -71,6 +96,7 @@
 q._from_obj = list(self._from_obj)
 q._joinpoint = self._joinpoint
 q._criterion = self._criterion
+q._params = self._params
 return q
 
 def _get_session(self):
@@ -694,8 +720,10 @@
 method, which takes the executed statement's ResultProxy
 directly.
 
-
-result = self.session.execute(self.mapper, clauseelement, 
params=params)
+final_params = self._params.copy()
+if params is not None:
+final_params.update(params)
+result = self.session.execute(self.mapper, clauseelement, 
params=final_params)
 try:
 return self.instances(result, **kwargs)
 finally:


[sqlalchemy] Re: [PATCH] Filtered one_to_many relationships (Experimental)

2007-04-10 Thread svilen

can u give some exampe, how this is supposed to be used (finaly)?
as relation and/or directly as Query()

On Tuesday 10 April 2007 17:22:45 Gaetan de Menten wrote:
 On 3/31/07, Michael Bayer [EMAIL PROTECTED] wrote:
  On Mar 31, 2007, at 1:17 PM, Gaetan de Menten wrote:
   That's approximately what I did in my patch with the new
   params keyword argument, except I only implemented the set
   operation, not the add operation on the params. Anyway, what
   can/should I do to get this included? Do you have any
   advice/pointers on how to do the same for eager attributes? (or
   will you implement it yourself?)
 
  im totally into a series of engine/execution patches/refactorings
  right now, so for properties that have lazy=False, there is
  still a LazyLoader strategy there...you should just call
  property._get_strategy(LazyLoader) in all cases to get at it.

 In case anybody is interested, here is my patch slightly modified
 with what you suggest above. Now it works wonders for both lazy and
 eager relationships. There is something ugly about it though:
 imports. I have to import the LazyLoader class from the
 orm.strategies module, but that module imports query, so what I did
 is import the LazyLoader class inside the from_attr method to avoid
 a circular import problem.

 By the way, should I create a ticket for this?



--~--~-~--~~~---~--~~
You received this message because you are subscribed to the Google Groups 
sqlalchemy group.
To post to this group, send email to sqlalchemy@googlegroups.com
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
-~--~~~~--~~--~--~---



[sqlalchemy] Re: [PATCH] Filtered one_to_many relationships (Experimental)

2007-04-10 Thread Gaetan de Menten
On 4/10/07, svilen [EMAIL PROTECTED] wrote:

 can u give some exampe, how this is supposed to be used (finaly)?
 as relation and/or directly as Query()

Attached is a simple example. The setup is done using Elixir but the
actual Query.from_attr usage would be the same with plain SA. If you
really need a plain SA example, just ask.


 On Tuesday 10 April 2007 17:22:45 Gaetan de Menten wrote:
  On 3/31/07, Michael Bayer [EMAIL PROTECTED] wrote:
   On Mar 31, 2007, at 1:17 PM, Gaetan de Menten wrote:
That's approximately what I did in my patch with the new
params keyword argument, except I only implemented the set
operation, not the add operation on the params. Anyway, what
can/should I do to get this included? Do you have any
advice/pointers on how to do the same for eager attributes? (or
will you implement it yourself?)
  
   im totally into a series of engine/execution patches/refactorings
   right now, so for properties that have lazy=False, there is
   still a LazyLoader strategy there...you should just call
   property._get_strategy(LazyLoader) in all cases to get at it.
 
  In case anybody is interested, here is my patch slightly modified
  with what you suggest above. Now it works wonders for both lazy and
  eager relationships. There is something ugly about it though:
  imports. I have to import the LazyLoader class from the
  orm.strategies module, but that module imports query, so what I did
  is import the LazyLoader class inside the from_attr method to avoid
  a circular import problem.
 
  By the way, should I create a ticket for this?





-- 
Gaëtan de Menten
http://openhex.org

--~--~-~--~~~---~--~~
You received this message because you are subscribed to the Google Groups 
sqlalchemy group.
To post to this group, send email to sqlalchemy@googlegroups.com
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 elixir import * 

class A(Entity):
has_field('name', String(30))

has_many('b', of_kind='B', lazy=False)

class B(Entity):
has_field('name', String(30))
has_field('extra', Integer)

belongs_to('a', of_kind='A')

metadata.connect('sqlite:///')
create_all()

a1 = A(name='a1')
b1 = B(name='b1', a=a1, extra=10)
b2 = B(name='b2', a=a1)
b3 = B(name='b3', a=a1, extra=5)
b4 = B(name='b4', a=a1, extra=4)
b5 = B(name='b5', a=a1, extra=7)

objectstore.flush()
objectstore.clear()

#metadata.engine.echo = True
a = A.get_by(name='a1')

print normal
q = Query.from_attr(a, 'b')
for b in q:
print b.name, b.extra

print filtered extra  8
for b in q.filter(B.c.extra  8):
print b.name, b.extra

print ordered by extra
for b in q.order_by(B.c.extra):
print b.name, b.extra



[sqlalchemy] Re: [PATCH] Filtered one_to_many relationships (Experimental)

2007-04-10 Thread Michael Bayer


On Apr 10, 2007, at 10:22 AM, Gaetan de Menten wrote:

 In case anybody is interested, here is my patch slightly modified with
 what you suggest above. Now it works wonders for both lazy and eager
 relationships. There is something ugly about it though: imports. I
 have to import the LazyLoader class from the orm.strategies module,
 but that module imports query, so what I did is import the LazyLoader
 class inside the from_attr method to avoid a circular import
 problem.

importing inside a method is perfectly fine, ill try to see if theres  
a workaround but sometimes there really isnt...

 By the way, should I create a ticket for this?

yes.  otherwise ill completely forget about it.

i might have to rush out some extra features this weekend to get  
0.3.7 out, so this one might not be in until 0.3.8.


--~--~-~--~~~---~--~~
You received this message because you are subscribed to the Google Groups 
sqlalchemy group.
To post to this group, send email to sqlalchemy@googlegroups.com
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
-~--~~~~--~~--~--~---



[sqlalchemy] Re: [PATCH] Filtered one_to_many relationships (Experimental)

2007-04-10 Thread Michael Bayer

hm, why is from_attr a classmethod ?  not very consistent with all  
the other generative methods ?   can we have a regular generative  
method as well ?



--~--~-~--~~~---~--~~
You received this message because you are subscribed to the Google Groups 
sqlalchemy group.
To post to this group, send email to sqlalchemy@googlegroups.com
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
-~--~~~~--~~--~--~---



[sqlalchemy] Re: [PATCH] Filtered one_to_many relationships (Experimental)

2007-03-31 Thread Michael Bayer


On Mar 31, 2007, at 1:17 PM, Gaetan de Menten wrote:

 That's approximately what I did in my patch with the new params
 keyword argument, except I only implemented the set operation, not
 the add operation on the params. Anyway, what can/should I do to get
 this included? Do you have any advice/pointers on how to do the same
 for eager attributes? (or will you implement it yourself?)

im totally into a series of engine/execution patches/refactorings  
right now, so for properties that have lazy=False, there is still a  
LazyLoader strategy there...you should just call  
property._get_strategy(LazyLoader) in all cases to get at it.  the  
strategy attribute youre looking at there should really be named  
_default_strategyand should probably be privately held and  
accessed via a property (with a docstring, yes), and _get_strategy()  
should become public (and docstring'ed, yes).

the interfaces module youre working with in orm is, trust me, or  
just go look at the 0.2 source...a work of art compared to how this  
whole area used to function.

--~--~-~--~~~---~--~~
You received this message because you are subscribed to the Google Groups 
sqlalchemy group.
To post to this group, send email to sqlalchemy@googlegroups.com
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
-~--~~~~--~~--~--~---



[sqlalchemy] Re: [PATCH] Filtered one_to_many relationships (Experimental)

2007-03-30 Thread Gaetan de Menten
On 3/26/07, Michael Bayer [EMAIL PROTECTED] wrote:

  (and whether that be a .query() or Query() or SelectResults not big
  difference imo.)
 

 i vote Query().

I tried to implement it but I couldn't do it the way I wanted to. The
problem is: how do I construct a clause from a clause with bind
parameters + a dictionary containing the values for said bind
parameters? I've only seen bind parameters resolved at execution time.
Is it possible to resolve them earlier? In the attached patch, I used
a workaround which is to store the bind parameters in the query
itself, and then use them whenever the query is executed.

Two remarks:

* I've implemented Query.from_attr, instead of adding new keywords to
the Query constructor, because I think:

Query.from_attr(someuser, 'addresses')

looks better, is shorter and is more readable than:

Query('Address', instance=someuser, attr_name='addresses')

* It only works for lazy attributes. I don't think there is any reason
we couldn't make it work for eager attributes, but by looking at the
eagerloader code, I couldn't figure how to do it.

-- 
Gaëtan de Menten
http://openhex.org

--~--~-~--~~~---~--~~
You received this message because you are subscribed to the Google Groups 
sqlalchemy group.
To post to this group, send email to sqlalchemy@googlegroups.com
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
-~--~~~~--~~--~--~---

Index: orm/query.py
===
--- orm/query.py(revision 2444)
+++ orm/query.py(working copy)
@@ -42,12 +42,32 @@
 self._distinct = kwargs.pop('distinct', False)
 self._offset = kwargs.pop('offset', None)
 self._limit = kwargs.pop('limit', None)
-self._criterion = None
+self._criterion = kwargs.pop('criterion', None)
+self._params = kwargs.pop('params', {})
 self._joinpoint = self.mapper
 self._from_obj = [self.table]
 
 for opt in util.flatten_iterator(self.with_options):
 opt.process_query(self)
+
+def from_attr(cls, instance, attr_name):
+prop = instance.mapper.props[attr_name]
+loader = prop.strategy
+#TODO: make it work for eager loader too.
+# code taken from strategies.py
+params = {}
+allparams = True
+for col, bind in loader.lazybinds.iteritems():
+params[bind.key] = loader.parent.get_attr_by_column(instance, col)
+if params[bind.key] is None:
+allparams = False
+break
+
+if not allparams:
+return None
+
+return Query(prop.mapper, criterion=loader.lazywhere, params=params)
+from_attr = classmethod(from_attr)
 
 def _clone(self):
 q = Query.__new__(Query)
@@ -71,6 +91,7 @@
 q._from_obj = list(self._from_obj)
 q._joinpoint = self._joinpoint
 q._criterion = self._criterion
+q._params = self._params
 return q
 
 def _get_session(self):
@@ -690,8 +711,10 @@
 method, which takes the executed statement's ResultProxy
 directly.
 
-
-result = self.session.execute(self.mapper, clauseelement, 
params=params)
+final_params = self._params.copy()
+if params is not None:
+final_params.update(params)
+result = self.session.execute(self.mapper, clauseelement, 
params=final_params)
 try:
 return self.instances(result, **kwargs)
 finally:


[sqlalchemy] Re: [PATCH] Filtered one_to_many relationships (Experimental)

2007-03-26 Thread Gaetan de Menten

On 3/23/07, Michael Bayer [EMAIL PROTECTED] wrote:

 OK, this is actually something people have asked for a lot.  in the
 beginning, recently, etc.  also for different reasons...i.e.
 convenience, or performance, etc.   So, first off let me start by
 illustrating how this use case is done right now.   Assuming your
 Address mapper has a backref user to the User mapper, its  just:

 for address in session.query(Address).filter_by(user=someuser).filter
 (address_table.c.postcode == 5000):
 print address.street

Once again, I discover a better way to do something I did the hard way.

 But,  the join conditions and bind params which
 have been calculated by LazyLoader are just sitting there, they can
 be currently pulled out with a little non-API attribute access but
 Ive no problem with adding some API-level accessors to get at the
 Query object calculated for a particular property (i.e. what you are
 using in your patch internally).

Yes, please do, that'd probably solve the problem nicely (see below
how I see things).


 now lets look at the way your patch does it.

 addresses = user.addresses.filter(address_table.c.postcode == 5000)

 seems easy.  right ?  remember that user is now in the session.
 anyone else that queries for user will get that same User
 instance.  but the rest of the app is going to assume normal
 relationship semantics on that collectionwhich means:

How I envisioned things, this wouldn't be a problem, because
user.addresses.filter(xxx) would return a new, independant list, which
doesn't affect user.addresses, and is not affected if user.addresses
has already been accessed or not. This is not what my patch does, I
know. Sorry for not explaining this in my first mail.

 print someaddress in user.addresses # -- FAIL - the address is not
 present
 user.addresses.remove(someaddress) # -- ERROR - the address is not
 present

 user.addresses.insert(5, someotheraddress) # -- FAIL - the list is
 incomplete, ordering will be incorrect

This is only a matter of getattr and __contains__ triggering init,
right? (At least if we exclude the other problems pointed above).

 session.flush()  # -- FAIL - we have to figure out what items were
 added/removed/unchanged from the collection...but the data's
 incomplete !

I don't master SQLAlchemy internals but I don't see how that is
different from when the collection is complete?

 so as long as we can agree on the its a read-only thing aspect of
 this, we're good to go.  otherwise you have to define for me how all
 those mutating operations are going to work (and even then, its
 additional core complexity im not sure if i can add to my support-load).

I'm fine with the readonly aspect of it. What I don't like is the fact
you have to create a readonly relation (lazyloader/whatever/...) in
advance (ie in your mapper), which is IMHO just a dupe of the normal
relation and pollutes the mapper. You'd end up with mappers like this:

 mapper(SomeClass, table, properties={
 'addresses':relation(Address)
 'addresses2':lazyloader(Address)
 })

which is pretty much as ugly as you can get.

On the other hand, I think that combined with a quick way to have
predefined filters it might be a nice addition anyway:

 mapper(SomeClass, table, properties={
 'addresses': relation(Address)
 'local_addresses': lazyloader(Address,
filter=address.c.postcode==5000)
 })

But it does in no case replace the dynamic non-polluting use-case
I'd like to have. What I had in mind is to reuse normal relations to
get a query. It feels much more natural and cleaner to me. And I think
the best compromise would be something along the lines of:

user.addresses: # use standard relation = read/write
user.addresses.filter(XXX): # returns a query = read only

the code would probably be cleaner if we did something more explicit like:

user.addresses.query # returns the query object that you can filter, etc...

though, as a user, I'd prefer the first solution.

Wouldn't that be possible? I think it should be. You only need to keep
the deferred approach of the InstrumentedList that I demonstrated in
my patch, so that the whole list is not fetched before we get the
query object, which would ruin the whole idea. Of course it was only a
proof-of-concept patch, but I think it should be fixable.

-- 
Gaëtan de Menten
http://openhex.org

--~--~-~--~~~---~--~~
You received this message because you are subscribed to the Google Groups 
sqlalchemy group.
To post to this group, send email to sqlalchemy@googlegroups.com
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
-~--~~~~--~~--~--~---



[sqlalchemy] Re: [PATCH] Filtered one_to_many relationships (Experimental)

2007-03-26 Thread Gaetan de Menten

And by the way, if you agree with that direction of things, I'd
happily work on a patch for the query-on-relation thing.

On 3/26/07, Gaetan de Menten [EMAIL PROTECTED] wrote:
 On 3/23/07, Michael Bayer [EMAIL PROTECTED] wrote:

  OK, this is actually something people have asked for a lot.  in the
  beginning, recently, etc.  also for different reasons...i.e.
  convenience, or performance, etc.   So, first off let me start by
  illustrating how this use case is done right now.   Assuming your
  Address mapper has a backref user to the User mapper, its  just:
 
  for address in 
  session.query(Address).filter_by(user=someuser).filter
  (address_table.c.postcode == 5000):
  print address.street

 Once again, I discover a better way to do something I did the hard way.

  But,  the join conditions and bind params which
  have been calculated by LazyLoader are just sitting there, they can
  be currently pulled out with a little non-API attribute access but
  Ive no problem with adding some API-level accessors to get at the
  Query object calculated for a particular property (i.e. what you are
  using in your patch internally).

 Yes, please do, that'd probably solve the problem nicely (see below
 how I see things).


  now lets look at the way your patch does it.
 
  addresses = user.addresses.filter(address_table.c.postcode == 5000)

  seems easy.  right ?  remember that user is now in the session.
  anyone else that queries for user will get that same User
  instance.  but the rest of the app is going to assume normal
  relationship semantics on that collectionwhich means:

 How I envisioned things, this wouldn't be a problem, because
 user.addresses.filter(xxx) would return a new, independant list, which
 doesn't affect user.addresses, and is not affected if user.addresses
 has already been accessed or not. This is not what my patch does, I
 know. Sorry for not explaining this in my first mail.

  print someaddress in user.addresses # -- FAIL - the address is not
  present
  user.addresses.remove(someaddress) # -- ERROR - the address is not
  present
 
  user.addresses.insert(5, someotheraddress) # -- FAIL - the list is
  incomplete, ordering will be incorrect

 This is only a matter of getattr and __contains__ triggering init,
 right? (At least if we exclude the other problems pointed above).

  session.flush()  # -- FAIL - we have to figure out what items were
  added/removed/unchanged from the collection...but the data's
  incomplete !

 I don't master SQLAlchemy internals but I don't see how that is
 different from when the collection is complete?

  so as long as we can agree on the its a read-only thing aspect of
  this, we're good to go.  otherwise you have to define for me how all
  those mutating operations are going to work (and even then, its
  additional core complexity im not sure if i can add to my support-load).

 I'm fine with the readonly aspect of it. What I don't like is the fact
 you have to create a readonly relation (lazyloader/whatever/...) in
 advance (ie in your mapper), which is IMHO just a dupe of the normal
 relation and pollutes the mapper. You'd end up with mappers like this:

  mapper(SomeClass, table, properties={
  'addresses':relation(Address)
  'addresses2':lazyloader(Address)
  })

 which is pretty much as ugly as you can get.

 On the other hand, I think that combined with a quick way to have
 predefined filters it might be a nice addition anyway:

  mapper(SomeClass, table, properties={
  'addresses': relation(Address)
  'local_addresses': lazyloader(Address,
 filter=address.c.postcode==5000)
  })

 But it does in no case replace the dynamic non-polluting use-case
 I'd like to have. What I had in mind is to reuse normal relations to
 get a query. It feels much more natural and cleaner to me. And I think
 the best compromise would be something along the lines of:

 user.addresses: # use standard relation = read/write
 user.addresses.filter(XXX): # returns a query = read only

 the code would probably be cleaner if we did something more explicit like:

 user.addresses.query # returns the query object that you can filter, etc...

 though, as a user, I'd prefer the first solution.

 Wouldn't that be possible? I think it should be. You only need to keep
 the deferred approach of the InstrumentedList that I demonstrated in
 my patch, so that the whole list is not fetched before we get the
 query object, which would ruin the whole idea. Of course it was only a
 proof-of-concept patch, but I think it should be fixable.

 --
 Gaëtan de Menten
 http://openhex.org



-- 
Gaëtan de Menten
http://openhex.org

--~--~-~--~~~---~--~~
You received this message because you are subscribed to the Google Groups 
sqlalchemy group.
To post to this group, send email to sqlalchemy@googlegroups.com
To 

[sqlalchemy] Re: [PATCH] Filtered one_to_many relationships (Experimental)

2007-03-26 Thread Michael Bayer


On Mar 26, 2007, at 6:15 AM, Gaetan de Menten wrote:


 though, as a user, I'd prefer the first solution.

 Wouldn't that be possible? I think it should be. You only need to keep
 the deferred approach of the InstrumentedList that I demonstrated in
 my patch, so that the whole list is not fetched before we get the
 query object, which would ruin the whole idea. Of course it was only a
 proof-of-concept patch, but I think it should be fixable.

my various issues with the deferred thing are as follows.   note that  
im not putting these out there as this is why we arent doing it, im  
putting it out there as this is why it makes me uncomfortable.

the current use case of has my lazy list been loaded? is:

addresses in myobject.__dict__

with deferred list, now we have to have a list element actually  
present there (but still loaded, maybe not).  so detecting when an  
attribute requires its callable fired off or not gets thorny...also  
breaks code for those who do it the above way, but also we need to  
add some new way to accomplish the above, which will probably have to  
be some messy function call like attribute_manager.is_loaded 
(myobject, addresses).

generally theres all sorts of places where we want to get at the  
attribute and fire it off, not fire it off (internally known as  
passive), etc. and the awareness of the deferred list there would  
have to be more deeply embedded throughout the attributes module for  
everything to keep working.  so my discomfort grows that we are  
changing the APIs of attributes.py, probably including its public  
API, just to suit something that IMHO is strictly for visual appeal.

also, while it looks cleaner, there is still a semantic co-mingling  
that im not very comfortable with.   i.e. that a collection of  
persisted objects on a parent class doubles as a database query  
object. to me the meaning of those two things is entirely different,  
and i think the decline of an API starts with minor conflation of  
concepts.


--~--~-~--~~~---~--~~
You received this message because you are subscribed to the Google Groups 
sqlalchemy group.
To post to this group, send email to sqlalchemy@googlegroups.com
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
-~--~~~~--~~--~--~---



[sqlalchemy] Re: [PATCH] Filtered one_to_many relationships (Experimental)

2007-03-26 Thread Rick Morrison


 i vote Query().


No surprise there ;-)

Me too, +1

--~--~-~--~~~---~--~~
You received this message because you are subscribed to the Google Groups 
sqlalchemy group.
To post to this group, send email to sqlalchemy@googlegroups.com
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
-~--~~~~--~~--~--~---



[sqlalchemy] Re: [PATCH] Filtered one_to_many relationships (Experimental)

2007-03-23 Thread Michael Bayer


OK, this is actually something people have asked for a lot.  in the  
beginning, recently, etc.  also for different reasons...i.e.  
convenience, or performance, etc.   So, first off let me start by  
illustrating how this use case is done right now.   Assuming your  
Address mapper has a backref user to the User mapper, its  just:

for address in session.query(Address).filter_by(user=someuser).filter 
(address_table.c.postcode == 5000):
print address.street

and of course, if you dont have the backref, then we go back to the  
regular SQL stuff:

for address in session.query(Address).filter 
(user.id==someuser.id).filter(address_table.c.postcode == 5000):
print address.street

in the second example, we are figuring out the lazy criterion  
ourselves.  in most cases this is pretty easy to figure out.  but we  
do have some exotic mappings these days, primarily because of  
polymorphic union queries, and it might not be as trivial to make up  
the join condition.  But,  the join conditions and bind params which  
have been calculated by LazyLoader are just sitting there, they can  
be currently pulled out with a little non-API attribute access but  
Ive no problem with adding some API-level accessors to get at the  
Query object calculated for a particular property (i.e. what you are  
using in your patch internally).

So if you want to take the above and make it all slick, all you have  
to do is stick the above query inside a property:

class User(object):
def _get_addresses(self):
return object_session(self).query(Address).filter 
(user.id==someuser.id)
addresses = property(_get_addresses)

then you can just say pretty much what you want:

for address in someuser.addresses.filter(address_table.c.postcode ==  
5000):
print address.street

plus, the addresses object is a Query !  so you already have the  
full interface, and it has __iter__ and __getitem__() !  so the list  
operations on the base object work just fine (and they even work like  
SQLObjects)

for address in someuser.addresses:   # __iter__() fires off the query !

for address in someuser.addresses[3:5]  # slice applies OFFSET and  
LIMIT !

now lets look at the way your patch does it.

addresses = user.addresses.filter(address_table.c.postcode == 5000)

seems easy.  right ?  remember that user is now in the session.   
anyone else that queries for user will get that same User  
instance.  but the rest of the app is going to assume normal  
relationship semantics on that collectionwhich means:

print user.addresess   # -- FAIL  - this code assumes the list is  
complete, its not, its already been loaded incompletely

print someaddress in user.addresses # -- FAIL - the address is not  
present

user.addresses.remove(someaddress) # -- ERROR - the address is not  
present

user.addresses.insert(5, someotheraddress) # -- FAIL - the list is  
incomplete, ordering will be incorrect

session.flush()  # -- FAIL - we have to figure out what items were  
added/removed/unchanged from the collection...but the data's  
incomplete !

This is an issue introduced by our usage of sessions, identity maps,  
etc., things that simpler ORMs dont have to worry about.  So thats  
the part of this I dont want to get into (and im hoping you dont,  
either).  Yes, the use case you want is desireable, and ive no  
problem adding in hooks to make it possible.  but No, I dont think it  
should be injected into the base attribute lazy-loading operation.
i think the semantics of a Query for related items and that of a  
mapped relationship are just plain different, even though  
implementation-wise on the view side at least they are both just a  
SQL query.  with the primary issue being that all mutating/uow- 
related operations go out the window with an incomplete collection.   
from a performance point of view,  the manipulation of huge  
collections could be achieved via loading stub objects which is  
something discussed long ago, which is how Hibernate addresses it  
(called extra-lazy loading).  so if we ever did that, thats how we  
would do it (i.e. a complete collection of incomplete objects).

anyway back to the query side, Ive even been considering making folks  
happy with this and adding a new kind of mapped property called, i  
dont knowquery_relation(), relquery(),  lazyloader(),  
something like that.  and, right in the core, how often does that  
happen ?  it would copy the setup style of relation() but be a lot  
more view oriented.

mapper(SomeClass, table, properties={
'addresses':lazyloader(Address)
})

and there you go !  does the same thing as the manual property code I  
illustrated does, returns a Query from which you can do whatever.

We also had a guy who wanted to do a more complex primaryjoin