Repository: tefra/xsdata

Issue:
Circular dependency while generating from OGC WFS XSD
Hello,

Models generated from OGC WFS XSD models cannot be used due to circular import:

```
xsdata generate https://raw.githubusercontent.com/highsource/ogc-schemas/master/schemas/src/main/resources/ogc/wfs/2.0/wfs.xsd
```

```
>>> from generated import WfsCapabilitiesType
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/tmp/generated/__init__.py", line 1, in <module>
    from generated.wfs import (
  File "/tmp/generated/wfs.py", line 6, in <module>
    from generated.filter.pkg_2.pkg_0.filter import (
  File "/tmp/generated/filter/pkg_2/pkg_0/__init__.py", line 8, in <module>
    from generated.filter.pkg_2.pkg_0.filter import (
  File "/tmp//generated/filter/pkg_2/pkg_0/filter.py", line 9, in <module>
    from generated.filter.pkg_2.pkg_0.query import AbstractSelectionClauseType
  File "/tmp/generated/filter/pkg_2/pkg_0/query.py", line 3, in <module>
    from generated.filter.pkg_2.pkg_0.filter import Filter
ImportError: cannot import name 'Filter' from partially initialized module 'generated.filter.pkg_2.pkg_0.filter' (most likely due to a circular import) (/tmp/generated/filter/pkg_2/pkg_0/filter.py)
```

Is that a bug ?

Best regards, Adam.

Discussion:
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=999) **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=999&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=999&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=999&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=999&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=tefra_xsdata&pullRequest=999)


