Example #1
0
    def __call__(self, event, context):
        """
        The core of the microframework. This method convers the given event to
        a MinikRequest, it looks for the view to execute from the given route, and
        it returns a well defined response. The response will be used by the API
        Gateway to communicate back to the caller.

        :param event: The raw event of the lambda function (straight from API gateway)
        :param context: The aws context included in every lambda function execution
        """

        request = self._request_builder.build(event, context)
        self.request = request
        self.response = Response(status_code=codes.ok,
                                 headers={'Content-Type': 'application/json'})

        with error_handling(self):
            route = self._find_route(request)
            update_uri_parameters(route, request)
            self._execute_view(route.view)

        # After executing the view run all the middlewares in sequence. If a middleware
        # fails, handle the exception and move on. This code needs to run after the
        # execution of the views in its own contenxt given that we do want to run this
        # even if the view itself raised an exception.
        with error_handling(self):
            for middleware in self._middleware:
                middleware(self)

        return self.response.to_dict()
Example #2
0
def test_server_error_middleware():

    error = MagicMock(status_code=432)
    mock_app = MagicMock(response=Response())

    ServerErrorMiddleware()(mock_app, error)

    assert mock_app.response.status_code == error.status_code
    assert mock_app.response.body == {'error_message': str(error)}
Example #3
0
def test_content_type_application_json(sample_header):

    response = Response(headers={sample_header: 'application/json'},
                        body={'findme': True})
    mock_app = MagicMock(response=response)

    ContentTypeMiddleware()(mock_app)

    assert response.body == json.dumps({'findme': True})
Example #4
0
    def __call__(self, event, context):
        """
        The entrypoint of a lambda function. When building a web app with minik,
        the app instance must be the handler of the lambda function. Minik will
        be responsible for consuming the raw event and context objects sent by the
        consumer service.

        The workflow of the framework is the following:
        1) Normalize the given event
        2) Find the route associated with the request
        3) Execute the handler associated with the route
        4) Run any additional middleware classes

        :param event: The raw event of the lambda function (straight from API Gateway/ALB)
        :param context: The aws context included in every lambda function execution
        """

        # Normalize the raw event by type and build a MinikRequest.
        self.request = build_request(event, context, self._router)
        self.response = Response(
            status_code=codes.ok,
            headers={'Content-Type': 'application/json'}
        )

        with error_handling(self):
            route = self._router.find_route(self.request)
            self.response.body = route.evaluate(self.request)

        # After executing the view run all the middlewares in sequence. If a middleware
        # fails, handle the exception and move on. This code needs to run after the
        # execution of the views in its own contenxt given that we do want to run this
        # even if the view itself raised an exception.
        with error_handling(self):
            for middleware in self._middleware:
                middleware(self)

        return self.response.to_dict()
Example #5
0
class Minik:
    """
    Minik is a microframwork that will handle a request from the API gateway and it
    will return a valid http response. The response returned will be determined by
    the view associated with a given route. If the route has a set of path parameters
    the parameters will be given to the view during execution. The view itself will
    have access to the current requests and all it's original data values.

    Very similar to Flask or Chalice, to associate a route to a view use an instance
    of minik to decorate the function:

    @app.route('/events/{event_type}')
    def sample_view(event_type):
        return {'data': ['MD Grand Fondo', 'San Diego Sparta']}

    """
    def __init__(self, **kwargs):
        self._debug = kwargs.get('debug', False)

        self._request_builder = kwargs.get('request_builder',
                                           APIGatewayRequestBuilder())
        self._error_middleware = kwargs.get('server_error_middleware',
                                            ServerErrorMiddleware())
        self._exception_middleware = kwargs.get('exception_middleware',
                                                ExceptionMiddleware())

        self._routes = defaultdict(list)
        self._middleware = [ContentTypeMiddleware()]

    @property
    def in_debug(self):
        return self._debug

    def add_middleware(self, middleware_instance):
        self._middleware.append(middleware_instance)

    def get(self, path, **kwargs):
        return self.route(path, methods=['GET'], **kwargs)

    def post(self, path, **kwargs):
        return self.route(path, methods=['POST'], **kwargs)

    def put(self, path, **kwargs):
        return self.route(path, methods=['PUT'], **kwargs)

    def delete(self, path, **kwargs):
        return self.route(path, methods=['DELETE'], **kwargs)

    def route(self, path, **kwargs):
        """
        The decorator function used to associate a given route with a view function.

        :param path: The endpoint associated with a given view.
        """
        def _register_view(view_func):

            methods = kwargs.get('methods', [])
            new_route = SimpleRoute(view_func, methods)
            self._routes[path].append(new_route)
            cache_custom_route_fields(new_route)

            return view_func

        return _register_view

    def __call__(self, event, context):
        """
        The core of the microframework. This method convers the given event to
        a MinikRequest, it looks for the view to execute from the given route, and
        it returns a well defined response. The response will be used by the API
        Gateway to communicate back to the caller.

        :param event: The raw event of the lambda function (straight from API gateway)
        :param context: The aws context included in every lambda function execution
        """

        request = self._request_builder.build(event, context)
        self.request = request
        self.response = Response(status_code=codes.ok,
                                 headers={'Content-Type': 'application/json'})

        with error_handling(self):
            route = self._find_route(request)
            update_uri_parameters(route, request)
            self._execute_view(route.view)

        # After executing the view run all the middlewares in sequence. If a middleware
        # fails, handle the exception and move on. This code needs to run after the
        # execution of the views in its own contenxt given that we do want to run this
        # even if the view itself raised an exception.
        with error_handling(self):
            for middleware in self._middleware:
                middleware(self)

        return self.response.to_dict()

    def _execute_view(self, view):
        """
        Given a view function, execute the view and update the body of the current
        response.
        :param view: The function to execute.
        """

        self.response.body = view(
            **self.request.uri_params) if self.request.uri_params else view()

    def _find_route(self, request):
        """
        Given the paramters of the request, lookup the associated view. The lookup
        process follows a set of steps. If the view is not found an exception is
        raised with the appropriate status code and error message.
        """

        routes = self._routes.get(request.resource)

        if not routes:
            raise MinikViewError(
                'The requested URL was not found on the server.',
                status_code=codes.not_found)

        target_route = [
            route for route in routes
            if not route.methods or (request.method in route.methods)
        ]

        if not target_route:
            raise MinikViewError('Method is not allowed.',
                                 status_code=codes.method_not_allowed)

        if len(target_route) > 1:
            raise MinikViewError(
                f'Found multiple views for the "{request.method}" method.',
                status_code=codes.not_allowed)

        return target_route[0]
Example #6
0
class Minik:
    """
    Minik is a microframwork that will handle a request from the API gateway and it
    will return a valid http response. The response returned will be determined by
    the view associated with a given route. If the route has a set of path parameters
    the parameters will be given to the view during execution. The view itself will
    have access to the current requests and all it's original data values.

    Very similar to Flask or Chalice, to associate a route to a view use an instance
    of minik to decorate the function:

    @app.route('/events/{event_type}')
    def sample_view(event_type):
        return {'data': ['MD Grand Fondo', 'San Diego Sparta']}

    """

    def __init__(self, **kwargs):
        self._debug = kwargs.get('debug', False)

        self._router = Router()
        self._error_middleware = kwargs.get('server_error_middleware', ServerErrorMiddleware())
        self._exception_middleware = kwargs.get('exception_middleware', ExceptionMiddleware())

        self._middleware = [ContentTypeMiddleware()]

    @property
    def in_debug(self):
        return self._debug

    def add_middleware(self, middleware_instance):
        self._middleware.append(middleware_instance)

    def get(self, path, **kwargs):
        return self.route(path, methods=['GET'], **kwargs)

    def post(self, path, **kwargs):
        return self.route(path, methods=['POST'], **kwargs)

    def put(self, path, **kwargs):
        return self.route(path, methods=['PUT'], **kwargs)

    def patch(self, path, **kwargs):
        return self.route(path, methods=['PATCH'], **kwargs)

    def delete(self, path, **kwargs):
        return self.route(path, methods=['DELETE'], **kwargs)

    def route(self, path, **kwargs):
        """
        The decorator function used to associate a given route path to a handler.

        @route('/events/{event_id}')
        def get_event(event_id: str):
            pass

        :param path: The endpoint associated with a given view.
        """

        def _register_view(view_func):
            self._router.add_route(path, view_func, **kwargs)
            return view_func

        return _register_view

    def __call__(self, event, context):
        """
        The entrypoint of a lambda function. When building a web app with minik,
        the app instance must be the handler of the lambda function. Minik will
        be responsible for consuming the raw event and context objects sent by the
        consumer service.

        The workflow of the framework is the following:
        1) Normalize the given event
        2) Find the route associated with the request
        3) Execute the handler associated with the route
        4) Run any additional middleware classes

        :param event: The raw event of the lambda function (straight from API Gateway/ALB)
        :param context: The aws context included in every lambda function execution
        """

        # Normalize the raw event by type and build a MinikRequest.
        self.request = build_request(event, context, self._router)
        self.response = Response(
            status_code=codes.ok,
            headers={'Content-Type': 'application/json'}
        )

        with error_handling(self):
            route = self._router.find_route(self.request)
            self.response.body = route.evaluate(self.request)

        # After executing the view run all the middlewares in sequence. If a middleware
        # fails, handle the exception and move on. This code needs to run after the
        # execution of the views in its own contenxt given that we do want to run this
        # even if the view itself raised an exception.
        with error_handling(self):
            for middleware in self._middleware:
                middleware(self)

        return self.response.to_dict()