Source code for xsdata.generators
from abc import ABC, abstractmethod
from pathlib import Path
from typing import Any, Iterator, List, Optional, Tuple
from xsdata.formats.dataclass.utils import replace_words
from xsdata.models.codegen import Attr, Class, Package
from xsdata.models.elements import Schema
from xsdata.models.enums import XSDType
from xsdata.utils import text
[docs]class AbstractGenerator(ABC):
[docs] @abstractmethod
def render(
self, schema: Schema, classes: List[Class], package: str
) -> Iterator[Tuple[Path, str]]:
pass
[docs] @abstractmethod
def print(
self, schema: Schema, classes: List[Class], package: str
) -> Iterator[Tuple[str, Class]]:
pass
[docs]class PythonAbstractGenerator(AbstractGenerator, ABC):
[docs] @classmethod
def process_class(cls, obj: Class, parents: List[str] = None) -> Class:
"""Normalize all class instance fields, extends, name and the inner
classes recursively."""
parents = parents or []
obj.name = cls.class_name(obj.name)
for extension in obj.extensions:
extension.name = cls.type_name(extension.name)
curr_parents = parents + [obj.name]
for inner in obj.inner:
cls.process_class(inner, curr_parents)
is_enum = obj.is_enumeration
for attr in obj.attrs:
if is_enum:
cls.process_enumeration(attr)
else:
cls.process_attribute(attr, curr_parents)
return obj
[docs] @classmethod
def process_attribute(cls, attr: Attr, parents) -> None:
"""Normalize attribute properties."""
attr.name = cls.attribute_name(attr.name)
attr.type = cls.attribute_type(attr, parents)
attr.local_name = text.split(attr.local_name)[1]
attr.default = cls.attribute_default(attr)
[docs] @classmethod
def process_enumeration(cls, attr: Attr, *args) -> None:
"""Normalize attribute properties."""
attr.name = cls.enumeration_name(attr.name)
attr.default = cls.attribute_default(attr)
[docs] @classmethod
def process_import(cls, package: Package) -> Package:
"""Normalize import package properties."""
package.name = cls.class_name(package.name)
if package.alias:
package.alias = cls.class_name(package.alias)
return package
[docs] @classmethod
def class_name(cls, name: str) -> str:
"""Convert class names to pascal case."""
return text.pascal_case(name)
[docs] @classmethod
def type_name(cls, name: str) -> str:
"""Convert xsd types to python or apply class name conventions after
stripping any reference prefix."""
return XSDType.get_local(name) or cls.class_name(text.split(name)[1])
[docs] @classmethod
def attribute_name(cls, name: str) -> str:
"""
Strip reference prefix and turn to snake case.
If the name is one of the python reserved words append the
prefix _value
"""
local_name = text.split(name)[1]
return text.snake_case(
replace_words.get(local_name.lower(), local_name)
)
[docs] @classmethod
def enumeration_name(cls, name: str) -> str:
"""
Strip reference prefix and turn to snake case.
If the name is one of the python reserved words append the
prefix _value
"""
return cls.attribute_name(name).upper()
[docs] @classmethod
def attribute_type(cls, attr: Attr, parents: List[str]) -> str:
"""
Normalize attribute type.
Steps:
* If type alias is present use class name normalization
* Otherwise use the type name normalization
* Prepend outer class names and quote result for forward references
* Wrap the result with List if the field accepts a list of values
* Wrap the result with Optional if the field default value is None
"""
type_names: List[str] = []
for name in attr.types:
type_name = (
cls.class_name(attr.type_aliases[name])
if name in attr.type_aliases
else cls.type_name(name)
)
if type_name not in type_names:
type_names.append(type_name)
result = ", ".join(type_names)
if attr.forward_ref:
outer_str = ".".join(parents)
result = f'"{outer_str}.{result}"'
elif len(type_names) > 1:
result = f"Union[{result}]"
if attr.is_list:
result = f"List[{result}]"
elif attr.default is None:
result = f"Optional[{result}]"
return result
[docs] @classmethod
def attribute_default(cls, attr: Attr) -> Optional[Any]:
"""Normalize default value/factory by the attribute type."""
if attr.is_list:
return "list"
elif isinstance(attr.default, str):
if attr.type == "bool":
return attr.default == "true"
if attr.type == "int":
return int(attr.default)
if attr.type == "float":
return float(attr.default)
return f'"{attr.default}"'
else:
return attr.default