|
10 | 10 | import types |
11 | 11 | from collections.abc import Callable |
12 | 12 | from copy import deepcopy |
| 13 | +import dataclasses |
13 | 14 | from dataclasses import MISSING, Field, dataclass, field, replace |
14 | 15 | from typing import Any, ClassVar |
15 | 16 |
|
@@ -633,3 +634,37 @@ def resolve_cfg_presets(cfg: object) -> object: |
633 | 634 | else: |
634 | 635 | resolve_cfg_presets(value) |
635 | 636 | return cfg |
| 637 | + |
| 638 | + |
| 639 | +def checked_apply(src: Any, target: Any) -> None: |
| 640 | + """Forward every declared field on ``src`` (a dataclass) onto ``target``. |
| 641 | +
|
| 642 | + Used by Isaac Lab configclasses that mirror an upstream/external dataclass |
| 643 | + (for example, Newton's ``ShapeConfig``): declare the overridable fields |
| 644 | + once on the wrapper, then forward them to the upstream object via this |
| 645 | + helper instead of writing ``setattr`` lines per field. |
| 646 | +
|
| 647 | + Raises :class:`AttributeError` if ``target`` is missing a field declared |
| 648 | + on ``src``. The two structures must match — the check guards against |
| 649 | + silent no-ops when the upstream API drifts (the bug class PR #5289 fixed |
| 650 | + for Newton ``ShapeConfig.contact_margin`` → ``margin``). |
| 651 | +
|
| 652 | + Args: |
| 653 | + src: Dataclass instance whose declared fields will be forwarded. |
| 654 | + Field names live here; this is the single source of truth. |
| 655 | + target: Object to receive the field values. Must already expose |
| 656 | + an attribute for every declared field on ``src``. |
| 657 | +
|
| 658 | + Raises: |
| 659 | + AttributeError: If ``target`` does not already have an attribute |
| 660 | + matching one of ``src``'s declared field names. |
| 661 | + """ |
| 662 | + if not hasattr(src, "__dataclass_fields__"): |
| 663 | + raise TypeError(f"checked_apply: src must be a dataclass, got {type(src).__name__}") |
| 664 | + for f in dataclasses.fields(src): |
| 665 | + if not hasattr(target, f.name): |
| 666 | + target_path = f"{type(target).__module__}.{type(target).__name__}" |
| 667 | + raise AttributeError( |
| 668 | + f"{target_path} has no attribute `{f.name}`. {type(src).__name__} is out of sync with target." |
| 669 | + ) |
| 670 | + setattr(target, f.name, getattr(src, f.name)) |
0 commit comments