Files
mlflow--mlflow/tests/tracking/test_client_workspace.py
2026-07-13 13:22:34 +08:00

252 lines
8.4 KiB
Python

from __future__ import annotations
from mlflow import MlflowClient
from mlflow.entities import TraceArchivalConfig
from mlflow.environment_variables import MLFLOW_TRACKING_URI, MLFLOW_WORKSPACE_STORE_URI
from mlflow.tracking._workspace import fluent as workspace_fluent
from mlflow.tracking._workspace.client import WorkspaceProviderClient
from mlflow.utils.workspace_utils import DEFAULT_WORKSPACE_NAME
def test_mlflow_client_resolves_workspace_uri(monkeypatch):
recorded: dict[str, str | None] = {}
class DummyTrackingClient:
def __init__(self, tracking_uri: str):
recorded["tracking_uri"] = tracking_uri
self.tracking_uri = tracking_uri
class DummyWorkspaceClient:
def __init__(self, workspace_uri: str | None = None):
recorded["workspace_uri"] = workspace_uri
# Methods invoked downstream are irrelevant for this initialization test.
def list_workspaces(self):
return []
monkeypatch.setattr("mlflow.tracking.client.TrackingServiceClient", DummyTrackingClient)
monkeypatch.setattr("mlflow.tracking.client.TracingClient", lambda _: None)
monkeypatch.setattr("mlflow.tracking.client.WorkspaceProviderClient", DummyWorkspaceClient)
monkeypatch.setattr(
"mlflow.tracking.client.utils._resolve_tracking_uri",
lambda uri: uri or "sqlite:///tracking.db",
)
monkeypatch.setattr(
"mlflow.tracking.client.registry_utils._resolve_registry_uri",
lambda registry_uri, tracking_uri: "registry-resolved",
)
monkeypatch.setenv(MLFLOW_TRACKING_URI.name, "sqlite:///tracking.db")
monkeypatch.setenv(MLFLOW_WORKSPACE_STORE_URI.name, "sqlite:///workspace.db")
client = MlflowClient()
client.list_workspaces()
assert recorded["workspace_uri"] == "sqlite:///workspace.db"
recorded.clear()
client = MlflowClient(workspace_store_uri="sqlite:///explicit.db")
client.list_workspaces()
assert recorded["workspace_uri"] == "sqlite:///explicit.db"
recorded.clear()
monkeypatch.delenv(MLFLOW_WORKSPACE_STORE_URI.name, raising=False)
client = MlflowClient()
client.list_workspaces()
# Falls back to the tracking URI when workspace URI is unset.
assert recorded["workspace_uri"] == "sqlite:///tracking.db"
def test_set_workspace_sets_env_and_context(monkeypatch):
calls: dict[str, list[str]] = {"set_context_workspace": []}
env: dict[str, str | None] = {}
monkeypatch.setattr(
workspace_fluent,
"WorkspaceNameValidator",
type(
"Validator",
(),
{"validate": lambda name: calls.setdefault("validate", []).append(name)},
),
)
monkeypatch.setattr(
workspace_fluent,
"set_context_workspace",
lambda name: (
calls["set_context_workspace"].append(name),
env.__setitem__("value", name),
),
)
workspace_fluent.set_workspace("team-space")
assert calls["validate"] == ["team-space"]
assert calls["set_context_workspace"] == ["team-space"]
assert env["value"] == "team-space"
def test_set_workspace_clears_when_none(monkeypatch):
env: dict[str, str | None] = {}
calls = {"clear_workspace": 0, "set_context_workspace": [], "validate": []}
monkeypatch.setattr(
workspace_fluent,
"WorkspaceNameValidator",
type(
"Validator",
(),
{"validate": lambda name: calls["validate"].append(name)},
),
)
monkeypatch.setattr(
workspace_fluent,
"set_context_workspace",
lambda name: (
calls["set_context_workspace"].append(name),
env.__setitem__("value", name),
calls.__setitem__(
"clear_workspace", calls["clear_workspace"] + (1 if name is None else 0)
),
),
)
# Ensure default workspace does not trigger validation but does set env
workspace_fluent.set_workspace(DEFAULT_WORKSPACE_NAME)
assert calls["validate"] == []
assert env["value"] == DEFAULT_WORKSPACE_NAME
workspace_fluent.set_workspace(None)
assert calls["clear_workspace"] == 1
assert env.get("value") is None
def test_mlflow_client_forwards_trace_archival_settings(monkeypatch):
recorded: dict[str, tuple[tuple[object, ...], dict[str, object]]] = {}
class DummyTrackingClient:
def __init__(self, tracking_uri: str):
self.tracking_uri = tracking_uri
class DummyWorkspaceClient:
def __init__(self, workspace_uri: str | None = None):
self.workspace_uri = workspace_uri
def create_workspace(self, *args, **kwargs):
recorded["create"] = (args, kwargs)
return None
def update_workspace(self, *args, **kwargs):
recorded["update"] = (args, kwargs)
return None
monkeypatch.setattr("mlflow.tracking.client.TrackingServiceClient", DummyTrackingClient)
monkeypatch.setattr("mlflow.tracking.client.TracingClient", lambda _: None)
monkeypatch.setattr("mlflow.tracking.client.WorkspaceProviderClient", DummyWorkspaceClient)
monkeypatch.setattr(
"mlflow.tracking.client.utils._resolve_tracking_uri",
lambda uri: uri or "sqlite:///tracking.db",
)
monkeypatch.setattr(
"mlflow.tracking.client.registry_utils._resolve_registry_uri",
lambda registry_uri, tracking_uri: "registry-resolved",
)
client = MlflowClient(tracking_uri="sqlite:///tracking.db")
client.create_workspace(
"team-a",
trace_archival_config=TraceArchivalConfig(
location="s3://archive/team-a",
retention="30d",
),
)
client.update_workspace(
"team-a",
trace_archival_config=TraceArchivalConfig(
location="s3://archive/team-b",
retention="14d",
),
)
assert recorded["create"][1]["trace_archival_config"] == TraceArchivalConfig(
location="s3://archive/team-a",
retention="30d",
)
assert recorded["update"][1]["trace_archival_config"] == TraceArchivalConfig(
location="s3://archive/team-b",
retention="14d",
)
def test_workspace_fluent_forwards_trace_archival_config(monkeypatch):
recorded: dict[str, tuple[tuple[object, ...], dict[str, object]]] = {}
class DummyClient:
def create_workspace(self, *args, **kwargs):
recorded["create"] = (args, kwargs)
return None
def update_workspace(self, *args, **kwargs):
recorded["update"] = (args, kwargs)
return None
monkeypatch.setattr(workspace_fluent, "MlflowClient", DummyClient)
workspace_fluent.create_workspace(
"team-a",
trace_archival_config=TraceArchivalConfig(
location="s3://archive/team-a",
retention="30d",
),
)
workspace_fluent.update_workspace(
"team-a",
trace_archival_config=TraceArchivalConfig(
location="",
retention="",
),
)
assert recorded["create"][1]["trace_archival_config"] == TraceArchivalConfig(
location="s3://archive/team-a",
retention="30d",
)
assert recorded["update"][1]["trace_archival_config"] == TraceArchivalConfig(
location="",
retention="",
)
def test_workspace_provider_client_flattens_trace_archival_config(monkeypatch):
recorded: dict[str, object] = {}
class DummyStore:
def create_workspace(self, workspace):
recorded["create"] = workspace
return workspace
def update_workspace(self, workspace):
recorded["update"] = workspace
return workspace
monkeypatch.setattr(
"mlflow.tracking._workspace.client.get_workspace_store",
lambda workspace_uri: DummyStore(),
)
client = WorkspaceProviderClient("sqlite:///workspace.db")
created = client.create_workspace(
"team-a",
trace_archival_config=TraceArchivalConfig(location="s3://archive/team-a"),
)
updated = client.update_workspace(
"team-a",
trace_archival_config=TraceArchivalConfig(location="", retention=""),
)
assert recorded["create"] == created
assert created.trace_archival_location == "s3://archive/team-a"
assert created.trace_archival_retention is None
assert recorded["update"] == updated
assert updated.trace_archival_location == ""
assert updated.trace_archival_retention == ""