Repository: tefra/xsdata
Pull request #458 (2021-04-13 20:08:35)

ClassSanitizer: use unique ids to rename references
The sanitizer has to rename duplicate class names  and their references. This could lead in renaming incorrectly references just because they share the same qualified name.


```xsd
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">
  <xsd:element name="Duplicate" type="foobar" />
  <xsd:complexType name="Duplicate">
    <xsd:sequence>
      <xsd:element ref="Duplicate" minOccurs="0" maxOccurs="unbounded" />
    </xsd:sequence>
  </xsd:complexType>
  <xsd:complexType name="foobar">
    <xsd:sequence>
      <xsd:element name="value" type="xsd:string" minOccurs="0" maxOccurs="1" />
    </xsd:sequence>
  </xsd:complexType>
</xsd:schema>
```

wrong output, here the sanitizer renames the duplicate field type by mistake because of the same qname.
 
```python
@dataclass
class Duplicate1:
    class Meta:
        name = "Duplicate"

    duplicate: List[Duplicate1] = field(
        default_factory=list,
        metadata={
            "name": "Duplicate",
            "type": "Element",
        }
    )
```


correct output, after using the id to find and locate the correct reference to rename

```python
@dataclass
class Duplicate1:
    class Meta:
        name = "Duplicate"

    duplicate: List[Duplicate] = field(
        default_factory=list,
        metadata={
            "name": "Duplicate",
            "type": "Element",
        }
    )
```





sonarcloud[bot]: Kudos, SonarCloud Quality Gate passed!

[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug.png' alt='Bug' width='16' height='16' />](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=458&resolved=false&types=BUG) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=458&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=458&resolved=false&types=BUG)  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability.png' alt='Vulnerability' width='16' height='16' />](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=458&resolved=false&types=VULNERABILITY) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=458&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=458&resolved=false&types=VULNERABILITY)  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot.png' alt='Security Hotspot' width='16' height='16' />](https://sonarcloud.io/project/security_hotspots?id=tefra_xsdata&pullRequest=458&resolved=false&types=SECURITY_HOTSPOT) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/security_hotspots?id=tefra_xsdata&pullRequest=458&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=tefra_xsdata&pullRequest=458&resolved=false&types=SECURITY_HOTSPOT)  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell.png' alt='Code Smell' width='16' height='16' />](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=458&resolved=false&types=CODE_SMELL) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=458&resolved=false&types=CODE_SMELL) [0 Code Smells](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=458&resolved=false&types=CODE_SMELL)

