applications.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250
  1. from __future__ import annotations
  2. import sys
  3. import typing
  4. import warnings
  5. if sys.version_info >= (3, 10): # pragma: no cover
  6. from typing import ParamSpec
  7. else: # pragma: no cover
  8. from typing_extensions import ParamSpec
  9. from starlette.datastructures import State, URLPath
  10. from starlette.middleware import Middleware, _MiddlewareFactory
  11. from starlette.middleware.base import BaseHTTPMiddleware
  12. from starlette.middleware.errors import ServerErrorMiddleware
  13. from starlette.middleware.exceptions import ExceptionMiddleware
  14. from starlette.requests import Request
  15. from starlette.responses import Response
  16. from starlette.routing import BaseRoute, Router
  17. from starlette.types import ASGIApp, ExceptionHandler, Lifespan, Receive, Scope, Send
  18. from starlette.websockets import WebSocket
  19. AppType = typing.TypeVar("AppType", bound="Starlette")
  20. P = ParamSpec("P")
  21. class Starlette:
  22. """
  23. Creates an application instance.
  24. **Parameters:**
  25. * **debug** - Boolean indicating if debug tracebacks should be returned on errors.
  26. * **routes** - A list of routes to serve incoming HTTP and WebSocket requests.
  27. * **middleware** - A list of middleware to run for every request. A starlette
  28. application will always automatically include two middleware classes.
  29. `ServerErrorMiddleware` is added as the very outermost middleware, to handle
  30. any uncaught errors occurring anywhere in the entire stack.
  31. `ExceptionMiddleware` is added as the very innermost middleware, to deal
  32. with handled exception cases occurring in the routing or endpoints.
  33. * **exception_handlers** - A mapping of either integer status codes,
  34. or exception class types onto callables which handle the exceptions.
  35. Exception handler callables should be of the form
  36. `handler(request, exc) -> response` and may be either standard functions, or
  37. async functions.
  38. * **on_startup** - A list of callables to run on application startup.
  39. Startup handler callables do not take any arguments, and may be either
  40. standard functions, or async functions.
  41. * **on_shutdown** - A list of callables to run on application shutdown.
  42. Shutdown handler callables do not take any arguments, and may be either
  43. standard functions, or async functions.
  44. * **lifespan** - A lifespan context function, which can be used to perform
  45. startup and shutdown tasks. This is a newer style that replaces the
  46. `on_startup` and `on_shutdown` handlers. Use one or the other, not both.
  47. """
  48. def __init__(
  49. self: AppType,
  50. debug: bool = False,
  51. routes: typing.Sequence[BaseRoute] | None = None,
  52. middleware: typing.Sequence[Middleware] | None = None,
  53. exception_handlers: typing.Mapping[typing.Any, ExceptionHandler] | None = None,
  54. on_startup: typing.Sequence[typing.Callable[[], typing.Any]] | None = None,
  55. on_shutdown: typing.Sequence[typing.Callable[[], typing.Any]] | None = None,
  56. lifespan: Lifespan[AppType] | None = None,
  57. ) -> None:
  58. # The lifespan context function is a newer style that replaces
  59. # on_startup / on_shutdown handlers. Use one or the other, not both.
  60. assert lifespan is None or (
  61. on_startup is None and on_shutdown is None
  62. ), "Use either 'lifespan' or 'on_startup'/'on_shutdown', not both."
  63. self.debug = debug
  64. self.state = State()
  65. self.router = Router(routes, on_startup=on_startup, on_shutdown=on_shutdown, lifespan=lifespan)
  66. self.exception_handlers = {} if exception_handlers is None else dict(exception_handlers)
  67. self.user_middleware = [] if middleware is None else list(middleware)
  68. self.middleware_stack: ASGIApp | None = None
  69. def build_middleware_stack(self) -> ASGIApp:
  70. debug = self.debug
  71. error_handler = None
  72. exception_handlers: dict[typing.Any, typing.Callable[[Request, Exception], Response]] = {}
  73. for key, value in self.exception_handlers.items():
  74. if key in (500, Exception):
  75. error_handler = value
  76. else:
  77. exception_handlers[key] = value
  78. middleware = (
  79. [Middleware(ServerErrorMiddleware, handler=error_handler, debug=debug)]
  80. + self.user_middleware
  81. + [Middleware(ExceptionMiddleware, handlers=exception_handlers, debug=debug)]
  82. )
  83. app = self.router
  84. for cls, args, kwargs in reversed(middleware):
  85. app = cls(app, *args, **kwargs)
  86. return app
  87. @property
  88. def routes(self) -> list[BaseRoute]:
  89. return self.router.routes
  90. def url_path_for(self, name: str, /, **path_params: typing.Any) -> URLPath:
  91. return self.router.url_path_for(name, **path_params)
  92. async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
  93. scope["app"] = self
  94. if self.middleware_stack is None:
  95. self.middleware_stack = self.build_middleware_stack()
  96. await self.middleware_stack(scope, receive, send)
  97. def on_event(self, event_type: str) -> typing.Callable: # type: ignore[type-arg]
  98. return self.router.on_event(event_type) # pragma: no cover
  99. def mount(self, path: str, app: ASGIApp, name: str | None = None) -> None:
  100. self.router.mount(path, app=app, name=name) # pragma: no cover
  101. def host(self, host: str, app: ASGIApp, name: str | None = None) -> None:
  102. self.router.host(host, app=app, name=name) # pragma: no cover
  103. def add_middleware(
  104. self,
  105. middleware_class: _MiddlewareFactory[P],
  106. *args: P.args,
  107. **kwargs: P.kwargs,
  108. ) -> None:
  109. if self.middleware_stack is not None: # pragma: no cover
  110. raise RuntimeError("Cannot add middleware after an application has started")
  111. self.user_middleware.insert(0, Middleware(middleware_class, *args, **kwargs))
  112. def add_exception_handler(
  113. self,
  114. exc_class_or_status_code: int | type[Exception],
  115. handler: ExceptionHandler,
  116. ) -> None: # pragma: no cover
  117. self.exception_handlers[exc_class_or_status_code] = handler
  118. def add_event_handler(
  119. self,
  120. event_type: str,
  121. func: typing.Callable, # type: ignore[type-arg]
  122. ) -> None: # pragma: no cover
  123. self.router.add_event_handler(event_type, func)
  124. def add_route(
  125. self,
  126. path: str,
  127. route: typing.Callable[[Request], typing.Awaitable[Response] | Response],
  128. methods: list[str] | None = None,
  129. name: str | None = None,
  130. include_in_schema: bool = True,
  131. ) -> None: # pragma: no cover
  132. self.router.add_route(path, route, methods=methods, name=name, include_in_schema=include_in_schema)
  133. def add_websocket_route(
  134. self,
  135. path: str,
  136. route: typing.Callable[[WebSocket], typing.Awaitable[None]],
  137. name: str | None = None,
  138. ) -> None: # pragma: no cover
  139. self.router.add_websocket_route(path, route, name=name)
  140. def exception_handler(self, exc_class_or_status_code: int | type[Exception]) -> typing.Callable: # type: ignore[type-arg]
  141. warnings.warn(
  142. "The `exception_handler` decorator is deprecated, and will be removed in version 1.0.0. "
  143. "Refer to https://www.starlette.io/exceptions/ for the recommended approach.",
  144. DeprecationWarning,
  145. )
  146. def decorator(func: typing.Callable) -> typing.Callable: # type: ignore[type-arg]
  147. self.add_exception_handler(exc_class_or_status_code, func)
  148. return func
  149. return decorator
  150. def route(
  151. self,
  152. path: str,
  153. methods: list[str] | None = None,
  154. name: str | None = None,
  155. include_in_schema: bool = True,
  156. ) -> typing.Callable: # type: ignore[type-arg]
  157. """
  158. We no longer document this decorator style API, and its usage is discouraged.
  159. Instead you should use the following approach:
  160. >>> routes = [Route(path, endpoint=...), ...]
  161. >>> app = Starlette(routes=routes)
  162. """
  163. warnings.warn(
  164. "The `route` decorator is deprecated, and will be removed in version 1.0.0. "
  165. "Refer to https://www.starlette.io/routing/ for the recommended approach.",
  166. DeprecationWarning,
  167. )
  168. def decorator(func: typing.Callable) -> typing.Callable: # type: ignore[type-arg]
  169. self.router.add_route(
  170. path,
  171. func,
  172. methods=methods,
  173. name=name,
  174. include_in_schema=include_in_schema,
  175. )
  176. return func
  177. return decorator
  178. def websocket_route(self, path: str, name: str | None = None) -> typing.Callable: # type: ignore[type-arg]
  179. """
  180. We no longer document this decorator style API, and its usage is discouraged.
  181. Instead you should use the following approach:
  182. >>> routes = [WebSocketRoute(path, endpoint=...), ...]
  183. >>> app = Starlette(routes=routes)
  184. """
  185. warnings.warn(
  186. "The `websocket_route` decorator is deprecated, and will be removed in version 1.0.0. "
  187. "Refer to https://www.starlette.io/routing/#websocket-routing for the recommended approach.",
  188. DeprecationWarning,
  189. )
  190. def decorator(func: typing.Callable) -> typing.Callable: # type: ignore[type-arg]
  191. self.router.add_websocket_route(path, func, name=name)
  192. return func
  193. return decorator
  194. def middleware(self, middleware_type: str) -> typing.Callable: # type: ignore[type-arg]
  195. """
  196. We no longer document this decorator style API, and its usage is discouraged.
  197. Instead you should use the following approach:
  198. >>> middleware = [Middleware(...), ...]
  199. >>> app = Starlette(middleware=middleware)
  200. """
  201. warnings.warn(
  202. "The `middleware` decorator is deprecated, and will be removed in version 1.0.0. "
  203. "Refer to https://www.starlette.io/middleware/#using-middleware for recommended approach.",
  204. DeprecationWarning,
  205. )
  206. assert middleware_type == "http", 'Currently only middleware("http") is supported.'
  207. def decorator(func: typing.Callable) -> typing.Callable: # type: ignore[type-arg]
  208. self.add_middleware(BaseHTTPMiddleware, dispatch=func)
  209. return func
  210. return decorator