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