Repository: pydantic/pydantic (Python)

Issue:
2.10: dataclass config issues (`arbitrary_types_allowed`, aliases, etc)
> Edited by @Viicos, see below for the original issue

Pydantic <2.10 had a bug with dataclasses, where the config of the model that uses the dataclass as an annotation would be used.

This means that the following would work:

```python
from dataclasses import dataclass

from pydantic import BaseModel, ConfigDict

class ArbitraryType: pass

@dataclass
class DC:
    a: ArbitraryType


class Model(BaseModel):
    model_config = ConfigDict(arbitrary_types_allowed=True)

    dc: DC
```

But it was inconsistent for two reasons:

- if `DC` were to be a Pydantic model, it wouldn't inherit the config of `Model`
- if you happen to set an unrelated configuration value on `DC` (as per [the docs](https://docs.pydantic.dev/latest/concepts/config/#configuration-with-dataclass-from-the-standard-library-or-typeddict)), it wouldn't inherit the `arbitrary_types_allowed` configuration value from the parent class.

This was fixed in https://github.com/pydantic/pydantic/pull/10576.

However, we realize it might be hard to set a configuration on `DC`, as it is a stdlib dataclasses and thus, in some scenarios, might be a type from an external library/application that users do not control.

Use cases welcomed, outcome of this issue still TBD.

<details>

<summary>Initial issue description (hidden for clarity):</summary>

### Description

Pydantic cannot generate core schema (and therefore create the model class) for model with `arbitrary_types_allowed=True` and a dataclass field that contains "arbitrary type".

See the example code below. `WorkingModel` is created correctly, the `NotWorkingModel` is not, exception is raised:

```raw
pydantic.errors.PydanticSchemaGenerationError: Unable to generate pydantic-core schema for <class '__main__.ArbitraryClass'>. Set `arbitrary_types_allowed=True` in the model_config to ignore this error or implement `__get_pydantic_core_schema__` on your type to fully support it.

If you got this error by calling handler(<some type>) within `__get_pydantic_core_schema__` then you likely need to call `handler.generate_schema(<some type>)` since we do not call `__get_pydantic_core_schema__` on `<some type>` otherwise to avoid infinite recursion.

For further information visit https://errors.pydantic.dev/2.10/u/schema-for-unknown-type
```

The workaround is to define dataclass like this:

```python
@dataclass
class WrappingDataClass:
    vsm_object: ArbitraryClass
    __pydantic_config__ = ConfigDict(arbitrary_types_allowed=True)
```

### Example Code

```Python
from dataclasses import dataclass

from pydantic import BaseModel, ConfigDict


class ArbitraryClass: ...


@dataclass
class WrappingDataClass:
    vsm_object: ArbitraryClass


class WorkingModel(BaseModel):
    model_config = ConfigDict(arbitrary_types_allowed=True)

    my_field: ArbitraryClass


class NotWorkingModel(BaseModel):
    model_config = ConfigDict(arbitrary_types_allowed=True)

    my_field: WrappingDataClass
```


### Python, Pydantic & OS Version

```Text
pydantic version: 2.10.0
        pydantic-core version: 2.27.0
          pydantic-core build: profile=release pgo=false
                 install path: C:\repos\panser\panser-fortigate\.venv\Lib\site-packages\pydantic
               python version: 3.11.9 (tags/v3.11.9:de54cf5, Apr  2 2024, 10:12:12) [MSC v.1938 64 bit (AMD64)]
                     platform: Windows-10-10.0.22631-SP0
             related packages: mypy-1.12.1 typing_extensions-4.12.2
                       commit: unknown
```

</details>



Discussion:
We used this functionality to create a Fastapi response model created from a dataclass defined in a external dependency.
The external dataclass also contains nested dataclasses so it would be a pain to override all internal dataclasses with a manual crafted pydantic model.

https://github.com/duo-labs/py_webauthn/blob/3504cd706204ffc67371e485126051cfaed5fb0c/webauthn/helpers/structs.py#L284-L308

```python
from webauthn.helpers.structs import PublicKeyCredentialCreationOptions
from pydantic import BaseModel

class PasskeyRegistrationChallenge(BaseModel, PublicKeyCredentialCreationOptions):
    model_config = {
        "ser_json_bytes": "base64",
        "alias_generator": snake_case_to_camel_case,
        "populate_by_name": True,
    }
```
We define those dataclassess in a separate component that is not aware about Pydantic at all. They are also used in non-Pydantic context, so we would not like to make them Pydantic aware.
Upon further consideration, we've decided we definitely should fix this.

Pydantic types have strict config boundaries (models, pydantic dataclasses, etc), but folks are relying on config pushdown for nested vanilla dataclasses and typed dicts, so we'll fix this promptly for v2.10.1.
Hey folks! We've just released [v2.10.1](https://github.com/pydantic/pydantic/releases/tag/v2.10.1) with a fix for this issue!

Let us know if you're still experiencing any difficulties. Thanks!



Patch:
diff --git a/docs/concepts/dataclasses.md b/docs/concepts/dataclasses.md
index 539366c6057..e2411f7183e 100644
--- a/docs/concepts/dataclasses.md
+++ b/docs/concepts/dataclasses.md
@@ -249,7 +249,7 @@ configuration value on the dataclass:
 ```python
 import dataclasses
 
-from pydantic import BaseModel
+from pydantic import BaseModel, ConfigDict
 from pydantic.errors import PydanticSchemaGenerationError
 
 
@@ -276,6 +276,9 @@ try:
         dc: DC
         other: str
 
+    # invalid as dc is now validated with pydantic, and ArbitraryType is not a known type
+    Model(dc=my_dc, other='other')
+
 except PydanticSchemaGenerationError as e:
     print(e.message)
     """
@@ -285,22 +288,17 @@ except PydanticSchemaGenerationError as e:
     """
 
 
-@dataclasses.dataclass
-class DC2:
-    a: ArbitraryType
-    b: str
-
-    __pydantic_config__ = {'arbitrary_types_allowed': True}
-
-
+# valid as we set arbitrary_types_allowed=True, and that config pushes down to the nested vanilla dataclass
 class Model(BaseModel):
-    dc: DC2
+    model_config = ConfigDict(arbitrary_types_allowed=True)
+
+    dc: DC
     other: str
 
 
-m = Model(dc=DC2(a=ArbitraryType(value=3), b='qwe'), other='other')
+m = Model(dc=my_dc, other='other')
 print(repr(m))
-#> Model(dc=DC2(a=ArbitraryType(value=3), b='qwe'), other='other')
+#> Model(dc=DC(a=ArbitraryType(value=3), b='qwe'), other='other')
 ```
 
 ### Checking if a dataclass is a Pydantic dataclass
diff --git a/pydantic/_internal/_generate_schema.py b/pydantic/_internal/_generate_schema.py
index c9bceddb99b..2d45056f31d 100644
--- a/pydantic/_internal/_generate_schema.py
+++ b/pydantic/_internal/_generate_schema.py
@@ -1411,6 +1411,8 @@ def _typed_dict_schema(self, typed_dict_cls: Any, origin: Any) -> core_schema.Co
                 )
 
             try:
+                # if a typed dictionary class doesn't have config, we use the parent's config, hence a default of `None`
+                # see https://github.com/pydantic/pydantic/issues/10917
                 config: ConfigDict | None = get_attribute_from_bases(typed_dict_cls, '__pydantic_config__')
             except AttributeError:
                 config = None
@@ -1738,7 +1740,10 @@ def _dataclass_schema(
             if origin is not None:
                 dataclass = origin
 
-            config = getattr(dataclass, '__pydantic_config__', ConfigDict())
+            # if (plain) dataclass doesn't have config, we use the parent's config, hence a default of `None`
+            # (Pydantic dataclasses have an empty dict config by default).
+            # see https://github.com/pydantic/pydantic/issues/10917
+            config = getattr(dataclass, '__pydantic_config__', None)
 
             from ..dataclasses import is_pydantic_dataclass
 


Test patch:
diff --git a/tests/test_dataclasses.py b/tests/test_dataclasses.py
index f69600d6d31..91c1f7857bf 100644
--- a/tests/test_dataclasses.py
+++ b/tests/test_dataclasses.py
@@ -21,7 +21,6 @@
     BeforeValidator,
     ConfigDict,
     PydanticDeprecatedSince20,
-    PydanticSchemaGenerationError,
     PydanticUndefinedAnnotation,
     PydanticUserError,
     RootModel,
@@ -3054,21 +3053,15 @@ class Foo:
             __pydantic_config__ = ConfigDict(title='from __pydantic_config__')
 
 
-def test_do_not_leak_config_from_other_types_during_building() -> None:
-    """Regression test, where dataclasses without any config would use the precedent type's config."""
-
+def test_config_pushdown_vanilla_dc() -> None:
     class ArbitraryType:
         pass
 
     @dataclasses.dataclass
     class DC:
         a: ArbitraryType
-        b: str
-
-    with pytest.raises(PydanticSchemaGenerationError):
 
-        class Model(BaseModel):
-            model_config = ConfigDict(arbitrary_types_allowed=True)
+    class Model(BaseModel):
+        model_config = ConfigDict(arbitrary_types_allowed=True)
 
-            dc: DC
-            other: str
+        dc: DC
diff --git a/tests/test_types_typeddict.py b/tests/test_types_typeddict.py
index aeb137ef4bd..9cc77dc7c97 100644
--- a/tests/test_types_typeddict.py
+++ b/tests/test_types_typeddict.py
@@ -916,3 +916,16 @@ class Model(TypedDict):
     ta = TypeAdapter(Model)
 
     assert ta.validate_python({'x': 'ABC'}) == {'x': 'abc'}
+
+
+def test_config_pushdown_typed_dict() -> None:
+    class ArbitraryType:
+        pass
+
+    class TD(TypedDict):
+        a: ArbitraryType
+
+    class Model(BaseModel):
+        model_config = ConfigDict(arbitrary_types_allowed=True)
+
+        td: TD