8000 feat: Detect and alert circular imports by tefra · Pull Request #999 · tefra/xsdata · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

feat: Detect and alert circular imports #999

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

Merged
merged 1 commit into from
Mar 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions tests/codegen/test_transformer.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,17 @@ def test_process_with_circular_dependencies_error(self, mock_process_classes):
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")
Expand Down
6 changes: 5 additions & 1 deletion tests/formats/dataclass/test_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,12 @@ def setUp(self):
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"),
Expand Down Expand Up @@ -56,6 +59,7 @@ def test_render(self, mock_render_module, mock_render_package):
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 = [
Expand Down
7 changes: 5 additions & 2 deletions xsdata/codegen/transformer.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,10 +138,13 @@ def process(self, uris: List[str], cache: bool = False):

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():
Expand Down
27 changes: 27 additions & 0 deletions xsdata/formats/dataclass/generator.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import importlib
import pkgutil
import re
import subprocess
import sys
from pathlib import Path
from typing import Iterator, List, Optional

Expand All @@ -10,6 +13,7 @@
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


Expand Down Expand Up @@ -82,6 +86,29 @@ def render(self, classes: List[Class]) -> Iterator[GeneratorResult]:
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.

Expand Down
18 changes: 0 additions & 18 deletions xsdata/formats/dataclass/models/builders.py
Original file line number Diff line number Diff line change
Expand Up @@ -562,24 +562,6 @@ def resolve_namespaces(

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."""
Expand Down
0