Files
2025-12-28 17:47:56 +03:00

160 lines
5.3 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from typing import Any, get_args, get_origin
from pydantic import BaseModel
from pydantic.fields import FieldInfo
from rich.console import Console
from rich.table import Table
def format_field_type(annotation: Any) -> str:
"""Форматирует тип поля для отображения"""
if annotation is None:
return "None"
origin = get_origin(annotation)
if origin is not None:
args = get_args(annotation)
origin_name = getattr(origin, "__name__", str(origin))
if origin_name == "UnionType" or str(origin) == "typing.Union":
args_str = " | ".join(format_field_type(a) for a in args)
return args_str
if args:
args_str = ", ".join(format_field_type(a) for a in args)
return f"{origin_name}[{args_str}]"
return origin_name
if isinstance(annotation, type) and issubclass(annotation, BaseModel):
return f"[magenta]{annotation.__name__}[/magenta]"
return getattr(annotation, "__name__", str(annotation))
def print_model_fields(
console: Console,
model_class: type[BaseModel],
indent: int = 0,
visited: set | None = None,
) -> None:
"""Рекурсивно выводит поля модели с поддержкой вложенных моделей"""
if visited is None:
visited = set()
if model_class in visited:
console.print(
f"{' ' * indent}[dim](циклическая ссылка на {model_class.__name__})[/dim]"
)
return
visited.add(model_class)
prefix = " " * indent
for field_name, field_info in model_class.model_fields.items():
annotation = field_info.annotation
field_type = format_field_type(annotation)
default = field_info.default
description = field_info.description or ""
if default is None:
default_str = "[dim]None[/dim]"
elif default is ...:
default_str = "[red]required[/red]"
elif isinstance(default, bool):
default_str = "[green]true[/green]" if default else "[red]false[/red]"
else:
default_str = str(default)
console.print(
f"{prefix}[yellow]{field_name}[/yellow]: {field_type} = {default_str}"
+ (f" [dim]# {description}[/dim]" if description else "")
)
nested_model = None
if isinstance(annotation, type) and issubclass(annotation, BaseModel):
nested_model = annotation
else:
origin = get_origin(annotation)
if origin is not None:
for arg in get_args(annotation):
if isinstance(arg, type) and issubclass(arg, BaseModel):
nested_model = arg
break
if nested_model is not None:
console.print(f"{prefix} [dim]└─ {nested_model.__name__}:[/dim]")
print_model_fields(console, nested_model, indent + 2, visited.copy())
def print_model_table(
console: Console,
model_class: type[BaseModel],
prefix: str = "",
visited: set | None = None,
) -> Table:
"""Выводит поля модели в виде таблицы с вложенными моделями"""
if visited is None:
visited = set()
table = Table(show_header=True, box=None if prefix else None)
table.add_column("Поле", style="yellow")
table.add_column("Тип", style="cyan")
table.add_column("По умолчанию")
table.add_column("Описание", style="dim")
_add_model_rows(table, model_class, prefix, visited)
return table
def _add_model_rows(
table: Table,
model_class: type[BaseModel],
prefix: str = "",
visited: set | None = None,
) -> None:
"""Добавляет строки модели в таблицу рекурсивно"""
if visited is None:
visited = set()
if model_class in visited:
return
visited.add(model_class)
for field_name, field_info in model_class.model_fields.items():
annotation = field_info.annotation
field_type = format_field_type(annotation)
default = field_info.default
description = field_info.description or ""
if default is None:
default_str = "-"
elif default is ...:
default_str = "[red]required[/red]"
elif isinstance(default, bool):
default_str = "true" if default else "false"
elif isinstance(default, BaseModel):
default_str = "{...}"
else:
default_str = str(default)[:20]
full_name = f"{prefix}{field_name}" if prefix else field_name
table.add_row(full_name, field_type, default_str, description[:40])
nested_model = None
if isinstance(annotation, type) and issubclass(annotation, BaseModel):
nested_model = annotation
else:
origin = get_origin(annotation)
if origin is not None:
for arg in get_args(annotation):
if isinstance(arg, type) and issubclass(arg, BaseModel):
nested_model = arg
break
if nested_model is not None and nested_model not in visited:
_add_model_rows(table, nested_model, f" {full_name}.", visited.copy())