Beispiel #1
0
    def __init__(
        self,
        components: typing.Optional[typing.List[Component]] = None,
        debug: bool = False,
        title: typing.Optional[str] = "",
        version: typing.Optional[str] = "",
        description: typing.Optional[str] = "",
        schema: typing.Optional[str] = "/schema/",
        docs: typing.Optional[str] = "/docs/",
        redoc: typing.Optional[str] = None,
        on_startup: typing.Sequence[typing.Callable] = None,
        on_shutdown: typing.Sequence[typing.Callable] = None,
        *args,
        **kwargs
    ) -> None:
        super().__init__(debug=debug, *args, **kwargs)

        if components is None:
            components = []

        # Initialize injector
        self.components = components

        self.router = Router(components=components, on_startup=on_startup, on_shutdown=on_shutdown)
        self.app = self.router
        self.exception_middleware = ExceptionMiddleware(self.router, debug=debug)
        self.error_middleware = ServerErrorMiddleware(self.exception_middleware, debug=debug)

        # Add exception handler for API exceptions
        self.add_exception_handler(exceptions.HTTPException, self.api_http_exception_handler)

        # Add schema and docs routes
        self.add_schema_docs_routes(
            title=title, version=version, description=description, schema=schema, docs=docs, redoc=redoc
        )
Beispiel #2
0
    def test_mount_router(self, router, component_mock):
        router.components = [component_mock]

        app = Router()

        router.mount("/app/", app=app)
        assert len(router.routes) == 1
        assert isinstance(router.routes[0], Mount)
        assert router.routes[0].path == "/app"
        assert router.routes[0].app == app
        assert router.routes[0].app.components == [component_mock]
Beispiel #3
0
class Flama(Starlette, SchemaMixin):
    def __init__(
        self,
        components: typing.Optional[typing.List[Component]] = None,
        debug: bool = False,
        title: typing.Optional[str] = "",
        version: typing.Optional[str] = "",
        description: typing.Optional[str] = "",
        schema: typing.Optional[str] = "/schema/",
        docs: typing.Optional[str] = "/docs/",
        redoc: typing.Optional[str] = None,
        *args,
        **kwargs
    ) -> None:
        super().__init__(debug=debug, *args, **kwargs)

        if components is None:
            components = []

        # Initialize injector
        self.components = components

        self.router = Router(components=components)
        self.app = self.router
        self.exception_middleware = ExceptionMiddleware(self.router, debug=debug)
        self.error_middleware = ServerErrorMiddleware(self.exception_middleware, debug=debug)

        # Add exception handler for API exceptions
        self.add_exception_handler(exceptions.HTTPException, self.api_http_exception_handler)

        # Add schema and docs routes
        self.add_schema_docs_routes(
            title=title, version=version, description=description, schema=schema, docs=docs, redoc=redoc
        )

    @property
    def injector(self):
        return Injector(components=self.components)

    def mount(self, path: str, app: ASGIApp, name: str = None) -> None:
        self.components += getattr(app, "components", [])
        self.router.mount(path, app=app, name=name)

    def add_resource(self, path: str, resource: "BaseResource"):
        self.router.add_resource(path, resource=resource)

    def resource(self, path: str) -> typing.Callable:
        def decorator(resource: "BaseResource") -> "BaseResource":
            self.router.add_resource(path, resource=resource)
            return resource

        return decorator

    def api_http_exception_handler(self, request: Request, exc: HTTPException) -> Response:
        return APIErrorResponse(detail=exc.detail, status_code=exc.status_code, exception=exc)
Beispiel #4
0
    def test_get_route_from_scope_nested_mount_view(self, app, router, scope):
        @router.route("/foo/")
        async def foo():
            return "foo"

        app.mount("/router", app=Router(routes=[Mount("/nested", app=router)]))

        scope["path"] = "/router/nested/foo/"
        scope["method"] = "GET"

        route, route_scope = app.router.get_route_from_scope(scope=scope)

        assert route_scope is not None
        assert route.endpoint == foo
        assert route.path == "/foo/"
        assert route_scope["path"] == "/foo/"
        assert route_scope["root_path"] == "/router/nested"
        assert route_scope["endpoint"] == foo
Beispiel #5
0
    def app(self):
        app_ = Flama(
            components=[],
            title="Foo",
            version="0.1",
            description="Bar",
            schema="/schema/",
            docs="/docs/",
            redoc="/redoc/",
        )

        @app_.route("/endpoint/", methods=["GET"])
        class PuppyEndpoint(HTTPEndpoint):
            async def get(self) -> Puppy:
                """
                description: Custom component.
                responses:
                  200:
                    description: Component.
                """
                return {"name": "Canna"}

        @app_.route("/custom-component/", methods=["GET"])
        async def get(self) -> Puppy:
            """
            description: Custom component.
            responses:
              200:
                description: Component.
            """
            return {"name": "Canna"}

        @app_.route("/many-custom-component/", methods=["GET"])
        async def many_custom_component() -> Puppy(many=True):
            """
            description: Many custom component.
            responses:
              200:
                description: Components.
            """
            return [{"name": "Canna"}, {"name": "Sandy"}]

        @app_.route("/query-param/", methods=["GET"])
        async def query_param(param: str = "Foo"):
            """
            description: Query param.
            responses:
              200:
                description: Param.
            """
            return {"name": param}

        @app_.route("/path-param/{param:int}/", methods=["GET"])
        async def path_param(param: int):
            """
            description: Path param.
            responses:
              200:
                description: Param.
            """
            return {"name": param}

        @app_.route("/body-param/", methods=["POST"])
        async def body_param(param: BodyParam):
            """
            description: Body param.
            responses:
              200:
                description: Param.
            """
            return {"name": param["name"]}

        @app_.route("/default-response/", methods=["GET"])
        async def default_response():
            """
            description: Default response.
            """
            return {"name": "Canna"}

        router = Router()
        router.add_route("/custom-component/", endpoint=get, methods=["GET"])
        app_.mount("/mount", router)

        return app_
Beispiel #6
0
class Flama(Starlette, SchemaMixin):
    def __init__(
        self,
        components: typing.Optional[typing.List[Component]] = None,
        debug: bool = False,
        title: typing.Optional[str] = "",
        version: typing.Optional[str] = "",
        description: typing.Optional[str] = "",
        schema: typing.Optional[str] = "/schema/",
        docs: typing.Optional[str] = "/docs/",
        redoc: typing.Optional[str] = None,
        on_startup: typing.Sequence[typing.Callable] = None,
        on_shutdown: typing.Sequence[typing.Callable] = None,
        *args,
        **kwargs
    ) -> None:
        super().__init__(debug=debug, *args, **kwargs)

        if components is None:
            components = []

        # Initialize injector
        self.components = components

        self.router = Router(components=components, on_startup=on_startup, on_shutdown=on_shutdown)
        self.app = self.router
        self.exception_middleware = ExceptionMiddleware(self.router, debug=debug)
        self.error_middleware = ServerErrorMiddleware(self.exception_middleware, debug=debug)

        # Add exception handler for API exceptions
        self.add_exception_handler(exceptions.HTTPException, self.api_http_exception_handler)

        # Add schema and docs routes
        self.add_schema_docs_routes(
            title=title, version=version, description=description, schema=schema, docs=docs, redoc=redoc
        )

    @property
    def injector(self):
        return Injector(components=self.components)

    def mount(self, path: str, app: ASGIApp, name: str = None) -> None:
        self.components += getattr(app, "components", [])
        self.router.mount(path, app=app, name=name)

    def add_resource(self, path: str, resource: "BaseResource"):
        self.router.add_resource(path, resource=resource)

    def resource(self, path: str) -> typing.Callable:
        def decorator(resource: "BaseResource") -> "BaseResource":
            self.router.add_resource(path, resource=resource)
            return resource

        return decorator

    def api_http_exception_handler(self, request: Request, exc: HTTPException) -> Response:
        return APIErrorResponse(detail=exc.detail, status_code=exc.status_code, exception=exc)

    def register_router(self, router: APIRouter):
        assert isinstance(router, APIRouter), "Registered router must be an instance of APIRouter"
        self.mount(router.prefix, app=router, name=router.name)

    def schemas(
        self,
        response_schema: marshmallow.Schema = None,
        **request_schemas: typing.Dict[str, marshmallow.Schema]
    ):
        def decorator(func):
            func._request_schemas = request_schemas
            func._response_schema = response_schema
            return func

        return decorator

    def route(
        self,
        path: str,
        methods: typing.List[str] = None,
        name: str = None,
        include_in_schema: bool = True,
        status_code: int = 200,
        response_schema: marshmallow.Schema = None,
        **request_schemas: typing.Dict[str, marshmallow.Schema]
    ) -> typing.Callable:
        def decorator(func: typing.Callable) -> typing.Callable:
            self.router.add_route(
                path,
                func,
                methods=methods,
                name=name,
                include_in_schema=include_in_schema,
                status_code=status_code,
                response_schema=response_schema,
                request_schemas=request_schemas,
            )
            return func

        return decorator

    def get(
        self,
        path: str,
        name: str = None,
        include_in_schema: bool = True,
        status_code: int = 200,
        response_schema: marshmallow.Schema = None,
        **request_schemas: typing.Dict[str, marshmallow.Schema]
    ) -> typing.Callable:
        return self.route(
            path=path,
            methods=["GET"],
            name=name,
            include_in_schema=include_in_schema,
            status_code=status_code,
            response_schema=response_schema,
            request_schemas=request_schemas,
        )

    def put(
        self,
        path: str,
        name: str = None,
        include_in_schema: bool = True,
        status_code: int = 200,
        response_schema: marshmallow.Schema = None,
        **request_schemas: typing.Dict[str, marshmallow.Schema]
    ) -> typing.Callable:
        return self.route(
            path=path,
            methods=["PUT"],
            name=name,
            include_in_schema=include_in_schema,
            status_code=status_code,
            response_schema=response_schema,
            request_schemas=request_schemas,
        )

    def post(
        self,
        path: str,
        name: str = None,
        include_in_schema: bool = True,
        status_code: int = 200,
        response_schema: marshmallow.Schema = None,
        **request_schemas: typing.Dict[str, marshmallow.Schema]
    ) -> typing.Callable:
        return self.route(
            path=path,
            methods=["POST"],
            name=name,
            include_in_schema=include_in_schema,
            status_code=status_code,
            response_schema=response_schema,
            request_schemas=request_schemas,
        )

    def delete(
        self,
        path: str,
        name: str = None,
        include_in_schema: bool = True,
        status_code: int = 200,
        response_schema: marshmallow.Schema = None,
        **request_schemas: typing.Dict[str, marshmallow.Schema]
    ) -> typing.Callable:
        return self.route(
            path=path,
            methods=["DELETE"],
            name=name,
            include_in_schema=include_in_schema,
            status_code=status_code,
            response_schema=response_schema,
            request_schemas=request_schemas,
        )

    def options(
        self,
        path: str,
        name: str = None,
        include_in_schema: bool = True,
        status_code: int = 200,
        response_schema: marshmallow.Schema = None,
        **request_schemas: typing.Dict[str, marshmallow.Schema]
    ) -> typing.Callable:
        return self.route(
            path=path,
            methods=["OPTIONS"],
            name=name,
            include_in_schema=include_in_schema,
            status_code=status_code,
            response_schema=response_schema,
            request_schemas=request_schemas,
        )

    def head(
        self,
        path: str,
        name: str = None,
        include_in_schema: bool = True,
        status_code: int = 200,
        response_schema: marshmallow.Schema = None,
        **request_schemas: typing.Dict[str, marshmallow.Schema]
    ) -> typing.Callable:
        return self.route(
            path=path,
            methods=["HEAD"],
            name=name,
            include_in_schema=include_in_schema,
            status_code=status_code,
            response_schema=response_schema,
            request_schemas=request_schemas,
        )

    def patch(
        self,
        path: str,
        name: str = None,
        include_in_schema: bool = True,
        status_code: int = 200,
        response_schema: marshmallow.Schema = None,
        **request_schemas: typing.Dict[str, marshmallow.Schema]
    ) -> typing.Callable:
        return self.route(
            path=path,
            methods=["PATCH"],
            name=name,
            include_in_schema=include_in_schema,
            status_code=status_code,
            response_schema=response_schema,
            request_schemas=request_schemas,
        )

    def trace(
        self,
        path: str,
        name: str = None,
        include_in_schema: bool = True,
        status_code: int = 200,
        response_schema: marshmallow.Schema = None,
        **request_schemas: typing.Dict[str, marshmallow.Schema]
    ) -> typing.Callable:
        return self.route(
            path=path,
            methods=["TRACE"],
            name=name,
            include_in_schema=include_in_schema,
            status_code=status_code,
            response_schema=response_schema,
            request_schemas=request_schemas,
        )
Beispiel #7
0
 def router(self):
     return Router()