Esempio n. 1
0
class Mapper(object):
    def __init__(self, import_name):
        self.import_name = import_name
        self.url_map = Map()
        self.url_views = {}

    @property
    def url_rules(self):
        return self.url_map.iter_rules()

    def build_endpoint(self, endpoint):
        return '{0}.{1}'.format(self.import_name, endpoint)

    def add_route(self, rule, endpoint, view_func=None, **options):
        options['endpoint'] = endpoint
        self.url_map.add(Rule(rule, **options))
        if view_func:
            self.url_views[endpoint] = view_func

    def route(self, rule, **options):
        def decorator(func):
            endpoint = options.pop('endpoint', func.__name__)
            endpoint = self.build_endpoint(endpoint)
            self.add_route(rule, endpoint, func, **options)
            return func
        return decorator

    def add_map(self, map, prefix='', rule_factory=Submount):
        self.url_views.update(map.url_views)
        self.url_map.add(
            rule_factory('/%s' % prefix.rstrip('/'), map.url_rules)
        )
Esempio n. 2
0
class Urls:
    def __init__(self, rules):
        self.map = Map(rules)
        self.urls = ContextVar("urls", default=self.map.bind(''))

    def iter_rules(self):
        return self.map.iter_rules()

    @contextmanager
    def _bind(self, urls):
        token = self.urls.set(urls)
        try:
            yield
        finally:
            self.urls.reset(token)

    def bind(self, *args, **kwargs):
        return self._bind(self.map.bind(*args, **kwargs))

    def bind_to_environ(self, *args, **kwargs):
        return self._bind(self.map.bind_to_environ(*args, **kwargs))

    def build(self,
              endpoint,
              values=None,
              method=None,
              force_external=False,
              append_unknown=True):
        urls = self.urls.get()
        path = urls.build(endpoint, values, method, force_external,
                          append_unknown)
        if urls.url_scheme == 'file' and urls.server_name == '.':
            assert not force_external
            if path.endswith('/'):
                path = path + 'index.html'
            return os.path.relpath(path, os.path.dirname(urls.path_info))
        return path

    def match(self, path=None, return_rule=False):
        urls = self.urls.get()
        if path is None:
            return urls.match(return_rule=return_rule)
        if urls.url_scheme == 'file' and urls.server_name == '.':
            path = os.path.normpath(
                os.path.join(os.path.dirname(urls.path_info), path))
            if path.endswith("/index.html"):
                path = path[:-11]
        else:
            script_name = urls.script_name.rstrip("/")
            assert path.startswith(script_name)
            path = path[len(script_name):]

        return urls.match(path, return_rule=return_rule)

    def dispatch(self, *args, **kwargs):
        return self.urls.get().dispatch(*args, **kwargs)

    def current_url(self):
        return self.dispatch(lambda e, v: self.build(e, v))
Esempio n. 3
0
def test_rule_templates():
    """Rule templates"""
    testcase = RuleTemplate(
        [ Submount('/test/$app',
          [ Rule('/foo/', endpoint='handle_foo')
          , Rule('/bar/', endpoint='handle_bar')
          , Rule('/baz/', endpoint='handle_baz')
          ]),
          EndpointPrefix('${app}',
          [ Rule('/${app}-blah', endpoint='bar')
          , Rule('/${app}-meh', endpoint='baz')
          ]),
          Subdomain('$app',
          [ Rule('/blah', endpoint='x_bar')
          , Rule('/meh', endpoint='x_baz')
          ])
        ])

    url_map = Map(
        [ testcase(app='test1')
        , testcase(app='test2')
        , testcase(app='test3')
        , testcase(app='test4')
        ])

    out = sorted([(x.rule, x.subdomain, x.endpoint)
                  for x in url_map.iter_rules()])

    assert out == ([
        ('/blah', 'test1', 'x_bar'),
        ('/blah', 'test2', 'x_bar'),
        ('/blah', 'test3', 'x_bar'),
        ('/blah', 'test4', 'x_bar'),
        ('/meh', 'test1', 'x_baz'),
        ('/meh', 'test2', 'x_baz'),
        ('/meh', 'test3', 'x_baz'),
        ('/meh', 'test4', 'x_baz'),
        ('/test/test1/bar/', '', 'handle_bar'),
        ('/test/test1/baz/', '', 'handle_baz'),
        ('/test/test1/foo/', '', 'handle_foo'),
        ('/test/test2/bar/', '', 'handle_bar'),
        ('/test/test2/baz/', '', 'handle_baz'),
        ('/test/test2/foo/', '', 'handle_foo'),
        ('/test/test3/bar/', '', 'handle_bar'),
        ('/test/test3/baz/', '', 'handle_baz'),
        ('/test/test3/foo/', '', 'handle_foo'),
        ('/test/test4/bar/', '', 'handle_bar'),
        ('/test/test4/baz/', '', 'handle_baz'),
        ('/test/test4/foo/', '', 'handle_foo'),
        ('/test1-blah', '', 'test1bar'),
        ('/test1-meh', '', 'test1baz'),
        ('/test2-blah', '', 'test2bar'),
        ('/test2-meh', '', 'test2baz'),
        ('/test3-blah', '', 'test3bar'),
        ('/test3-meh', '', 'test3baz'),
        ('/test4-blah', '', 'test4bar'),
        ('/test4-meh', '', 'test4baz')
    ])
