summaryrefslogtreecommitdiff
path: root/venv/lib/python3.11/site-packages/litestar/static_files
diff options
context:
space:
mode:
Diffstat (limited to 'venv/lib/python3.11/site-packages/litestar/static_files')
-rw-r--r--venv/lib/python3.11/site-packages/litestar/static_files/__init__.py4
-rw-r--r--venv/lib/python3.11/site-packages/litestar/static_files/__pycache__/__init__.cpython-311.pycbin0 -> 443 bytes
-rw-r--r--venv/lib/python3.11/site-packages/litestar/static_files/__pycache__/base.cpython-311.pycbin0 -> 7052 bytes
-rw-r--r--venv/lib/python3.11/site-packages/litestar/static_files/__pycache__/config.cpython-311.pycbin0 -> 10192 bytes
-rw-r--r--venv/lib/python3.11/site-packages/litestar/static_files/base.py141
-rw-r--r--venv/lib/python3.11/site-packages/litestar/static_files/config.py224
6 files changed, 369 insertions, 0 deletions
diff --git a/venv/lib/python3.11/site-packages/litestar/static_files/__init__.py b/venv/lib/python3.11/site-packages/litestar/static_files/__init__.py
new file mode 100644
index 0000000..3cd4594
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/litestar/static_files/__init__.py
@@ -0,0 +1,4 @@
+from litestar.static_files.base import StaticFiles
+from litestar.static_files.config import StaticFilesConfig, create_static_files_router
+
+__all__ = ("StaticFiles", "StaticFilesConfig", "create_static_files_router")
diff --git a/venv/lib/python3.11/site-packages/litestar/static_files/__pycache__/__init__.cpython-311.pyc b/venv/lib/python3.11/site-packages/litestar/static_files/__pycache__/__init__.cpython-311.pyc
new file mode 100644
index 0000000..1cc4497
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/litestar/static_files/__pycache__/__init__.cpython-311.pyc
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/litestar/static_files/__pycache__/base.cpython-311.pyc b/venv/lib/python3.11/site-packages/litestar/static_files/__pycache__/base.cpython-311.pyc
new file mode 100644
index 0000000..0ca9dae
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/litestar/static_files/__pycache__/base.cpython-311.pyc
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/litestar/static_files/__pycache__/config.cpython-311.pyc b/venv/lib/python3.11/site-packages/litestar/static_files/__pycache__/config.cpython-311.pyc
new file mode 100644
index 0000000..fd93af6
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/litestar/static_files/__pycache__/config.cpython-311.pyc
Binary files differ
diff --git a/venv/lib/python3.11/site-packages/litestar/static_files/base.py b/venv/lib/python3.11/site-packages/litestar/static_files/base.py
new file mode 100644
index 0000000..9827697
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/litestar/static_files/base.py
@@ -0,0 +1,141 @@
+from __future__ import annotations
+
+from os.path import commonpath
+from pathlib import Path
+from typing import TYPE_CHECKING, Literal, Sequence
+
+from litestar.enums import ScopeType
+from litestar.exceptions import MethodNotAllowedException, NotFoundException
+from litestar.file_system import FileSystemAdapter
+from litestar.response.file import ASGIFileResponse
+from litestar.status_codes import HTTP_404_NOT_FOUND
+
+__all__ = ("StaticFiles",)
+
+
+if TYPE_CHECKING:
+ from litestar.types import Receive, Scope, Send
+ from litestar.types.composite_types import PathType
+ from litestar.types.file_types import FileInfo, FileSystemProtocol
+
+
+class StaticFiles:
+ """ASGI App that handles file sending."""
+
+ __slots__ = ("is_html_mode", "directories", "adapter", "send_as_attachment", "headers")
+
+ def __init__(
+ self,
+ is_html_mode: bool,
+ directories: Sequence[PathType],
+ file_system: FileSystemProtocol,
+ send_as_attachment: bool = False,
+ resolve_symlinks: bool = True,
+ headers: dict[str, str] | None = None,
+ ) -> None:
+ """Initialize the Application.
+
+ Args:
+ is_html_mode: Flag dictating whether serving html. If true, the default file will be ``index.html``.
+ directories: A list of directories to serve files from.
+ file_system: The file_system spec to use for serving files.
+ send_as_attachment: Whether to send the file with a ``content-disposition`` header of
+ ``attachment`` or ``inline``
+ resolve_symlinks: Resolve symlinks to the directories
+ headers: Headers that will be sent with every response.
+ """
+ self.adapter = FileSystemAdapter(file_system)
+ self.directories = tuple(Path(p).resolve() if resolve_symlinks else Path(p) for p in directories)
+ self.is_html_mode = is_html_mode
+ self.send_as_attachment = send_as_attachment
+ self.headers = headers
+
+ async def get_fs_info(
+ self, directories: Sequence[PathType], file_path: PathType
+ ) -> tuple[Path, FileInfo] | tuple[None, None]:
+ """Return the resolved path and a :class:`stat_result <os.stat_result>`.
+
+ Args:
+ directories: A list of directory paths.
+ file_path: A file path to resolve
+
+ Returns:
+ A tuple with an optional resolved :class:`Path <anyio.Path>` instance and an optional
+ :class:`stat_result <os.stat_result>`.
+ """
+ for directory in directories:
+ try:
+ joined_path = Path(directory, file_path)
+ file_info = await self.adapter.info(joined_path)
+ if file_info and commonpath([str(directory), file_info["name"], joined_path]) == str(directory):
+ return joined_path, file_info
+ except FileNotFoundError:
+ continue
+ return None, None
+
+ async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
+ """ASGI callable.
+
+ Args:
+ scope: ASGI scope
+ receive: ASGI ``receive`` callable
+ send: ASGI ``send`` callable
+
+ Returns:
+ None
+ """
+ if scope["type"] != ScopeType.HTTP or scope["method"] not in {"GET", "HEAD"}:
+ raise MethodNotAllowedException()
+
+ res = await self.handle(path=scope["path"], is_head_response=scope["method"] == "HEAD")
+ await res(scope=scope, receive=receive, send=send)
+
+ async def handle(self, path: str, is_head_response: bool) -> ASGIFileResponse:
+ split_path = path.split("/")
+ filename = split_path[-1]
+ joined_path = Path(*split_path)
+ resolved_path, fs_info = await self.get_fs_info(directories=self.directories, file_path=joined_path)
+ content_disposition_type: Literal["inline", "attachment"] = (
+ "attachment" if self.send_as_attachment else "inline"
+ )
+
+ if self.is_html_mode and fs_info and fs_info["type"] == "directory":
+ filename = "index.html"
+ resolved_path, fs_info = await self.get_fs_info(
+ directories=self.directories,
+ file_path=Path(resolved_path or joined_path) / filename,
+ )
+
+ if fs_info and fs_info["type"] == "file":
+ return ASGIFileResponse(
+ file_path=resolved_path or joined_path,
+ file_info=fs_info,
+ file_system=self.adapter.file_system,
+ filename=filename,
+ content_disposition_type=content_disposition_type,
+ is_head_response=is_head_response,
+ headers=self.headers,
+ )
+
+ if self.is_html_mode:
+ # for some reason coverage doesn't catch these two lines
+ filename = "404.html" # pragma: no cover
+ resolved_path, fs_info = await self.get_fs_info( # pragma: no cover
+ directories=self.directories, file_path=filename
+ )
+
+ if fs_info and fs_info["type"] == "file":
+ return ASGIFileResponse(
+ file_path=resolved_path or joined_path,
+ file_info=fs_info,
+ file_system=self.adapter.file_system,
+ filename=filename,
+ status_code=HTTP_404_NOT_FOUND,
+ content_disposition_type=content_disposition_type,
+ is_head_response=is_head_response,
+ headers=self.headers,
+ )
+
+ raise NotFoundException(
+ f"no file or directory match the path {resolved_path or joined_path} was found"
+ ) # pragma: no cover
diff --git a/venv/lib/python3.11/site-packages/litestar/static_files/config.py b/venv/lib/python3.11/site-packages/litestar/static_files/config.py
new file mode 100644
index 0000000..22b6620
--- /dev/null
+++ b/venv/lib/python3.11/site-packages/litestar/static_files/config.py
@@ -0,0 +1,224 @@
+from __future__ import annotations
+
+from dataclasses import dataclass
+from pathlib import PurePath # noqa: TCH003
+from typing import TYPE_CHECKING, Any, Sequence
+
+from litestar.exceptions import ImproperlyConfiguredException
+from litestar.file_system import BaseLocalFileSystem
+from litestar.handlers import asgi, get, head
+from litestar.response.file import ASGIFileResponse # noqa: TCH001
+from litestar.router import Router
+from litestar.static_files.base import StaticFiles
+from litestar.types import Empty
+from litestar.utils import normalize_path, warn_deprecation
+
+__all__ = ("StaticFilesConfig",)
+
+if TYPE_CHECKING:
+ from litestar.datastructures import CacheControlHeader
+ from litestar.handlers.asgi_handlers import ASGIRouteHandler
+ from litestar.openapi.spec import SecurityRequirement
+ from litestar.types import (
+ AfterRequestHookHandler,
+ AfterResponseHookHandler,
+ BeforeRequestHookHandler,
+ EmptyType,
+ ExceptionHandlersMap,
+ Guard,
+ Middleware,
+ PathType,
+ )
+
+
+@dataclass
+class StaticFilesConfig:
+ """Configuration for static file service.
+
+ To enable static files, pass an instance of this class to the :class:`Litestar <litestar.app.Litestar>` constructor using
+ the 'static_files_config' key.
+ """
+
+ path: str
+ """Path to serve static files from.
+
+ Note that the path cannot contain path parameters.
+ """
+ directories: list[PathType]
+ """A list of directories to serve files from."""
+ html_mode: bool = False
+ """Flag dictating whether serving html.
+
+ If true, the default file will be 'index.html'.
+ """
+ name: str | None = None
+ """An optional string identifying the static files handler."""
+ file_system: Any = BaseLocalFileSystem() # noqa: RUF009
+ """The file_system spec to use for serving files.
+
+ Notes:
+ - A file_system is a class that adheres to the
+ :class:`FileSystemProtocol <litestar.types.FileSystemProtocol>`.
+ - You can use any of the file systems exported from the
+ [fsspec](https://filesystem-spec.readthedocs.io/en/latest/) library for this purpose.
+ """
+ opt: dict[str, Any] | None = None
+ """A string key dictionary of arbitrary values that will be added to the static files handler."""
+ guards: list[Guard] | None = None
+ """A list of :class:`Guard <litestar.types.Guard>` callables."""
+ exception_handlers: ExceptionHandlersMap | None = None
+ """A dictionary that maps handler functions to status codes and/or exception types."""
+ send_as_attachment: bool = False
+ """Whether to send the file as an attachment."""
+
+ def __post_init__(self) -> None:
+ _validate_config(path=self.path, directories=self.directories, file_system=self.file_system)
+ self.path = normalize_path(self.path)
+ warn_deprecation(
+ "2.6.0",
+ kind="class",
+ deprecated_name="StaticFilesConfig",
+ removal_in="3.0",
+ alternative="create_static_files_router",
+ info='Replace static_files_config=[StaticFilesConfig(path="/static", directories=["assets"])] with '
+ 'route_handlers=[..., create_static_files_router(path="/static", directories=["assets"])]',
+ )
+
+ def to_static_files_app(self) -> ASGIRouteHandler:
+ """Return an ASGI app serving static files based on the config.
+
+ Returns:
+ :class:`StaticFiles <litestar.static_files.StaticFiles>`
+ """
+ static_files = StaticFiles(
+ is_html_mode=self.html_mode,
+ directories=self.directories,
+ file_system=self.file_system,
+ send_as_attachment=self.send_as_attachment,
+ )
+ return asgi(
+ path=self.path,
+ name=self.name,
+ is_static=True,
+ opt=self.opt,
+ guards=self.guards,
+ exception_handlers=self.exception_handlers,
+ )(static_files)
+
+
+def create_static_files_router(
+ path: str,
+ directories: list[PathType],
+ file_system: Any = None,
+ send_as_attachment: bool = False,
+ html_mode: bool = False,
+ name: str = "static",
+ after_request: AfterRequestHookHandler | None = None,
+ after_response: AfterResponseHookHandler | None = None,
+ before_request: BeforeRequestHookHandler | None = None,
+ cache_control: CacheControlHeader | None = None,
+ exception_handlers: ExceptionHandlersMap | None = None,
+ guards: list[Guard] | None = None,
+ include_in_schema: bool | EmptyType = Empty,
+ middleware: Sequence[Middleware] | None = None,
+ opt: dict[str, Any] | None = None,
+ security: Sequence[SecurityRequirement] | None = None,
+ tags: Sequence[str] | None = None,
+ router_class: type[Router] = Router,
+ resolve_symlinks: bool = True,
+) -> Router:
+ """Create a router with handlers to serve static files.
+
+ Args:
+ path: Path to serve static files under
+ directories: Directories to serve static files from
+ file_system: A *file system* implementing
+ :class:`~litestar.types.FileSystemProtocol`.
+ `fsspec <https://filesystem-spec.readthedocs.io/en/latest/>`_ can be passed
+ here as well
+ send_as_attachment: Whether to send the file as an attachment
+ html_mode: When in HTML:
+ - Serve an ``index.html`` file from ``/``
+ - Serve ``404.html`` when a file could not be found
+ name: Name to pass to the generated handlers
+ after_request: ``after_request`` handlers passed to the router
+ after_response: ``after_response`` handlers passed to the router
+ before_request: ``before_request`` handlers passed to the router
+ cache_control: ``cache_control`` passed to the router
+ exception_handlers: Exception handlers passed to the router
+ guards: Guards passed to the router
+ include_in_schema: Include the routes / router in the OpenAPI schema
+ middleware: Middlewares passed to the router
+ opt: Opts passed to the router
+ security: Security options passed to the router
+ tags: ``tags`` passed to the router
+ router_class: The class used to construct a router from
+ resolve_symlinks: Resolve symlinks of ``directories``
+ """
+
+ if file_system is None:
+ file_system = BaseLocalFileSystem()
+
+ _validate_config(path=path, directories=directories, file_system=file_system)
+ path = normalize_path(path)
+
+ headers = None
+ if cache_control:
+ headers = {cache_control.HEADER_NAME: cache_control.to_header()}
+
+ static_files = StaticFiles(
+ is_html_mode=html_mode,
+ directories=directories,
+ file_system=file_system,
+ send_as_attachment=send_as_attachment,
+ resolve_symlinks=resolve_symlinks,
+ headers=headers,
+ )
+
+ @get("{file_path:path}", name=name)
+ async def get_handler(file_path: PurePath) -> ASGIFileResponse:
+ return await static_files.handle(path=file_path.as_posix(), is_head_response=False)
+
+ @head("/{file_path:path}", name=f"{name}/head")
+ async def head_handler(file_path: PurePath) -> ASGIFileResponse:
+ return await static_files.handle(path=file_path.as_posix(), is_head_response=True)
+
+ handlers = [get_handler, head_handler]
+
+ if html_mode:
+
+ @get("/", name=f"{name}/index")
+ async def index_handler() -> ASGIFileResponse:
+ return await static_files.handle(path="/", is_head_response=False)
+
+ handlers.append(index_handler)
+
+ return router_class(
+ after_request=after_request,
+ after_response=after_response,
+ before_request=before_request,
+ cache_control=cache_control,
+ exception_handlers=exception_handlers,
+ guards=guards,
+ include_in_schema=include_in_schema,
+ middleware=middleware,
+ opt=opt,
+ path=path,
+ route_handlers=handlers,
+ security=security,
+ tags=tags,
+ )
+
+
+def _validate_config(path: str, directories: list[PathType], file_system: Any) -> None:
+ if not path:
+ raise ImproperlyConfiguredException("path must be a non-zero length string,")
+
+ if not directories or not any(bool(d) for d in directories):
+ raise ImproperlyConfiguredException("directories must include at least one path.")
+
+ if "{" in path:
+ raise ImproperlyConfiguredException("path parameters are not supported for static files")
+
+ if not (callable(getattr(file_system, "info", None)) and callable(getattr(file_system, "open", None))):
+ raise ImproperlyConfiguredException("file_system must adhere to the FileSystemProtocol type")