Files
AI/참고/guardrails-main/guardrails/cli/hub/utils.py
2026-05-12 19:40:31 +09:00

240 lines
7.4 KiB
Python

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)