how to pass state to Colubrid application

6 views
Skip to first unread message

odigity

unread,
Mar 21, 2007, 5:26:09 PM3/21/07
to colubrid-users
I'm new to Python, and am exploring Colubrid for a project at work.
There's one thing I can't figure out.

The Quickstart guide uses the following example:

---
from colubrid import BaseApplication, HttpResponse, execute

class Application(BaseApplication):

def process_request(self):
return HttpResponse('Hello World')

app = Application

if __name__ == '__main__':
execute(app)
---

It seems the class object is being passed to execute, which means you
don't get a chance to instantiate an object of the class yourself, and
therefore you never get a chance to pass state to the constructor.
How would I build a web application using Colubrid but still be able
to configure the application, populated from command line arguments to
the launch script, for example?

Armin Ronacher

unread,
Mar 22, 2007, 1:50:39 PM3/22/07
to colubri...@googlegroups.com
Hi,

odigity wrote:
> It seems the class object is being passed to execute, which means you
> don't get a chance to instantiate an object of the class yourself, and
> therefore you never get a chance to pass state to the constructor.
> How would I build a web application using Colubrid but still be able
> to configure the application, populated from command line arguments to
> the launch script, for example?

Yes. That's a stupid limitation. I designed colubrid some time ago when
I was new to WSGI. Unfortunately there is currently no way to bypass
that limitation except of using a factory function.

Regards,
Armin

odigity

unread,
Mar 22, 2007, 6:07:35 PM3/22/07
to colubrid-users
On Mar 22, 10:50 am, Armin Ronacher <armin.ronac...@active-4.com>
wrote:

> Yes. That's a stupid limitation. I designed colubrid some time ago when
> I was new to WSGI. Unfortunately there is currently no way to bypass
> that limitation except of using a factory function.

Well, if we agree on that, then there's common ground to move
forward. :)

I've coded up a hack that let's me do what I want with Colubrid. I
ended up not using it, but I think it's illustrative nonetheless:

---
class MyApp(BaseApplication):
def __init__(self, some_state):
self.some_state = some_state
def __call__(self, environ, start_response):
BaseApplication.__init__(self, environ, start_response)
return self
def process_request(self):
return HttpResponse('My state: ' + self.some_state)

app = MyApp('California')
execute(app)
---

The standard Colubrid example demonstrates creating a custom
Application class that derives from BaseApplication. You then pass
the class to execute, and it instantiates the class every time a
request arrives, passing in environ and start_response to __init__,
and getting back an iterable object of type BaseApplication that will
produce the output. In effect, the BaseApplication class in Colubrid
is really acting as a RequestHandler class. There is no true
Application class.

In the above example, I've defined my own __init__ method so I can
pass in application-specific state and initialize my web application.
I then defined a __call__ method so that I can instantiate an object
of class MyApp and pass that to execute, instead of passing it the
class directly. Now MyApp objects are themselves callable (and
correctly implement WSGI), and when called simply call
BaseApplication's __init__ method (since it behaves like a Request
initializer), and returns itself. In effect, I'm making up for lack
of an Application class by making the same class behave as both the
traditional Application and RequestHandler classes.

