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

abstract attribut problem #406

Closed
untereiner opened this issue Feb 11, 2021 · 31 comments
Closed

abstract attribut problem #406

untereiner opened this issue Feb 11, 2021 · 31 comments
Labels
enhancement New feature or request

Comments

@untereiner
Copy link

Hi,

  • An abstract complexType <xs:complexType name="myType" abstract="true"> is not marked as abstract is the generation process. I do not have a minimial example here but I made print outs in the code and I saw that the mixins is called twice for the same complexType. The second time the abstract attribut is False. I know it is not very clear. I will try to clarify it.
  • Could an abstract complexType inherit from ABC or abc.collections ? I read here that it is possible.
@tefra
Copy link
Owner

tefra commented Feb 11, 2021

abstract ABC and dataclasses don't play nice together without adding extra code to ensure you can't instantiate an abstract class. do you have a specific use case that you would need this as a feature?

@untereiner
Copy link
Author

untereiner commented Feb 12, 2021

  • There is not need of extra code. Inheriting from ABC raises an error on instantiation. I have classes in my xsd files that are abstract. Other classes need to inherit from them but they should not be instantiated. I can also use them for type checking
from abc import ABC, abstractmethod
from typing import List
from dataclasses import dataclass, field

@dataclass
class Animal(ABC):
    @abstractmethod
    def move(self):
        pass
# a = Animal() 
# TypeError: Can't instantiate abstract class Animal with abstract methods move

@dataclass
class Cat(Animal):
    def move(self):
        super().move()
        print('Cat moves')

@dataclass
class Dog(Animal):
    def move(self):
        super().move()
        print('Dog moves')
    
@dataclass
class Animals():
    animals: List[Animal] = field(default_factory=list)

    def move(self):        
        for a in self.animals:
            a.move()

als = Animals()
als.animals.append(Dog())
als.animals.append(Cat())
als.move()
  • For the first problem I mentioned. When you print out mixins.py the is_abstract property you will see that is it false even if the xsd:complexType has attribute "abstract"

@tefra tefra added the enhancement New feature or request label Feb 12, 2021
@tefra
Copy link
Owner

tefra commented Feb 12, 2021

I am pretty sure I tried that in the very beginning and I had encountered an issue, anyway I 'll take another look.

@tefra
Copy link
Owner

tefra commented Feb 12, 2021

Yeah this one

In [8]: from abc import ABC, abstractmethod
   ...: from typing import List
   ...: from dataclasses import dataclass, field
   ...: 
   ...: @dataclass
   ...: class Animal(ABC):
   ...:     value: int
   ...: 
   ...: @dataclass
   ...: class Cat(Animal):
   ...:    pass
   ...: 
   ...: @dataclass
   ...: class Dog(Animal):
   ...:     pass
   ...: 

In [9]: 

In [9]: Animal(1)
Out[9]: Animal(value=1)  # this one shouldn't work

In [10]: Cat(1)
Out[10]: Cat(value=1)

@untereiner
Copy link
Author

ok! Very weird behavior. I haven't tested an abstract class without abstract methods.
People are solving this by adding an extra post_init function

@tefra
Copy link
Owner

tefra commented Feb 12, 2021

Yeah that's what I am trying to avoid, I don't want to complicate the models but I understand that it's necessary for devs to recognize what models they are not supposed to instantiate.

@untereiner
Copy link
Author

untereiner commented Feb 12, 2021

I have troubles with abstract classes but I can't share my xsd publicly.
My class should inherit from another but the generated python dataclass does not.
The list obj.extensions is empty and should not be.
I found that xs:element type attribut is not the same as the complexType name attribut. Exemple:

<xs:element name="Foo" type="test:Foo"/>
	<xs:complexType name="Foo" abstract="true">
        ....
              <xs:complexContent>
		  <xs:extension base="test:Bar">
       .......

This is an example very close to what I have and does not work as expected

