Esempio n. 1
0
    def test_with_variables(self):
        m = Map([
            URITemplateRule('/', endpoint='root'),
            URITemplateRule('/browse/', endpoint='entry'),
            URITemplateRule('/browse/{id}/', endpoint='entry/id'),
            # this has to explicitly consume the final '/' path fragment
            URITemplateRule('/browse/{id}{/path*}/', endpoint='entry/id/dir'),
            # before the fully consumed version
            URITemplateRule('/browse/{id}{/path*}', endpoint='entry/id/path'),
        ]).bind('example.com')
        self.assertEqual(('root', {}), m.match('/'))
        # TODO allow this type of redirects to work?
        # self.assertEqual(('entry', {}), m.match('/browse'))

        self.assertEqual(('entry', {}), m.match('/browse/'))
        self.assertEqual(('entry/id', {
            'id': '1',
        }), m.match('/browse/1/'))  # ditto for /browse/1

        # however, if we have this kind of distinction...
        self.assertEqual(('entry/id/path', {
            'id': '1',
            'path': ['some', 'nested', 'path'],
        }), m.match('/browse/1/some/nested/path'))

        # ... this forces endpoint implementation to be explicit about
        # how they handle the paths passed.
        self.assertEqual(('entry/id/dir', {
            'id': '1',
            'path': ['some', 'nested', 'path'],
        }), m.match('/browse/1/some/nested/path/'))
Esempio n. 2
0
 def test_precedence(self):
     m = Map([
         URITemplateRule('/entry/{id}', endpoint='entry'),
         URITemplateRule('/entry/index', endpoint='entry_index'),
     ]).bind('example.com')
     self.assertEqual(('entry', {'id': '1'}), m.match('/entry/1'))
     self.assertEqual(('entry_index', {}), m.match('/entry/index'))
Esempio n. 3
0
    def test_basic_creation_from_str(self):
        m = Map([
            URITemplateRule('/', endpoint='root'),
            URITemplateRule('/somewhere', endpoint='elsewhere'),
        ]).bind('example.com')
        self.assertEqual(('root', {}), m.match('/'))
        self.assertEqual(('elsewhere', {}), m.match('/somewhere'))

        with self.assertRaises(NotFound):
            m.match('/nowhere')
Esempio n. 4
0
def test_pattern(path, pattern):
    pattern = '/' + pattern.strip('/') + '/<path:extra>'
    adapter = Map([Rule(pattern)]).bind('dummy.invalid')
    try:
        endpoint, values = adapter.match(path.strip('/'))
    except NotFound:
        return
    return values['year'], values['month'], values['day']
Esempio n. 5
0
def test_pattern(path, pattern):
    pattern = "/" + pattern.strip("/") + "/<path:extra>"
    adapter = Map([Rule(pattern)]).bind("dummy.invalid")
    try:
        endpoint, values = adapter.match(path.strip("/"))
    except NotFound:
        return
    return values["year"], values["month"], values["day"]
Esempio n. 6
0
def test_pattern(path, pattern):
    pattern = '/' + pattern.strip('/') + '/<path:extra>'
    adapter = Map([Rule(pattern)]).bind('dummy.invalid')
    try:
        endpoint, values = adapter.match(path.strip('/'))
    except NotFound:
        return
    return values['year'], values['month'], values['day']
Esempio n. 7
0
 def test_with_variables_without_trailing_slash(self):
     m = Map([
         URITemplateRule('/', endpoint='root'),
         URITemplateRule('/browse/', endpoint='entry'),
         URITemplateRule('/browse/{id}/', endpoint='entry/id'),
         URITemplateRule('/browse/{id}{/path*}', endpoint='entry/id/path'),
     ]).bind('example.com')
     # unlike the previous where there is a "...{/path*}/" version
     # defined, this will simply be fed into the other view, with
     # path list containing a trailing empty string path fragment.
     self.assertEqual(('entry/id/path', {
         'id': '1',
         'path': ['some', 'nested', 'path', ''],
     }), m.match('/browse/1/some/nested/path/'))
Esempio n. 8
0
def match_endpoint(path: str) -> tuple[models.Endpoint, Mapping[str, Any]]:
    """
    Uses werkzeug routing to match the given path to an endpoint.
    Returns the matched endpoint as well as a mapping of URL parameters passed to the endpoint.
    If no endpoint was matched, raises a NotFound exception.
    """

    log.debug(f"Matching path `{path}`")

    endpoints: list[models.Endpoint] = models.Endpoint.query.filter(
        models.Endpoint.method == request.method.upper()).all()

    rules = [
        Rule(endpoint.path, endpoint=endpoint, methods=[request.method])
        for endpoint in endpoints
    ]
    urls = Map(rules).bind("localhost")

    # we're disabling mypy here because you're supposed to get strings back from `match`, not full endpoint objects.
    return urls.match(path, method=request.method)  # type: ignore
Esempio n. 9
0
class Router(object):
    converters = {
        str: 'string',
        int: 'int',
        float: 'float',
        # path, any, uuid
    }
    not_found = None
    method_not_allowed = None

    def __init__(self, routes: List[Route]):
        required_type = wsgi.WSGIResponse
        initial_types = [wsgi.WSGIEnviron, URLPathArgs]

        rules = []
        views = {}

        for (path, method, view) in routes:
            view_signature = inspect.signature(view)
            uritemplate = URITemplate(path)

            # Ensure view arguments include all URL arguments
            for arg in uritemplate.variable_names:
                assert arg in view_signature.parameters, (
                    'URL argument "%s" in path "%s" must be included as a '
                    'keyword argument in the view function "%s"' %
                    (arg, path, view.__name__))

            # Create a werkzeug path string
            werkzeug_path = path[:]
            for arg in uritemplate.variable_names:
                param = view_signature.parameters[arg]
                if param.annotation == inspect.Signature.empty:
                    annotated_type = str
                else:
                    annotated_type = param.annotation
                converter = self.converters[annotated_type]
                werkzeug_path = werkzeug_path.replace(
                    '{%s}' % arg, '<%s:%s>' % (converter, arg))

            # Create a werkzeug routing rule
            name = view.__name__
            rule = Rule(werkzeug_path, methods=[method], endpoint=name)
            rules.append(rule)

            # Determine any inferred type annotations for the view
            extra_annotations = {}
            for param in view_signature.parameters.values():

                if param.annotation == inspect.Signature.empty:
                    annotated_type = str
                else:
                    annotated_type = param.annotation

                if param.name in uritemplate.variable_names:

                    class TypedURLPathArg(URLPathArg):
                        schema = annotated_type

                    extra_annotations[param.name] = TypedURLPathArg
                elif (annotated_type in primitive_types) or issubclass(
                        annotated_type, schema_types):

                    class TypedQueryParam(http.QueryParam):
                        schema = annotated_type

                    extra_annotations[param.name] = TypedQueryParam

            if 'return' not in view.__annotations__:
                extra_annotations['return'] = http.ResponseData

            # Determine the pipeline for the view.
            pipeline = pipelines.build_pipeline(view, initial_types,
                                                required_type,
                                                extra_annotations)
            views[name] = Endpoint(view, pipeline)

        # Add pipelines for 404 and 405 cases.
        pipeline = pipelines.build_pipeline(view_404, initial_types,
                                            required_type, {})
        self.not_found = (None, pipeline, {})
        pipeline = pipelines.build_pipeline(view_405, initial_types,
                                            required_type, {})
        self.method_not_allowed = (None, pipeline, {})

        self.routes = routes
        self.adapter = Map(rules).bind('example.com')
        self.views = views

    def lookup(self, path, method):
        try:
            (name, kwargs) = self.adapter.match(path, method)
        except werkzeug.exceptions.NotFound:
            return self.not_found
        except werkzeug.exceptions.MethodNotAllowed:
            return self.method_not_allowed
        (view, pipeline) = self.views[name]
        return (view, pipeline, kwargs)
