commit 127a245d7e0c3bf96e0f69e31281c13427324567 Author: Christophe de Vienne Date: Sun Sep 18 21:37:40 2011 +0200 A first working implemetation for the core controller code diff --git a/.hgignore b/.hgignore new file mode 100644 index 0000000..2d5b9aa --- /dev/null +++ b/.hgignore @@ -0,0 +1,14 @@ +syntax: glob + +*.pyc +*.pyo +*.egg-info +.coverage + +syntax: regexp + +^lib +^bin +^include +^dist + diff --git a/ews/__init__.py b/ews/__init__.py new file mode 100644 index 0000000..f3a36e8 --- /dev/null +++ b/ews/__init__.py @@ -0,0 +1,2 @@ +from controller import * +from wsgiapp import * diff --git a/ews/controller.py b/ews/controller.py new file mode 100644 index 0000000..04202ba --- /dev/null +++ b/ews/controller.py @@ -0,0 +1,99 @@ +import inspect + +__all__ = ['expose', 'validate', 'WSRoot'] + +registered_protocols = {} + +def scan_api(controller, path=[]): + for name in dir(controller): + if name.startswith('_'): + continue + a = getattr(controller, name) + if hasattr(a, '_ews_definition'): + yield path, a._ews_definition + else: + for i in scan_api(a, path + [name]): + yield i + + +class FunctionArgument(object): + def __init__(self, name, datatype, mandatory, default): + self.name = name + self.datatype = datatype + self.mandatory = mandatory + self.default = default + + +class FunctionDefinition(object): + def __init__(self, name): + self.name = name + self.return_type = None + self.arguments = [] + + @classmethod + def get(cls, func): + fd = getattr(func, '_ews_definition', None) + if fd is None: + fd = FunctionDefinition(func.__name__) + func._ews_definition = fd + return fd + + +def register_protocol(protocol): + global registered_protocols + registered_protocols[protocol.name] = protocol + + +class expose(object): + def __init__(self, return_type=None): + self.return_type = return_type + + def __call__(self, func): + fd = FunctionDefinition.get(func) + fd.return_type = self.return_type + return func + + +class validate(object): + def __init__(self, *args, **kw): + self.param_types = args + + def __call__(self, func): + fd = FunctionDefinition.get(func) + args, varargs, keywords, defaults = inspect.getargspec(func) + print args, defaults + if args[0] == 'self': + args = args[1:] + for i, argname in enumerate(args): + datatype = self.param_types[i] + mandatory = defaults is None or i <= len(defaults) + default = None + if not mandatory: + default = defaults[i-(len(args)-len(defaults))] + print argname, datatype, mandatory, default + fd.arguments.append(FunctionArgument(argname, datatype, + mandatory, default)) + return func + + +class WSRoot(object): + def __init__(self, protocols=None): + if protocols is None: + protocols = registered_protocols.values() + self.protocols = {} + for protocol in protocols: + if isinstance(protocol, str): + protocol = registered_protocols[protocol] + self.protocols[protocol.name] = protocol + + def _handle_request(self, request): + protocol = None + if 'ewsproto' in request.params: + protocol = self.protocols[request.params['ewsproto']] + else: + for p in self.protocols.values(): + if p.accept(self, request): + protocol = p + break + + return protocol.handle(self, request) diff --git a/ews/rest.py b/ews/rest.py new file mode 100644 index 0000000..0089da3 --- /dev/null +++ b/ews/rest.py @@ -0,0 +1,13 @@ +class RestProtocol(object): + name = None + dataformat = None + content_types = [] + + def accept(self, root, request): + if request.path.endswith('.' + self.dataformat): + return True + return request.headers.get('Content-Type') in self.content_types + + def handle(self, root, request): + path = request.path.split('/') + diff --git a/ews/restjson.py b/ews/restjson.py new file mode 100644 index 0000000..c673bca --- /dev/null +++ b/ews/restjson.py @@ -0,0 +1,7 @@ +class RestJsonProtocol(RestProtocol): + name = 'REST+Json' + dataformat = 'json' + content_types = [None, 'application/json', 'text/json'] + + +controller.register_protocol(RestJsonProtocol) diff --git a/ews/restxml.py b/ews/restxml.py new file mode 100644 index 0000000..7c1acd0 --- /dev/null +++ b/ews/restxml.py @@ -0,0 +1,7 @@ +class RestXmlProtocol(RestProtocol): + name = 'REST+XML' + +import controller +controller.register_protocol(RestXmlProtocol) + + diff --git a/ews/soap.py b/ews/soap.py new file mode 100644 index 0000000..aeef20e --- /dev/null +++ b/ews/soap.py @@ -0,0 +1,9 @@ +class SoapProtocol(object): + name = 'soap' + accept = '' + + def __init__(self): + pass + + +controller.register_protocol(SoapProtocol) diff --git a/ews/tests/__init__.py b/ews/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ews/tests/test_controller.py b/ews/tests/test_controller.py new file mode 100644 index 0000000..e5cfd3c --- /dev/null +++ b/ews/tests/test_controller.py @@ -0,0 +1,110 @@ +import unittest +import webob +from webob.dec import wsgify +import webtest + +from ews import * +from ews.controller import scan_api + +class DummyProtocol(object): + name = 'dummy' + + def __init__(self): + self.hits = 0 + + def accept(self, root, req): + return True + + def handle(self, root, req): + self.lastreq = req + self.lastroot = root + res = webob.Response() + self.hits += 1 + return res + + +def serve_ws(req, root): + return root._handle_request(req) + + +class TestController(unittest.TestCase): + def test_expose(self): + class MyWS(WSRoot): + @expose(int) + def getint(self): + return 1 + + assert MyWS.getint._ews_definition.return_type == int + + def test_validate(self): + class MyWS(object): + @expose(int) + @validate(int, int, int) + def add(self, a, b, c=0): + return a + b + c + + args = MyWS.add._ews_definition.arguments + + assert args[0].name == 'a' + assert args[0].datatype == int + assert args[0].mandatory + assert args[0].default is None + + assert args[1].name == 'b' + assert args[1].datatype == int + assert args[1].mandatory + assert args[1].default is None + + assert args[2].name == 'c' + assert args[2].datatype == int + assert not args[2].mandatory + assert args[2].default == 0 + + def test_register_protocol(self): + p = DummyProtocol() + import ews.controller + ews.controller.register_protocol(p) + assert ews.controller.registered_protocols['dummy'] == p + + r = WSRoot() + assert r.protocols['dummy'] + + def test_scan_api(self): + class NS(object): + @expose(int) + @validate(int, int) + def multiply(self, a, b): + return a * b + + class MyRoot(WSRoot): + ns = NS() + + r = MyRoot() + + api = [i for i in scan_api(r)] + assert len(api) == 1 + assert api[0][0] == ['ns'] + fd = api[0][1] + assert fd.name == 'multiply' + + def test_handle_request(self): + class MyRoot(WSRoot): + pass + + p = DummyProtocol() + r = MyRoot(protocols=[p]) + + app = webtest.TestApp( + wsgify(r._handle_request)) + + res = app.get('/') + + assert p.lastreq.path == '/' + assert p.lastroot == r + assert p.hits == 1 + + res = app.get('/?ewsproto=dummy') + + assert p.lastreq.path == '/' + assert p.lastroot == r + assert p.hits == 2 diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..b3b23b7 --- /dev/null +++ b/setup.py @@ -0,0 +1,7 @@ +from setuptools import setup + +setup( + name='EWS', + packages=['ews'], + install_requires=['webob'], +)