diff --git a/docs/concepts/remote_validation_inference.ipynb b/docs/concepts/remote_validation_inference.ipynb
index 8c4d5531b..4595c17fd 100644
--- a/docs/concepts/remote_validation_inference.ipynb
+++ b/docs/concepts/remote_validation_inference.ipynb
@@ -19,6 +19,10 @@
"\n",
":::note\n",
"Remote validation inferencing is only available in Guardrails versions 0.5.0 and above.\n",
+ ":::\n",
+ "\n",
+ "::note\n",
+ "To enable/disable, you can run the cli command `guardrails configure` or modify your `~/.guardrailsrc`. For example, to disable remote inference use: `use_remote_inferencing=false`.\n",
":::"
]
},
@@ -147,7 +151,7 @@
],
"metadata": {
"kernelspec": {
- "display_name": ".venv",
+ "display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
@@ -161,9 +165,9 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
- "version": "3.11.7"
+ "version": "3.11.9"
}
},
"nbformat": 4,
- "nbformat_minor": 2
+ "nbformat_minor": 4
}
diff --git a/docs/how_to_guides/continuous_integration_continuous_deployment.md b/docs/how_to_guides/continuous_integration_continuous_deployment.md
index 9530dd8eb..344f9c828 100644
--- a/docs/how_to_guides/continuous_integration_continuous_deployment.md
+++ b/docs/how_to_guides/continuous_integration_continuous_deployment.md
@@ -255,7 +255,7 @@ terraform apply -var="aws_region=us-east-1" -var="backend_memory=2048" -var="bac
Once the deployment has succeeded you should see some output values (which will be required if you wish to set up CI).
-## Step4: Deploying Guardrails API
+## Step 4: Deploying Guardrails API
### Manual
Firstly, create or use your existing guardrails token and export it to your current shell `export GUARDRAILS_TOKEN="..."`
@@ -444,7 +444,67 @@ By setting the above environment variable `GUARDRAILS_BASE_URL` the SDK will be
## Quick Start Repository Template
-We've conveniently packaged all the artifacts from this document in a github repository that can be used as a template for your own verification and deployment [here](https://github.com/guardrails-ai/continuous_integration_and_deployment_aws_template).
+We've conveniently packaged all the artifacts from this document in a github repository that can be used as a template for your own verification and deployment [here](https://github.com/guardrails-ai/continuous_integration_and_deployment_aws_template).
+
+## Diagram
+
+```mermaid
+graph TD
+ %% Internet and IGW
+ Internet((Internet)) --> IGW[Internet Gateway]
+ IGW --> RouteTable[Public Route Table]
+
+ %% VPC Container
+ VPC[VPC
10.0.0.0/16] --> IGW
+
+ %% IAM Permissions Group
+ subgraph ECSIAMPermissions["ECS IAM Permissions"]
+ ExecutionRole[ECS Execution Role]
+ TaskRole[ECS Task Role]
+ end
+
+ %% Public Subnet Group
+ subgraph PublicSubnets["Public Subnets x3"]
+ NLB[Network Load Balancer]
+ TG[Target Group
TCP:80]
+ SG[Security Group
Ingress: 8000
Egress: All]
+ ECSCluster[ECS Cluster]
+
+ %% ECS Components
+ ECSService[ECS Service]
+ TaskDef[Task Definition
CPU: 1024
Memory: 2048]
+ Container[Container
Port: 8000]
+
+ %% Internal Connections
+ NLB --> |Port 80| TG
+ TG --> ECSService
+ ECSCluster --> ECSService
+ SG --> ECSService
+ ECSService --> TaskDef
+ TaskDef --> Container
+ end
+
+ %% IAM Connections
+ ECSIAMPermissions --> TaskDef
+
+ %% Route Table Connection
+ RouteTable --> PublicSubnets
+
+ %% External Service Connections
+ CloudWatchLogs[CloudWatch Log Group] --> Container
+ ECR[ECR Repository] --> |Image| Container
+
+ %% Internet Access
+ Internet --> NLB
+
+ %% Styling
+ classDef aws fill:#FF9900,stroke:#232F3E,stroke-width:2px,color:black
+ classDef subnet fill:#FFD700,stroke:#232F3E,stroke-width:2px,color:black
+ classDef iam fill:#FF6B6B,stroke:#232F3E,stroke-width:2px,color:black
+ class VPC,IGW,NATGateway,RouteTable,NLB,TG,ECSCluster,ECSService,TaskDef,Container,SG,CloudWatchLogs,ECR aws
+ class PublicSubnets subnet
+ class ECSIAMPermissions,ExecutionRole,TaskRole iam
+```
## Terraform
diff --git a/docs/migration_guides/0-6-alpha-migration.md b/docs/migration_guides/0-6-alpha-migration.md
new file mode 100644
index 000000000..a4eac4065
--- /dev/null
+++ b/docs/migration_guides/0-6-alpha-migration.md
@@ -0,0 +1,133 @@
+# Migrating to 0.6.0-alpha
+
+## Summary
+This guide will help you migrate your codebase from 0.5.X to 0.6.0-alpha.
+
+## Installation
+```bash
+pip install --pre guardrails-ai
+```
+
+## List of backwards incompatible changes
+
+1. All validators will require authentication with a guardrails token. This will also apply to all versions >=0.4.x of the guardrails-ai package.
+1. prompt, msg_history, instructions, reask_messages, reask_instructions, and will be removed from the `Guard.__call__` function and will instead be supported by a single messages argument, and a reask_messages argument for reasks.
+1. Custom callables now need to expect `messages`, as opposed to `prompt` to come in as a keyword argument.
+1. The default on_fail action for validators will change from noop to exception.
+1. In cases where we try and generate structured data, Guardrails will no longer automatically attempt to try and coerce the LLM into giving correctly formatted information.
+1. Guardrails will no longer automatically set a tool selection in the OpenAI callable when initialized using pydantic for initial prompts or reasks
+1. Guard.from_string is being removed in favor of Guard()
+1. Guard.from_pydantic is renamed to Guard.for_pydantic
+1. Guard.from_rail is renamed to Guard.for_rail
+1. Guard.from_rail_string is renamed to guard.for_rail_string
+1. The guardrails server will change from using Flask to FastAPI. We recommend serving uvicorn runners via a gunicorn WSGI.
+1. OpenAI, Cohere and Anthropic **callables are being removed in favor of support through passing no callable and setting the appropriate api key and model argument.
+
+
+## How to migrate
+
+### Messages support for reask and RAILS
+`Guard.__call` and rails now fully support `reask_messages` as an argument.
+
+### For `Guard.__call`
+```py
+response = guard(
+ messages=[{
+ "role":"user",
+ "content":"tell me a joke"
+ }],
+ reask_messages=[{
+ "role":"system"
+ "content":"your previous joke failed validation can you tell me another joke?"
+ }]
+)
+```
+
+#### For Rail
+```
+
+
+
+ Given the following document, answer the following questions. If the answer doesn't exist in the document, enter 'None'.
+ ${document}
+ ${gr.xml_prefix_prompt}
+
+
+ ${question}
+
+
+
+
+ You were asked ${question} and it was not correct can you try again?
+
+
+
+```
+
+## Improvements
+
+### Per message validation and fix support
+Message validation is now more granular and executed on a per message content basis.
+This allows for on_fix behavior to be fully supported.
+
+## Backwards-incompatible changes
+### Validator onFail default behavior is now exception
+Previously the default behavior for validation failure was noop. This meant developers were required to set it on_fail to exception or check validation_failed
+to know if validation failed. This was unintuitive for new users and led to confusion around if validators were working or not. This new behavior will require
+exception handling be added or configurations manually to be set to noop if desired.
+
+### Simplified schema injection behavior
+Previously prompt and instruction suffixes and formatting hints were sometimes automatically injected
+into prompt and instructions if guardrails detected xml or a structured schema being used for output.
+This caused confusion and unexpected behavior when arguments to llms were being mutated without a developer asking for it.
+Developers will now need to intentionally include Guardrails template variables such as `${gr.complete_xml_suffix_v2}`
+
+### Guardrails Server migration from Flask to FastAPI
+In 0.5.X guard.__call and guard.validate async streaming received support for on_fail="fix" merge and parallelized async validation.
+We have updated the server to use FastAPI which is build on ASGI to be able to fully take advantage of these improvements.
+config.py now fully supports the definition of AsyncGuards and streaming=true as a request argument.
+We recommend the combination of `gunicorn` and `uvicorn.workers.UvicornWorker`s
+
+### Streamlined prompt, instructions and msg_history arguments into messages
+`Guard.__call` prompt, instruction, reask_prompt and reask_instruction arguments have been streamlined into messages and reask_messages
+Instructions should be specified with the role system and prompts with the role user. Any of the out of the box supported llms that require only one text prompt will automatically have the messages converted to one unless a custom callable is being used.
+```
+# version < 0.6.0
+guard(
+ instructions="you are a funny assistant",
+ prompt="tell me a joke"
+)
+# version >= 0.6.0
+guard(
+ messages=[
+ {"role":"system", "content":"you are a funny assistant"},
+ {"role":"user", "content":"tell me a joke"}
+ ]
+)
+```
+
+### Removal of guardrails OpenAI, Cohere, Anthropic Callables
+These callables are being removed in favor of support through passing no callable and setting the appropriate api key and model argument.
+
+### Prompt no longer a required positional argument on custom callables
+Custom callables will no longer throw an error if the prompt arg is missing in their declaration and guardrails will no longer pass prompt as the first argument. They need to be updated to the messages kwarg to get text input. If a custom callables underlying llm only accepts a single string a helper exists that can compose messages into one otherwise some code to adapt them will be required.
+
+```py
+from guardrails import messages_to_prompt_string
+
+class CustomCallableCallable(PromptCallableBase):
+ def llm_api(
+ self,
+ *args,
+ **kwargs,
+ ) -> str:
+ messages = kwargs.pop("messages", [])
+ prompt = messages_to_prompt_string(messages)
+
+ llm_string_output = some_llm_call_requiring_prompt(
+ prompt,
+ *args,
+ **kwargs,
+ )
+ return llm_string_output
+```
\ No newline at end of file
diff --git a/docusaurus/docusaurus.config.js b/docusaurus/docusaurus.config.js
index eff83f6eb..743e28a25 100644
--- a/docusaurus/docusaurus.config.js
+++ b/docusaurus/docusaurus.config.js
@@ -29,7 +29,7 @@ const config = {
markdown: {
mermaid: true
},
- // themes: ['@docusaurus/theme-mermaid', '@docusaurus/theme-classic'],
+ themes: ['@docusaurus/theme-mermaid'],
// Even if you don't use internalization, you can use this field to set useful
// metadata like html lang. For example, if your site is Chinese, you may want
// to replace "en" with "zh-Hans".
diff --git a/guardrails/cli/hub/list.py b/guardrails/cli/hub/list.py
index 2dcf0cd3d..8f18dff92 100644
--- a/guardrails/cli/hub/list.py
+++ b/guardrails/cli/hub/list.py
@@ -2,7 +2,6 @@
import re
from guardrails.cli.hub.hub import hub_command
-from guardrails.cli.hub.utils import get_site_packages_location
from guardrails.hub_telemetry.hub_tracing import trace
from .console import console
@@ -11,7 +10,9 @@
@trace(name="guardrails-cli/hub/list")
def list():
"""List all installed validators."""
- site_packages = get_site_packages_location()
+ from guardrails.hub.validator_package_service import ValidatorPackageService
+
+ site_packages = ValidatorPackageService.get_site_packages_location()
hub_init_file = os.path.join(site_packages, "guardrails", "hub", "__init__.py")
installed_validators = []
diff --git a/guardrails/cli/hub/uninstall.py b/guardrails/cli/hub/uninstall.py
index bd3722701..5fdcd5755 100644
--- a/guardrails/cli/hub/uninstall.py
+++ b/guardrails/cli/hub/uninstall.py
@@ -1,5 +1,4 @@
import os
-import shutil
import sys
from typing import List, Literal
@@ -10,9 +9,7 @@
from guardrails.cli.server.hub_client import get_validator_manifest
from guardrails_hub_types import Manifest
-from guardrails.cli.hub.utils import get_site_packages_location
-from guardrails.cli.hub.utils import get_org_and_package_dirs
-from guardrails.cli.hub.utils import get_hub_directory
+from guardrails.cli.hub.utils import pip_process
from guardrails.hub_telemetry.hub_tracing import trace
from .console import console
@@ -32,46 +29,28 @@ def remove_line(file_path: str, line_content: str):
def remove_from_hub_inits(manifest: Manifest, site_packages: str):
- org_package = get_org_and_package_dirs(manifest)
+ from guardrails.hub.validator_package_service import ValidatorPackageService
+
exports: List[str] = manifest.exports or []
sorted_exports = sorted(exports, reverse=True)
- module_name = manifest.module_name
- relative_path = ".".join([*org_package, module_name])
- import_line = (
- f"from guardrails.hub.{relative_path} import {', '.join(sorted_exports)}"
+
+ 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)
- # Remove import line from namespace __init__.py
- namespace = org_package[0]
- namespace_init_location = os.path.join(
- site_packages, "guardrails", "hub", namespace, "__init__.py"
- )
- lines = remove_line(namespace_init_location, import_line)
-
- # remove namespace pkg if namespace __init__.py is empty
- if (len(lines) == 0) and (namespace != "hub"):
- logger.info(f"Removing namespace package {namespace} as it is now empty")
- try:
- shutil.rmtree(os.path.join(site_packages, "guardrails", "hub", namespace))
- except Exception as e:
- logger.error(f"Error removing namespace package {namespace}")
- logger.error(e)
- sys.exit(1)
-
-
-def uninstall_hub_module(manifest: Manifest, site_packages: str):
- uninstall_directory = get_hub_directory(manifest, site_packages)
- logger.info(f"Removing directory {uninstall_directory}")
- try:
- shutil.rmtree(uninstall_directory)
- except Exception as e:
- logger.error("Error removing directory")
- logger.error(e)
- sys.exit(1)
+
+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()
@@ -82,6 +61,8 @@ def uninstall(
),
):
"""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)
@@ -98,11 +79,11 @@ def uninstall(
# Prep
with console.status("Fetching manifest", spinner="bouncingBar"):
module_manifest = get_validator_manifest(module_name)
- site_packages = get_site_packages_location()
+ site_packages = ValidatorPackageService.get_site_packages_location()
# Uninstall
with console.status("Removing module", spinner="bouncingBar"):
- uninstall_hub_module(module_manifest, site_packages)
+ uninstall_hub_module(module_manifest)
# Cleanup
with console.status("Cleaning up", spinner="bouncingBar"):
diff --git a/guardrails/cli/hub/utils.py b/guardrails/cli/hub/utils.py
index 8d7220801..c3f1b924a 100644
--- a/guardrails/cli/hub/utils.py
+++ b/guardrails/cli/hub/utils.py
@@ -3,17 +3,22 @@
import re
import subprocess
import sys
-from email.parser import BytesHeaderParser
-from typing import List, Literal, Union
-from pydash.strings import snake_case
-from guardrails_hub_types import Manifest
-from guardrails.cli.logger import logger
+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"
+
def pip_process(
action: str,
@@ -34,63 +39,46 @@ def pip_process(
env = dict(os.environ)
if no_color:
env["NO_COLOR"] = "true"
- if not quiet:
- logger.debug(f"decoding output from pip {action} {package}")
- output = subprocess.check_output(command, env=env)
- else:
- output = subprocess.check_output(
- command, stderr=subprocess.DEVNULL, env=env
- )
+
+ 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:
- parsed = BytesHeaderParser().parsebytes(output)
try:
remove_color_codes = re.compile(r"\x1b\[[0-9;]*m")
- parsed_as_string = re.sub(
- remove_color_codes, "", parsed.as_string().strip()
- )
+ 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} {package}. Falling back to accumulating the byte stream",
+ 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 str(output.decode())
+
+ return result.stdout
+
except subprocess.CalledProcessError as exc:
logger.error(
(
f"Failed to {action} {package}\n"
f"Exit code: {exc.returncode}\n"
- f"stdout: {exc.output}"
+ 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 try to {action} {package}!",
+ f"An unexpected exception occurred while trying to {action} {package}!",
e,
)
sys.exit(1)
-
-
-def get_site_packages_location() -> str:
- output = pip_process("show", "pip", format=json_format)
- pip_location = output["Location"] # type: ignore
- return pip_location
-
-
-def get_org_and_package_dirs(manifest: Manifest) -> List[str]:
- org_name = manifest.namespace
- package_name = manifest.package_name
- org = snake_case(org_name if len(org_name) > 1 else "")
- package = snake_case(package_name if len(package_name) > 1 else package_name)
- return list(filter(None, [org, package]))
-
-
-def get_hub_directory(manifest: Manifest, site_packages: str) -> str:
- org_package = get_org_and_package_dirs(manifest)
- return os.path.join(site_packages, "guardrails", "hub", *org_package)
diff --git a/guardrails/hub/install.py b/guardrails/hub/install.py
index 677e7c392..7f09b604a 100644
--- a/guardrails/hub/install.py
+++ b/guardrails/hub/install.py
@@ -1,7 +1,10 @@
from contextlib import contextmanager
+import contextlib
from string import Template
from typing import Callable, cast, List
+import pkg_resources
+
from guardrails.hub.validator_package_service import (
ValidatorPackageService,
ValidatorModuleType,
@@ -52,9 +55,11 @@ def install(
Examples:
>>> RegexMatch = install("hub://guardrails/regex_match").RegexMatch
+ >>> RegexMatch = install("hub://guardrails/regex_match~=1.4").RegexMatch
+
- >>> install("hub://guardrails/regex_match")
- >>> import guardrails.hub.regex_match as regex_match
+ >>> install("hub://guardrails/regex_match>=1.4,==1.*.")
+ >>> from guardrails.hub.regex_match import RegexMatch
"""
verbose_printer = console.print
@@ -62,7 +67,9 @@ def install(
# 1. Validation
rc_file_exists = RC.exists()
- module_name = ValidatorPackageService.get_module_name(package_uri)
+ validator_id, validator_version = ValidatorPackageService.get_validator_id(
+ package_uri
+ )
installing_msg = f"Installing {package_uri}..."
cli_logger.log(
@@ -78,15 +85,15 @@ def install(
fetch_manifest_msg = "Fetching manifest"
with loader(fetch_manifest_msg, spinner="bouncingBar"):
(module_manifest, site_packages) = (
- ValidatorPackageService.get_manifest_and_site_packages(module_name)
+ ValidatorPackageService.get_manifest_and_site_packages(validator_id)
)
# 3. Install - Pip Installation of git module
dl_deps_msg = "Downloading dependencies"
with loader(dl_deps_msg, spinner="bouncingBar"):
ValidatorPackageService.install_hub_module(
- module_manifest,
- site_packages,
+ validator_id,
+ validator_version=validator_version,
quiet=quiet,
upgrade=upgrade,
logger=cli_logger,
@@ -139,7 +146,16 @@ def install(
# Print success messages
cli_logger.info("Installation complete")
- verbose_printer(f"✅Successfully installed {module_name}!\n\n")
+ installed_version_message = ""
+ with contextlib.suppress(Exception):
+ package_name = ValidatorPackageService.get_normalized_package_name(validator_id)
+ installed_version = pkg_resources.get_distribution(package_name).version
+ if installed_version:
+ installed_version_message = f" version {installed_version}"
+
+ verbose_printer(
+ f"✅Successfully installed {validator_id}{installed_version_message}!\n\n"
+ )
success_message_cli = Template(
"[bold]Import validator:[/bold]\n"
"from guardrails.hub import ${export}\n\n"
diff --git a/guardrails/hub/validator_package_service.py b/guardrails/hub/validator_package_service.py
index e90825d27..f54ccdbc9 100644
--- a/guardrails/hub/validator_package_service.py
+++ b/guardrails/hub/validator_package_service.py
@@ -1,20 +1,21 @@
import importlib
import os
from pathlib import Path
+import re
import subprocess
import sys
-from typing import List, Literal
+from typing import List, Literal, Optional
from types import ModuleType
-from pydash.strings import snake_case
+from packaging.utils import canonicalize_name # PEP 503
-from guardrails.classes.generic.stack import Stack
from guardrails.logger import logger as guardrails_logger
from guardrails.cli.hub.utils import pip_process
from guardrails_hub_types import Manifest
from guardrails.cli.server.hub_client import get_validator_manifest
+from guardrails.settings import settings
json_format: Literal["json"] = "json"
@@ -91,35 +92,27 @@ def get_validator_from_manifest(manifest: Manifest) -> ModuleType:
Returns:
Any: The Validator class from the installed module
"""
- org_package = ValidatorPackageService.get_org_and_package_dirs(manifest)
- module_name = manifest.module_name
- _relative_path = ".".join([*org_package, module_name])
- import_line = f"guardrails.hub.{_relative_path}"
+ validator_id = manifest.id
+ import_path = ValidatorPackageService.get_import_path_from_validator_id(
+ validator_id
+ )
+
+ import_line = f"{import_path}"
# Reload or import the module
return ValidatorPackageService.reload_module(import_line)
- @staticmethod
- def get_org_and_package_dirs(
- manifest: Manifest,
- ) -> List[str]:
- org_name = manifest.namespace
- package_name = manifest.package_name
- org = snake_case(org_name if len(org_name) > 1 else "")
- package = snake_case(package_name if len(package_name) > 1 else package_name)
- return list(filter(None, [org, package]))
-
@staticmethod
def add_to_hub_inits(manifest: Manifest, site_packages: str):
- org_package = ValidatorPackageService.get_org_and_package_dirs(manifest)
+ validator_id = manifest.id
exports: List[str] = manifest.exports or []
sorted_exports = sorted(exports, reverse=True)
- module_name = manifest.module_name
- relative_path = ".".join([*org_package, module_name])
- import_line = (
- f"from guardrails.hub.{relative_path} import {', '.join(sorted_exports)}"
+
+ import_path = ValidatorPackageService.get_import_path_from_validator_id(
+ validator_id
)
+ import_line = f"from {import_path} import {', '.join(sorted_exports)}"
hub_init_location = os.path.join(
site_packages, "guardrails", "hub", "__init__.py"
@@ -136,27 +129,6 @@ def add_to_hub_inits(manifest: Manifest, site_packages: str):
hub_init.write(import_line)
hub_init.close()
- namespace = org_package[0]
- namespace_init_location = os.path.join(
- site_packages, "guardrails", "hub", namespace, "__init__.py"
- )
- if os.path.isfile(namespace_init_location):
- with open(namespace_init_location, "a+") as namespace_init:
- namespace_init.seek(0, 0)
- content = namespace_init.read()
- if import_line in content:
- namespace_init.close()
- else:
- namespace_init.seek(0, 2)
- if len(content) > 0:
- namespace_init.write("\n")
- namespace_init.write(import_line)
- namespace_init.close()
- else:
- with open(namespace_init_location, "w") as namespace_init:
- namespace_init.write(import_line)
- namespace_init.close()
-
@staticmethod
def get_module_path(package_name):
try:
@@ -179,46 +151,47 @@ def get_module_path(package_name):
return package_path
@staticmethod
- def get_module_name(package_uri: str):
- if not package_uri.startswith("hub://"):
+ def get_validator_id(validator_uri: str):
+ if not validator_uri.startswith("hub://"):
raise InvalidHubInstallURL(
"Invalid URI! The package URI must start with 'hub://'"
)
- module_name = package_uri.replace("hub://", "")
- return module_name
+ validator_uri_with_version = validator_uri.replace("hub://", "")
- @staticmethod
- def get_install_url(manifest: Manifest) -> str:
- repo = manifest.repository
- repo_url = repo.url
- branch = repo.branch
-
- git_url = repo_url
- if not repo_url.startswith("git+"):
- git_url = f"git+{repo_url}"
+ validator_id_version_regex = (
+ r"(?P[\/a-zA-Z0-9\-_]+)(?P.*)"
+ )
+ match = re.match(validator_id_version_regex, validator_uri_with_version)
+ validator_version = None
- if branch is not None:
- git_url = f"{git_url}@{branch}"
+ if match:
+ validator_id = match.group("validator_id")
+ validator_version = (
+ match.group("version").strip() if match.group("version") else None
+ )
+ else:
+ validator_id = validator_uri_with_version
- return git_url
+ return (validator_id, validator_version)
@staticmethod
def run_post_install(
manifest: Manifest, site_packages: str, logger=guardrails_logger
):
- org_package = ValidatorPackageService.get_org_and_package_dirs(manifest)
+ validator_id = manifest.id
post_install_script = manifest.post_install
+
if not post_install_script:
return
- module_name = manifest.module_name
+ import_path = ValidatorPackageService.get_import_path_from_validator_id(
+ validator_id
+ )
+
relative_path = os.path.join(
site_packages,
- "guardrails",
- "hub",
- *org_package,
- module_name,
+ import_path,
post_install_script,
)
@@ -251,24 +224,41 @@ def run_post_install(
)
@staticmethod
- def get_hub_directory(manifest: Manifest, site_packages: str) -> str:
- org_package = ValidatorPackageService.get_org_and_package_dirs(manifest)
- return os.path.join(site_packages, "guardrails", "hub", *org_package)
+ def get_normalized_package_name(validator_id: str):
+ validator_id_parts = validator_id.split("/")
+ concatanated_package_name = (
+ f"{validator_id_parts[0]}-grhub-{validator_id_parts[1]}"
+ )
+ pep_503_package_name = canonicalize_name(concatanated_package_name)
+ return pep_503_package_name
+
+ @staticmethod
+ def get_import_path_from_validator_id(validator_id):
+ pep_503_package_name = ValidatorPackageService.get_normalized_package_name(
+ validator_id
+ )
+ return pep_503_package_name.replace("-", "_")
@staticmethod
def install_hub_module(
- module_manifest: Manifest,
- site_packages: str,
+ validator_id: str,
+ validator_version: Optional[str] = "",
quiet: bool = False,
upgrade: bool = False,
logger=guardrails_logger,
):
- install_url = ValidatorPackageService.get_install_url(module_manifest)
- install_directory = ValidatorPackageService.get_hub_directory(
- module_manifest, site_packages
+ pep_503_package_name = ValidatorPackageService.get_normalized_package_name(
+ validator_id
)
+ validator_version = validator_version if validator_version else ""
+ full_package_name = f"{pep_503_package_name}{validator_version}"
+
+ guardrails_token = settings.rc.token
- pip_flags = [f"--target={install_directory}", "--no-deps"]
+ pip_flags = [
+ f"--index-url=https://__token__:{guardrails_token}@pypi.guardrailsai.com/simple",
+ "--extra-index-url=https://pypi.org/simple",
+ ]
if upgrade:
pip_flags.append("--upgrade")
@@ -276,46 +266,9 @@ def install_hub_module(
if quiet:
pip_flags.append("-q")
- # Install validator module in namespaced directory under guardrails.hub
- download_output = pip_process("install", install_url, pip_flags, quiet=quiet)
+ # Install from guardrails hub pypi server with public pypi index as fallback
+ download_output = pip_process(
+ "install", full_package_name, pip_flags, quiet=quiet
+ )
if not quiet:
logger.info(download_output)
-
- # Install validator module's dependencies in normal site-packages directory
- inspect_output = pip_process(
- "inspect",
- flags=[f"--path={install_directory}"],
- format=json_format,
- quiet=quiet,
- no_color=True,
- )
-
- # throw if inspect_output is a string. Mostly for pyright
- if isinstance(inspect_output, str):
- logger.error("Failed to inspect the installed package!")
- raise FailedPackageInspection
-
- dependencies = (
- Stack(*inspect_output.get("installed", []))
- .at(0, {})
- .get("metadata", {}) # type: ignore
- .get("requires_dist", []) # type: ignore
- )
- requirements = list(filter(lambda dep: "extra" not in dep, dependencies))
- for req in requirements:
- if "git+" in req:
- install_spec = req.replace(" ", "")
- dep_install_output = pip_process("install", install_spec, quiet=quiet)
- if not quiet:
- logger.info(dep_install_output)
- else:
- req_info = Stack(*req.split(" "))
- name = req_info.at(0, "").strip() # type: ignore
- versions = req_info.at(1, "").strip("()") # type: ignore
- if name:
- install_spec = name if not versions else f"{name}{versions}"
- dep_install_output = pip_process(
- "install", install_spec, quiet=quiet
- )
- if not quiet:
- logger.info(dep_install_output)
diff --git a/tests/unit_tests/cli/hub/test_install.py b/tests/unit_tests/cli/hub/test_install.py
index 5c9a749e2..d69103573 100755
--- a/tests/unit_tests/cli/hub/test_install.py
+++ b/tests/unit_tests/cli/hub/test_install.py
@@ -1,4 +1,4 @@
-from unittest.mock import ANY, call
+from unittest.mock import ANY, MagicMock, call
from typer.testing import CliRunner
from guardrails.cli.hub.install import hub_command
@@ -140,25 +140,27 @@ def test_no_package_string_format(self, mocker):
mock_sys_executable = mocker.patch("guardrails.cli.hub.utils.sys.executable")
- mock_subprocess_check_output = mocker.patch(
- "guardrails.cli.hub.utils.subprocess.check_output"
- )
- mock_subprocess_check_output.return_value = str.encode("string output")
+ mock_subprocess_run = mocker.patch("guardrails.cli.hub.utils.subprocess.run")
+ subprocess_result_mock = MagicMock()
+ subprocess_result_mock.stdout = "string output"
+ mock_subprocess_run.return_value = subprocess_result_mock
from guardrails.cli.hub.utils import pip_process
response = pip_process("inspect", flags=["--path=./install-here"])
- assert mock_logger_debug.call_count == 2
+ assert mock_logger_debug.call_count == 1
debug_calls = [
call("running pip inspect --path=./install-here "),
- call("decoding output from pip inspect "),
]
mock_logger_debug.assert_has_calls(debug_calls)
- mock_subprocess_check_output.assert_called_once_with(
+ mock_subprocess_run.assert_called_once_with(
[mock_sys_executable, "-m", "pip", "inspect", "--path=./install-here"],
env={},
+ capture_output=True,
+ text=True,
+ check=True,
)
assert response == "string output"
@@ -169,10 +171,11 @@ def test_json_format(self, mocker):
mock_sys_executable = mocker.patch("guardrails.cli.hub.utils.sys.executable")
- mock_subprocess_check_output = mocker.patch(
- "guardrails.cli.hub.utils.subprocess.check_output"
- )
- mock_subprocess_check_output.return_value = str.encode("json output")
+ mock_subprocess_run = mocker.patch("guardrails.cli.hub.utils.subprocess.run")
+ subprocess_result_mock = MagicMock()
+ subprocess_result_mock.stdout = "json outout"
+
+ mock_subprocess_run.return_value = subprocess_result_mock
class MockBytesHeaderParser:
def parsebytes(self, *args):
@@ -186,18 +189,21 @@ def parsebytes(self, *args):
response = pip_process("show", "pip", format="json")
- assert mock_logger_debug.call_count == 3
+ assert mock_logger_debug.call_count == 2
debug_calls = [
call("running pip show pip"),
- call("decoding output from pip show pip"),
call(
"JSON parse exception in decoding output from pip show pip. Falling back to accumulating the byte stream" # noqa
),
]
mock_logger_debug.assert_has_calls(debug_calls)
- mock_subprocess_check_output.assert_called_once_with(
- [mock_sys_executable, "-m", "pip", "show", "pip"], env={}
+ mock_subprocess_run.assert_called_once_with(
+ [mock_sys_executable, "-m", "pip", "show", "pip"],
+ env={},
+ capture_output=True,
+ text=True,
+ check=True,
)
assert response == {"output": "json"}
@@ -271,16 +277,3 @@ def test_install_with_upgrade_flag(self, mocker):
)
assert result.exit_code == 0
-
-
-def test_get_site_packages_location(mocker):
- mock_pip_process = mocker.patch("guardrails.cli.hub.utils.pip_process")
- mock_pip_process.return_value = {"Location": "/site-packages"}
-
- from guardrails.cli.hub.utils import get_site_packages_location
-
- response = get_site_packages_location()
-
- mock_pip_process.assert_called_once_with("show", "pip", format="json")
-
- assert response == "/site-packages"
diff --git a/tests/unit_tests/cli/hub/test_list.py b/tests/unit_tests/cli/hub/test_list.py
index 5c40c831e..d569dea8e 100644
--- a/tests/unit_tests/cli/hub/test_list.py
+++ b/tests/unit_tests/cli/hub/test_list.py
@@ -13,7 +13,7 @@ class TestListCommand:
@pytest.fixture(autouse=True)
def setup(self, mocker):
mocker.patch(
- "guardrails.cli.hub.utils.get_site_packages_location",
+ "guardrails.hub.validator_package_service.ValidatorPackageService.get_manifest_and_site_packages",
return_value="/test/site-packages",
)
diff --git a/tests/unit_tests/cli/hub/test_uninstall.py b/tests/unit_tests/cli/hub/test_uninstall.py
index aef3db760..f1779938d 100644
--- a/tests/unit_tests/cli/hub/test_uninstall.py
+++ b/tests/unit_tests/cli/hub/test_uninstall.py
@@ -5,46 +5,35 @@
from guardrails_hub_types import Manifest
from guardrails.cli.hub.uninstall import remove_from_hub_inits
-manifest_mock = Manifest(
- encoder="some_encoder",
- id="module_id",
- name="test_module",
- author={"name": "Author Name", "email": "author@example.com"},
- maintainers=[{"name": "Maintainer Name", "email": "maintainer@example.com"}],
- repository={"url": "https://github.com/example/repo"},
- namespace="guardrails",
- package_name="test_package",
- description="Test module",
- module_name="test_module",
- exports=["Validator", "Helper"],
+manifest_mock = Manifest.from_dict(
+ {
+ "id": "guardrails/test_package",
+ "name": "test_module",
+ "author": {"name": "Author Name", "email": "author@example.com"},
+ "maintainers": [{"name": "Maintainer Name", "email": "maintainer@example.com"}],
+ "repository": {"url": "https://github.com/example/repo"},
+ "packageName": "test_package",
+ "moduleName": "test_module",
+ "namespace": "guardrails",
+ "description": "Test module",
+ "exports": ["Validator", "Helper"],
+ }
)
def test_remove_from_hub_inits(mocker):
- mocker.patch(
- "guardrails.cli.hub.uninstall.get_org_and_package_dirs",
- return_value=["guardrails", "test_package"],
- )
mock_remove_line = mocker.patch("guardrails.cli.hub.uninstall.remove_line")
- mock_remove_dirs = mocker.patch("shutil.rmtree")
remove_from_hub_inits(manifest_mock, "/site-packages")
expected_calls = [
call(
"/site-packages/guardrails/hub/__init__.py",
- "from guardrails.hub.guardrails.test_package.test_module import "
- "Validator, Helper",
- ),
- call(
- "/site-packages/guardrails/hub/guardrails/__init__.py",
- "from guardrails.hub.guardrails.test_package.test_module import "
- "Validator, Helper",
+ "from guardrails_grhub_test_package import " "Validator, Helper",
),
]
mock_remove_line.assert_has_calls(expected_calls, any_order=True)
- mock_remove_dirs.assert_called_once_with("/site-packages/guardrails/hub/guardrails")
def test_uninstall_invalid_uri(mocker):
@@ -81,10 +70,7 @@ def test_uninstall_valid_uri(mocker):
"guardrails.cli.hub.uninstall.get_validator_manifest",
return_value=manifest_mock,
)
- mocker.patch(
- "guardrails.cli.hub.uninstall.get_site_packages_location",
- return_value="/site-packages",
- )
+
mock_uninstall_hub_module = mocker.patch(
"guardrails.cli.hub.uninstall.uninstall_hub_module"
)
@@ -93,9 +79,27 @@ def test_uninstall_valid_uri(mocker):
)
mocker.patch("guardrails.cli.hub.uninstall.console")
+ validator_package_service_mock = mocker.patch(
+ "guardrails.hub.validator_package_service.ValidatorPackageService",
+ )
+ validator_package_service_mock.get_site_packages_location.return_value = (
+ "/site-packages"
+ )
+
from guardrails.cli.hub.uninstall import uninstall
uninstall("hub://guardrails/test-validator")
- mock_uninstall_hub_module.assert_called_once_with(manifest_mock, "/site-packages")
+ mock_uninstall_hub_module.assert_called_once_with(manifest_mock)
mock_remove_from_hub_inits.assert_called_once_with(manifest_mock, "/site-packages")
+
+
+def test_uninstall_hub_module(mocker):
+ mock_pip_process = mocker.patch("guardrails.cli.hub.uninstall.pip_process")
+ from guardrails.cli.hub.uninstall import uninstall_hub_module
+
+ uninstall_hub_module(manifest_mock)
+
+ mock_pip_process.assert_called_once_with(
+ "uninstall", "guardrails-grhub-test-package", flags=["-y"], quiet=True
+ )
diff --git a/tests/unit_tests/hub/test_hub_install.py b/tests/unit_tests/hub/test_hub_install.py
index 3aea52899..c94c909c7 100644
--- a/tests/unit_tests/hub/test_hub_install.py
+++ b/tests/unit_tests/hub/test_hub_install.py
@@ -18,7 +18,7 @@ class TestInstall:
def setup_method(self):
self.manifest = Manifest.from_dict(
{
- "id": "id",
+ "id": "guardrails/id",
"name": "name",
"author": {"name": "me", "email": "me@me.me"},
"maintainers": [],
@@ -74,13 +74,13 @@ def test_install_local_models__false(self, mocker, use_remote_inferencing):
)
install(
- "hub://guardrails/test-validator",
+ "hub://guardrails/id",
install_local_models=False,
install_local_models_confirm=lambda: False,
)
log_calls = [
- call(level=5, msg="Installing hub://guardrails/test-validator..."),
+ call(level=5, msg="Installing hub://guardrails/id..."),
call(
level=5,
msg="Skipping post install, models will not be downloaded for local "
@@ -88,18 +88,16 @@ def test_install_local_models__false(self, mocker, use_remote_inferencing):
),
call(
level=5,
- msg="✅Successfully installed hub://guardrails/test-validator!\n\nImport validator:\nfrom guardrails.hub import TestValidator\n\nGet more info:\nhttps://hub.guardrailsai.com/validator/id\n", # noqa
+ msg="✅Successfully installed hub://guardrails/id!\n\nImport validator:\nfrom guardrails.hub import TestValidator\n\nGet more info:\nhttps://hub.guardrailsai.com/validator/guardrails/id\n", # noqa
), # noqa
]
assert mock_logger_log.call_count == 3
mock_logger_log.assert_has_calls(log_calls)
- get_manifest_and_site_packages_mock.assert_called_once_with(
- "guardrails/test-validator"
- )
+ get_manifest_and_site_packages_mock.assert_called_once_with("guardrails/id")
mock_pip_install_hub_module.assert_called_once_with(
- self.manifest, self.site_packages, quiet=ANY, upgrade=ANY, logger=ANY
+ self.manifest.id, validator_version=None, quiet=ANY, upgrade=ANY, logger=ANY
)
mock_add_to_hub_init.assert_called_once_with(self.manifest, self.site_packages)
@@ -117,6 +115,9 @@ def test_install_local_models__true(self, mocker, use_remote_inferencing):
mock_logger_log = mocker.patch("guardrails.hub.install.cli_logger.log")
+ pkg_resources = mocker.patch("guardrails.hub.install.pkg_resources")
+ pkg_resources.get_distribution.return_value.version = "1.0.0"
+
get_manifest_and_site_packages_mock = mocker.patch(
"guardrails.hub.validator_package_service.ValidatorPackageService.get_manifest_and_site_packages"
)
@@ -136,31 +137,29 @@ def test_install_local_models__true(self, mocker, use_remote_inferencing):
)
install(
- "hub://guardrails/test-validator",
+ "hub://guardrails/id",
install_local_models=True,
install_local_models_confirm=lambda: True,
)
log_calls = [
- call(level=5, msg="Installing hub://guardrails/test-validator..."),
+ call(level=5, msg="Installing hub://guardrails/id..."),
call(
level=5,
msg="Installing models locally!",
),
call(
level=5,
- msg="✅Successfully installed hub://guardrails/test-validator!\n\nImport validator:\nfrom guardrails.hub import TestValidator\n\nGet more info:\nhttps://hub.guardrailsai.com/validator/id\n", # noqa
+ msg="✅Successfully installed hub://guardrails/id!\n\nImport validator:\nfrom guardrails.hub import TestValidator\n\nGet more info:\nhttps://hub.guardrailsai.com/validator/guardrails/id\n", # noqa
), # noqa
]
assert mock_logger_log.call_count == 3
mock_logger_log.assert_has_calls(log_calls)
- get_manifest_and_site_packages_mock.assert_called_once_with(
- "guardrails/test-validator"
- )
+ get_manifest_and_site_packages_mock.assert_called_once_with("guardrails/id")
mock_pip_install_hub_module.assert_called_once_with(
- self.manifest, self.site_packages, quiet=ANY, upgrade=ANY, logger=ANY
+ self.manifest.id, validator_version=None, quiet=ANY, upgrade=ANY, logger=ANY
)
mock_add_to_hub_init.assert_called_once_with(self.manifest, self.site_packages)
@@ -197,31 +196,29 @@ def test_install_local_models__none(self, mocker, use_remote_inferencing):
)
install(
- "hub://guardrails/test-validator",
+ "hub://guardrails/id",
install_local_models=None,
install_local_models_confirm=lambda: True,
)
log_calls = [
- call(level=5, msg="Installing hub://guardrails/test-validator..."),
+ call(level=5, msg="Installing hub://guardrails/id..."),
call(
level=5,
msg="Installing models locally!",
),
call(
level=5,
- msg="✅Successfully installed hub://guardrails/test-validator!\n\nImport validator:\nfrom guardrails.hub import TestValidator\n\nGet more info:\nhttps://hub.guardrailsai.com/validator/id\n", # noqa
+ msg="✅Successfully installed hub://guardrails/id!\n\nImport validator:\nfrom guardrails.hub import TestValidator\n\nGet more info:\nhttps://hub.guardrailsai.com/validator/guardrails/id\n", # noqa
), # noqa
]
assert mock_logger_log.call_count == 3
mock_logger_log.assert_has_calls(log_calls)
- get_manifest_and_site_packages_mock.assert_called_once_with(
- "guardrails/test-validator"
- )
+ get_manifest_and_site_packages_mock.assert_called_once_with("guardrails/id")
mock_pip_install_hub_module.assert_called_once_with(
- self.manifest, self.site_packages, quiet=ANY, upgrade=ANY, logger=ANY
+ self.manifest.id, validator_version=None, quiet=ANY, upgrade=ANY, logger=ANY
)
mock_add_to_hub_init.assert_called_once_with(self.manifest, self.site_packages)
@@ -258,12 +255,12 @@ def test_happy_path(self, mocker, use_remote_inferencing):
)
install(
- "hub://guardrails/test-validator",
+ "hub://guardrails/id",
install_local_models_confirm=lambda: True,
)
log_calls = [
- call(level=5, msg="Installing hub://guardrails/test-validator..."),
+ call(level=5, msg="Installing hub://guardrails/id..."),
call(
level=5,
msg="Installing models locally!", # noqa
@@ -273,12 +270,10 @@ def test_happy_path(self, mocker, use_remote_inferencing):
assert mock_logger_log.call_count == 3
mock_logger_log.assert_has_calls(log_calls)
- get_manifest_and_site_packages_mock.assert_called_once_with(
- "guardrails/test-validator"
- )
+ get_manifest_and_site_packages_mock.assert_called_once_with("guardrails/id")
mock_pip_install_hub_module.assert_called_once_with(
- self.manifest, self.site_packages, quiet=ANY, upgrade=ANY, logger=ANY
+ self.manifest.id, validator_version=None, quiet=ANY, upgrade=ANY, logger=ANY
)
mock_add_to_hub_init.assert_called_once_with(self.manifest, self.site_packages)
@@ -408,7 +403,7 @@ def test_use_remote_endpoint(self, mocker, use_remote_inferencing: bool):
manifest = Manifest.from_dict(
{
- "id": "id",
+ "id": "guardrails/test-validator",
"name": "name",
"author": {"name": "me", "email": "me@me.me"},
"maintainers": [],
diff --git a/tests/unit_tests/hub/test_validator_package_service.py b/tests/unit_tests/hub/test_validator_package_service.py
index a407c312b..7fe7129a3 100644
--- a/tests/unit_tests/hub/test_validator_package_service.py
+++ b/tests/unit_tests/hub/test_validator_package_service.py
@@ -1,4 +1,5 @@
from pathlib import Path
+from typing import cast
import pytest
import sys
from unittest.mock import call, patch, MagicMock
@@ -47,7 +48,7 @@ class TestAddToHubInits:
def test_closes_early_if_already_added(self, mocker):
manifest = Manifest.from_dict(
{
- "id": "id",
+ "id": "guardrails-ai/id",
"name": "name",
"author": {"name": "me", "email": "me@me.me"},
"maintainers": [],
@@ -63,37 +64,26 @@ def test_closes_early_if_already_added(self, mocker):
site_packages = "./site-packages"
hub_init_file = MockFile()
- ns_init_file = MockFile()
mock_open = mocker.patch("guardrails.hub.validator_package_service.open")
- mock_open.side_effect = [hub_init_file, ns_init_file]
+ mock_open.side_effect = [hub_init_file]
mock_hub_read = mocker.patch.object(hub_init_file, "read")
- mock_hub_read.return_value = "from guardrails.hub.guardrails_ai.test_validator.validator import helper, TestValidator" # noqa
+ mock_hub_read.return_value = (
+ "from guardrails_ai_grhub_id import helper, TestValidator" # noqa
+ )
hub_seek_spy = mocker.spy(hub_init_file, "seek")
hub_write_spy = mocker.spy(hub_init_file, "write")
hub_close_spy = mocker.spy(hub_init_file, "close")
- mock_ns_read = mocker.patch.object(ns_init_file, "read")
- mock_ns_read.return_value = "from guardrails.hub.guardrails_ai.test_validator.validator import helper, TestValidator" # noqa
-
- ns_seek_spy = mocker.spy(ns_init_file, "seek")
- ns_write_spy = mocker.spy(ns_init_file, "write")
- ns_close_spy = mocker.spy(ns_init_file, "close")
-
- mock_is_file = mocker.patch(
- "guardrails.hub.validator_package_service.os.path.isfile"
- )
- mock_is_file.return_value = True
-
from guardrails.hub.validator_package_service import ValidatorPackageService
+ manifest = cast(Manifest, manifest)
ValidatorPackageService.add_to_hub_inits(manifest, site_packages)
- assert mock_open.call_count == 2
+ assert mock_open.call_count == 1
open_calls = [
call("./site-packages/guardrails/hub/__init__.py", "a+"),
- call("./site-packages/guardrails/hub/guardrails_ai/__init__.py", "a+"),
]
mock_open.assert_has_calls(open_calls)
@@ -102,18 +92,10 @@ def test_closes_early_if_already_added(self, mocker):
assert hub_write_spy.call_count == 0
assert hub_close_spy.call_count == 1
- mock_is_file.assert_called_once_with(
- "./site-packages/guardrails/hub/guardrails_ai/__init__.py"
- )
- assert ns_seek_spy.call_count == 1
- assert mock_ns_read.call_count == 1
- assert ns_write_spy.call_count == 0
- assert ns_close_spy.call_count == 1
-
def test_appends_import_line_if_not_present(self, mocker):
manifest = Manifest.from_dict(
{
- "id": "id",
+ "id": "guardrails-ai/id",
"name": "name",
"author": {"name": "me", "email": "me@me.me"},
"maintainers": [],
@@ -129,9 +111,8 @@ def test_appends_import_line_if_not_present(self, mocker):
site_packages = "./site-packages"
hub_init_file = MockFile()
- ns_init_file = MockFile()
mock_open = mocker.patch("guardrails.hub.validator_package_service.open")
- mock_open.side_effect = [hub_init_file, ns_init_file]
+ mock_open.side_effect = [hub_init_file]
mock_hub_read = mocker.patch.object(hub_init_file, "read")
mock_hub_read.return_value = "from guardrails.hub.other_org.other_validator.validator import OtherValidator" # noqa
@@ -140,26 +121,14 @@ def test_appends_import_line_if_not_present(self, mocker):
hub_write_spy = mocker.spy(hub_init_file, "write")
hub_close_spy = mocker.spy(hub_init_file, "close")
- mock_ns_read = mocker.patch.object(ns_init_file, "read")
- mock_ns_read.return_value = ""
-
- ns_seek_spy = mocker.spy(ns_init_file, "seek")
- ns_write_spy = mocker.spy(ns_init_file, "write")
- ns_close_spy = mocker.spy(ns_init_file, "close")
-
- mock_is_file = mocker.patch(
- "guardrails.hub.validator_package_service.os.path.isfile"
- )
- mock_is_file.return_value = True
-
from guardrails.hub.validator_package_service import ValidatorPackageService
+ manifest = cast(Manifest, manifest)
ValidatorPackageService.add_to_hub_inits(manifest, site_packages)
- assert mock_open.call_count == 2
+ assert mock_open.call_count == 1
open_calls = [
call("./site-packages/guardrails/hub/__init__.py", "a+"),
- call("./site-packages/guardrails/hub/guardrails_ai/__init__.py", "a+"),
]
mock_open.assert_has_calls(open_calls)
@@ -173,32 +142,17 @@ def test_appends_import_line_if_not_present(self, mocker):
hub_write_calls = [
call("\n"),
call(
- "from guardrails.hub.guardrails_ai.test_validator.validator import TestValidator" # noqa
+ "from guardrails_ai_grhub_id import TestValidator" # noqa
),
]
hub_write_spy.assert_has_calls(hub_write_calls)
assert hub_close_spy.call_count == 1
- mock_is_file.assert_called_once_with(
- "./site-packages/guardrails/hub/guardrails_ai/__init__.py"
- )
-
- assert ns_seek_spy.call_count == 2
- ns_seek_calls = [call(0, 0), call(0, 2)]
- ns_seek_spy.assert_has_calls(ns_seek_calls)
-
- assert mock_ns_read.call_count == 1
- assert ns_write_spy.call_count == 1
- ns_write_spy.assert_called_once_with(
- "from guardrails.hub.guardrails_ai.test_validator.validator import TestValidator" # noqa
- )
- assert ns_close_spy.call_count == 1
-
def test_creates_namespace_init_if_not_exists(self, mocker):
manifest = Manifest.from_dict(
{
- "id": "id",
+ "id": "guardrails-ai/id",
"name": "name",
"author": {"name": "me", "email": "me@me.me"},
"maintainers": [],
@@ -214,19 +168,11 @@ def test_creates_namespace_init_if_not_exists(self, mocker):
site_packages = "./site-packages"
hub_init_file = MockFile()
- ns_init_file = MockFile()
mock_open = mocker.patch("guardrails.hub.validator_package_service.open")
- mock_open.side_effect = [hub_init_file, ns_init_file]
+ mock_open.side_effect = [hub_init_file]
mock_hub_read = mocker.patch.object(hub_init_file, "read")
- mock_hub_read.return_value = "from guardrails.hub.guardrails_ai.test_validator.validator import TestValidator" # noqa
-
- mock_ns_read = mocker.patch.object(ns_init_file, "read")
- mock_ns_read.return_value = ""
-
- ns_seek_spy = mocker.spy(ns_init_file, "seek")
- ns_write_spy = mocker.spy(ns_init_file, "write")
- ns_close_spy = mocker.spy(ns_init_file, "close")
+ mock_hub_read.return_value = "from guardrails_ai_grhub_id import TestValidator" # noqa
mock_is_file = mocker.patch(
"guardrails.hub.validator_package_service.os.path.isfile"
@@ -235,27 +181,15 @@ def test_creates_namespace_init_if_not_exists(self, mocker):
from guardrails.hub.validator_package_service import ValidatorPackageService
+ manifest = cast(Manifest, manifest)
ValidatorPackageService.add_to_hub_inits(manifest, site_packages)
- assert mock_open.call_count == 2
+ assert mock_open.call_count == 1
open_calls = [
call("./site-packages/guardrails/hub/__init__.py", "a+"),
- call("./site-packages/guardrails/hub/guardrails_ai/__init__.py", "w"),
]
mock_open.assert_has_calls(open_calls)
- mock_is_file.assert_called_once_with(
- "./site-packages/guardrails/hub/guardrails_ai/__init__.py"
- )
-
- assert ns_seek_spy.call_count == 0
- assert mock_ns_read.call_count == 0
- assert ns_write_spy.call_count == 1
- ns_write_spy.assert_called_once_with(
- "from guardrails.hub.guardrails_ai.test_validator.validator import TestValidator" # noqa
- )
- assert ns_close_spy.call_count == 1
-
class TestReloadModule:
@patch("guardrails.hub.validator_package_service.importlib")
@@ -335,7 +269,7 @@ class TestRunPostInstall:
[
Manifest.from_dict(
{
- "id": "id",
+ "id": "guardrails-ai/id",
"name": "name",
"author": {"name": "me", "email": "me@me.me"},
"maintainers": [],
@@ -350,7 +284,7 @@ class TestRunPostInstall:
),
Manifest.from_dict(
{
- "id": "id",
+ "id": "guardrails-ai/id",
"name": "name",
"author": {"name": "me", "email": "me@me.me"},
"maintainers": [],
@@ -391,7 +325,7 @@ def test_runs_script_if_exists(self, mocker):
manifest = Manifest.from_dict(
{
- "id": "id",
+ "id": "guardrails-ai/id",
"name": "name",
"author": {"name": "me", "email": "me@me.me"},
"maintainers": [],
@@ -406,13 +340,14 @@ def test_runs_script_if_exists(self, mocker):
}
)
+ manifest = cast(Manifest, manifest)
ValidatorPackageService.run_post_install(manifest, "./site_packages")
assert mock_subprocess_check_output.call_count == 1
mock_subprocess_check_output.assert_called_once_with(
[
mock_sys_executable,
- "./site_packages/guardrails/hub/guardrails_ai/test_validator/validator/post_install.py", # noqa
+ "./site_packages/guardrails_ai_grhub_id/post_install.py", # noqa
]
)
@@ -421,7 +356,7 @@ class TestValidatorPackageService:
def setup_method(self):
self.manifest = Manifest.from_dict(
{
- "id": "id",
+ "id": "guardrails/id",
"name": "name",
"author": {"name": "me", "email": "me@me.me"},
"maintainers": [],
@@ -466,169 +401,38 @@ def test_get_site_packages_location(self, mock_get_module_path):
site_packages_path = ValidatorPackageService.get_site_packages_location()
assert site_packages_path == "/fake/site-packages"
- @patch(
- "guardrails.hub.validator_package_service.ValidatorPackageService.get_org_and_package_dirs"
- )
@patch(
"guardrails.hub.validator_package_service.ValidatorPackageService.reload_module"
)
- def test_get_validator_from_manifest(
- self, mock_reload_module, mock_get_org_and_package_dirs
- ):
- mock_get_org_and_package_dirs.return_value = ["guardrails_ai", "test_package"]
-
+ def test_get_validator_from_manifest(self, mock_reload_module):
mock_validator_module = MagicMock()
mock_reload_module.return_value = mock_validator_module
- ValidatorPackageService.get_validator_from_manifest(self.manifest)
+ manifest = cast(Manifest, self.manifest)
+ ValidatorPackageService.get_validator_from_manifest(manifest)
- mock_reload_module.assert_called_once_with(
- f"guardrails.hub.guardrails_ai.test_package.{self.manifest.module_name}"
- )
-
- @pytest.mark.parametrize(
- "manifest,expected",
- [
- (
- Manifest.from_dict(
- {
- "id": "id",
- "name": "name",
- "author": {"name": "me", "email": "me@me.me"},
- "maintainers": [],
- "repository": {"url": "some-repo"},
- "namespace": "guardrails-ai",
- "packageName": "test-validator",
- "moduleName": "test_validator",
- "description": "description",
- "exports": ["TestValidator"],
- "tags": {},
- }
- ),
- ["guardrails_ai", "test_validator"],
- ),
- (
- Manifest.from_dict(
- {
- "id": "id",
- "name": "name",
- "author": {"name": "me", "email": "me@me.me"},
- "maintainers": [],
- "repository": {"url": "some-repo"},
- "namespace": "",
- "packageName": "test-validator",
- "moduleName": "test_validator",
- "description": "description",
- "exports": ["TestValidator"],
- "tags": {},
- }
- ),
- ["test_validator"],
- ),
- ],
- )
- def test_get_org_and_package_dirs(self, manifest, expected):
- from guardrails.hub.validator_package_service import ValidatorPackageService
-
- actual = ValidatorPackageService.get_org_and_package_dirs(manifest)
- assert actual == expected
+ mock_reload_module.assert_called_once_with("guardrails_grhub_id")
def test_get_module_name_valid(self):
- module_name = ValidatorPackageService.get_module_name("hub://test-module")
+ module_name, module_version = ValidatorPackageService.get_validator_id(
+ "hub://test-module>=1.0.0"
+ )
assert module_name == "test-module"
+ assert module_version == ">=1.0.0"
def test_get_module_name_invalid(self):
with pytest.raises(InvalidHubInstallURL):
- ValidatorPackageService.get_module_name("invalid-uri")
-
- @pytest.mark.parametrize(
- "manifest,expected",
- [
- (
- Manifest.from_dict(
- {
- "id": "id",
- "name": "name",
- "author": {"name": "me", "email": "me@me.me"},
- "maintainers": [],
- "repository": {"url": "some-repo"},
- "namespace": "guardrails-ai",
- "packageName": "test-validator",
- "moduleName": "validator",
- "description": "description",
- "exports": ["TestValidator"],
- "tags": {},
- }
- ),
- "git+some-repo",
- ),
- (
- Manifest.from_dict(
- {
- "id": "id",
- "name": "name",
- "author": {"name": "me", "email": "me@me.me"},
- "maintainers": [],
- "repository": {"url": "git+some-repo"},
- "namespace": "guardrails-ai",
- "packageName": "test-validator",
- "moduleName": "validator",
- "description": "description",
- "exports": ["TestValidator"],
- "tags": {},
- "post_install": "",
- }
- ),
- "git+some-repo",
- ),
- (
- Manifest.from_dict(
- {
- "id": "id",
- "name": "name",
- "author": {"name": "me", "email": "me@me.me"},
- "maintainers": [],
- "repository": {"url": "git+some-repo", "branch": "prod"},
- "namespace": "guardrails-ai",
- "packageName": "test-validator",
- "moduleName": "validator",
- "description": "description",
- "exports": ["TestValidator"],
- "tags": {},
- "post_install": "",
- }
- ),
- "git+some-repo@prod",
- ),
- ],
- )
- def test_get_install_url(self, manifest, expected):
- actual = ValidatorPackageService.get_install_url(manifest)
- assert actual == expected
-
- def test_get_hub_directory(self):
- hub_directory = ValidatorPackageService.get_hub_directory(
- self.manifest, self.site_packages
- )
- assert (
- hub_directory
- == "./.venv/lib/python3.X/site-packages/guardrails/hub/guardrails/test_validator" # noqa
- ) # noqa
+ ValidatorPackageService.get_validator_id("invalid-uri")
def test_install_hub_module(self, mocker):
- mock_get_install_url = mocker.patch(
- "guardrails.hub.validator_package_service.ValidatorPackageService.get_install_url"
- )
- mock_get_install_url.return_value = "mock-install-url"
-
- mock_get_hub_directory = mocker.patch(
- "guardrails.hub.validator_package_service.ValidatorPackageService.get_hub_directory"
- )
- mock_get_hub_directory.return_value = "mock/install/directory"
-
mock_pip_process = mocker.patch(
"guardrails.hub.validator_package_service.pip_process"
)
+ mock_settings = mocker.patch(
+ "guardrails.hub.validator_package_service.settings"
+ )
+ mock_settings.rc.token = "mock-token"
+
inspect_report = {
"installed": [
{
@@ -653,7 +457,7 @@ def test_install_hub_module(self, mocker):
manifest = Manifest.from_dict(
{
- "id": "id",
+ "id": "guardrails-ai/id",
"name": "name",
"author": {"name": "me", "email": "me@me.me"},
"maintainers": [],
@@ -666,29 +470,19 @@ def test_install_hub_module(self, mocker):
"tags": {},
}
)
- site_packages = "./site-packages"
- ValidatorPackageService.install_hub_module(manifest, site_packages)
+ manifest = cast(Manifest, manifest)
+ ValidatorPackageService.install_hub_module(manifest.id)
- mock_get_install_url.assert_called_once_with(manifest)
- mock_get_hub_directory.assert_called_once_with(manifest, site_packages)
-
- assert mock_pip_process.call_count == 5
+ assert mock_pip_process.call_count == 1
pip_calls = [
call(
"install",
- "mock-install-url",
- ["--target=mock/install/directory", "--no-deps"],
- quiet=False,
- ),
- call(
- "inspect",
- flags=["--path=mock/install/directory"],
- format="json",
+ "guardrails-ai-grhub-id",
+ [
+ "--index-url=https://__token__:mock-token@pypi.guardrailsai.com/simple",
+ "--extra-index-url=https://pypi.org/simple",
+ ],
quiet=False,
- no_color=True,
),
- call("install", "rstr", quiet=False),
- call("install", "openai<2", quiet=False),
- call("install", "pydash>=7.0.6,<8.0.0", quiet=False),
]
mock_pip_process.assert_has_calls(pip_calls)