Analyze the class body of an attr maker, its parents, and return the Attributes found. auto_attribs=True means we'll generate attributes from type annotations also. auto_attribs=None means we'll detect which mode to use. class_kw_only=True means that all attributes created here will be
(
ctx: mypy.plugin.ClassDefContext, auto_attribs: bool | None, class_kw_only: bool
)
| 423 | |
| 424 | |
| 425 | def _analyze_class( |
| 426 | ctx: mypy.plugin.ClassDefContext, auto_attribs: bool | None, class_kw_only: bool |
| 427 | ) -> list[Attribute]: |
| 428 | """Analyze the class body of an attr maker, its parents, and return the Attributes found. |
| 429 | |
| 430 | auto_attribs=True means we'll generate attributes from type annotations also. |
| 431 | auto_attribs=None means we'll detect which mode to use. |
| 432 | class_kw_only=True means that all attributes created here will be keyword only args by |
| 433 | default in __init__. |
| 434 | """ |
| 435 | own_attrs: dict[str, Attribute] = {} |
| 436 | if auto_attribs is None: |
| 437 | auto_attribs = _detect_auto_attribs(ctx) |
| 438 | |
| 439 | # Walk the body looking for assignments and decorators. |
| 440 | for stmt in ctx.cls.defs.body: |
| 441 | if isinstance(stmt, AssignmentStmt): |
| 442 | for attr in _attributes_from_assignment(ctx, stmt, auto_attribs, class_kw_only): |
| 443 | # When attrs are defined twice in the same body we want to use the 2nd definition |
| 444 | # in the 2nd location. So remove it from the OrderedDict. |
| 445 | # Unless it's auto_attribs in which case we want the 2nd definition in the |
| 446 | # 1st location. |
| 447 | if not auto_attribs and attr.name in own_attrs: |
| 448 | del own_attrs[attr.name] |
| 449 | own_attrs[attr.name] = attr |
| 450 | elif isinstance(stmt, Decorator): |
| 451 | _cleanup_decorator(stmt, own_attrs) |
| 452 | |
| 453 | for attribute in own_attrs.values(): |
| 454 | # Even though these look like class level assignments we want them to look like |
| 455 | # instance level assignments. |
| 456 | if attribute.name in ctx.cls.info.names: |
| 457 | node = ctx.cls.info.names[attribute.name].node |
| 458 | if isinstance(node, PlaceholderNode): |
| 459 | # This node is not ready yet. |
| 460 | continue |
| 461 | assert isinstance(node, Var), node |
| 462 | node.is_initialized_in_class = False |
| 463 | |
| 464 | # Traverse the MRO and collect attributes from the parents. |
| 465 | taken_attr_names = set(own_attrs) |
| 466 | super_attrs = [] |
| 467 | for super_info in ctx.cls.info.mro[1:-1]: |
| 468 | if "attrs" in super_info.metadata: |
| 469 | # Each class depends on the set of attributes in its attrs ancestors. |
| 470 | ctx.api.add_plugin_dependency(make_wildcard_trigger(super_info.fullname)) |
| 471 | |
| 472 | for data in super_info.metadata["attrs"]["attributes"]: |
| 473 | # Only add an attribute if it hasn't been defined before. This |
| 474 | # allows for overwriting attribute definitions by subclassing. |
| 475 | if data["name"] not in taken_attr_names: |
| 476 | a = Attribute.deserialize(super_info, data, ctx.api) |
| 477 | a.expand_typevar_from_subtype(ctx.cls.info) |
| 478 | super_attrs.append(a) |
| 479 | taken_attr_names.add(a.name) |
| 480 | attributes = super_attrs + list(own_attrs.values()) |
| 481 | |
| 482 | # Check the init args for correct default-ness. Note: This has to be done after all the |
no test coverage detected
searching dependent graphs…