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
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)
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
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
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')
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
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)
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)
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
# 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"])
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)
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"]
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)
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)
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)