diff --git a/clearest/core.py b/clearest/core.py index d5d9a0b..3ac63ac 100644 --- a/clearest/core.py +++ b/clearest/core.py @@ -6,14 +6,14 @@ from copy import deepcopy try: - from urllib.parse import urlparse + from urllib.parse import parse_qs except ImportError: - from urlparse import urlparse + from urlparse import parse_qs import six from clearest.exceptions import MissingArgumentError, AlreadyRegisteredError, NotUniqueError, HttpError, \ - HttpNotFound + HttpNotFound, NotRootError from clearest.http import HTTP_GET, HTTP_POST, CONTENT_TYPE, MIME_TEXT_PLAIN, HTTP_OK from clearest.wsgi import REQUEST_METHOD, PATH_INFO, QUERY_STRING @@ -36,7 +36,9 @@ def __hash__(self): def parse_path(path): if path is None: raise TypeError - parts = path[1:].split("/") if path.startswith("/") else path.split("/") # meh + elif not path.startswith("/"): + raise NotRootError(path) + parts = path[1:].split("/") for index, part in enumerate(parts): found = KEY_PATTERN.match(part) if found: @@ -79,14 +81,22 @@ def is_matching(signature, args, path, query): return True +def parse_args(args, path, query): + kwargs = {} + for arg, parse_fn in six.iteritems(args): + if parse_fn is None: + kwargs[arg] = query[arg][0] if len(query[arg]) == 1 else query[arg] + return kwargs + + def application(environ, start_response): try: if environ[REQUEST_METHOD] in all_registered(): - path = tuple(environ[PATH_INFO].split("/")) - query = urlparse.parse_qs(environ[QUERY_STRING]) if QUERY_STRING in environ else tuple() + path = tuple(environ[PATH_INFO][1:].split("/")) + query = parse_qs(environ[QUERY_STRING]) if QUERY_STRING in environ else tuple() for signature, (fn, args) in six.iteritems(BaseDecorator.registered[environ[REQUEST_METHOD]]): if is_matching(signature, args, path, query): - result = fn() + result = fn(**parse_args(args, path, query)) start_response(STATUS_FMT.format(**HTTP_OK._asdict()), [(CONTENT_TYPE, MIME_TEXT_PLAIN)]) return [result] @@ -110,7 +120,9 @@ def __call__(self, fn): @functools.wraps(fn) def wrapped(*args, **kwargs): result = fn(*args, **kwargs) - wrapped.__dict__ = deepcopy(fn.__dict__) + wrapped.__dict__ = deepcopy(fn.__dict__["__wrapped__"].__dict__ if + "__wrapped__" in fn.__dict__ else + fn.__dict__) return result registered = BaseDecorator.registered[self.type()] diff --git a/clearest/exceptions.py b/clearest/exceptions.py index d5cd6e0..52e003a 100644 --- a/clearest/exceptions.py +++ b/clearest/exceptions.py @@ -18,6 +18,11 @@ def __init__(self, var_name): super(NotUniqueError, self).__init__("variable {var} is not unique".format(var=var_name)) +class NotRootError(Exception): + def __init__(self, path): + super(NotRootError, self).__init__("path is not it the root (change it to /{path})".format(path=path)) + + class HttpError(Exception): def __init__(self, code, msg): super(HttpError, self).__init__() diff --git a/requirements.txt b/requirements.txt index 7a197db..0457bd3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1,2 @@ -six==1.10.0 \ No newline at end of file +six==1.10.0 +decorator==4.0.9 \ No newline at end of file diff --git a/tests/test_application.py b/tests/test_application.py index bb3ba2c..3904fff 100644 --- a/tests/test_application.py +++ b/tests/test_application.py @@ -33,6 +33,16 @@ def test_application_simple_query(self): def asd(): return {} - self.get("asd") + self.get("/asd") + self.assertEqual(HTTP_OK, self.status) self.assertEqual(((), {}), asd.called_with) + + def test_application_simple_var(self): + @GET("/asd") + @called_with + def asd(a): + return {} + + self.get("/asd?a=hi") self.assertEqual(HTTP_OK, self.status) + self.assertEqual((("hi",), {}), asd.called_with) diff --git a/tests/test_exceptions.py b/tests/test_exceptions.py index 3277f6c..e8e2e79 100644 --- a/tests/test_exceptions.py +++ b/tests/test_exceptions.py @@ -1,6 +1,6 @@ from unittest import TestCase -from clearest import GET, MissingArgumentError, AlreadyRegisteredError, NotUniqueError, unregister_all +from clearest import GET, MissingArgumentError, AlreadyRegisteredError, NotUniqueError, unregister_all, NotRootError class Test(TestCase): @@ -58,3 +58,11 @@ def asd(a, b): pass self.assertRaises(NotUniqueError, test_fn) + + def test_not_root(self): + def test_fn(): + @GET("asd") + def asd(a, b): + pass + + self.assertRaises(NotRootError, test_fn) diff --git a/tests/util.py b/tests/util.py index 73f09b4..44184fd 100644 --- a/tests/util.py +++ b/tests/util.py @@ -1,10 +1,8 @@ -import functools +from decorator import decorator -def called_with(fn): - @functools.wraps(fn) - def wrapped(*args, **kwargs): - wrapped.called_with = args, kwargs - return fn(*args, **kwargs) - - return wrapped +@decorator +def called_with(fn, *args, **kwargs): + result = fn(*args, **kwargs) + fn.called_with = args, kwargs + return result