Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.
 
 
 
 

251 linhas
10 KiB

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