1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98
|
import abc
import datetime
from pathlib import Path
from typing import Dict, Iterator, List, NamedTuple
from xsdata import __version__
from xsdata.codegen.models import Class
from xsdata.exceptions import CodeGenerationError
from xsdata.models.config import GeneratorConfig
from xsdata.utils.collections import group_by
from xsdata.utils.package import module_path, package_path
class GeneratorResult(NamedTuple):
"""
Generator easy access output wrapper.
:param path: file path to be written
:param title: result title for misc usage
:param source: source code/output to be written
"""
path: Path
title: str
source: str
class AbstractGenerator(abc.ABC):
"""Abstract code generator class."""
__slots__ = "config"
def __init__(self, config: GeneratorConfig):
"""
Generator constructor.
:param config Generator configuration
"""
self.config = config
def module_name(self, module: str) -> str:
"""Convert the given module name to match the generator conventions."""
return module
def package_name(self, package: str) -> str:
"""Convert the given module name to match the generator conventions."""
return package
@abc.abstractmethod
def render(self, classes: List[Class]) -> Iterator[GeneratorResult]:
"""Return an iterator of the generated results."""
@classmethod
def group_by_package(cls, classes: List[Class]) -> Dict[Path, List[Class]]:
"""Group the given list of classes by the target package directory."""
return group_by(classes, lambda x: package_path(x.target_module))
@classmethod
def group_by_module(cls, classes: List[Class]) -> Dict[Path, List[Class]]:
"""Group the given list of classes by the target module directory."""
return group_by(classes, lambda x: module_path(x.target_module))
def render_header(self) -> str:
"""Generate a header for the writer to prepend on the output files."""
if not self.config.output.include_header:
return ""
now = datetime.datetime.now().isoformat(sep=" ", timespec="seconds")
return (
f'"""This file was generated by xsdata, v{__version__}, on {now}'
f"\n\nGenerator: {self.__class__.__qualname__}\n"
f"See: https://xsdata.readthedocs.io/\n"
'"""\n'
)
def normalize_packages(self, classes: List[Class]):
"""
Normalize the target package and module names by the given output
generator.
:param classes: a list of codegen class instances
"""
modules = {}
packages = {}
for obj in classes:
if obj.package is None or obj.module is None:
raise CodeGenerationError(
f"Class `{obj.name}` has not been assigned to a package"
)
if obj.module not in modules:
modules[obj.module] = self.module_name(obj.module)
if obj.package not in packages:
packages[obj.package] = self.package_name(obj.package)
obj.module = modules[obj.module]
obj.package = packages[obj.package]
|