Esempio n. 10
0
class Router(BaseRouter):

    def __init__(self, routes):
        rules = []
        name_lookups = {}

        for path, name, route in self.walk_routes(routes):
            path_params = [
                item.strip('{}') for item in re.findall('{[^}]*}', path)
            ]
            args = inspect.signature(route.handler).parameters
            for path_param in path_params:
                if path_param.startswith('+'):
                    path = path.replace(
                        '{%s}' % path_param,
                        "<path:%s>" % path_param.lstrip('+')
                    )
                elif path_param in args and args[path_param].annotation is int:
                    path = path.replace(
                        '{%s}' % path_param,
                        "<int:%s>" % path_param
                    )
                elif path_param in args and args[path_param].annotation is float:
                    path = path.replace(
                        '{%s}' % path_param,
                        "<float:%s>" % path_param
                    )
                else:
                    path = path.replace(
                        '{%s}' % path_param,
                        "<string:%s>" % path_param
                    )

            rule = Rule(path, methods=[route.method], endpoint=name)
            rules.append(rule)
            name_lookups[name] = route

        self.adapter = Map(rules).bind('')
        self.name_lookups = name_lookups

        # Use an MRU cache for router lookups.
        self._lookup_cache = dict_type()
        self._lookup_cache_size = 10000

    def walk_routes(self, routes, url_prefix='', name_prefix=''):
        walked = []
        for item in routes:
            if isinstance(item, Route):
                result = (url_prefix + item.url, name_prefix + item.name, item)
                walked.append(result)
            elif isinstance(item, Include):
                result = self.walk_routes(
                    item.routes,
                    url_prefix + item.url,
                    name_prefix + item.name + ':'
                )
                walked.extend(result)
        return walked

    def lookup(self, path: str, method: str):
        lookup_key = method + ' ' + path
        try:
            return self._lookup_cache[lookup_key]
        except KeyError:
            pass

        try:
            name, path_params = self.adapter.match(path, method)
        except werkzeug.exceptions.NotFound:
            raise exceptions.NotFound() from None
        except werkzeug.exceptions.MethodNotAllowed:
            raise exceptions.MethodNotAllowed() from None
        except werkzeug.routing.RequestRedirect as exc:
            path = urlparse(exc.new_url).path
            raise exceptions.Found(path) from None

        route = self.name_lookups[name]

        self._lookup_cache[lookup_key] = (route, path_params)
        if len(self._lookup_cache) > self._lookup_cache_size:
            self._lookup_cache.pop(next(iter(self._lookup_cache)))

        return (route, path_params)

    def reverse_url(self, name: str, **params) -> str:
        try:
            return self.adapter.build(name, params)
        except werkzeug.routing.BuildError as exc:
            raise exceptions.NoReverseMatch(str(exc)) from None
Esempio n. 11
0
class Router(object):
    def __init__(self,
                 routes: RoutesConfig,
                 initial_types: List[type]=None) -> None:
        required_type = wsgi.WSGIResponse

        initial_types = initial_types or []
        initial_types += [wsgi.WSGIEnviron, URLPathArgs, Exception]

        rules = []
        views = {}

        for (path, method, view) in walk(routes):
            view_signature = inspect.signature(view)
            uritemplate = URITemplate(path)

            # Ensure view arguments include all URL arguments
            for arg in uritemplate.variable_names:
                assert arg in view_signature.parameters, (
                    'URL argument "%s" in path "%s" must be included as a '
                    'keyword argument in the view function "%s"' %
                    (arg, path, view.__name__)
                )

            # Create a werkzeug path string
            werkzeug_path = path[:]
            for arg in uritemplate.variable_names:
                param = view_signature.parameters[arg]
                if param.annotation is inspect.Signature.empty:
                    converter = 'string'
                elif issubclass(param.annotation, (schema.String, str)):
                    if getattr(param.annotation, 'format', None) == 'path':
                        converter = 'path'
                    else:
                        converter = 'string'
                elif issubclass(param.annotation, (schema.Number, float)):
                    converter = 'float'
                elif issubclass(param.annotation, (schema.Integer, int)):
                    converter = 'int'
                else:
                    msg = 'Invalid type for path parameter, %s.' % param.annotation
                    raise exceptions.ConfigurationError(msg)

                werkzeug_path = werkzeug_path.replace(
                    '{%s}' % arg,
                    '<%s:%s>' % (converter, arg)
                )

            # Create a werkzeug routing rule
            name = view.__name__
            rule = Rule(werkzeug_path, methods=[method], endpoint=name)
            rules.append(rule)

            # Determine any inferred type annotations for the view
            extra_annotations = {}  # type: Dict[str, type]
            for param in view_signature.parameters.values():

                if param.annotation is inspect.Signature.empty:
                    annotated_type = str
                else:
                    annotated_type = param.annotation

                if param.name in uritemplate.variable_names:
                    class TypedURLPathArg(URLPathArg):
                        schema = annotated_type
                    extra_annotations[param.name] = TypedURLPathArg
                elif (annotated_type in primitive_types) or issubclass(annotated_type, schema_types):
                    if method in ('POST', 'PUT', 'PATCH'):
                        if issubclass(annotated_type, (schema.Object, schema.Array)):
                            class TypedDataParam(http.RequestData):
                                schema = annotated_type
                            extra_annotations[param.name] = TypedDataParam
                        else:
                            class TypedFieldParam(http.RequestField):
                                schema = annotated_type
                            extra_annotations[param.name] = TypedFieldParam
                    else:
                        class TypedQueryParam(http.QueryParam):
                            schema = annotated_type
                        extra_annotations[param.name] = TypedQueryParam

            return_annotation = view_signature.return_annotation
            if return_annotation is inspect.Signature.empty:
                extra_annotations['return'] = http.ResponseData
            elif issubclass(return_annotation, (schema_types, primitive_types, typing_types)):  # type: ignore
                extra_annotations['return'] = http.ResponseData

            # Determine the pipeline for the view.
            pipeline = core.build_pipeline(view, initial_types, required_type, extra_annotations)
            views[name] = Endpoint(view, pipeline)

        self.exception_pipeline = core.build_pipeline(exception_handler, initial_types, required_type, {})
        self.routes = routes
        self.adapter = Map(rules).bind('')
        self.views = views

    def lookup(self, path: str, method: str) -> RouterLookup:
        try:
            (name, kwargs) = self.adapter.match(path, method)
        except werkzeug.exceptions.NotFound:
            raise exceptions.NotFound()
        except werkzeug.exceptions.MethodNotAllowed:
            raise exceptions.MethodNotAllowed()
        except werkzeug.routing.RequestRedirect as exc:
            path = urlparse(exc.new_url).path
            raise exceptions.Found(path)

        (view, pipeline) = self.views[name]
        return (view, pipeline, kwargs)

    def reverse_url(self, view_name: str, **url_params) -> str:
        endpoint = self.views.get(view_name)
        if not endpoint:
            raise exceptions.NoReverseMatch

        flattened_routes = walk(self.routes)
        matched_views = [
            route for route in flattened_routes
            if route.view == endpoint.view
        ]

        return matched_views[0].path.format(**url_params)
Esempio n. 12
0
class Router(object):
    def __init__(self,
                 routes: RoutesConfig,
                 initial_types: List[type] = None) -> None:
        required_type = wsgi.WSGIResponse

        initial_types = initial_types or []
        initial_types += [wsgi.WSGIEnviron, URLPathArgs, Exception]

        rules = []
        views = {}

        for (path, method, view) in walk(routes):
            view_signature = inspect.signature(view)
            uritemplate = URITemplate(path)

            # Ensure view arguments include all URL arguments
            for arg in uritemplate.variable_names:
                assert arg in view_signature.parameters, (
                    'URL argument "%s" in path "%s" must be included as a '
                    'keyword argument in the view function "%s"' %
                    (arg, path, view.__name__))

            # Create a werkzeug path string
            werkzeug_path = path[:]
            for arg in uritemplate.variable_names:
                param = view_signature.parameters[arg]
                if param.annotation is inspect.Signature.empty:
                    converter = 'string'
                elif issubclass(param.annotation, (schema.String, str)):
                    if getattr(param.annotation, 'format', None) == 'path':
                        converter = 'path'
                    else:
                        converter = 'string'
                elif issubclass(param.annotation, (schema.Number, float)):
                    converter = 'float'
                elif issubclass(param.annotation, (schema.Integer, int)):
                    converter = 'int'
                else:
                    msg = 'Invalid type for path parameter, %s.' % param.annotation
                    raise exceptions.ConfigurationError(msg)

                werkzeug_path = werkzeug_path.replace(
                    '{%s}' % arg, '<%s:%s>' % (converter, arg))

            # Create a werkzeug routing rule
            name = view.__name__
            rule = Rule(werkzeug_path, methods=[method], endpoint=name)
            rules.append(rule)

            # Determine any inferred type annotations for the view
            extra_annotations = {}  # type: Dict[str, type]
            for param in view_signature.parameters.values():

                if param.annotation is inspect.Signature.empty:
                    annotated_type = str
                else:
                    annotated_type = param.annotation

                if param.name in uritemplate.variable_names:

                    class TypedURLPathArg(URLPathArg):
                        schema = annotated_type

                    extra_annotations[param.name] = TypedURLPathArg
                elif (annotated_type in primitive_types) or issubclass(
                        annotated_type, schema_types):
                    if method in ('POST', 'PUT', 'PATCH'):
                        if issubclass(annotated_type, schema.Object):

                            class TypedDataParam(http.RequestData):
                                schema = annotated_type

                            extra_annotations[param.name] = TypedDataParam
                        else:

                            class TypedFieldParam(http.RequestField):
                                schema = annotated_type

                            extra_annotations[param.name] = TypedFieldParam
                    else:

                        class TypedQueryParam(http.QueryParam):
                            schema = annotated_type

                        extra_annotations[param.name] = TypedQueryParam

            return_annotation = view_signature.return_annotation
            if return_annotation is inspect.Signature.empty:
                extra_annotations['return'] = http.ResponseData
            elif issubclass(
                    return_annotation,
                (schema_types, primitive_types, typing_types)):  # type: ignore
                extra_annotations['return'] = http.ResponseData

            # Determine the pipeline for the view.
            pipeline = pipelines.build_pipeline(view, initial_types,
                                                required_type,
                                                extra_annotations)
            views[name] = Endpoint(view, pipeline)

        self.exception_pipeline = pipelines.build_pipeline(
            exception_handler, initial_types, required_type, {})
        self.routes = routes
        self.adapter = Map(rules).bind('example.com')
        self.views = views

    def lookup(self, path: str, method: str) -> RouterLookup:
        try:
            (name, kwargs) = self.adapter.match(path, method)
        except werkzeug.exceptions.NotFound:
            raise exceptions.NotFound()
        except werkzeug.exceptions.MethodNotAllowed:
            raise exceptions.MethodNotAllowed()
        except werkzeug.routing.RequestRedirect as exc:
            path = urlparse(exc.new_url).path
            raise exceptions.Found(path)

        (view, pipeline) = self.views[name]
        return (view, pipeline, kwargs)
