|
34 | 34 | ) |
35 | 35 |
|
36 | 36 | from . import _logger, jsonld_context, ref_resolver, validate |
37 | | -from .avro.schema import Names, SchemaParseException, make_avsc_object |
| 37 | +from .avro.schema import Names, SchemaParseException, make_avsc_object, is_subtype |
38 | 38 | from .exceptions import ( |
39 | 39 | ClassValidationException, |
40 | 40 | SchemaSaladException, |
@@ -617,7 +617,7 @@ def extend_and_specialize( |
617 | 617 | for spec in aslist(stype["specialize"]): |
618 | 618 | specs[spec["specializeFrom"]] = spec["specializeTo"] |
619 | 619 |
|
620 | | - exfields = [] # type: List[str] |
| 620 | + exfields = [] # type: List[Any] |
621 | 621 | exsym = [] # type: List[str] |
622 | 622 | for ex in aslist(stype["extends"]): |
623 | 623 | if ex not in types: |
@@ -645,8 +645,46 @@ def extend_and_specialize( |
645 | 645 |
|
646 | 646 | if stype["type"] == "record": |
647 | 647 | stype = copy.copy(stype) |
648 | | - exfields.extend(stype.get("fields", [])) |
649 | | - stype["fields"] = exfields |
| 648 | + combined_fields = [] |
| 649 | + fields = stype.get("fields", []) |
| 650 | + # We use short names here so that if a type inherits a field |
| 651 | + # (e.g. Child#id) from a parent (Parent#id) we avoid adding |
| 652 | + # the same field twice (previously we had just |
| 653 | + # ``exfields.extends(stype.fields)``). |
| 654 | + sns_fields = {shortname(field["name"]): field for field in fields} |
| 655 | + sns_exfields = { |
| 656 | + shortname(exfield["name"]): exfield for exfield in exfields |
| 657 | + } |
| 658 | + |
| 659 | + # N.B.: This could be simpler. We could have a single loop |
| 660 | + # to create the list of fields. The reason for this more |
| 661 | + # convoluted solution is to make sure we keep the order |
| 662 | + # of ``exfields`` first, and then the type fields. That's |
| 663 | + # because we have unit tests that rely on the order that |
| 664 | + # fields are written. Codegen output changes as well. |
| 665 | + # We are relying on the insertion order preserving |
| 666 | + # property of python dicts (i.e. relyig on Py3.5+). |
| 667 | + |
| 668 | + # First pass adding the exfields. |
| 669 | + for sn_exfield, exfield in sns_exfields.items(): |
| 670 | + field = sns_fields.get(sn_exfield, None) |
| 671 | + if field is None: |
| 672 | + field = exfield |
| 673 | + else: |
| 674 | + # make sure field name has not been used yet |
| 675 | + if not is_subtype(exfield["type"], field["type"]): |
| 676 | + raise SchemaParseException( |
| 677 | + f"Field name {field['name']} already in use with " |
| 678 | + "incompatible type. " |
| 679 | + f"{field['type']} vs {exfield['type']}." |
| 680 | + ) |
| 681 | + combined_fields.append(field) |
| 682 | + # Second pass, now add the ones that are specific to the subtype. |
| 683 | + for field in sns_fields.values(): |
| 684 | + if field not in combined_fields: |
| 685 | + combined_fields.append(field) |
| 686 | + |
| 687 | + stype["fields"] = combined_fields |
650 | 688 |
|
651 | 689 | fieldnames = set() # type: Set[str] |
652 | 690 | for field in stype["fields"]: |
|
0 commit comments