<xsd:schema  xmlns:test="urn:MyNamespace" targetNamespace="urn:MyNamespace" xmlns:xsd="http://www.w3.org/2001/XMLSchema">

  <xsd:annotation>
    <xsd:documentation>
      This example illustrates complex types that are derived from other specified types.
    </xsd:documentation>
  </xsd:annotation>

  <xsd:element name="ItemsType" type="test:ItemsType"/>
  <xsd:complexType name="test:ItemsType">
    <xsd:choice minOccurs="0" maxOccurs="unbounded">
      <xsd:element name="hat" type="test:ProductType"/>
      <xsd:element name="umbrella" type="test:RestrictedProductType"/>
      <xsd:element name="shirt" type="test:ShirtType"/>
    </xsd:choice>
  </xsd:complexType>

  <!--Empty Content Type-->
  <xsd:element name="ItemType" type="test:ItemType"/>
  <xsd:complexType name="test:ItemType" abstract="true">
    <xsd:attribute name="routingNum" type="xsd:integer"/>
  </xsd:complexType>

  <!--Empty Content Extension (with Attribute Extension)-->
  <xsd:element name="ProductType" type="test:ProductType"/>
  <xsd:complexType name="test:ProductType">
    <xsd:complexContent>
      <xsd:extension base="test:ItemType">
        <xsd:sequence>
          <xsd:element name="number" type="xsd:integer"/>
          <xsd:element name="name" type="xsd:string"/>
          <xsd:element name="description" type="xsd:string" minOccurs="0"/>
        </xsd:sequence>
        <xsd:attribute name="effDate" type="xsd:date"/>
        <xsd:attribute name="lang" type="xsd:language"/>
      </xsd:extension>
    </xsd:complexContent>
  </xsd:complexType>

  <!--Complex Content Restriction-->
  <xsd:element name="RestrictedProductType" type="test:RestrictedProductType"/>
  <xsd:complexType name="test:RestrictedProductType">
    <xsd:complexContent>
      <xsd:restriction base="test:ProductType">
        <xsd:sequence>
          <xsd:element name="number" type="xsd:integer"/>
          <xsd:element name="name" type="xsd:token"/>
        </xsd:sequence>
        <xsd:attribute name="routingNum" type="xsd:short" use="required"/>
        <xsd:attribute name="effDate" type="xsd:date" default="1900-01-01"/>
        <xsd:attribute name="lang" use="prohibited"/>
      </xsd:restriction>
    </xsd:complexContent>
  </xsd:complexType>

  <!--Complex Content Extension-->
  <xsd:element name="ShirtType" type="test:ShirtType"/>
  <xsd:complexType name="test:ShirtType">
    <xsd:complexContent>
      <xsd:extension base="test:RestrictedProductType">
        <xsd:choice maxOccurs="unbounded">
          <xsd:element name="size" type="test:SmallSizeType"/>
          <xsd:element name="color" type="test:ColorType"/>
        </xsd:choice>
        <xsd:attribute name="sleeve" type="xsd:integer"/>
      </xsd:extension>
    </xsd:complexContent>
  </xsd:complexType>

  <!--Simple Content Extension-->
  <xsd:element name="SizeType" type="test:SizeType"/>
  <xsd:complexType name="test:SizeType">
    <xsd:simpleContent>
      <xsd:extension base="xsd:integer">
        <xsd:attribute name="system" type="xsd:token"/>
      </xsd:extension>
    </xsd:simpleContent>
  </xsd:complexType>

  <!--Simple Content Restriction-->
  <xsd:element name="SmallSizeType" type="test:SmallSizeType"/>
  <xsd:complexType name="test:SmallSizeType">
    <xsd:simpleContent>
      <xsd:restriction base="test:SizeType">
        <xsd:minInclusive value="2"/>
        <xsd:maxInclusive value="6"/>
        <xsd:attribute  name="system" type="xsd:token"
                        use="required"/>
      </xsd:restriction>
    </xsd:simpleContent>
  </xsd:complexType>

  <xsd:element name="ColorType" type="test:ColorType"/>
  <xsd:complexType name="test:ColorType">
    <xsd:attribute name="value" type="xsd:string"/>
  </xsd:complexType>

