]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
added select_by, get_by, magic methods
authorMike Bayer <mike_mp@zzzcomputing.com>
Wed, 14 Dec 2005 05:45:49 +0000 (05:45 +0000)
committerMike Bayer <mike_mp@zzzcomputing.com>
Wed, 14 Dec 2005 05:45:49 +0000 (05:45 +0000)
lib/sqlalchemy/mapping/mapper.py
lib/sqlalchemy/mapping/properties.py
test/mapper.py

index 390bfbd48aa00d0d44d147f40aef2cd275940243..5fdf0e35b0f50e2b25598a1f79e4329e58fb072a 100644 (file)
@@ -290,6 +290,79 @@ class Mapper(object):
                 option.process(mapper)
             return mapper_registry.setdefault(hashkey, mapper)
 
+    def get_by(self, **params):
+        """returns a single object instance based on the given key/value criterion. 
+        this is either the first value in the result list, or None if the list is 
+        empty.
+        
+        the keys are mapped to property or column names mapped by this mapper's Table, and the values
+        are coerced into a WHERE clause separated by AND operators.  If the local property/column
+        names dont contain the key, a search will be performed against this mapper's immediate
+        list of relations as well, forming the appropriate join conditions if a matching property
+        is located.
+        
+        e.g.   u = usermapper.get_by(user_name = 'fred')
+        """
+        x = self.select_by(**params)
+        if len(x):
+            return x[0]
+        else:
+            return None
+            
+    def select_by(self, **params):
+        """returns an array of object instances based on the given key/value criterion. 
+        
+        the keys are mapped to property or column names mapped by this mapper's Table, and the values
+        are coerced into a WHERE clause separated by AND operators.  If the local property/column
+        names dont contain the key, a search will be performed against this mapper's immediate
+        list of relations as well, forming the appropriate join conditions if a matching property
+        is located.
+        
+        e.g.   result = usermapper.select_by(user_name = 'fred')
+        """
+        clause = None
+        for key, value in params.iteritems():
+            if value is False:
+                continue
+            c = self._get_criterion(key, value)
+            if c is None:
+                raise "Cant find criterion for property '"+ key + "'"
+            if clause is None:
+                clause = c
+            else:                
+                clause &= c
+        return self.select_whereclause(clause)
+
+    def _get_criterion(self, key, value):
+        """used by select_by to match a key/value pair against
+        local properties, column names, or a matching property in this mapper's
+        list of relations."""
+        if self.props.has_key(key):
+            return self.props[key].columns[0] == value
+        elif self.table.c.has_key(key):
+            return self.table.c[key] == value
+        else:
+            for prop in self.props.values():
+                c = prop.get_criterion(key, value)
+                if c is not None:
+                    return c
+            else:
+                return None
+
+    def __getattr__(self, key):
+        if (key.startswith('select_by_')):
+            key = key[10:]
+            def foo(arg):
+                return self.select_by(**{key:arg})
+            return foo
+        elif (key.startswith('get_by_')):
+            key = key[7:]
+            def foo(arg):
+                return self.get_by(**{key:arg})
+            return foo
+        else:
+            raise AttributeError(key)
+        
     def selectone(self, *args, **params):
         """works like select(), but only returns the first result by itself, or None if no 
         objects returned."""
@@ -565,6 +638,14 @@ class MapperProperty(object):
         raise NotImplementedError()
     def _copy(self):
         raise NotImplementedError()
+    def get_criterion(self, key, value):
+        """Returns a WHERE clause suitable for this MapperProperty corresponding to the 
+        given key/value pair, where the key is a column or object property name, and value
+        is a value to be matched.  This is only picked up by PropertyLoaders.
+            
+        this is called by a mappers select_by method to formulate a set of key/value pairs into 
+        a WHERE criterion that spans multiple tables if needed."""
+        return None
     def hash_key(self):
         """describes this property and its instantiated arguments in such a way
         as to uniquely identify the concept this MapperProperty represents,within 
index f8e2b1b1f30234750086f2f5c88e5da68f3d7f56..59cc85a5ffd5f4a68dc1c7ff4c63f4e5441455d9 100644 (file)
@@ -254,6 +254,34 @@ class PropertyLoader(MapperProperty):
         if self.secondaryjoin is not None:
             self.secondaryjoin.accept_visitor(processor)
 
+    def get_criterion(self, key, value):
+        """given a key/value pair, determines if this PropertyLoader's mapper contains a key of the
+        given name in its property list, or if this PropertyLoader's association mapper, if any, 
+        contains a key of the given name in its property list, and returns a WHERE clause against
+        the given value if found.
+        
+        this is called by a mappers select_by method to formulate a set of key/value pairs into 
+        a WHERE criterion that spans multiple tables if needed."""
+        # TODO: optimization: change mapper to accept a WHERE clause with separate bind parameters
+        # then cache the generated WHERE clauses here, since the creation + the copy_container 
+        # is an extra expense
+        if self.mapper.props.has_key(key):
+            if self.secondaryjoin is not None:
+                c = (self.mapper.props[key].columns[0]==value) & self.primaryjoin & self.secondaryjoin
+            else:
+                c = (self.mapper.props[key].columns[0]==value) & self.primaryjoin
+            return c.copy_container()
+        elif self.mapper.table.c.has_key(key):
+            if self.secondaryjoin is not None:
+                c = (self.mapper.table.c[key].columns[0]==value) & self.primaryjoin & self.secondaryjoin
+            else:
+                c = (self.mapper.table.c[key].columns[0]==value) & self.primaryjoin
+            return c.copy_container()
+        elif self.association is not None:
+            c = self.mapper._get_criterion(key, value) & self.primaryjoin
+            return c.copy_container()
+
+        return None
 
     def register_deleted(self, obj, uow):
         if not self.private:
index 3029e4c89843ba7c13bd20c9d4848c3e0b151ff2..944082f5cc9dbad359673cbb131d3a3bddfdf561 100644 (file)
@@ -83,6 +83,20 @@ class MapperTest(MapperSuperTest):
         u2 = m.get(7)
         self.assert_(u is not u2)
 
+    def testmagic(self):
+        m = mapper(User, users, properties = {
+            'addresses' : relation(Address, addresses)
+        })
+        l = m.select_by(user_name='fred')
+        self.assert_result(l, User, *[{'user_id':9}])
+        u = l[0]
+        
+        u2 = m.get_by_user_name('fred')
+        self.assert_(u is u2)
+        
+        l = m.select_by(email_address='ed@bettyboop.com')
+        self.assert_result(l, User, *[{'user_id':8}])
+
     def testload(self):
         """tests loading rows with a mapper and producing object instances"""
         m = mapper(User, users)