From 4d02009588a2ecd42f05d9172279adb6f34d6ab2 Mon Sep 17 00:00:00 2001 From: Brian Lai Date: Mon, 21 Oct 2024 17:13:36 -0700 Subject: [PATCH 01/15] update docs --- docs/concepts/remote_validation_inference.ipynb | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) 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 } From fd100f35b0b07c4d347d872f03cbaaf1c2b72b20 Mon Sep 17 00:00:00 2001 From: zsimjee Date: Tue, 22 Oct 2024 15:32:02 -0700 Subject: [PATCH 02/15] add diag to ci/cd --- ...nuous_integration_continuous_deployment.md | 65 ++++++++++++++++++- docusaurus/docusaurus.config.js | 2 +- 2 files changed, 64 insertions(+), 3 deletions(-) diff --git a/docs/how_to_guides/continuous_integration_continuous_deployment.md b/docs/how_to_guides/continuous_integration_continuous_deployment.md index 9530dd8eb..a33056fa7 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,68 @@ 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"] + NATGateway[NAT Gateway x2] + 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/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". From 561cd687e618d4843d4c50ecc91a04392a51db1e Mon Sep 17 00:00:00 2001 From: zsimjee Date: Tue, 22 Oct 2024 15:46:56 -0700 Subject: [PATCH 03/15] alpha migration guide --- docs/migration_guides/0-6-alpha-migration.md | 133 +++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100644 docs/migration_guides/0-6-alpha-migration.md 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..e47073c7c --- /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_stringis 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 From f5ed7005fe0c62de5e0be2e551e069890a86b699 Mon Sep 17 00:00:00 2001 From: zsimjee Date: Tue, 22 Oct 2024 15:48:54 -0700 Subject: [PATCH 04/15] Update docs/how_to_guides/continuous_integration_continuous_deployment.md Co-authored-by: Alejandro Esquivel --- .../continuous_integration_continuous_deployment.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/how_to_guides/continuous_integration_continuous_deployment.md b/docs/how_to_guides/continuous_integration_continuous_deployment.md index a33056fa7..344f9c828 100644 --- a/docs/how_to_guides/continuous_integration_continuous_deployment.md +++ b/docs/how_to_guides/continuous_integration_continuous_deployment.md @@ -465,7 +465,6 @@ graph TD %% Public Subnet Group subgraph PublicSubnets["Public Subnets x3"] - NATGateway[NAT Gateway x2] NLB[Network Load Balancer] TG[Target Group
TCP:80] SG[Security Group
Ingress: 8000
Egress: All] From 7aac96d6305a7d3f88bcbff64145f0f4945cdac3 Mon Sep 17 00:00:00 2001 From: zsimjee Date: Tue, 22 Oct 2024 16:02:05 -0700 Subject: [PATCH 05/15] Update docs/migration_guides/0-6-alpha-migration.md Co-authored-by: dtam --- docs/migration_guides/0-6-alpha-migration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/migration_guides/0-6-alpha-migration.md b/docs/migration_guides/0-6-alpha-migration.md index e47073c7c..a4eac4065 100644 --- a/docs/migration_guides/0-6-alpha-migration.md +++ b/docs/migration_guides/0-6-alpha-migration.md @@ -16,7 +16,7 @@ pip install --pre guardrails-ai 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_stringis being removed in favor of Guard() +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 From 595ccf5adbe4bbf1a00a0f3ace3c7ad2e15ee073 Mon Sep 17 00:00:00 2001 From: Alejandro Date: Tue, 22 Oct 2024 19:08:58 -0700 Subject: [PATCH 06/15] Installs from private pypi with validator versioning --- guardrails/cli/hub/utils.py | 52 +++++--- guardrails/hub/install.py | 30 ++++- guardrails/hub/validator_package_service.py | 140 ++++++++++---------- 3 files changed, 127 insertions(+), 95 deletions(-) diff --git a/guardrails/cli/hub/utils.py b/guardrails/cli/hub/utils.py index 8d7220801..5abe7d2c9 100644 --- a/guardrails/cli/hub/utils.py +++ b/guardrails/cli/hub/utils.py @@ -3,17 +3,24 @@ import re import subprocess import sys + +from typing import Literal +import logging + from email.parser import BytesHeaderParser -from typing import List, Literal, Union +from typing import List, Union from pydash.strings import snake_case from guardrails_hub_types import Manifest -from guardrails.cli.logger import logger - 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,47 +41,50 @@ 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}" + "{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) + # Re-raise the error or handle it accordingly + raise 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) + raise def get_site_packages_location() -> str: 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..8431dc9d1 100644 --- a/guardrails/hub/validator_package_service.py +++ b/guardrails/hub/validator_package_service.py @@ -1,20 +1,22 @@ 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,11 +93,13 @@ 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) @@ -112,14 +116,15 @@ def get_org_and_package_dirs( @staticmethod def add_to_hub_inits(manifest: Manifest, site_packages: str): + validator_id = manifest.id org_package = ValidatorPackageService.get_org_and_package_dirs(manifest) 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" @@ -179,14 +184,29 @@ 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://", "") + + 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 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 (validator_id, validator_version) @staticmethod def get_install_url(manifest: Manifest) -> str: @@ -207,18 +227,19 @@ def get_install_url(manifest: Manifest) -> str: 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, ) @@ -255,20 +276,42 @@ 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) + @staticmethod + 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}@e4c4zula06.execute-api.us-east-1.amazonaws.com/simple", + "--extra-index-url=https://pypi.org/simple", + ] if upgrade: pip_flags.append("--upgrade") @@ -276,46 +319,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) From 6d31efc25da3290fef43dea3f2a012392e192898 Mon Sep 17 00:00:00 2001 From: Alejandro Date: Tue, 22 Oct 2024 19:13:35 -0700 Subject: [PATCH 07/15] chore: Fix typo in pip_process function --- guardrails/cli/hub/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guardrails/cli/hub/utils.py b/guardrails/cli/hub/utils.py index 5abe7d2c9..b4af8283f 100644 --- a/guardrails/cli/hub/utils.py +++ b/guardrails/cli/hub/utils.py @@ -58,7 +58,7 @@ def pip_process( except Exception: logger.debug( f"JSON parse exception in decoding output from pip {action}" - "{package}. Falling back to accumulating the byte stream", + f"{package}. Falling back to accumulating the byte stream", ) accumulator = {} parsed = BytesHeaderParser().parsebytes(result.stdout.encode()) From 3b781146d07f1f347b7cbf9fcfa0d4fc624a0432 Mon Sep 17 00:00:00 2001 From: Alejandro Date: Tue, 22 Oct 2024 19:40:03 -0700 Subject: [PATCH 08/15] wip: updating tests --- guardrails/hub/validator_package_service.py | 20 --- .../hub/test_validator_package_service.py | 147 +++--------------- 2 files changed, 25 insertions(+), 142 deletions(-) diff --git a/guardrails/hub/validator_package_service.py b/guardrails/hub/validator_package_service.py index 8431dc9d1..41518bc3b 100644 --- a/guardrails/hub/validator_package_service.py +++ b/guardrails/hub/validator_package_service.py @@ -208,21 +208,6 @@ def get_validator_id(validator_uri: str): return (validator_id, validator_version) - @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}" - - if branch is not None: - git_url = f"{git_url}@{branch}" - - return git_url - @staticmethod def run_post_install( manifest: Manifest, site_packages: str, logger=guardrails_logger @@ -271,11 +256,6 @@ 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) - @staticmethod def get_normalized_package_name(validator_id: str): validator_id_parts = validator_id.split("/") diff --git a/tests/unit_tests/hub/test_validator_package_service.py b/tests/unit_tests/hub/test_validator_package_service.py index a407c312b..7f27d9c79 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 @@ -88,6 +89,7 @@ def test_closes_early_if_already_added(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 @@ -154,6 +156,7 @@ def test_appends_import_line_if_not_present(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 @@ -235,6 +238,7 @@ 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 @@ -335,7 +339,7 @@ class TestRunPostInstall: [ Manifest.from_dict( { - "id": "id", + "id": "guardrails-ai/id", "name": "name", "author": {"name": "me", "email": "me@me.me"}, "maintainers": [], @@ -350,7 +354,7 @@ class TestRunPostInstall: ), Manifest.from_dict( { - "id": "id", + "id": "guardrails-ai/id", "name": "name", "author": {"name": "me", "email": "me@me.me"}, "maintainers": [], @@ -391,7 +395,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,6 +410,7 @@ 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 @@ -421,7 +426,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,25 +471,17 @@ 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}" - ) + mock_reload_module.assert_called_once_with("guardrails_grhub_id") @pytest.mark.parametrize( "manifest,expected", @@ -492,7 +489,7 @@ def test_get_validator_from_manifest( ( Manifest.from_dict( { - "id": "id", + "id": "guardrails-ai/id", "name": "name", "author": {"name": "me", "email": "me@me.me"}, "maintainers": [], @@ -510,12 +507,12 @@ def test_get_validator_from_manifest( ( Manifest.from_dict( { - "id": "id", + "id": "guardrails-ai/id", "name": "name", "author": {"name": "me", "email": "me@me.me"}, "maintainers": [], "repository": {"url": "some-repo"}, - "namespace": "", + "namespace": "guardrails-ai", "packageName": "test-validator", "moduleName": "test_validator", "description": "description", @@ -534,98 +531,17 @@ def test_get_org_and_package_dirs(self, manifest, expected): assert actual == expected 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" ) @@ -653,7 +569,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,13 +582,10 @@ 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", @@ -680,15 +593,5 @@ def test_install_hub_module(self, mocker): ["--target=mock/install/directory", "--no-deps"], quiet=False, ), - call( - "inspect", - flags=["--path=mock/install/directory"], - format="json", - 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) From 0d1367a6a66d3319edac19054abb6323224c0901 Mon Sep 17 00:00:00 2001 From: Alejandro Date: Tue, 22 Oct 2024 23:04:13 -0700 Subject: [PATCH 09/15] fixed tests --- guardrails/hub/validator_package_service.py | 2 +- tests/unit_tests/hub/test_hub_install.py | 53 +++++++++---------- .../hub/test_validator_package_service.py | 38 ++++++++----- 3 files changed, 50 insertions(+), 43 deletions(-) diff --git a/guardrails/hub/validator_package_service.py b/guardrails/hub/validator_package_service.py index 41518bc3b..ee2b87e42 100644 --- a/guardrails/hub/validator_package_service.py +++ b/guardrails/hub/validator_package_service.py @@ -289,7 +289,7 @@ def install_hub_module( guardrails_token = settings.rc.token pip_flags = [ - f"--index-url=https://__token__:{guardrails_token}@e4c4zula06.execute-api.us-east-1.amazonaws.com/simple", + f"--index-url=https://__token__:{guardrails_token}@pypi.guardrailsai.com/simple", "--extra-index-url=https://pypi.org/simple", ] 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 7f27d9c79..9e8b10cf3 100644 --- a/tests/unit_tests/hub/test_validator_package_service.py +++ b/tests/unit_tests/hub/test_validator_package_service.py @@ -48,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": [], @@ -69,14 +69,18 @@ def test_closes_early_if_already_added(self, mocker): mock_open.side_effect = [hub_init_file, ns_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 + mock_ns_read.return_value = ( + "from guardrails_ai_grhub_id import helper, TestValidator" # noqa + ) ns_seek_spy = mocker.spy(ns_init_file, "seek") ns_write_spy = mocker.spy(ns_init_file, "write") @@ -115,7 +119,7 @@ def test_closes_early_if_already_added(self, mocker): 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": [], @@ -176,7 +180,7 @@ 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) @@ -194,14 +198,14 @@ def test_appends_import_line_if_not_present(self, mocker): 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 + "from guardrails_ai_grhub_id 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": [], @@ -222,7 +226,7 @@ def test_creates_namespace_init_if_not_exists(self, mocker): mock_open.side_effect = [hub_init_file, ns_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_hub_read.return_value = "from guardrails_ai_grhub_id import TestValidator" # noqa mock_ns_read = mocker.patch.object(ns_init_file, "read") mock_ns_read.return_value = "" @@ -256,7 +260,7 @@ def test_creates_namespace_init_if_not_exists(self, mocker): 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 + "from guardrails_ai_grhub_id import TestValidator" # noqa ) assert ns_close_spy.call_count == 1 @@ -417,7 +421,7 @@ def test_runs_script_if_exists(self, mocker): 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 ] ) @@ -512,7 +516,7 @@ def test_get_validator_from_manifest(self, mock_reload_module): "author": {"name": "me", "email": "me@me.me"}, "maintainers": [], "repository": {"url": "some-repo"}, - "namespace": "guardrails-ai", + "namespace": "", "packageName": "test-validator", "moduleName": "test_validator", "description": "description", @@ -545,6 +549,11 @@ def test_install_hub_module(self, mocker): 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": [ { @@ -589,8 +598,11 @@ def test_install_hub_module(self, mocker): pip_calls = [ call( "install", - "mock-install-url", - ["--target=mock/install/directory", "--no-deps"], + "guardrails-ai-grhub-id", + [ + "--index-url=https://__token__:mock-token@pypi.guardrailsai.com/simple", + "--extra-index-url=https://pypi.org/simple", + ], quiet=False, ), ] From b8d4d18977ea155d990497f57bd4f69357156817 Mon Sep 17 00:00:00 2001 From: Alejandro Date: Wed, 23 Oct 2024 09:55:35 -0700 Subject: [PATCH 10/15] fix f-string syntax error --- guardrails/cli/hub/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/guardrails/cli/hub/utils.py b/guardrails/cli/hub/utils.py index b4af8283f..0ab34a5cb 100644 --- a/guardrails/cli/hub/utils.py +++ b/guardrails/cli/hub/utils.py @@ -73,8 +73,8 @@ def pip_process( ( 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()}" + f"stderr: {(exc.stderr or '').strip()}\n" + f"stdout: {(exc.stdout or '').strip()}" ) ) # Re-raise the error or handle it accordingly From 5b4d6b8a097af8f2fe81d6f90a25220b8fddebb0 Mon Sep 17 00:00:00 2001 From: Alejandro Date: Wed, 23 Oct 2024 13:56:24 -0700 Subject: [PATCH 11/15] fixed uninstall, list validators cli cmds, and tests --- guardrails/cli/hub/list.py | 5 +- guardrails/cli/hub/uninstall.py | 58 +++++++------------- guardrails/cli/hub/utils.py | 13 +---- tests/unit_tests/cli/hub/test_install.py | 51 ++++++++--------- tests/unit_tests/cli/hub/test_list.py | 2 +- tests/unit_tests/cli/hub/test_uninstall.py | 64 ++++++++++++---------- 6 files changed, 83 insertions(+), 110 deletions(-) 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..8d298b885 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,9 @@ def uninstall( ), ): """Uninstall a validator from the Hub.""" + from guardrails.hub.validator_package_service import ValidatorPackageService + + print(f"ValidatorPackageService: {ValidatorPackageService}") if not package_uri.startswith("hub://"): logger.error("Invalid URI!") sys.exit(1) @@ -98,11 +80,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 0ab34a5cb..5714f32d8 100644 --- a/guardrails/cli/hub/utils.py +++ b/guardrails/cli/hub/utils.py @@ -58,7 +58,7 @@ def pip_process( except Exception: logger.debug( f"JSON parse exception in decoding output from pip {action}" - f"{package}. Falling back to accumulating the byte stream", + f" {package}. Falling back to accumulating the byte stream", ) accumulator = {} parsed = BytesHeaderParser().parsebytes(result.stdout.encode()) @@ -77,20 +77,13 @@ def pip_process( f"stdout: {(exc.stdout or '').strip()}" ) ) - # Re-raise the error or handle it accordingly - raise + sys.exit(1) except Exception as e: logger.error( f"An unexpected exception occurred while trying to {action} {package}!", e, ) - raise - - -def get_site_packages_location() -> str: - output = pip_process("show", "pip", format=json_format) - pip_location = output["Location"] # type: ignore - return pip_location + sys.exit(1) def get_org_and_package_dirs(manifest: Manifest) -> List[str]: 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 + ) From 106cc86e59a01cd43153acfbb027670e36f993b1 Mon Sep 17 00:00:00 2001 From: Alejandro Date: Wed, 23 Oct 2024 13:58:27 -0700 Subject: [PATCH 12/15] removed unused methods from cli hub utils --- guardrails/cli/hub/utils.py | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/guardrails/cli/hub/utils.py b/guardrails/cli/hub/utils.py index 5714f32d8..c3f1b924a 100644 --- a/guardrails/cli/hub/utils.py +++ b/guardrails/cli/hub/utils.py @@ -9,9 +9,7 @@ from email.parser import BytesHeaderParser from typing import List, Union -from pydash.strings import snake_case -from guardrails_hub_types import Manifest json_format: Literal["json"] = "json" string_format: Literal["string"] = "string" @@ -84,16 +82,3 @@ def pip_process( e, ) sys.exit(1) - - -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) From f73adf73e57389b7306016861596a4a65b6d7437 Mon Sep 17 00:00:00 2001 From: Alejandro Date: Wed, 23 Oct 2024 14:46:21 -0700 Subject: [PATCH 13/15] Removed writes to org namespaced init files during install, removed unused methods --- guardrails/hub/validator_package_service.py | 33 ------------- .../hub/test_validator_package_service.py | 47 ------------------- 2 files changed, 80 deletions(-) diff --git a/guardrails/hub/validator_package_service.py b/guardrails/hub/validator_package_service.py index ee2b87e42..f54ccdbc9 100644 --- a/guardrails/hub/validator_package_service.py +++ b/guardrails/hub/validator_package_service.py @@ -7,7 +7,6 @@ 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.logger import logger as guardrails_logger @@ -104,20 +103,9 @@ def get_validator_from_manifest(manifest: Manifest) -> ModuleType: # 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): validator_id = manifest.id - org_package = ValidatorPackageService.get_org_and_package_dirs(manifest) exports: List[str] = manifest.exports or [] sorted_exports = sorted(exports, reverse=True) @@ -141,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: diff --git a/tests/unit_tests/hub/test_validator_package_service.py b/tests/unit_tests/hub/test_validator_package_service.py index 9e8b10cf3..12030ba34 100644 --- a/tests/unit_tests/hub/test_validator_package_service.py +++ b/tests/unit_tests/hub/test_validator_package_service.py @@ -487,53 +487,6 @@ def test_get_validator_from_manifest(self, mock_reload_module): mock_reload_module.assert_called_once_with("guardrails_grhub_id") - @pytest.mark.parametrize( - "manifest,expected", - [ - ( - Manifest.from_dict( - { - "id": "guardrails-ai/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": "guardrails-ai/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 - def test_get_module_name_valid(self): module_name, module_version = ValidatorPackageService.get_validator_id( "hub://test-module>=1.0.0" From c0dfcce6552eb511edf9cf5663b5c99479d48d6b Mon Sep 17 00:00:00 2001 From: Alejandro Date: Wed, 23 Oct 2024 17:36:21 -0700 Subject: [PATCH 14/15] fix tests associated with removal of namespace init file --- .../hub/test_validator_package_service.py | 86 ++----------------- 1 file changed, 6 insertions(+), 80 deletions(-) diff --git a/tests/unit_tests/hub/test_validator_package_service.py b/tests/unit_tests/hub/test_validator_package_service.py index 12030ba34..7fe7129a3 100644 --- a/tests/unit_tests/hub/test_validator_package_service.py +++ b/tests/unit_tests/hub/test_validator_package_service.py @@ -64,9 +64,8 @@ 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 = ( @@ -77,29 +76,14 @@ def test_closes_early_if_already_added(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 = ( - "from guardrails_ai_grhub_id 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) @@ -108,14 +92,6 @@ 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( { @@ -135,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 @@ -146,27 +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) @@ -187,21 +149,6 @@ def test_appends_import_line_if_not_present(self, mocker): 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_ai_grhub_id import TestValidator" # noqa - ) - assert ns_close_spy.call_count == 1 - def test_creates_namespace_init_if_not_exists(self, mocker): manifest = Manifest.from_dict( { @@ -221,20 +168,12 @@ 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_ai_grhub_id 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_is_file = mocker.patch( "guardrails.hub.validator_package_service.os.path.isfile" ) @@ -245,25 +184,12 @@ def test_creates_namespace_init_if_not_exists(self, mocker): 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_ai_grhub_id import TestValidator" # noqa - ) - assert ns_close_spy.call_count == 1 - class TestReloadModule: @patch("guardrails.hub.validator_package_service.importlib") From a82a412d37d0f242781c14f4d6f9abd8116ff30b Mon Sep 17 00:00:00 2001 From: Alejandro Esquivel Date: Mon, 28 Oct 2024 15:33:43 -0700 Subject: [PATCH 15/15] Update guardrails/cli/hub/uninstall.py --- guardrails/cli/hub/uninstall.py | 1 - 1 file changed, 1 deletion(-) diff --git a/guardrails/cli/hub/uninstall.py b/guardrails/cli/hub/uninstall.py index 8d298b885..5fdcd5755 100644 --- a/guardrails/cli/hub/uninstall.py +++ b/guardrails/cli/hub/uninstall.py @@ -63,7 +63,6 @@ def uninstall( """Uninstall a validator from the Hub.""" from guardrails.hub.validator_package_service import ValidatorPackageService - print(f"ValidatorPackageService: {ValidatorPackageService}") if not package_uri.startswith("hub://"): logger.error("Invalid URI!") sys.exit(1)