</xsd:schema>
``` 

My xsd files are exported from Enterprise Architect.
I tested to put the namespace "test:" in front of the complexType name attribute but it does not work

@tefra
Copy link
Owner

tefra commented Feb 12, 2021

Can you be a little more specific please, if you check the generated models with the latest version
https://xsdata.readthedocs.io/en/latest/defxmlschema/chapter13.html

You can see ProductType extending ItemType, ShirtType extending RestrictedProductType

It's not obvious @untereiner what the problem is. You don't have to share your whole xsd just the two basic types if you can the abstract complex type and the extending element. Change names and types if you want, but I need something more concrete to continue the investigation

@untereiner
Copy link
Author

Yeah, that's why I modified your example to copycat mine. I added the namespaces. If you test you will see it does not generate the inheritance

@tefra
Copy link
Owner

tefra commented Feb 12, 2021

xsdata is pretty forgiving with invalid schemas but it's not a validator, the schema itself fails to parse xmlschema and a couple validators I tried online.

@untereiner
Copy link
Author

I was to confident on the Enterprise Architect output. However this one is w3c compliant and the problem remains

<xsd:schema  xmlns:test="urn:MyNamespace" targetNamespace="urn:MyNamespace" xmlns:xsd="http://www.w3.org/2001/XMLSchema">

  <xsd:annotation>
    <xsd:documentation>
      This example illustrates complex types that are derived from other specified types.
    </xsd:documentation>
  </xsd:annotation>

  <xsd:element name="ItemsType" type="test:ItemsType"/>
  <xsd:complexType name="ItemsType">
    <xsd:choice minOccurs="0" maxOccurs="unbounded">
      <xsd:element name="hat" type="test:ProductType"/>
      <xsd:element name="umbrella" type="test:RestrictedProductType"/>
      <xsd:element name="shirt" type="test:ShirtType"/>
    </xsd:choice>
  </xsd:complexType>

  <!--Empty Content Type-->
  <xsd:element name="ItemType" type="test:ItemType"/>
  <xsd:complexType name="ItemType" abstract="true">
    <xsd:attribute name="routingNum" type="xsd:integer"/>
  </xsd:complexType>

  <!--Empty Content Extension (with Attribute Extension)-->
  <xsd:element name="ProductType" type="test:ProductType"/>
  <xsd:complexType name="ProductType">
    <xsd:complexContent>
      <xsd:extension base="test:ItemType">
        <xsd:sequence>
          <xsd:element name="number" type="xsd:integer"/>
          <xsd:element name="name" type="xsd:string"/>
          <xsd:element name="description" type="xsd:string" minOccurs="0"/>
        </xsd:sequence>
        <xsd:attribute name="effDate" type="xsd:date"/>
        <xsd:attribute name="lang" type="xsd:language"/>
      </xsd:extension>
    </xsd:complexContent>
  </xsd:complexType>

  <!--Complex Content Restriction-->
  <xsd:element name="RestrictedProductType" type="test:RestrictedProductType"/>
  <xsd:complexType name="RestrictedProductType">
    <xsd:complexContent>
      <xsd:restriction base="test:ProductType">
        <xsd:sequence>
          <xsd:element name="number" type="xsd:integer"/>
          <xsd:element name="name" type="xsd:token"/>
        </xsd:sequence>
        <xsd:attribute name="routingNum" type="xsd:short" use="required"/>
        <xsd:attribute name="effDate" type="xsd:date" default="1900-01-01"/>
        <xsd:attribute name="lang" use="prohibited"/>
      </xsd:restriction>
    </xsd:complexContent>
  </xsd:complexType>

  <!--Complex Content Extension-->
  <xsd:element name="ShirtType" type="test:ShirtType"/>
  <xsd:complexType name="ShirtType">
    <xsd:complexContent>
      <xsd:extension base="test:RestrictedProductType">
        <xsd:choice maxOccurs="unbounded">
          <xsd:element name="size" type="test:SmallSizeType"/>
          <xsd:element name="color" type="test:ColorType"/>
        </xsd:choice>
        <xsd:attribute name="sleeve" type="xsd:integer"/>
      </xsd:extension>
    </xsd:complexContent>
  </xsd:complexType>

  <!--Simple Content Extension-->
  <xsd:element name="SizeType" type="test:SizeType"/>
  <xsd:complexType name="SizeType">
    <xsd:simpleContent>
      <xsd:extension base="xsd:integer">
        <xsd:attribute name="system" type="xsd:token"/>
      </xsd:extension>
    </xsd:simpleContent>
  </xsd:complexType>

  <!--Simple Content Restriction-->
  <xsd:element name="SmallSizeType" type="test:SmallSizeType"/>
  <xsd:complexType name="SmallSizeType">
    <xsd:simpleContent>
      <xsd:restriction base="test:SizeType">
        <xsd:minInclusive value="2"/>
        <xsd:maxInclusive value="6"/>
        <xsd:attribute  name="system" type="xsd:token"
                        use="required"/>
      </xsd:restriction>
    </xsd:simpleContent>
  </xsd:complexType>

  <xsd:element name="ColorType" type="test:ColorType"/>
  <xsd:complexType name="ColorType">
    <xsd:attribute name="value" type="xsd:string"/>
  </xsd:complexType>

</xsd:schema>

@tefra
Copy link
Owner

tefra commented Feb 12, 2021

Oh now I see you added an element with a matching name to a complexType and that complex type is abstract.

Here xsdata handling has always been a little fuzzy, it decides to keep the element and flatten the complex type, although fuzzy its a sane way to proceed other processors like xjc for java fail for schemas like that.

How would you suggest to resolve this when an element and a complexType share the same name?

@tefra
Copy link
Owner

tefra commented Feb 14, 2021

I have an idea about this to not flatten base classes if there is a name conflict, it might take some time

@untereiner
Copy link
Author

untereiner commented Feb 15, 2021

I do not control the xsd generation. It is an output of Enterprise Architect.
JAXB is just ignoring these xs:element.

An hint maybe.
A second one.
The global xs:element is in fact a "XSDtopLevelElement" in EA.
Ignoring them when they have the same name as complexType could be a way to do

@tefra
Copy link
Owner

tefra commented Feb 15, 2021

Well ignoring them is too crude, effectively now xsdata ignores/flattens the complexType, also I tried xjc for me it's throwing errors.

I am thinking in these cases to mark as base class the complexType with a suffix or something and rename all type occurrences.

In our case it would create something like this

class ItemsTypeBase:
   ...

class ItemsType(ItemsTypeBase)
   ....

@untereiner
Copy link
Author

I was writing that because of this sentence: "On XSD import, by default, Enterprise Architect treats this global element and its bounding ComplexType as a single entity, and creates a single XSDcomplexType stereotyped Class with the same name as the global element, as shown:"

class ItemsTypeBase:
       hat
       ...

class ItemsType(ItemsTypeBase):
       pass

The child will be passed

@tefra
Copy link
Owner

tefra commented Feb 16, 2021

give it a try #411 @untereiner

It's still wip because it has some side effects I need to verify first but it will avoid flattening duplicate complex types.

@untereiner
Copy link
Author

@tefra I'll give it a try in the coming days. Thanks !

@untereiner
Copy link
Author

After a first test:

  • With this renaming scheme the hierarchy of types is weird and does not correspond to the one modeled with Enterprise Architect.
  • I have classes without properties that are not useful

@tefra
Copy link
Owner

tefra commented Feb 23, 2021

Well that's what happens when you have "dummy" elements like that what did you expect?

  <xsd:element name="ItemType" type="test:ItemType"/>
  <xsd:complexType name="ItemType" abstract="true">
    <xsd:attribute name="routingNum" type="xsd:integer"/>
  </xsd:complexType>

Please provide some samples and what you think would be the proper output, otherwise I don't know how to proceed.

@untereiner
Copy link
Author

untereiner commented Feb 23, 2021

Yes I try to provide what I am expecting (sort of).
I am realizing that I mixed multiple things in my issue: (abstract classes / namespaces / dummy elements)

@untereiner
Copy link
Author

untereiner commented Feb 23, 2021

  1. Abstract class:
  <xsd:complexType name="AbstractObject" abstract="true">
  <xs:sequence>
	<xs:element name="Aliases" type="String" minOccurs="0" maxOccurs="unbounded"/>
	<xs:element name="Citation" type="String" minOccurs="0" maxOccurs="1"/>
   </xs:sequence>
  </xs:complexType>

  <xsd:complexType name="FeatureDictionnary">
    <xs:complexContent>
	<xs:extension base="AbstractObject">
	    <xs:sequence>
		<xs:element name="Feature" type="Feature" minOccurs="1" maxOccurs="unbounded"/>
	    </xs:sequence>
	  </xs:extension>
	</xs:complexContent>
   </xs:complexType>

I expect to not be able to instantiate it. It is modeled in UML that way

class AbstractObject():
    aliases: List[str] = field(default_factory=list, metadata...)       
    citation: Optional[str] = field(default=None, metadata=.....)

class FeatureDictionnary(AbstractObject):
    feature: List[Feature] = field( default_factory=list, meta.....)

It should not be possible to instantiate and AbstractObject()

@untereiner
Copy link
Author

  1. Dummy elements created by EA
  <xsd:element name="ItemType" type="ItemType"/>
  <xsd:complexType name="ItemType">
    <xsd:attribute name="routingNum" type="xsd:integer"/>
  </xsd:complexType>

should give (simplified)

class ItemType():
   routingNum: int

@tefra
Copy link
Owner

tefra commented Feb 23, 2021

It does that already v20.2 (and latest master), I hope you have a number 2 use case

@dataclass
class ItemType:
    routing_num: Optional[int] = field(
        default=None,
        metadata={
            "name": "routingNum",
            "type": "Attribute",
            "required": True,
        }
    )

@untereiner
Copy link
Author

untereiner commented Feb 23, 2021

yes the tiny example works so I do not understand why I don't get a proper type hierarchy with my xsd files

@tefra
Copy link
Owner

tefra commented Feb 23, 2021

Can you create an excerpt of the two types that have the issue?

There are a couple of cases because of limitation either in dataclasses or python in general that base classes have to be flattened but without any sample I can't say for sure that's your case or if there is an actual bug. If you trust me I won't share anything shared privately contact me at chris[at]komposta.net

@tefra
Copy link
Owner

tefra commented Feb 23, 2021

Thanks for sending me the actual xsd files

So let's take this schema that demonstrates your case

<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">
  <xs:element name="Root" type="Root" />
  <xs:complexType name="Root" abstract="true">
    <xs:complexContent>
      <xs:extension base="AbstractRoot">
        <xs:sequence>
          <xs:element name="a" type="xs:string" minOccurs="0" maxOccurs="1" />
          <xs:element name="b" type="xs:string" minOccurs="0" maxOccurs="1" />
        </xs:sequence>
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>
  <xs:element name="AbstractRoot" type="AbstractRoot" />
  <xs:complexType name="AbstractRoot" abstract="true">
    <xs:sequence>
      <xs:element name="c" type="xs:string" minOccurs="0" maxOccurs="unbounded" />
      <xs:element name="d" type="xs:string" minOccurs="0" maxOccurs="unbounded" />
    </xs:sequence>
  </xs:complexType>
</xs:schema>

This is the output with the latest master

@dataclass
class AbstractRoot:
    c: List[str] = field(
        default_factory=list,
        metadata={
            "type": "Element",
        }
    )
    d: List[str] = field(
        default_factory=list,
        metadata={
            "type": "Element",
        }
    )


@dataclass
class Root:
    c: List[str] = field(
        default_factory=list,
        metadata={
            "type": "Element",
        }
    )
    d: List[str] = field(
        default_factory=list,
        metadata={
            "type": "Element",
        }
    )
    a: Optional[str] = field(
        default=None,
        metadata={
            "type": "Element",
        }
    )
    b: Optional[str] = field(
        default=None,
        metadata={
            "type": "Element",
        }
    )

The generator finds one element and one complex type with the same name, it decides to flatten the complex type and keep the element. Flatten means all it's properties recursively are copied to the element(s). That's where I agree the generator is a bit clunky here.

This is the output from the experimental solution

from dataclasses import dataclass, field
from typing import List, Optional


@dataclass
class AbstractRoot1:
    class Meta:
        name = "AbstractRoot"

    c: List[str] = field(
        default_factory=list,
        metadata={
            "type": "Element",
        }
    )
    d: List[str] = field(
        default_factory=list,
        metadata={
            "type": "Element",
        }
    )


@dataclass
class AbstractRoot(AbstractRoot1):
    pass


@dataclass
class Root1(AbstractRoot1):
    class Meta:
        name = "Root"

    a: Optional[str] = field(
        default=None,
        metadata={
            "type": "Element",
        }
    )
    b: Optional[str] = field(
        default=None,
        metadata={
            "type": "Element",
        }
    )


@dataclass
class Root(Root1):
    pass

The experimental solution will force the complex types with the same names to be renamed but generates all models with the correct hierarchy without flattening any properties or base classes. Which is 100% accurate representation of the xsd types.

Currently the renaming module simply auto appends the next available auto increment. If I understand correctly you don't like the generation of the bare classes class Root(Root1) and class AbstractRoot(AbstractRoot1) right that don't have any properties at all because of the dummy elements like

  <xs:element name="Root" type="Root" />
  <xs:complexType name="Root" abstract="true">

If that's the case I am thinking to add another layer for the duplicate class name handler, to skip those from generating, let me know if that's really the issue here.

@untereiner
Copy link
Author

untereiner commented Feb 24, 2021

Thank you @tefra. You pointed out exactly the issue here.
When you say " it decides to flatten the complex type and keep the element." Does it mean that it copies the elements of the xs:complexType to the same named xs:element ?

Why do not flatten (copying) the <xs:extension base="AbstractRoot">. Would it not result to something like that ?

from dataclasses import dataclass, field
from typing import List, Optional


@dataclass
class AbstractRoot:
    class Meta:
        name = "AbstractRoot"

    c: List[str] = field(
        default_factory=list,
        metadata={
            "type": "Element",
        }
    )
    d: List[str] = field(
        default_factory=list,
        metadata={
            "type": "Element",
        }
    )

@dataclass
class Root(AbstractRoot):
    class Meta:
        name = "Root"

    a: Optional[str] = field(
        default=None,
        metadata={
            "type": "Element",
        }
    )
    b: Optional[str] = field(
        default=None,
        metadata={
            "type": "Element",
        }
    )

@tefra
Copy link
Owner

tefra commented Feb 24, 2021

We can't have to classes with the same name right? So until now the generator has to chose which type to generate, the properties of the type the types that are not generated have to copied to the subclasses. That's how xsdata works in general if you notice only root complex types and elements are converted to classes, all root simple types, attributes, attribute groups etc etc are flattened.

Yeah it's not that simple, the process is linear, the generator converts all types to a basic form of class, then based on rules these classes are reduced to avoid duplication and as much noise as possible.

So if we have this structure A(B), B(C), C it doesn't just jump from C -> A, since it has decided to flatten class B all its properties including the ones from C are copied to class A.

Anyway don't worry about that, if we sort out the strategy when there is an xs:element and xs:complexType everything will fall into place.

@untereiner
Copy link
Author

ok!

@tefra tefra closed this as completed in 34c9aa2 Feb 25, 2021
@tefra
Copy link
Owner

tefra commented Feb 25, 2021

Thank you for reporting @untereiner the change is on master and will be included in the next release.

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

No branches or pull requests

2 participants