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

73 lines
2.5 KiB
Python

"""Shared helpers for workspace-related database operations.
Used by both ``workspace_migration`` (migrate-to-default-workspace) and
``workspace_move`` (move-resources).
"""
from __future__ import annotations
import sqlalchemy as sa
# Child tables of registered_models that carry their own workspace column.
# These must be updated explicitly because not all backends honour
# ON UPDATE CASCADE at the SQL level (e.g. SQLite without foreign_keys pragma).
MODEL_CHILD_TABLES = [
"model_versions",
"registered_model_tags",
"model_version_tags",
"registered_model_aliases",
]
# Tables that carry a workspace column but belong to non-model root resources.
# They must also be migrated explicitly during workspace operations.
OTHER_WORKSPACE_CHILD_TABLES = [
"guardrail_configs",
]
_NOT_ENABLED_MSG = (
"Aborted: the database does not have workspaces enabled. This command "
"operates directly on the SQL database used by the default workspace "
"provider. Please make sure the specified database URI is correct and "
"that workspaces have been enabled via `mlflow db upgrade`."
)
def get_workspace_table(conn, table_name: str) -> sa.Table:
"""Reflect *table_name* and verify it contains a ``workspace`` column."""
try:
table = sa.Table(table_name, sa.MetaData(), autoload_with=conn)
except sa.exc.NoSuchTableError:
raise RuntimeError(f"{_NOT_ENABLED_MSG} (missing table {table_name!r}).")
if "workspace" not in table.c:
raise RuntimeError(_NOT_ENABLED_MSG)
return table
def validate_workspace_exists(conn, workspace_name: str) -> None:
"""Raise ``RuntimeError`` if *workspace_name* is not in the workspaces table."""
try:
workspaces = sa.Table("workspaces", sa.MetaData(), autoload_with=conn)
except sa.exc.NoSuchTableError:
raise RuntimeError(_NOT_ENABLED_MSG)
exists = conn.execute(
sa.select(sa.literal(1)).select_from(workspaces).where(workspaces.c.name == workspace_name)
).first()
if not exists:
raise RuntimeError(f"Workspace {workspace_name!r} does not exist.")
def format_truncated_list(
items: list[str],
*,
max_rows: int | None,
) -> str:
"""Format a list of display strings with optional truncation."""
rows = items if max_rows is None else items[:max_rows]
formatted = "\n ".join(rows)
if formatted:
formatted = f"\n {formatted}"
if max_rows is not None and len(items) > max_rows:
formatted += f"\n ... ({len(items) - max_rows} more)"
return formatted