참고소스 수정본
This commit is contained in:
6
참고/guardrails-main/guardrails/cli/hub/__init__.py
Normal file
6
참고/guardrails-main/guardrails/cli/hub/__init__.py
Normal file
@@ -0,0 +1,6 @@
|
||||
import guardrails.cli.hub.create_validator # noqa
|
||||
import guardrails.cli.hub.install # noqa
|
||||
import guardrails.cli.hub.uninstall # noqa
|
||||
import guardrails.cli.hub.submit # noqa
|
||||
import guardrails.cli.hub.list # noqa
|
||||
from guardrails.cli.hub.hub import hub_command # noqa
|
||||
3
참고/guardrails-main/guardrails/cli/hub/console.py
Normal file
3
참고/guardrails-main/guardrails/cli/hub/console.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from rich.console import Console
|
||||
|
||||
console = Console()
|
||||
205
참고/guardrails-main/guardrails/cli/hub/create_validator.py
Normal file
205
참고/guardrails-main/guardrails/cli/hub/create_validator.py
Normal file
@@ -0,0 +1,205 @@
|
||||
# ruff: noqa: E501
|
||||
import os
|
||||
from datetime import date
|
||||
from string import Template
|
||||
|
||||
import typer
|
||||
from pydash import pascal_case, snake_case
|
||||
|
||||
from guardrails.cli.hub.hub import hub_command
|
||||
from guardrails.cli.logger import LEVELS, logger
|
||||
from guardrails.hub_telemetry.hub_tracing import trace
|
||||
|
||||
validator_template = Template(
|
||||
"""
|
||||
\"""
|
||||
This template is intended for creating simple validators.
|
||||
|
||||
If your validator is complex or requires additional post-installation steps, consider using the template repository instead.
|
||||
|
||||
The template repository can be found here: https://github.com/guardrails-ai/validator-template
|
||||
\"""
|
||||
|
||||
from typing import Any, Callable, Dict, Optional
|
||||
|
||||
from guardrails.validator_base import (
|
||||
FailResult,
|
||||
PassResult,
|
||||
ValidationResult,
|
||||
Validator,
|
||||
register_validator,
|
||||
)
|
||||
|
||||
|
||||
@register_validator(name="guardrails/${package_name}", data_type="string")
|
||||
class ${class_name}(Validator):
|
||||
# FIXME: Update the class docstring to reflect the purpose and usage of your validator.
|
||||
\"""# Overview
|
||||
|
||||
| Developed by | {FIXME: Your organization name} |
|
||||
| Date of development | ${dev_date} |
|
||||
| Validator type | Format |
|
||||
| License | Apache 2 |
|
||||
| Input/Output | Output |
|
||||
|
||||
# Description
|
||||
{FIXME: A brief description of what your validator does.}
|
||||
|
||||
## (Optional) Intended Use
|
||||
{FIXME: Optionally, include a brief description of the intended use of your validator, including any limitations or constraints.}
|
||||
|
||||
## Requirements
|
||||
|
||||
* Dependencies:
|
||||
- guardrails-ai>=0.4.0
|
||||
- {FIXME: Include any other dependencies you need here}
|
||||
|
||||
* Dev Dependencies:
|
||||
- pytest
|
||||
- pyright
|
||||
- ruff
|
||||
- {FIXME: Include any other dev dependencies you need here}
|
||||
|
||||
* Foundation model access keys:
|
||||
- {FIXME: Include any access environment variables you need here like OPENAI_API_KEY}
|
||||
|
||||
|
||||
# Installation
|
||||
|
||||
```bash
|
||||
$ guardrails hub install hub://guardrails/${package_name}
|
||||
```
|
||||
|
||||
# Usage Examples
|
||||
|
||||
## Validating string output via Python
|
||||
|
||||
In this example, we apply the validator to a string output generated by an LLM.
|
||||
|
||||
```python
|
||||
# Import Guard and Validator
|
||||
from guardrails.hub import ${class_name}
|
||||
from guardrails import Guard
|
||||
|
||||
# Setup Guard
|
||||
guard = Guard.use(
|
||||
${class_name}({FIXME: list any args here})
|
||||
)
|
||||
|
||||
guard.validate({FIXME: Add an input that should pass the validator}) # Validator passes
|
||||
guard.validate({FIXME: Add an input that should fail the validator}) # Validator fails
|
||||
```
|
||||
\""" # noqa
|
||||
|
||||
# If you don't have any init args, you can omit the __init__ method.
|
||||
def __init__(
|
||||
self,
|
||||
arg_1: str, # FIXME: Replace with your custom init args.
|
||||
on_fail: Optional[Callable] = None,
|
||||
):
|
||||
\"""Initializes a new instance of the ${class_name} class.
|
||||
|
||||
Args:
|
||||
arg_1 (str): FIXME: Describe the purpose of this argument.
|
||||
on_fail`** *(str, Callable)*: The policy to enact when a validator fails. If `str`, must be one of `reask`, `fix`, `filter`, `refrain`, `noop`, `exception` or `fix_reask`. Otherwise, must be a function that is called when the validator fails.
|
||||
\"""
|
||||
super().__init__(on_fail=on_fail, arg_1=arg_1)
|
||||
self._arg_1 = arg_1
|
||||
|
||||
def validate(self, value: Any, metadata: Dict) -> ValidationResult:
|
||||
\"""Validates that {fill in how you validator interacts with the passed value}.
|
||||
|
||||
Args:
|
||||
value (Any): The value to validate.
|
||||
metadata (Dict): The metadata to validate against.
|
||||
|
||||
FIXME: Add any additional args you need here in metadata.
|
||||
| Key | Description |
|
||||
| --- | --- |
|
||||
| a | b |
|
||||
\"""
|
||||
|
||||
# Add your custom validator logic here and return a PassResult or FailResult accordingly.
|
||||
if value != "pass": # FIXME
|
||||
return FailResult(
|
||||
error_message="{FIXME: A descriptive but concise error message about why validation failed}",
|
||||
fix_value="{FIXME: The programmtic fix if applicable, otherwise remove this kwarg.}",
|
||||
)
|
||||
return PassResult()
|
||||
|
||||
|
||||
# Run tests via `pytest -rP ${filepath}`
|
||||
class Test${class_name}:
|
||||
def test_success_case(self):
|
||||
# FIXME: Replace with your custom test logic for the success case.
|
||||
validator = ${class_name}("s")
|
||||
result = validator.validate("pass", {})
|
||||
assert isinstance(result, PassResult) is True
|
||||
|
||||
def test_failure_case(self):
|
||||
# FIXME: Replace with your custom test logic for the failure case.
|
||||
validator = ${class_name}("s")
|
||||
result = validator.validate("fail", {})
|
||||
assert isinstance(result, FailResult) is True
|
||||
assert result.error_message == "{A descriptive but concise error message about why validation failed}"
|
||||
assert result.fix_value == "fails"
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
@hub_command.command(name="create-validator")
|
||||
@trace(name="guardrails-cli/hub/create-validator")
|
||||
def create_validator(
|
||||
name: str = typer.Argument(help="The name for your validator."),
|
||||
filepath: str = typer.Argument(
|
||||
help="The location to write your validator template to",
|
||||
default="./{validator_name}.py",
|
||||
),
|
||||
):
|
||||
"""Lightweight method for creating simple validators.
|
||||
|
||||
For more complex submissions see here:
|
||||
https://github.com/guardrails-ai/validator-template?tab=readme-ov-file#how-to-create-a-guardrails-validator
|
||||
"""
|
||||
disclaimer = """
|
||||
|
||||
This utility is intended for creating simple validators.
|
||||
|
||||
If your validator is complex or requires additional post-installation steps,\
|
||||
consider using the template repository instead.
|
||||
|
||||
The template repository can be found here:\
|
||||
https://github.com/guardrails-ai/validator-template
|
||||
"""
|
||||
logger.log(level=LEVELS.get("NOTICE") or 0, msg=disclaimer)
|
||||
|
||||
package_name = snake_case(name)
|
||||
class_name = pascal_case(name)
|
||||
if not filepath or filepath == "./{validator_name}.py":
|
||||
filepath = f"./{package_name}.py"
|
||||
|
||||
template = validator_template.safe_substitute(
|
||||
{
|
||||
"package_name": package_name,
|
||||
"class_name": class_name,
|
||||
"filepath": filepath,
|
||||
"dev_date": date.today().strftime("%b %d, %Y"),
|
||||
}
|
||||
)
|
||||
|
||||
target = os.path.abspath(filepath)
|
||||
with open(target, "w") as validator_file:
|
||||
validator_file.write(template)
|
||||
validator_file.close()
|
||||
|
||||
success_message = Template(
|
||||
"""
|
||||
|
||||
Successfully created validator template at ${filepath}!
|
||||
|
||||
Make any necessary changes then submit for review with the following command:
|
||||
|
||||
guardrails hub submit ${package_name} ${filepath}
|
||||
"""
|
||||
).safe_substitute({"filepath": filepath, "package_name": package_name})
|
||||
logger.log(level=LEVELS.get("SUCCESS"), msg=success_message) # type: ignore
|
||||
3
참고/guardrails-main/guardrails/cli/hub/hub.py
Normal file
3
참고/guardrails-main/guardrails/cli/hub/hub.py
Normal file
@@ -0,0 +1,3 @@
|
||||
import typer
|
||||
|
||||
hub_command = typer.Typer()
|
||||
72
참고/guardrails-main/guardrails/cli/hub/install.py
Normal file
72
참고/guardrails-main/guardrails/cli/hub/install.py
Normal file
@@ -0,0 +1,72 @@
|
||||
import sys
|
||||
from typing import Optional, List
|
||||
|
||||
import typer
|
||||
|
||||
from guardrails.cli.hub.hub import hub_command
|
||||
from guardrails.cli.logger import logger
|
||||
from guardrails.hub_telemetry.hub_tracing import trace
|
||||
from guardrails.cli.hub.console import console
|
||||
from guardrails.cli.version import version_warnings_if_applicable
|
||||
|
||||
|
||||
# Quick note: This is the command for `guardrails hub install`. We change the name of
|
||||
# the function def to prevent confusion, lest people import it directly and calling it
|
||||
# with a string for package_uris instead of a list, which behaves oddly. If you need to
|
||||
# call install from a script, please consider importing install from guardrails,
|
||||
# not guardrails.cli.hub.install.
|
||||
@hub_command.command(name="install")
|
||||
@trace(name="guardrails-cli/hub/install")
|
||||
def install_cli(
|
||||
package_uris: List[str] = typer.Argument(
|
||||
...,
|
||||
help="URIs to the packages to install. Example: hub://guardrails/regex_match hub://guardrails/toxic_language",
|
||||
),
|
||||
local_models: Optional[bool] = typer.Option(
|
||||
None,
|
||||
"--install-local-models/--no-install-local-models",
|
||||
help="Install local models",
|
||||
),
|
||||
quiet: bool = typer.Option(
|
||||
False,
|
||||
"-q",
|
||||
"--quiet",
|
||||
help="Run the command in quiet mode to reduce output verbosity.",
|
||||
),
|
||||
upgrade: bool = typer.Option(
|
||||
False, "--upgrade", help="Upgrade the package to the latest version."
|
||||
),
|
||||
):
|
||||
try:
|
||||
if isinstance(package_uris, str):
|
||||
logger.error(
|
||||
f"`install` in {__file__} was called with a string instead of "
|
||||
"a list! This can happen if it is invoked directly instead of "
|
||||
"being run via the CLI. Did you mean to import `from guardrails import "
|
||||
"install` instead? Recovering..."
|
||||
)
|
||||
package_uris = [
|
||||
package_uris,
|
||||
]
|
||||
|
||||
from guardrails.hub.install import install_multiple
|
||||
|
||||
def confirm():
|
||||
return typer.confirm(
|
||||
"This validator has a Guardrails AI inference endpoint available. "
|
||||
"Would you still like to install the"
|
||||
" local models for local inference?",
|
||||
)
|
||||
|
||||
version_warnings_if_applicable(console)
|
||||
|
||||
install_multiple(
|
||||
package_uris,
|
||||
install_local_models=local_models,
|
||||
quiet=quiet,
|
||||
upgrade=upgrade,
|
||||
install_local_models_confirm=confirm,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(str(e))
|
||||
sys.exit(1)
|
||||
21
참고/guardrails-main/guardrails/cli/hub/list.py
Normal file
21
참고/guardrails-main/guardrails/cli/hub/list.py
Normal file
@@ -0,0 +1,21 @@
|
||||
from guardrails.cli.hub.hub import hub_command
|
||||
from guardrails.hub.registry import get_registry
|
||||
from guardrails.hub_telemetry.hub_tracing import trace
|
||||
from .console import console
|
||||
|
||||
|
||||
@hub_command.command(name="list")
|
||||
@trace(name="guardrails-cli/hub/list")
|
||||
def list():
|
||||
"""List all installed validators."""
|
||||
registry = get_registry()
|
||||
|
||||
validators = registry.validators
|
||||
if not validators:
|
||||
console.print("No validators installed.")
|
||||
return
|
||||
|
||||
console.print("Installed Validators:")
|
||||
for validator_id, entry in sorted(validators.items()):
|
||||
exports = ", ".join(entry.exports)
|
||||
console.print(f"- {validator_id} ({exports})")
|
||||
55
참고/guardrails-main/guardrails/cli/hub/submit.py
Normal file
55
참고/guardrails-main/guardrails/cli/hub/submit.py
Normal file
@@ -0,0 +1,55 @@
|
||||
import os
|
||||
import sys
|
||||
from string import Template
|
||||
|
||||
import typer
|
||||
from pydash import snake_case
|
||||
|
||||
from guardrails.cli.hub.hub import hub_command
|
||||
from guardrails.cli.logger import LEVELS, logger
|
||||
from guardrails.cli.server.hub_client import HttpError, post_validator_submit
|
||||
from guardrails.hub_telemetry.hub_tracing import trace
|
||||
|
||||
|
||||
@hub_command.command(name="submit")
|
||||
@trace(name="guardrails-cli/hub/submit")
|
||||
def submit(
|
||||
package_name: str = typer.Argument(help="The package name for your validator."),
|
||||
filepath: str = typer.Argument(
|
||||
help="The location to your validator file.", default="./{package_name}.py"
|
||||
),
|
||||
):
|
||||
"""Submit a validator to the Guardrails AI team for review and
|
||||
publishing."""
|
||||
try:
|
||||
if not filepath or filepath == "./{package_name}.py":
|
||||
filepath = f"./{package_name}.py"
|
||||
|
||||
target = os.path.abspath(filepath)
|
||||
with open(target, "r") as validator_file:
|
||||
content = validator_file.read()
|
||||
|
||||
post_validator_submit(package_name, content)
|
||||
|
||||
validator_file.close()
|
||||
|
||||
success_message = Template(
|
||||
"""
|
||||
|
||||
Successfully submitted validator!
|
||||
|
||||
Once your submission is reviewed and published you will be able to install it via:
|
||||
|
||||
guardrails hub install hub://guardrails/${package_name}
|
||||
|
||||
The Guardrails AI team will be in touch with you soon regarding the status of your submission.
|
||||
""" # noqa
|
||||
).safe_substitute({"package_name": snake_case(package_name)})
|
||||
logger.log(level=LEVELS.get("SUCCESS"), msg=success_message) # type: ignore
|
||||
|
||||
except HttpError:
|
||||
logger.error(f"Failed to submit {package_name}!")
|
||||
sys.exit(1)
|
||||
except Exception as e:
|
||||
logger.error("An unexpected error occurred!", e)
|
||||
sys.exit(1)
|
||||
27
참고/guardrails-main/guardrails/cli/hub/template.py
Normal file
27
참고/guardrails-main/guardrails/cli/hub/template.py
Normal file
@@ -0,0 +1,27 @@
|
||||
import json
|
||||
import os
|
||||
|
||||
from guardrails.cli.server.hub_client import get_guard_template
|
||||
|
||||
|
||||
def get_template(template_name: str) -> tuple[dict, str]:
|
||||
# if template ends in .json load file from disk relative to the execution directory
|
||||
if template_name.endswith(".json"):
|
||||
template_file_name = template_name
|
||||
try:
|
||||
file_path = os.path.join(os.getcwd(), template_name)
|
||||
with open(file_path, "r") as fin:
|
||||
return json.load(fin), template_file_name
|
||||
except FileNotFoundError:
|
||||
raise FileNotFoundError(f"Template file {template_name} not found.")
|
||||
|
||||
template_file_name = f"{template_name.split('/')[-1]}.json"
|
||||
|
||||
template = get_guard_template(template_name)
|
||||
|
||||
# write template to file
|
||||
out_path = os.path.join(os.getcwd(), template_file_name)
|
||||
with open(out_path, "wt") as file_out:
|
||||
file_out.write(json.dumps(template, indent=4))
|
||||
|
||||
return template, template_file_name
|
||||
@@ -0,0 +1,15 @@
|
||||
import json
|
||||
import os
|
||||
from guardrails import Guard
|
||||
from guardrails.hub import {VALIDATOR_IMPORTS}
|
||||
|
||||
try:
|
||||
file_path = os.path.join(os.getcwd(), "{TEMPLATE_FILE_NAME}")
|
||||
with open(file_path, "r") as fin:
|
||||
guards = json.load(fin)["guards"] or []
|
||||
except json.JSONDecodeError:
|
||||
print("Error parsing guards from JSON")
|
||||
SystemExit(1)
|
||||
|
||||
# instantiate guards
|
||||
{GUARD_INSTANTIATIONS}
|
||||
92
참고/guardrails-main/guardrails/cli/hub/uninstall.py
Normal file
92
참고/guardrails-main/guardrails/cli/hub/uninstall.py
Normal file
@@ -0,0 +1,92 @@
|
||||
import os
|
||||
import sys
|
||||
from typing import List, Literal
|
||||
|
||||
import typer
|
||||
|
||||
from guardrails.cli.hub.hub import hub_command
|
||||
from guardrails.cli.logger import LEVELS, logger
|
||||
from guardrails.cli.server.hub_client import get_validator_manifest
|
||||
from guardrails_hub_types import Manifest
|
||||
|
||||
from guardrails.cli.hub.utils import pip_process
|
||||
from guardrails.hub_telemetry.hub_tracing import trace
|
||||
|
||||
from .console import console
|
||||
|
||||
json_format: Literal["json"] = "json"
|
||||
string_format: Literal["string"] = "string"
|
||||
|
||||
|
||||
def remove_line(file_path: str, line_content: str):
|
||||
with open(file_path, "r+") as file:
|
||||
lines = file.readlines()
|
||||
file.seek(0)
|
||||
lines = [line for line in lines if line.strip() != line_content.strip()]
|
||||
file.writelines(lines)
|
||||
file.truncate()
|
||||
return lines
|
||||
|
||||
|
||||
def remove_from_hub_inits(manifest: Manifest, site_packages: str):
|
||||
from guardrails.hub.validator_package_service import ValidatorPackageService
|
||||
|
||||
exports: List[str] = manifest.exports or []
|
||||
sorted_exports = sorted(exports, reverse=True)
|
||||
|
||||
validator_id = manifest.id
|
||||
import_path = ValidatorPackageService.get_import_path_from_validator_id(
|
||||
validator_id
|
||||
)
|
||||
import_line = f"from {import_path} import {', '.join(sorted_exports)}"
|
||||
|
||||
# Remove import line from main __init__.py
|
||||
hub_init_location = os.path.join(site_packages, "guardrails", "hub", "__init__.py")
|
||||
remove_line(hub_init_location, import_line)
|
||||
|
||||
|
||||
def uninstall_hub_module(manifest: Manifest):
|
||||
from guardrails.hub.validator_package_service import ValidatorPackageService
|
||||
|
||||
validator_id = manifest.id
|
||||
package_name = ValidatorPackageService.get_normalized_package_name(validator_id)
|
||||
pip_process("uninstall", package_name, flags=["-y"], quiet=True)
|
||||
|
||||
|
||||
@hub_command.command()
|
||||
@trace(name="guardrails-cli/hub/uninstall")
|
||||
def uninstall(
|
||||
package_uri: str = typer.Argument(
|
||||
help="URI to the package to uninstall. Example: hub://guardrails/regex_match."
|
||||
),
|
||||
):
|
||||
"""Uninstall a validator from the Hub."""
|
||||
from guardrails.hub.validator_package_service import ValidatorPackageService
|
||||
|
||||
if not package_uri.startswith("hub://"):
|
||||
logger.error("Invalid URI!")
|
||||
sys.exit(1)
|
||||
|
||||
console.print(f"\nUninstalling {package_uri}...\n")
|
||||
logger.log(
|
||||
level=LEVELS.get("SPAM", 0),
|
||||
msg=f"Uninstalling {package_uri}...",
|
||||
)
|
||||
|
||||
# Validation
|
||||
module_name = package_uri.replace("hub://", "")
|
||||
|
||||
# Prep
|
||||
with console.status("Fetching manifest", spinner="bouncingBar"):
|
||||
module_manifest = get_validator_manifest(module_name)
|
||||
|
||||
# Uninstall
|
||||
with console.status("Removing module", spinner="bouncingBar"):
|
||||
uninstall_hub_module(module_manifest)
|
||||
|
||||
# Cleanup
|
||||
with console.status("Cleaning up", spinner="bouncingBar"):
|
||||
ValidatorPackageService.unregister_validator(module_name)
|
||||
|
||||
console.print("✅ Successfully uninstalled!") # type: ignore
|
||||
logger.log(level=LEVELS.get("SPAM"), msg="✅ Successfully uninstalled!") # type: ignore
|
||||
239
참고/guardrails-main/guardrails/cli/hub/utils.py
Normal file
239
참고/guardrails-main/guardrails/cli/hub/utils.py
Normal file
@@ -0,0 +1,239 @@
|
||||
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)
|
||||
Reference in New Issue
Block a user