참고소스 수정본

This commit is contained in:
LASTA_DEV01\lasta
2026-05-12 19:40:31 +09:00
parent 0f34a451fc
commit 2e9204243d
8708 changed files with 3259488 additions and 869 deletions

View File

@@ -0,0 +1,281 @@
import json
from unittest.mock import patch, MagicMock
import pytest
import guardrails.hub as hub_module
@pytest.fixture(autouse=True)
def reset_export_map_cache():
"""Reset the module-level cache before each test."""
hub_module._export_map_cache = None
yield
hub_module._export_map_cache = None
@pytest.fixture
def registry_dir(tmp_path):
guardrails_dir = tmp_path / ".guardrails"
guardrails_dir.mkdir()
return guardrails_dir
@pytest.fixture
def registry_file(registry_dir):
return registry_dir / "hub_registry.json"
def _write_registry(registry_file, validators):
registry = {"version": 1, "validators": validators}
registry_file.write_text(json.dumps(registry))
def test_getattr_resolves_registered_validator(tmp_path, registry_file):
_write_registry(
registry_file,
{
"guardrails/test-validator": {
"import_path": "guardrails_grhub_test_validator",
"exports": ["TestValidator"],
"installed_at": "2025-01-01T00:00:00+00:00",
"package_name": "guardrails-grhub-test-validator",
}
},
)
mock_module = MagicMock()
mock_module.TestValidator = "resolved_class"
with (
patch("guardrails.hub.registry.get_registry_path", return_value=registry_file),
patch(
"guardrails.hub.importlib.import_module",
return_value=mock_module,
) as mock_import,
):
result = hub_module.__getattr__("TestValidator")
assert result == "resolved_class"
mock_import.assert_called_once_with("guardrails_grhub_test_validator")
def test_getattr_caches_in_globals(tmp_path, registry_file):
_write_registry(
registry_file,
{
"guardrails/test-validator": {
"import_path": "guardrails_grhub_test_validator",
"exports": ["TestValidator"],
"installed_at": "2025-01-01T00:00:00+00:00",
"package_name": "guardrails-grhub-test-validator",
}
},
)
mock_module = MagicMock()
mock_module.TestValidator = "resolved_class"
with (
patch("guardrails.hub.registry.get_registry_path", return_value=registry_file),
patch(
"guardrails.hub.importlib.import_module",
return_value=mock_module,
) as mock_import,
):
result1 = hub_module.__getattr__("TestValidator")
assert result1 == "resolved_class"
assert mock_import.call_count == 1
# After caching, the attr should be in globals
assert "TestValidator" in hub_module.__dict__
# Clean up
del hub_module.__dict__["TestValidator"]
def test_getattr_raises_attribute_error_for_unknown(tmp_path, registry_file):
_write_registry(registry_file, {})
with patch("guardrails.hub.registry.get_registry_path", return_value=registry_file):
with pytest.raises(
AttributeError,
match="has no attribute 'NonExistent'",
):
hub_module.__getattr__("NonExistent")
def test_getattr_raises_import_error_for_missing_module(tmp_path, registry_file):
_write_registry(
registry_file,
{
"guardrails/missing": {
"import_path": "guardrails_grhub_missing",
"exports": ["MissingValidator"],
"installed_at": "2025-01-01T00:00:00+00:00",
"package_name": "guardrails-grhub-missing",
}
},
)
with (
patch("guardrails.hub.registry.get_registry_path", return_value=registry_file),
patch(
"guardrails.hub.importlib.import_module",
side_effect=ModuleNotFoundError(
"No module named 'guardrails_grhub_missing'"
),
),
):
with pytest.raises(ImportError, match="Cannot import 'MissingValidator'"):
hub_module.__getattr__("MissingValidator")
def test_getattr_raises_import_error_for_missing_attribute(tmp_path, registry_file):
"""Module loads but the registered export name does not exist."""
_write_registry(
registry_file,
{
"guardrails/test-validator": {
"import_path": "guardrails_grhub_test_validator",
"exports": ["NonExistentClass"],
"installed_at": "2025-01-01T00:00:00+00:00",
"package_name": "guardrails-grhub-test-validator",
}
},
)
mock_module = MagicMock(spec=[])
with (
patch("guardrails.hub.registry.get_registry_path", return_value=registry_file),
patch(
"guardrails.hub.importlib.import_module",
return_value=mock_module,
),
):
with pytest.raises(ImportError, match="Cannot import 'NonExistentClass'"):
hub_module.__getattr__("NonExistentClass")
def test_dir_includes_registered_exports(tmp_path, registry_file):
_write_registry(
registry_file,
{
"guardrails/test-validator": {
"import_path": "guardrails_grhub_test_validator",
"exports": ["TestValidator", "TestHelper"],
"installed_at": "2025-01-01T00:00:00+00:00",
"package_name": "guardrails-grhub-test-validator",
}
},
)
with patch("guardrails.hub.registry.get_registry_path", return_value=registry_file):
result = hub_module.__dir__()
assert "TestValidator" in result
assert "TestHelper" in result
def test_dir_with_no_registry(tmp_path, registry_file):
"""__dir__ should not crash when no registry file exists."""
with patch("guardrails.hub.registry.get_registry_path", return_value=registry_file):
result = hub_module.__dir__()
assert isinstance(result, list)
assert "_build_export_map" in result
def test_empty_registry(tmp_path, registry_file):
with patch("guardrails.hub.registry.get_registry_path", return_value=registry_file):
with pytest.raises(
AttributeError,
match="has no attribute 'SomeName'",
):
hub_module.__getattr__("SomeName")
def test_corrupt_registry_falls_back_gracefully(tmp_path, registry_dir, registry_file):
"""Corrupt JSON should fall back to AttributeError, not crash."""
(registry_dir / "hub_registry.json").write_text("{not valid json")
with patch("guardrails.hub.registry.get_registry_path", return_value=registry_file):
with pytest.raises(
AttributeError,
match="has no attribute 'DetectPII'",
):
hub_module.__getattr__("DetectPII")
def test_export_name_collision_last_wins(tmp_path, registry_file):
"""When two validators export the same name, last in dict wins."""
_write_registry(
registry_file,
{
"org-a/validator": {
"import_path": "org_a_grhub_validator",
"exports": ["SharedName"],
"installed_at": "2025-01-01T00:00:00+00:00",
"package_name": "org-a-grhub-validator",
},
"org-b/validator": {
"import_path": "org_b_grhub_validator",
"exports": ["SharedName"],
"installed_at": "2025-01-01T00:00:00+00:00",
"package_name": "org-b-grhub-validator",
},
},
)
with patch("guardrails.hub.registry.get_registry_path", return_value=registry_file):
export_map = hub_module._build_export_map()
assert export_map["SharedName"] == "org_b_grhub_validator"
def test_build_export_map_skips_empty_exports(tmp_path, registry_file):
"""Entry with empty exports list produces no entries."""
_write_registry(
registry_file,
{
"guardrails/no-exports": {
"import_path": "guardrails_grhub_no_exports",
"exports": [],
"installed_at": "2025-01-01T00:00:00+00:00",
"package_name": "guardrails-grhub-no-exports",
}
},
)
with patch("guardrails.hub.registry.get_registry_path", return_value=registry_file):
export_map = hub_module._build_export_map()
assert len(export_map) == 0
def test_build_export_map_handles_missing_keys(tmp_path, registry_file):
"""Entry with missing import_path or exports should not crash."""
_write_registry(registry_file, {"guardrails/broken": {}})
with patch("guardrails.hub.registry.get_registry_path", return_value=registry_file):
export_map = hub_module._build_export_map()
assert len(export_map) == 0
def test_get_export_map_caches_result(tmp_path, registry_file):
"""_get_export_map should only build the map once."""
_write_registry(
registry_file,
{
"guardrails/test": {
"import_path": "guardrails_grhub_test",
"exports": ["Test"],
"installed_at": "2025-01-01T00:00:00+00:00",
"package_name": "guardrails-grhub-test",
}
},
)
with patch("guardrails.hub.registry.get_registry_path", return_value=registry_file):
result1 = hub_module._get_export_map()
result2 = hub_module._get_export_map()
assert result1 is result2

