db(...).select().group_by_value(db.table.field), thanks Yair

This commit is contained in:
Massimo Di Pierro
2012-02-22 11:32:30 -06:00
parent 620c61b51a
commit cd20ee3875
3 changed files with 60 additions and 42 deletions
+1 -1
View File
@@ -1 +1 @@
Version 1.99.4 (2012-02-21 16:25:39) stable Version 1.99.4 (2012-02-22 11:32:05) stable
+2 -2
View File
@@ -120,9 +120,9 @@
</li><li>Thadeus Burgess (validators) </li><li>Thadeus Burgess (validators)
</li><li>Tim Michelsen (Sphinx documentation) </li><li>Tim Michelsen (Sphinx documentation)
</li><li>Timothy Farrell (python 2.6 compliance, windows support) </li><li>Timothy Farrell (python 2.6 compliance, windows support)
</li><li>Yair Eshel (internationalizaiton) </li><li>Yair Eshel (internationalizaiton, DAL improvement)
</li><li>Yannis Aribaud (CAS compliance) </li><li>Yannis Aribaud (CAS compliance)
</li><li>Yarko Tymciurak (design, Sphinx documentation) </li><li>Yarko Tymciurak (design)
</li><li>Younghyun Jo (internationalization) </li><li>Younghyun Jo (internationalization)
</li><li>Vidul Nikolaev Petrov (captcha) </li><li>Vidul Nikolaev Petrov (captcha)
</li><li>Vinicius Assef </li><li>Vinicius Assef
+57 -39
View File
@@ -930,7 +930,7 @@ class BaseAdapter(ConnectionPool):
self.execute(query) self.execute(query)
except Exception, e: except Exception, e:
if isinstance(e,self.integrity_error_class()): if isinstance(e,self.integrity_error_class()):
return None return None
raise e raise e
if hasattr(table,'_primarykey'): if hasattr(table,'_primarykey'):
return dict([(k[0].name, k[1]) for k in fields \ return dict([(k[0].name, k[1]) for k in fields \
@@ -1190,7 +1190,7 @@ class BaseAdapter(ConnectionPool):
for tablename in self.tables(field): for tablename in self.tables(field):
if not tablename in tablenames: if not tablename in tablenames:
tablenames.append(tablename) tablenames.append(tablename)
if use_common_filters(query): if use_common_filters(query):
query = self.common_filter(query,tablenames) query = self.common_filter(query,tablenames)
@@ -1470,7 +1470,7 @@ class BaseAdapter(ConnectionPool):
try: try:
value = value.decode(self.db._db_codec) value = value.decode(self.db._db_codec)
except Exception: except Exception:
pass pass
if isinstance(value, unicode): if isinstance(value, unicode):
value = value.encode('utf-8') value = value.encode('utf-8')
elif isinstance(field_type, SQLCustomType): elif isinstance(field_type, SQLCustomType):
@@ -1478,7 +1478,7 @@ class BaseAdapter(ConnectionPool):
if not isinstance(field_type, str) or value is None: if not isinstance(field_type, str) or value is None:
return value return value
elif field_type in ('string', 'text', 'password', 'upload'): elif field_type in ('string', 'text', 'password', 'upload'):
return value return value
else: else:
key = regex_type.match(field_type).group(0) key = regex_type.match(field_type).group(0)
return self.parsemap[key](value,field_type) return self.parsemap[key](value,field_type)
@@ -1522,7 +1522,7 @@ class BaseAdapter(ConnectionPool):
def parse_blob(self, value, field_type): def parse_blob(self, value, field_type):
return base64.b64decode(str(value)) return base64.b64decode(str(value))
def parse_decimal(self, value, field_type): def parse_decimal(self, value, field_type):
decimals = int(field_type[8:-1].split(',')[-1]) decimals = int(field_type[8:-1].split(',')[-1])
if self.dbengine == 'sqlite': if self.dbengine == 'sqlite':
@@ -1536,7 +1536,7 @@ class BaseAdapter(ConnectionPool):
value = bar_decode_integer(value) value = bar_decode_integer(value)
return value return value
def parse_list_references(self, value, field_type): def parse_list_references(self, value, field_type):
if not self.dbengine=='google:datastore': if not self.dbengine=='google:datastore':
value = bar_decode_integer(value) value = bar_decode_integer(value)
return [self.parse_reference(r, field_type[5:]) for r in value] return [self.parse_reference(r, field_type[5:]) for r in value]
@@ -1568,7 +1568,7 @@ class BaseAdapter(ConnectionPool):
'blob':self.parse_blob, 'blob':self.parse_blob,
'decimal':self.parse_decimal, 'decimal':self.parse_decimal,
'list:integer':self.parse_list_integers, 'list:integer':self.parse_list_integers,
'list:reference':self.parse_list_references, 'list:reference':self.parse_list_references,
'list:string':self.parse_list_strings, 'list:string':self.parse_list_strings,
} }
@@ -1974,10 +1974,10 @@ class PostgreSQLAdapter(BaseAdapter):
elif library == "postgres:psycopg2": elif library == "postgres:psycopg2":
self.driver = self.drivers.get('psycopg2') self.driver = self.drivers.get('psycopg2')
elif library == "postgres:pg8000": elif library == "postgres:pg8000":
self.driver = self.drivers.get('pg8000') self.driver = self.drivers.get('pg8000')
if not self.driver: if not self.driver:
raise RuntimeError, "%s is not available" % library raise RuntimeError, "%s is not available" % library
self.__version__ = "%s %s" % (self.driver.__name__, self.driver.__version__) self.__version__ = "%s %s" % (self.driver.__name__, self.driver.__version__)
def connect(msg=msg,driver_args=driver_args): def connect(msg=msg,driver_args=driver_args):
return self.driver.connect(msg,**driver_args) return self.driver.connect(msg,**driver_args)
@@ -2073,8 +2073,8 @@ class OracleAdapter(BaseAdapter):
'datetime': 'DATE', 'datetime': 'DATE',
'id': 'NUMBER PRIMARY KEY', 'id': 'NUMBER PRIMARY KEY',
'reference': 'NUMBER, CONSTRAINT %(constraint_name)s FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s', 'reference': 'NUMBER, CONSTRAINT %(constraint_name)s FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s',
'reference FK': ', CONSTRAINT FK_%(constraint_name)s FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s', 'reference FK': ', CONSTRAINT FK_%(constraint_name)s FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_key)s ON DELETE %(on_delete_action)s',
'reference TFK': ' CONSTRAINT FK_%(foreign_table)s_PK FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_table)s (%(foreign_key)s) ON DELETE %(on_delete_action)s', 'reference TFK': ' CONSTRAINT FK_%(foreign_table)s_PK FOREIGN KEY (%(field_name)s) REFERENCES %(foreign_table)s (%(foreign_key)s) ON DELETE %(on_delete_action)s',
'list:integer': 'CLOB', 'list:integer': 'CLOB',
'list:string': 'CLOB', 'list:string': 'CLOB',
'list:reference': 'CLOB', 'list:reference': 'CLOB',
@@ -3911,7 +3911,7 @@ class MongoDBAdapter(NoSQLAdapter):
elif fieldtype == 'list:string' or fieldtype == 'list:integer' or fieldtype == 'list:reference': elif fieldtype == 'list:string' or fieldtype == 'list:integer' or fieldtype == 'list:reference':
return value #raise SyntaxError, "Not Supported" return value #raise SyntaxError, "Not Supported"
return value return value
#Safe determines whether a asynchronious request is done or a synchronious action is done #Safe determines whether a asynchronious request is done or a synchronious action is done
#For safety, we use by default synchronious requests #For safety, we use by default synchronious requests
def insert(self,table,fields,safe=None): def insert(self,table,fields,safe=None):
@@ -3920,14 +3920,14 @@ class MongoDBAdapter(NoSQLAdapter):
ctable = self.connection[table._tablename] ctable = self.connection[table._tablename]
values = dict((k.name,self.represent(v,table[k.name].type)) for k,v in fields) values = dict((k.name,self.represent(v,table[k.name].type)) for k,v in fields)
ctable.insert(values,safe=safe) ctable.insert(values,safe=safe)
return int(str(values['_id']), 16) return int(str(values['_id']), 16)
def create_table(self, table, migrate=True, fake_migrate=False, polymodel=None, isCapped=False): def create_table(self, table, migrate=True, fake_migrate=False, polymodel=None, isCapped=False):
if isCapped: if isCapped:
raise RuntimeError, "Not implemented" raise RuntimeError, "Not implemented"
else: else:
pass pass
def count(self,query,distinct=None,snapshot=True): def count(self,query,distinct=None,snapshot=True):
if distinct: if distinct:
raise RuntimeError, "COUNT DISTINCT not supported" raise RuntimeError, "COUNT DISTINCT not supported"
@@ -3966,7 +3966,7 @@ class MongoDBAdapter(NoSQLAdapter):
raise SyntaxError, 'second argument must be of type bson.objectid.ObjectId or an objectid representable integer' raise SyntaxError, 'second argument must be of type bson.objectid.ObjectId or an objectid representable integer'
elif expression.second == 0: elif expression.second == 0:
expression.second = pymongo.objectid.ObjectId('000000000000000000000000') expression.second = pymongo.objectid.ObjectId('000000000000000000000000')
return expression.op(expression.first, expression.second) return expression.op(expression.first, expression.second)
if isinstance(expression, Field): if isinstance(expression, Field):
if expression.type=='id': if expression.type=='id':
return "_id" return "_id"
@@ -3988,16 +3988,16 @@ class MongoDBAdapter(NoSQLAdapter):
return ','.join(self.represent(item,field_type) for item in expression) return ','.join(self.represent(item,field_type) for item in expression)
else: else:
return expression return expression
def _select(self,query,fields,attributes): def _select(self,query,fields,attributes):
from pymongo import son from pymongo import son
for key in set(attributes.keys())-set(('limitby','orderby')): for key in set(attributes.keys())-set(('limitby','orderby')):
raise SyntaxError, 'invalid select attribute: %s' % key raise SyntaxError, 'invalid select attribute: %s' % key
new_fields=[] new_fields=[]
mongosort_list = [] mongosort_list = []
# try an orderby attribute # try an orderby attribute
orderby = attributes.get('orderby', False) orderby = attributes.get('orderby', False)
limitby = attributes.get('limitby', False) limitby = attributes.get('limitby', False)
@@ -4007,7 +4007,7 @@ class MongoDBAdapter(NoSQLAdapter):
if isinstance(orderby, (list, tuple)): if isinstance(orderby, (list, tuple)):
print "in xorify" print "in xorify"
orderby = xorify(orderby) orderby = xorify(orderby)
# !!!! need to add 'random' # !!!! need to add 'random'
for f in self.expand(orderby).split(','): for f in self.expand(orderby).split(','):
@@ -4015,10 +4015,10 @@ class MongoDBAdapter(NoSQLAdapter):
mongosort_list.append((f[1:],-1)) mongosort_list.append((f[1:],-1))
else: else:
mongosort_list.append((f,1)) mongosort_list.append((f,1))
print "mongosort_list = %s" % mongosort_list print "mongosort_list = %s" % mongosort_list
if limitby: if limitby:
# a tuple # a tuple
limitby_skip,limitby_limit = limitby limitby_skip,limitby_limit = limitby
else: else:
limitby_skip = 0 limitby_skip = 0
@@ -4029,7 +4029,7 @@ class MongoDBAdapter(NoSQLAdapter):
#if distinct: #if distinct:
#print "in distinct %s" % distinct #print "in distinct %s" % distinct
mongofields_dict = son.SON() mongofields_dict = son.SON()
mongoqry_dict = {} mongoqry_dict = {}
for item in fields: for item in fields:
@@ -4049,7 +4049,7 @@ class MongoDBAdapter(NoSQLAdapter):
for f in fieldnames: for f in fieldnames:
mongofields_dict[f.name] = 1 # ie field=1 mongofields_dict[f.name] = 1 # ie field=1
return tablename, mongoqry_dict, mongofields_dict, mongosort_list, limitby_limit, limitby_skip return tablename, mongoqry_dict, mongofields_dict, mongosort_list, limitby_limit, limitby_skip
# need to define all the 'sql' methods gt,lt etc.... # need to define all the 'sql' methods gt,lt etc....
def select(self,query,fields,attributes,count=False,snapshot=False): def select(self,query,fields,attributes,count=False,snapshot=False):
@@ -4069,10 +4069,10 @@ class MongoDBAdapter(NoSQLAdapter):
return {'count' : ctable.find(mongoqry_dict,mongofields_dict,skip=limitby_skip, limit=limitby_limit, sort=mongosort_list,snapshot=snapshot).count()} return {'count' : ctable.find(mongoqry_dict,mongofields_dict,skip=limitby_skip, limit=limitby_limit, sort=mongosort_list,snapshot=snapshot).count()}
else: else:
mongo_list_dicts = ctable.find(mongoqry_dict,mongofields_dict,skip=limitby_skip, limit=limitby_limit, sort=mongosort_list,snapshot=snapshot) # pymongo cursor object mongo_list_dicts = ctable.find(mongoqry_dict,mongofields_dict,skip=limitby_skip, limit=limitby_limit, sort=mongosort_list,snapshot=snapshot) # pymongo cursor object
print "mongo_list_dicts=%s" % mongo_list_dicts print "mongo_list_dicts=%s" % mongo_list_dicts
#if mongo_list_dicts.count() > 0: # #if mongo_list_dicts.count() > 0: #
#colnames = mongo_list_dicts[0].keys() # assuming all docs have same "shape", grab colnames from first dictionary (aka row) #colnames = mongo_list_dicts[0].keys() # assuming all docs have same "shape", grab colnames from first dictionary (aka row)
#else: #else:
#colnames = mongofields_dict.keys() #colnames = mongofields_dict.keys()
#print "colnames = %s" % colnames #print "colnames = %s" % colnames
#rows = [row.values() for row in mongo_list_dicts] #rows = [row.values() for row in mongo_list_dicts]
@@ -4095,7 +4095,7 @@ class MongoDBAdapter(NoSQLAdapter):
def INVERT(self,first): def INVERT(self,first):
#print "in invert first=%s" % first #print "in invert first=%s" % first
return '-%s' % self.expand(first) return '-%s' % self.expand(first)
def drop(self, table, mode=''): def drop(self, table, mode=''):
ctable = self.connection[table._tablename] ctable = self.connection[table._tablename]
@@ -4208,7 +4208,7 @@ class MongoDBAdapter(NoSQLAdapter):
#return '(%s == %s)' % (self.expand(first),self.expand(second,first.type)) #return '(%s == %s)' % (self.expand(first),self.expand(second,first.type))
result[self.expand(first)] = self.expand(second) result[self.expand(first)] = self.expand(second)
return result return result
def NE(self, first, second=None): def NE(self, first, second=None):
print "in NE" print "in NE"
result = {} result = {}
@@ -4345,7 +4345,7 @@ class MongoDBAdapter(NoSQLAdapter):
#return '(%s == %s)' % (self.expand(first),self.expand(second,first.type)) #return '(%s == %s)' % (self.expand(first),self.expand(second,first.type))
result[self.expand(first)] = self.expand(second) result[self.expand(first)] = self.expand(second)
return result return result
def NE(self, first, second=None): def NE(self, first, second=None):
print "in NE" print "in NE"
result = {} result = {}
@@ -4453,14 +4453,14 @@ class IMAPAdapter(NoSQLAdapter):
IMAP server mailbox list information. IMAP server mailbox list information.
Here is a list of supported fields: Here is a list of supported fields:
Field Type Description Field Type Description
################################################################ ################################################################
uid string uid string
answered boolean Flag answered boolean Flag
created date created date
content list:string A list of text or html parts content list:string A list of text or html parts
to string to string
cc string cc string
bcc string bcc string
size integer the amount of octets of the message* size integer the amount of octets of the message*
@@ -4470,7 +4470,7 @@ class IMAPAdapter(NoSQLAdapter):
sender string sender string
recent boolean Flag recent boolean Flag
seen boolean Flag seen boolean Flag
subject string subject string
mime string The mime header declaration mime string The mime header declaration
email string The complete RFC822 message** email string The complete RFC822 message**
attachments list:string Each non text decoded part as string attachments list:string Each non text decoded part as string
@@ -4498,7 +4498,7 @@ class IMAPAdapter(NoSQLAdapter):
# Count today's unseen messages # Count today's unseen messages
# smaller than 6000 octets from the # smaller than 6000 octets from the
# inbox mailbox # inbox mailbox
q = imapdb.INBOX.seen == False q = imapdb.INBOX.seen == False
q &= imapdb.INBOX.created == datetime.date.today() q &= imapdb.INBOX.created == datetime.date.today()
q &= imapdb.INBOX.size < 6000 q &= imapdb.INBOX.size < 6000
@@ -4524,7 +4524,7 @@ class IMAPAdapter(NoSQLAdapter):
# It is possible also to mark messages for deletion instead of ereasing them # It is possible also to mark messages for deletion instead of ereasing them
# directly with set.update(deleted=True) # directly with set.update(deleted=True)
""" """
types = { types = {
@@ -6384,7 +6384,7 @@ class Table(dict):
if not field.name in fieldnames and not field.type=='id': if not field.name in fieldnames and not field.type=='id':
field = copy.copy(field) field = copy.copy(field)
# correct self references # correct self references
if not table._actual and field.type == 'reference '+table._tablename: if not table._actual and field.type == 'reference '+table._tablename:
field.type = 'reference '+self._tablename field.type = 'reference '+self._tablename
newfields.append(field) newfields.append(field)
fieldnames.add(field.name) fieldnames.add(field.name)
@@ -7373,7 +7373,7 @@ class Set(object):
def count(self,distinct=None): def count(self,distinct=None):
return self.db._adapter.count(self.query,distinct) return self.db._adapter.count(self.query,distinct)
def select(self, *fields, **attributes): def select(self, *fields, **attributes):
adapter = self.db._adapter adapter = self.db._adapter
fields = adapter.expand_all(fields, adapter.tables(self.query)) fields = adapter.expand_all(fields, adapter.tables(self.query))
return adapter.select(self.query,fields,attributes) return adapter.select(self.query,fields,attributes)
@@ -7624,6 +7624,23 @@ class Rows(object):
""" """
return Rows(self.db,sorted(self,key=f,reverse=reverse),self.colnames) return Rows(self.db,sorted(self,key=f,reverse=reverse),self.colnames)
def group_by_value(self, field):
"""
regroups the rows, by one of the fields
"""
if not self.records:
return {}
key = str(field)
grouped_row_group = dict()
for row in self:
value = row[key]
if not value in grouped_row_group:
grouped_row_group[value] = [row]
else:
grouped_row_group[value].append(row)
return grouped_row_group
def as_list(self, def as_list(self,
compact=True, compact=True,
storage_to_dict=True, storage_to_dict=True,
@@ -8000,3 +8017,4 @@ if __name__ == '__main__':
import doctest import doctest
doctest.testmod() doctest.testmod()