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/_openapi/schema_generation/schema.py b/litestar/_openapi/schema_generation/schema.py
--- a/litestar/_openapi/schema_generation/schema.py
+++ b/litestar/_openapi/schema_generation/schema.py
@@ -33,6 +33,7 @@
 from uuid import UUID
 
 from _decimal import Decimal
+from msgspec.structs import FieldInfo
 from msgspec.structs import fields as msgspec_struct_fields
 from typing_extensions import Annotated, NotRequired, Required, get_args, get_type_hints
 
@@ -669,12 +670,16 @@ def for_struct_class(self, annotation: type[Struct], dto_for: ForType | None) ->
         Returns:
             A schema instance.
         """
+
+        def _is_field_required(field: FieldInfo) -> bool:
+            return field.required or field.default_factory is Empty
+
         return Schema(
             required=sorted(
                 [
                     field.encode_name
                     for field in msgspec_struct_fields(annotation)
-                    if field.required and not is_optional_union(field.type)
+                    if _is_field_required(field=field) and not is_optional_union(field.type)
                 ]
             ),
             properties={


Test patch:
diff --git a/tests/unit/test_dto/test_factory/test_integration.py b/tests/unit/test_dto/test_factory/test_integration.py
--- a/tests/unit/test_dto/test_factory/test_integration.py
+++ b/tests/unit/test_dto/test_factory/test_integration.py
@@ -5,10 +5,16 @@
 from typing import TYPE_CHECKING, Dict, Optional, Sequence
 from unittest.mock import MagicMock
 
+import msgspec
 import pytest
+from msgspec import Struct
+from pydantic import BaseModel
 from typing_extensions import Annotated
 
-from litestar import patch, post
+from litestar import Litestar, patch, post
+from litestar.connection.request import Request
+from litestar.contrib.msgspec import MsgspecDTO
+from litestar.contrib.pydantic import PydanticDTO
 from litestar.datastructures import UploadFile
 from litestar.dto.factory import DTOConfig, DTOData, dto_field
 from litestar.dto.factory.stdlib.dataclass import DataclassDTO
@@ -681,3 +687,112 @@ def handler() -> Response[List[User]]:
     with TestClient(app=module.app) as client:
         response = client.get("/")
         assert response.json() == [{"name": "John"}, {"name": "Jane"}]
+
+
+def test_schema_required_fields_with_msgspec_dto() -> None:
+    class MsgspecUser(Struct):
+        age: int
+        name: str
+
+    class UserDTO(MsgspecDTO[MsgspecUser]):
+        pass
+
+    @post(dto=UserDTO, return_dto=None, signature_namespace={"MsgspecUser": MsgspecUser})
+    def handler(data: MsgspecUser, request: Request) -> dict:
+        schema = request.app.openapi_schema
+        return schema.to_schema()
+
+    app = Litestar(route_handlers=[handler])
+    with TestClient(app=app) as client:
+        data = MsgspecUser(name="A", age=10)
+        headers = {}
+        headers["Content-Type"] = "application/json; charset=utf-8"
+        received = client.post(
+            "/",
+            content=msgspec.json.encode(data),
+            headers=headers,
+        )
+        required = list(received.json()["components"]["schemas"].values())[0]["required"]
+        assert len(required) == 2
+
+
+def test_schema_required_fields_with_pydantic_dto() -> None:
+    class PydanticUser(BaseModel):
+        age: int
+        name: str
+
+    class UserDTO(PydanticDTO[PydanticUser]):
+        pass
+
+    @post(dto=UserDTO, return_dto=None, signature_namespace={"PydanticUser": PydanticUser})
+    def handler(data: PydanticUser, request: Request) -> dict:
+        schema = request.app.openapi_schema
+        return schema.to_schema()
+
+    app = Litestar(route_handlers=[handler])
+    with TestClient(app=app) as client:
+        data = PydanticUser(name="A", age=10)
+        headers = {}
+        headers["Content-Type"] = "application/json; charset=utf-8"
+        received = client.post(
+            "/",
+            content=data.json(),
+            headers=headers,
+        )
+        required = list(received.json()["components"]["schemas"].values())[0]["required"]
+        assert len(required) == 2
+
+
+def test_schema_required_fields_with_dataclass_dto() -> None:
+    @dataclass
+    class DataclassUser:
+        age: int
+        name: str
+
+    class UserDTO(DataclassDTO[DataclassUser]):
+        pass
+
+    @post(dto=UserDTO, return_dto=None, signature_namespace={"DataclassUser": DataclassUser})
+    def handler(data: DataclassUser, request: Request) -> dict:
+        schema = request.app.openapi_schema
+        return schema.to_schema()
+
+    app = Litestar(route_handlers=[handler])
+    with TestClient(app=app) as client:
+        data = DataclassUser(name="A", age=10)
+        headers = {}
+        headers["Content-Type"] = "application/json; charset=utf-8"
+        received = client.post(
+            "/",
+            content=msgspec.json.encode(data),
+            headers=headers,
+        )
+        required = list(received.json()["components"]["schemas"].values())[0]["required"]
+        assert len(required) == 2
+
+
+def test_schema_required_fields_with_msgspec_dto_and_default_fields() -> None:
+    class MsgspecUser(Struct):
+        age: int
+        name: str = "A"
+
+    class UserDTO(MsgspecDTO[MsgspecUser]):
+        pass
+
+    @post(dto=UserDTO, return_dto=None, signature_namespace={"MsgspecUser": MsgspecUser})
+    def handler(data: MsgspecUser, request: Request) -> dict:
+        schema = request.app.openapi_schema
+        return schema.to_schema()
+
+    app = Litestar(route_handlers=[handler])
+    with TestClient(app=app) as client:
+        data = MsgspecUser(name="A", age=10)
+        headers = {}
+        headers["Content-Type"] = "application/json; charset=utf-8"
+        received = client.post(
+            "/",
+            content=msgspec.json.encode(data),
+            headers=headers,
+        )
+        required = list(received.json()["components"]["schemas"].values())[0]["required"]
+        assert required == ["age"]
diff --git a/tests/unit/test_openapi/test_schema.py b/tests/unit/test_openapi/test_schema.py
--- a/tests/unit/test_openapi/test_schema.py
+++ b/tests/unit/test_openapi/test_schema.py
@@ -254,6 +254,7 @@ class Lookup(msgspec.Struct):
     assert schema.properties["id"].description == "description"  # type: ignore
     assert schema.properties["id"].title == "title"  # type: ignore
     assert schema.properties["id"].max_length == 16  # type: ignore
+    assert schema.required == ["id"]
 
 
 def test_create_schema_for_pydantic_field() -> None: