Files
2026-07-13 13:22:34 +08:00

164 lines
5.6 KiB
Python

from importlib import reload
from unittest import mock
import pytest
import mlflow.tracking.request_header.registry
from mlflow.tracking.request_header.databricks_request_header_provider import (
DatabricksRequestHeaderProvider,
)
from mlflow.tracking.request_header.registry import (
RequestHeaderProviderRegistry,
resolve_request_headers,
)
@pytest.fixture(autouse=True)
def reload_registry():
yield
reload(mlflow.tracking.request_header.registry)
def test_request_header_context_provider_registry_register():
provider_class = mock.Mock()
registry = RequestHeaderProviderRegistry()
registry.register(provider_class)
assert set(registry) == {provider_class.return_value}
def test_request_header_provider_registry_register_entrypoints():
provider_class = mock.Mock()
mock_entrypoint = mock.Mock()
mock_entrypoint.load.return_value = provider_class
with mock.patch(
"mlflow.utils.plugins._get_entry_points", return_value=[mock_entrypoint]
) as mock_get_group_all:
registry = RequestHeaderProviderRegistry()
registry.register_entrypoints()
assert set(registry) == {provider_class.return_value}
mock_entrypoint.load.assert_called_once_with()
mock_get_group_all.assert_called_once_with("mlflow.request_header_provider")
@pytest.mark.parametrize(
"exception", [AttributeError("test exception"), ImportError("test exception")]
)
def test_request_header_provider_registry_register_entrypoints_handles_exception(exception):
mock_entrypoint = mock.Mock()
mock_entrypoint.load.side_effect = exception
with mock.patch(
"mlflow.utils.plugins._get_entry_points", return_value=[mock_entrypoint]
) as mock_get_group_all:
registry = RequestHeaderProviderRegistry()
# Check that the raised warning contains the message from the original exception
with pytest.warns(UserWarning, match="test exception"):
registry.register_entrypoints()
mock_entrypoint.load.assert_called_once_with()
mock_get_group_all.assert_called_once_with("mlflow.request_header_provider")
def _currently_registered_request_header_provider_classes():
return {
provider.__class__
for provider in mlflow.tracking.request_header.registry._request_header_provider_registry
}
def test_registry_instance_defaults():
expected_classes = {DatabricksRequestHeaderProvider}
assert expected_classes.issubset(_currently_registered_request_header_provider_classes())
def test_registry_instance_loads_entrypoints():
class MockRequestHeaderProvider:
pass
mock_entrypoint = mock.Mock()
mock_entrypoint.load.return_value = MockRequestHeaderProvider
with mock.patch(
"mlflow.utils.plugins._get_entry_points", return_value=[mock_entrypoint]
) as mock_get_group_all:
# Entrypoints are registered at import time, so we need to reload the module to register the
# entrypoint given by the mocked entrypoints.get_group_all
reload(mlflow.tracking.request_header.registry)
assert MockRequestHeaderProvider in _currently_registered_request_header_provider_classes()
mock_get_group_all.assert_called_once_with("mlflow.request_header_provider")
def test_run_context_provider_registry_with_installed_plugin():
reload(mlflow.tracking.request_header.registry)
from mlflow_test_plugin.request_header_provider import PluginRequestHeaderProvider
assert PluginRequestHeaderProvider in _currently_registered_request_header_provider_classes()
# The test plugin's request header provider always returns False from in_context to avoid
# polluting request headers in developers' environments. The following mock overrides this to
# perform the integration test.
with mock.patch.object(PluginRequestHeaderProvider, "in_context", return_value=True):
assert resolve_request_headers()["test"] == "header"
@pytest.fixture
def mock_request_header_providers():
base_provider = mock.Mock()
base_provider.in_context.return_value = True
base_provider.request_headers.return_value = {
"one": "one-val",
"two": "two-val",
"three": "three-val",
}
skipped_provider = mock.Mock()
skipped_provider.in_context.return_value = False
exception_provider = mock.Mock()
exception_provider.in_context.return_value = True
exception_provider.request_headers.return_value = {
"random-header": "This val will never make it to header resolution"
}
exception_provider.request_headers.side_effect = Exception(
"This should be caught by logic in resolve_request_headers()"
)
override_provider = mock.Mock()
override_provider.in_context.return_value = True
override_provider.request_headers.return_value = {"one": "override", "new": "new-val"}
providers = [base_provider, skipped_provider, exception_provider, override_provider]
with mock.patch(
"mlflow.tracking.request_header.registry._request_header_provider_registry", providers
):
yield
skipped_provider.tags.assert_not_called()
def test_resolve_request_headers(mock_request_header_providers):
request_headers_arg = {"two": "arg-override", "arg": "arg-val"}
assert resolve_request_headers(request_headers_arg) == {
"one": "one-val override",
"two": "arg-override",
"three": "three-val",
"new": "new-val",
"arg": "arg-val",
}
def test_resolve_request_headers_no_arg(mock_request_header_providers):
assert resolve_request_headers() == {
"one": "one-val override",
"two": "two-val",
"three": "three-val",
"new": "new-val",
}