mirror of
https://github.com/opendatalab/MinerU.git
synced 2026-10-25 17:42:08 +08:00
223 lines
6.1 KiB
Python
223 lines
6.1 KiB
Python
"""Centralized CLI output, error, and exit-code runtime."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import ast
|
|
from collections.abc import Callable
|
|
from typing import Any, NoReturn, TypeVar
|
|
|
|
import httpx
|
|
import typer
|
|
|
|
from ..errors import MineruError, ServerNotRunningError, error_response
|
|
from . import output
|
|
from .contracts import (
|
|
CliContext,
|
|
CliGuidance,
|
|
CliRenderer,
|
|
CliResult,
|
|
CliTaskResult,
|
|
RenderableOutput,
|
|
RichObject,
|
|
)
|
|
|
|
T = TypeVar("T")
|
|
|
|
|
|
def cli_ok(
|
|
data: T | None = None,
|
|
*,
|
|
render: CliRenderer[T] | None = None,
|
|
exit_code: int = 0,
|
|
warnings: list[str] | None = None,
|
|
notices: list[str] | None = None,
|
|
) -> CliResult[T]:
|
|
return CliResult(
|
|
data=data,
|
|
render=render,
|
|
exit_code=exit_code,
|
|
warnings=warnings or [],
|
|
notices=notices or [],
|
|
)
|
|
|
|
|
|
def cli_task(
|
|
data: T,
|
|
*,
|
|
status: str | None,
|
|
render: CliRenderer[T] | None = None,
|
|
fail_if_final_failed: bool,
|
|
fail_statuses: frozenset[str] = frozenset({"failed"}),
|
|
warnings: list[str] | None = None,
|
|
notices: list[str] | None = None,
|
|
) -> CliTaskResult[T]:
|
|
return CliTaskResult(
|
|
data=data,
|
|
render=render,
|
|
warnings=warnings or [],
|
|
notices=notices or [],
|
|
status=status,
|
|
fail_statuses=fail_statuses,
|
|
fail_if_final_failed=fail_if_final_failed,
|
|
)
|
|
|
|
|
|
def run_cli(
|
|
ctx: CliContext,
|
|
action: Callable[[], T | CliResult[T]],
|
|
*,
|
|
render: CliRenderer[T] | None = None,
|
|
error_guidance: Callable[[MineruError], CliGuidance | None] | None = None,
|
|
warnings: Callable[[T], list[str]] | None = None,
|
|
notices: Callable[[T], list[str]] | None = None,
|
|
exit_code: int | Callable[[T], int] = 0,
|
|
) -> None:
|
|
try:
|
|
value = action()
|
|
except typer.Exit:
|
|
raise
|
|
except Exception as exc:
|
|
guidance = _resolve_error_guidance(exc, error_guidance)
|
|
emit_error(ctx, exc, guidance=guidance)
|
|
result = _coerce_result(
|
|
value,
|
|
render=render,
|
|
warnings=warnings,
|
|
notices=notices,
|
|
exit_code=exit_code,
|
|
)
|
|
emit_result(ctx, result)
|
|
|
|
|
|
def emit_result(ctx: CliContext, result: CliResult[T]) -> None:
|
|
for notice in result.notices:
|
|
output.print_notice(notice)
|
|
for warning in result.warnings:
|
|
output.print_notice(warning)
|
|
|
|
if ctx.json_mode:
|
|
if result.data is not None:
|
|
output.print_json(result.data)
|
|
|
|
elif result.data is not None:
|
|
rendered_output: RenderableOutput | None
|
|
if result.render is not None:
|
|
rendered_output = result.render(result.data)
|
|
else:
|
|
rendered_output = str(result.data)
|
|
if rendered_output is not None:
|
|
_emit_rendered_output(rendered_output)
|
|
|
|
exit_code = _result_exit_code(result)
|
|
if exit_code:
|
|
raise typer.Exit(exit_code)
|
|
|
|
|
|
def emit_error(ctx: CliContext, exc: Exception, *, guidance: CliGuidance | None = None) -> NoReturn:
|
|
mineru_error = to_mineru_error(exc)
|
|
if ctx.json_mode:
|
|
payload = error_response(mineru_error)
|
|
if guidance is not None:
|
|
payload["guidance"] = guidance.data
|
|
output.print_json(payload)
|
|
else:
|
|
output.print_error(mineru_error.message or str(exc))
|
|
if guidance is not None:
|
|
output.print_notice(guidance.text)
|
|
raise typer.Exit(1) from None
|
|
|
|
|
|
def _emit_rendered_output(rendered_output: RenderableOutput) -> None:
|
|
if isinstance(rendered_output, str):
|
|
output.print_text(rendered_output)
|
|
return
|
|
if isinstance(rendered_output, RichObject):
|
|
output.print_rich(rendered_output)
|
|
return
|
|
for item in rendered_output:
|
|
if isinstance(item, str):
|
|
output.print_text(item)
|
|
elif isinstance(item, RichObject):
|
|
output.print_rich(item)
|
|
else:
|
|
output.print_text(item)
|
|
|
|
|
|
def to_mineru_error(exc: Exception) -> MineruError:
|
|
if isinstance(exc, MineruError):
|
|
return exc
|
|
if _is_connection_error(exc):
|
|
return ServerNotRunningError()
|
|
parsed = _parse_error_tuple(str(exc))
|
|
if parsed is not None:
|
|
code, message, param = parsed
|
|
return MineruError(code, message, param)
|
|
return MineruError("cli_internal_error", str(exc), None)
|
|
|
|
|
|
def _result_exit_code(result: CliResult[Any]) -> int:
|
|
if (
|
|
isinstance(result, CliTaskResult)
|
|
and result.fail_if_final_failed
|
|
and result.status is not None
|
|
and result.status in result.fail_statuses
|
|
):
|
|
return 1
|
|
return result.exit_code
|
|
|
|
|
|
def _coerce_result(
|
|
value: T | CliResult[T],
|
|
*,
|
|
render: CliRenderer[T] | None,
|
|
warnings: Callable[[T], list[str]] | None,
|
|
notices: Callable[[T], list[str]] | None,
|
|
exit_code: int | Callable[[T], int],
|
|
) -> CliResult[T]:
|
|
if isinstance(value, CliResult):
|
|
return value
|
|
resolved_exit_code = exit_code(value) if callable(exit_code) else exit_code
|
|
return cli_ok(
|
|
value,
|
|
render=render,
|
|
exit_code=resolved_exit_code,
|
|
warnings=warnings(value) if warnings is not None else None,
|
|
notices=notices(value) if notices is not None else None,
|
|
)
|
|
|
|
|
|
def _resolve_error_guidance(
|
|
exc: Exception,
|
|
resolver: Callable[[MineruError], CliGuidance | None] | None,
|
|
) -> CliGuidance | None:
|
|
if resolver is None:
|
|
return None
|
|
try:
|
|
return resolver(to_mineru_error(exc))
|
|
except Exception:
|
|
return None
|
|
|
|
|
|
def _parse_error_tuple(raw: str) -> tuple[str, str, str | None] | None:
|
|
try:
|
|
value = ast.literal_eval(raw)
|
|
except (SyntaxError, ValueError):
|
|
return None
|
|
if not isinstance(value, tuple) or len(value) < 2 or not isinstance(value[0], str):
|
|
return None
|
|
param = value[2] if len(value) > 2 and isinstance(value[2], str) else None
|
|
return value[0], str(value[1]), param
|
|
|
|
|
|
def _is_connection_error(exc: Exception) -> bool:
|
|
return isinstance(
|
|
exc,
|
|
(
|
|
ConnectionRefusedError,
|
|
ConnectionResetError,
|
|
ConnectionAbortedError,
|
|
httpx.ConnectError,
|
|
httpx.ConnectTimeout,
|
|
),
|
|
)
|