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.