web2py

July 30, 2010

web2py and metaclasses

Filed under: Uncategorized — mdipierro @ 2:26 pm

Motivations

In the web2py world we always think about how to we make things easier for users. Here we want to show how you can use metaclasses to make your code very readable.

Out goal is to define some new syntax that has immediate meaning in English and can be reused in any web2py application.

Preamble

Create an empty application (called for example thing_manager) and define

# black box
db=DAL('sqlite://storage.sqlite')
is_a = type('_',(),{'__getattr__':(lambda s,n: (lambda *a, **b: db.define_table(n,*a,**b)))})()
with_a = type('_',(),{'__getattr__':(lambda s,n: (lambda *a, **b: Field(n,*a,**b)))})()
this = type('_',(),{'__getattr__': (lambda s,n: db[n][request.args(0)])})()
please = crud
get_me = lambda a,*b,**c: db(a).select(*b,**c)
delete = lambda a,*b,**c: db(a).delete(*b,**c)
update = lambda a,*b,**c: db(a).update(*b,**c)
to_visitor = lambda *a: dict(page=DIV(*[DIV(i) for i in a]))
# end black box

The model

Now in your model you can define tables using the new syntax:


the_thing = is_a.thing(with_a.name(), with_a.category())
my_things = the_thing.id>0

where ‘thing’ is the name of the table, ‘name’ and ‘category’ are fields. You can specify named field attributes of name and category using normal Field(…) attribute syntax such as in

with_a.category(type='string',default=None,requires=IS_NOT_EMPTY())

The Controller

Now in the default controller you can use the new syntax to define functions. Here is one for example:


def index():
    if this.thing:
        form = please.update(the_thing, this.thing)
    else:
        form = please.create(the_thing)
    things = get_me(my_things)
    return to_visitor(form,things)

This function can be called by
http://127.0.0.1:8000/thing_manager/default/index
(will make a create interface for thing) or
http://127.0.0.1:8000/thing_manager/default/index/1
(will make an update for for thing with id==1)

This action creates a complete thing_management interface. get_me(my_things) returns all current thing records. please.create and please.update simply map into the corresponding crud functions.

preview

Here is how to looks like:

Other class

Of course there is nothing special about thing. Let’s use the same trick for a table “product” and different actions:


the_product = is_a.product(with_a.name(), with_a.price('double'))
my_products = the_product.price>0.0

# http://...new_product                                                                                                        
def new_product():
     return to_visitor(please.create(the_product))

#http://...update_product/[id]
def update_product():
     return to_visitor(please.update(the_product,this.product))

#http://.../list_products
def list_products():
     products = get_me(my_products)
     return to_visitor(products)

Some explanation

The magic happens in is_a, with_a and this. There are instances of three different temp classes defined by the type function. They take attributes of arbitrary names and return lambda function that return the desired function:

  • is_a.thing(…) maps into db.define_table(‘thing’,….)
  • with_a.name(…) maps into Field(‘name’,….)
  • this.thing() maps into db[‘thing’][request.args(0)] this standard web2py syntax for fetching the record with id==request.args(0) (the [id] in http://…/index/%5Bid%5D) or None if the record does not exist.

Create a free website or blog at WordPress.com.