However, this is a horrible hack. :) It illustrates the key design
problem that prevents me from being comfortable using Colubrid (which
is a shame, because in terms of purpose it's exactly what I want).
The another annoyance is that your examples demonstrated called
write() methods on an attributed called request, which kind of bugs me
(it should be response.write, not request.write).

In the meantime, I've slapped together a minimal replacement for
Colubrid called WSGIApp that I'm using at work. It does what I need,
and took all of 10 minutes to write (partly thanks to <a href="http://
pythonpaste.org/do-it-yourself-framework.html">Ian Bicking's great
article</a>), but I'd still rather use existing code like Colubrid.

For the curious, here's the contents of my wsgi.py:

---
from jinja import Template, Context, FileSystemLoader
from paste.request import parse_formvars

class Request(object):
def __init__(self, environ):
self.args = parse_formvars(environ)

class Response(object):
def __init__(self):
self.headers = {'content-type':'text/html'}
self.body = []
self.status = '200 OK'

def set_content_type(self, type):
self.headers['content-type'] = type

def write(self, chunk):
self.body.append(chunk)

class WSGIApp(object):
def __call__(self, environ, start_response):
self.request = Request(environ)
self.response = Response()
self.handler()
start_response(self.response.status,
self.response.headers.items())
return self.response.body

class TemplatedWSGIApp(WSGIApp):
def __init__(self, template_dir):
self.template_dir = template_dir

def render(self, template, data):
template = Template(template,
FileSystemLoader(self.template_dir))
context = Context(data)
self.response.write(template.render(context))
---

And here's how I'm using it to build my PyLucene HTTP/XML service:

---
from xml.sax import saxutils

from mm.search.searcher import Searcher
from mm.web.wsgi import WSGIApp

class SearcherApp(WSGIApp):
def __init__(self, index_dir, conf):
self.searcher = Searcher(index_dir, conf)

def handler(self):
hits = self.searcher.search(self.request.args['q'])
self.response.set_content_type('application/xml')
self.response.write('<?xml version="1.0" encoding="utf-8"?>')
self.response.write('<search>')
if hits is not None:
for i, doc in hits:
self.response.write('<hit>')
self.response.write('<pid>' + doc.get('product_id') +
'</pid>')
self.response.write('<title>' +
saxutils.escape(doc.get('title')) + '</title>')
self.response.write('<score>%.2f</score>' %
(hits.score(i),))
self.response.write('<category_id>' +
doc.get('category_id') + '</category_id>')
self.response.write('<category_path_ids>' +
doc.get('category_path_ids') + '</category_path_ids>')
self.response.write('</hit>')
if i == 9: break
self.response.write('</search>')
---

Graham Dumpleton

unread,
Mar 22, 2007, 8:00:08 PM3/22/07
to colubrid-users
On Mar 23, 9:07 am, "odigity" <odig...@gmail.com> wrote:
> On Mar 22, 10:50 am, Armin Ronacher <armin.ronac...@active-4.com>
> wrote:
>
> > Yes. That's a stupid limitation. I designed colubrid some time ago when
> > I was new to WSGI. Unfortunately there is currently no way to bypass
> > that limitation except of using a factory function.
>
> Well, if we agree on that, then there's common ground to move
> forward. :)
>
> I've coded up a hack that let's me do what I want with Colubrid. I
> ended up not using it, but I think it's illustrative nonetheless:
>
> ---
> class MyApp(BaseApplication):
> def __init__(self, some_state):
> self.some_state = some_state
> def __call__(self, environ, start_response):
> BaseApplication.__init__(self, environ, start_response)
> return self
> def process_request(self):
> return HttpResponse('My state: ' + self.some_state)
>
> app = MyApp('California')
> execute(app)

Separating it into two parts would look nicer and gives you a wrapper
that you can apply to any application class. Ie., something like:

class Application(BaseApplication):

def __init__(self, some_state):
self.some_state = some_state

def process_request(self):


return HttpResponse('Hello World')

class Instance:

def __init__(self, type, *args, **kwargs):
self.__type = type
self.__args = args
self.__kwargs = kwargs

def __call__(self):
return self.__type(*self.__args, **self.__kwargs)

app = Instance(MyApp, 'California')
execute(app)

> self.response.write('<hit>')
> self.response.write('<pid>' + doc.get('product_id') +
> '</pid>')
> self.response.write('<title>' +
> saxutils.escape(doc.get('title')) + '</title>')
> self.response.write('<score>%.2f</score>' %
> (hits.score(i),))

FWIW, because response object has a write() function, it can be used
with 'print'. More often than not I find calls to write() like above
look messy and are hard to follow. Alternative is to use:

print >> response, '<hit>'
print >> response, '<pid>', doc.get('product_id'), '</
pid>'
print >> response, '<title>' +


saxutils.escape(doc.get('title')) + '</title>'

print >> response, '<score>%.2f</score>' %
(hits.score(i),)

That 'print' will automatically convert non string value to strings
can save a bit of code sometimes. Just have to be careful where
'print' inserts newlines and spaces in case it changes meaning of data
values.

Graham

Graham Dumpleton

unread,
Mar 22, 2007, 8:03:25 PM3/22/07
to colubrid-users
On Mar 23, 11:00 am, "Graham Dumpleton" <Graham.Dumple...@gmail.com>
wrote:

> Separating it into two parts would look nicer and gives you a wrapper
> that you can apply to any application class. Ie., something like:
>
> class Application(BaseApplication):
>
> def __init__(self, some_state):

Missed:

BaseApplication.__init__(self)

> self.some_state = some_state
>
> def process_request(self):
> return HttpResponse('Hello World')
>
> class Instance:
>
> def __init__(self, type, *args, **kwargs):
> self.__type = type
> self.__args = args
> self.__kwargs = kwargs
>
> def __call__(self):
> return self.__type(*self.__args, **self.__kwargs)
>
> app = Instance(MyApp, 'California')
> execute(app)

