diff --git a/rocket/http_handling.py b/rocket/http_handling.py index 1c389cb..e781508 100644 --- a/rocket/http_handling.py +++ b/rocket/http_handling.py @@ -23,6 +23,19 @@ def __init__(self, code, msg, *args, **kwargs): def __str__(self): return '%s - (code: %s)' % (self.msg, self.code) +class AdvancedRequest(urllib2.Request): + """ + Advanced urllib2 request with custom http method. + """ + + def __init__(self, url, method, data={}, headers={}, + origin_req_host=None, unverifiable=False): + self._method = method + urllib2.Request.__init__(self, url, data, headers, + origin_req_host, unverifiable) + + def get_method(self): + return self._method or urllib2.Request.get_method(self) ######################################## # URL handling ######################### @@ -49,12 +62,12 @@ def urlread(url, data=None, headers={}, method=DEFAULT_REQUEST_METHOD, # encoded_args = unicode_urlencode(encoded_args) - if method == 'GET': + if method in ('GET', 'DELETE'): if data is not None: url = '%s?%s' % (url, data) logger.debug("Make %s connection to %s" % ( method, data ) ) - data = None - + data = None + if basic_auth_pair: auth_handler = urllib2.HTTPBasicAuthHandler() auth_handler.add_password(realm=basic_auth_realm, @@ -65,19 +78,21 @@ def urlread(url, data=None, headers={}, method=DEFAULT_REQUEST_METHOD, # ...and install it globally so it can be used with urlopen. urllib2.install_opener(opener) - request = urllib2.Request(url, data) + request = AdvancedRequest(url, method, data) + try: open_req = urllib2.urlopen(request) # commenting out the code the commented comment comments on #if method == 'POST': # req.add_header('Content-type', "application/x-www-form-urlencoded") # req.add_header('Accept', "text/plain") - open_req.http_method = method + #open_req.http_method = method except urllib2.HTTPError, http_e: logger.error("HTTP error: %s" % http_e) raise RocketAPIException( http_e.code, http_e ) except urllib2.URLError,e: logger.warn("Connection error %s" % e) + # TODO: RocketAPIError is underfined here. raise RocketAPIError(None, e ) except Exception,e: logger.critical("uknown error %s" % e ) diff --git a/rocket/rocket.py b/rocket/rocket.py index d213b15..0aa84de 100644 --- a/rocket/rocket.py +++ b/rocket/rocket.py @@ -19,6 +19,7 @@ import mimetypes import logging +from collections import defaultdict import proxies from auth import sign_args @@ -72,13 +73,35 @@ def logging_context(log_stream=sys.stdout, log_level=logging.INFO): # set stream from user, user sys.stdout as rocket default sh = logging.StreamHandler( log_stream ) # set the log level requested by user, default is logging.info - sh.setLevel( log_level ) - formatter = logging.Formatter("%(asctime)s %(process)d %(filename)s %(lineno)d %(levelname)s #rocket| %(message)s") + sh.setLevel(log_level) + formatter = logging.Formatter("%(asctime)s %(process)d %(filename)s" \ + "%(lineno)d %(levelname)s #rocket| %(message)s") sh.setFormatter(formatter) logger.addHandler(sh) return logger +######################################### +# Events ################################ +######################################### + +class Event(object): + + def __init__(self, query_url, secure, ns_fun, method, data): + self.query_url = query_url + self.secure = secure + self.data = data + self.ns_fun = ns_fun + self.method = method + self.succeed = None + + def success(self, response): + self.succeed = True + self.response = response + + def fail(self, exception): + self.succeed = False + self.result = exception ######################################### # The Rocket ############################ @@ -118,6 +141,7 @@ def __init__(self, function_list, self.gen_namespace_pair = gen_namespace_pair self._log_level=log_level self._log_stream=log_stream + self._callbacks = defaultdict(lambda : defaultdict(set)) logger = logging_context(log_stream=log_stream, log_level=log_level) logger.debug("Create rocket: url: %s client %s" % (api_url,client)) @@ -131,6 +155,7 @@ def __init__(self, function_list, for namespace in self.function_list: (ns_name, ns_title) = self.gen_namespace_pair(namespace) proxy_class = rocket_proxies['%sProxy' % ns_title] + # TODO: setattr? self.__dict__[ns_name] = proxy_class(self, '%s.%s' % (client, ns_name)) self.namespace_map[ns_name] = namespace @@ -139,7 +164,8 @@ def __init__(self, function_list, def _expand_arguments(self, args): """Expands arguments from native type to web friendly type """ - logger = logging_context(log_stream=self._log_stream, log_level=self._log_level) + logger = logging_context(log_stream=self._log_stream, + log_level=self._log_level) logger.debug("rocket _expand_arguments %s" % args ) for arg in args.items(): @@ -156,7 +182,8 @@ def _parse_response(self, response, method): """Parses the response according to the given (optional) format, which should be 'json'. """ - logger = logging_context(log_stream=self._log_stream, log_level=self._log_level) + logger = logging_context(log_stream=self._log_stream, + log_level=self._log_level) logger.debug("rocket _parse_response, response=%s and method=%s" % (response, method) ) @@ -202,30 +229,68 @@ def __call__(self, function_key=None, args=None, secure=False): format=self.format, method=method, get_args=args) + event = Event(query_url, secure, ns_fun, method, args) - if self.web_proxy: - web_proxy_handler = urllib2.ProxyHandler(self.web_proxy) - opener = urllib2.build_opener(web_proxy_handler) - response = opener.open(query_url).read() - else: - response = http_handling.urlread(query_url, data=args, - method=method.upper(), - basic_auth_pair=self.basic_auth_pair, - basic_auth_realm=self.basic_auth_realm, - logger=logging_context()) - - if response: - return self._parse_response(response, method) + try: + if self.web_proxy: + web_proxy_handler = urllib2.ProxyHandler(self.web_proxy) + opener = urllib2.build_opener(web_proxy_handler) + response = opener.open(query_url).read() else: - return None - - return response + response = http_handling.urlread(query_url, data=args, + method=method.upper(), + basic_auth_pair=self.basic_auth_pair, + basic_auth_realm=self.basic_auth_realm, + logger=logging_context()) + + if response: + response = self._parse_response(response, method) + else: + response = None + except Exception, ex_inst: + event.fail(ex_inst) + raise + else: + event.success(response) + finally: + self.fire_callbacks(event) + return response ######################################## # Callbacks ############################ ######################################## + def add_callback(self, method, ns_fun, callback): + self._callbacks[method][ns_fun].add(callback) + + def add_callbacks(self, methods, ns_funs, callback): + for method in methods: + for ns_fun in ns_funs: + self.add_callback(method, ns_fun, callback) + + def remove_callback(self, method, ns_fun, callback): + self._callbacks[method][ns_fun].remove(callback) + + def remove_callbacks(self, methods, ns_funs, callback): + for method in methods: + for ns_fun in ns_funs: + self.remove_callback(method, ns_fun, callback) + + def fire_callbacks(self, event): + + callbacks_collections = (self._callbacks[event.method][event.ns_fun], + self._callbacks[event.method][None], + self._callbacks[None][None], + ) + + for callbacks in callbacks_collections: + for callback in callbacks: + callback(event) + + if callbacks: # stop fire a callback on a first not empty stack + break + def check_error(self, response): """Some API's transmit errors over successful HTTP connections, eg. 200. @@ -234,7 +299,8 @@ def check_error(self, response): pass - def gen_query_url(self, url, function, format=None, method=None, get_args=None): + def gen_query_url(self, url, function, format=None, method=None, + get_args=None): """Generates URL for request according to structure of IDL. Implementation formats worth considering: