From e9565efe5e6d2bb06eaaafad117a390adc83493d Mon Sep 17 00:00:00 2001 From: Caleb Courier Date: Tue, 6 Feb 2024 14:28:01 -0800 Subject: [PATCH 1/9] polyfills for 3.8 --- guardrails/cli/hub/install.py | 11 ++++++++- guardrails/cli/server/module_manifest.py | 29 +++++++++++++++--------- guardrails/cli/server/serializeable.py | 23 +++++++++++++------ 3 files changed, 44 insertions(+), 19 deletions(-) diff --git a/guardrails/cli/hub/install.py b/guardrails/cli/hub/install.py index 05a2eb6fb..96fdd746e 100644 --- a/guardrails/cli/hub/install.py +++ b/guardrails/cli/hub/install.py @@ -16,6 +16,15 @@ from guardrails.cli.server.hub_client import fetch_module from guardrails.cli.server.module_manifest import ModuleManifest + +def removesuffix (string: str, suffix: str) -> str: + if sys.version_info.minor >= 9: + return string.removesuffix(suffix) + else: + if string.endswith(suffix): + return string[:-len(suffix)] + + string_format: Literal["string"] = "string" json_format: Literal["json"] = "json" @@ -125,7 +134,7 @@ def run_post_install(manifest: ModuleManifest): post_install_script = manifest.post_install if post_install_script: module_name = manifest.module_name - post_install_module = post_install_script.removesuffix(".py") + post_install_module = removesuffix(post_install_script, ".py") relative_path = ".".join([*org_package, module_name]) importlib.import_module(f"guardrails.hub.{relative_path}.{post_install_module}") diff --git a/guardrails/cli/server/module_manifest.py b/guardrails/cli/server/module_manifest.py index 77512cc43..e53f249f7 100644 --- a/guardrails/cli/server/module_manifest.py +++ b/guardrails/cli/server/module_manifest.py @@ -1,7 +1,8 @@ from dataclasses import dataclass, field from typing import Any, Dict, List, Optional -from guardrails.cli.server.serializeable import Serializeable +from pydash.strings import snake_case +from guardrails.cli.server.serializeable import Serializeable, SerializeableJSONEncoder @dataclass @@ -25,6 +26,8 @@ class ModuleTags(Serializeable): @dataclass class ModuleManifest(Serializeable): + id: str + name: str author: Contributor maintainers: List[Contributor] repository: Repository @@ -33,21 +36,25 @@ class ModuleManifest(Serializeable): module_name: str exports: List[str] tags: ModuleTags + requires_auth: Optional[bool] = True post_install: Optional[str] = None index: Optional[str] = None # @override @classmethod def from_dict(cls, data: Dict[str, Any]): + init_kwargs = { + snake_case(k): data.get(k) for k in data + } + init_kwargs["encoder"] = init_kwargs.get("encoder", SerializeableJSONEncoder) + author = init_kwargs.pop("author", {}) + maintainers = init_kwargs.pop("maintainers", []) + repository = init_kwargs.pop("repository", {}) + tags = init_kwargs.pop("tags", {}) return cls( - Contributor.from_dict(data.get("author", {})), - [Contributor.from_dict(m) for m in data.get("maintainers", [])], - Repository.from_dict(data.get("repository", {})), - data.get("namespace"), # type: ignore - data.get("packageName"), # type: ignore - data.get("moduleName"), # type: ignore - data.get("exports"), # type: ignore - ModuleTags.from_dict(data.get("tags", {})), - data.get("postInstall"), - data.get("index"), + **init_kwargs, + author=Contributor.from_dict(author), + maintainers=[Contributor.from_dict(m) for m in maintainers], + repository=Repository.from_dict(repository), + tags=ModuleTags.from_dict(tags), ) diff --git a/guardrails/cli/server/serializeable.py b/guardrails/cli/server/serializeable.py index 7da328555..d0b8dae43 100644 --- a/guardrails/cli/server/serializeable.py +++ b/guardrails/cli/server/serializeable.py @@ -1,3 +1,4 @@ +import sys import inspect import json from dataclasses import InitVar, asdict, dataclass, field, is_dataclass @@ -6,6 +7,12 @@ from pydash.strings import snake_case +def get_annotations(obj): + if sys.version_info.minor >= 10: + return inspect.get_annotations(obj) + else: + return obj.__annotations__ + class SerializeableJSONEncoder(JSONEncoder): def default(self, o): @@ -14,22 +21,24 @@ def default(self, o): return super().default(o) +encoder_kwargs = {} +if sys.version_info.minor >= 10: + encoder_kwargs["kw_only"] = True + encoder_kwargs["default"] = SerializeableJSONEncoder + @dataclass class Serializeable: - encoder: InitVar[JSONEncoder] = field( - kw_only=True, default=SerializeableJSONEncoder # type: ignore - ) + encoder: InitVar[JSONEncoder] = field(**encoder_kwargs) @classmethod def from_dict(cls, data: Dict[str, Any]): - annotations = inspect.get_annotations(cls) + annotations = get_annotations(cls) attributes = dict.keys(annotations) - kwargs = {k: data.get(k) for k in data if k in attributes} snake_case_kwargs = { snake_case(k): data.get(k) for k in data if snake_case(k) in attributes } - kwargs.update(snake_case_kwargs) - return cls(**kwargs) # type: ignore + snake_case_kwargs["encoder"] = snake_case_kwargs.get("encoder", SerializeableJSONEncoder) + return cls(**snake_case_kwargs) # type: ignore @property def __dict__(self) -> Dict[str, Any]: From 3c8f216cde8d22798d7b0c36367b7951c3399848 Mon Sep 17 00:00:00 2001 From: Caleb Courier Date: Tue, 6 Feb 2024 14:47:44 -0800 Subject: [PATCH 2/9] add link to tokens page --- guardrails/cli/hub/credentials.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guardrails/cli/hub/credentials.py b/guardrails/cli/hub/credentials.py index 7a67928f1..34f534c81 100644 --- a/guardrails/cli/hub/credentials.py +++ b/guardrails/cli/hub/credentials.py @@ -33,6 +33,6 @@ def from_rc_file() -> "Credentials": logger.error( "Guardrails Hub credentials not found!" "You will need to sign up to use any authenticated Validators here:" - "{insert url}" + "https://hub.guardrailsai.com/tokens" ) return Credentials() From 3e1f446767f29b85ec4ad98984c6951fa6f60585 Mon Sep 17 00:00:00 2001 From: Caleb Courier Date: Tue, 6 Feb 2024 14:49:34 -0800 Subject: [PATCH 3/9] make missing rc file a warning instead of an error --- guardrails/cli/hub/credentials.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/guardrails/cli/hub/credentials.py b/guardrails/cli/hub/credentials.py index 34f534c81..f51d4306b 100644 --- a/guardrails/cli/hub/credentials.py +++ b/guardrails/cli/hub/credentials.py @@ -29,8 +29,8 @@ def from_rc_file() -> "Credentials": return Credentials.from_dict(creds) except FileNotFoundError as e: - logger.error(e) - logger.error( + logger.warning(e) + logger.warning( "Guardrails Hub credentials not found!" "You will need to sign up to use any authenticated Validators here:" "https://hub.guardrailsai.com/tokens" From 56e3882bb7fff4bd18bbf27b35b9603e50b82368 Mon Sep 17 00:00:00 2001 From: Zayd Simjee Date: Tue, 6 Feb 2024 16:43:47 -0800 Subject: [PATCH 4/9] remove compile, add help for hub --- guardrails/cli/__init__.py | 3 +-- guardrails/cli/compile.py | 25 ------------------------- 2 files changed, 1 insertion(+), 27 deletions(-) delete mode 100644 guardrails/cli/compile.py diff --git a/guardrails/cli/__init__.py b/guardrails/cli/__init__.py index 4198e7db4..b5168127f 100644 --- a/guardrails/cli/__init__.py +++ b/guardrails/cli/__init__.py @@ -1,10 +1,9 @@ -import guardrails.cli.compile # noqa import guardrails.cli.configure # noqa import guardrails.cli.validate # noqa from guardrails.cli.guardrails import guardrails as cli from guardrails.cli.hub import hub -cli.add_typer(hub, name="hub") +cli.add_typer(hub, name="hub", help="Manage validators installed from the Guardrails Hub.") if __name__ == "__main__": diff --git a/guardrails/cli/compile.py b/guardrails/cli/compile.py deleted file mode 100644 index d759a60d8..000000000 --- a/guardrails/cli/compile.py +++ /dev/null @@ -1,25 +0,0 @@ -import typer - -from guardrails.cli.guardrails import guardrails -from guardrails.cli.logger import logger - - -def compile_rail(rail: str, out: str) -> None: - """Compile guardrails from the guardrails.yml file.""" - raise NotImplementedError("Currently compiling rail is not supported.") - - -@guardrails.command() -def compile( - rail: str = typer.Argument( - ..., help="Path to the rail spec.", exists=True, file_okay=True, dir_okay=False - ), - out: str = typer.Option( - default=".rail_output", - help="Path to the compiled output directory.", - file_okay=False, - dir_okay=True, - ), -): - """Compile guardrails from a `rail` spec.""" - logger.error("Not supported yet. Use `validate` instead.") From bb1fc04d7be6db4f7e4d24df05c2fadb610b108f Mon Sep 17 00:00:00 2001 From: Caleb Courier Date: Tue, 6 Feb 2024 17:18:40 -0800 Subject: [PATCH 5/9] validate creds after configure, success or error message --- guardrails/cli/configure.py | 29 ++++++++++- guardrails/cli/hub/install.py | 4 +- guardrails/cli/server/auth.py | 2 +- guardrails/cli/{hub => server}/credentials.py | 0 guardrails/cli/server/hub_client.py | 51 +++++++++++++++++-- guardrails/cli/server/module_manifest.py | 5 +- guardrails/cli/server/serializeable.py | 8 ++- .../cli/hub/{ => server}/test_credentials.py | 2 +- 8 files changed, 86 insertions(+), 15 deletions(-) rename guardrails/cli/{hub => server}/credentials.py (100%) rename tests/unit_tests/cli/hub/{ => server}/test_credentials.py (95%) diff --git a/guardrails/cli/configure.py b/guardrails/cli/configure.py index 74e2adce0..4fb161f98 100644 --- a/guardrails/cli/configure.py +++ b/guardrails/cli/configure.py @@ -1,4 +1,5 @@ import os +import sys import uuid from os.path import expanduser from typing import Optional @@ -6,7 +7,8 @@ import typer from guardrails.cli.guardrails import guardrails -from guardrails.cli.logger import logger +from guardrails.cli.logger import LEVELS, logger +from guardrails.cli.server.hub_client import AuthenticationError, check_auth def save_configuration_file( @@ -44,3 +46,28 @@ def configure( client_secret = typer.prompt("Client secret", hide_input=True) logger.info("Configuring...") save_configuration_file(client_id, client_secret, no_metrics) # type: ignore + logger.info("Validating credentials...") + try: + check_auth() + success_message = """ + + Login successful. + + Get started by installing a validator from the Guardrails Hub! + + guardrails hub install hub://guardrails/lowercase + + Find more validators at https://hub.guardrailsai.com + """ + logger.log(level=LEVELS.get("SUCCESS"), msg=success_message) # type: ignore + except AuthenticationError as auth_error: + logger.error(auth_error) + logger.error( + """ + Check that your Client ID and Client secret are correct and try again. + """ + ) + except Exception as e: + logger.error("An unexpected error occurred!") + logger.error(e) + sys.exit(1) diff --git a/guardrails/cli/hub/install.py b/guardrails/cli/hub/install.py index 96fdd746e..0dea65878 100644 --- a/guardrails/cli/hub/install.py +++ b/guardrails/cli/hub/install.py @@ -17,12 +17,12 @@ from guardrails.cli.server.module_manifest import ModuleManifest -def removesuffix (string: str, suffix: str) -> str: +def removesuffix(string: str, suffix: str) -> str: if sys.version_info.minor >= 9: return string.removesuffix(suffix) else: if string.endswith(suffix): - return string[:-len(suffix)] + return string[: -len(suffix)] string_format: Literal["string"] = "string" diff --git a/guardrails/cli/server/auth.py b/guardrails/cli/server/auth.py index f0152b352..e68833d6c 100644 --- a/guardrails/cli/server/auth.py +++ b/guardrails/cli/server/auth.py @@ -1,7 +1,7 @@ import http.client import json -from guardrails.cli.hub.credentials import Credentials +from guardrails.cli.server.credentials import Credentials def authenticate(creds: Credentials) -> str: diff --git a/guardrails/cli/hub/credentials.py b/guardrails/cli/server/credentials.py similarity index 100% rename from guardrails/cli/hub/credentials.py rename to guardrails/cli/server/credentials.py diff --git a/guardrails/cli/server/hub_client.py b/guardrails/cli/server/hub_client.py index 2049859bc..33e65d411 100644 --- a/guardrails/cli/server/hub_client.py +++ b/guardrails/cli/server/hub_client.py @@ -4,9 +4,9 @@ import requests -from guardrails.cli.hub.credentials import Credentials from guardrails.cli.logger import logger from guardrails.cli.server.auth import authenticate +from guardrails.cli.server.credentials import Credentials from guardrails.cli.server.module_manifest import ModuleManifest validator_hub_service = "https://so4sg4q4pb.execute-api.us-east-1.amazonaws.com" @@ -15,6 +15,15 @@ ) +class AuthenticationError(Exception): + pass + + +class HttpError(Exception): + status: int + message: str + + def fetch(url: str, token: Optional[str], anonymousUserId: Optional[str]): try: # For Debugging @@ -29,9 +38,14 @@ def fetch(url: str, token: Optional[str], anonymousUserId: Optional[str]): if not req.ok: logger.error(req.status_code) logger.error(body.get("message")) - sys.exit(1) + http_error = HttpError() + http_error.status = req.status_code + http_error.message = body.get("message") + raise http_error return body + except HttpError as http_e: + raise http_e except Exception as e: logger.error("An unexpected error occurred!", e) sys.exit(1) @@ -52,6 +66,33 @@ def fetch_module(module_name: str) -> ModuleManifest: creds = Credentials.from_rc_file() token = authenticate(creds) - module_manifest_json = fetch_module_manifest(module_name, token, creds.id) - module_manifest = ModuleManifest.from_dict(module_manifest_json) - return module_manifest + try: + module_manifest_json = fetch_module_manifest(module_name, token, creds.id) + module_manifest = ModuleManifest.from_dict(module_manifest_json) + if not module_manifest: + logger.error(f"Failed to install hub://{module_name}") + sys.exit(1) + return module_manifest + except HttpError: + logger.error(f"Failed to install hub://{module_name}") + sys.exit(1) + except Exception as e: + logger.error("An unexpected error occurred!", e) + sys.exit(1) + + +def check_auth(): + try: + creds = Credentials.from_rc_file() + token = authenticate(creds) + auth_url = f"{validator_hub_service}/auth" + response = fetch(auth_url, token, creds.id) + print("check_auth fetch response: ", response) + if not response: + raise AuthenticationError("Failed to authenticate!") + except HttpError as http_error: + logger.error(http_error) + raise AuthenticationError("Failed to authenticate!") + except Exception as e: + logger.error("An unexpected error occurred!", e) + raise AuthenticationError("Failed to authenticate!") diff --git a/guardrails/cli/server/module_manifest.py b/guardrails/cli/server/module_manifest.py index e53f249f7..df66fb77e 100644 --- a/guardrails/cli/server/module_manifest.py +++ b/guardrails/cli/server/module_manifest.py @@ -2,6 +2,7 @@ from typing import Any, Dict, List, Optional from pydash.strings import snake_case + from guardrails.cli.server.serializeable import Serializeable, SerializeableJSONEncoder @@ -43,9 +44,7 @@ class ModuleManifest(Serializeable): # @override @classmethod def from_dict(cls, data: Dict[str, Any]): - init_kwargs = { - snake_case(k): data.get(k) for k in data - } + init_kwargs = {snake_case(k): data.get(k) for k in data} init_kwargs["encoder"] = init_kwargs.get("encoder", SerializeableJSONEncoder) author = init_kwargs.pop("author", {}) maintainers = init_kwargs.pop("maintainers", []) diff --git a/guardrails/cli/server/serializeable.py b/guardrails/cli/server/serializeable.py index d0b8dae43..8d3252872 100644 --- a/guardrails/cli/server/serializeable.py +++ b/guardrails/cli/server/serializeable.py @@ -1,12 +1,13 @@ -import sys import inspect import json +import sys from dataclasses import InitVar, asdict, dataclass, field, is_dataclass from json import JSONEncoder from typing import Any, Dict from pydash.strings import snake_case + def get_annotations(obj): if sys.version_info.minor >= 10: return inspect.get_annotations(obj) @@ -26,6 +27,7 @@ def default(self, o): encoder_kwargs["kw_only"] = True encoder_kwargs["default"] = SerializeableJSONEncoder + @dataclass class Serializeable: encoder: InitVar[JSONEncoder] = field(**encoder_kwargs) @@ -37,7 +39,9 @@ def from_dict(cls, data: Dict[str, Any]): snake_case_kwargs = { snake_case(k): data.get(k) for k in data if snake_case(k) in attributes } - snake_case_kwargs["encoder"] = snake_case_kwargs.get("encoder", SerializeableJSONEncoder) + snake_case_kwargs["encoder"] = snake_case_kwargs.get( + "encoder", SerializeableJSONEncoder + ) return cls(**snake_case_kwargs) # type: ignore @property diff --git a/tests/unit_tests/cli/hub/test_credentials.py b/tests/unit_tests/cli/hub/server/test_credentials.py similarity index 95% rename from tests/unit_tests/cli/hub/test_credentials.py rename to tests/unit_tests/cli/hub/server/test_credentials.py index 6ad9a4481..87b2d2192 100644 --- a/tests/unit_tests/cli/hub/test_credentials.py +++ b/tests/unit_tests/cli/hub/server/test_credentials.py @@ -22,7 +22,7 @@ def test_from_rc_file(mocker): readlines_spy.return_value = ["key1=val1", "key2=val2"] close_spy = mocker.spy(mock_file, "close") - from guardrails.cli.hub.credentials import Credentials + from guardrails.cli.server.credentials import Credentials mock_from_dict = mocker.patch.object(Credentials, "from_dict") From bb8ed355c19ab435c372198b4591c024c5764519 Mon Sep 17 00:00:00 2001 From: Caleb Courier Date: Tue, 6 Feb 2024 17:19:22 -0800 Subject: [PATCH 6/9] lint fix --- guardrails/cli/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/guardrails/cli/__init__.py b/guardrails/cli/__init__.py index b5168127f..833ada8b4 100644 --- a/guardrails/cli/__init__.py +++ b/guardrails/cli/__init__.py @@ -3,7 +3,9 @@ from guardrails.cli.guardrails import guardrails as cli from guardrails.cli.hub import hub -cli.add_typer(hub, name="hub", help="Manage validators installed from the Guardrails Hub.") +cli.add_typer( + hub, name="hub", help="Manage validators installed from the Guardrails Hub." +) if __name__ == "__main__": From 3eb335f3b074c703fefa7336c26ef263a218b034 Mon Sep 17 00:00:00 2001 From: Caleb Courier Date: Tue, 6 Feb 2024 17:38:40 -0800 Subject: [PATCH 7/9] fix tests --- .../cli/hub/server/test_credentials.py | 4 +-- tests/unit_tests/cli/hub/test_install.py | 29 +++++++++++++++++++ tests/unit_tests/cli/test_compile.py | 18 ------------ tests/unit_tests/cli/test_configure.py | 11 +++++-- 4 files changed, 40 insertions(+), 22 deletions(-) delete mode 100644 tests/unit_tests/cli/test_compile.py diff --git a/tests/unit_tests/cli/hub/server/test_credentials.py b/tests/unit_tests/cli/hub/server/test_credentials.py index 87b2d2192..3366c12d7 100644 --- a/tests/unit_tests/cli/hub/server/test_credentials.py +++ b/tests/unit_tests/cli/hub/server/test_credentials.py @@ -7,7 +7,7 @@ def test_from_rc_file(mocker): mocker.patch("nltk.data.find") mocker.patch("nltk.download") - expanduser_mock = mocker.patch("guardrails.cli.hub.credentials.expanduser") + expanduser_mock = mocker.patch("guardrails.cli.server.credentials.expanduser") expanduser_mock.return_value = "/Home" import os @@ -15,7 +15,7 @@ def test_from_rc_file(mocker): join_spy = mocker.spy(os.path, "join") mock_file = MockFile() - mock_open = mocker.patch("guardrails.cli.hub.credentials.open") + mock_open = mocker.patch("guardrails.cli.server.credentials.open") mock_open.return_value = mock_file readlines_spy = mocker.patch.object(mock_file, "readlines") diff --git a/tests/unit_tests/cli/hub/test_install.py b/tests/unit_tests/cli/hub/test_install.py index a494a8810..cd8c805b2 100644 --- a/tests/unit_tests/cli/hub/test_install.py +++ b/tests/unit_tests/cli/hub/test_install.py @@ -4,6 +4,7 @@ import pytest from guardrails.cli.server.module_manifest import ModuleManifest, ModuleTags, Repository +from guardrails.cli.server.serializeable import SerializeableJSONEncoder from tests.unit_tests.mocks.mock_file import MockFile @@ -26,6 +27,8 @@ def test_happy_path(self, mocker): mock_fetch_module = mocker.patch("guardrails.cli.hub.install.fetch_module") manifest = ModuleManifest( + "id", + "name", "me", [], Repository(url="some-repo"), @@ -213,6 +216,8 @@ def test_get_site_packages_location(mocker): [ ( ModuleManifest( + "id", + "name", "me", [], Repository(url="some-repo"), @@ -226,6 +231,8 @@ def test_get_site_packages_location(mocker): ), ( ModuleManifest( + "id", + "name", "me", [], Repository(url="some-repo"), @@ -249,6 +256,8 @@ def test_get_org_and_package_dirs(manifest, expected): def test_get_hub_directory(): manifest = ModuleManifest( + "id", + "name", "me", [], Repository(url="some-repo"), @@ -269,6 +278,8 @@ def test_get_hub_directory(): class TestAddToHubInits: def test_closes_early_if_already_added(self, mocker): manifest = ModuleManifest( + "id", + "name", "me", [], Repository(url="some-repo"), @@ -328,6 +339,8 @@ def test_closes_early_if_already_added(self, mocker): def test_appends_import_line_if_not_present(self, mocker): manifest = ModuleManifest( + "id", + "name", "me", [], Repository(url="some-repo"), @@ -406,6 +419,8 @@ def test_appends_import_line_if_not_present(self, mocker): def test_creates_namespace_init_if_not_exists(self, mocker): manifest = ModuleManifest( + "id", + "name", "me", [], Repository(url="some-repo"), @@ -464,6 +479,8 @@ class TestRunPostInstall: "manifest", [ ModuleManifest( + "id", + "name", "me", [], Repository(url="some-repo"), @@ -474,6 +491,8 @@ class TestRunPostInstall: ModuleTags(), ), ModuleManifest( + "id", + "name", "me", [], Repository(url="some-repo"), @@ -503,6 +522,8 @@ def test_runs_script_if_exists(self, mocker): from guardrails.cli.hub.install import run_post_install manifest = ModuleManifest( + "id", + "name", "me", [], Repository(url="some-repo"), @@ -527,6 +548,8 @@ def test_runs_script_if_exists(self, mocker): [ ( ModuleManifest( + "id", + "name", "me", [], Repository(url="some-repo"), @@ -540,6 +563,8 @@ def test_runs_script_if_exists(self, mocker): ), ( ModuleManifest( + "id", + "name", "me", [], Repository(url="git+some-repo"), @@ -554,6 +579,8 @@ def test_runs_script_if_exists(self, mocker): ), ( ModuleManifest( + "id", + "name", "me", [], Repository(url="git+some-repo", branch="prod"), @@ -611,6 +638,8 @@ def test_install_hub_module(mocker): from guardrails.cli.hub.install import install_hub_module manifest = ModuleManifest( + "id", + "name", "me", [], Repository(url="some-repo"), diff --git a/tests/unit_tests/cli/test_compile.py b/tests/unit_tests/cli/test_compile.py deleted file mode 100644 index 85ae19ccb..000000000 --- a/tests/unit_tests/cli/test_compile.py +++ /dev/null @@ -1,18 +0,0 @@ -import pytest - -from guardrails.cli.compile import compile, compile_rail, logger - - -def test_compile_rail(): - with pytest.raises(NotImplementedError) as nie: - compile_rail("my_spec.rail", ".rail_output") - assert nie is not None - assert str(nie) == "Currently compiling rail is not supported." - - -def test_compile(mocker): - error_log_mock = mocker.patch.object(logger, "error") - - compile("my_spec.rail") - - error_log_mock.assert_called_once_with("Not supported yet. Use `validate` instead.") diff --git a/tests/unit_tests/cli/test_configure.py b/tests/unit_tests/cli/test_configure.py index e50fa28b7..757415ee6 100644 --- a/tests/unit_tests/cli/test_configure.py +++ b/tests/unit_tests/cli/test_configure.py @@ -24,7 +24,10 @@ def test_configure(mocker, client_id, client_secret, no_metrics): configure(client_id, client_secret, no_metrics) - mock_logger_info.assert_called_once_with("Configuring...") + assert mock_logger_info.call_count == 2 + expected_calls = [call("Configuring..."), call("Validating credentials...")] + mock_logger_info.assert_has_calls(expected_calls) + mock_save_configuration_file.assert_called_once_with( client_id, client_secret, no_metrics ) @@ -45,7 +48,11 @@ def test_configure_prompting(mocker): assert mock_typer_prompt.call_count == 2 expected_calls = [call("Client ID"), call("Client secret", hide_input=True)] mock_typer_prompt.assert_has_calls(expected_calls) - mock_logger_info.assert_called_once_with("Configuring...") + + assert mock_logger_info.call_count == 2 + expected_calls = [call("Configuring..."), call("Validating credentials...")] + mock_logger_info.assert_has_calls(expected_calls) + mock_save_configuration_file.assert_called_once_with("id", "secret", False) From de905eff6337ec592f39fcc6a62b9db97b257f07 Mon Sep 17 00:00:00 2001 From: Caleb Courier Date: Wed, 7 Feb 2024 09:33:19 -0800 Subject: [PATCH 8/9] lint, refactor high ordered client methods --- guardrails/cli/configure.py | 20 ++++++++++--------- guardrails/cli/hub/install.py | 4 ++-- guardrails/cli/server/auth.py | 2 +- guardrails/cli/server/hub_client.py | 19 +++++++++++------- tests/unit_tests/cli/hub/test_install.py | 7 +++---- .../cli/{hub => }/server/test_auth.py | 2 +- .../cli/{hub => }/server/test_credentials.py | 0 .../cli/{hub => }/server/test_hub_client.py | 12 +++++++++++ tests/unit_tests/cli/test_configure.py | 9 ++++++--- 9 files changed, 48 insertions(+), 27 deletions(-) rename tests/unit_tests/cli/{hub => }/server/test_auth.py (69%) rename tests/unit_tests/cli/{hub => }/server/test_credentials.py (100%) rename tests/unit_tests/cli/{hub => }/server/test_hub_client.py (60%) diff --git a/guardrails/cli/configure.py b/guardrails/cli/configure.py index 4fb161f98..f7a4296d5 100644 --- a/guardrails/cli/configure.py +++ b/guardrails/cli/configure.py @@ -8,7 +8,7 @@ from guardrails.cli.guardrails import guardrails from guardrails.cli.logger import LEVELS, logger -from guardrails.cli.server.hub_client import AuthenticationError, check_auth +from guardrails.cli.server.hub_client import AuthenticationError, get_auth def save_configuration_file( @@ -40,15 +40,16 @@ def configure( ), ): """Set the global configuration for the Guardrails CLI and Hub.""" - if not client_id: - client_id = typer.prompt("Client ID") - if not client_secret: - client_secret = typer.prompt("Client secret", hide_input=True) - logger.info("Configuring...") - save_configuration_file(client_id, client_secret, no_metrics) # type: ignore - logger.info("Validating credentials...") try: - check_auth() + if not client_id: + client_id = typer.prompt("Client ID") + if not client_secret: + client_secret = typer.prompt("Client secret", hide_input=True) + logger.info("Configuring...") + save_configuration_file(client_id, client_secret, no_metrics) # type: ignore + + logger.info("Validating credentials...") + get_auth() success_message = """ Login successful. @@ -67,6 +68,7 @@ def configure( Check that your Client ID and Client secret are correct and try again. """ ) + sys.exit(1) except Exception as e: logger.error("An unexpected error occurred!") logger.error(e) diff --git a/guardrails/cli/hub/install.py b/guardrails/cli/hub/install.py index 0dea65878..54acac1f3 100644 --- a/guardrails/cli/hub/install.py +++ b/guardrails/cli/hub/install.py @@ -13,7 +13,7 @@ from guardrails.classes.generic import Stack from guardrails.cli.hub.hub import hub from guardrails.cli.logger import LEVELS, logger -from guardrails.cli.server.hub_client import fetch_module +from guardrails.cli.server.hub_client import get_validator_manifest from guardrails.cli.server.module_manifest import ModuleManifest @@ -203,7 +203,7 @@ def install( module_name = package_uri.replace("hub://", "") # Prep - module_manifest = fetch_module(module_name) + module_manifest = get_validator_manifest(module_name) site_packages = get_site_packages_location() # Install diff --git a/guardrails/cli/server/auth.py b/guardrails/cli/server/auth.py index e68833d6c..49cc1feef 100644 --- a/guardrails/cli/server/auth.py +++ b/guardrails/cli/server/auth.py @@ -4,7 +4,7 @@ from guardrails.cli.server.credentials import Credentials -def authenticate(creds: Credentials) -> str: +def get_auth_token(creds: Credentials) -> str: if creds.client_id and creds.client_secret: audience = "https://validator-hub-service.guardrailsai.com" conn = http.client.HTTPSConnection("guardrailsai.us.auth0.com") diff --git a/guardrails/cli/server/hub_client.py b/guardrails/cli/server/hub_client.py index 33e65d411..50a1cbf52 100644 --- a/guardrails/cli/server/hub_client.py +++ b/guardrails/cli/server/hub_client.py @@ -5,7 +5,7 @@ import requests from guardrails.cli.logger import logger -from guardrails.cli.server.auth import authenticate +from guardrails.cli.server.auth import get_auth_token from guardrails.cli.server.credentials import Credentials from guardrails.cli.server.module_manifest import ModuleManifest @@ -64,11 +64,16 @@ def fetch_module_manifest( def fetch_module(module_name: str) -> ModuleManifest: creds = Credentials.from_rc_file() - token = authenticate(creds) + token = get_auth_token(creds) + module_manifest_json = fetch_module_manifest(module_name, token, creds.id) + return ModuleManifest.from_dict(module_manifest_json) + + +# GET /validator-manifests/{namespace}/{validatorName} +def get_validator_manifest(module_name: str): try: - module_manifest_json = fetch_module_manifest(module_name, token, creds.id) - module_manifest = ModuleManifest.from_dict(module_manifest_json) + module_manifest = fetch_module(module_name) if not module_manifest: logger.error(f"Failed to install hub://{module_name}") sys.exit(1) @@ -81,13 +86,13 @@ def fetch_module(module_name: str) -> ModuleManifest: sys.exit(1) -def check_auth(): +# GET /auth +def get_auth(): try: creds = Credentials.from_rc_file() - token = authenticate(creds) + token = get_auth_token(creds) auth_url = f"{validator_hub_service}/auth" response = fetch(auth_url, token, creds.id) - print("check_auth fetch response: ", response) if not response: raise AuthenticationError("Failed to authenticate!") except HttpError as http_error: diff --git a/tests/unit_tests/cli/hub/test_install.py b/tests/unit_tests/cli/hub/test_install.py index cd8c805b2..17b9db2c4 100644 --- a/tests/unit_tests/cli/hub/test_install.py +++ b/tests/unit_tests/cli/hub/test_install.py @@ -4,7 +4,6 @@ import pytest from guardrails.cli.server.module_manifest import ModuleManifest, ModuleTags, Repository -from guardrails.cli.server.serializeable import SerializeableJSONEncoder from tests.unit_tests.mocks.mock_file import MockFile @@ -25,7 +24,7 @@ def test_exits_early_if_uri_is_not_valid(self, mocker): def test_happy_path(self, mocker): mock_logger_log = mocker.patch("guardrails.cli.hub.install.logger.log") - mock_fetch_module = mocker.patch("guardrails.cli.hub.install.fetch_module") + mock_get_validator_manifest = mocker.patch("guardrails.cli.hub.install.get_validator_manifest") manifest = ModuleManifest( "id", "name", @@ -38,7 +37,7 @@ def test_happy_path(self, mocker): ["TestValidator"], ModuleTags(), ) - mock_fetch_module.return_value = manifest + mock_get_validator_manifest.return_value = manifest mock_get_site_packages_location = mocker.patch( "guardrails.cli.hub.install.get_site_packages_location" @@ -70,7 +69,7 @@ def test_happy_path(self, mocker): assert mock_logger_log.call_count == 2 mock_logger_log.assert_has_calls(log_calls) - mock_fetch_module.assert_called_once_with("guardrails/test-validator") + mock_get_validator_manifest.assert_called_once_with("guardrails/test-validator") assert mock_get_site_packages_location.call_count == 1 diff --git a/tests/unit_tests/cli/hub/server/test_auth.py b/tests/unit_tests/cli/server/test_auth.py similarity index 69% rename from tests/unit_tests/cli/hub/server/test_auth.py rename to tests/unit_tests/cli/server/test_auth.py index 1b13494c4..aff255c98 100644 --- a/tests/unit_tests/cli/hub/server/test_auth.py +++ b/tests/unit_tests/cli/server/test_auth.py @@ -3,5 +3,5 @@ # TODO @pytest.mark.skip() -def test_authenticate(): +def test_get_auth_token(): assert 1 == 1 diff --git a/tests/unit_tests/cli/hub/server/test_credentials.py b/tests/unit_tests/cli/server/test_credentials.py similarity index 100% rename from tests/unit_tests/cli/hub/server/test_credentials.py rename to tests/unit_tests/cli/server/test_credentials.py diff --git a/tests/unit_tests/cli/hub/server/test_hub_client.py b/tests/unit_tests/cli/server/test_hub_client.py similarity index 60% rename from tests/unit_tests/cli/hub/server/test_hub_client.py rename to tests/unit_tests/cli/server/test_hub_client.py index aa7b8aaf1..a7bafe600 100644 --- a/tests/unit_tests/cli/hub/server/test_hub_client.py +++ b/tests/unit_tests/cli/server/test_hub_client.py @@ -17,3 +17,15 @@ def test_fetch_module_manifest(): @pytest.mark.skip() def test_fetch_module(): assert 1 == 1 + + +# TODO +@pytest.mark.skip() +def test_get_validator_manifest(): + assert 1 == 1 + + +# TODO +@pytest.mark.skip() +def test_get_auth(): + assert 1 == 1 diff --git a/tests/unit_tests/cli/test_configure.py b/tests/unit_tests/cli/test_configure.py index 757415ee6..959263b4a 100644 --- a/tests/unit_tests/cli/test_configure.py +++ b/tests/unit_tests/cli/test_configure.py @@ -27,7 +27,7 @@ def test_configure(mocker, client_id, client_secret, no_metrics): assert mock_logger_info.call_count == 2 expected_calls = [call("Configuring..."), call("Validating credentials...")] mock_logger_info.assert_has_calls(expected_calls) - + mock_save_configuration_file.assert_called_once_with( client_id, client_secret, no_metrics ) @@ -40,6 +40,7 @@ def test_configure_prompting(mocker): "guardrails.cli.configure.save_configuration_file" ) mock_logger_info = mocker.patch("guardrails.cli.configure.logger.info") + mock_get_auth = mocker.patch("guardrails.cli.server.hub_client.get_auth") from guardrails.cli.configure import configure @@ -48,13 +49,15 @@ def test_configure_prompting(mocker): assert mock_typer_prompt.call_count == 2 expected_calls = [call("Client ID"), call("Client secret", hide_input=True)] mock_typer_prompt.assert_has_calls(expected_calls) - + assert mock_logger_info.call_count == 2 expected_calls = [call("Configuring..."), call("Validating credentials...")] mock_logger_info.assert_has_calls(expected_calls) - + mock_save_configuration_file.assert_called_once_with("id", "secret", False) + assert mock_get_auth.call_count == 1 + def test_save_configuration_file(mocker): # TODO: Re-enable this once we move nltk.download calls to individual validator repos. # noqa From 9e40c44b59291bd447dd82128778f42061392eb0 Mon Sep 17 00:00:00 2001 From: Caleb Courier Date: Wed, 7 Feb 2024 09:44:58 -0800 Subject: [PATCH 9/9] fix tests --- guardrails/cli/configure.py | 4 ++++ tests/unit_tests/cli/test_configure.py | 4 +++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/guardrails/cli/configure.py b/guardrails/cli/configure.py index f7a4296d5..ce674e1e2 100644 --- a/guardrails/cli/configure.py +++ b/guardrails/cli/configure.py @@ -66,6 +66,10 @@ def configure( logger.error( """ Check that your Client ID and Client secret are correct and try again. + + If you don't have your token credentials you can find them here: + + https://hub.guardrailsai.com/tokens """ ) sys.exit(1) diff --git a/tests/unit_tests/cli/test_configure.py b/tests/unit_tests/cli/test_configure.py index 959263b4a..7a587d6e3 100644 --- a/tests/unit_tests/cli/test_configure.py +++ b/tests/unit_tests/cli/test_configure.py @@ -19,6 +19,7 @@ def test_configure(mocker, client_id, client_secret, no_metrics): "guardrails.cli.configure.save_configuration_file" ) mock_logger_info = mocker.patch("guardrails.cli.configure.logger.info") + mock_get_auth = mocker.patch("guardrails.cli.configure.get_auth") from guardrails.cli.configure import configure @@ -32,6 +33,7 @@ def test_configure(mocker, client_id, client_secret, no_metrics): client_id, client_secret, no_metrics ) + assert mock_get_auth.call_count == 1 def test_configure_prompting(mocker): mock_typer_prompt = mocker.patch("typer.prompt") @@ -40,7 +42,7 @@ def test_configure_prompting(mocker): "guardrails.cli.configure.save_configuration_file" ) mock_logger_info = mocker.patch("guardrails.cli.configure.logger.info") - mock_get_auth = mocker.patch("guardrails.cli.server.hub_client.get_auth") + mock_get_auth = mocker.patch("guardrails.cli.configure.get_auth") from guardrails.cli.configure import configure