Graham

odigity

unread,
Mar 22, 2007, 8:36:01 PM3/22/07
to colubrid-users
On Mar 22, 5:00 pm, "Graham Dumpleton" <Graham.Dumple...@gmail.com>
wrote:

> Separating it into two parts would look nicer and gives you a wrapper
> that you can apply to any application class. Ie., something like:
>
> class Application(BaseApplication):
>
> def __init__(self, some_state):
> self.some_state = some_state
>
> def process_request(self):
> return HttpResponse('Hello World')
>
> class Instance:
>
> def __init__(self, type, *args, **kwargs):
> self.__type = type
> self.__args = args
> self.__kwargs = kwargs
>
> def __call__(self):
> return self.__type(*self.__args, **self.__kwargs)
>
> app = Instance(MyApp, 'California')
> execute(app)

So, app is an object of type Instance, and everytime the WSGI server
calls app() to handle a request, it's going to instantiate an instance
of MyApp, but this time with my state?

That's a nice hack that keeps the current Colubrid model while
allowing me to inject state. However, it's even more mind-bending
than Colubrid by itself. I'm trying to argue the case that the
traditional OOP model that I implemented is easier to understand and
use.

> > self.response.write('<score>%.2f</score>' %
> > (hits.score(i),))
>
> FWIW, because response object has a write() function, it can be used
> with 'print'. More often than not I find calls to write() like above
> look messy and are hard to follow. Alternative is to use:
>

> print >> response, '<score>%.2f</score>' %
> (hits.score(i),)
>
> That 'print' will automatically convert non string value to strings
> can save a bit of code sometimes. Just have to be careful where
> 'print' inserts newlines and spaces in case it changes meaning of data
> values.

Thanks for the tip! I've been using Python for less than a month, so
I didn't know about that. I'll switch. For anything more complex I'd
use my TemplatedWSGIApp class and have no print/write statements at
all, but since those few lines are literally entire app in this case,
I felt having to store and reference a template file was adding
complexity in this case, rather than reducing it.

Graham Dumpleton

unread,
Mar 22, 2007, 9:00:47 PM3/22/07
to colubrid-users

Ahhh, its not a hack but simply one accepted way of writing the
factory function that Armin referred to. In this case the factory
wrapper is itself a reusable class rather than it being hardwired. The
way of doing it so it was hardwired would have been:

def Factory():
return MyApp('California')

app = Factory
execute(app)

Graham

Armin Ronacher

unread,
Mar 23, 2007, 1:11:49 PM3/23/07
to colubri...@googlegroups.com
Hi,

odigity wrote:
> On Mar 22, 10:50 am, Armin Ronacher <armin.ronac...@active-4.com>
> wrote:
> from jinja import Template, Context, FileSystemLoader
> from paste.request import parse_formvars

Just a small notice. I released Jinja 1.0 some minutes ago which has a
completely different way of accessing the API and some differences in
the syntax. The old one will still receive bugfix updates but it's a
better idea to use the new more powerful jinja version. If your
application is quite small so far you may want to update your templates.

Regards,
Armin

odigity

unread,
Mar 23, 2007, 3:10:01 PM3/23/07
to colubrid-users
On Mar 23, 10:11 am, Armin Ronacher <armin.ronac...@active-4.com>
wrote:

> Just a small notice. I released Jinja 1.0 some minutes ago which has a
> completely different way of accessing the API and some differences in
> the syntax. The old one will still receive bugfix updates but it's a
> better idea to use the new more powerful jinja version. If your
> application is quite small so far you may want to update your templates.

Cool! I'll check it out as soon as it's up on the site.

Armin Ronacher

unread,
Mar 23, 2007, 3:17:43 PM3/23/07
to colubri...@googlegroups.com
Hoi,

odigity wrote:
> Cool! I'll check it out as soon as it's up on the site.

It's online on the new webpage at http://jinja.pocoo.org/

Regards,
Armin

odigity

unread,
Mar 23, 2007, 3:50:53 PM3/23/07
to colubrid-users
On Mar 23, 12:17 pm, Armin Ronacher <armin.ronac...@active-4.com>
wrote:

> odigity wrote:
> > Cool! I'll check it out as soon as it's up on the site.
>
> It's online on the new webpage athttp://jinja.pocoo.org/

Spiffy! I've been using http://wsgiarea.pocoo.org/jinja/ all this
time.

Reply all
Reply to author
Forward
0 new messages