Skip to content

Commit c5bb74f

Browse files
chore(python): handle duplicate field names (#3384)
## Why? This PR handles the duplicate of field names for inheritance. ## What does this PR do? The handling is done by making every child class's field override its parent's till the most-derived field kept. - First, we iterate over the full class MRO (cls.__mro__) to extract field metadata from all parent classes. - Then, deduplicate fields so that each field name appears only once in the resulting list with keeping the child class definition if a field is shadowed. - Finally, added some tests to cover: - Single-level inheritance - Multi-level inheritance - Field shadowing in child and grandchild classes - Deduplication of overridden fields - [ ] Does this PR introduce any public API change? - [ ] Does this PR introduce any binary protocol compatibility change?
1 parent 13eda46 commit c5bb74f

2 files changed

Lines changed: 55 additions & 7 deletions

File tree

python/pyfory/meta/typedef_encoder.py

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
# specific language governing permissions and limitations
1616
# under the License.
1717

18-
from collections import Counter
1918

2019
from pyfory.meta.typedef import (
2120
FieldInfo,
@@ -61,12 +60,6 @@ def encode_typedef(type_resolver, cls, include_fields: bool = True):
6160
"""
6261
if include_fields:
6362
field_infos = build_field_infos(type_resolver, cls)
64-
# Check for duplicate field names
65-
field_names = [field_info.name for field_info in field_infos]
66-
duplicate_field_names = [name for name, count in Counter(field_names).items() if count > 1]
67-
if duplicate_field_names:
68-
# TODO: handle duplicate field names for inheritance in future
69-
raise ValueError(f"Duplicate field names: {duplicate_field_names}")
7063
else:
7164
field_infos = []
7265

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# Licensed to the Apache Software Foundation (ASF) under one
2+
# or more contributor license agreements. See the NOTICE file
3+
# distributed with this work for additional information
4+
# regarding copyright ownership. The ASF licenses this file
5+
# to you under the Apache License, Version 2.0 (the
6+
# "License"); you may not use this file except in compliance
7+
# with the License. You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing,
12+
# software distributed under the License is distributed on an
13+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
# KIND, either express or implied. See the License for the
15+
# specific language governing permissions and limitations
16+
# under the License.
17+
18+
"""
19+
Tests for field shadowing in inheritance.
20+
"""
21+
22+
from dataclasses import dataclass
23+
from pyfory import Fory
24+
from pyfory.meta.typedef_encoder import encode_typedef
25+
26+
27+
@dataclass
28+
class Parent:
29+
name: str
30+
value: int
31+
32+
33+
@dataclass
34+
class ChildWithShadow(Parent):
35+
name: str # Shadows Parent.name
36+
extra: float
37+
38+
39+
def test_shadowed_fields_serialization():
40+
"""Serialization with shadowed and inherited fields."""
41+
fory = Fory(xlang=True)
42+
fory.register(Parent, namespace="test", typename="Parent")
43+
fory.register(ChildWithShadow, namespace="test", typename="ChildWithShadow")
44+
45+
# Verify TypeDef has exactly 3 fields (no duplicate 'name')
46+
typedef = encode_typedef(fory.type_resolver, ChildWithShadow)
47+
assert len(typedef.fields) == 3
48+
49+
obj = ChildWithShadow(name="shadowed", value=10, extra=3.14)
50+
data = fory.serialize(obj)
51+
result = fory.deserialize(data)
52+
53+
assert result.name == "shadowed"
54+
assert result.value == 10 # inherited field
55+
assert result.extra == 3.14

0 commit comments

Comments
 (0)