Last active
August 29, 2015 14:24
-
-
Save grafuls/b56522cec980ac4cd79b to your computer and use it in GitHub Desktop.
Virtual method implementation example - some magic to bind an XML-RPC method to an RPC server
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class VirtualMethod(object): | |
# some magic to bind an XML-RPC method to an RPC server. | |
# supports "nested" methods (e.g. examples.getStateName) | |
# supports named arguments (if server does) | |
def __init__(self, func, name): | |
self.__func = func | |
self.__name = name | |
def __getattr__(self, name): | |
return type(self)(self.__func, "%s.%s" % (self.__name, name)) | |
def __call__(self, *args, **opts): | |
return self.__func(self.__name,args,opts) | |
class ClientSession(object): | |
def __init__(self, opts=None): | |
if opts == None: | |
opts = {} | |
else: | |
opts = opts.copy() | |
self.opts = opts | |
self.multicall = False | |
self._calls = [] | |
def __getattr__(self,name): | |
return VirtualMethod(self._callMethod,name) | |
def _callMethod(self, name, args, kwargs=None): | |
"""Make a call to the hub with retries and other niceties""" | |
if self.multicall: | |
if kwargs is None: | |
kwargs = {} | |
args = encode_args(*args, **kwargs) | |
self._calls.append({'methodName': name, 'params': args}) | |
return MultiCallInProgress | |
else: | |
handler, headers, request = self._prepCall(name, args, kwargs) | |
tries = 0 | |
self.retries = 0 | |
debug = self.opts.get('debug',False) | |
max_retries = self.opts.get('max_retries',30) | |
interval = self.opts.get('retry_interval',20) | |
while True: | |
tries += 1 | |
self.retries += 1 | |
try: | |
return self._sendCall(handler, headers, request) | |
#basically, we want to retry on most errors, with a few exceptions | |
# - faults (this means the call completed and failed) | |
# - SystemExit, KeyboardInterrupt | |
# note that, for logged-in sessions the server should tell us (via a RetryError fault) | |
# if the call cannot be retried. For non-logged-in sessions, all calls should be read-only | |
# and hence retryable. | |
except Fault, fault: | |
#try to convert the fault to a known exception | |
err = convertFault(fault) | |
if isinstance(err, ServerOffline): | |
if self.opts.get('offline_retry',False): | |
secs = self.opts.get('offline_retry_interval', interval) | |
self.logger.debug("Server offline. Retrying in %i seconds", secs) | |
time.sleep(secs) | |
#reset try count - this isn't a typical error, this is a running server | |
#correctly reporting an outage | |
tries = 0 | |
continue | |
raise err | |
except (SystemExit, KeyboardInterrupt): | |
#(depending on the python version, these may or may not be subclasses of Exception) | |
raise | |
except Exception, e: | |
if not self.logged_in: | |
#in the past, non-logged-in sessions did not retry. For compatibility purposes | |
#this behavior is governed by the anon_retry opt. | |
if not self.opts.get('anon_retry',False): | |
raise | |
if tries > max_retries: | |
raise | |
#otherwise keep retrying | |
if self.logger.isEnabledFor(logging.DEBUG): | |
tb_str = ''.join(traceback.format_exception(*sys.exc_info())) | |
self.logger.debug(tb_str) | |
self.logger.info("Try #%s for call %s (%s) failed: %s", tries, self.callnum, name, e) | |
if tries > 1: | |
# first retry is immediate, after that we honor retry_interval | |
time.sleep(interval) | |
#not reached |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment