예제 #1
0
    def __init__(
        self,
        *,
        engine: Engine = None,
        sdl: str = None,
        graphiql: typing.Union[None, bool, GraphiQL] = True,
        path: str = "/",
        subscriptions: typing.Union[bool, Subscriptions] = None,
        context: dict = None,
        schema_name: str = "default",
    ) -> None:
        if engine is None:
            assert sdl, "`sdl` expected if `engine` not given"
            engine = Engine(sdl=sdl, schema_name=schema_name)

        assert engine, "`engine` expected if `sdl` not given"

        self.engine = engine

        if context is None:
            context = {}

        if graphiql is True:
            graphiql = GraphiQL()
        elif not graphiql:
            graphiql = None

        assert graphiql is None or isinstance(graphiql, GraphiQL)

        if subscriptions is True:
            subscriptions = Subscriptions(path="/subscriptions")
        elif not subscriptions:
            subscriptions = None

        assert subscriptions is None or isinstance(subscriptions,
                                                   Subscriptions)

        router = Router()

        if graphiql and graphiql.path is not None:
            router.add_route(path=graphiql.path, endpoint=GraphiQLEndpoint)

        router.add_route(path=path, endpoint=GraphQLEndpoint)

        if subscriptions is not None:
            router.add_websocket_route(path=subscriptions.path,
                                       endpoint=SubscriptionEndpoint)

        config = GraphQLConfig(
            engine=self.engine,
            context=context,
            graphiql=graphiql,
            path=path,
            subscriptions=subscriptions,
        )

        self.app = GraphQLMiddleware(router, config=config)
        self.lifespan = Lifespan(on_startup=self.startup)

        self._started_up = False
예제 #2
0
async def init_app(app: Application):
    swagger = Router()
    swagger.add_route('/schema/{chain}/{network}',
                      custom_schema,
                      include_in_schema=False)

    create_swagger(apispec, router=swagger)
    app.mount('/', swagger)
예제 #3
0
def swagger_ui(
    schema_url: str = "/openapi.json",
    swagger_ui_title: str = "Piccolo Swagger UI",
    csrf_cookie_name: t.Optional[str] = DEFAULT_COOKIE_NAME,
    csrf_header_name: t.Optional[str] = DEFAULT_HEADER_NAME,
):
    """
    Even though ASGI frameworks such as FastAPI and BlackSheep have endpoints
    for viewing OpenAPI / Swagger docs, out of the box they don't work well
    with some Piccolo middleware (namely CSRF middleware, which requires
    Swagger UI to add the CSRF token to the header of each API call).

    By using this endpoint instead, it will work correctly with CSRF.

    **FastAPI example**

    .. code-block:: python

        from fastapi import FastAPI
        from piccolo_api.openapi.endpoints import swagger_ui

        # By setting these values to None, we disable the builtin endpoints.
        app = FastAPI(docs_url=None, redoc_url=None)

        app.mount('/docs', swagger_ui())

    :param schema_url:
        The URL to the OpenAPI schema.
    :param csrf_cookie_name:
        The name of the CSRF cookie.
    :param csrf_header_name:
        The HTTP header name which the CSRF cookie value will be added to.

    """

    # We return a router, because it's effectively a mini ASGI
    # app, which can be mounted in any ASGI app which supports mounting.
    router = Router()

    class DocsEndpoint(HTTPEndpoint):
        def get(self, request: Request):
            template = ENVIRONMENT.get_template("swagger_ui.html.jinja")
            html = template.render(
                schema_url=schema_url,
                swagger_ui_title=swagger_ui_title,
                csrf_cookie_name=csrf_cookie_name,
                csrf_header_name=csrf_header_name,
            )
            return HTMLResponse(content=html)

    class OAuthRedirectEndpoint(HTTPEndpoint):
        def get(self, request: Request):
            return get_swagger_ui_oauth2_redirect_html()

    router.add_route("/", endpoint=DocsEndpoint)
    router.add_route("/oauth2-redirect/", endpoint=OAuthRedirectEndpoint)

    return router
예제 #4
0
def create_swagger(apispec: APISpec, *, router: Router = None):
    handler = create_schema_handler(apispec)
    router.add_route('/schema', handler, include_in_schema=False)

    router.mount(
        path='/',
        app=StaticFiles(packages=[__package__], html=True),
        name='statics',
    )

    return router
예제 #5
0
class BackendStarlette(ABCBackend):
    def __init__(self, api):
        self.api = api
        self.router = Router()

    def bind(self, app):
        """Bind the current API to given Starlette app."""
        app.router.routes.append(Mount(self.api.prefix, self.router))

    def register(self, endpoint, *paths, methods=None):
        """Register the given Endpoint in routing."""

        for path in paths:
            self.router.add_route(path,
                                  endpoint.as_view(self.api),
                                  methods=methods)

        return endpoint

    def to_response(self, response, status_code=200, **options):
        """Prepare a response."""
        if isinstance(response, Response):
            return response

        return JSONResponse(response, status_code=status_code, **options)

    def get_params(self, request):
        """Get Path params from the given request."""
        return dict(request.path_params)

    def get_query(self, request):
        """Get Query params from the given request."""
        return dict(request.query_params)

    async def get_data(self, request):
        """Get data from the given request."""
        content_type = request.headers.get('content-type')
        if content_type in {
                'application/x-www-form-urlencoded', 'multipart/form-data'
        }:
            return await request.form()

        if content_type == 'application/json':
            return await request.json()

        data = await request.body()
        return data.decode('utf-8')
예제 #6
0
def build_api() -> Router:
    from . import address, block, fee, stats, tx, wallet, status

    # noinspection PyShadowingNames
    api = Router()
    api.mount('/{chain}/{network}/address', address.api)
    api.mount('/{chain}/{network}/block', block.api)
    api.mount('/{chain}/{network}/fee', fee.api)
    api.mount('/{chain}/{network}/stats', stats.api)
    api.mount('/{chain}/{network}/tx', tx.api)
    api.mount('/{chain}/{network}/wallet', wallet.api)
    api.mount('/status', status.api)

    api.add_route('/{chain}/{network}/block',
                  block.stream_blocks,
                  include_in_schema=False)
    api.add_route('/{chain}/{network}/tx',
                  tx.stream_transactions,
                  include_in_schema=False)
    api.add_route('/{chain}/{network}/wallet',
                  wallet.create_wallet,
                  methods=['POST'],
                  include_in_schema=False)

    return api
예제 #7
0
class TartifletteApp:
    def __init__(
        self,
        *,
        engine: Engine = None,
        sdl: str = None,
        graphiql: typing.Union[None, bool, GraphiQL] = True,
        path: str = "/",
        subscriptions: typing.Union[bool, Subscriptions] = None,
        context: dict = None,
        schema_name: str = "default",
    ) -> None:
        if engine is None:
            assert sdl, "`sdl` expected if `engine` not given"
            engine = Engine(sdl=sdl, schema_name=schema_name)

        assert engine, "`engine` expected if `sdl` not given"

        self.engine = engine

        if context is None:
            context = {}

        if graphiql is True:
            graphiql = GraphiQL()
        elif not graphiql:
            graphiql = None

        assert graphiql is None or isinstance(graphiql, GraphiQL)

        if subscriptions is True:
            subscriptions = Subscriptions(path="/subscriptions")
        elif not subscriptions:
            subscriptions = None

        assert subscriptions is None or isinstance(subscriptions,
                                                   Subscriptions)

        self.router = Router(on_startup=[self.startup])

        if graphiql and graphiql.path is not None:
            self.router.add_route(path=graphiql.path,
                                  endpoint=GraphiQLEndpoint)

        self.router.add_route(path=path, endpoint=GraphQLEndpoint)

        if subscriptions is not None:
            self.router.add_websocket_route(path=subscriptions.path,
                                            endpoint=SubscriptionEndpoint)

        config = GraphQLConfig(
            engine=self.engine,
            context=context,
            graphiql=graphiql,
            path=path,
            subscriptions=subscriptions,
        )

        self.app = GraphQLMiddleware(self.router, config=config)

        self._started_up = False

    async def startup(self) -> None:
        await self.engine.cook()
        self._started_up = True

    async def __call__(self, scope: Scope, receive: Receive,
                       send: Send) -> None:
        if scope["type"] == "lifespan":
            await self.router.lifespan(scope, receive, send)
        else:
            if not self._started_up:
                raise RuntimeError(
                    "GraphQL engine is not ready.\n\n"
                    "HINT: you must register the startup event handler on the "
                    "parent ASGI application.\n"
                    "Starlette example:\n\n"
                    "   app.mount('/graphql', graphql)\n"
                    "   app.add_event_handler('startup', graphql.startup)")
            await self.app(scope, receive, send)
예제 #8
0
class Starlette:
    def __init__(self,
                 debug: bool = False,
                 routes: typing.List[BaseRoute] = None) -> None:
        self._debug = debug
        self.router = Router(routes)
        self.exception_middleware = ExceptionMiddleware(self.router,
                                                        debug=debug)
        self.error_middleware = ServerErrorMiddleware(
            self.exception_middleware, debug=debug)

    @property
    def routes(self) -> typing.List[BaseRoute]:
        return self.router.routes

    @property
    def debug(self) -> bool:
        return self._debug

    @debug.setter
    def debug(self, value: bool) -> None:
        self._debug = value
        self.exception_middleware.debug = value
        self.error_middleware.debug = value

    def on_event(self, event_type: str) -> typing.Callable:
        return self.router.lifespan.on_event(event_type)

    def mount(self, path: str, app: ASGIApp, name: str = None) -> None:
        self.router.mount(path, app=app, name=name)

    def host(self, host: str, app: ASGIApp, name: str = None) -> None:
        self.router.host(host, app=app, name=name)

    def add_middleware(self, middleware_class: type,
                       **kwargs: typing.Any) -> None:
        self.error_middleware.app = middleware_class(self.error_middleware.app,
                                                     **kwargs)

    def add_exception_handler(
        self,
        exc_class_or_status_code: typing.Union[int, typing.Type[Exception]],
        handler: typing.Callable,
    ) -> None:
        if exc_class_or_status_code in (500, Exception):
            self.error_middleware.handler = handler
        else:
            self.exception_middleware.add_exception_handler(
                exc_class_or_status_code, handler)

    def add_event_handler(self, event_type: str,
                          func: typing.Callable) -> None:
        self.router.lifespan.add_event_handler(event_type, func)

    def add_route(
        self,
        path: str,
        route: typing.Callable,
        methods: typing.List[str] = None,
        name: str = None,
        include_in_schema: bool = True,
    ) -> None:
        self.router.add_route(path,
                              route,
                              methods=methods,
                              name=name,
                              include_in_schema=include_in_schema)

    def add_websocket_route(self,
                            path: str,
                            route: typing.Callable,
                            name: str = None) -> None:
        self.router.add_websocket_route(path, route, name=name)

    def exception_handler(
        self, exc_class_or_status_code: typing.Union[int,
                                                     typing.Type[Exception]]
    ) -> typing.Callable:
        def decorator(func: typing.Callable) -> typing.Callable:
            self.add_exception_handler(exc_class_or_status_code, func)
            return func

        return decorator

    def route(
        self,
        path: str,
        methods: typing.List[str] = None,
        name: str = None,
        include_in_schema: bool = True,
    ) -> 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,
            )
            return func

        return decorator

    def websocket_route(self, path: str, name: str = None) -> typing.Callable:
        def decorator(func: typing.Callable) -> typing.Callable:
            self.router.add_websocket_route(path, func, name=name)
            return func

        return decorator

    def middleware(self, middleware_type: str) -> typing.Callable:
        assert (middleware_type == "http"
                ), 'Currently only middleware("http") is supported.'

        def decorator(func: typing.Callable) -> typing.Callable:
            self.add_middleware(BaseHTTPMiddleware, dispatch=func)
            return func

        return decorator

    def url_path_for(self, name: str, **path_params: str) -> URLPath:
        return self.router.url_path_for(name, **path_params)

    async def __call__(self, scope: Scope, receive: Receive,
                       send: Send) -> None:
        scope["app"] = self
        await self.error_middleware(scope, receive, send)
