Skip to content

writer

xsdata.codegen.writer

CodeWriter

Code writer class.

Parameters:

Name Type Description Default
generator AbstractGenerator

The code generator instance

required

Attributes:

Name Type Description
generators Dict[str, Type[AbstractGenerator]]

A map of registered code generators

Source code in xsdata/codegen/writer.py
 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
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
class CodeWriter:
    """Code writer class.

    Args:
        generator: The code generator instance

    Attributes:
        generators: A map of registered code generators
    """

    __slots__ = "generator"

    generators: ClassVar[Dict[str, Type[AbstractGenerator]]] = {
        "dataclasses": DataclassGenerator,
    }

    def __init__(self, generator: AbstractGenerator):
        self.generator = generator

    def write(self, classes: List[Class]):
        """Write the classes to the designated modules.

        The classes may be written in the same module or
        different ones, the entrypoint must create the
        directory structure write the file outputs.

        Args:
            classes: A list of class instances
        """
        self.generator.normalize_packages(classes)
        header = self.generator.render_header()

        for result in self.generator.render(classes):
            if result.source.strip():
                logger.info("Generating package: %s", result.title)
                src_code = self.ruff_code(header + result.source, result.path)
                result.path.parent.mkdir(parents=True, exist_ok=True)
                result.path.write_text(src_code, encoding="utf-8")

    def print(self, classes: List[Class]):
        """Print the generated code for the given classes.

        Args:
            classes: A list of class instances
        """
        self.generator.normalize_packages(classes)
        header = self.generator.render_header()
        for result in self.generator.render(classes):
            if result.source.strip():
                src_code = self.ruff_code(header + result.source, result.path)
                print(src_code, end="")

    @classmethod
    def from_config(cls, config: GeneratorConfig) -> "CodeWriter":
        """Instance the code writer from the generator configuration instance.

        Validates that the output format is registered as a generator.

        Args:
            config: The generator configuration instance

        Returns:
            A new code writer instance.
        """
        if config.output.format.value not in cls.generators:
            raise CodeGenerationError(
                f"Unknown output format: '{config.output.format.value}'"
            )

        generator_class = cls.generators[config.output.format.value]
        return cls(generator=generator_class(config))

    @classmethod
    def register_generator(cls, name: str, clazz: Type[AbstractGenerator]):
        """Register a generator by name.

        Args:
            name: The generator name
            clazz: The generator class
        """
        cls.generators[name] = clazz

    @classmethod
    def unregister_generator(cls, name: str):
        """Remove a generator by name.

        Args:
            name: The generator name
        """
        cls.generators.pop(name)

    def ruff_code(self, src_code: str, file_path: Path) -> str:
        """Run ruff format on the src code.

        Args:
            src_code: The output source code
            file_path: The file path the source code will be written to

        Returns:
            The formatted output source code
        """
        commands = [
            [
                "ruff",
                "format",
                "--stdin-filename",
                str(file_path),
                "--line-length",
                str(self.generator.config.output.max_line_length),
            ],
        ]
        try:
            src_code_encoded = src_code.encode()
            for command in commands:
                result = subprocess.run(
                    command,
                    input=src_code_encoded,
                    capture_output=True,
                    check=True,
                )
                src_code_encoded = result.stdout

            return src_code_encoded.decode()
        except subprocess.CalledProcessError as e:
            error = indent(e.stderr.decode(), "  ")
            raise CodeGenerationError(f"Ruff failed:\n{error}")

write(classes)

Write the classes to the designated modules.

The classes may be written in the same module or different ones, the entrypoint must create the directory structure write the file outputs.

Parameters:

Name Type Description Default
classes List[Class]

A list of class instances

required
Source code in xsdata/codegen/writer.py
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
def write(self, classes: List[Class]):
    """Write the classes to the designated modules.

    The classes may be written in the same module or
    different ones, the entrypoint must create the
    directory structure write the file outputs.

    Args:
        classes: A list of class instances
    """
    self.generator.normalize_packages(classes)
    header = self.generator.render_header()

    for result in self.generator.render(classes):
        if result.source.strip():
            logger.info("Generating package: %s", result.title)
            src_code = self.ruff_code(header + result.source, result.path)
            result.path.parent.mkdir(parents=True, exist_ok=True)
            result.path.write_text(src_code, encoding="utf-8")

print(classes)

Print the generated code for the given classes.

Parameters:

Name Type Description Default
classes List[Class]

A list of class instances

required
Source code in xsdata/codegen/writer.py
53
54
55
56
57
58
59
60
61
62
63
64
def print(self, classes: List[Class]):
    """Print the generated code for the given classes.

    Args:
        classes: A list of class instances
    """
    self.generator.normalize_packages(classes)
    header = self.generator.render_header()
    for result in self.generator.render(classes):
        if result.source.strip():
            src_code = self.ruff_code(header + result.source, result.path)
            print(src_code, end="")

from_config(config) classmethod

Instance the code writer from the generator configuration instance.

Validates that the output format is registered as a generator.

Parameters:

Name Type Description Default
config GeneratorConfig

The generator configuration instance

required

Returns:

Type Description
CodeWriter

A new code writer instance.

Source code in xsdata/codegen/writer.py
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
@classmethod
def from_config(cls, config: GeneratorConfig) -> "CodeWriter":
    """Instance the code writer from the generator configuration instance.

    Validates that the output format is registered as a generator.

    Args:
        config: The generator configuration instance

    Returns:
        A new code writer instance.
    """
    if config.output.format.value not in cls.generators:
        raise CodeGenerationError(
            f"Unknown output format: '{config.output.format.value}'"
        )

    generator_class = cls.generators[config.output.format.value]
    return cls(generator=generator_class(config))

register_generator(name, clazz) classmethod

Register a generator by name.

Parameters:

Name Type Description Default
name str

The generator name

required
clazz Type[AbstractGenerator]

The generator class

required
Source code in xsdata/codegen/writer.py
86
87
88
89
90
91
92
93
94
@classmethod
def register_generator(cls, name: str, clazz: Type[AbstractGenerator]):
    """Register a generator by name.

    Args:
        name: The generator name
        clazz: The generator class
    """
    cls.generators[name] = clazz

unregister_generator(name) classmethod

Remove a generator by name.

Parameters:

Name Type Description Default
name str

The generator name

required
Source code in xsdata/codegen/writer.py
 96
 97
 98
 99
100
101
102
103
@classmethod
def unregister_generator(cls, name: str):
    """Remove a generator by name.

    Args:
        name: The generator name
    """
    cls.generators.pop(name)

ruff_code(src_code, file_path)

Run ruff format on the src code.

Parameters:

Name Type Description Default
src_code str

The output source code

required
file_path Path

The file path the source code will be written to

required

Returns:

Type Description
str

The formatted output source code

Source code in xsdata/codegen/writer.py
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
def ruff_code(self, src_code: str, file_path: Path) -> str:
    """Run ruff format on the src code.

    Args:
        src_code: The output source code
        file_path: The file path the source code will be written to

    Returns:
        The formatted output source code
    """
    commands = [
        [
            "ruff",
            "format",
            "--stdin-filename",
            str(file_path),
            "--line-length",
            str(self.generator.config.output.max_line_length),
        ],
    ]
    try:
        src_code_encoded = src_code.encode()
        for command in commands:
            result = subprocess.run(
                command,
                input=src_code_encoded,
                capture_output=True,
                check=True,
            )
            src_code_encoded = result.stdout

        return src_code_encoded.decode()
    except subprocess.CalledProcessError as e:
        error = indent(e.stderr.decode(), "  ")
        raise CodeGenerationError(f"Ruff failed:\n{error}")