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/contrib/sqlalchemy/dto.py b/litestar/contrib/sqlalchemy/dto.py
--- a/litestar/contrib/sqlalchemy/dto.py
+++ b/litestar/contrib/sqlalchemy/dto.py
@@ -9,9 +9,11 @@
 from sqlalchemy.ext.hybrid import HybridExtensionType, hybrid_property
 from sqlalchemy.orm import (
     ColumnProperty,
+    CompositeProperty,
     DeclarativeBase,
     InspectionAttr,
     Mapped,
+    MappedColumn,
     NotExtension,
     QueryableAttribute,
     RelationshipDirection,
@@ -35,7 +37,7 @@
 
 T = TypeVar("T", bound="DeclarativeBase | Collection[DeclarativeBase]")
 
-ElementType: TypeAlias = "Column | RelationshipProperty"
+ElementType: TypeAlias = "Column | RelationshipProperty | CompositeProperty"
 SQLA_NS = {**vars(orm), **vars(sql)}
 
 
@@ -72,7 +74,7 @@ def _(
             if not isinstance(orm_descriptor.property.expression, Column):
                 raise NotImplementedError(f"Expected 'Column', got: '{orm_descriptor.property.expression}'")
             elem = orm_descriptor.property.expression
-        elif isinstance(orm_descriptor.property, RelationshipProperty):
+        elif isinstance(orm_descriptor.property, (RelationshipProperty, CompositeProperty)):
             elem = orm_descriptor.property
         else:
             raise NotImplementedError(f"Unhandled property type: '{orm_descriptor.property}'")
@@ -189,6 +191,13 @@ def generate_field_definitions(cls, model_type: type[DeclarativeBase]) -> Genera
         # the same hybrid property descriptor can be included in `all_orm_descriptors` multiple times, once
         # for each method name it is bound to. We only need to see it once, so track views of it here.
         seen_hybrid_descriptors: set[hybrid_property] = set()
+        skipped_columns: set[str] = set()
+        for composite_property in mapper.composites:
+            for attr in composite_property.attrs:
+                if isinstance(attr, (MappedColumn, Column)):
+                    skipped_columns.add(attr.name)
+                elif isinstance(attr, str):
+                    skipped_columns.add(attr)
         for key, orm_descriptor in mapper.all_orm_descriptors.items():
             if isinstance(orm_descriptor, hybrid_property):
                 if orm_descriptor in seen_hybrid_descriptors:
@@ -196,6 +205,9 @@ def generate_field_definitions(cls, model_type: type[DeclarativeBase]) -> Genera
 
                 seen_hybrid_descriptors.add(orm_descriptor)
 
+            if key in skipped_columns:
+                continue
+
             yield from cls.handle_orm_descriptor(
                 orm_descriptor.extension_type, key, orm_descriptor, model_type_hints, model_name
             )
@@ -262,6 +274,9 @@ def parse_type_from_element(elem: ElementType) -> FieldDefinition:
 
         return FieldDefinition.from_annotation(elem.mapper.class_)
 
+    if isinstance(elem, CompositeProperty):
+        return FieldDefinition.from_annotation(elem.composite_class)
+
     raise ImproperlyConfiguredException(
         f"Unable to parse type from element '{elem}'. Consider adding a type hint.",
     )


Test patch:
diff --git a/tests/unit/test_contrib/test_sqlalchemy/test_dto_integration.py b/tests/unit/test_contrib/test_sqlalchemy/test_dto_integration.py
--- a/tests/unit/test_contrib/test_sqlalchemy/test_dto_integration.py
+++ b/tests/unit/test_contrib/test_sqlalchemy/test_dto_integration.py
@@ -3,8 +3,8 @@
 from typing import Any, Callable, Dict, List, Tuple
 
 import pytest
-from sqlalchemy import ForeignKey, String
-from sqlalchemy.orm import DeclarativeBase, Mapped, declared_attr, mapped_column, relationship
+from sqlalchemy import Column, ForeignKey, Integer, String, Table
+from sqlalchemy.orm import DeclarativeBase, Mapped, composite, declared_attr, mapped_column, relationship
 from typing_extensions import Annotated
 
 from litestar import get, post
@@ -328,6 +328,121 @@ def get_handler(data: Circle) -> Circle:
         assert module.DIAMETER == 10
 
 
+async def test_dto_with_composite_map() -> None:
+    @dataclass
+    class Point:
+        x: int
+        y: int
+
+    class Vertex1(Base):
+        start: Mapped[Point] = composite(mapped_column("x1"), mapped_column("y1"))
+        end: Mapped[Point] = composite(mapped_column("x2"), mapped_column("y2"))
+
+    dto = SQLAlchemyDTO[Vertex1]
+
+    @post(dto=dto, signature_namespace={"Vertex": Vertex1})
+    def post_handler(data: Vertex1) -> Vertex1:
+        return data
+
+    with create_test_client(route_handlers=[post_handler]) as client:
+        response = client.post(
+            "/",
+            json={
+                "id": "1",
+                "start": {"x": 10, "y": 20},
+                "end": {"x": 1, "y": 2},
+            },
+        )
+        assert response.json() == {
+            "id": "1",
+            "start": {"x": 10, "y": 20},
+            "end": {"x": 1, "y": 2},
+        }
+
+
+async def test_dto_with_composite_map_using_explicit_columns() -> None:
+    @dataclass
+    class Point:
+        x: int
+        y: int
+
+    class Vertex2(Base):
+        x1: Mapped[int]
+        y1: Mapped[int]
+        x2: Mapped[int]
+        y2: Mapped[int]
+
+        start: Mapped[Point] = composite("x1", "y1")
+        end: Mapped[Point] = composite("x2", "y2")
+
+    dto = SQLAlchemyDTO[Vertex2]
+
+    @post(dto=dto, signature_namespace={"Vertex": Vertex2})
+    def post_handler(data: Vertex2) -> Vertex2:
+        return data
+
+    with create_test_client(route_handlers=[post_handler]) as client:
+        response = client.post(
+            "/",
+            json={
+                "id": "1",
+                "start": {"x": 10, "y": 20},
+                "end": {"x": 1, "y": 2},
+            },
+        )
+        assert response.json() == {
+            "id": "1",
+            "start": {"x": 10, "y": 20},
+            "end": {"x": 1, "y": 2},
+        }
+
+
+async def test_dto_with_composite_map_using_hybrid_imperative_mapping() -> None:
+    @dataclass
+    class Point:
+        x: int
+        y: int
+
+    table = Table(
+        "vertices2",
+        Base.metadata,
+        Column("id", String, primary_key=True),
+        Column("x1", Integer),
+        Column("y1", Integer),
+        Column("x2", Integer),
+        Column("y2", Integer),
+    )
+
+    class Vertex3(Base):
+        __table__ = table
+
+        id: Mapped[str]
+
+        start = composite(Point, table.c.x1, table.c.y1)
+        end = composite(Point, table.c.x2, table.c.y2)
+
+    dto = SQLAlchemyDTO[Vertex3]
+
+    @post(dto=dto, signature_namespace={"Vertex": Vertex3})
+    def post_handler(data: Vertex3) -> Vertex3:
+        return data
+
+    with create_test_client(route_handlers=[post_handler]) as client:
+        response = client.post(
+            "/",
+            json={
+                "id": "1",
+                "start": {"x": 10, "y": 20},
+                "end": {"x": 1, "y": 2},
+            },
+        )
+        assert response.json() == {
+            "id": "1",
+            "start": {"x": 10, "y": 20},
+            "end": {"x": 1, "y": 2},
+        }
+
+
 async def test_field_with_sequence_default(create_module: Callable[[str], ModuleType]) -> None:
     module = create_module(
         """