[Twisted-web] Some help with Nevow and databse I/O
Eric Gaumer
egaumer at pagecache.org
Fri Feb 24 14:49:01 MST 2006
On Friday 24 February 2006 12:29 pm, Valentino Volonghi aka Dialtone wrote:
> >I have two questions that are somewhat interrelated. In my test cases I
> > have authentication (using nevow session guard) setup using a SHA stored
> > via the shelve module.
>
> This is technically wrong.
Sorry. I should have clarified a bit. I am authenticating with twisted.cred
using a class/method like this:
class AuthMech(object):
implements(checkers.ICredentialsChecker)
credentialInterfaces = (credentials.IUsernamePassword,)
def _checkPasswd(self, cipher, password):
cipher = base64.decodestring(str(cipher))
salt, hash = cipher[:4], cipher[4:]
hash2 = sha.new(salt + password).digest()
return hash2 == hash
p.registerChecker(pages.AuthMech())
r = guard.SessionWrapper(p)
> Let me explain why:
> Yes, you are using nevow session guard to have authentication but no, it is
> not guard that is setup to use a SHA stored via the shelve module. The
> thing that is setup in that way is twisted.cred.
>
> In fact guard is only glue that is used to get credentials from a request
> and pass them to twisted.cred.
>
> Why do I tell you this? Because it means that you don't have to change your
> application in order to change the source of authentication data.
>
> And yet people say that cred/guard is crappy... Oh man. :)
Not crappy at all, in fact it's been very easy to get running and seems to
work perfectly. I haven't really encountered any problems and my questions
are more about doing things the "right" way rather than just getting it
working.
>
> >The true customer info is stored in a MySQL database. I can pull that data
> >doing the following:
> >
> > def data_query(self, context, data):
> > return self.dbpool.runQuery('SELECT name, email, id, TransferType,
> > basic_charge, over_charge, permitted_transfer FROM Customers')
> >
> >I've read Abe's book on twisted and on page 54 he states "Nevow is
> > designed from the ground up for Twisted, which means you can use
> > Deferreds everywhere"
> >
> >My question is do I have to use the adbapi module or can I use deferreds
> > to handle database queries?
>
> adbapi module returns deferreds for each of the 3 methods:
>
> DBPool.runQuery
> DBPool.runOperation
> DBPool.runInteraction
So I have to use the adbapi (threads) regardless? Okay I can handle that but
my question now is what's the most efficient way to go about it?
Referring to Glyph's blog post, he claims most twisted developers are misusing
the adbapi interface.
My concern is this, my code thus far looks pretty good. I've reviewed many
examples of bits and pieces and generally have a decent feel for what I'm
doing. Now that my application "framework" is basically setup (XHTML,
templates, authentication, server, etc...) I'm to the point where I need to
interface with an existing SQL database to retrieve data to fill in some
templates.
This sort of method works fine:
def data_query(self, context, data):
return self.dbpool.runQuery('SELECT name, email, id, TransferType,
basic_charge, over_charge, permitted_transfer FROM Customers')
But I'm wondering if this the best way to use the adbapi interface.
I guess I have questions concerning when should I connect. As it stands I
connect in the class constructor like this:
class ListCustomers(rend.Page):
def __init__(self, user):
self.dbpool = adbapi.ConnectionPool(...
My gut says this is wrong because now each page class contains a connection
object. That really seems to be more of a general Python design issue and I
could probably come up with a better solution.
But what about queries themselves? Is it bad design to run a query from each
data_* method for all data methods in all page objects? I have three methods
to work with:
DBPool.runQuery
DBPool.runOperation
DBPool.runInteraction
You mentioned runInteraction above. Would it be more wise to provide a general
data_* method that did several queries and stored (cached) that data in the
Page object itself for later retieval?
Each page has to do some database I/O but it would be helpful to not have to
do it everytime a page is refreshed or as the user goes back and forth
through pages since the data is unlikely to change that quickly.
I'm looking for some insight as to how handle this type of scenario from a
Twisted/Nevow perspective. It's obvious that database I/O is going to slow
things down a bit but there must be a "best case" situation that avoids
unnecessary queries.
> >I've read the docs on deferreds but I'd be lying if I said I can fully
> > wrap my brain around all aspects of the concept or how it's implemented.
>
> class Deferred(list):
> def addCallback(self, fun, *args, **kwargs):
> self.append((fun, args, kwargs))
>
> def callback(self, initial_value):
> for fun, args, kwargs in self:
> initial_value = fun(initial_value, *args, **kwargs)
> return initial_value
>
> Easy isn't it?
Yes, it's the "deferreds don't make blocking code non-blocking" part that can
get a bit confusing. I've never really had to think about blocking vs.
non-blocking calls and truthfully I like it. It forces you to be a little
more conscious about what you're doing. I'm really stoked about not using
threads because they are such a nightmare to debug.
> deferreds don't make blocking code non-blocking, they are just a different
> mechanism to register callbacks. Instead of doing:
>
> button.handle_event('clicked', list_of_callbacks)
>
> you do
>
> button.clicked().addCallback(callback1).addCallback(callback2)
This chaining is very cool. So what actually makes the code non-blocking? The
use of select() sys calls? I guess that's where my hangup is. How can we take
a connection to a mail server and defer that result but yet we can't do the
same for a database connection? At the OS level, aren't they both sockets
that can be watched via select()?
My head hurts :-)
>
> You need to spawn a thread because there is hardly any database adapter
> that is written to be async, and those that are written to be async are
> either buggy or not complete or not really usable, plus having deferreds
> after every query in python (which doesn't support deferreds natively)
> makes coding a lot harder (you can deal with some deferreds here and there,
> but not with 10 deferreds each function).
When you say adapter your are referring to (in this case) MySQLdb or it's
underlying _mysql module or yet even lower down to the C api?
If I wanted to implement a limited set of queries, could I write some
non-blocking way of doing that and wrap those functions in Deferred objects.
A silly question I guess because it's obviously possible. I guess I should
ask if it's feasible. Maybe just attempting this as an exercise would give me
greater understanding of how Twisted provides non-blocking calls.
> If you want to do N operations at the same time just use runInteraction
> instead of runQuery (but this doesn't seem to be the case).
No it isn't the case but it is wise for me to do runQuery() calls in each
data_ method of each page or should I be looking for some way to unify
queries and cache that data?
Thanks,
--
Eric Gaumer
Debian GNU/Linux PPC
egaumer at pagecache.org
http://egaumer.pagecache.org
PGP/GPG Key 0xF15D41E9
-------------- next part --------------
A non-text attachment was scrubbed...
Name: not available
Type: application/pgp-signature
Size: 189 bytes
Desc: not available
Url : http://twistedmatrix.com/pipermail/twisted-web/attachments/20060224/d63812a6/attachment.pgp
More information about the Twisted-web
mailing list