Skip to content

click

xsdata.utils.click

EnumChoice

Bases: Choice

Custom click choice widget for enumerations.

Source code in xsdata/utils/click.py
106
107
108
109
110
111
112
113
114
115
116
class EnumChoice(click.Choice):
    """Custom click choice widget for enumerations."""

    def __init__(self, enumeration: type[enum.Enum]):
        """Initialize the enum choice widget."""
        self.enumeration = enumeration
        super().__init__([e.value for e in enumeration])

    def convert(self, value: Any, *args: Any) -> enum.Enum:
        """Parse the value into an enumeration member."""
        return self.enumeration(value)

__init__(enumeration)

Initialize the enum choice widget.

Source code in xsdata/utils/click.py
109
110
111
112
def __init__(self, enumeration: type[enum.Enum]):
    """Initialize the enum choice widget."""
    self.enumeration = enumeration
    super().__init__([e.value for e in enumeration])

convert(value, *args)

Parse the value into an enumeration member.

Source code in xsdata/utils/click.py
114
115
116
def convert(self, value: Any, *args: Any) -> enum.Enum:
    """Parse the value into an enumeration member."""
    return self.enumeration(value)

LogFormatter

Bases: Formatter

Custom log formatter with click colors.

Source code in xsdata/utils/click.py
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
class LogFormatter(logging.Formatter):
    """Custom log formatter with click colors."""

    colors: ClassVar[dict[str, Any]] = {
        "error": {"fg": "red"},
        "exception": {"fg": "red"},
        "critical": {"fg": "red"},
        "debug": {"fg": "blue"},
        "warning": {"fg": "yellow"},
    }

    def format(self, record: logging.LogRecord) -> str:
        """Format the log record with click styles."""
        if not record.exc_info:
            level = record.levelname.lower()
            msg = record.getMessage()
            if level in self.colors:
                prefix = click.style(level, **self.colors[level])
                msg = f"{prefix}: {msg}"
            return msg

        return super().format(record)  # pragma: no cover

format(record)

Format the log record with click styles.

Source code in xsdata/utils/click.py
130
131
132
133
134
135
136
137
138
139
140
def format(self, record: logging.LogRecord) -> str:
    """Format the log record with click styles."""
    if not record.exc_info:
        level = record.levelname.lower()
        msg = record.getMessage()
        if level in self.colors:
            prefix = click.style(level, **self.colors[level])
            msg = f"{prefix}: {msg}"
        return msg

    return super().format(record)  # pragma: no cover

LogHandler

Bases: Handler

Custom click log handler to record warnings.

Source code in xsdata/utils/click.py
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
class LogHandler(logging.Handler):
    """Custom click log handler to record warnings."""

    def __init__(self, level: int | str = logging.NOTSET):
        """Initialize the log handler."""
        super().__init__(level)
        self.warnings: list[str] = []

    def emit(self, record: logging.LogRecord) -> None:
        """Override emit to record warnings."""
        try:
            msg = self.format(record)
            if record.levelno > logging.INFO:
                self.warnings.append(msg)
            else:
                click.echo(msg, err=True)
        except Exception:  # pragma: no cover
            self.handleError(record)

    def emit_warnings(self) -> None:
        """Print all recorded warnings to click stdout."""
        num = len(self.warnings)
        if num:
            click.echo(click.style(f"Warnings: {num}", bold=True))
            for msg in self.warnings:
                click.echo(msg, err=True)

            self.warnings.clear()

__init__(level=logging.NOTSET)

Initialize the log handler.

Source code in xsdata/utils/click.py
146
147
148
149
def __init__(self, level: int | str = logging.NOTSET):
    """Initialize the log handler."""
    super().__init__(level)
    self.warnings: list[str] = []

emit(record)

Override emit to record warnings.

Source code in xsdata/utils/click.py
151
152
153
154
155
156
157
158
159
160
def emit(self, record: logging.LogRecord) -> None:
    """Override emit to record warnings."""
    try:
        msg = self.format(record)
        if record.levelno > logging.INFO:
            self.warnings.append(msg)
        else:
            click.echo(msg, err=True)
    except Exception:  # pragma: no cover
        self.handleError(record)

emit_warnings()

Print all recorded warnings to click stdout.

Source code in xsdata/utils/click.py
162
163
164
165
166
167
168
169
170
def emit_warnings(self) -> None:
    """Print all recorded warnings to click stdout."""
    num = len(self.warnings)
    if num:
        click.echo(click.style(f"Warnings: {num}", bold=True))
        for msg in self.warnings:
            click.echo(msg, err=True)

        self.warnings.clear()

URL

Bases: ParamType

Click parameter type for validating HTTP/HTTPS URLs.

This ensures that only remote HTTP or HTTPS URLs are accepted, rejecting local file paths or file:// URIs.

Source code in xsdata/utils/click.py
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
class URL(click.ParamType):
    """Click parameter type for validating HTTP/HTTPS URLs.

    This ensures that only remote HTTP or HTTPS URLs are accepted,
    rejecting local file paths or file:// URIs.
    """

    name = "url"

    def convert(self, value: str, param: Parameter | None, ctx: Context | None) -> str:
        """Validate that the value is an HTTP or HTTPS URL.

        Args:
            value: The parameter value to validate
            param: The parameter object
            ctx: The click context

        Returns:
            The validated URL string

        Raises:
            click.BadParameter: If the URL scheme is not http or https
        """
        parsed = urlparse(value)
        if parsed.scheme not in ("http", "https"):
            self.fail(f"Source must be an HTTP URL, got: {value}\n", param, ctx)

        return value

convert(value, param, ctx)

Validate that the value is an HTTP or HTTPS URL.

Parameters:

Name Type Description Default
value str

The parameter value to validate

required
param Parameter | None

The parameter object

required
ctx Context | None

The click context

required

Returns:

Type Description
str

The validated URL string

Raises:

Type Description
BadParameter

If the URL scheme is not http or https

Source code in xsdata/utils/click.py
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
def convert(self, value: str, param: Parameter | None, ctx: Context | None) -> str:
    """Validate that the value is an HTTP or HTTPS URL.

    Args:
        value: The parameter value to validate
        param: The parameter object
        ctx: The click context

    Returns:
        The validated URL string

    Raises:
        click.BadParameter: If the URL scheme is not http or https
    """
    parsed = urlparse(value)
    if parsed.scheme not in ("http", "https"):
        self.fail(f"Source must be an HTTP URL, got: {value}\n", param, ctx)

    return value

model_options(obj)

Decorate click commands to add model options.

Source code in xsdata/utils/click.py
24
25
26
27
28
29
30
31
32
def model_options(obj: Any) -> Callable[[FC], FC]:
    """Decorate click commands to add model options."""

    def decorator(f: F) -> F:
        for option in reversed(list(build_options(obj, ""))):
            option(f)
        return f

    return decorator

build_options(obj, parent)

Build click options by a data class.

Source code in xsdata/utils/click.py
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
def build_options(obj: Any, parent: str) -> Iterator[Callable[[FC], FC]]:
    """Build click options by a data class."""
    type_hints = get_type_hints(obj)
    doc_hints = get_doc_hints(obj)

    for field in fields(obj):
        type_hint = type_hints[field.name]
        doc_hint = doc_hints[field.name]
        name = field.metadata.get("cli", field.name)

        if not name:
            continue

        qname = f"{parent}.{field.name}".strip(".")

        if is_dataclass(type_hint):
            yield from build_options(type_hint, qname)
        else:
            is_flag = False
            opt_type = type_hint
            if name == "output":
                opt_type = click.Choice(CodeWriter.generators.keys())
                names = ["-o", "--output"]
            elif type_hint is bool:
                is_flag = True
                opt_type = None
                name = text.kebab_case(name)
                names = [f"--{name}/--no-{name}"]
            else:
                if issubclass(type_hint, enum.Enum):
                    opt_type = EnumChoice(type_hint)

                parts = text.split_words(name)
                name = "-".join(parts)
                name_short = "".join(part[0] for part in parts)
                names = [f"--{name}", f"-{name_short}"]

            names.append("__".join(qname.split(".")))

            default_value = (
                field.default.value
                if isinstance(field.default, enum.Enum)
                else field.default
            )
            doc_hint += f" [default: {default_value}]"

            yield click.option(
                *names,
                help=doc_hint,
                is_flag=is_flag,
                type=opt_type,
                default=None,
            )

get_doc_hints(obj)

Return a param-docstring map of the class arguments.

Source code in xsdata/utils/click.py
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
def get_doc_hints(obj: Any) -> dict[str, str]:
    """Return a param-docstring map of the class arguments."""
    docstrings = inspect.getdoc(obj)
    assert docstrings is not None

    start = docstrings.index("Args:") + 6
    params = docstrings[start:].replace("\n        ", " ")

    result = {}
    for line in params.splitlines():
        param, hint = line.split(":", 1)
        result[param.strip()] = " ".join(hint.split())

    return result