Temporarily patch the stdlib dataclasses bases of `cls` if the Pydantic `Field()` function is used. When creating a Pydantic dataclass, it is possible to inherit from stdlib dataclasses, where the Pydantic `Field()` function is used. To create this Pydantic dataclass, we first apply the
(cls: type[Any])
| 228 | |
| 229 | @contextmanager |
| 230 | def patch_base_fields(cls: type[Any]) -> Generator[None]: |
| 231 | """Temporarily patch the stdlib dataclasses bases of `cls` if the Pydantic `Field()` function is used. |
| 232 | |
| 233 | When creating a Pydantic dataclass, it is possible to inherit from stdlib dataclasses, where |
| 234 | the Pydantic `Field()` function is used. To create this Pydantic dataclass, we first apply |
| 235 | the stdlib `@dataclass` decorator on it. During the construction of the stdlib dataclass, |
| 236 | the `kw_only` and `repr` field arguments need to be understood by the stdlib *during* the |
| 237 | dataclass construction. To do so, we temporarily patch the fields dictionary of the affected |
| 238 | bases. |
| 239 | |
| 240 | For instance, with the following example: |
| 241 | |
| 242 | ```python {test="skip" lint="skip"} |
| 243 | import dataclasses as stdlib_dc |
| 244 | |
| 245 | import pydantic |
| 246 | import pydantic.dataclasses as pydantic_dc |
| 247 | |
| 248 | @stdlib_dc.dataclass |
| 249 | class A: |
| 250 | a: int = pydantic.Field(repr=False) |
| 251 | |
| 252 | # Notice that the `repr` attribute of the dataclass field is `True`: |
| 253 | A.__dataclass_fields__['a'] |
| 254 | #> dataclass.Field(default=FieldInfo(repr=False), repr=True, ...) |
| 255 | |
| 256 | @pydantic_dc.dataclass |
| 257 | class B(A): |
| 258 | b: int = pydantic.Field(repr=False) |
| 259 | ``` |
| 260 | |
| 261 | When passing `B` to the stdlib `@dataclass` decorator, it will look for fields in the parent classes |
| 262 | and reuse them directly. When this context manager is active, `A` will be temporarily patched to be |
| 263 | equivalent to: |
| 264 | |
| 265 | ```python {test="skip" lint="skip"} |
| 266 | @stdlib_dc.dataclass |
| 267 | class A: |
| 268 | a: int = stdlib_dc.field(default=Field(repr=False), repr=False) |
| 269 | ``` |
| 270 | |
| 271 | !!! note |
| 272 | This is only applied to the bases of `cls`, and not `cls` itself. The reason is that the Pydantic |
| 273 | dataclass decorator "owns" `cls` (in the previous example, `B`). As such, we instead modify the fields |
| 274 | directly (in the previous example, we simply do `setattr(B, 'b', as_dataclass_field(pydantic_field))`). |
| 275 | |
| 276 | !!! note |
| 277 | This approach is far from ideal, and can probably be the source of unwanted side effects/race conditions. |
| 278 | The previous implemented approach was mutating the `__annotations__` dict of `cls`, which is no longer a |
| 279 | safe operation in Python 3.14+, and resulted in unexpected behavior with field ordering anyway. |
| 280 | """ |
| 281 | # A list of two-tuples, the first element being a reference to the |
| 282 | # dataclass fields dictionary, the second element being a mapping between |
| 283 | # the field names that were modified, and their original `Field`: |
| 284 | original_fields_list: list[tuple[DcFields, DcFields]] = [] |
| 285 | |
| 286 | for base in cls.__mro__[1:]: |
| 287 | dc_fields: dict[str, dataclasses.Field[Any]] = base.__dict__.get('__dataclass_fields__', {}) |