Repository: litestar-org/litestar

Issue:
Bug: Premature instantiation of nested models
### Description

Assume two dataclasses `Person `and `Address`. `Person `contains an `address` (see MCVE).
Using `DataDTO[Person]` in a route handler results in a `TypeError(\"Address.__init__() missing 1 required positional argument: 'id'\")` because it tries to initialize the nested `Address` (which fails because the `address.id` is excluded from the `WriteDTO`).

### URL to code causing the issue

https://discord.com/channels/919193495116337154/1110854577575698463

### MCVE

```python
from dataclasses import dataclass
import uuid

from litestar import Litestar, post
from litestar.dto.factory import DTOConfig, DTOData
from litestar.dto.factory.stdlib import DataclassDTO

@dataclass
class Address:
    id: uuid.UUID
    street: str
    city: str

@dataclass
class Person:
    id: uuid.UUID
    name: str
    email: str
    address: Address

class WriteDTO(DataclassDTO[Person]):
    config = DTOConfig(exclude={"id", "address.id"})

@post("/person", dto=WriteDTO, return_dto=None, sync_to_thread=False)
def create_person(data: DTOData[Person]) -> str:
    return "Success"

app = Litestar([create_person])
```


### Steps to reproduce

```bash
curl -X 'POST' \
  'http://127.0.0.1:8000/person' \
  -H 'accept: text/plain' \
  -H 'Content-Type: application/json' \
  -d '{
  "name": "test",
  "email": "test",
  "address": {
    "street": "test",
    "city": "test"
  }
}'
```


### Screenshots

```bash
"![SCREENSHOT_DESCRIPTION](SCREENSHOT_LINK.png)"
```


### Logs

```bash
INFO:     127.0.0.1:36960 - "POST /person HTTP/1.1" 500 Internal Server Error
```


### Litestar Version

2.0.0a7

### Platform

- [X] Linux
- [X] Mac
- [X] Windows
- [ ] Other (Please specify in the description above)


Discussion:
Thanks @MoBoo - confirmed.

Resolving patch:
diff --git a/litestar/dto/factory/_backends/utils.py b/litestar/dto/factory/_backends/utils.py
--- a/litestar/dto/factory/_backends/utils.py
+++ b/litestar/dto/factory/_backends/utils.py
@@ -171,13 +171,23 @@ def filter_missing(value: Any) -> bool:
         source_value = source_instance[source_name] if source_is_mapping else getattr(source_instance, source_name)
         if field_definition.is_partial and dto_for == "data" and filter_missing(source_value):
             continue
-        unstructured_data[destination_name] = transfer_type_data(source_value, transfer_type, dto_for)
+        unstructured_data[destination_name] = transfer_type_data(
+            source_value, transfer_type, dto_for, nested_as_dict=destination_type is dict
+        )
     return destination_type(**unstructured_data)
 
 
-def transfer_type_data(source_value: Any, transfer_type: TransferType, dto_for: ForType) -> Any:
+def transfer_type_data(
+    source_value: Any, transfer_type: TransferType, dto_for: ForType, nested_as_dict: bool = False
+) -> Any:
     if isinstance(transfer_type, SimpleType) and transfer_type.nested_field_info:
-        dest_type = transfer_type.parsed_type.annotation if dto_for == "data" else transfer_type.nested_field_info.model
+        if nested_as_dict:
+            dest_type = dict
+        else:
+            dest_type = (
+                transfer_type.parsed_type.annotation if dto_for == "data" else transfer_type.nested_field_info.model
+            )
+
         return transfer_nested_simple_type_data(dest_type, transfer_type.nested_field_info, dto_for, source_value)
     if isinstance(transfer_type, UnionType) and transfer_type.has_nested:
         return transfer_nested_union_type_data(transfer_type, dto_for, source_value)


Test patch:
diff --git a/tests/dto/factory/test_integration.py b/tests/dto/factory/test_integration.py
--- a/tests/dto/factory/test_integration.py
+++ b/tests/dto/factory/test_integration.py
@@ -2,7 +2,7 @@
 from __future__ import annotations
 
 from dataclasses import dataclass, field
-from typing import Optional
+from typing import TYPE_CHECKING, Optional
 
 import pytest
 from typing_extensions import Annotated
@@ -16,6 +16,10 @@
 from litestar.params import Body
 from litestar.testing import create_test_client
 
+if TYPE_CHECKING:
+    from types import ModuleType
+    from typing import Callable
+
 
 def test_url_encoded_form_data() -> None:
     @dataclass
@@ -139,6 +143,43 @@ def handler(data: DTOData[Foo]) -> Foo:
         assert response.json() == {"bar": "hello"}
 
 
+def test_dto_data_injection_with_nested_model(create_module: Callable[[str], ModuleType]) -> None:
+    module = create_module(
+        """
+from dataclasses import dataclass
+from typing import Any, Dict
+
+from typing_extensions import Annotated
+
+from litestar import post
+from litestar.dto.factory import DTOConfig, DTOData
+from litestar.dto.factory.stdlib import DataclassDTO
+
+@dataclass
+class Foo:
+    bar: str
+    baz: str
+
+@dataclass
+class Bar:
+    foo: Foo
+
+config = DTOConfig(exclude={"foo.baz"})
+dto = DataclassDTO[Annotated[Bar, config]]
+
+@post(dto=dto, return_dto=None)
+def handler(data: DTOData[Bar]) -> Dict[str, Any]:
+    assert isinstance(data, DTOData)
+    return data.as_builtins()
+"""
+    )
+
+    with create_test_client(route_handlers=[module.handler], debug=True) as client:
+        resp = client.post("/", json={"foo": {"bar": "hello"}})
+        assert resp.status_code == 201
+        assert resp.json() == {"foo": {"bar": "hello"}}
+
+
 def test_dto_data_with_url_encoded_form_data() -> None:
     @dataclass
     class User: