Find the dataclass transform spec for the given node, if any exists. Per PEP 681 (https://peps.python.org/pep-0681/#the-dataclass-transform-decorator), dataclass transforms can be specified in multiple ways, including decorator functions and metaclasses/base classes. This function
(node: Node | None)
| 378 | |
| 379 | |
| 380 | def find_dataclass_transform_spec(node: Node | None) -> DataclassTransformSpec | None: |
| 381 | """ |
| 382 | Find the dataclass transform spec for the given node, if any exists. |
| 383 | |
| 384 | Per PEP 681 (https://peps.python.org/pep-0681/#the-dataclass-transform-decorator), dataclass |
| 385 | transforms can be specified in multiple ways, including decorator functions and |
| 386 | metaclasses/base classes. This function resolves the spec from any of these variants. |
| 387 | """ |
| 388 | |
| 389 | # The spec only lives on the function/class definition itself, so we need to unwrap down to that |
| 390 | # point |
| 391 | if isinstance(node, CallExpr): |
| 392 | # Like dataclasses.dataclass, transform-based decorators can be applied either with or |
| 393 | # without parameters; ie, both of these forms are accepted: |
| 394 | # |
| 395 | # @typing.dataclass_transform |
| 396 | # class Foo: ... |
| 397 | # @typing.dataclass_transform(eq=True, order=True, ...) |
| 398 | # class Bar: ... |
| 399 | # |
| 400 | # We need to unwrap the call for the second variant. |
| 401 | node = node.callee |
| 402 | |
| 403 | if isinstance(node, RefExpr): |
| 404 | node = node.node |
| 405 | |
| 406 | if isinstance(node, Decorator): |
| 407 | # typing.dataclass_transform usage must always result in a Decorator; it always uses the |
| 408 | # `@dataclass_transform(...)` syntax and never `@dataclass_transform` |
| 409 | node = node.func |
| 410 | |
| 411 | if isinstance(node, OverloadedFuncDef): |
| 412 | # The dataclass_transform decorator may be attached to any single overload, so we must |
| 413 | # search them all. |
| 414 | # Note that using more than one decorator is undefined behavior, so we can just take the |
| 415 | # first that we find. |
| 416 | for candidate in node.items: |
| 417 | spec = find_dataclass_transform_spec(candidate) |
| 418 | if spec is not None: |
| 419 | return spec |
| 420 | return find_dataclass_transform_spec(node.impl) |
| 421 | |
| 422 | # For functions, we can directly consult the AST field for the spec |
| 423 | if isinstance(node, FuncDef): |
| 424 | return node.dataclass_transform_spec |
| 425 | |
| 426 | if isinstance(node, ClassDef): |
| 427 | node = node.info |
| 428 | if isinstance(node, TypeInfo): |
| 429 | # Search all parent classes to see if any are decorated with `typing.dataclass_transform` |
| 430 | for base in node.mro[1:]: |
| 431 | if base.dataclass_transform_spec is not None: |
| 432 | return base.dataclass_transform_spec |
| 433 | |
| 434 | # Check if there is a metaclass that is decorated with `typing.dataclass_transform` |
| 435 | # |
| 436 | # Note that PEP 681 only discusses using a metaclass that is directly decorated with |
| 437 | # `typing.dataclass_transform`; subclasses thereof should be treated with dataclass |
no test coverage detected
searching dependent graphs…