Esempio n. 4
0
    def validate_api(self, endpoints: Map) -> None:
        """Validated if the input endpoints are consistent to the OpenAPI specification of the Flask API gateway.

        This also includes the available HTTP options of the API.

        Args:
            endpoints: The endpoints of the API which should be validated.
        """
        # Methods that are validated (do not validate HEAD, OPTION)
        allowed_methods = ("get", "post", "put", "patch", "delete")

        # Extract the target specified blueprint of the API
        target: Dict[str, List[str]] = {}
        for endpoint, methods in self._specs["paths"].items():
            # Add versioning to ever URL. Exceptions:
            #  - "/.well-known/openeo" -> no versioning allowed
            #  - "/base" -> maps to "/" which redirects to "/.well-known/openeo"
            # Note that this still creates "/v1.0" which maps to "/" of the opeEO specs
            if not endpoint == '/.well-known/openeo':
                if endpoint == "/base":
                    endpoint = "/"
                else:
                    # Add versioning
                    endpoint = f"/{settings.OPENEO_VERSION}{endpoint}"
            target[endpoint] = []
            for method in methods:
                if method in allowed_methods:
                    target[endpoint].append(method.lower())

        # Extract the current status of the API
        status: Dict[str, List[str]] = {}
        for rule in endpoints.iter_rules():
            if not rule.rule.startswith("/static"):
                endpoint = rule.rule.replace("<", "{").replace(">", "}")
                if endpoint not in status:
                    status[endpoint] = []
                for method in rule.methods:
                    method = method.lower()
                    if method in allowed_methods:
                        status[endpoint].append(method)

        # Check if the target matches the status
        difference = set(target.keys()).symmetric_difference(status.keys())

        if len(difference) > 0:
            raise OpenAPISpecException(
                f"The gateway or specification is missing the endpoint(s) '{difference}'"
            )

        for status_endpoint, status_methods in status.items():
            target_methods = target[status_endpoint]

            method_diff = set(target_methods).symmetric_difference(
                status_methods)
            if len(method_diff) > 0:
                raise OpenAPISpecException(
                    f"The gateway or specification is missing the HTTP method(s) '{method_diff}'"
                    f" at endpoint '{status_endpoint}'")
Esempio n. 5
0
def test_rule_templates():
    """Rule templates"""
    testcase = RuleTemplate(
        [ Submount('/test/$app',
          [ Rule('/foo/', endpoint='handle_foo')
          , Rule('/bar/', endpoint='handle_bar')
          , Rule('/baz/', endpoint='handle_baz')
          ]),
          EndpointPrefix('foo_',
          [ Rule('/blah', endpoint='bar')
          , Rule('/meh', endpoint='baz')
          ]),
          Subdomain('meh',
          [ Rule('/blah', endpoint='x_bar')
          , Rule('/meh', endpoint='x_baz')
          ])
        ])

    url_map = Map(
        [ testcase(app='test1')
        , testcase(app='test2')
        , testcase(app='test3')
        , testcase(app='test4')
        ])

    out = [(x.rule, x.subdomain, x.endpoint)
           for x in url_map.iter_rules()]
    assert out == (
        [ ('/test/test1/foo/', '', 'handle_foo')
        , ('/test/test1/bar/', '', 'handle_bar')
        , ('/test/test1/baz/', '', 'handle_baz')
        , ('/blah', '', 'foo_bar')
        , ('/meh', '', 'foo_baz')
        , ('/blah', 'meh', 'x_bar')
        , ('/meh', 'meh', 'x_baz')
        , ('/test/test2/foo/', '', 'handle_foo')
        , ('/test/test2/bar/', '', 'handle_bar')
        , ('/test/test2/baz/', '', 'handle_baz')
        , ('/blah', '', 'foo_bar')
        , ('/meh', '', 'foo_baz')
        , ('/blah', 'meh', 'x_bar')
        , ('/meh', 'meh', 'x_baz')
        , ('/test/test3/foo/', '', 'handle_foo')
        , ('/test/test3/bar/', '', 'handle_bar')
        , ('/test/test3/baz/', '', 'handle_baz')
        , ('/blah', '', 'foo_bar')
        , ('/meh', '', 'foo_baz')
        , ('/blah', 'meh', 'x_bar')
        , ('/meh', 'meh', 'x_baz')
        , ('/test/test4/foo/', '', 'handle_foo')
        , ('/test/test4/bar/', '', 'handle_bar')
        , ('/test/test4/baz/', '', 'handle_baz')
        , ('/blah', '', 'foo_bar')
        , ('/meh', '', 'foo_baz')
        , ('/blah', 'meh', 'x_bar')
        , ('/meh', 'meh', 'x_baz') ]
    )
Esempio n. 6
0
class Sockets(object):

    def __init__(self, app=None):
        #: Compatibility with 'Flask' application.
        #: The :class:`~werkzeug.routing.Map` for this instance. You can use
        #: this to change the routing converters after the class was created
        #: but before any routes are connected.
        self.url_map = Map()

        #: Compatibility with 'Flask' application.
        #: All the attached blueprints in a dictionary by name. Blueprints
        #: can be attached multiple times so this dictionary does not tell
        #: you how often they got attached.
        self.blueprints = {}
        self._blueprint_order = []

        if app:
            self.init_app(app)

    def init_app(self, app):
        app.wsgi_app = SocketMiddleware(app.wsgi_app, app, self)

    def route(self, rule, **options):

        def decorator(f):
            endpoint = options.pop('endpoint', None)
            print('experiment', rule, f, endpoint, options)
            self.add_url_rule(rule, endpoint, f, **options)
            return f
        return decorator

    def add_url_rule(self, rule, _, f, **options):
        self.url_map.add(Rule(rule, endpoint=f))
        print("Map", self.url_map.iter_rules())

    def register_blueprint(self, blueprint, **options):
        """
        Registers a blueprint for web sockets like for 'Flask' application.
        Decorator :meth:`~flask.app.setupmethod` is not applied, because it
        requires ``debug`` and ``_got_first_request`` attributes to be defined.
        """
        first_registration = False

        if blueprint.name in self.blueprints:
            assert self.blueprints[blueprint.name] is blueprint, (
                'A blueprint\'s name collision occurred between %r and '
                '%r.  Both share the same name "%s".  Blueprints that '
                'are created on the fly need unique names.'
                % (blueprint, self.blueprints[blueprint.name], blueprint.name))
        else:
            self.blueprints[blueprint.name] = blueprint
            self._blueprint_order.append(blueprint)
            first_registration = True

        blueprint.register(self, options, first_registration)
Esempio n. 7
0
class BaseDispatcher(object):
    request_class = None
    response_class = None

    auth_class = None

    def __init__(self):
        self.url_map = Map()

    def add_resource(self, resource, parent_resource=None):
        if parent_resource:
            try:
                for rule in self.url_map.iter_rules(parent_resource.on_detail):
                    detail_rule = rule

                rulefactory = Submount(detail_rule.rule, (resource,))
            except AttributeError:
                raise AttributeError(
                    'The parent resource must define the "on_detail" method')
        else:
            rulefactory = resource

        self.url_map.add(rulefactory)

    def dispatch_request(self, request):
        adapter = self.url_map.bind_to_environ(request.environ)
        endpoint, params = adapter.match()

        params.update(data=request.data)
        params.update(request.args)

        if endpoint.requires_auth:
            endpoint = self.auth_class(endpoint, request)

        return endpoint(params)

    def __call__(self, environ, start_response):
        request = self.request_class(environ)
        try:
            response = self.response_class(self.dispatch_request(request))
        except HTTPException as e:
            response = self.response_class({'error': e.description}, e.code, headers=e.get_headers())
        return response(environ, start_response)

    def run(self, host='127.0.0.1', port=5000,
            use_debugger=True, use_reloader=True):
        from werkzeug.serving import run_simple
        run_simple(
            host,
            port,
            self,
            use_debugger=use_debugger,
            use_reloader=use_reloader
        )
Esempio n. 8
0
def test_rule_templates():
    """Rule templates"""
    testcase = RuleTemplate(
        [
            Submount(
                "/test/$app",
                [
                    Rule("/foo/", endpoint="handle_foo"),
                    Rule("/bar/", endpoint="handle_bar"),
                    Rule("/baz/", endpoint="handle_baz"),
                ],
            ),
            EndpointPrefix("foo_", [Rule("/blah", endpoint="bar"), Rule("/meh", endpoint="baz")]),
            Subdomain("meh", [Rule("/blah", endpoint="x_bar"), Rule("/meh", endpoint="x_baz")]),
        ]
    )

    url_map = Map([testcase(app="test1"), testcase(app="test2"), testcase(app="test3"), testcase(app="test4")])

    out = [(x.rule, x.subdomain, x.endpoint) for x in url_map.iter_rules()]
    assert out == (
        [
            ("/test/test1/foo/", "", "handle_foo"),
            ("/test/test1/bar/", "", "handle_bar"),
            ("/test/test1/baz/", "", "handle_baz"),
            ("/blah", "", "foo_bar"),
            ("/meh", "", "foo_baz"),
            ("/blah", "meh", "x_bar"),
            ("/meh", "meh", "x_baz"),
            ("/test/test2/foo/", "", "handle_foo"),
            ("/test/test2/bar/", "", "handle_bar"),
            ("/test/test2/baz/", "", "handle_baz"),
            ("/blah", "", "foo_bar"),
            ("/meh", "", "foo_baz"),
            ("/blah", "meh", "x_bar"),
            ("/meh", "meh", "x_baz"),
            ("/test/test3/foo/", "", "handle_foo"),
            ("/test/test3/bar/", "", "handle_bar"),
            ("/test/test3/baz/", "", "handle_baz"),
            ("/blah", "", "foo_bar"),
            ("/meh", "", "foo_baz"),
            ("/blah", "meh", "x_bar"),
            ("/meh", "meh", "x_baz"),
            ("/test/test4/foo/", "", "handle_foo"),
            ("/test/test4/bar/", "", "handle_bar"),
            ("/test/test4/baz/", "", "handle_baz"),
            ("/blah", "", "foo_bar"),
            ("/meh", "", "foo_baz"),
            ("/blah", "meh", "x_bar"),
            ("/meh", "meh", "x_baz"),
        ]
    )
Esempio n. 9
0
def make_application (setup_graph):

    site_config = setup_graph.value (None, a, WALD.SiteConfig)
    project_root = setup_graph.value (site_config, WALD.projectRoot)
    site_root = setup_graph.value (site_config, WALD.siteRoot)

    base = setup_graph.value (site_config, WALD.base)

    ld = wald.storage.tools.LinkedData (project_root, site_root)

    endpoints = {}

    rules = []
    for dataset in setup_graph[:a:WALD.Dataset]:
        name = setup_graph.value (dataset, DC.identifier)

        readOnly = bool (setup_graph.value (dataset, WALD.readOnly))
        if not readOnly:
            # The following sets up the edit endpoint for a dataset.
            endpoints['edit ' + name] = patch_endpoint (setup_graph, ld, dataset)
            rules.append (Rule ('/' + name + '/', methods=[ 'PATCH', 'POST' ],
                                endpoint='edit ' + name))

        data_graph = setup_graph.value (dataset, WALD.dataGraph)
        edit_graph = setup_graph.value (dataset, WALD.editGraph)

        ldf_base = iri_join (base, 'fragments', name).rstrip ('/')
        for g, ldf in [ (data_graph, ldf_base),
                        (edit_graph, iri_join (ldf_base, 'edit').rstrip ('/')) ]:
            path = '/' + g.replace (base, '').lstrip ('/').rstrip ('/') + '/'
            endpoint = 'named graph redirect ' + g
            endpoints[endpoint] = redirect_endpoint (ldf)
            rules.append (Rule (path, methods=[ 'GET' ], endpoint=endpoint))

    routing_map = Map (rules)

    for rule in routing_map.iter_rules ():
        print (rule.description ())

    @LimitedRequest.application
    def application (request):
        adapter = routing_map.bind_to_environ (request.environ)
        try:
            endpoint, values = adapter.match ()
            return endpoints[endpoint] (request, **values)
        except werkzeug.exceptions.HTTPException as e:
            return e

    return application
Esempio n. 10
0
class SocketAPI(object):

    def __init__(self, socketio=None, namespace=None):
        self.namespace = namespace

        self.routes = Map()
        self.urls = self.routes.bind('/', '/')

        self.patch_handlers = {}

        if socketio is not None:
            self.init_socketio(socketio)

    def init_socketio(self, socketio):
        self.socketio = socketio

        @socketio.on('create', namespace=self.namespace)
        def handle_create(payload):
            # Retreive request arguments.
            if 'uri' not in payload:
                raise InvalidRequestError('missing URI')
            uri = payload['uri']
            attributes = payload.get('attributes', {})

            # Search for a matching route.
            try:
                creator, kwargs = self.urls.match(uri, method='POST')
            except HTTPException:
                # No registered resource creator for this uri.
                raise InvalidRequestError("no registered resource creator for %s'" % uri)

            # Create the new resource instance.
            kwargs.update(attributes)
            resource = creator(**kwargs)

            # Send the creation event to all subscribers of the uri.
            self.socketio.emit('create', {
                'uri': uri,
                'resource': resource
            }, room=uri)

        @socketio.on('patch')
        def handle_patch(payload, namespace=self.namespace):
            # Retreive request arguments.
            if 'uri' not in payload:
                raise InvalidRequestError('missing URI')
            uri = payload['uri']
            patch = payload.get('patch', {})

            # Search for a matching route.
            try:
                rule, kwargs = self.urls.match(uri, return_rule=True, method='PATCH')
                kwargs['patch'] = patch
            except HTTPException:
                # No registered resource patcher for this uri.
                raise InvalidRequestError("no registered resource patcher for %s'" % uri)

            # Call all the resource patchers for the given uri.
            for patch_handler in self.patch_handlers[rule.rule]:
                patch_handler(**kwargs)

            # Send the patch event to all subscribers of the resource, and of
            # the resource list.
            for room_name in (uri, uri[0:len(uri) - len(uri.split('/')[-1])]):
                self.socketio.emit('patch', {
                    'uri': uri,
                    'patch': patch
                }, room=room_name)

        @socketio.on('delete', namespace=self.namespace)
        def handle_delete(payload):
            # Retreive request arguments.
            if 'uri' not in payload:
                raise InvalidRequestError('missing URI')
            uri = payload['uri']

            # Search for a matching route.
            try:
                deleter, kwargs = self.urls.match(uri, method='DELETE')
            except HTTPException:
                # No registered resource deleter for this uri.
                raise InvalidRequestError("no registered resource deleter for %s'" % uri)

            # Delete the resource.
            resource = deleter(**kwargs)

            # Send the deletion event to all subscribers of the resource, and
            # of the resource list.
            for room_name in (uri, uri[0:len(uri) - len(uri.split('/')[-1])]):
                self.socketio.emit('delete', {
                    'uri': uri
                }, room=room_name)

        @socketio.on('subscribe', namespace=self.namespace)
        def handle_subscribe(uri):
            # Try to retrieve the subscribed resource, so that we can send its
            # current state to the subscriber.
            try:
                getter, kwargs = self.urls.match(uri, method='GET')
                resource = getter(**kwargs)
            except HTTPException:
                resource = None

            if resource is not None:
                self.socketio.emit('state', {
                    'uri': uri,
                    'resource': resource
                }, room=request.sid)

            join_room(uri)

        @socketio.on('unsubscribe', namespace=self.namespace)
        def handle_unsubscribe(uri):
            leave_room(uri)

        @socketio.on_error(self.namespace)
        def handle_error(e):
            if isinstance(e, SocketAPIError):
                # Instances of SocketAPIError are forwarded to the client.
                self.socketio.emit('api_error', {
                    'error':  e.__class__.__name__,
                    'message': str(e)
                }, room=request.sid)
            else:
                # Other errors are considered server errors and should not be
                # forwarded to the client, except in debug mode.
                self.socketio.emit('server_error', {
                    'error':  e.__class__.__name__,
                    'message': str(e) if current_app.debug else None
                }, room=request.sid)

            # Log the error.
            current_app.logger.exception(e)

    def resource_creator(self, rule):
        # Make sure the given rule corresponds to a list uri.
        if not rule.endswith('/'):
            raise InvalidURIError('resource creators should be registered on list uri')

        def decorate(fn):
            @wraps(fn)
            def decorated(*args, **kwargs):
                return fn(*args, **kwargs)

            # Register a new POST route for the given rule.
            self.routes.add(Rule(rule, endpoint=decorated, methods=['POST']))

            return decorated
        return decorate

    def resource_getter(self, rule):
        def decorate(fn):
            @wraps(fn)
            def decorated(*args, **kwargs):
                return fn(*args, **kwargs)

            # Register a new GET route for the given rule.
            self.routes.add(Rule(rule, endpoint=decorated, methods=['GET']))

            return decorated
        return decorate

    def resource_patcher(self, rule):
        # Make sure the rule doesn't correspond to a list.
        if rule.endswith('/'):
            raise InvalidURIError('cannot register resource patchers on a list uri')

        def decorate(fn):
            @wraps(fn)
            def decorated(*args, **kwargs):
                return fn(*args, **kwargs)

            # Check if there already is a route to catch patch requests on the
            # given rule.
            for route in self.routes.iter_rules():
                if (route.rule == rule) and ('PATCH' in route.methods):
                    break
            else:
                # Register a new PATCH route for the given rule.
                self.routes.add(Rule(rule, methods=['PATCH']))

            # Register the given patch handler.
            if rule not in self.patch_handlers:
                self.patch_handlers[rule] = []
            self.patch_handlers[rule].append(decorated)

            return decorated
        return decorate

    def resource_deleter(self, rule):
        # Make sure the rule doesn't correspond to a list.
        if rule.endswith('/'):
            raise InvalidURIError('cannot register resource deleters on a list uri')

        def decorate(fn):
            @wraps(fn)
            def decorated(*args, **kwargs):
                return fn(*args, **kwargs)

            # Register a new DELETE route for the given rule.
            self.routes.add(Rule(rule, endpoint=decorated, methods=['DELETE']))

            return decorated
        return decorate
