Repository: tefra/xsdata
Pull request #974 (2024-03-10 15:10:46)

fix: unnest class with circular reference
## 📒 Description

Unnest an class with a circular reference to itself fails

## 🔗 What I've Done

> Write a description of the steps taken to resolve the issue

## 💬 Comments

> A place to write any comments to the reviewer.

## 🛫 Checklist

- [ ] Updated docs
- [ ] Added unit-tests
- [ ] [Sample tests](https://github.com/tefra/xsdata-samples) pass
- [ ] [W3C tests](https://github.com/tefra/xsdata-w3c-tests) pass


sonarcloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=tefra_xsdata&pullRequest=974) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=tefra_xsdata&pullRequest=974&resolved=false&inNewCodePeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/component_measures?id=tefra_xsdata&pullRequest=974&metric=new_accepted_issues&view=list)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=tefra_xsdata&pullRequest=974&resolved=false&inNewCodePeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/no-data-16px.png '') No data about Coverage  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=tefra_xsdata&pullRequest=974&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=tefra_xsdata&pullRequest=974)


codecov[bot]: ## [Codecov](https://app.codecov.io/gh/tefra/xsdata/pull/974?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+Tsou) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 99.71%. Comparing base [(`8e50e1e`)](https://app.codecov.io/gh/tefra/xsdata/commit/8e50e1e1c27f506138edb68bb7e363a87b564357?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+Tsou) to head [(`61912b9`)](https://app.codecov.io/gh/tefra/xsdata/pull/974?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+Tsou).


<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main     #974      +/-   ##
==========================================
- Coverage   99.97%   99.71%   -0.27%     
==========================================
  Files         112      112              
  Lines        8973     8978       +5     
  Branches     2135     2138       +3     
==========================================
- Hits         8971     8952      -19     
- Misses          1       21      +20     
- Partials        1        5       +4     
```



</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/tefra/xsdata/pull/974?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+Tsou).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+Tsou).


Patch:
diff --git a/CHANGES.md b/CHANGES.md
index fb3304b5..181bf003 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,3 +1,10 @@
+## 24.3.1 (2024-03-10)
+
+**Fixes**
+
+- Unnest class with circular reference
+  ([#974](https://github.com/tefra/xsdata/pull/974))
+
 ## 24.3 (2024-03-10)
 
 **Features**
diff --git a/README.md b/README.md
index 28bce8b6..a7bf6255 100644
--- a/README.md
+++ b/README.md
@@ -73,6 +73,13 @@ Check the [documentation](https://xsdata.readthedocs.io) for more ✨✨✨
   - Support xinclude statements and unknown properties
   - Customize behaviour through config
 
+## Changelog: 24.3.1 (2024-03-10)
+
+**Fixes**
+
+- Unnest class with circular reference
+  ([#974](https://github.com/tefra/xsdata/pull/974))
+
 ## Changelog: 24.3 (2024-03-10)
 
 **Features**
diff --git a/pyproject.toml b/pyproject.toml
index 0399e875..739fd36f 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -42,7 +42,7 @@ content-type = "text/markdown"
 Homepage = "https://github.com/tefra/xsdata"
 Source = "https://github.com/tefra/xsdata"
 Documentation = "https://xsdata.readthedocs.io/"
-Changelog = "https://xsdata.readthedocs.io/en/latest/changelog.html"
+Changelog = "https://xsdata.readthedocs.io/en/latest/changelog/"
 
 [project.optional-dependencies]
 cli = [
diff --git a/xsdata/__init__.py b/xsdata/__init__.py
index 2926ea9f..c83195cd 100644
--- a/xsdata/__init__.py
+++ b/xsdata/__init__.py
@@ -1,1 +1,1 @@
-__version__ = "24.3"
+__version__ = "24.3.1"
diff --git a/xsdata/codegen/handlers/unnest_inner_classes.py b/xsdata/codegen/handlers/unnest_inner_classes.py
index 5ddf0ef7..4b38d8cf 100644
--- a/xsdata/codegen/handlers/unnest_inner_classes.py
+++ b/xsdata/codegen/handlers/unnest_inner_classes.py
@@ -62,6 +62,13 @@ class UnnestInnerClasses(RelativeHandlerInterface):
         clone = inner.clone()
         clone.local_type = True
         clone.qname = build_qname(inner.target_namespace, f"{name}_{inner.name}")
+
+        for attr in clone.attrs:
+            for tp in attr.types:
+                if tp.circular and tp.qname == inner.qname:
+                    tp.qname = clone.qname
+                    tp.reference = clone.ref
+
         return clone
 
     @classmethod


Test patch:
diff --git a/tests/codegen/handlers/test_unnest_inner_classes.py b/tests/codegen/handlers/test_unnest_inner_classes.py
index b59a7fd1..8b7b953c 100644
--- a/tests/codegen/handlers/test_unnest_inner_classes.py
+++ b/tests/codegen/handlers/test_unnest_inner_classes.py
@@ -61,7 +61,7 @@ class UnnestInnerClassesTests(FactoryTestCase):
         self.assertFalse(attr.types[0].forward)
         self.assertEqual("{xsdata}class_C_class_B", attr.types[0].qname)
 
-    def test_clone_attr(self):
+    def test_clone_class(self):
         target = ClassFactory.create(qname="{a}b")
         actual = self.processor.clone_class(target, "parent")
 
@@ -69,6 +69,25 @@ class UnnestInnerClassesTests(FactoryTestCase):
         self.assertTrue(actual.local_type)
         self.assertEqual("{a}parent_b", actual.qname)
 
+    def test_clone_class_with_circular_reference(self):
+        target = ClassFactory.create(qname="{a}b")
+        target.attrs.append(
+            AttrFactory.create(
+                name="self",
+                types=[AttrTypeFactory.create(qname=target.qname, circular=True)],
+            )
+        )
+
+        actual = self.processor.clone_class(target, "parent")
+
+        self.assertIsNot(target, actual)
+        self.assertTrue(actual.local_type)
+        self.assertEqual("{a}parent_b", actual.qname)
+
+        self.assertTrue(actual.attrs[0].types[0].circular)
+        self.assertEqual(actual.ref, actual.attrs[0].types[0].reference)
+        self.assertEqual(actual.qname, actual.attrs[0].types[0].qname)
+
     def test_update_types(self):
         attr = AttrFactory.create(
             types=[