Files
AI/참고/guardrails-main/guardrails/cli/hub/utils.py

240 lines
7.4 KiB
Python
Raw Normal View History

2026-05-12 19:40:31 +09:00
import json
import os
import re
import subprocess
import sys
from typing import Literal
import logging
from email.parser import BytesHeaderParser
from typing import List, Union
json_format: Literal["json"] = "json"
string_format: Literal["string"] = "string"
logger = logging.getLogger(__name__)
json_format = "json"
string_format = "string"
class PipProcessError(Exception):
action: str
package: str
stderr: str = ""
stdout: str = ""
returncode: int = 1
def __init__(
self,
action: str,
package: str,
stderr: str = "",
stdout: str = "",
returncode: int = 1,
):
self.action = action
self.package = package
self.stderr = stderr
self.stdout = stdout
self.returncode = returncode
message = (
f"PipProcessError: {action} on '{package}' failed with"
"return code {returncode}.\n"
f"Stdout:\n{stdout}\n"
f"Stderr:\n{stderr}"
)
super().__init__(message)
def pip_process_with_custom_exception(
action: str,
package: str = "",
flags: List[str] = [],
format: Union[Literal["string"], Literal["json"]] = string_format,
quiet: bool = False,
no_color: bool = False,
) -> Union[str, dict]:
try:
if not quiet:
logger.debug(f"running pip {action} {' '.join(flags)} {package}")
command = [sys.executable, "-m", "pip", action]
command.extend(flags)
if package:
command.append(package)
env = dict(os.environ)
if no_color:
env["NO_COLOR"] = "true"
result = subprocess.run(
command,
env=env,
capture_output=True, # Capture both stdout and stderr
text=True, # Automatically decode to strings
check=True, # Automatically raise error on non-zero exit code
)
if format == json_format:
try:
remove_color_codes = re.compile(r"\x1b\[[0-9;]*m")
parsed_as_string = re.sub(remove_color_codes, "", result.stdout.strip())
return json.loads(parsed_as_string)
except Exception:
logger.debug(
f"JSON parse exception in decoding output from pip {action}"
f" {package}. Falling back to accumulating the byte stream",
)
accumulator = {}
parsed = BytesHeaderParser().parsebytes(result.stdout.encode())
for key, value in parsed.items():
accumulator[key] = value
return accumulator
return result.stdout
except subprocess.CalledProcessError as exc:
raise PipProcessError(action, package, exc.stderr, exc.stdout, exc.returncode)
except Exception as e:
raise PipProcessError(action, package, stderr=str(e), stdout="", returncode=1)
def installer_process(
action: str,
package: str = "",
flags: List[str] = [],
format: Union[Literal["string"], Literal["json"]] = string_format,
quiet: bool = False,
no_color: bool = False,
installer: str = "pip",
) -> Union[str, dict]:
"""Run a package install action using the specified installer (uv or pip).
Args:
action: The pip action to run (e.g., "install").
package: The package name to act on.
flags: Additional flags to pass to the installer.
format: Output format ("string" or "json").
quiet: Whether to suppress output.
no_color: Whether to disable color output.
installer: Package installer to use ("uv" or "pip").
"""
try:
if installer == "uv":
command = ["uv", "pip", action]
else:
command = [sys.executable, "-m", "pip", action]
if not quiet:
installer_label = "uv pip" if installer == "uv" else "pip"
logger.debug(
f"running {installer_label} {action} {' '.join(flags)} {package}"
)
command.extend(flags)
if package:
command.append(package)
env = dict(os.environ)
if no_color:
env["NO_COLOR"] = "true"
result = subprocess.run(
command,
env=env,
capture_output=True,
text=True,
check=True,
)
if format == json_format:
try:
remove_color_codes = re.compile(r"\x1b\[[0-9;]*m")
parsed_as_string = re.sub(remove_color_codes, "", result.stdout.strip())
return json.loads(parsed_as_string)
except Exception:
logger.debug(
f"JSON parse exception in decoding output from {action}"
f" {package}. Falling back to accumulating the byte stream",
)
accumulator = {}
parsed = BytesHeaderParser().parsebytes(result.stdout.encode())
for key, value in parsed.items():
accumulator[key] = value
return accumulator
return result.stdout
except subprocess.CalledProcessError as exc:
raise PipProcessError(
action, package, exc.stderr or "", exc.stdout or "", exc.returncode
)
except Exception as e:
raise PipProcessError(action, package, stderr=str(e), stdout="", returncode=1)
def pip_process(
action: str,
package: str = "",
flags: List[str] = [],
format: Union[Literal["string"], Literal["json"]] = string_format,
quiet: bool = False,
no_color: bool = False,
) -> Union[str, dict]:
try:
if not quiet:
logger.debug(f"running pip {action} {' '.join(flags)} {package}")
command = [sys.executable, "-m", "pip", action]
command.extend(flags)
if package:
command.append(package)
env = dict(os.environ)
if no_color:
env["NO_COLOR"] = "true"
result = subprocess.run(
command,
env=env,
capture_output=True, # Capture both stdout and stderr
text=True, # Automatically decode to strings
check=True, # Automatically raise error on non-zero exit code
)
if format == json_format:
try:
remove_color_codes = re.compile(r"\x1b\[[0-9;]*m")
parsed_as_string = re.sub(remove_color_codes, "", result.stdout.strip())
return json.loads(parsed_as_string)
except Exception:
logger.debug(
f"JSON parse exception in decoding output from pip {action}"
f" {package}. Falling back to accumulating the byte stream",
)
accumulator = {}
parsed = BytesHeaderParser().parsebytes(result.stdout.encode())
for key, value in parsed.items():
accumulator[key] = value
return accumulator
return result.stdout
except subprocess.CalledProcessError as exc:
logger.error(
(
f"Failed to {action} {package}\n"
f"Exit code: {exc.returncode}\n"
f"stderr: {(exc.stderr or '').strip()}\n"
f"stdout: {(exc.stdout or '').strip()}"
)
)
sys.exit(1)
except Exception as e:
logger.error(
f"An unexpected exception occurred while trying to {action} {package}!",
e,
)
sys.exit(1)