예제 #9
0
class Starlette:
    """
    Creates an application instance.

    **Parameters:**

    * **debug** - Boolean indicating if debug tracebacks should be returned on errors.
    * **routes** - A list of routes to serve incoming HTTP and WebSocket requests.
    * **middleware** - A list of middleware to run for every request. A starlette
    application will always automatically include two middleware classes.
    `ServerErrorMiddleware` is added the very outermost middleware, to handle
    any uncaught errors occuring anywhere in the entire stack.
    `ExceptionMiddleware` is added as the very innermost middleware, to deal
    with handled exception cases occuring in the routing or endpoints.
    * **exception_handlers** - A dictionary mapping either integer status codes,
    or exception class types onto callables which handle the exceptions.
    Exception handler callables should be of the form `handler(request, exc) -> response`
    and may be be either standard functions, or async functions.
    * **on_startup** - A list of callables to run on application startup.
    Startup handler callables do not take any arguments, and may be be either
    standard functions, or async functions.
    * **on_shutdown** - A list of callables to run on application shutdown.
    Shutdown handler callables do not take any arguments, and may be be either
    standard functions, or async functions.
    """
    def __init__(
        self,
        debug: bool = False,
        routes: typing.Sequence[BaseRoute] = None,
        middleware: typing.Sequence[Middleware] = None,
        exception_handlers: typing.Dict[typing.Union[int,
                                                     typing.Type[Exception]],
                                        typing.Callable] = None,
        on_startup: typing.Sequence[typing.Callable] = None,
        on_shutdown: typing.Sequence[typing.Callable] = None,
        lifespan: typing.Callable[["Starlette"], typing.AsyncGenerator] = None,
    ) -> None:
        # The lifespan context function is a newer style that replaces
        # on_startup / on_shutdown handlers. Use one or the other, not both.
        assert lifespan is None or (
            on_startup is None and on_shutdown is None
        ), "Use either 'lifespan' or 'on_startup'/'on_shutdown', not both."

        self._debug = debug
        self.state = State()
        self.router = Router(routes,
                             on_startup=on_startup,
                             on_shutdown=on_shutdown,
                             lifespan=lifespan)
        self.exception_handlers = ({} if exception_handlers is None else
                                   dict(exception_handlers))
        self.user_middleware = [] if middleware is None else list(middleware)
        self.middleware_stack = self.build_middleware_stack()

    def build_middleware_stack(self) -> ASGIApp:
        debug = self.debug
        error_handler = None
        exception_handlers = {}

        for key, value in self.exception_handlers.items():
            if key in (500, Exception):
                error_handler = value
            else:
                exception_handlers[key] = value

        middleware = ([
            Middleware(
                ServerErrorMiddleware,
                handler=error_handler,
                debug=debug,
            )
        ] + self.user_middleware + [
            Middleware(
                ExceptionMiddleware,
                handlers=exception_handlers,
                debug=debug,
            )
        ])

        app = self.router
        for cls, options in reversed(middleware):
            app = cls(app=app, **options)
        return app

    @property
    def routes(self) -> typing.List[BaseRoute]:
        return self.router.routes

    @property
    def debug(self) -> bool:
        return self._debug

    @debug.setter
    def debug(self, value: bool) -> None:
        self._debug = value
        self.middleware_stack = self.build_middleware_stack()

    def url_path_for(self, name: str, **path_params: str) -> URLPath:
        return self.router.url_path_for(name, **path_params)

    async def __call__(self, scope: Scope, receive: Receive,
                       send: Send) -> None:
        scope["app"] = self
        await self.middleware_stack(scope, receive, send)

    # The following usages are now discouraged in favour of configuration
    #  during Starlette.__init__(...)
    def on_event(self, event_type: str) -> typing.Callable:
        return self.router.on_event(event_type)

    def mount(self, path: str, app: ASGIApp, name: str = None) -> None:
        self.router.mount(path, app=app, name=name)

    def host(self, host: str, app: ASGIApp, name: str = None) -> None:
        self.router.host(host, app=app, name=name)

    def add_middleware(self, middleware_class: type,
                       **options: typing.Any) -> None:
        self.user_middleware.insert(0, Middleware(middleware_class, **options))
        self.middleware_stack = self.build_middleware_stack()

    def add_exception_handler(
        self,
        exc_class_or_status_code: typing.Union[int, typing.Type[Exception]],
        handler: typing.Callable,
    ) -> None:
        self.exception_handlers[exc_class_or_status_code] = handler
        self.middleware_stack = self.build_middleware_stack()

    def add_event_handler(self, event_type: str,
                          func: typing.Callable) -> None:
        self.router.add_event_handler(event_type, func)

    def add_route(
        self,
        path: str,
        route: typing.Callable,
        methods: typing.List[str] = None,
        name: str = None,
        include_in_schema: bool = True,
    ) -> None:
        self.router.add_route(path,
                              route,
                              methods=methods,
                              name=name,
                              include_in_schema=include_in_schema)

    def add_websocket_route(self,
                            path: str,
                            route: typing.Callable,
                            name: str = None) -> None:
        self.router.add_websocket_route(path, route, name=name)

    def exception_handler(
        self, exc_class_or_status_code: typing.Union[int,
                                                     typing.Type[Exception]]
    ) -> typing.Callable:
        def decorator(func: typing.Callable) -> typing.Callable:
            self.add_exception_handler(exc_class_or_status_code, func)
            return func

        return decorator

    def route(
        self,
        path: str,
        methods: typing.List[str] = None,
        name: str = None,
        include_in_schema: bool = True,
    ) -> 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,
            )
            return func

        return decorator

    def websocket_route(self, path: str, name: str = None) -> typing.Callable:
        def decorator(func: typing.Callable) -> typing.Callable:
            self.router.add_websocket_route(path, func, name=name)
            return func

        return decorator

    def middleware(self, middleware_type: str) -> typing.Callable:
        assert (middleware_type == "http"
                ), 'Currently only middleware("http") is supported.'

        def decorator(func: typing.Callable) -> typing.Callable:
            self.add_middleware(BaseHTTPMiddleware, dispatch=func)
            return func

        return decorator
예제 #10
0
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
# DEALINGS IN THE SOFTWARE.

import logging
import typing

from starlette.requests import Request
from starlette.routing import Router

from ...db import session
from ...executor import async_
from ...middlewares import WithTemplates
from ..models import IdentityPool
from .endpoint import endpoint_handler

logger = logging.getLogger(__name__)
routes = Router()


@routes.route("/", name="index", methods=["GET"])
async def index(request: Request):
    pools = await async_(lambda: session.query(IdentityPool).all())()
    return typing.cast(WithTemplates, request).templates("index.html", {"pools": pools})


