|
21 | 21 | else: |
22 | 22 | from typing_extensions import Self |
23 | 23 |
|
24 | | - |
25 | 24 | _COLUMN_ATTR = "__dataframely_columns__" |
26 | 25 | _RULE_ATTR = "__dataframely_rules__" |
27 | 26 |
|
28 | 27 | ORIGINAL_COLUMN_PREFIX = "__DATAFRAMELY_ORIGINAL__" |
29 | 28 |
|
| 29 | + |
30 | 30 | # --------------------------------------- UTILS -------------------------------------- # |
31 | 31 |
|
32 | 32 |
|
@@ -84,6 +84,25 @@ class Metadata: |
84 | 84 | rules: dict[str, RuleFactory] = field(default_factory=dict) |
85 | 85 |
|
86 | 86 | def update(self, other: Self) -> None: |
| 87 | + """Merge another Metadata instance into this one. |
| 88 | +
|
| 89 | + Overlapping keys are allowed if and only if they refer to the *same* underlying |
| 90 | + object. This accommodates multiple-inheritance / diamond patterns where the same |
| 91 | + base schema is visited more than once. |
| 92 | + """ |
| 93 | + # Detect conflicting column definitions: same name, different Column instance |
| 94 | + duplicated_column_names = self.columns.keys() & other.columns.keys() |
| 95 | + conflicting_columns = { |
| 96 | + name |
| 97 | + for name in duplicated_column_names |
| 98 | + if self.columns[name] is not other.columns[name] |
| 99 | + } |
| 100 | + if conflicting_columns: |
| 101 | + raise ImplementationError( |
| 102 | + f"Columns {conflicting_columns} are duplicated with conflicting definitions." |
| 103 | + ) |
| 104 | + |
| 105 | + # All clear |
87 | 106 | self.columns.update(other.columns) |
88 | 107 | self.rules.update(other.rules) |
89 | 108 |
|
@@ -203,6 +222,8 @@ def _get_metadata(source: dict[str, Any]) -> Metadata: |
203 | 222 | k: v for k, v in source.items() if not k.startswith("__") |
204 | 223 | }.items(): |
205 | 224 | if isinstance(value, Column): |
| 225 | + if (col_name := value.alias or attr) in result.columns: |
| 226 | + raise ImplementationError(f"Column {col_name!r} is duplicated.") |
206 | 227 | result.columns[value.alias or attr] = value |
207 | 228 | if isinstance(value, RuleFactory): |
208 | 229 | # We must ensure that custom rules do not clash with internal rules. |
|
0 commit comments