| 357 | |
| 358 | |
| 359 | class Mount(BaseRoute): |
| 360 | def __init__( |
| 361 | self, |
| 362 | path: str, |
| 363 | app: ASGIApp | None = None, |
| 364 | routes: Sequence[BaseRoute] | None = None, |
| 365 | name: str | None = None, |
| 366 | *, |
| 367 | middleware: Sequence[Middleware] | None = None, |
| 368 | ) -> None: |
| 369 | assert path == "" or path.startswith("/"), "Routed paths must start with '/'" |
| 370 | assert app is not None or routes is not None, "Either 'app=...', or 'routes=' must be specified" |
| 371 | self.path = path.rstrip("/") |
| 372 | if app is not None: |
| 373 | self._base_app: ASGIApp = app |
| 374 | else: |
| 375 | self._base_app = Router(routes=routes) |
| 376 | self.app = self._base_app |
| 377 | if middleware is not None: |
| 378 | for cls, args, kwargs in reversed(middleware): |
| 379 | self.app = cls(self.app, *args, **kwargs) |
| 380 | self.name = name |
| 381 | self.path_regex, self.path_format, self.param_convertors = compile_path(self.path + "/{path:path}") |
| 382 | |
| 383 | @property |
| 384 | def routes(self) -> list[BaseRoute]: |
| 385 | return getattr(self._base_app, "routes", []) |
| 386 | |
| 387 | def matches(self, scope: Scope) -> tuple[Match, Scope]: |
| 388 | path_params: dict[str, Any] |
| 389 | if scope["type"] in ("http", "websocket"): # pragma: no branch |
| 390 | root_path = scope.get("root_path", "") |
| 391 | route_path = get_route_path(scope) |
| 392 | match = self.path_regex.match(route_path) |
| 393 | if match: |
| 394 | matched_params = match.groupdict() |
| 395 | for key, value in matched_params.items(): |
| 396 | matched_params[key] = self.param_convertors[key].convert(value) |
| 397 | remaining_path = "/" + matched_params.pop("path") |
| 398 | matched_path = route_path[: -len(remaining_path)] |
| 399 | path_params = dict(scope.get("path_params", {})) |
| 400 | path_params.update(matched_params) |
| 401 | child_scope = { |
| 402 | "path_params": path_params, |
| 403 | # app_root_path will only be set at the top level scope, |
| 404 | # initialized with the (optional) value of a root_path |
| 405 | # set above/before Starlette. And even though any |
| 406 | # mount will have its own child scope with its own respective |
| 407 | # root_path, the app_root_path will always be available in all |
| 408 | # the child scopes with the same top level value because it's |
| 409 | # set only once here with a default, any other child scope will |
| 410 | # just inherit that app_root_path default value stored in the |
| 411 | # scope. All this is needed to support Request.url_for(), as it |
| 412 | # uses the app_root_path to build the URL path. |
| 413 | "app_root_path": scope.get("app_root_path", root_path), |
| 414 | "root_path": root_path + matched_path, |
| 415 | "endpoint": self.app, |
| 416 | } |
no outgoing calls