forked from openSUSE/docbuild
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy patherrors.py
More file actions
143 lines (117 loc) · 4.75 KB
/
errors.py
File metadata and controls
143 lines (117 loc) · 4.75 KB
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
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
140
141
142
143
"""Utilities for handling and formatting application errors."""
import tomllib
from pydantic import BaseModel, ValidationError
from rich.console import Console
from rich.text import Text
from ..constants import DEFAULT_ERROR_LIMIT
def format_pydantic_error(
error: ValidationError,
model_class: type[BaseModel],
config_file: str,
verbose: int = 0,
console: Console | None = None,
) -> None:
"""Centralized formatter for Pydantic ValidationErrors using Rich.
:param error: The caught ValidationError object.
:param model_class: The Pydantic model class that failed validation.
:param config_file: The name/path of the config file being processed.
:param verbose: Verbosity level to control error detail.
:param console: Optional Rich console object. If None, creates a stderr console.
"""
# Use provided console or fall back to default (Dependency Injection)
con = console or Console(stderr=True)
errors = error.errors()
error_count = len(errors)
# Header
header = Text.assemble(
(f"{error_count} Validation error{'s' if error_count > 1 else ''} ", "bold red"),
("in config file ", "white"),
(f"'{config_file}'", "bold cyan"),
(":", "white")
)
con.print(header)
con.print()
# Smart Truncation: Use the constant from constants.py
max_display = DEFAULT_ERROR_LIMIT if verbose < 2 else error_count
display_errors = errors[:max_display]
for i, err in enumerate(display_errors, 1):
# 1. Resolve Location and Field Info
# Filter out internal Pydantic tags (strings with '[' or '-')
# to keep the path clean for end users.
clean_loc = [
str(v) for v in err["loc"]
if not (isinstance(v, str) and ("[" in v or "-" in v))
and not isinstance(v, int)
]
loc_path = ".".join(clean_loc)
err_type = err["type"]
msg = err["msg"]
# 2. Extract Field Metadata from the Model Class
field_info = None
current_model = model_class
for part in err["loc"]:
if (isinstance(current_model, type) and
issubclass(current_model, BaseModel) and
part in current_model.model_fields):
field_info = current_model.model_fields[part]
annotation = field_info.annotation
if (isinstance(annotation, type) and
issubclass(annotation, BaseModel)):
current_model = annotation
else:
current_model = None
else:
field_info = None
break
# 3. Build the Display
error_panel = Text()
error_panel.append(f"({i}) In '", style="white")
error_panel.append(loc_path, style="bold yellow")
error_panel.append("':\n", style="white")
# Error detail
error_panel.append(f" {msg}\n", style="red")
# Helpful context from Field metadata
if field_info:
if field_info.title:
error_panel.append(" Expected: ", style="dim")
error_panel.append(f"{field_info.title}\n", style="italic green")
if verbose > 0 and field_info.description:
error_panel.append(" Description: ", style="dim")
error_panel.append(f"{field_info.description}\n", style="dim italic")
# Documentation Link
error_panel.append(" See: ", style="dim")
error_panel.append(
f"https://opensuse.github.io/docbuild/latest/errors/{err_type}.html",
style="link underline blue"
)
con.print(error_panel)
con.print()
# Footer for Truncation
if error_count > max_display:
con.print(
f"[dim]... and {error_count - max_display} more errors. "
"Use '-vv' to see all errors.[/dim]\n"
)
def format_toml_error(
error: tomllib.TOMLDecodeError,
config_file: str,
console: Console | None = None,
) -> None:
"""Format TOML syntax errors using Rich.
:param error: The caught TOMLDecodeError object.
:param config_file: The name/path of the config file with the syntax error.
:param console: Optional Rich console object.
"""
con = console or Console(stderr=True)
header = Text.assemble(
("Syntax error ", "bold red"),
("in config file ", "white"),
(f"'{config_file}'", "bold cyan"),
(":", "white")
)
con.print(header)
# tomllib error messages include the line and column info naturally
con.print(f" [red]{error}[/red]")
con.print()
con.print(" [dim]Please verify that the file is a valid TOML file.[/dim]")
con.print(" [dim]Note: Booleans must be lowercase (true/false) in TOML.[/dim]")