First of all, and since this is my first posting to this list, I want to
thank everyone who helped in creating and maintaining web.py. I really
like its non-intrusive and lightweight approach to solving my daily web
app problems. It's great :)
Now while using it I've stumbled upon a small, let's call it inconvenience.
I usually use the class dispatching mechanism that instantiates some
class for a matching request and calls a method depending on the request
method (GET or POST or HEAD). In these instances I usually want to use a
db connection, service, auth manager or whatever.
My question is, how one would inject those dependencies into the
generated instances.
To elaborate a little more:
The dependencies could of course be instantiated in the default
constructor of the class or in the GET/POST methods itself. But that
makes testing the handlers at least more difficult.
I surely know several ways around this problem, which mostly include
global variables of sorts (globals, registries or whatever), but they
all do not seem very elegant.
So how would you go about injecting dependencies into your handler
instances (or don't you?)?
Thanks for your suggestions -
Ole.
--
Ole Trenner
<o...@jayotee.de>
> I usually use the class dispatching mechanism that instantiates some
> class for a matching request and calls a method depending on the
> request method (GET or POST or HEAD). In these instances I usually
> want to use a db connection, service, auth manager or whatever.
From my own personal fork of Web.py (http://github.com/GothAlice/
webpy/) I've introduced more literal object/method dispatch (without
the distinction between get, post, head, etc.) which can be seen
illustrated on the following Wiki page: http://wiki.github.com/GothAlice/webpy/object-dispatch-example
.
> My question is, how one would inject those dependencies into the
> generated instances.
For the most part I use the add_processor extension method to add pre/
post request hooks to my code. As an example, from the Web.py
cookbook article on adding SQLAlchemy ORM support, I've used the
following in my code to automatically handle database sessions:
def load_sqlalchemy(handler):
web.ctx.orm = scoped_session(sessionmaker(bind=engine))
try:
return handler()
except web.HTTPError:
web.ctx.orm.commit()
raise
except:
web.ctx.orm.rollback()
raise
finally:
web.ctx.orm.commit()
# ...
app.add_processor(load_sqlalchemy)
> I surely know several ways around this problem, which mostly include
> global variables of sorts (globals, registries or whatever), but
> they all do not seem very elegant.
>
> So how would you go about injecting dependencies into your handler
> instances (or don't you?)?
For my object dispatch mechanism, I can add common hooks to the
Controller or dispatch classes in:
But the add_processor method seems to be the most useful. As another
example, I've added simplified template usage to my fork as well:
http://wiki.github.com/GothAlice/webpy/object-dispatch-using-templates-example
— Alice.
Thanks for your reply.
Alice Zoë Bevan–McGregor wrote:
> From my own personal fork of Web.py (http://github.com/GothAlice/webpy/)
> I've introduced more literal object/method dispatch (without the
> distinction between get, post, head, etc.) which can be seen illustrated
> on the following Wiki page:
> http://wiki.github.com/GothAlice/webpy/object-dispatch-example.
I've read your announcement and think it looks interesting, but the
default class dispatching mechanism of web.py is much more convenient
for me. The regexp matching of the url which simultaneously generates
the handler method parameters (like GET(self, id)) saves me a lot of
work in parsing and validating complex urls myself.
> For the most part I use the add_processor extension method to add
> pre/post request hooks to my code. As an example, from the Web.py
> cookbook article on adding SQLAlchemy ORM support, I've used the
> following in my code to automatically handle database sessions:
I've looked into the preprocessors, and while they are a great idea for
some stuff, I can't get hold of the handler class instance from there.
The handler parameter is either the next preprocessor in the chain or
the handle() method of the web.py application that does the actual
instantiation of the handler class and calls the appropriate method. So
I haven't found a method to inject dependencies from there.
Cheers,
what do you mean by dependency injection? can you give an example?
> I've read your announcement and think it looks interesting, but the
> default class dispatching mechanism of web.py is much more
> convenient for me. The regexp matching of the url which
> simultaneously generates the handler method parameters (like
> GET(self, id)) saves me a lot of work in parsing and validating
> complex urls myself.
Which is why I kept the original dispatching mechanism in place. With
some of my larger projects I may use three forms of dispatch at once:
Component Management System / DB deferred dispatch, object instance /
method dispatch, and GET/POST split class dispatch. Each has distinct
advantages for certain kinds of projects, or sections of a single
project.
>> For the most part I use the add_processor extension method to add
>> pre/post request hooks to my code. As an example, from the Web.py
>> cookbook article on adding SQLAlchemy ORM support, I've used the
>> following in my code to automatically handle database sessions:
>
> I've looked into the preprocessors, and while they are a great idea
> for
> some stuff, I can't get hold of the handler class instance from there.
> The handler parameter is either the next preprocessor in the chain or
> the handle() method of the web.py application that does the actual
> instantiation of the handler class and calls the appropriate method.
> So
> I haven't found a method to inject dependencies from there.
I see. I'll be adding a __before__ and __after__ method to the base
Controller class, to be overridden in subclasses, allowing for the
addition of standard processor hooks with the ability to access the
instance and whole class tree. In fact, I think I'll commit that
right now. :)
— Alice.
Anand Chitipothu wrote:
> what do you mean by dependency injection? can you give an example?
In my handlers I need access to a db service, an authentication manager
and so on. These helpers are partly interconnected and partly
longliving, that's why I don't want to spread their instantiation across
the code but rather concentrate it in one place. Plus I actually don't
want to instantiate them from the handler methods at all, because during
testing I'd prefer having the possibility to use simplified or mocked
versions of these helpers.
That's why I'd like to have the possibility to somehow influence the
instantiation phase of my handlers. The easiest approach were
constructor arguments, but there are others.
Currently I'm helping myself with monkey patching and things like that,
but they're just bad style.
I still didn't understand your intent. An example will help.
Anyway, you can do something like this:
def myprocessor(handler):
if web.config.debug: # or if is_test_mode:
return handler()
else:
initialize_my_db()
return handler()
app.add_processor(my_processor)
Does it make sense?
Is there anything preventing you from using a common superclass?
E.g.
class Foo(object):
def __init__(self):
# Perform your startup actions here, initializing web.ctx variables
from web.conf ones, etc.
# You have full access to the controller instance at this point
through `self`.
class MyController(Foo):
def GET(self):
pass
def POT(self):
pass
I've committed the __before__ and __after__ hooks in my object
dispatch controller; now to test them. :)
— Alice.
thanks for your patience :)
Anand Chitipothu wrote:
> I still didn't understand your intent. An example will help.
Ok, that means code :)
> Anyway, you can do something like this:
>
> def myprocessor(handler):
> if web.config.debug: # or if is_test_mode:
> return handler()
> else:
> initialize_my_db()
> return handler()
>
> app.add_processor(my_processor)
>
> Does it make sense?
It does, and it shows my problem. In the processor you can only do
global things, like your initialize_my_db(). It can only change the
global state. My handler will then likely do something like this:
class Handler(object):
def GET(self, id):
db = DB() #I don't want this
data = db.get_data(id)
return SimpleView(data).render()
When I want to unittest the Handler, I have to live with the fact that
it tries to access some database service, that just might not work or
even exist during testing. So currently I'm doing something like this:
class Handler(object):
def GET(self, id):
db = somemodule.get_db() #this is my workaround
...
Now at least I can influence the kind of db service my handler gets. But
this is a little unelegant, because changing the outcome of
somemodule.get_db during the tests is problematic when the tests run
simultaneously or in an undefined order.
So what I'd much rather like to do is this:
class Handler(object):
def __init__(self, db):
self.db = db
def GET(self, id):
data = self.db.get_data(id)
return SimpleView(data).render()
Now, during testing, I construct the handler with whatever fake db I
might consider suited, while in production it gets the real db service
from somewhere above.
I hope that helped clarify my thoughts.
Thanks again,
Alice Zoë Bevan–McGregor wrote:
> Is there anything preventing you from using a common superclass?
>
> E.g.
>
> class Foo(object):
> def __init__(self):
> # Perform your startup actions here, initializing web.ctx
> variables from web.conf ones, etc.
> # You have full access to the controller instance at this
> point through `self`.
>
> class MyController(Foo):
> def GET(self):
> pass
>
> def POT(self):
> pass
No, in fact that is one of the things I'm doing currently. It surely
prevents me from repeating the same instantiating code over and over
again. But as far as I can see it doesn't change the fact that the
handler creates its collaborators itself, meaning I can only have one
and the same set of collaborators during testing and during production.
I've actually done something like that:
class BaseHandler(object):
def __init__(self, testdb=None):
self.db = testdb or somemodule.get_production_db()
class Handler(BaseHandler):
def GET(...):
data = self.db....
which works around this fact. But I thought there might be just some
better way to do it. That's why I asked for your ideas :)
Thanks,
It seems as if my "problem" really isn't one. Maybe I'm a little
overengineering, it wouldn't be for the first time ;)
Actually, I was going for something like this:
app = web.application(urls, globals(), handlerargs=[DB(), Auth()])
(i.e. you could pass arguments to your application that would be passed
on to your handler constructors. Of course this is purely fictional.)
Thanks anyway for your suggestions :)
Keep up the great work.
Cheers,
Ole.