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/app.py b/litestar/app.py
--- a/litestar/app.py
+++ b/litestar/app.py
@@ -84,7 +84,6 @@
         Message,
         Middleware,
         OnAppInitHandler,
-        OptionalSequence,
         ParametersMap,
         Receive,
         ResponseCookies,
@@ -165,14 +164,14 @@ class Litestar(Router):
 
     def __init__(
         self,
-        route_handlers: OptionalSequence[ControllerRouterHandler] | None = None,
+        route_handlers: Sequence[ControllerRouterHandler] | None = None,
         *,
-        after_exception: OptionalSequence[AfterExceptionHookHandler] | None = None,
+        after_exception: Sequence[AfterExceptionHookHandler] | None = None,
         after_request: AfterRequestHookHandler | None = None,
         after_response: AfterResponseHookHandler | None = None,
         allowed_hosts: Sequence[str] | AllowedHostsConfig | None = None,
         before_request: BeforeRequestHookHandler | None = None,
-        before_send: OptionalSequence[BeforeMessageSendHookHandler] | None = None,
+        before_send: Sequence[BeforeMessageSendHookHandler] | None = None,
         cache_control: CacheControlHeader | None = None,
         compression_config: CompressionConfig | None = None,
         cors_config: CORSConfig | None = None,
@@ -183,38 +182,37 @@ def __init__(
         etag: ETag | None = None,
         event_emitter_backend: type[BaseEventEmitterBackend] = SimpleEventEmitter,
         exception_handlers: ExceptionHandlersMap | None = None,
-        guards: OptionalSequence[Guard] | None = None,
+        guards: Sequence[Guard] | None = None,
         include_in_schema: bool | EmptyType = Empty,
-        listeners: OptionalSequence[EventListener] | None = None,
+        listeners: Sequence[EventListener] | None = None,
         logging_config: BaseLoggingConfig | EmptyType | None = Empty,
-        middleware: OptionalSequence[Middleware] | None = None,
+        middleware: Sequence[Middleware] | None = None,
         multipart_form_part_limit: int = 1000,
-        on_app_init: OptionalSequence[OnAppInitHandler] | None = None,
-        on_shutdown: OptionalSequence[LifespanHook] | None = None,
-        on_startup: OptionalSequence[LifespanHook] | None = None,
+        on_app_init: Sequence[OnAppInitHandler] | None = None,
+        on_shutdown: Sequence[LifespanHook] | None = None,
+        on_startup: Sequence[LifespanHook] | None = None,
         openapi_config: OpenAPIConfig | None = DEFAULT_OPENAPI_CONFIG,
         opt: Mapping[str, Any] | None = None,
         parameters: ParametersMap | None = None,
-        plugins: OptionalSequence[PluginProtocol] | None = None,
+        plugins: Sequence[PluginProtocol] | None = None,
         request_class: type[Request] | None = None,
         response_cache_config: ResponseCacheConfig | None = None,
         response_class: ResponseType | None = None,
         response_cookies: ResponseCookies | None = None,
-        response_headers: OptionalSequence[ResponseHeader] | None = None,
+        response_headers: Sequence[ResponseHeader] | None = None,
         return_dto: type[AbstractDTO] | None | EmptyType = Empty,
-        security: OptionalSequence[SecurityRequirement] | None = None,
+        security: Sequence[SecurityRequirement] | None = None,
         signature_namespace: Mapping[str, Any] | None = None,
         state: State | None = None,
-        static_files_config: OptionalSequence[StaticFilesConfig] | None = None,
+        static_files_config: Sequence[StaticFilesConfig] | None = None,
         stores: StoreRegistry | dict[str, Store] | None = None,
         tags: Sequence[str] | None = None,
         template_config: TemplateConfig | None = None,
         type_encoders: TypeEncodersMap | None = None,
         type_decoders: TypeDecodersSequence | None = None,
         websocket_class: type[WebSocket] | None = None,
-        lifespan: OptionalSequence[
-            Callable[[Litestar], AbstractAsyncContextManager] | AbstractAsyncContextManager
-        ] = None,
+        lifespan: Sequence[Callable[[Litestar], AbstractAsyncContextManager] | AbstractAsyncContextManager]
+        | None = None,
         pdb_on_exception: bool | None = None,
     ) -> None:
         """Initialize a ``Litestar`` application.
diff --git a/litestar/controller.py b/litestar/controller.py
--- a/litestar/controller.py
+++ b/litestar/controller.py
@@ -4,7 +4,7 @@
 from copy import deepcopy
 from functools import partial
 from operator import attrgetter
-from typing import TYPE_CHECKING, Any, Mapping, cast
+from typing import TYPE_CHECKING, Any, Mapping, Sequence, cast
 
 from litestar._layers.utils import narrow_response_cookies, narrow_response_headers
 from litestar.exceptions import ImproperlyConfiguredException
@@ -31,7 +31,6 @@
         ExceptionHandlersMap,
         Guard,
         Middleware,
-        OptionalSequence,
         ParametersMap,
         ResponseCookies,
         TypeEncodersMap,
@@ -105,11 +104,11 @@ class Controller:
     """
     exception_handlers: ExceptionHandlersMap | None
     """A map of handler functions to status codes and/or exception types."""
-    guards: OptionalSequence[Guard]
+    guards: Sequence[Guard] | None
     """A sequence of :class:`Guard <.types.Guard>` callables."""
     include_in_schema: bool | EmptyType
     """A boolean flag dictating whether  the route handler should be documented in the OpenAPI schema"""
-    middleware: OptionalSequence[Middleware]
+    middleware: Sequence[Middleware] | None
     """A sequence of :class:`Middleware <.types.Middleware>`."""
     opt: Mapping[str, Any] | None
     """A string key mapping of arbitrary values that can be accessed in :class:`Guards <.types.Guard>` or wherever you
@@ -139,9 +138,9 @@ class Controller:
     """:class:`AbstractDTO <.dto.base_dto.AbstractDTO>` to use for serializing outbound response
     data.
     """
-    tags: OptionalSequence[str]
+    tags: Sequence[str] | None
     """A sequence of string tags that will be appended to the schema of all route handlers under the controller."""
-    security: OptionalSequence[SecurityRequirement]
+    security: Sequence[SecurityRequirement] | None
     """A sequence of dictionaries that to the schema of all route handlers under the controller."""
     signature_namespace: dict[str, Any]
     """A mapping of names to types for use in forward reference resolution during signature modelling."""


Test patch:
diff --git a/litestar/testing/client/sync_client.py b/litestar/testing/client/sync_client.py
--- a/litestar/testing/client/sync_client.py
+++ b/litestar/testing/client/sync_client.py
@@ -1,7 +1,7 @@
 from __future__ import annotations
 
 from contextlib import ExitStack
-from typing import TYPE_CHECKING, Any, Generic, Mapping, TypeVar
+from typing import TYPE_CHECKING, Any, Generic, Mapping, Sequence, TypeVar
 from urllib.parse import urljoin
 
 from httpx import USE_CLIENT_DEFAULT, Client, Response
@@ -28,7 +28,6 @@
 
     from litestar.middleware.session.base import BaseBackendConfig
     from litestar.testing.websocket_test_session import WebSocketTestSession
-    from litestar.types.helper_types import OptionalSequence
 
 
 T = TypeVar("T", bound=ASGIApp)
@@ -476,7 +475,7 @@ def delete(
     def websocket_connect(
         self,
         url: str,
-        subprotocols: OptionalSequence[str] | None = None,
+        subprotocols: Sequence[str] | None = None,
         params: QueryParamTypes | None = None,
         headers: HeaderTypes | None = None,
         cookies: CookieTypes | None = None,
diff --git a/litestar/testing/helpers.py b/litestar/testing/helpers.py
--- a/litestar/testing/helpers.py
+++ b/litestar/testing/helpers.py
@@ -44,7 +44,6 @@
         LifespanHook,
         Middleware,
         OnAppInitHandler,
-        OptionalSequence,
         ParametersMap,
         ResponseCookies,
         ResponseType,
@@ -55,7 +54,7 @@
 def create_test_client(
     route_handlers: ControllerRouterHandler | Sequence[ControllerRouterHandler] | None = None,
     *,
-    after_exception: OptionalSequence[AfterExceptionHookHandler] | None = None,
+    after_exception: Sequence[AfterExceptionHookHandler] | None = None,
     after_request: AfterRequestHookHandler | None = None,
     after_response: AfterResponseHookHandler | None = None,
     allowed_hosts: Sequence[str] | AllowedHostsConfig | None = None,
@@ -63,7 +62,7 @@ def create_test_client(
     backend_options: Mapping[str, Any] | None = None,
     base_url: str = "http://testserver.local",
     before_request: BeforeRequestHookHandler | None = None,
-    before_send: OptionalSequence[BeforeMessageSendHookHandler] | None = None,
+    before_send: Sequence[BeforeMessageSendHookHandler] | None = None,
     cache_control: CacheControlHeader | None = None,
     compression_config: CompressionConfig | None = None,
     cors_config: CORSConfig | None = None,
@@ -74,19 +73,19 @@ def create_test_client(
     etag: ETag | None = None,
     event_emitter_backend: type[BaseEventEmitterBackend] = SimpleEventEmitter,
     exception_handlers: ExceptionHandlersMap | None = None,
-    guards: OptionalSequence[Guard] | None = None,
+    guards: Sequence[Guard] | None = None,
     include_in_schema: bool | EmptyType = Empty,
-    listeners: OptionalSequence[EventListener] | None = None,
+    listeners: Sequence[EventListener] | None = None,
     logging_config: BaseLoggingConfig | EmptyType | None = Empty,
-    middleware: OptionalSequence[Middleware] | None = None,
+    middleware: Sequence[Middleware] | None = None,
     multipart_form_part_limit: int = 1000,
-    on_app_init: OptionalSequence[OnAppInitHandler] | None = None,
-    on_shutdown: OptionalSequence[LifespanHook] | None = None,
-    on_startup: OptionalSequence[LifespanHook] | None = None,
+    on_app_init: Sequence[OnAppInitHandler] | None = None,
+    on_shutdown: Sequence[LifespanHook] | None = None,
+    on_startup: Sequence[LifespanHook] | None = None,
     openapi_config: OpenAPIConfig | None = DEFAULT_OPENAPI_CONFIG,
     opt: Mapping[str, Any] | None = None,
     parameters: ParametersMap | None = None,
-    plugins: OptionalSequence[PluginProtocol] | None = None,
+    plugins: Sequence[PluginProtocol] | None = None,
     lifespan: list[Callable[[Litestar], AbstractAsyncContextManager] | AbstractAsyncContextManager] | None = None,
     raise_server_exceptions: bool = True,
     pdb_on_exception: bool | None = None,
@@ -94,14 +93,14 @@ def create_test_client(
     response_cache_config: ResponseCacheConfig | None = None,
     response_class: ResponseType | None = None,
     response_cookies: ResponseCookies | None = None,
-    response_headers: OptionalSequence[ResponseHeader] | None = None,
+    response_headers: Sequence[ResponseHeader] | None = None,
     return_dto: type[AbstractDTO] | None | EmptyType = Empty,
     root_path: str = "",
-    security: OptionalSequence[SecurityRequirement] | None = None,
+    security: Sequence[SecurityRequirement] | None = None,
     session_config: BaseBackendConfig | None = None,
     signature_namespace: Mapping[str, Any] | None = None,
     state: State | None = None,
-    static_files_config: OptionalSequence[StaticFilesConfig] | None = None,
+    static_files_config: Sequence[StaticFilesConfig] | None = None,
     stores: StoreRegistry | dict[str, Store] | None = None,
     tags: Sequence[str] | None = None,
     template_config: TemplateConfig | None = None,
@@ -300,7 +299,7 @@ def test_my_handler() -> None:
 def create_async_test_client(
     route_handlers: ControllerRouterHandler | Sequence[ControllerRouterHandler] | None = None,
     *,
-    after_exception: OptionalSequence[AfterExceptionHookHandler] | None = None,
+    after_exception: Sequence[AfterExceptionHookHandler] | None = None,
     after_request: AfterRequestHookHandler | None = None,
     after_response: AfterResponseHookHandler | None = None,
     allowed_hosts: Sequence[str] | AllowedHostsConfig | None = None,
@@ -308,7 +307,7 @@ def create_async_test_client(
     backend_options: Mapping[str, Any] | None = None,
     base_url: str = "http://testserver.local",
     before_request: BeforeRequestHookHandler | None = None,
-    before_send: OptionalSequence[BeforeMessageSendHookHandler] | None = None,
+    before_send: Sequence[BeforeMessageSendHookHandler] | None = None,
     cache_control: CacheControlHeader | None = None,
     compression_config: CompressionConfig | None = None,
     cors_config: CORSConfig | None = None,
@@ -319,34 +318,34 @@ def create_async_test_client(
     etag: ETag | None = None,
     event_emitter_backend: type[BaseEventEmitterBackend] = SimpleEventEmitter,
     exception_handlers: ExceptionHandlersMap | None = None,
-    guards: OptionalSequence[Guard] | None = None,
+    guards: Sequence[Guard] | None = None,
     include_in_schema: bool | EmptyType = Empty,
     lifespan: list[Callable[[Litestar], AbstractAsyncContextManager] | AbstractAsyncContextManager] | None = None,
-    listeners: OptionalSequence[EventListener] | None = None,
+    listeners: Sequence[EventListener] | None = None,
     logging_config: BaseLoggingConfig | EmptyType | None = Empty,
-    middleware: OptionalSequence[Middleware] | None = None,
+    middleware: Sequence[Middleware] | None = None,
     multipart_form_part_limit: int = 1000,
-    on_app_init: OptionalSequence[OnAppInitHandler] | None = None,
-    on_shutdown: OptionalSequence[LifespanHook] | None = None,
-    on_startup: OptionalSequence[LifespanHook] | None = None,
+    on_app_init: Sequence[OnAppInitHandler] | None = None,
+    on_shutdown: Sequence[LifespanHook] | None = None,
+    on_startup: Sequence[LifespanHook] | None = None,
     openapi_config: OpenAPIConfig | None = DEFAULT_OPENAPI_CONFIG,
     opt: Mapping[str, Any] | None = None,
     parameters: ParametersMap | None = None,
     pdb_on_exception: bool | None = None,
-    plugins: OptionalSequence[PluginProtocol] | None = None,
+    plugins: Sequence[PluginProtocol] | None = None,
     raise_server_exceptions: bool = True,
     request_class: type[Request] | None = None,
     response_cache_config: ResponseCacheConfig | None = None,
     response_class: ResponseType | None = None,
     response_cookies: ResponseCookies | None = None,
-    response_headers: OptionalSequence[ResponseHeader] | None = None,
+    response_headers: Sequence[ResponseHeader] | None = None,
     return_dto: type[AbstractDTO] | None | EmptyType = Empty,
     root_path: str = "",
-    security: OptionalSequence[SecurityRequirement] | None = None,
+    security: Sequence[SecurityRequirement] | None = None,
     session_config: BaseBackendConfig | None = None,
     signature_namespace: Mapping[str, Any] | None = None,
     state: State | None = None,
-    static_files_config: OptionalSequence[StaticFilesConfig] | None = None,
+    static_files_config: Sequence[StaticFilesConfig] | None = None,
     stores: StoreRegistry | dict[str, Store] | None = None,
     tags: Sequence[str] | None = None,
     template_config: TemplateConfig | None = None,
diff --git a/tests/unit/test_signature/test_parsing.py b/tests/unit/test_signature/test_parsing.py
--- a/tests/unit/test_signature/test_parsing.py
+++ b/tests/unit/test_signature/test_parsing.py
@@ -11,7 +11,6 @@
 from litestar.status_codes import HTTP_200_OK, HTTP_204_NO_CONTENT
 from litestar.testing import TestClient, create_test_client
 from litestar.types import Empty
-from litestar.types.helper_types import OptionalSequence
 from litestar.utils.signature import ParsedSignature
 
 
@@ -114,7 +113,7 @@ def fn(a: Iterable[int], b: Optional[Iterable[int]]) -> None:
 
 
 def test_field_definition_is_non_string_sequence() -> None:
-    def fn(a: Sequence[int], b: OptionalSequence[int]) -> None:
+    def fn(a: Sequence[int], b: Optional[Sequence[int]]) -> None:
         pass
 
     model = SignatureModel.create(