Web server with Python
Andrew Bennetts
andrew-pythonlist at puzzling.org
Fri Sep 12 00:03:38 EDT 2003
On Thu, Sep 11, 2003 at 05:19:50PM -0700, Dave Kuhlman wrote:
>
> The Twisted documentation seems pretty good and fairly extensive.
> And, for Web applications, here is a document I've written that
> might help you to get started:
>
> http://www.rexx.com/~dkuhlman/twisted_patterns.html
>
> I've checked this document carefully myself, but have not yet been
> able to get any Twisted experts to review it yet (again, the email
> list is down temporarily), so "reader beware".
Here's some quick feedback for you in the meantime, then :)
] Here is an example of a class that dispatches requests:
]
] class ResourceDispatcher(resource.Resource):
]
] isLeaf = True
]
] def render(self, request):
] done = False
] if len(request.postpath) == 1 and request.postpath[0]:
] if request.postpath[0] == 'show_plants':
] dbaccess = dbmod.DBAccess(registry)
] dbaccess.show_plants(request)
] done = True
] elif request.postpath[0] == 'show_plants_by_rating':
] dbaccess = dbmod.DBAccess(registry)
] dbaccess.show_plants_by_rating(request)
] done = True
[...etc...]
Ugh. You can do this with much less pain:
class ResourceDispatcher(resource.Resource):
isLeaf = True
def render(self, request):
if len(request.postpath) == 1 and request.postpath[0]:
name = request.postpath[0]
dbaccess = dbmod.DBAccess(registry)
try:
method = getattr(dbaccess, 'webrequest_' + name)
except AttributeError:
pass
else:
method(request)
return NOT_DONE_YET
content = Content_Dispatch % (
request.postpath,
request.args,
time.ctime(),
)
request.write(content)
request.finish()
Note that the getattr-with-a-prefix idiom is used in several places in
Twisted, e.g. twisted.web.xmlrpc and twisted.spread.pb, among others.
] def delete_plant_step_3(self, resultlist):
] self.db.getPlants().addCallbacks(self.gotPlants,
] self.db.operationError,
] callbackArgs=())
callbackArgs defaults to (), there's no need to explicitly pass it.
] def add_plant_machine(self, *args):
] if self.state == STEP_1:
] self.db = PlantDatabase(self.dbpool)
Again, prefixed methods make this much nicer:
class Foo:
state = 'step1'
def add_plant_machine(self, *args):
method = getattr(self, 'state_' + self.state, self.invalidState)(*args)
def state_step1(self, *args):
self.db = PlantDatabase(self.dbpool)
# ...etc...
self.state = 'step2'
def state_step2(self, *args):
# And so on...
This is much nicer way to construct a state machine than a massive
if/elif/elif/elif/... block. A good example of this in Twisted is
twisted.protocols.smtp (and there's a helper class at
twisted.protocols.basic.StatefulStringProtocol -- but that's not really
relevant to web programming)
Even nicer would be thinking of meaningful names for the states instead of
just "step1", etc :)
] class PlantDatabase(adbapi.Augmentation):
Augmentation is deprecated, and will die soon. You're better off just using
adbapi.ConnectionPool directly.
] class GlimpseTextRepository:
] """Update and retrieve from the Plant_DB database.
] """
] def __init__(self):
] pass
Why not just omit the redundant __init__ definition entirely?
] # ..snip..
] self.deferred.addCallback(self.got_query_results)
] # ..snip..
]
] def got_query_results(self, results):
] return results
Your comment below the code is right; you can just omit this entirely. That
callback is redundant.
] os.system(cmd)
This is a no-no in Twisted -- it will block. Use the methods like
twisted.internet.utils.getProcessOutput instead.
] #
] # XML-RPC applicationLogic class
] #
] class TemperatureAccess:
[..snip..]
] #
] # Produce a form to be used to search the text repository.
] #
] def show_temperature_form(self, request):
] dbglogmsg('*** (show_search_form)')
] self.request = request
] self.content = Content_ConvertTemperatureForm
] self.request.write(self.content)
] self.request.finish()
] return NOT_DONE_YET
This is a pretty messy seperation of application logic from the XML-RPC
layer -- you're operating on a HTTPRequest object, which has nothing to do
with temperature logic. Your abstractions are leaking.
Your example here is pretty confusing. I *think* what you're doing is
having a Twisted webserver render requests based on data retrieved
dynamically via XML-RPC, but I can't see anywhere where you state that
clearly. It took me a while to figure out that TemperatureAccess was on the
XML-RPC *client* side, not the server.
I recommend trying to rewrite this example using Woven. It provides a
framework that encourages proper seperation of Models (the data structures,
whereever they come from, in this case XML-RPC) and Views (the way the model
is presented), and think it would come out much more elegantly.
I also recommend rewriting your example XML-RPC server using
twisted.web.xmlrpc.XMLRPC -- it provides a very easy way to write XML-RPC
servers, and seeing as this is a document about Twisted, it may as well use
it :) You'll probably find the example in doc/examples/xmlrpc.py to be
helpful.
Finally, for the test harness, you might want to consider re-using the
infrastructure in Twisted, rather than rolling your own. For a brief
example, I recommend the DeferredModelTestCase in
twisted/test/test_woven.py.
I hope this feedback helps.
-Andrew.
More information about the Python-list
mailing list