Esempio n. 11
0
class MinimalWeb():
    ROOT_DIR = os.path.dirname(__file__)

    config = {
        'debug': True,
        'reloader': True,
        'export_files': {
            '/static': os.path.join(ROOT_DIR, 'static'),
        },
        'host': '127.0.0.1',
        'port': 8000,
    }

    def __init__(self):
        self.routes = {}
        self.url_rules = Map()

        self.middleware = BaseMiddleware(self)

    def __call__(self, environ, start_response):
        return self.middleware(environ, start_response)

    def add_middleware(self, middleware_cls):
        if not isinstance(self.middleware, BaseMiddleware):
            raise Exception(
                "You shouldn't add middleware after serving files.")

        self.middleware.add(middleware_cls)

    def dispatch_request(self, request):
        adapter = self.url_rules.bind_to_environ(request.environ)
        try:
            endpoint, kwargs = adapter.match()
            handler = self.routes[endpoint]
            if inspect.isclass(handler):
                handler = getattr(handler(), request.method.lower(), None)
                assert handler is not None, f"Method not exists in {endpoint}."

            return handler(request, **kwargs)
        except NotFound:
            return self.error_404(request)
        except MethodNotAllowed:
            return self.error_405(request)
        except HTTPException as e:
            return e

    def error_404(self, request):
        return TextResponse("Page Not Found!", status=404)

    def error_405(self, request):
        return TextResponse("Method not allowed!", status=405)

    def check_route_exists(self, path, endpoint):
        if endpoint in self.routes:
            return True
        for rule in self.url_rules.iter_rules():
            if rule.rule == path:
                return True
        return False

    def route(self, path, methods=None):
        def wrapper(handler):
            endpoint = handler.__name__
            assert not self.check_route_exists(
                path, endpoint), "Method already exists."
            rule = Rule(path, endpoint=endpoint, methods=methods)
            self.routes[endpoint] = handler
            self.url_rules.add(rule)
            return handler

        return wrapper

    def serve_files(self, export_files=None, extend_config=True):
        if not self.config['debug']:
            raise Exception("You can't serve files out of debug mode.")

        if export_files is None:
            export_files = {}
        if extend_config:
            export_files.update(self.config.get('export_files', {}))

        self.middleware = SharedDataMiddleware(self.middleware, export_files)

    def run(self, host=None, port=None, debug=True, reloader=True):
        from werkzeug.serving import run_simple
        _host = host or self.config['host']
        _port = port or self.config['port']
        use_debugger = debug or self.config['debug']
        use_reloader = reloader or self.config['reloader']
        run_simple(_host,
                   _port,
                   self.middleware,
                   use_debugger=use_debugger,
                   use_reloader=use_reloader)
Esempio n. 12
0
class Spatz:
    """The Spatz WSGI Application Class.
    Including Jinga Template Engine, Whitenoise Static files management, SQLAlchemy ORM...

    You can replace the template engine or static files directory if you know well Jinga2 Template and whitenoise.
    """

    # the default configuration
    default_config = {
        "ENV": None,
        "DEBUG": None,
        "SECRET_KEY": None,
        "SESSION_COOKIE_NAME": "session",
        "SESSION_COOKIE_DOMAIN": None,
        "SESSION_COOKIE_PATH": None,
        "SESSION_COOKIE_HTTPONLY": True,
        "SESSION_COOKIE_SECURE": False,
        "SESSION_COOKIE_SAMESITE": None,
        "PERMANENT_SESSION_LIFETIME": timedelta(days=1),
    }

    def __init__(self, templates_dir="templates", static_dir="static"):

        self.routes = Map()
        self.handlers = {}

        self.templates_env = Environment(
            loader=FileSystemLoader(os.path.abspath(templates_dir))
        )
        self.exception_handler = {}
        self.whitenoise = WhiteNoise(
            self.wsgi_app, root=static_dir, prefix="static/", max_age=31536000
        )
        self.middleware = Middleware(self)
        self.db = Database(self)
        self.config = self.default_config.copy()

        # session interface
        self.SessionInterface = ClientSession

        # cache interface
        self.CacheInterface = None

    def __call__(self, environ, start_response):
        return self.whitenoise(environ, start_response)

    def wsgi_app(self, environ, start_response):
        return self.middleware(environ, start_response)

    def add_route(self, rule, handler, endpoint=None, methods=["GET"]):
        """Add a URL Rule.

        :param url: the URL rule.
        :type url: str
        :param handler: the function handling a request
        :type handler: callable
        :param endpoint: the endpoint for registered URL rule, defaults to None
        :type endpoint: str, optional
        :param methods: allowed methods, defaults to ["GET"]
        :type methods: list, optional
        """

        # check if the rule exists
        for r in self.routes.iter_rules():
            if rule == r.rule:
                raise AssertionError("Such URL already exists.")

        if endpoint is None:
            endpoint = handler.__name__

        # class-based handler
        if inspect.isclass(handler):
            for method in ["post", "put", "delete"]:
                if hasattr(handler, method):
                    methods.append(method)

        rule = Rule(rule, endpoint=endpoint, methods=methods)
        self.routes.add(rule)
        self.handlers[endpoint] = handler

    def route(self, rule, **kwargs):
        def wrapper(handler):
            self.add_route(rule, handler, **kwargs)
            return handler

        return wrapper

    def handle_request(self, request):
        """Handle requests and dispatch the requests to view functions

        :param request: requests from clients
        :type request: webob.Request
        :return: responses from view functions
        :rtype: webob.Response
        """
        response = Response()
        response.render = self.render

        urls = self.routes.bind_to_environ(request.environ)
        try:
            endpoints, kwargs = urls.match()
            handler = self.handlers[endpoints]

            # class-based handler
            if inspect.isclass(handler):
                handler = getattr(handler(), request.method.lower(), None)
            if not handler:
                raise MethodNotAllowed()

            handler(request, response, **kwargs)

        except HTTPException as e:
            return e

        return response

    def render(self, template_name, context=None):
        if context is None:
            context = {}
        return self.templates_env.get_template(template_name).render(**context)

    def default_response(self, response):
        response.status_code = 404
        response.text = "Not Found."

        return response

    def test_session(self, base_url="http://testserver"):
        session = RequestsSession()
        session.mount(prefix=base_url, adapter=RequestsWSGIAdapter(self))
        return session

    def add_middleware(self, middleware_cls):
        self.middleware.add(middleware_cls)