routes.add_route("/", endpoint_handler, name="endpoint", methods=["POST"])
예제 #11
0
class Starlette:
    def __init__(self,
                 debug: bool = False,
                 template_directory: str = None) -> None:
        self._debug = debug
        self.router = Router()
        self.exception_middleware = ExceptionMiddleware(self.router,
                                                        debug=debug)
        self.error_middleware = ServerErrorMiddleware(
            self.exception_middleware, debug=debug)
        self.lifespan_middleware = LifespanMiddleware(self.error_middleware)
        self.schema_generator = None  # type: typing.Optional[BaseSchemaGenerator]
        self.template_env = self.load_template_env(template_directory)

    @property
    def routes(self) -> typing.List[BaseRoute]:
        return self.router.routes

    @property
    def debug(self) -> bool:
        return self._debug

    @debug.setter
    def debug(self, value: bool) -> None:
        self._debug = value
        self.exception_middleware.debug = value
        self.error_middleware.debug = value

    def load_template_env(self, template_directory: str = None) -> typing.Any:
        if template_directory is None:
            return None

        # Import jinja2 lazily.
        import jinja2

        @jinja2.contextfunction
        def url_for(context: dict, name: str,
                    **path_params: typing.Any) -> str:
            request = context["request"]
            return request.url_for(name, **path_params)

        loader = jinja2.FileSystemLoader(str(template_directory))
        env = jinja2.Environment(loader=loader, autoescape=True)
        env.globals["url_for"] = url_for
        return env

    def get_template(self, name: str) -> typing.Any:
        return self.template_env.get_template(name)

    @property
    def schema(self) -> dict:
        assert self.schema_generator is not None
        return self.schema_generator.get_schema(self.routes)

    def on_event(self, event_type: str) -> typing.Callable:
        return self.lifespan_middleware.on_event(event_type)

    def mount(self, path: str, app: ASGIApp, name: str = None) -> None:
        self.router.mount(path, app=app, name=name)

    def host(self, host: str, app: ASGIApp, name: str = None) -> None:
        self.router.host(host, app=app, name=name)

    def add_middleware(self, middleware_class: type,
                       **kwargs: typing.Any) -> None:
        self.error_middleware.app = middleware_class(self.error_middleware.app,
                                                     **kwargs)

    def add_exception_handler(
        self,
        exc_class_or_status_code: typing.Union[int, typing.Type[Exception]],
        handler: typing.Callable,
    ) -> None:
        if exc_class_or_status_code in (500, Exception):
            self.error_middleware.handler = handler
        else:
            self.exception_middleware.add_exception_handler(
                exc_class_or_status_code, handler)

    def add_event_handler(self, event_type: str,
                          func: typing.Callable) -> None:
        self.lifespan_middleware.add_event_handler(event_type, func)

    def add_route(
        self,
        path: str,
        route: typing.Callable,
        methods: typing.List[str] = None,
        name: str = None,
        include_in_schema: bool = True,
    ) -> None:
        self.router.add_route(path,
                              route,
                              methods=methods,
                              name=name,
                              include_in_schema=include_in_schema)

    def add_websocket_route(self,
                            path: str,
                            route: typing.Callable,
                            name: str = None) -> None:
        self.router.add_websocket_route(path, route, name=name)

    def exception_handler(
        self, exc_class_or_status_code: typing.Union[int,
                                                     typing.Type[Exception]]
    ) -> typing.Callable:
        def decorator(func: typing.Callable) -> typing.Callable:
            self.add_exception_handler(exc_class_or_status_code, func)
            return func

        return decorator

    def route(
        self,
        path: str,
        methods: typing.List[str] = None,
        name: str = None,
        include_in_schema: bool = True,
    ) -> 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,
            )
            return func

        return decorator

    def websocket_route(self, path: str, name: str = None) -> typing.Callable:
        def decorator(func: typing.Callable) -> typing.Callable:
            self.router.add_websocket_route(path, func, name=name)
            return func

        return decorator

    def middleware(self, middleware_type: str) -> typing.Callable:
        assert (middleware_type == "http"
                ), 'Currently only middleware("http") is supported.'

        def decorator(func: typing.Callable) -> typing.Callable:
            self.add_middleware(BaseHTTPMiddleware, dispatch=func)
            return func

        return decorator

    def url_path_for(self, name: str, **path_params: str) -> URLPath:
        return self.router.url_path_for(name, **path_params)

    def __call__(self, scope: Scope) -> ASGIInstance:
        scope["app"] = self
        return self.lifespan_middleware(scope)
예제 #12
0
from starlette.responses import RedirectResponse

from .views.index import Index
from .views.dashboard import Dashboard
from .views.signup import SignUp, SignOut
from .views.user import UserList, UserDetail

routes = Router()


def init_app(app: Starlette, path="/", name=None):
    app.mount(path, routes, name=name)
    return app


routes.add_route("/", Index)
routes.add_route("/signup", SignUp)
routes.add_route("/signout", SignOut)
routes.add_route("/dashboard", Dashboard)
routes.add_route("/users", UserList)
routes.add_route("/users/{id:int}", UserDetail)


@routes.route("/pages/{page_path:path}")
def page_assets(request: Request):
    from property_app.config import get_config

    config = get_config()
    dist_root = config.DIST_ROOT

    page_path = request.path_params["page_path"]
예제 #13
0
class Starlette:
    def __init__(
        self,
        debug: bool = False,
        routes: typing.List[BaseRoute] = None,
        middleware: typing.List[Middleware] = None,
        exception_handlers: typing.Dict[typing.Union[int,
                                                     typing.Type[Exception]],
                                        typing.Callable] = None,
        on_startup: typing.List[typing.Callable] = None,
        on_shutdown: typing.List[typing.Callable] = None,
    ) -> None:
        self._debug = debug
        self.state = State()
        self.router = Router(routes,
                             on_startup=on_startup,
                             on_shutdown=on_shutdown)
        self.exception_handlers = ({} if exception_handlers is None else
                                   dict(exception_handlers))
        self.user_middleware = list(middleware or [])
        self.middleware_stack = self.build_middleware_stack()

    def build_middleware_stack(self) -> ASGIApp:
        debug = self.debug
        error_handler = None
        exception_handlers = {}

        for key, value in self.exception_handlers.items():
            if key in (500, Exception):
                error_handler = value
            else:
                exception_handlers[key] = value

        server_errors = Middleware(
            ServerErrorMiddleware,
            options={
                "handler": error_handler,
                "debug": debug
            },
        )
        exceptions = Middleware(
            ExceptionMiddleware,
            options={
                "handlers": exception_handlers,
                "debug": debug
            },
        )

        middleware = [server_errors] + self.user_middleware + [exceptions]

        app = self.router
        for cls, options, enabled in reversed(middleware):
            if enabled:
                app = cls(app=app, **options)
        return app

    @property
    def routes(self) -> typing.List[BaseRoute]:
        return self.router.routes

    @property
    def debug(self) -> bool:
        return self._debug

    @debug.setter
    def debug(self, value: bool) -> None:
        self._debug = value
        self.middleware_stack = self.build_middleware_stack()

    def on_event(self, event_type: str) -> typing.Callable:
        return self.router.lifespan.on_event(event_type)

    def mount(self, path: str, app: ASGIApp, name: str = None) -> None:
        self.router.mount(path, app=app, name=name)

    def host(self, host: str, app: ASGIApp, name: str = None) -> None:
        self.router.host(host, app=app, name=name)

    def add_middleware(self, middleware_class: type,
                       **kwargs: typing.Any) -> None:
        self.user_middleware.insert(
            0, Middleware(middleware_class, options=kwargs))
        self.middleware_stack = self.build_middleware_stack()

    def add_exception_handler(
        self,
        exc_class_or_status_code: typing.Union[int, typing.Type[Exception]],
        handler: typing.Callable,
    ) -> None:
        self.exception_handlers[exc_class_or_status_code] = handler
        self.middleware_stack = self.build_middleware_stack()

    def add_event_handler(self, event_type: str,
                          func: typing.Callable) -> None:
        self.router.lifespan.add_event_handler(event_type, func)

    def add_route(
        self,
        path: str,
        route: typing.Callable,
        methods: typing.List[str] = None,
        name: str = None,
        include_in_schema: bool = True,
    ) -> None:
        self.router.add_route(path,
                              route,
                              methods=methods,
                              name=name,
                              include_in_schema=include_in_schema)

    def add_websocket_route(self,
                            path: str,
                            route: typing.Callable,
                            name: str = None) -> None:
        self.router.add_websocket_route(path, route, name=name)

    def exception_handler(
        self, exc_class_or_status_code: typing.Union[int,
                                                     typing.Type[Exception]]
    ) -> typing.Callable:
        def decorator(func: typing.Callable) -> typing.Callable:
            self.add_exception_handler(exc_class_or_status_code, func)
            return func

        return decorator

    def route(
        self,
        path: str,
        methods: typing.List[str] = None,
        name: str = None,
        include_in_schema: bool = True,
    ) -> 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,
            )
            return func

        return decorator

    def websocket_route(self, path: str, name: str = None) -> typing.Callable:
        def decorator(func: typing.Callable) -> typing.Callable:
            self.router.add_websocket_route(path, func, name=name)
            return func

        return decorator

    def middleware(self, middleware_type: str) -> typing.Callable:
        assert (middleware_type == "http"
                ), 'Currently only middleware("http") is supported.'

        def decorator(func: typing.Callable) -> typing.Callable:
            self.add_middleware(BaseHTTPMiddleware, dispatch=func)
            return func

        return decorator

    def url_path_for(self, name: str, **path_params: str) -> URLPath:
        return self.router.url_path_for(name, **path_params)

    async def __call__(self, scope: Scope, receive: Receive,
                       send: Send) -> None:
        scope["app"] = self
        await self.middleware_stack(scope, receive, send)
예제 #14
0
class Starlette:
    def __init__(self, debug: bool = False) -> None:
        self.router = Router()
        self.lifespan_handler = LifespanHandler()
        self.app = self.router
        self.exception_middleware = ExceptionMiddleware(self.router, debug=debug)

    @property
    def routes(self) -> typing.List[BaseRoute]:
        return self.router.routes

    @property
    def debug(self) -> bool:
        return self.exception_middleware.debug

    @debug.setter
    def debug(self, value: bool) -> None:
        self.exception_middleware.debug = value

    def on_event(self, event_type: str) -> typing.Callable:
        return self.lifespan_handler.on_event(event_type)

    def mount(self, path: str, app: ASGIApp) -> None:
        self.router.mount(path, app=app)

    def add_middleware(self, middleware_class: type, **kwargs: typing.Any) -> None:
        self.exception_middleware.app = middleware_class(self.app, **kwargs)

    def add_exception_handler(self, exc_class: type, handler: typing.Callable) -> None:
        self.exception_middleware.add_exception_handler(exc_class, handler)

    def add_event_handler(self, event_type: str, func: typing.Callable) -> None:
        self.lifespan_handler.add_event_handler(event_type, func)

    def add_route(
        self, path: str, route: typing.Callable, methods: typing.List[str] = None
    ) -> None:
        self.router.add_route(path, route, methods=methods)

    def add_graphql_route(self, path: str, schema: typing.Any) -> None:
        self.router.add_graphql_route(path, schema)

    def add_websocket_route(self, path: str, route: typing.Callable) -> None:
        self.router.add_websocket_route(path, route)

    def exception_handler(self, exc_class: type) -> typing.Callable:
        def decorator(func: typing.Callable) -> typing.Callable:
            self.add_exception_handler(exc_class, func)
            return func

        return decorator

    def route(self, path: str, methods: typing.List[str] = None) -> typing.Callable:
        def decorator(func: typing.Callable) -> typing.Callable:
            self.router.add_route(path, func, methods=methods)
            return func

        return decorator

    def websocket_route(self, path: str) -> typing.Callable:
        def decorator(func: typing.Callable) -> typing.Callable:
            self.router.add_websocket_route(path, func)
            return func

        return decorator

    def url_path_for(self, name: str, **path_params: str) -> URL:
        return self.router.url_path_for(name, **path_params)

    def __call__(self, scope: Scope) -> ASGIInstance:
        scope["app"] = self
        scope["router"] = self.router
        if scope["type"] == "lifespan":
            return self.lifespan_handler(scope)
        return self.exception_middleware(scope)