[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/NoCoverageInfo.png' alt='No Coverage information' width='16' height='16' />](https://sonarcloud.io/component_measures?id=tefra_xsdata&pullRequest=458) No Coverage information  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/3.png' alt='0.0%' width='16' height='16' />](https://sonarcloud.io/component_measures?id=tefra_xsdata&pullRequest=458&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=tefra_xsdata&pullRequest=458&metric=new_duplicated_lines_density&view=list)


codecov[bot]: # [Codecov](https://codecov.io/gh/tefra/xsdata/pull/458?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+T) Report
> Merging [#458](https://codecov.io/gh/tefra/xsdata/pull/458?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+T) (e7035b7) into [master](https://codecov.io/gh/tefra/xsdata/commit/c1c77b1c20745cf8312a44fd486bb4f836ce3ec6?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+T) (c1c77b1) will **not change** coverage.
> The diff coverage is `100.00%`.

[![Impacted file tree graph](https://codecov.io/gh/tefra/xsdata/pull/458/graphs/tree.svg?width=650&height=150&src=pr&token=YzDDLtywvl&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+T)](https://codecov.io/gh/tefra/xsdata/pull/458?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+T)

```diff
@@            Coverage Diff            @@
##            master      #458   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files           75        75           
  Lines         6968      6974    +6     
  Branches      1234      1234           
=========================================
+ Hits          6968      6974    +6     
```


| [Impacted Files](https://codecov.io/gh/tefra/xsdata/pull/458?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+T) | Coverage Δ | |
|---|---|---|
| [xsdata/utils/testing.py](https://codecov.io/gh/tefra/xsdata/pull/458/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+T#diff-eHNkYXRhL3V0aWxzL3Rlc3RpbmcucHk=) | `100.00% <ø> (ø)` | |
| [xsdata/codegen/handlers/attribute\_type.py](https://codecov.io/gh/tefra/xsdata/pull/458/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+T#diff-eHNkYXRhL2NvZGVnZW4vaGFuZGxlcnMvYXR0cmlidXRlX3R5cGUucHk=) | `100.00% <100.00%> (ø)` | |
| [xsdata/codegen/handlers/class\_extension.py](https://codecov.io/gh/tefra/xsdata/pull/458/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+T#diff-eHNkYXRhL2NvZGVnZW4vaGFuZGxlcnMvY2xhc3NfZXh0ZW5zaW9uLnB5) | `100.00% <100.00%> (ø)` | |
| [xsdata/codegen/models.py](https://codecov.io/gh/tefra/xsdata/pull/458/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+T#diff-eHNkYXRhL2NvZGVnZW4vbW9kZWxzLnB5) | `100.00% <100.00%> (ø)` | |
| [xsdata/codegen/sanitizer.py](https://codecov.io/gh/tefra/xsdata/pull/458/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+T#diff-eHNkYXRhL2NvZGVnZW4vc2FuaXRpemVyLnB5) | `100.00% <100.00%> (ø)` | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/tefra/xsdata/pull/458?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+T).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+T)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/tefra/xsdata/pull/458?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+T). Last update [c1c77b1...e7035b7](https://codecov.io/gh/tefra/xsdata/pull/458?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+T). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+T).

sonarcloud[bot]: Kudos, SonarCloud Quality Gate passed!

[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug.png' alt='Bug' width='16' height='16' />](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=458&resolved=false&types=BUG) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=458&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=458&resolved=false&types=BUG)  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability.png' alt='Vulnerability' width='16' height='16' />](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=458&resolved=false&types=VULNERABILITY) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=458&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=458&resolved=false&types=VULNERABILITY)  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot.png' alt='Security Hotspot' width='16' height='16' />](https://sonarcloud.io/project/security_hotspots?id=tefra_xsdata&pullRequest=458&resolved=false&types=SECURITY_HOTSPOT) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/security_hotspots?id=tefra_xsdata&pullRequest=458&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=tefra_xsdata&pullRequest=458&resolved=false&types=SECURITY_HOTSPOT)  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell.png' alt='Code Smell' width='16' height='16' />](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=458&resolved=false&types=CODE_SMELL) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=458&resolved=false&types=CODE_SMELL) [0 Code Smells](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=458&resolved=false&types=CODE_SMELL)

[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/NoCoverageInfo.png' alt='No Coverage information' width='16' height='16' />](https://sonarcloud.io/component_measures?id=tefra_xsdata&pullRequest=458) No Coverage information  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/3.png' alt='0.0%' width='16' height='16' />](https://sonarcloud.io/component_measures?id=tefra_xsdata&pullRequest=458&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=tefra_xsdata&pullRequest=458&metric=new_duplicated_lines_density&view=list)



Patch:
diff --git a/xsdata/codegen/handlers/attribute_type.py b/xsdata/codegen/handlers/attribute_type.py
index f9a3535c..949c7d1b 100644
--- a/xsdata/codegen/handlers/attribute_type.py
+++ b/xsdata/codegen/handlers/attribute_type.py
@@ -125,6 +125,7 @@ class AttributeTypeHandler(HandlerInterface):
             attr.restrictions.format = collections.first(
                 x.restrictions.format for x in source.attrs if x.restrictions.format
             )
+            attr_type.reference = id(source)
         else:
             self.set_circular_flag(source, target, attr_type)
 
@@ -161,6 +162,7 @@ class AttributeTypeHandler(HandlerInterface):
 
     def set_circular_flag(self, source: Class, target: Class, attr_type: AttrType):
         """Update circular reference flag."""
+        attr_type.reference = id(source)
         attr_type.circular = self.is_circular_dependency(source, target, set())
 
     def is_circular_dependency(self, source: Class, target: Class, seen: Set) -> bool:
diff --git a/xsdata/codegen/handlers/class_extension.py b/xsdata/codegen/handlers/class_extension.py
index aaf228d4..2284be8a 100644
--- a/xsdata/codegen/handlers/class_extension.py
+++ b/xsdata/codegen/handlers/class_extension.py
@@ -137,6 +137,7 @@ class ClassExtensionHandler(HandlerInterface):
         elif cls.should_flatten_extension(source, target):
             ClassUtils.copy_attributes(source, target, ext)
         else:
+            ext.type.reference = id(source)
             logger.debug("Ignore extension: %s", ext.type.name)
 
     def find_dependency(self, attr_type: AttrType) -> Optional[Class]:
diff --git a/xsdata/codegen/models.py b/xsdata/codegen/models.py
index 94e60c89..6bc207f5 100644
--- a/xsdata/codegen/models.py
+++ b/xsdata/codegen/models.py
@@ -196,6 +196,7 @@ class AttrType:
 
     :param qname:
     :param alias:
+    :param reference:
     :param native:
     :param forward:
     :param circular:
@@ -203,6 +204,7 @@ class AttrType:
 
     qname: str
     alias: Optional[str] = field(default=None, compare=False)
+    reference: int = field(default=0, compare=False)
     native: bool = field(default=False)
     forward: bool = field(default=False)
     circular: bool = field(default=False)
diff --git a/xsdata/codegen/sanitizer.py b/xsdata/codegen/sanitizer.py
index 1ddda7c9..d5a6cd0b 100644
--- a/xsdata/codegen/sanitizer.py
+++ b/xsdata/codegen/sanitizer.py
@@ -11,6 +11,7 @@ from xsdata.logger import logger
 from xsdata.models.enums import DataType
 from xsdata.models.enums import Tag
 from xsdata.utils import collections
+from xsdata.utils import text
 from xsdata.utils.collections import first
 from xsdata.utils.collections import group_by
 from xsdata.utils.namespaces import build_qname
@@ -189,7 +190,7 @@ class ClassSanitizer:
         self.container.reset(target, qname)
 
         for item in self.container.iterate():
-            self.rename_class_dependencies(item, qname, target.qname)
+            self.rename_class_dependencies(item, id(target), target.qname)
 
     def next_qname(self, namespace: str, name: str) -> str:
         """Append the next available index number for the given namespace and
@@ -202,32 +203,33 @@ class ClassSanitizer:
             if alnum(qname) not in reserved:
                 return qname
 
-    def rename_class_dependencies(self, target: Class, search: str, replace: str):
+    def rename_class_dependencies(self, target: Class, reference: int, replace: str):
         """Search and replace the old qualified attribute type name with the
         new one if it exists in the target class attributes, extensions and
         inner classes."""
         for attr in target.attrs:
-            self.rename_attr_dependencies(attr, search, replace)
+            self.rename_attr_dependencies(attr, reference, replace)
 
         for ext in target.extensions:
-            if ext.type.qname == search:
+            if ext.type.reference == reference:
                 ext.type.qname = replace
 
         for inner in target.inner:
-            self.rename_class_dependencies(inner, search, replace)
+            self.rename_class_dependencies(inner, reference, replace)
 
-    def rename_attr_dependencies(self, attr: Attr, search: str, replace: str):
+    def rename_attr_dependencies(self, attr: Attr, reference: int, replace: str):
         """Search and replace the old qualified attribute type name with the
         new one in the attr types, choices and default value."""
         for attr_type in attr.types:
-            if attr_type.qname == search:
+            if attr_type.reference == reference:
                 attr_type.qname = replace
 
                 if isinstance(attr.default, str) and attr.default.startswith("@enum@"):
-                    attr.default = attr.default.replace(search, replace)
+                    member = text.suffix(attr.default, "::")
+                    attr.default = f"@enum@{replace}::{member}"
 
         for choice in attr.choices:
-            self.rename_attr_dependencies(choice, search, replace)
+            self.rename_attr_dependencies(choice, reference, replace)
 
     def find_enum(self, attr_type: AttrType) -> Optional[Class]:
         """Find an enumeration class byte the attribute type."""


Test patch:
diff --git a/tests/codegen/handlers/test_attribute_type.py b/tests/codegen/handlers/test_attribute_type.py
index d706f8b9..cdac8e89 100644
--- a/tests/codegen/handlers/test_attribute_type.py
+++ b/tests/codegen/handlers/test_attribute_type.py
@@ -270,6 +270,7 @@ class AttributeTypeHandlerTests(FactoryTestCase):
 
         self.processor.set_circular_flag(source, target, attr_type)
         self.assertTrue(attr_type.circular)
+        self.assertEqual(id(source), attr_type.reference)
 
         mock_is_circular_dependency.assert_called_once_with(source, target, set())
 
diff --git a/tests/codegen/handlers/test_class_extension.py b/tests/codegen/handlers/test_class_extension.py
index d74691cb..21c19a3b 100644
--- a/tests/codegen/handlers/test_class_extension.py
+++ b/tests/codegen/handlers/test_class_extension.py
@@ -293,6 +293,7 @@ class ClassExtensionHandlerTests(FactoryTestCase):
 
         mock_should_remove_extension.assert_called_once_with(source, target)
         self.assertEqual(0, mock_copy_attributes.call_count)
+        self.assertEqual(0, extension.type.reference)
 
     @mock.patch.object(ClassUtils, "copy_attributes")
     @mock.patch.object(ClassExtensionHandler, "should_flatten_extension")
@@ -305,6 +306,7 @@ class ClassExtensionHandlerTests(FactoryTestCase):
         source = ClassFactory.create()
 
         self.processor.process_complex_extension(source, target, extension)
+        self.assertEqual(0, extension.type.reference)
         mock_compare_attributes.assert_called_once_with(source, target)
         mock_should_flatten_extension.assert_called_once_with(source, target, extension)
 
@@ -321,6 +323,7 @@ class ClassExtensionHandlerTests(FactoryTestCase):
 
         self.processor.process_complex_extension(source, target, extension)
         self.assertEqual(1, len(target.extensions))
+        self.assertEqual(id(source), extension.type.reference)
 
     def test_find_dependency(self):
         attr_type = AttrTypeFactory.create(qname="a")
diff --git a/tests/codegen/test_sanitizer.py b/tests/codegen/test_sanitizer.py
index bfbb0d3e..382827cb 100644
--- a/tests/codegen/test_sanitizer.py
+++ b/tests/codegen/test_sanitizer.py
@@ -396,7 +396,7 @@ class ClassSanitizerTest(FactoryTestCase):
         self.assertEqual("_a", target.meta_name)
 
         mock_rename_class_dependencies.assert_has_calls(
-            mock.call(item, "{foo}_a", "{foo}_a_3")
+            mock.call(item, id(target), "{foo}_a_3")
             for item in self.sanitizer.container.iterate()
         )
 
@@ -404,7 +404,7 @@ class ClassSanitizerTest(FactoryTestCase):
         self.assertEqual([], self.container.data["{foo}_a"])
 
     def test_rename_class_dependencies(self):
-        attr_type = AttrTypeFactory.create("{foo}bar")
+        attr_type = AttrTypeFactory.create(qname="{foo}bar", reference=1)
 
         target = ClassFactory.create(
             extensions=[
@@ -428,29 +428,30 @@ class ClassSanitizerTest(FactoryTestCase):
             ],
         )
 
-        self.sanitizer.rename_class_dependencies(target, "{foo}bar", "thug")
+        self.sanitizer.rename_class_dependencies(target, 1, "thug")
         dependencies = set(target.dependencies())
         self.assertNotIn("{foo}bar", dependencies)
         self.assertIn("thug", dependencies)
 
     def test_rename_attr_dependencies_with_default_enum(self):
-        attr_type = AttrTypeFactory.create("{foo}bar")
+        attr_type = AttrTypeFactory.create(qname="{foo}bar", reference=1)
         target = ClassFactory.create(
             attrs=[
                 AttrFactory.create(
-                    types=[attr_type], default=f"@enum@{attr_type.qname}::member"
+                    types=[attr_type],
+                    default=f"@enum@{attr_type.qname}::member",
                 ),
             ]
         )
 
-        self.sanitizer.rename_class_dependencies(target, "{foo}bar", "thug")
+        self.sanitizer.rename_class_dependencies(target, 1, "thug")
         dependencies = set(target.dependencies())
         self.assertEqual("@enum@thug::member", target.attrs[0].default)
         self.assertNotIn("{foo}bar", dependencies)
         self.assertIn("thug", dependencies)
 
     def test_rename_attr_dependencies_with_choices(self):
-        attr_type = AttrTypeFactory.create("{foo}bar")
+        attr_type = AttrTypeFactory.create(qname="foo", reference=1)
         target = ClassFactory.create(
             attrs=[
                 AttrFactory.create(
@@ -461,10 +462,10 @@ class ClassSanitizerTest(FactoryTestCase):
             ]
         )
 
-        self.sanitizer.rename_class_dependencies(target, "{foo}bar", "thug")
+        self.sanitizer.rename_class_dependencies(target, 1, "bar")
         dependencies = set(target.dependencies())
-        self.assertNotIn("{foo}bar", dependencies)
-        self.assertIn("thug", dependencies)
+        self.assertNotIn("foo", dependencies)
+        self.assertIn("bar", dependencies)
 
     @mock.patch.object(ClassSanitizer, "group_fields")
     def test_group_compound_fields(self, mock_group_fields):
diff --git a/xsdata/utils/testing.py b/xsdata/utils/testing.py
index a5d1a8fa..62f91317 100644
--- a/xsdata/utils/testing.py
+++ b/xsdata/utils/testing.py
@@ -206,6 +206,7 @@ class AttrTypeFactory(Factory):
         native: bool = False,
         forward: bool = False,
         circular: bool = False,
+        reference: int = 0,
         **kwargs: Any,
     ) -> AttrType:
         if not qname:
@@ -217,6 +218,7 @@ class AttrTypeFactory(Factory):
             native=native,
             circular=circular,
             forward=forward,
+            reference=reference,
         )
 
     @classmethod