applications.py 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  1. from __future__ import annotations
  2. from collections.abc import Awaitable, Callable, Mapping, Sequence
  3. from typing import Any, ParamSpec, TypeVar
  4. from starlette.datastructures import State, URLPath
  5. from starlette.middleware import Middleware, _MiddlewareFactory
  6. from starlette.middleware.body_limit import RequestBodyLimitMiddleware
  7. from starlette.middleware.errors import ServerErrorMiddleware
  8. from starlette.middleware.exceptions import ExceptionMiddleware
  9. from starlette.requests import Request
  10. from starlette.responses import Response
  11. from starlette.routing import BaseRoute, Router
  12. from starlette.types import ASGIApp, ExceptionHandler, Lifespan, Receive, Scope, Send
  13. AppType = TypeVar("AppType", bound="Starlette")
  14. P = ParamSpec("P")
  15. class Starlette:
  16. """Creates an Starlette application."""
  17. def __init__(
  18. self: AppType,
  19. debug: bool = False,
  20. routes: Sequence[BaseRoute] | None = None,
  21. middleware: Sequence[Middleware] | None = None,
  22. exception_handlers: Mapping[Any, ExceptionHandler] | None = None,
  23. lifespan: Lifespan[AppType] | None = None,
  24. *,
  25. max_body_size: int | None = None,
  26. ) -> None:
  27. """Initializes the application.
  28. Parameters:
  29. debug: Boolean indicating if debug tracebacks should be returned on errors.
  30. routes: A list of routes to serve incoming HTTP and WebSocket requests.
  31. middleware: A list of middleware to run for every request. A starlette
  32. application will always automatically include two middleware classes.
  33. `ServerErrorMiddleware` is added as the very outermost middleware, to handle
  34. any uncaught errors occurring anywhere in the entire stack.
  35. `ExceptionMiddleware` is added as the very innermost middleware, to deal
  36. with handled exception cases occurring in the routing or endpoints.
  37. exception_handlers: A mapping of either integer status codes,
  38. or exception class types onto callables which handle the exceptions.
  39. Exception handler callables should be of the form
  40. `handler(request, exc) -> response` and may be either standard functions, or
  41. async functions.
  42. lifespan: A lifespan context function, which can be used to perform
  43. startup and shutdown tasks. This is a newer style that replaces the
  44. `on_startup` and `on_shutdown` handlers. Use one or the other, not both.
  45. max_body_size: Non-negative maximum total size in bytes of an HTTP request
  46. body. The default, `None`, does not limit request body size.
  47. """
  48. self.debug = debug
  49. self.state = State()
  50. self.router = Router(routes, lifespan=lifespan)
  51. self.max_body_size = max_body_size
  52. self.exception_handlers = {} if exception_handlers is None else dict(exception_handlers)
  53. self.user_middleware = [] if middleware is None else list(middleware)
  54. self.middleware_stack: ASGIApp | None = None
  55. def build_middleware_stack(self) -> ASGIApp:
  56. debug = self.debug
  57. error_handler = None
  58. exception_handlers: dict[Any, ExceptionHandler] = {}
  59. for key, value in self.exception_handlers.items():
  60. if key in (500, Exception):
  61. error_handler = value
  62. else:
  63. exception_handlers[key] = value
  64. middleware = [Middleware(ServerErrorMiddleware, handler=error_handler, debug=debug)]
  65. if self.max_body_size is not None:
  66. middleware.append(Middleware(RequestBodyLimitMiddleware, max_body_size=self.max_body_size))
  67. middleware += self.user_middleware
  68. middleware.append(Middleware(ExceptionMiddleware, handlers=exception_handlers, debug=debug))
  69. app = self.router
  70. for cls, args, kwargs in reversed(middleware):
  71. app = cls(app, *args, **kwargs)
  72. return app
  73. @property
  74. def routes(self) -> list[BaseRoute]:
  75. return self.router.routes
  76. def url_path_for(self, name: str, /, **path_params: Any) -> URLPath:
  77. return self.router.url_path_for(name, **path_params)
  78. async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
  79. scope["app"] = self
  80. if self.middleware_stack is None:
  81. self.middleware_stack = self.build_middleware_stack()
  82. await self.middleware_stack(scope, receive, send)
  83. def mount(self, path: str, app: ASGIApp, name: str | None = None) -> None:
  84. self.router.mount(path, app=app, name=name) # pragma: no cover
  85. def host(self, host: str, app: ASGIApp, name: str | None = None) -> None:
  86. self.router.host(host, app=app, name=name) # pragma: no cover
  87. def add_middleware(self, middleware_class: _MiddlewareFactory[P], *args: P.args, **kwargs: P.kwargs) -> None:
  88. if self.middleware_stack is not None: # pragma: no cover
  89. raise RuntimeError("Cannot add middleware after an application has started")
  90. self.user_middleware.insert(0, Middleware(middleware_class, *args, **kwargs))
  91. def add_exception_handler(
  92. self,
  93. exc_class_or_status_code: int | type[Exception],
  94. handler: ExceptionHandler,
  95. ) -> None: # pragma: no cover
  96. self.exception_handlers[exc_class_or_status_code] = handler
  97. def add_route(
  98. self,
  99. path: str,
  100. route: Callable[[Request], Awaitable[Response] | Response],
  101. methods: list[str] | None = None,
  102. name: str | None = None,
  103. include_in_schema: bool = True,
  104. ) -> None: # pragma: no cover
  105. self.router.add_route(path, route, methods=methods, name=name, include_in_schema=include_in_schema)