Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

XML Schema restrictions are not reflected within __dataclass_fields__ due to inheritance #854

Closed
skinkie opened this issue Oct 19, 2023 · 9 comments · Fixed by #908
Closed

Comments

@skinkie
Copy link
Contributor

skinkie commented Oct 19, 2023

I have yesterday encountered a situation where there was a presumable recursive definition of an object. NeTEx-CEN/NeTEx#526 I have learned this definitionis actually prevented by an XML Schema restriction, which implicitly removes the fields that are not in use. xsData seems to do the right job, but for anyone that is using inspection on the dataclasses is faced with inheritance from the subsitutionGroup.

        <xsd:element name="AlternativeText" substitutionGroup="VersionedChild">
                <xsd:annotation>
                        <xsd:documentation>Alternative Text. +v1.1</xsd:documentation>
                </xsd:annotation>
                <xsd:complexType>
                        <xsd:complexContent>
                                <xsd:restriction base="AlternativeText_VersionedChildStructure">
                                        <xsd:sequence>
                                                <xsd:sequence>
                                                        <xsd:element name="DataManagedObjectRef" type="VersionOfObjectRefStructure" minOccurs="0">
                                                                <xsd:annotation>
                                                                        <xsd:documentation>Object for  attribute for which ALTERNATIVE TEXTprovides an alias. May be omitted if given by context.</xsd:documentation>
                                                                </xsd:annotation>
                                                        </xsd:element>
                                                        <xsd:element name="Text" type="MultilingualString">
                                                                <xsd:annotation>
                                                                        <xsd:documentation>Name of the entity.</xsd:documentation>
                                                                </xsd:annotation>
                                                        </xsd:element>
                                                </xsd:sequence>
                                        </xsd:sequence>
                                        <xsd:attribute name="id" type="AlternativeTextIdType" use="optional">
                                                <xsd:annotation>
                                                        <xsd:documentation>Identifier of ENTITY.</xsd:documentation>
                                                </xsd:annotation>
                                        </xsd:attribute>
                                        <xsd:attribute name="attributeName" type="xsd:NCName">
                                                <xsd:annotation>
                                                        <xsd:documentation>Name of text attribute for which this is the  alternative tex. Must be an existing attribute name </xsd:documentation>
                                                </xsd:annotation>
                                        </xsd:attribute>
                                </xsd:restriction>
                        </xsd:complexContent>
                </xsd:complexType>
        </xsd:element>

Is transformed into:

@dataclass(unsafe_hash=True, kw_only=True)
class AlternativeTextVersionedChildStructure(VersionedChildStructure):
    """
    Type for ALTERNATIVE TEXT.

    :ivar data_managed_object_ref: Object for  attribute for which
        ALTERNATIVE TEXTprovides an alias. May be omitted if given by
        context.
    :ivar text: Name of the entity.
    :ivar attribute_name: Name of text attribute for which this is the
        alternative tex. Must be an existing attribute name
    :ivar use_for_language: Name of language for which this is to be
        used.
    :ivar order: Order of name.
    """ 
    class Meta:
        name = "AlternativeText_VersionedChildStructure"
        
    data_managed_object_ref: Optional[VersionOfObjectRefStructure] = field(
        default=None,
        metadata={
            "name": "DataManagedObjectRef",
            "type": "Element",
            "namespace": "http://www.netex.org.uk/netex",
        }
    )       
    text: MultilingualString = field(
        metadata={
            "name": "Text",
            "type": "Element",
            "namespace": "http://www.netex.org.uk/netex",
            "required": True,
        }
    )
    attribute_name: Optional[str] = field(
        default=None,
        metadata={
            "name": "attributeName",
            "type": "Attribute",
        }
    )
    use_for_language: Optional[str] = field(
        default=None,
        metadata={
            "name": "useForLanguage",
            "type": "Attribute",
        }
    )
    order: Optional[int] = field(
        default=None,
        metadata={
            "type": "Attribute",
        }
    )

But the effect when looking at AlternativeText.dataclass_fields still include the fields outside the restriction inhereted via VersionedChildStructure:
image