예제 #15
0
class AppUnit(RouterMixin):
    """
    Creates an application instance.

    **Parameters:**

    * **debug** - Boolean indicating if debug tracebacks should be returned on errors.
    * **exception_handlers** - A dictionary mapping either integer status codes,
    or exception class types onto callables which handle the exceptions.
    * **middleware** - A list of middleware (Middleware class or function) to run for every request.
    * **response_class** - Default response class used in routes.
    Default is `JSONResponse.`
    * **modules** - A list of configuration modules.
    * **auto_bind** - Whether to automatically bind missing types.
    """

    def __init__(
        self,
        debug: bool = False,
        exception_handlers: Dict[Union[int, Type[Exception]], Callable] = None,
        middleware: Sequence[Union[Middleware, Callable]] = None,
        response_class: Optional[Type[Response]] = None,
        modules: Optional[List[ModuleType]] = None,
        auto_bind: bool = False,
    ):
        self._debug = debug
        self.injector = Injector(auto_bind=auto_bind)
        self.router = Router()
        self.exception_handlers = {
            exc_type: self._inject_exception_handler(handler)
            for exc_type, handler in (
                {} if exception_handlers is None else dict(exception_handlers)
            ).items()
        }
        self.user_middleware = (
            []
            if middleware is None
            else [self.prepare_middleware(m) for m in middleware]
        )
        self.user_middleware.insert(0, Middleware(RequestScopeMiddleware))
        self.middleware_stack = self.build_middleware_stack()
        self.cli = click.Group()
        self.response_class = response_class or JSONResponse

        self.injector.binder.bind(AppUnit, to=self, scope=SingletonScope)
        self.injector.binder.bind(Injector, to=self.injector, scope=SingletonScope)
        self.injector.binder.bind(Request, to=context.get_current_request)

        modules = modules or []
        for module in modules:
            self.add_module(module)

    async def __call__(self, scope: ASGIScope, receive: Receive, send: Send) -> None:
        scope["app"] = self
        await self.middleware_stack(scope, receive, send)

    @property
    def debug(self) -> bool:
        return self._debug

    @debug.setter
    def debug(self, value: bool) -> None:
        self._debug = value
        self.middleware_stack = self.build_middleware_stack()

    def add_module(self, module: ModuleType) -> None:
        self.injector.binder.install(module)

    def lookup(
        self, interface: Type[InterfaceType], *, scope: Optional[ScopeType] = None
    ) -> InterfaceType:
        return self.injector.get(interface, scope=scope)

    def bind(
        self,
        interface: Type[InterfaceType],
        to: Optional[Any] = None,
        *,
        scope: Optional[ScopeType] = None,
    ) -> None:
        self.injector.binder.bind(interface, to=to, scope=scope)

    def singleton(
        self, interface: Type[InterfaceType], to: Optional[Any] = None
    ) -> None:
        self.injector.binder.bind(interface, to=to, scope=SingletonScope)

    def add_startup_event(self, event: Callable) -> None:
        event = self._inject_event(event)
        self.router.on_startup.append(event)

    def add_shutdown_event(self, event: Callable) -> None:
        event = self._inject_event(event)
        self.router.on_shutdown.append(event)

    def on_startup(self) -> Callable:
        def decorator(event: Callable):
            self.add_startup_event(event)
            return event

        return decorator

    def on_shutdown(self) -> Callable:
        def decorator(event: Callable):
            self.add_shutdown_event(event)
            return event

        return decorator

    async def startup(self) -> None:
        await self.router.startup()

    async def shutdown(self) -> None:
        await self.router.shutdown()

    def add_route(
        self,
        path: str,
        route: Callable,
        methods: List[str] = None,
        name: str = None,
        include_in_schema: bool = True,
    ) -> None:
        self.router.add_route(
            path,
            endpoint=self._inject_route(route),
            methods=methods,
            name=name,
            include_in_schema=include_in_schema,
        )

    def add_exception_handler(
        self, status_or_exc: Union[int, Type[Exception]], *, handler: Callable
    ) -> None:
        self.exception_handlers[status_or_exc] = self._inject_exception_handler(
            handler=handler
        )
        self.middleware_stack = self.build_middleware_stack()

    def exception_handler(self, status_or_exc: Union[int, Type[Exception]]) -> Callable:
        def decorator(handler: Callable) -> Callable:
            self.add_exception_handler(status_or_exc, handler=handler)
            return handler

        return decorator

    def add_middleware(self, middleware: Union[Middleware, Callable]) -> None:
        self.user_middleware.insert(0, self.prepare_middleware(middleware))
        self.middleware_stack = self.build_middleware_stack()

    def middleware(self) -> Callable:
        def decorator(middleware: Callable) -> Callable:
            self.add_middleware(middleware)
            return middleware

        return decorator

    def prepare_middleware(self, middleware: Union[Middleware, Callable]) -> Middleware:
        if inspect.isfunction(middleware):
            middleware = Middleware(
                BaseHTTPMiddleware,
                dispatch=self._inject_middleware(cast(Callable, middleware)),
            )
        return cast(Middleware, middleware)

    def build_middleware_stack(self) -> ASGIApp:
        debug = self.debug
        error_handler = None
        exception_handlers = {}

        for key, value in self.exception_handlers.items():
            if key in (500, Exception):
                error_handler = value
            else:
                exception_handlers[key] = value

        middleware = (
            [Middleware(ServerErrorMiddleware, handler=error_handler, debug=debug)]
            + self.user_middleware
            + [
                Middleware(
                    ExceptionMiddleware, handlers=exception_handlers, debug=debug
                )
            ]
        )

        app = self.router
        for cls, options in reversed(middleware):
            app = cls(app=app, **options)
        return app

    def add_command(
        self,
        cmd: Callable,
        *,
        name: Optional[str] = None,
        cli: Optional[click.Group] = None,
        lifespan: bool = True,
    ) -> None:
        make_command = click.command()
        cli = cli or self.cli
        cli.add_command(
            make_command(self._inject_command(cmd, lifespan=lifespan)), name=name
        )

    def command(self, name: Optional[str] = None, *, lifespan: bool = True) -> Callable:
        def decorator(cmd: Callable) -> Callable:
            self.add_command(cmd, name=name, lifespan=lifespan)
            return cmd

        return decorator

    def run(self, host: str = "localhost", port: int = 8000, **kwargs) -> None:
        """
        Run Uvicorn server.
        """
        if uvicorn is None:
            raise RuntimeError("`uvicorn` is not installed.")

        uvicorn.run(app=self, host=host, port=port, **kwargs)

    def main(
        self,
        args: Optional[List[str]] = None,
        prog_name: Optional[str] = None,
        complete_var: Optional[str] = None,
        standalone_mode: bool = True,
        **extra,
    ) -> int:
        """
        Start application CLI.
        """
        return self.cli.main(
            args=args,
            prog_name=prog_name,
            complete_var=complete_var,
            standalone_mode=standalone_mode,
            **extra,
        )

    ############################################
    # Dependency Injection helpers
    ############################################

    def _inject_event(self, event: Callable) -> Callable:
        def wrapper(func: Callable) -> Callable:
            @functools.wraps(func)
            async def wrapped():
                handler = self.injector.call_with_injection(inject(func))
                if inspect.iscoroutine(handler):
                    return await handler

            return wrapped

        return wrapper(event)

    def _inject_route(self, route: Callable) -> Callable:
        def wrapper(func: Callable) -> Callable:
            @functools.wraps(func)
            async def wrapped(_: Request):
                response = self.injector.call_with_injection(inject(func))
                if inspect.iscoroutine(response):
                    response = await response

                if isinstance(response, Response):
                    return response
                return self.response_class(content=response)

            return wrapped

        return wrapper(route)

    def _inject_exception_handler(self, handler: Callable) -> Callable:
        def wrapper(func: Callable) -> Callable:
            @functools.wraps(func)
            async def wrapped(request: Request, exc: Exception):
                return await self.injector.call_with_injection(
                    inject(func), args=(request, exc)
                )

            return wrapped

        return wrapper(handler)

    def _inject_middleware(self, middleware: Callable) -> Callable:
        def wrapper(func: Callable) -> Callable:
            @functools.wraps(func)
            async def wrapped(request: Request, call_next: Callable):
                return await self.injector.call_with_injection(
                    inject(func), args=(request, call_next)
                )

            return wrapped

        return wrapper(middleware)

    def _inject_command(self, cmd: Callable, lifespan: bool = True) -> Callable:
        def wrapper(func: Callable) -> Callable:
            @functools.wraps(func)
            def wrapped(*args, **kwargs):
                def run():
                    return self.injector.call_with_injection(
                        inject(func), args=args, kwargs=kwargs
                    )

                async def async_run():
                    if lifespan:
                        await self.startup()
                        try:
                            await run()
                        finally:
                            await self.shutdown()
                    else:
                        await run()

                if inspect.iscoroutinefunction(func):
                    return asyncio.run(async_run())
                return run()

            return wrapped

        return wrapper(cmd)