def test_reverse_mount_urls(): mounted = Router([Mount("/users", ok, name="users")]) assert mounted.url_path_for("users", path="/a") == "/users/a" users = Router([Route("/{username}", ok, name="user")]) mounted = Router([Mount("/{subpath}/users", users, name="users")]) assert (mounted.url_path_for("users:user", subpath="test", username="******") == "/test/users/tom") assert (mounted.url_path_for("users", subpath="test", path="/tom") == "/test/users/tom")
def test_datetime_convertor(test_client_factory, app: Router): client = test_client_factory(app) response = client.get("/datetime/2020-01-01T00:00:00") assert response.json() == {"datetime": "2020-01-01T00:00:00"} assert (app.url_path_for("datetime-convertor", param=datetime( 1996, 1, 22, 23, 0, 0)) == "/datetime/1996-01-22T23:00:00")
class Starlette: def __init__(self, debug: bool = False, routes: typing.List[BaseRoute] = None) -> None: self._debug = debug self.router = Router(routes) self.exception_middleware = ExceptionMiddleware(self.router, debug=debug) self.error_middleware = ServerErrorMiddleware( self.exception_middleware, debug=debug) @property def routes(self) -> typing.List[BaseRoute]: return self.router.routes @property def debug(self) -> bool: return self._debug @debug.setter def debug(self, value: bool) -> None: self._debug = value self.exception_middleware.debug = value self.error_middleware.debug = value def on_event(self, event_type: str) -> typing.Callable: return self.router.lifespan.on_event(event_type) def mount(self, path: str, app: ASGIApp, name: str = None) -> None: self.router.mount(path, app=app, name=name) def host(self, host: str, app: ASGIApp, name: str = None) -> None: self.router.host(host, app=app, name=name) def add_middleware(self, middleware_class: type, **kwargs: typing.Any) -> None: self.error_middleware.app = middleware_class(self.error_middleware.app, **kwargs) def add_exception_handler( self, exc_class_or_status_code: typing.Union[int, typing.Type[Exception]], handler: typing.Callable, ) -> None: if exc_class_or_status_code in (500, Exception): self.error_middleware.handler = handler else: self.exception_middleware.add_exception_handler( exc_class_or_status_code, handler) def add_event_handler(self, event_type: str, func: typing.Callable) -> None: self.router.lifespan.add_event_handler(event_type, func) def add_route( self, path: str, route: typing.Callable, methods: typing.List[str] = None, name: str = None, include_in_schema: bool = True, ) -> None: self.router.add_route(path, route, methods=methods, name=name, include_in_schema=include_in_schema) def add_websocket_route(self, path: str, route: typing.Callable, name: str = None) -> None: self.router.add_websocket_route(path, route, name=name) def exception_handler( self, exc_class_or_status_code: typing.Union[int, typing.Type[Exception]] ) -> typing.Callable: def decorator(func: typing.Callable) -> typing.Callable: self.add_exception_handler(exc_class_or_status_code, func) return func return decorator def route( self, path: str, methods: typing.List[str] = None, name: str = None, include_in_schema: bool = True, ) -> typing.Callable: def decorator(func: typing.Callable) -> typing.Callable: self.router.add_route( path, func, methods=methods, name=name, include_in_schema=include_in_schema, ) return func return decorator def websocket_route(self, path: str, name: str = None) -> typing.Callable: def decorator(func: typing.Callable) -> typing.Callable: self.router.add_websocket_route(path, func, name=name) return func return decorator def middleware(self, middleware_type: str) -> typing.Callable: assert (middleware_type == "http" ), 'Currently only middleware("http") is supported.' def decorator(func: typing.Callable) -> typing.Callable: self.add_middleware(BaseHTTPMiddleware, dispatch=func) return func return decorator def url_path_for(self, name: str, **path_params: str) -> URLPath: return self.router.url_path_for(name, **path_params) async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None: scope["app"] = self await self.error_middleware(scope, receive, send)
class Starlette: """ Creates an application instance. **Parameters:** * **debug** - Boolean indicating if debug tracebacks should be returned on errors. * **routes** - A list of routes to serve incoming HTTP and WebSocket requests. * **middleware** - A list of middleware to run for every request. A starlette application will always automatically include two middleware classes. `ServerErrorMiddleware` is added the very outermost middleware, to handle any uncaught errors occuring anywhere in the entire stack. `ExceptionMiddleware` is added as the very innermost middleware, to deal with handled exception cases occuring in the routing or endpoints. * **exception_handlers** - A dictionary mapping either integer status codes, or exception class types onto callables which handle the exceptions. Exception handler callables should be of the form `handler(request, exc) -> response` and may be be either standard functions, or async functions. * **on_startup** - A list of callables to run on application startup. Startup handler callables do not take any arguments, and may be be either standard functions, or async functions. * **on_shutdown** - A list of callables to run on application shutdown. Shutdown handler callables do not take any arguments, and may be be either standard functions, or async functions. """ def __init__( self, debug: bool = False, routes: typing.Sequence[BaseRoute] = None, middleware: typing.Sequence[Middleware] = None, exception_handlers: typing.Dict[typing.Union[int, typing.Type[Exception]], typing.Callable] = None, on_startup: typing.Sequence[typing.Callable] = None, on_shutdown: typing.Sequence[typing.Callable] = None, lifespan: typing.Callable[["Starlette"], typing.AsyncGenerator] = None, ) -> None: # The lifespan context function is a newer style that replaces # on_startup / on_shutdown handlers. Use one or the other, not both. assert lifespan is None or ( on_startup is None and on_shutdown is None ), "Use either 'lifespan' or 'on_startup'/'on_shutdown', not both." self._debug = debug self.state = State() self.router = Router(routes, on_startup=on_startup, on_shutdown=on_shutdown, lifespan=lifespan) self.exception_handlers = ({} if exception_handlers is None else dict(exception_handlers)) self.user_middleware = [] if middleware is None else list(middleware) self.middleware_stack = self.build_middleware_stack() def build_middleware_stack(self) -> ASGIApp: debug = self.debug error_handler = None exception_handlers = {} for key, value in self.exception_handlers.items(): if key in (500, Exception): error_handler = value else: exception_handlers[key] = value middleware = ([ Middleware( ServerErrorMiddleware, handler=error_handler, debug=debug, ) ] + self.user_middleware + [ Middleware( ExceptionMiddleware, handlers=exception_handlers, debug=debug, ) ]) app = self.router for cls, options in reversed(middleware): app = cls(app=app, **options) return app @property def routes(self) -> typing.List[BaseRoute]: return self.router.routes @property def debug(self) -> bool: return self._debug @debug.setter def debug(self, value: bool) -> None: self._debug = value self.middleware_stack = self.build_middleware_stack() def url_path_for(self, name: str, **path_params: str) -> URLPath: return self.router.url_path_for(name, **path_params) async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None: scope["app"] = self await self.middleware_stack(scope, receive, send) # The following usages are now discouraged in favour of configuration # during Starlette.__init__(...) def on_event(self, event_type: str) -> typing.Callable: return self.router.on_event(event_type) def mount(self, path: str, app: ASGIApp, name: str = None) -> None: self.router.mount(path, app=app, name=name) def host(self, host: str, app: ASGIApp, name: str = None) -> None: self.router.host(host, app=app, name=name) def add_middleware(self, middleware_class: type, **options: typing.Any) -> None: self.user_middleware.insert(0, Middleware(middleware_class, **options)) self.middleware_stack = self.build_middleware_stack() def add_exception_handler( self, exc_class_or_status_code: typing.Union[int, typing.Type[Exception]], handler: typing.Callable, ) -> None: self.exception_handlers[exc_class_or_status_code] = handler self.middleware_stack = self.build_middleware_stack() def add_event_handler(self, event_type: str, func: typing.Callable) -> None: self.router.add_event_handler(event_type, func) def add_route( self, path: str, route: typing.Callable, methods: typing.List[str] = None, name: str = None, include_in_schema: bool = True, ) -> None: self.router.add_route(path, route, methods=methods, name=name, include_in_schema=include_in_schema) def add_websocket_route(self, path: str, route: typing.Callable, name: str = None) -> None: self.router.add_websocket_route(path, route, name=name) def exception_handler( self, exc_class_or_status_code: typing.Union[int, typing.Type[Exception]] ) -> typing.Callable: def decorator(func: typing.Callable) -> typing.Callable: self.add_exception_handler(exc_class_or_status_code, func) return func return decorator def route( self, path: str, methods: typing.List[str] = None, name: str = None, include_in_schema: bool = True, ) -> typing.Callable: def decorator(func: typing.Callable) -> typing.Callable: self.router.add_route( path, func, methods=methods, name=name, include_in_schema=include_in_schema, ) return func return decorator def websocket_route(self, path: str, name: str = None) -> typing.Callable: def decorator(func: typing.Callable) -> typing.Callable: self.router.add_websocket_route(path, func, name=name) return func return decorator def middleware(self, middleware_type: str) -> typing.Callable: assert (middleware_type == "http" ), 'Currently only middleware("http") is supported.' def decorator(func: typing.Callable) -> typing.Callable: self.add_middleware(BaseHTTPMiddleware, dispatch=func) return func return decorator
class Starlette: def __init__(self, debug: bool = False, template_directory: str = None) -> None: self._debug = debug self.router = Router() self.exception_middleware = ExceptionMiddleware(self.router, debug=debug) self.error_middleware = ServerErrorMiddleware( self.exception_middleware, debug=debug) self.lifespan_middleware = LifespanMiddleware(self.error_middleware) self.schema_generator = None # type: typing.Optional[BaseSchemaGenerator] self.template_env = self.load_template_env(template_directory) @property def routes(self) -> typing.List[BaseRoute]: return self.router.routes @property def debug(self) -> bool: return self._debug @debug.setter def debug(self, value: bool) -> None: self._debug = value self.exception_middleware.debug = value self.error_middleware.debug = value def load_template_env(self, template_directory: str = None) -> typing.Any: if template_directory is None: return None # Import jinja2 lazily. import jinja2 @jinja2.contextfunction def url_for(context: dict, name: str, **path_params: typing.Any) -> str: request = context["request"] return request.url_for(name, **path_params) loader = jinja2.FileSystemLoader(str(template_directory)) env = jinja2.Environment(loader=loader, autoescape=True) env.globals["url_for"] = url_for return env def get_template(self, name: str) -> typing.Any: return self.template_env.get_template(name) @property def schema(self) -> dict: assert self.schema_generator is not None return self.schema_generator.get_schema(self.routes) def on_event(self, event_type: str) -> typing.Callable: return self.lifespan_middleware.on_event(event_type) def mount(self, path: str, app: ASGIApp, name: str = None) -> None: self.router.mount(path, app=app, name=name) def host(self, host: str, app: ASGIApp, name: str = None) -> None: self.router.host(host, app=app, name=name) def add_middleware(self, middleware_class: type, **kwargs: typing.Any) -> None: self.error_middleware.app = middleware_class(self.error_middleware.app, **kwargs) def add_exception_handler( self, exc_class_or_status_code: typing.Union[int, typing.Type[Exception]], handler: typing.Callable, ) -> None: if exc_class_or_status_code in (500, Exception): self.error_middleware.handler = handler else: self.exception_middleware.add_exception_handler( exc_class_or_status_code, handler) def add_event_handler(self, event_type: str, func: typing.Callable) -> None: self.lifespan_middleware.add_event_handler(event_type, func) def add_route( self, path: str, route: typing.Callable, methods: typing.List[str] = None, name: str = None, include_in_schema: bool = True, ) -> None: self.router.add_route(path, route, methods=methods, name=name, include_in_schema=include_in_schema) def add_websocket_route(self, path: str, route: typing.Callable, name: str = None) -> None: self.router.add_websocket_route(path, route, name=name) def exception_handler( self, exc_class_or_status_code: typing.Union[int, typing.Type[Exception]] ) -> typing.Callable: def decorator(func: typing.Callable) -> typing.Callable: self.add_exception_handler(exc_class_or_status_code, func) return func return decorator def route( self, path: str, methods: typing.List[str] = None, name: str = None, include_in_schema: bool = True, ) -> typing.Callable: def decorator(func: typing.Callable) -> typing.Callable: self.router.add_route( path, func, methods=methods, name=name, include_in_schema=include_in_schema, ) return func return decorator def websocket_route(self, path: str, name: str = None) -> typing.Callable: def decorator(func: typing.Callable) -> typing.Callable: self.router.add_websocket_route(path, func, name=name) return func return decorator def middleware(self, middleware_type: str) -> typing.Callable: assert (middleware_type == "http" ), 'Currently only middleware("http") is supported.' def decorator(func: typing.Callable) -> typing.Callable: self.add_middleware(BaseHTTPMiddleware, dispatch=func) return func return decorator def url_path_for(self, name: str, **path_params: str) -> URLPath: return self.router.url_path_for(name, **path_params) def __call__(self, scope: Scope) -> ASGIInstance: scope["app"] = self return self.lifespan_middleware(scope)
class Starlette: def __init__( self, debug: bool = False, routes: typing.List[BaseRoute] = None, middleware: typing.List[Middleware] = None, exception_handlers: typing.Dict[typing.Union[int, typing.Type[Exception]], typing.Callable] = None, on_startup: typing.List[typing.Callable] = None, on_shutdown: typing.List[typing.Callable] = None, ) -> None: self._debug = debug self.state = State() self.router = Router(routes, on_startup=on_startup, on_shutdown=on_shutdown) self.exception_handlers = ({} if exception_handlers is None else dict(exception_handlers)) self.user_middleware = list(middleware or []) self.middleware_stack = self.build_middleware_stack() def build_middleware_stack(self) -> ASGIApp: debug = self.debug error_handler = None exception_handlers = {} for key, value in self.exception_handlers.items(): if key in (500, Exception): error_handler = value else: exception_handlers[key] = value server_errors = Middleware( ServerErrorMiddleware, options={ "handler": error_handler, "debug": debug }, ) exceptions = Middleware( ExceptionMiddleware, options={ "handlers": exception_handlers, "debug": debug }, ) middleware = [server_errors] + self.user_middleware + [exceptions] app = self.router for cls, options, enabled in reversed(middleware): if enabled: app = cls(app=app, **options) return app @property def routes(self) -> typing.List[BaseRoute]: return self.router.routes @property def debug(self) -> bool: return self._debug @debug.setter def debug(self, value: bool) -> None: self._debug = value self.middleware_stack = self.build_middleware_stack() def on_event(self, event_type: str) -> typing.Callable: return self.router.lifespan.on_event(event_type) def mount(self, path: str, app: ASGIApp, name: str = None) -> None: self.router.mount(path, app=app, name=name) def host(self, host: str, app: ASGIApp, name: str = None) -> None: self.router.host(host, app=app, name=name) def add_middleware(self, middleware_class: type, **kwargs: typing.Any) -> None: self.user_middleware.insert( 0, Middleware(middleware_class, options=kwargs)) self.middleware_stack = self.build_middleware_stack() def add_exception_handler( self, exc_class_or_status_code: typing.Union[int, typing.Type[Exception]], handler: typing.Callable, ) -> None: self.exception_handlers[exc_class_or_status_code] = handler self.middleware_stack = self.build_middleware_stack() def add_event_handler(self, event_type: str, func: typing.Callable) -> None: self.router.lifespan.add_event_handler(event_type, func) def add_route( self, path: str, route: typing.Callable, methods: typing.List[str] = None, name: str = None, include_in_schema: bool = True, ) -> None: self.router.add_route(path, route, methods=methods, name=name, include_in_schema=include_in_schema) def add_websocket_route(self, path: str, route: typing.Callable, name: str = None) -> None: self.router.add_websocket_route(path, route, name=name) def exception_handler( self, exc_class_or_status_code: typing.Union[int, typing.Type[Exception]] ) -> typing.Callable: def decorator(func: typing.Callable) -> typing.Callable: self.add_exception_handler(exc_class_or_status_code, func) return func return decorator def route( self, path: str, methods: typing.List[str] = None, name: str = None, include_in_schema: bool = True, ) -> typing.Callable: def decorator(func: typing.Callable) -> typing.Callable: self.router.add_route( path, func, methods=methods, name=name, include_in_schema=include_in_schema, ) return func return decorator def websocket_route(self, path: str, name: str = None) -> typing.Callable: def decorator(func: typing.Callable) -> typing.Callable: self.router.add_websocket_route(path, func, name=name) return func return decorator def middleware(self, middleware_type: str) -> typing.Callable: assert (middleware_type == "http" ), 'Currently only middleware("http") is supported.' def decorator(func: typing.Callable) -> typing.Callable: self.add_middleware(BaseHTTPMiddleware, dispatch=func) return func return decorator def url_path_for(self, name: str, **path_params: str) -> URLPath: return self.router.url_path_for(name, **path_params) async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None: scope["app"] = self await self.middleware_stack(scope, receive, send)
class Starlette: def __init__(self, debug: bool = False) -> None: self.router = Router() self.lifespan_handler = LifespanHandler() self.app = self.router self.exception_middleware = ExceptionMiddleware(self.router, debug=debug) @property def routes(self) -> typing.List[BaseRoute]: return self.router.routes @property def debug(self) -> bool: return self.exception_middleware.debug @debug.setter def debug(self, value: bool) -> None: self.exception_middleware.debug = value def on_event(self, event_type: str) -> typing.Callable: return self.lifespan_handler.on_event(event_type) def mount(self, path: str, app: ASGIApp) -> None: self.router.mount(path, app=app) def add_middleware(self, middleware_class: type, **kwargs: typing.Any) -> None: self.exception_middleware.app = middleware_class(self.app, **kwargs) def add_exception_handler(self, exc_class: type, handler: typing.Callable) -> None: self.exception_middleware.add_exception_handler(exc_class, handler) def add_event_handler(self, event_type: str, func: typing.Callable) -> None: self.lifespan_handler.add_event_handler(event_type, func) def add_route( self, path: str, route: typing.Callable, methods: typing.List[str] = None ) -> None: self.router.add_route(path, route, methods=methods) def add_graphql_route(self, path: str, schema: typing.Any) -> None: self.router.add_graphql_route(path, schema) def add_websocket_route(self, path: str, route: typing.Callable) -> None: self.router.add_websocket_route(path, route) def exception_handler(self, exc_class: type) -> typing.Callable: def decorator(func: typing.Callable) -> typing.Callable: self.add_exception_handler(exc_class, func) return func return decorator def route(self, path: str, methods: typing.List[str] = None) -> typing.Callable: def decorator(func: typing.Callable) -> typing.Callable: self.router.add_route(path, func, methods=methods) return func return decorator def websocket_route(self, path: str) -> typing.Callable: def decorator(func: typing.Callable) -> typing.Callable: self.router.add_websocket_route(path, func) return func return decorator def url_path_for(self, name: str, **path_params: str) -> URL: return self.router.url_path_for(name, **path_params) def __call__(self, scope: Scope) -> ASGIInstance: scope["app"] = self scope["router"] = self.router if scope["type"] == "lifespan": return self.lifespan_handler(scope) return self.exception_middleware(scope)