This is really interesting. Please give me some time to study it, meanwhile, so that I do not forget, please open an issue and post the code there.
Massimo On Aug 10, 7:11 pm, Michael Toomim <too...@gmail.com> wrote: > Ok. The basic idea is to allow you to define helpers methods on rows, > sort of like the Models of rails/django. > > You use it like this... I put this in models/db_methods.py: > > @extra_db_methods > class Users(): > def name(self): > return '%s %s' % ((self.first_name or ''), > (self.last_name or '')) > def fb_name(self): > p = self.person() > return (p and p.name) or 'Unknown dude' > def person(self): > return db.people(db.people.fb_id == self.fb_id) > def friends(self): > return [Storage(name=f[0], id=f[1]) > for f in sj.loads(self.friends_cache)] > > @extra_db_methods > class People(): > ... etc > > These are for tables db.users and db.people. It looks up the table > name from the class name. For each table that you want to extend, you > make a class and put @extra_db_methods on top. > > It's implemented with the following @extra_db_methods decorator and a > patch to dal.py. The decorator just traverses the class, pulls out all > methods, and throws them into a "methods" variable on the appropriate > table in dal. Then the dal's parse() routine adds these methods each > row object, using the python type.MethodType() routine for > retargetting a method from one class to another object. > > The downside is extending dal with yet ANOTHER way of adding methods > to objects. That makes 3 apis to maintain for similar things > (virtualfields, computedfields, and this). And I'm not sure about the > names (like "extra_db_methods") for these things yet. Also I think we > might be able to get it even faster by being more clever with python > inheritance in the Row class. Right now it has roughly 10% overhead on > selects in my tests (uncompiled code). > > At the bottom of this message is the decorator that implements the > same functionality using the existing virtualfields mechanism and your > "lazy" decorator. Its downside is a 2x to 3x overhead on selects and > instead of self.field you have to say self.<tablename>.field in the > method bodies. > > def extra_db_methods(clss): > tablename = clss.__name__.lower() > if not tablename in db: > raise Error('There is no `%s\' table to put virtual methods in' > % tablename) > > for k in clss.__dict__.keys(): > method = clss.__dict__[k] > if type(method).__name__ == 'function' or type(method).__name__ > == 'instancemethod': > db[tablename].methods.update({method.__name__ : method}) > > return clss > > --- k/web2py/gluon/dal.py 2011-08-03 16:46:39.000000000 -0700 > +++ web2py/gluon/dal.py 2011-08-10 17:04:48.344795251 -0700 > @@ -1459,6 +1459,7 @@ > new_rows.append(new_row) > rowsobj = Rows(db, new_rows, colnames, rawrows=rows) > for tablename in virtualtables: > + rowsobj.setmethods(tablename, db[tablename].methods) > for item in db[tablename].virtualfields: > try: > rowsobj = > rowsobj.setvirtualfields(**{tablename:item}) > @@ -4559,6 +4560,7 @@ > tablename = tablename > self.fields = SQLCallableList() > self.virtualfields = [] > + self.methods = {} > fields = list(fields) > > if db and self._db._adapter.uploads_in_blob==True: > @@ -5574,6 +5576,14 @@ > self.compact = compact > self.response = rawrows > > + def setmethods(self, tablename, methods): > + if len(methods) < 0: return > + for row in self.records: > + if tablename not in row: break # Abort on this and all > rows. For efficiency. > + for (k,v) in methods.items(): > + r = row[tablename] > + r.__dict__[k] = types.MethodType(v, r) > + return self > def setvirtualfields(self,**keyed_virtualfields): > if not keyed_virtualfields: > return self > > --- > And Here's the implementation using virtualfields: > > def lazy(f): > def g(self,f=f): > import copy > self=copy.copy(self) > return lambda *a,**b: f(self,*a,**b) > return g > > def extra_db_methods_vf(clss): > ''' This decorator clears virtualfields on the table and replaces > them with the methods on this class. > ''' > # First let's make the methods lazy > for k in clss.__dict__.keys(): > if type(getattr(clss, k)).__name__ == 'instancemethod': > setattr(clss, k, lazy(getattr(clss, k))) > > tablename = clss.__name__.lower() > if not tablename in db: > raise Error('There is no `%s\' table to put virtual methods in' > % tablename) > del db[tablename].virtualfields[:] # We clear virtualfields each > time > db[tablename].virtualfields.append(clss()) > return clss > > You use this just like before but with @extra_db_methods_vf instead of > @extra_db_methods, and append <tablename> to each use of "self". > > On Aug 9, 11:16 pm, Massimo Di Pierro <massimo.dipie...@gmail.com> > wrote: > > > > > > > > > let us see it! > > > On Aug 9, 9:36 pm, MichaelToomim<too...@gmail.com> wrote: > > > > Result: Fixed by upgrading. I was seeing this > > > bug:http://code.google.com/p/web2py/issues/detail?id=345 > > > > However, virtualfields still take more time than they should. My > > > selects take 2-3x longer with virtualfields enabled than without. I > > > implemented a little hack in the dal that adds methods to rows with > > > only a 10% overhead (instead of 200-300%) and can share that if > > > anyone's interested. > > > > On Aug 8, 8:38 pm, MichaelToomim<too...@gmail.com> wrote: > > > > > It turns out the speed problem is REALLY bad. I have a table with > > > > virtualfields of 14,000 rows. When I run raw sql: > > > > > a = db.executesql('select * from people;') > > > > > ...the query returns in 121ms. But when I run it through the DAL on > > > > only a subset of the data: > > > > > a = db(db.people.id > 0).select(limitby=(0,1000)) > > > > > ...it returns in 141096.431ms. That's... 141 seconds. So 1000x longer > > > > on .1 of the database. > > > > > My virtualfields are all lazy functions. I'm looking into what's > > > > causing it and will report back when I find out. It seems it might > > > > have something to do with the lazy decorator func because when I hit C- > > > > c the code is often stuck there... inside import copy or something. > > > > > def lazy(f): > > > > def g(self,f=f): > > > > import copy > > > > self=copy.copy(self) > > > > return lambda *a,**b: f(self,*a,**b) > > > > return g > > > > > Anyway, I'll send an update when I have more info. > > > > > On Aug 2, 3:03 pm, MichaelToomim<too...@gmail.com> wrote: > > > > > > That's way better syntax! Great idea! > > > > > > On Aug 2, 2011, at 2:31 AM, Massimo Di Pierro wrote: > > > > > > > We need to work on the speed. This can perhaps help the syntax: > > > > > > > db=DAL() > > > > > > db.define_table('a',Field('b','integer')) > > > > > > for i in range(10): > > > > > > db.a.insert(b=i) > > > > > > > def lazy(f): > > > > > > def g(self,f=f): > > > > > > import copy > > > > > > self=copy.copy(self) > > > > > > return lambda *a,**b: f(self,*a,**b) > > > > > > return g > > > > > > > class Scale: > > > > > > @lazy > > > > > > def c(self,scale=1): > > > > > > return self.a.b*scale > > > > > > > db.a.virtualfields.append(Scale()) > > > > > > for row in db(db.a).select(): > > > > > > print row.b, row.c(1), row.c(2), row.c(3) > > > > > > > On Aug 1, 3:10 pm, MichaelToomim<too...@gmail.com> wrote: > > > > > >> Maybe it helps for me to explain my use-case. I mainly use virtual > > > > > >> fields as lazy methods, to help traverse related tables. I was > > > > > >> actually surprised that lazy evaluation wasn't the default. I > > > > > >> noticed a few implications of this: > > > > > >> - Large queries are slowed byvirtualfields, even if they won't > > > > > >> be needed, esp if they query db > > > > > >> - My definitions forvirtualfieldsaren't as clean as they could > > > > > >> be, because I have many nested "lazy" funcs in the class definition > > > > > >> - We can't serialize all objects intosessionvariables > > > > > > >> So really I'm just using this because it's a nicer notation to > > > > > >> call row.otherthing() instead of getotherthing(row). Maybe I > > > > > >> really want some different feature here? > > > > > > >> On Aug 1, 2011, at 5:40 AM, Anthony Bastardi wrote: > > > > > > >>> Note, after looking at this some more, Massimo recalled that the > > > > > >>> reason auth_user virtual fields were excluded from auth.user (and > > > > > >>> therefore from saving in thesession) is because some virtual > > > > > >>> fields are objects that cannot be pickled and therefore cannot be > > > > > >>> serialized to store in thesession. So, we're thinking of either > > > > > >>> creating an option to store auth_user virutual fields in > > > > > >>> auth.user, or maybe testing to make sure the virtual fields can > > > > > >>> be pickled, and excluding them if not. > > > > > > >>> Anthony > > > > > > >>> On Mon, Aug 1, 2011 at 5:30 AM, > > > > > >>> MichaelToomim<too...@cs.washington.edu> wrote: > > > > > >>> Awesome! I did not know there was an issue submission system. > > > > > > >>> On Jul 30, 2011, at 7:02 AM, Anthony wrote: > > > > > > >>>> An issue has been submitted, and this should be corrected soon. > > > > > > >>>> Anthony > > > > > > >>>> On Friday, July 29, 2011 9:57:30 PM UTC-4, Anthony wrote: > > > > > >>>> auth.user is Storage(table_user._filter_fields(user, id=True)). > > > > > >>>> The _filter_fields method of the auth_user table only selects > > > > > >>>> actual table fields, not virtual fields, so auth.user will not > > > > > >>>> include any virtual fields. Perhaps this should be changed. > > > > > > >>>> Anthony > > > > > > >>>> On Friday, July 29, 2011 9:05:39 PM UTC-4, MichaelToomimwrote: > > > > > >>>> I think I found a bug invirtualfields. I have the following > > > > > >>>> controller: > > > > > > >>>> def posts(): > > > > > >>>> user =session.auth.user > > > > > >>>> n = user.name # returns None > > > > > > >>>> Where "person" is defined as a virtualfield on user: > > > > > > >>>> class Users(): > > > > > >>>> def name(self): > > > > > >>>> > > ... > > read more »