View File

@@ -0,0 +1,434 @@
import pytest
from unittest.mock import ANY, call, MagicMock
from guardrails.classes.rc import RC
from guardrails_hub_types import Manifest
from guardrails.hub.validator_package_service import (
InvalidHubInstallURL,
)
from guardrails.hub.install import LocalModelFlagNotSet, install
@pytest.mark.parametrize(
"use_remote_inferencing",
[False, True],
)
class TestInstall:
def setup_method(self):
self.manifest = Manifest.from_dict(
{
"id": "guardrails/id",
"name": "name",
"author": {"name": "me", "email": "me@me.me"},
"maintainers": [],
"repository": {"url": "some-repo"},
"namespace": "guardrails",
"packageName": "test-validator",
"moduleName": "test_validator",
"description": "test-description",
"exports": ["TestValidator"],
"tags": {"hasGuardrailsEndpoint": False},
}
)
self.site_packages = "./.venv/lib/python3.X/site-packages"
def test_exits_early_if_uri_is_not_valid(self, mocker, use_remote_inferencing):
mocker.patch(
"guardrails.hub.install.RC.exists",
return_value=True,
)
with pytest.raises(InvalidHubInstallURL):
install("not a hub uri")
def test_install_local_models__false(self, mocker, use_remote_inferencing):
mocker.patch(
"guardrails.hub.install.RC.exists",
return_value=True,
)
mocker.patch(
"guardrails.hub.install.RC.load",
return_value=RC.from_dict(
{"use_remote_inferencing": use_remote_inferencing}
),
)
mock_logger_log = mocker.patch("guardrails.hub.install.cli_logger.log")
get_manifest_and_site_packages_mock = mocker.patch(
"guardrails.hub.validator_package_service.ValidatorPackageService.get_manifest_and_site_packages"
)
mock_pip_install_hub_module = mocker.patch(
"guardrails.hub.validator_package_service.ValidatorPackageService.install_hub_module"
)
mocker.patch(
"guardrails.hub.validator_package_service.ValidatorPackageService.get_validator_from_manifest"
)
mocker.patch(
"guardrails.hub.validator_package_service.ValidatorPackageService.register_validator"
)
get_manifest_and_site_packages_mock.return_value = (
self.manifest,
self.site_packages,
)
install(
"hub://guardrails/id",
install_local_models=False,
install_local_models_confirm=lambda: False,
)
log_calls = [
call(level=5, msg="Installing hub://guardrails/id..."),
call(
level=5,
msg="Skipping post install, models will not be downloaded for local "
"inference.",
),
call(
level=5,
msg="✅Successfully installed hub://guardrails/id!\n\nImport validator:\nfrom guardrails.hub import TestValidator\n\nGet more info:\nhttps://guardrailsai.com/hub/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/id")
mock_pip_install_hub_module.assert_called_once_with(
self.manifest.id, validator_version=None, quiet=ANY, upgrade=ANY, logger=ANY
)
def test_install_local_models__true(self, mocker, use_remote_inferencing):
mocker.patch(
"guardrails.hub.install.RC.exists",
return_value=True,
)
mocker.patch(
"guardrails.hub.install.RC.load",
return_value=RC.from_dict(
{"use_remote_inferencing": use_remote_inferencing}
),
)
mock_logger_log = mocker.patch("guardrails.hub.install.cli_logger.log")
importlib_metadata = mocker.patch("guardrails.hub.install.importlib.metadata")
importlib_metadata.version = "1.0.0"
get_manifest_and_site_packages_mock = mocker.patch(
"guardrails.hub.validator_package_service.ValidatorPackageService.get_manifest_and_site_packages"
)
mock_pip_install_hub_module = mocker.patch(
"guardrails.hub.validator_package_service.ValidatorPackageService.install_hub_module"
)
mocker.patch(
"guardrails.hub.validator_package_service.ValidatorPackageService.get_validator_from_manifest"
)
mocker.patch(
"guardrails.hub.validator_package_service.ValidatorPackageService.register_validator"
)
get_manifest_and_site_packages_mock.return_value = (
self.manifest,
self.site_packages,
)
install(
"hub://guardrails/id",
install_local_models=True,
install_local_models_confirm=lambda: True,
)
log_calls = [
call(level=5, msg="Installing hub://guardrails/id..."),
call(
level=5,
msg="Installing models locally!",
),
call(
level=5,
msg="✅Successfully installed hub://guardrails/id!\n\nImport validator:\nfrom guardrails.hub import TestValidator\n\nGet more info:\nhttps://guardrailsai.com/hub/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/id")
mock_pip_install_hub_module.assert_called_once_with(
self.manifest.id, validator_version=None, quiet=ANY, upgrade=ANY, logger=ANY
)
def test_install_local_models__none(self, mocker, use_remote_inferencing):
mocker.patch(
"guardrails.hub.install.RC.exists",
return_value=True,
)
mocker.patch(
"guardrails.hub.install.RC.load",
return_value=RC.from_dict(
{"use_remote_inferencing": use_remote_inferencing}
),
)
mock_logger_log = mocker.patch("guardrails.hub.install.cli_logger.log")
get_manifest_and_site_packages_mock = mocker.patch(
"guardrails.hub.validator_package_service.ValidatorPackageService.get_manifest_and_site_packages"
)
mock_pip_install_hub_module = mocker.patch(
"guardrails.hub.validator_package_service.ValidatorPackageService.install_hub_module"
)
mocker.patch(
"guardrails.hub.validator_package_service.ValidatorPackageService.get_validator_from_manifest"
)
mocker.patch(
"guardrails.hub.validator_package_service.ValidatorPackageService.register_validator"
)
get_manifest_and_site_packages_mock.return_value = (
self.manifest,
self.site_packages,
)
install(
"hub://guardrails/id",
install_local_models=None,
install_local_models_confirm=lambda: True,
)
log_calls = [
call(level=5, msg="Installing hub://guardrails/id..."),
call(
level=5,
msg="Installing models locally!",
),
call(
level=5,
msg="✅Successfully installed hub://guardrails/id!\n\nImport validator:\nfrom guardrails.hub import TestValidator\n\nGet more info:\nhttps://guardrailsai.com/hub/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/id")
mock_pip_install_hub_module.assert_called_once_with(
self.manifest.id, validator_version=None, quiet=ANY, upgrade=ANY, logger=ANY
)
def test_happy_path(self, mocker, use_remote_inferencing):
mocker.patch(
"guardrails.hub.install.RC.exists",
return_value=True,
)
mocker.patch(
"guardrails.hub.install.RC.load",
return_value=RC.from_dict(
{"use_remote_inferencing": use_remote_inferencing}
),
)
mock_logger_log = mocker.patch("guardrails.hub.install.cli_logger.log")
get_manifest_and_site_packages_mock = mocker.patch(
"guardrails.hub.validator_package_service.ValidatorPackageService.get_manifest_and_site_packages"
)
mock_pip_install_hub_module = mocker.patch(
"guardrails.hub.validator_package_service.ValidatorPackageService.install_hub_module"
)
mocker.patch(
"guardrails.hub.validator_package_service.ValidatorPackageService.get_validator_from_manifest"
)
mocker.patch(
"guardrails.hub.validator_package_service.ValidatorPackageService.register_validator"
)
get_manifest_and_site_packages_mock.return_value = (
self.manifest,
self.site_packages,
)
install(
"hub://guardrails/id",
install_local_models_confirm=lambda: True,
)
log_calls = [
call(level=5, msg="Installing hub://guardrails/id..."),
call(
level=5,
msg="Installing models locally!", # 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/id")
mock_pip_install_hub_module.assert_called_once_with(
self.manifest.id, validator_version=None, quiet=ANY, upgrade=ANY, logger=ANY
)
def test_install_local_models_confirmation(self, mocker, use_remote_inferencing):
mocker.patch(
"guardrails.hub.install.RC.exists",
return_value=False,
)
mocker.patch("guardrails.hub.install.cli_logger.log")
mocker.patch(
"guardrails.hub.validator_package_service.ValidatorPackageService.install_hub_module"
)
mocker.patch(
"guardrails.hub.validator_package_service.ValidatorPackageService.get_validator_from_manifest"
)
mocker.patch(
"guardrails.hub.validator_package_service.ValidatorPackageService.register_validator"
)
mock_get_manifest_and_site_packages = mocker.patch(
"guardrails.hub.validator_package_service.ValidatorPackageService.get_manifest_and_site_packages"
)
manifest_with_endpoint = Manifest.from_dict(
{
"id": "test-id",
"name": "test-name",
"author": {"name": "test-author", "email": "test@email.com"},
"maintainers": [],
"repository": {"url": "test-repo"},
"namespace": "test-namespace",
"packageName": "test-package",
"moduleName": "test_module",
"description": "test-description",
"exports": ["TestValidator"],
"tags": {"hasGuardrailsEndpoint": True},
}
)
mock_get_manifest_and_site_packages.return_value = (
manifest_with_endpoint,
self.site_packages,
)
mock_confirm = MagicMock()
install(
"hub://guardrails/test-validator",
install_local_models_confirm=mock_confirm,
)
mock_confirm.assert_called_once()
def test_install_local_models_confirmation_raises_exception(
self, mocker, use_remote_inferencing
):
mocker.patch(
"guardrails.hub.install.RC.exists",
return_value=False,
)
mocker.patch("guardrails.hub.install.cli_logger.log")
mocker.patch(
"guardrails.hub.validator_package_service.ValidatorPackageService.install_hub_module"
)
mocker.patch(
"guardrails.hub.validator_package_service.ValidatorPackageService.get_validator_from_manifest"
)
mocker.patch(
"guardrails.hub.validator_package_service.ValidatorPackageService.register_validator"
)
mock_get_manifest_and_site_packages = mocker.patch(
"guardrails.hub.validator_package_service.ValidatorPackageService.get_manifest_and_site_packages"
)
manifest_with_endpoint = Manifest.from_dict(
{
"id": "test-id",
"name": "test-name",
"author": {"name": "test-author", "email": "test@email.com"},
"maintainers": [],
"repository": {"url": "test-repo"},
"namespace": "test-namespace",
"packageName": "test-package",
"moduleName": "test_module",
"description": "test-description",
"exports": ["TestValidator"],
"tags": {"hasGuardrailsEndpoint": True},
}
)
mock_get_manifest_and_site_packages.return_value = (
manifest_with_endpoint,
self.site_packages,
)
with pytest.raises(LocalModelFlagNotSet):
install(
"hub://guardrails/test-validator",
)
def test_use_remote_endpoint(self, mocker, use_remote_inferencing: bool):
mocker.patch(
"guardrails.hub.install.RC.exists",
return_value=True,
)
mocker.patch(
"guardrails.hub.install.RC.load",
return_value=RC.from_dict(
{"use_remote_inferencing": use_remote_inferencing}
),
)
mock_logger_log = mocker.patch("guardrails.hub.install.cli_logger.log")
get_manifest_and_site_packages_mock = mocker.patch(
"guardrails.hub.validator_package_service.ValidatorPackageService.get_manifest_and_site_packages"
)
mocker.patch(
"guardrails.hub.validator_package_service.ValidatorPackageService.install_hub_module"
)
mocker.patch(
"guardrails.hub.validator_package_service.ValidatorPackageService.get_validator_from_manifest"
)
mocker.patch(
"guardrails.hub.validator_package_service.ValidatorPackageService.register_validator"
)
manifest = Manifest.from_dict(
{
"id": "guardrails/test-validator",
"name": "name",
"author": {"name": "me", "email": "me@me.me"},
"maintainers": [],
"repository": {"url": "some-repo"},
"namespace": "guardrails",
"packageName": "test-validator",
"moduleName": "test_validator",
"description": "test-description",
"exports": ["TestValidator"],
"tags": {"hasGuardrailsEndpoint": True},
}
)
get_manifest_and_site_packages_mock.return_value = manifest, self.site_packages
install("hub://guardrails/test-validator")
msg = (
"Skipping post install, models will not be downloaded for local inference."
if use_remote_inferencing
else "Installing models locally!"
)
log_calls = [
call(level=5, msg="Installing hub://guardrails/test-validator..."),
call(
level=5,
msg=msg, # noqa
), # noqa
]
assert mock_logger_log.call_count == 3
mock_logger_log.assert_has_calls(log_calls)

File diff suppressed because it is too large Load Diff