Esempio n. 13
0
class WerkzeugRouter(Router):
    def __init__(self, routes: RouteConfig) -> None:
        rules = []  # type: typing.List[Rule]
        views = {}  # type: typing.Dict[str, typing.Callable]

        for path, method, view, name in flatten_routes(routes):
            if name in views:
                msg = ('Route wtih name "%s" exists more than once. Use an '
                       'explicit name="..." on the Route to avoid a conflict.'
                       ) % name
                raise exceptions.ConfigurationError(msg)

            template = uritemplate.URITemplate(path)
            werkzeug_path = str(path)

            parameters = inspect.signature(view).parameters

            for arg in template.variable_names:
                converter = self._get_converter(parameters, arg, view)
                template_format = '{%s}' % arg
                werkzeug_format = '<%s:%s>' % (converter, arg)
                werkzeug_path = werkzeug_path.replace(template_format,
                                                      werkzeug_format)

            rule = Rule(werkzeug_path, methods=[method], endpoint=name)
            rules.append(rule)
            views[name] = view

        self._routes = routes
        self._adapter = Map(rules).bind('')
        self._views = views

        # Use an MRU cache for router lookups.
        self._lookup_cache = collections.OrderedDict(
        )  # type: collections.OrderedDict
        self._lookup_cache_size = 10000

    def _get_converter(self, parameters: typing.Mapping[str,
                                                        inspect.Parameter],
                       arg: str, view: typing.Callable) -> str:
        if arg not in parameters:
            msg = 'URL Argument "%s" missing from view "%s".' % (arg, view)
            raise exceptions.ConfigurationError(msg)

        annotation = parameters[arg].annotation

        if annotation is inspect.Parameter.empty:
            return 'string'
        elif issubclass(annotation, PathWildcard):
            return 'path'
        elif issubclass(annotation, str):
            return 'string'
        elif issubclass(annotation, int):
            return 'int'
        elif issubclass(annotation, float):
            return 'float'

        msg = 'Invalid type for path parameter "%s" in view "%s".' % (
            parameters[arg], view)
        raise exceptions.ConfigurationError(msg)

    def lookup(self, path: str, method: str) -> HandlerLookup:
        lookup_key = method + ' ' + path
        try:
            return self._lookup_cache[lookup_key]
        except KeyError:
            pass

        try:
            name, kwargs = self._adapter.match(path, method)
        except werkzeug.exceptions.NotFound:
            raise exceptions.NotFound() from None
        except werkzeug.exceptions.MethodNotAllowed:
            raise exceptions.MethodNotAllowed() from None
        except werkzeug.routing.RequestRedirect as exc:
            path = urlparse(exc.new_url).path
            raise exceptions.Found(path) from None

        view = self._views[name]

        self._lookup_cache[lookup_key] = (view, kwargs)
        if len(self._lookup_cache) > self._lookup_cache_size:
            self._lookup_cache.pop(next(iter(
                self._lookup_cache)))  # pragma: nocover

        return (view, kwargs)

    def reverse_url(self, identifier: str, values: dict = None) -> str:
        try:
            return self._adapter.build(identifier, values)
        except werkzeug.routing.BuildError as exc:
            raise exceptions.NoReverseMatch(str(exc)) from None
