From 86a2c529b9adf3ab050985aef7fe0c51462985a2 Mon Sep 17 00:00:00 2001 From: BrenBarn Date: Tue, 31 Jan 2017 14:13:43 -0800 Subject: [PATCH] Change to modify Service instead of adding FlexibleService --- gluon/tools.py | 415 ++----------------------------------------------- 1 file changed, 17 insertions(+), 398 deletions(-) diff --git a/gluon/tools.py b/gluon/tools.py index 67fd5ac1..331fa713 100644 --- a/gluon/tools.py +++ b/gluon/tools.py @@ -5386,7 +5386,9 @@ def universal_caller(f, *a, **b): class Service(object): - def __init__(self, environment=None): + def __init__(self, environment=None, check_args=False): + self.check_args = check_args + self.run_procedures = {} self.csv_procedures = {} self.xml_procedures = {} @@ -5436,7 +5438,7 @@ class Service(object): wget http://..../app/default/call/csv/myfunction?a=3&b=4 """ - self.run_procedures[f.__name__] = f + self.csv_procedures[f.__name__] = f return f def xml(self, f): @@ -5456,7 +5458,7 @@ class Service(object): wget http://..../app/default/call/xml/myfunction?a=3&b=4 """ - self.run_procedures[f.__name__] = f + self.xml_procedures[f.__name__] = f return f def rss(self, f): @@ -5643,7 +5645,7 @@ class Service(object): if not args: args = request.args if args and args[0] in self.run_procedures: - return str(universal_caller(self.run_procedures[args[0]], + return str(self.call_service_function(self.run_procedures[args[0]], *args[1:], **dict(request.vars))) self.error() @@ -5662,9 +5664,9 @@ class Service(object): if value is None: return '' return value - if args and args[0] in self.run_procedures: + if args and args[0] in self.csv_procedures: import types - r = universal_caller(self.run_procedures[args[0]], + r = self.call_service_function(self.csv_procedures[args[0]], *args[1:], **dict(request.vars)) s = StringIO() if hasattr(r, 'export_to_csv_file'): @@ -5690,8 +5692,8 @@ class Service(object): response.headers['Content-Type'] = 'text/xml' if not args: args = request.args - if args and args[0] in self.run_procedures: - s = universal_caller(self.run_procedures[args[0]], + if args and args[0] in self.xml_procedures: + s = self.call_service_function(self.xml_procedures[args[0]], *args[1:], **dict(request.vars)) if hasattr(s, 'as_list'): s = s.as_list() @@ -5704,7 +5706,7 @@ class Service(object): if not args: args = request.args if args and args[0] in self.rss_procedures: - feed = universal_caller(self.rss_procedures[args[0]], + feed = self.call_service_function(self.rss_procedures[args[0]], *args[1:], **dict(request.vars)) else: self.error() @@ -5719,7 +5721,7 @@ class Service(object): args = request.args d = dict(request.vars) if args and args[0] in self.json_procedures: - s = universal_caller(self.json_procedures[args[0]], *args[1:], **d) + s = self.call_service_function(self.json_procedures[args[0]], *args[1:], **d) if hasattr(s, 'as_list'): s = s.as_list() return response.json(s) @@ -6052,398 +6054,15 @@ class Service(object): def error(self): raise HTTP(404, "Object does not exist") - -class FlexibleService(Service): - # __init__ inherited - # we override csv and xml to fix a bug that caused these procedures to be stored under run_procedures instead of csv_procedures or xml_procedures - # other service decorators (run, json, etc.) are inherited - - def csv(self, f): - """ - Example: - Use as:: - - service = Service() - @service.csv - def myfunction(a, b): - return a + b - def call(): - return service() - - Then call it with:: - - wget http://..../app/default/call/csv/myfunction?a=3&b=4 - - """ - self.csv_procedures[f.__name__] = f - return f - - # fix bug where stuff was put in run_procedures instead of xml_procedures - def xml(self, f): - """ - Example: - Use as:: - - service = Service() - @service.xml - def myfunction(a, b): - return a + b - def call(): - return service() - - Then call it with:: - - wget http://..../app/default/call/xml/myfunction?a=3&b=4 - - """ - self.xml_procedures[f.__name__] = f - return f - - def serve_run(self, args=None): - request = current.request - if not args: - args = request.args - if args and args[0] in self.run_procedures: - return str(self.call_service_function(self.run_procedures[args[0]], - *args[1:], **dict(request.vars))) - self.error() - - def serve_csv(self, args=None): - request = current.request - response = current.response - response.headers['Content-Type'] = 'text/x-csv' - if not args: - args = request.args - - def none_exception(value): - if isinstance(value, unicodeT): - return value.encode('utf8') - if hasattr(value, 'isoformat'): - return value.isoformat()[:19].replace('T', ' ') - if value is None: - return '' - return value - if args and args[0] in self.csv_procedures: - import types - r = self.call_service_function(self.csv_procedures[args[0]], - *args[1:], **dict(request.vars)) - s = StringIO() - if hasattr(r, 'export_to_csv_file'): - r.export_to_csv_file(s) - elif r and not isinstance(r, types.GeneratorType) and isinstance(r[0], (dict, Storage)): - import csv - writer = csv.writer(s) - writer.writerow(r[0].keys()) - for line in r: - writer.writerow([none_exception(v) - for v in line.values()]) - else: - import csv - writer = csv.writer(s) - for line in r: - writer.writerow(line) - return s.getvalue() - self.error() - - def serve_xml(self, args=None): - request = current.request - response = current.response - response.headers['Content-Type'] = 'text/xml' - if not args: - args = request.args - if args and args[0] in self.xml_procedures: - s = self.call_service_function(self.xml_procedures[args[0]], - *args[1:], **dict(request.vars)) - if hasattr(s, 'as_list'): - s = s.as_list() - return serializers.xml(s, quote=False) - self.error() - - def serve_rss(self, args=None): - request = current.request - response = current.response - if not args: - args = request.args - if args and args[0] in self.rss_procedures: - feed = self.call_service_function(self.rss_procedures[args[0]], - *args[1:], **dict(request.vars)) - else: - self.error() - response.headers['Content-Type'] = 'application/rss+xml' - return serializers.rss(feed) - - def serve_json(self, args=None): - request = current.request - response = current.response - response.headers['Content-Type'] = 'application/json; charset=utf-8' - if not args: - args = request.args - d = dict(request.vars) - if args and args[0] in self.json_procedures: - s = self.call_service_function(self.json_procedures[args[0]], *args[1:], **d) - if hasattr(s, 'as_list'): - s = s.as_list() - return response.json(s) - self.error() - - def serve_jsonrpc(self): - def return_response(id, result): - return serializers.json({'version': '1.1', 'id': id, 'result': result, 'error': None}) - - def return_error(id, code, message, data=None): - error = {'name': 'JSONRPCError', - 'code': code, 'message': message} - if data is not None: - error['data'] = data - return serializers.json({'id': id, - 'version': '1.1', - 'error': error, - }) - - request = current.request - response = current.response - response.headers['Content-Type'] = 'application/json; charset=utf-8' - methods = self.jsonrpc_procedures - data = json.loads(request.body.read()) - jsonrpc_2 = data.get('jsonrpc') - if jsonrpc_2: # hand over to version 2 of the protocol - return self.serve_jsonrpc2(data) - id, method, params = data.get('id'), data.get('method'), data.get('params', []) - if id is None: - return return_error(0, 100, 'missing id') - if method not in methods: - return return_error(id, 100, 'method "%s" does not exist' % method) - try: - if isinstance(params, dict): - s = methods[method](**params) - else: - s = methods[method](*params) - if hasattr(s, 'as_list'): - s = s.as_list() - return return_response(id, s) - except Service.JsonRpcException as e: - return return_error(id, e.code, e.info) - except: - etype, eval, etb = sys.exc_info() - message = '%s: %s' % (etype.__name__, eval) - data = request.is_local and traceback.format_tb(etb) - logger.warning('jsonrpc exception %s\n%s' % (message, traceback.format_tb(etb))) - return return_error(id, 100, message, data) - - def serve_jsonrpc2(self, data=None, batch_element=False): - - def return_response(id, result): - if not must_respond: - return None - return serializers.json({'jsonrpc': '2.0', 'id': id, 'result': result}) - - def return_error(id, code, message=None, data=None): - error = {'code': code} - if code in Service.jsonrpc_errors: - error['message'] = Service.jsonrpc_errors[code][0] - error['data'] = Service.jsonrpc_errors[code][1] - if message is not None: - error['message'] = message - if data is not None: - error['data'] = data - return serializers.json({'jsonrpc': '2.0', 'id': id, 'error': error}) - - def validate(data): - """ - Validate request as defined in: http://www.jsonrpc.org/specification#request_object. - - Args: - data(str): The json object. - - Returns: - - True -- if successful - - False -- if no error should be reported (i.e. data is missing 'id' member) - - Raises: - JsonRPCException - - """ - - iparms = set(data.keys()) - mandatory_args = set(['jsonrpc', 'method']) - missing_args = mandatory_args - iparms - - if missing_args: - raise Service.JsonRpcException(-32600, 'Missing arguments %s.' % list(missing_args)) - if data['jsonrpc'] != '2.0': - raise Service.JsonRpcException(-32603, 'Unsupported jsonrpc version "%s"' % data['jsonrpc']) - if 'id' not in iparms: - return False - - return True - - request = current.request - response = current.response - if not data: - response.headers['Content-Type'] = 'application/json; charset=utf-8' - try: - data = json.loads(request.body.read()) - except ValueError: # decoding error in json lib - return return_error(None, -32700) - - # Batch handling - if isinstance(data, list) and not batch_element: - retlist = [] - for c in data: - retstr = self.serve_jsonrpc2(c, batch_element=True) - if retstr: # do not add empty responses - retlist.append(retstr) - if len(retlist) == 0: # return nothing - return '' - else: - return "[" + ','.join(retlist) + "]" - methods = self.jsonrpc2_procedures - methods.update(self.jsonrpc_procedures) - - try: - must_respond = validate(data) - except Service.JsonRpcException as e: - return return_error(None, e.code, e.info) - - id, method, params = data.get('id'), data['method'], data.get('params', '') - if method not in methods: - return return_error(id, -32601, data='Method "%s" does not exist' % method) - try: - if isinstance(params, dict): - s = methods[method](**params) - else: - s = methods[method](*params) - if hasattr(s, 'as_list'): - s = s.as_list() - if must_respond: - return return_response(id, s) - else: - return '' - except HTTP as e: - raise e - except Service.JsonRpcException as e: - return return_error(id, e.code, e.info) - except: - etype, eval, etb = sys.exc_info() - data = '%s: %s\n' % (etype.__name__, eval) + str(request.is_local and traceback.format_tb(etb)) - logger.warning('%s: %s\n%s' % (etype.__name__, eval, traceback.format_tb(etb))) - return return_error(id, -32099, data=data) - - def serve_xmlrpc(self): - request = current.request - response = current.response - services = self.xmlrpc_procedures.values() - return response.xmlrpc(request, services) - - def serve_amfrpc(self, version=0): - try: - import pyamf - import pyamf.remoting.gateway - except: - return "pyamf not installed or not in Python sys.path" - request = current.request - response = current.response - if version == 3: - services = self.amfrpc3_procedures - base_gateway = pyamf.remoting.gateway.BaseGateway(services) - pyamf_request = pyamf.remoting.decode(request.body) - else: - services = self.amfrpc_procedures - base_gateway = pyamf.remoting.gateway.BaseGateway(services) - context = pyamf.get_context(pyamf.AMF0) - pyamf_request = pyamf.remoting.decode(request.body, context) - pyamf_response = pyamf.remoting.Envelope(pyamf_request.amfVersion) - for name, message in pyamf_request: - pyamf_response[name] = base_gateway.getProcessor(message)(message) - response.headers['Content-Type'] = pyamf.remoting.CONTENT_TYPE - if version == 3: - return pyamf.remoting.encode(pyamf_response).getvalue() - else: - return pyamf.remoting.encode(pyamf_response, context).getvalue() - - def serve_soap(self, version="1.1"): - try: - from gluon.contrib.pysimplesoap.server import SoapDispatcher - except: - return "pysimplesoap not installed in contrib" - request = current.request - response = current.response - procedures = self.soap_procedures - - location = "%s://%s%s" % (request.env.wsgi_url_scheme, - request.env.http_host, - URL(r=request, f="call/soap", vars={})) - namespace = 'namespace' in response and response.namespace or location - documentation = response.description or '' - dispatcher = SoapDispatcher( - name=response.title, - location=location, - action=location, # SOAPAction - namespace=namespace, - prefix='pys', - documentation=documentation, - ns=True) - for method, (function, returns, args, doc) in iteritems(procedures): - dispatcher.register_function(method, function, returns, args, doc) - if request.env.request_method == 'POST': - fault = {} - # Process normal Soap Operation - response.headers['Content-Type'] = 'text/xml' - xml = dispatcher.dispatch(request.body.read(), fault=fault) - if fault: - # May want to consider populating a ticket here... - response.status = 500 - # return the soap response - return xml - elif 'WSDL' in request.vars: - # Return Web Service Description - response.headers['Content-Type'] = 'text/xml' - return dispatcher.wsdl() - elif 'op' in request.vars: - # Return method help webpage - response.headers['Content-Type'] = 'text/html' - method = request.vars['op'] - sample_req_xml, sample_res_xml, doc = dispatcher.help(method) - body = [H1("Welcome to Web2Py SOAP webservice gateway"), - A("See all webservice operations", - _href=URL(r=request, f="call/soap", vars={})), - H2(method), - P(doc), - UL(LI("Location: %s" % dispatcher.location), - LI("Namespace: %s" % dispatcher.namespace), - LI("SoapAction: %s" % dispatcher.action), - ), - H3("Sample SOAP XML Request Message:"), - CODE(sample_req_xml, language="xml"), - H3("Sample SOAP XML Response Message:"), - CODE(sample_res_xml, language="xml"), - ] - return {'body': body} - else: - # Return general help and method list webpage - response.headers['Content-Type'] = 'text/html' - body = [H1("Welcome to Web2Py SOAP webservice gateway"), - P(response.description), - P("The following operations are available"), - A("See WSDL for webservice description", - _href=URL(r=request, f="call/soap", vars={"WSDL": None})), - UL([LI(A("%s: %s" % (method, doc or ''), - _href=URL(r=request, f="call/soap", vars={'op': method}))) - for method, doc in dispatcher.list_methods()]), - ] - return {'body': body} - - # inherited __call__ method - - # inherited error() method - # we make this a method so that subclasses can override it if they want to do more specific argument-checking # but the default implmentation is the simplest: just pass the arguments we got, with no checking def call_service_function(self, f, *a, **b): - return f(*a, **b) - - + if self.check_args: + return universal_caller(f, *a, **b) + else: + return f(*a, **b) + def completion(callback): """ Executes a task on completion of the called action.