codecov[bot]: ## [Codecov](https://app.codecov.io/gh/tefra/xsdata/pull/999?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+Tsou) Report
Attention: Patch coverage is `88.46154%` with `3 lines` in your changes are missing coverage. Please review.
> Project coverage is 99.65%. Comparing base [(`96d2085`)](https://app.codecov.io/gh/tefra/xsdata/commit/96d20855804d54bb139b6ebfafd462da17796aae?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+Tsou) to head [(`2d9c0e2`)](https://app.codecov.io/gh/tefra/xsdata/pull/999?dropdown=coverage&src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+Tsou).

| [Files](https://app.codecov.io/gh/tefra/xsdata/pull/999?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+Tsou) | Patch % | Lines |
|---|---|---|
| [xsdata/formats/dataclass/generator.py](https://app.codecov.io/gh/tefra/xsdata/pull/999?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Chris+Tsou#diff-eHNkYXRhL2Zvcm1hdHMvZGF0YWNsYXNzL2dlbmVyYXRvci5weQ==) | 88.00% | [1 Missing and 2 partials :warning: ](https://app.codecov.io/gh/tefra/xsdata/pull/999?src=pr&el=tree&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     #999      +/-   ##
==========================================
- Coverage   99.94%   99.65%   -0.30%     
==========================================
  Files         117      117              
  Lines        9174     9197      +23     
  Branches     2180     2184       +4     
==========================================
- Hits         9169     9165       -4     
- Misses          4       25      +21     
- Partials        1        7       +6     
```



</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/tefra/xsdata/pull/999?dropdown=coverage&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).

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=999) **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=999&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=999&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=999&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=999&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=tefra_xsdata&pullRequest=999)


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=999) **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=999&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=999&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=999&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=999&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=tefra_xsdata&pullRequest=999)



Patch:
diff --git a/xsdata/codegen/transformer.py b/xsdata/codegen/transformer.py
index d0fce0c3..edf645b3 100644
--- a/xsdata/codegen/transformer.py
+++ b/xsdata/codegen/transformer.py
@@ -138,10 +138,13 @@ class ResourceTransformer:
 
         try:
             self.process_classes()
-        except CircularDependencyError:
+        except ModuleNotFoundError:
+            logger.warning("Module not found on imports validation, please report it.")
+        except (CircularDependencyError, ImportError):
             raise CodegenError(
                 "Circular Dependencies Found",
-                help="Try a different structure style and enabling unnest classes.",
+                help="Try a different structure style and/or enable unnest classes. "
+                "Cleanup previously generated packages and modules.",
             )
 
         for name, times in stopwatches.items():
diff --git a/xsdata/formats/dataclass/generator.py b/xsdata/formats/dataclass/generator.py
index 8e2c79a1..497e1399 100644
--- a/xsdata/formats/dataclass/generator.py
+++ b/xsdata/formats/dataclass/generator.py
@@ -1,5 +1,8 @@
+import importlib
+import pkgutil
 import re
 import subprocess
+import sys
 from pathlib import Path
 from typing import Iterator, List, Optional
 
@@ -10,6 +13,7 @@ from xsdata.codegen.models import Class, Import
 from xsdata.codegen.resolver import DependenciesResolver
 from xsdata.formats.dataclass.filters import Filters
 from xsdata.formats.mixins import AbstractGenerator, GeneratorResult
+from xsdata.logger import logger
 from xsdata.models.config import GeneratorConfig
 
 
@@ -82,6 +86,29 @@ class DataclassGenerator(AbstractGenerator):
                 source=src_code,
             )
 
+        self.validate_imports()
+
+    def validate_imports(self):
+        """Recursively import all generated packages.
+
+        Raises:
+            ImportError: On circular imports
+        """
+
+        def import_package(package_name):
+            logger.debug(f"Importing: {package_name}")
+            module = importlib.import_module(package_name)
+            if hasattr(module, "__path__"):
+                for _, name, _ in pkgutil.walk_packages(
+                    module.__path__, module.__name__ + "."
+                ):
+                    logger.debug(f"Importing: {name}")
+                    importlib.import_module(name)
+
+        sys.path.insert(0, str(Path.cwd().absolute()))
+        package = self.config.output.package
+        import_package(self.package_name(package))
+
     def render_package(self, classes: List[Class], module: str, filename: Path) -> str:
         """Render the package for the given classes.
 
diff --git a/xsdata/formats/dataclass/models/builders.py b/xsdata/formats/dataclass/models/builders.py
index dff734d1..f2b1d566 100644
--- a/xsdata/formats/dataclass/models/builders.py
+++ b/xsdata/formats/dataclass/models/builders.py
@@ -562,24 +562,6 @@ class XmlVarBuilder:
 
         return tuple(result)
 
-    @classmethod
-    def default_namespace(cls, namespaces: Sequence[str]) -> Optional[str]:
-        """Return the first valid namespace uri or None.
-
-        Args:
-            namespaces: A list of namespace options which may include
-                valid uri(s) or a placeholder e.g. ##any, ##other,
-                ##targetNamespace, ##local
-
-        Returns:
-            A namespace uri or None if there isn't any.
-        """
-        for namespace in namespaces:
-            if namespace and not namespace.startswith("#"):
-                return namespace
-
-        return None
-
     @classmethod
     def is_any_type(cls, types: Sequence[Type], xml_type: str) -> bool:
         """Return whether the given xml type supports generic values."""


Test patch:
diff --git a/tests/codegen/test_transformer.py b/tests/codegen/test_transformer.py
index 7d8426a9..84f2b775 100644
--- a/tests/codegen/test_transformer.py
+++ b/tests/codegen/test_transformer.py
@@ -113,6 +113,17 @@ class ResourceTransformerTests(FactoryTestCase):
         with self.assertRaises(CodegenError):
             self.transformer.process([])
 
+    @mock.patch("xsdata.codegen.transformer.logger.warning")
+    @mock.patch.object(ResourceTransformer, "process_classes")
+    def test_process_with_module_not_found_error(
+        self, mock_process_classes, mock_warning
+    ):
+        mock_process_classes.side_effect = ModuleNotFoundError({})
+        self.transformer.process([])
+        mock_warning.assert_called_once_with(
+            "Module not found on imports validation, please report it."
+        )
+
     @mock.patch.object(ResourceTransformer, "convert_schema")
     @mock.patch.object(ResourceTransformer, "convert_definitions")
     @mock.patch.object(ResourceTransformer, "parse_definitions")
diff --git a/tests/formats/dataclass/test_generator.py b/tests/formats/dataclass/test_generator.py
index ffecd747..c2147113 100644
--- a/tests/formats/dataclass/test_generator.py
+++ b/tests/formats/dataclass/test_generator.py
@@ -16,9 +16,12 @@ class DataclassGeneratorTests(FactoryTestCase):
         config = GeneratorConfig()
         self.generator = DataclassGenerator(config)
 
+    @mock.patch.object(DataclassGenerator, "validate_imports")
     @mock.patch.object(DataclassGenerator, "render_package")
     @mock.patch.object(DataclassGenerator, "render_module")
-    def test_render(self, mock_render_module, mock_render_package):
+    def test_render(
+        self, mock_render_module, mock_render_package, mock_validate_imports
+    ):
         classes = [
             ClassFactory.create(package="foo.bar", module="tests"),
             ClassFactory.create(package="bar.foo", module="tests"),
@@ -56,6 +59,7 @@ class DataclassGeneratorTests(FactoryTestCase):
         mock_render_module.assert_has_calls(
             [mock.call(mock.ANY, [x], mock.ANY) for x in classes]
         )
+        mock_validate_imports.assert_called_once()
 
     def test_render_package(self):
         classes = [