applications.py 10 KB

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