Repository: litestar-org/litestar

Issue:
StaticFilesConfig and virtual directories
I'm trying to write a ``FileSystemProtocol`` to load files from the package data using [importlib_resources](https://importlib-resources.readthedocs.io/en/latest/using.html#). But because ``directories`` is defined as ``DirectoryPath``, pydantic checks if the given directories exist in the local filesystem. 

This is not generally true, especially in any kind of virtual filesystem (e.g. a zipped package). I think this condition should be relaxed to support virtual filesystems.

https://github.com/starlite-api/starlite/blob/9bb6dcd57c10a591377cf8e3a537e9292566d5b9/starlite/config/static_files.py#L32


Discussion:
I agree. Do you want to submit a PR?

Resolving patch:
diff --git a/litestar/constants.py b/litestar/constants.py
--- a/litestar/constants.py
+++ b/litestar/constants.py
@@ -2,6 +2,7 @@
 
 from pydantic.fields import Undefined
 
+from litestar.enums import MediaType
 from litestar.types import Empty
 
 DEFAULT_ALLOWED_CORS_HEADERS = {"Accept", "Accept-Language", "Content-Language", "Content-Type"}
@@ -12,6 +13,7 @@
 ONE_MEGABYTE = 1024 * 1024
 OPENAPI_NOT_INITIALIZED = "Litestar has not been instantiated with OpenAPIConfig"
 REDIRECT_STATUS_CODES = {301, 302, 303, 307, 308}
+REDIRECT_ALLOWED_MEDIA_TYPES = {MediaType.TEXT, MediaType.HTML}
 RESERVED_KWARGS = {"state", "headers", "cookies", "request", "socket", "data", "query", "scope", "body"}
 SCOPE_STATE_DEPENDENCY_CACHE = "dependency_cache"
 SCOPE_STATE_NAMESPACE = "__litestar__"
diff --git a/litestar/response/redirect.py b/litestar/response/redirect.py
--- a/litestar/response/redirect.py
+++ b/litestar/response/redirect.py
@@ -3,7 +3,7 @@
 from typing import TYPE_CHECKING, Any, Literal
 from urllib.parse import quote
 
-from litestar.constants import REDIRECT_STATUS_CODES
+from litestar.constants import REDIRECT_ALLOWED_MEDIA_TYPES, REDIRECT_STATUS_CODES
 from litestar.enums import MediaType
 from litestar.exceptions import ImproperlyConfiguredException
 from litestar.response.base import Response
@@ -29,6 +29,7 @@ def __init__(
         headers: dict[str, Any] | None = None,
         cookies: ResponseCookies | None = None,
         encoding: str = "utf-8",
+        media_type: str | MediaType = MediaType.TEXT,
     ) -> None:
         """Initialize the response.
 
@@ -41,9 +42,11 @@ def __init__(
             cookies: A list of :class:`Cookie <.datastructures.Cookie>` instances to be set under the response
                 ``Set-Cookie`` header.
             encoding: The encoding to be used for the response headers.
+            media_type: A value for the response ``Content-Type`` header.
+
 
         Raises:
-            ImproperlyConfiguredException: If status code is not a redirect status code.
+            ImproperlyConfiguredException: Either if status code is not a redirect status code or media type is not supported.
         """
         if status_code not in REDIRECT_STATUS_CODES:
             raise ImproperlyConfiguredException(
@@ -51,12 +54,18 @@ def __init__(
                 f"Redirect responses should have one of "
                 f"the following status codes: {', '.join([str(s) for s in REDIRECT_STATUS_CODES])}"
             )
+        if media_type not in REDIRECT_ALLOWED_MEDIA_TYPES:
+            raise ImproperlyConfiguredException(
+                f"{media_type} media type is not supported yet. "
+                f"Media type should be one of "
+                f"the following values: {', '.join([str(s) for s in REDIRECT_ALLOWED_MEDIA_TYPES])}"
+            )
         super().__init__(
             background=background,
             content=b"",
             cookies=cookies,
             headers={**(headers or {}), "location": quote(url, safe="/#%[]=:;$&()+,!?*@'~")},
-            media_type=MediaType.TEXT,
+            media_type=media_type,
             status_code=status_code,
             encoding=encoding,
         )


Test patch:
diff --git a/tests/response/test_redirect_response.py b/tests/response/test_redirect_response.py
--- a/tests/response/test_redirect_response.py
+++ b/tests/response/test_redirect_response.py
@@ -10,7 +10,7 @@
 from litestar import Response
 from litestar.exceptions import ImproperlyConfiguredException
 from litestar.response import RedirectResponse
-from litestar.status_codes import HTTP_200_OK
+from litestar.status_codes import HTTP_200_OK, HTTP_307_TEMPORARY_REDIRECT
 from litestar.testing import TestClient
 
 if TYPE_CHECKING:
@@ -62,3 +62,22 @@ async def app(scope: "Scope", receive: "Receive", send: "Send") -> None:
 def test_redirect_response_status_validation() -> None:
     with pytest.raises(ImproperlyConfiguredException):
         RedirectResponse("/", status_code=HTTP_200_OK)  # type: ignore
+
+
+def test_redirect_response_html_media_type() -> None:
+    async def app(scope: "Scope", receive: "Receive", send: "Send") -> None:
+        if scope["path"] == "/":
+            response = Response("hello")
+        else:
+            response = RedirectResponse("/", media_type="text/html")
+        await response(scope, receive, send)
+
+    client: TestClient = TestClient(app)
+    response = client.request("GET", "/redirect", follow_redirects=False)
+    assert str(response.url) == "http://testserver.local/redirect"
+    assert "text/html" in str(response.headers["Content-Type"])
+
+
+def test_redirect_response_media_type_validation() -> None:
+    with pytest.raises(ImproperlyConfiguredException):
+        RedirectResponse("/", status_code=HTTP_307_TEMPORARY_REDIRECT, media_type="application/json")