I have been naively started to experiment if there was a clean way to clear the upstream inheritance, which was surprisingly simple. By just setting the already created field to None, dataclass_fields would ignore it.

@dataclass(unsafe_hash=True, kw_only=True)
class AlternativeTextVersionedChildStructure(VersionedChildStructure):
    """
    Type for ALTERNATIVE TEXT.

    :ivar data_managed_object_ref: Object for  attribute for which
        ALTERNATIVE TEXTprovides an alias. May be omitted if given by
        context.
    :ivar text: Name of the entity.
    :ivar attribute_name: Name of text attribute for which this is the
        alternative tex. Must be an existing attribute name
    :ivar use_for_language: Name of language for which this is to be
        used.
    :ivar order: Order of name.
    """
    class Meta:
        name = "AlternativeText_VersionedChildStructure"

    alternative_texts: None

    validity_conditions_or_valid_between: None

    extensions: None

    data_managed_object_ref: Optional[VersionOfObjectRefStructure] = field(
        default=None,
        metadata={
            "name": "DataManagedObjectRef",
            "type": "Element",
            "namespace": "http://www.netex.org.uk/netex",
        }
    )
    text: MultilingualString = field(
        metadata={
            "name": "Text",
            "type": "Element",
            "namespace": "http://www.netex.org.uk/netex",
            "required": True,
        }
    )
    attribute_name: Optional[str] = field(
        default=None,
        metadata={
            "name": "attributeName",
            "type": "Attribute",
        }
    )
    use_for_language: Optional[str] = field(
        default=None,
        metadata={
            "name": "useForLanguage",
            "type": "Attribute",
        }
    )
    order: Optional[int] = field(
        default=None,
        metadata={
            "type": "Attribute",
        }
    )

image

skinkie added a commit to skinkie/xsdata that referenced this issue Oct 19, 2023
When a restriction is set, the ideal situation for the datamodel would be that only the properties that fall under the restriction are available.

From https://www.reddit.com/r/learnpython/comments/12qhzi6/dataclasses_with_inheritance/ I found that kw_only was used to resolve the issue with non-default argument 'class name' follows default argument.
@skinkie
Copy link
Contributor Author

skinkie commented Oct 24, 2023

Important comment:
NeTEx-CEN/NeTEx#531 (comment)

@tefra
Copy link
Owner

tefra commented Nov 4, 2023

Hey @skinkie can you please provide a simple example of the behavior that needs to change? I am sorry netex is too complicated to follow.

@skinkie
Copy link
Contributor Author

skinkie commented Nov 4, 2023

I think I already have an example schema somewhere. I'll try to post it tonight.

@skinkie
Copy link
Contributor Author

skinkie commented Nov 4, 2023

Needed to be a bit more creative. As you can see from the (valid) XML example test3 is not allowed in the output. But the attribute version is correctly inherited. This has been described by O'Reilly. xsdata (current version) incorrently inherits test3, while it is not part of the restriction, thus should not be part of the final object.

ssp.xml.txt
ssp.xsd.txt

@tefra
Copy link
Owner

tefra commented Nov 5, 2023

Our options here are

  1. Flatten the extension (let's not do that)
  2. Use ClassVar to override parent definition
from typing import ClassVar as RestrictedVar

@dataclass
class Parent:
    one: int
    two: int

@dataclass
class Child(Parent):
    two: RestrictedVar
    three: int

@skinkie
Copy link
Contributor Author

skinkie commented Nov 5, 2023

RestrictedVar looks nice :-) Should I reuse is_prohibited, instead of my introduction of is_null?

@tefra
Copy link
Owner

tefra commented Nov 5, 2023

the is_prohibited has a different application let's not mix those two

@tefra
Copy link
Owner

tefra commented Dec 29, 2023

Hrm my suggestion doesn't work either mypy complaints with

error: Cannot override instance variable (previously declared on base class "Parent") with class variable

@tefra
Copy link
Owner

tefra commented Dec 29, 2023

Our only options is to override the new field with init=Faslse

Screenshot 2023-12-29 220527

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants