From b7e5d114f563e7c92c3e168f8d65ad0d4cfb1388 Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Wed, 14 Dec 2005 05:45:49 +0000 Subject: [PATCH] added select_by, get_by, magic methods --- lib/sqlalchemy/mapping/mapper.py | 81 ++++++++++++++++++++++++++++ lib/sqlalchemy/mapping/properties.py | 28 ++++++++++ test/mapper.py | 14 +++++ 3 files changed, 123 insertions(+) diff --git a/lib/sqlalchemy/mapping/mapper.py b/lib/sqlalchemy/mapping/mapper.py index 390bfbd48a..5fdf0e35b0 100644 --- a/lib/sqlalchemy/mapping/mapper.py +++ b/lib/sqlalchemy/mapping/mapper.py @@ -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 diff --git a/lib/sqlalchemy/mapping/properties.py b/lib/sqlalchemy/mapping/properties.py index f8e2b1b1f3..59cc85a5ff 100644 --- a/lib/sqlalchemy/mapping/properties.py +++ b/lib/sqlalchemy/mapping/properties.py @@ -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: diff --git a/test/mapper.py b/test/mapper.py index 3029e4c898..944082f5cc 100644 --- a/test/mapper.py +++ b/test/mapper.py @@ -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) -- 2.47.2