You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

202 lines
7.4 KiB

  1. import typing
  2. from starlette.datastructures import State, URLPath
  3. from starlette.exceptions import ExceptionMiddleware
  4. from starlette.middleware import Middleware
  5. from starlette.middleware.base import BaseHTTPMiddleware
  6. from starlette.middleware.errors import ServerErrorMiddleware
  7. from starlette.routing import BaseRoute, Router
  8. from starlette.types import ASGIApp, Receive, Scope, Send
  9. class Starlette:
  10. """
  11. Creates an application instance.
  12. **Parameters:**
  13. * **debug** - Boolean indicating if debug tracebacks should be returned on errors.
  14. * **routes** - A list of routes to serve incoming HTTP and WebSocket requests.
  15. * **middleware** - A list of middleware to run for every request. A starlette
  16. application will always automatically include two middleware classes.
  17. `ServerErrorMiddleware` is added as the very outermost middleware, to handle
  18. any uncaught errors occurring anywhere in the entire stack.
  19. `ExceptionMiddleware` is added as the very innermost middleware, to deal
  20. with handled exception cases occurring in the routing or endpoints.
  21. * **exception_handlers** - A dictionary mapping either integer status codes,
  22. or exception class types onto callables which handle the exceptions.
  23. Exception handler callables should be of the form
  24. `handler(request, exc) -> response` and may be be either standard functions, or
  25. async functions.
  26. * **on_startup** - A list of callables to run on application startup.
  27. Startup handler callables do not take any arguments, and may be be either
  28. standard functions, or async functions.
  29. * **on_shutdown** - A list of callables to run on application shutdown.
  30. Shutdown handler callables do not take any arguments, and may be be either
  31. standard functions, or async functions.
  32. """
  33. def __init__(
  34. self,
  35. debug: bool = False,
  36. routes: typing.Sequence[BaseRoute] = None,
  37. middleware: typing.Sequence[Middleware] = None,
  38. exception_handlers: typing.Dict[
  39. typing.Union[int, typing.Type[Exception]], typing.Callable
  40. ] = None,
  41. on_startup: typing.Sequence[typing.Callable] = None,
  42. on_shutdown: typing.Sequence[typing.Callable] = None,
  43. lifespan: typing.Callable[["Starlette"], typing.AsyncContextManager] = None,
  44. ) -> None:
  45. # The lifespan context function is a newer style that replaces
  46. # on_startup / on_shutdown handlers. Use one or the other, not both.
  47. assert lifespan is None or (
  48. on_startup is None and on_shutdown is None
  49. ), "Use either 'lifespan' or 'on_startup'/'on_shutdown', not both."
  50. self._debug = debug
  51. self.state = State()
  52. self.router = Router(
  53. routes, on_startup=on_startup, on_shutdown=on_shutdown, lifespan=lifespan
  54. )
  55. self.exception_handlers = (
  56. {} if exception_handlers is None else dict(exception_handlers)
  57. )
  58. self.user_middleware = [] if middleware is None else list(middleware)
  59. self.middleware_stack = self.build_middleware_stack()
  60. def build_middleware_stack(self) -> ASGIApp:
  61. debug = self.debug
  62. error_handler = None
  63. exception_handlers = {}
  64. for key, value in self.exception_handlers.items():
  65. if key in (500, Exception):
  66. error_handler = value
  67. else:
  68. exception_handlers[key] = value
  69. middleware = (
  70. [Middleware(ServerErrorMiddleware, handler=error_handler, debug=debug)]
  71. + self.user_middleware
  72. + [
  73. Middleware(
  74. ExceptionMiddleware, handlers=exception_handlers, debug=debug
  75. )
  76. ]
  77. )
  78. app = self.router
  79. for cls, options in reversed(middleware):
  80. app = cls(app=app, **options)
  81. return app
  82. @property
  83. def routes(self) -> typing.List[BaseRoute]:
  84. return self.router.routes
  85. @property
  86. def debug(self) -> bool:
  87. return self._debug
  88. @debug.setter
  89. def debug(self, value: bool) -> None:
  90. self._debug = value
  91. self.middleware_stack = self.build_middleware_stack()
  92. def url_path_for(self, name: str, **path_params: str) -> URLPath:
  93. return self.router.url_path_for(name, **path_params)
  94. async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
  95. scope["app"] = self
  96. await self.middleware_stack(scope, receive, send)
  97. # The following usages are now discouraged in favour of configuration
  98. #  during Starlette.__init__(...)
  99. def on_event(self, event_type: str) -> typing.Callable:
  100. return self.router.on_event(event_type)
  101. def mount(self, path: str, app: ASGIApp, name: str = None) -> None:
  102. self.router.mount(path, app=app, name=name)
  103. def host(self, host: str, app: ASGIApp, name: str = None) -> None:
  104. self.router.host(host, app=app, name=name)
  105. def add_middleware(self, middleware_class: type, **options: typing.Any) -> None:
  106. self.user_middleware.insert(0, Middleware(middleware_class, **options))
  107. self.middleware_stack = self.build_middleware_stack()
  108. def add_exception_handler(
  109. self,
  110. exc_class_or_status_code: typing.Union[int, typing.Type[Exception]],
  111. handler: typing.Callable,
  112. ) -> None:
  113. self.exception_handlers[exc_class_or_status_code] = handler
  114. self.middleware_stack = self.build_middleware_stack()
  115. def add_event_handler(self, event_type: str, func: typing.Callable) -> None:
  116. self.router.add_event_handler(event_type, func)
  117. def add_route(
  118. self,
  119. path: str,
  120. route: typing.Callable,
  121. methods: typing.List[str] = None,
  122. name: str = None,
  123. include_in_schema: bool = True,
  124. ) -> None:
  125. self.router.add_route(
  126. path, route, methods=methods, name=name, include_in_schema=include_in_schema
  127. )
  128. def add_websocket_route(
  129. self, path: str, route: typing.Callable, name: str = None
  130. ) -> None:
  131. self.router.add_websocket_route(path, route, name=name)
  132. def exception_handler(
  133. self, exc_class_or_status_code: typing.Union[int, typing.Type[Exception]]
  134. ) -> typing.Callable:
  135. def decorator(func: typing.Callable) -> typing.Callable:
  136. self.add_exception_handler(exc_class_or_status_code, func)
  137. return func
  138. return decorator
  139. def route(
  140. self,
  141. path: str,
  142. methods: typing.List[str] = None,
  143. name: str = None,
  144. include_in_schema: bool = True,
  145. ) -> typing.Callable:
  146. def decorator(func: typing.Callable) -> typing.Callable:
  147. self.router.add_route(
  148. path,
  149. func,
  150. methods=methods,
  151. name=name,
  152. include_in_schema=include_in_schema,
  153. )
  154. return func
  155. return decorator
  156. def websocket_route(self, path: str, name: str = None) -> typing.Callable:
  157. def decorator(func: typing.Callable) -> typing.Callable:
  158. self.router.add_websocket_route(path, func, name=name)
  159. return func
  160. return decorator
  161. def middleware(self, middleware_type: str) -> typing.Callable:
  162. assert (
  163. middleware_type == "http"
  164. ), 'Currently only middleware("http") is supported.'
  165. def decorator(func: typing.Callable) -> typing.Callable:
  166. self.add_middleware(BaseHTTPMiddleware, dispatch=func)
  167. return func
  168. return decorator