On 14Sep2012 10:53, Chicken McNuggets <chic...@mcnuggets.com> wrote: | On 14/09/2012 03:31, Cameron Simpson wrote: | > On 13Sep2012 19:34, Chicken McNuggets <chic...@mcnuggets.com> wrote: | > | I'm writing a simple library that communicates with a web service and am | > | wondering if there are any generally well regarded methods for batching | > | HTTP requests? | > | | > | The problem with most web services is that they require a list of | > | sequential commands to be executed in a certain order to complete a | > | given task (or at least the one I am using does) so having to manually | > | call each command is a bit of a pain. How would you go about the design | > | of a library to interact with these services? | > | > Maybe I'm missing something. What's hard about: | > | > - wrapping the web services calls in a simple wrapper which | > composes the call, runs it, and returns the result parts | > This lets you hide all the waffle about the base URL, | > credentials etc in the wrapper and only supply the essentials | > at call time. | > | > - writing your workflow thing then as a simple function: | > | > def doit(...): | > web_service_call1(...) | > web_service_call2(...) | > web_service_call3(...) | > | > with whatever internal control is required? | > | > This has worked for me for simple things. | > What am I missing about the larger context? | | That is what I have at the moment but it is ugly as hell. I was | wondering if there was a somewhat more elegant solution that I was missing.
Leading disclaimer: I'm no web service expert. Well, I presume you're using a web service library like ZSI or suds? I'm using suds; started with ZSI but it had apparent maintenance stagnancy and also some other issues. I moved to suds and it's been fine. I make a class wrapping a suds Client object and some convenience functions. A suds Client object is made from the WSDL file or URL and fills out all the services with callable methods accepting the web service parameters. So the class has a setup method like this: def set_client(self, wsdlfile, baseurl): ''' Common code to set the .client attribute to a suds SOAP Client. ''' from suds.client import Client self.client = Client('file://'+wsdlfile) self.client.set_options(location = baseurl) self.client.set_options(proxy = {'https': 'proxy:3128'}) wsdlfile points at a local WSDL file, removing dependence on some server to provide the WSDL. Makes testing easier too. Then there's a general call wrapper. The wscall() method below is a slightly scoured version of something I made for work. You hand it the service name and any parameters needed for that service call and it looks it up in the Client, calls it, sanity checks the result in a generic sense. If things are good it returns the suds reply object. But if anything goes wrong it logs it and returns None. "Goes wrong" includes a successful call whose internal result contains an error response - obviously that's application dependent. The upshot is that the wrapper for a specific web service call then becomes: def thing1(self, blah, blah, ...): reply = self.wscall('thing1', blah, blah, ...) if reply is None: # badness happened; it has been logged, just return in whatever # fashion you desire - return None, raise exception, etc ... # otherwise we pick apart the reply object to get the bits that # matter and return them return stuff from the reply object and your larger logic looks like: def doit(self, ...): self.thing1(1,2,3) self.thing2(...) and so forth. The general web service wrapper wscall() below has some stuff ripped out, but I've left in some application specific stuff for illustration: - optional _client and _cred keyword parameters to override the default Client and credentials from the main class instance - the Pfx context manager stuff arranges to prefix log messages and exception strings with context, which I find very handy in debugging and logging - likewise the LogTime context manager logs things that exceed a time threshold - the wsQueue.submit(...) stuff punts the actual web service call through a capacity queue (a bit like the futures module) because this is a capacity limited multithreaded app. For your purposes you could write: websvc = getattr(_client.service, servicename) with LogTime(servicename, threshold=3): try: reply = websvc(userId=_cred.username, password=_cred.password, *args, **kwargs) except: exc_info = sys.exc_info reply = None else: exc_info = None which is much easier to follow. - the 'callStatus' thing is application specific, left as an example of a post-call sanity check you would add for calls that complete but return with an error indication Anyway, all that said, the general wrapper looks like this: def doWScall(self, servicename, _client=None, _cred=None, *args, **kwargs): ''' General wrapper for a web service call. Returns the native SUDS reply object if OK, otherwise None. `_client` can be used to override use of self.client. `_cred` can be used to override use of self.cred. ''' if _client is None: _client = self.client if _cred is None: _cred = self.cred with self.pfx: with Pfx("doWScall.%s(...)", servicename): websvc = getattr(_client.service, servicename) with LogTime(servicename, threshold=3): reply, exc_info = self.wsQueue.submit( partial(websvc, userId=_cred.username, password=_cred.password, *args, **kwargs), name=servicename).wait() if exc_info: error("FAIL, exception in web service call", exc_info=exc_info) return None if reply is None: arglist = listargs(args, kwargs) error("web service called but got None anyway: arglist=(%s)", ", ".join(arglist)) return None if hasattr(reply, 'callStatus'): if reply.callStatus == "OK": info("OK") return reply ... log an error message including relevant stuff from the reply ... return None Anyway, I hope that shows a way to get the main logic clear and the overhead for wrapping specific calls quite lightweight. Cheers, -- Cameron Simpson <c...@zip.com.au> Well, it's one louder, isn't it? It's not ten. You see, most blokes are gonna be playing at ten, you're on ten here, all the way up, all the way up, all the way up, you're on ten on your guitar, where can you go from there? Where? Nowhere, exactly. What we do is, if we need that extra push over the cliff, you know what we do? Eleven. Exactly. One louder. - Nigel Tufnel, _This Is Spinal Tap_ -- http://mail.python.org/mailman/listinfo/python-list