Esempio n. 14
0
class Arsa(object):
    """
        Arsa is an object that stores the configuration
        needed for Arsa.io.
    """
    def __init__(self):
        self.factory = RouteFactory()
        self.routes = None
        self.middlewares = []
        self.exceptions = []

    def route(self, rule, methods=None, content_type='application/json'):
        """ Convenience decorator for defining a route """
        if methods is None:
            methods = ['GET']

        def decorator(func):
            route = self.factory.register_endpoint(func)
            route.set_rule(rule, methods, mimetype=content_type)
            return func

        return decorator

    def required(self, **expected_kwargs):
        def decorator(func):
            route = self.factory.register_endpoint(func)
            route.add_validation(**expected_kwargs)
            return func

        return decorator

    def optional(self, **optional_kwargs):
        def decorator(func):
            route = self.factory.register_endpoint(func)
            route.add_validation(True, **optional_kwargs)
            return func

        return decorator

    def handler(self, event, context):
        app = self.create_app()

        builder = AWSEnvironBuilder(event, context)
        builder.close()
        environ = builder.get_environ()
        resp = run_wsgi_app(app, environ)

        #wrap response
        response = Response(*resp)

        # log response
        print('{host} - - "{method} {path} {protocol}" {status}\n'.format(
            host=environ.get('SERVER_NAME', 'localhost'),
            method=environ.get('REQUEST_METHOD', 'GET'),
            path=environ.get('PATH_INFO', '/'),
            protocol=environ.get('SERVER_PROTOCOL', '/'),
            status=response.status_code))

        return {
            "statusCode": response.status_code,
            "headers": dict(response.headers),
            "body": response.get_data(as_text=True)
        }

    def add_middleware(self, middleware):
        if callable(middleware):
            self.middlewares.append(middleware)

    def add_exception(self, error_type):
        self.exceptions.append(error_type)

    def create_app(self):

        if not self.routes:
            self.routes = Map(rules=[self.factory]).bind('arsa.io')

        def app(environ, start_response):
            req = Request(environ)
            _request_ctx_stack.push(RequestContext(req))

            try:

                # Call middlewares
                for middleware in self.middlewares:
                    middleware()

                # Find url rule
                (rule, arguments) = self.routes.match(req.path,
                                                      method=req.method,
                                                      return_rule=True)

                arguments.update(dict(req.args))

                if req.form:
                    arguments.update(req.form)

                if req.data:
                    try:
                        data = json.loads(req.data)
                        arguments.update(data)
                    except ValueError:
                        raise BadRequest("JSON body was malformed")

                rule.has_valid_arguments(arguments)
                decoded_args = rule.decode_arguments(arguments)

                body = rule.endpoint(**decoded_args)

                if rule.mimetype == 'application/json':
                    body = json.dumps(body, default=to_serializable)

                resp = Response(body, mimetype=rule.mimetype)
            except Redirect as error:
                resp = error
            except tuple(self.exceptions) as error:
                code = error.code if hasattr(error, 'code') else 400
                resp = Response(response=json.dumps(error,
                                                    default=to_serializable),
                                status=code,
                                mimetype='application/json')
            except HTTPException as error:
                resp = Response(response=json.dumps(error,
                                                    default=to_serializable),
                                status=error.code,
                                mimetype='application/json')

            _request_ctx_stack.pop()

            return resp(environ, start_response)

        return app