import shutil from pathlib import Path import pytest import sqlalchemy from mlflow.environment_variables import MLFLOW_TRACKING_URI from mlflow.store.db.db_types import MSSQL, MYSQL, POSTGRES, SQLITE from mlflow.store.tracking.dbmodels.models import ( SqlDataset, SqlEntityAssociation, SqlEvaluationDataset, SqlEvaluationDatasetRecord, SqlExperiment, SqlExperimentTag, SqlInput, SqlInputTag, SqlLatestMetric, SqlLoggedModel, SqlLoggedModelMetric, SqlLoggedModelParam, SqlLoggedModelTag, SqlMetric, SqlOnlineScoringConfig, SqlParam, SqlRun, SqlTag, SqlTraceInfo, SqlTraceMetadata, SqlTraceTag, ) from mlflow.store.tracking.sqlalchemy_store import SqlAlchemyStore ARTIFACT_URI = "artifact_folder" def _get_query_to_reset_experiment_id(store: SqlAlchemyStore): dialect = store._get_dialect() if dialect == POSTGRES: return "ALTER SEQUENCE experiments_experiment_id_seq RESTART WITH 1" elif dialect == MYSQL: return "ALTER TABLE experiments AUTO_INCREMENT = 1" elif dialect == MSSQL: return "DBCC CHECKIDENT (experiments, RESEED, 0)" elif dialect == SQLITE: # In SQLite, deleting all experiments resets experiment_id return None raise ValueError(f"Invalid dialect: {dialect}") def _cleanup_database(store: SqlAlchemyStore): with store.ManagedSessionMaker() as session: # Delete all rows in all tables for model in ( SqlLoggedModel, SqlLoggedModelMetric, SqlLoggedModelParam, SqlLoggedModelTag, SqlParam, SqlMetric, SqlLatestMetric, SqlTag, SqlInputTag, SqlInput, SqlDataset, SqlRun, SqlTraceTag, SqlTraceMetadata, SqlTraceInfo, SqlEvaluationDatasetRecord, SqlEntityAssociation, SqlEvaluationDataset, SqlExperimentTag, SqlOnlineScoringConfig, SqlExperiment, ): session.query(model).delete() # Reset experiment_id to start at 1 if reset_experiment_id := _get_query_to_reset_experiment_id(store): session.execute(sqlalchemy.sql.text(reset_experiment_id)) @pytest.fixture(scope="module") def cached_db(tmp_path_factory) -> Path: """Creates and caches a SQLite database to avoid repeated migrations for each test run.""" tmp_path = tmp_path_factory.mktemp("sqlite_db") db_path = tmp_path / "mlflow.db" db_uri = f"sqlite:///{db_path}" store = SqlAlchemyStore(db_uri, ARTIFACT_URI) store.engine.dispose() return db_path @pytest.fixture def store(tmp_path: Path, cached_db: Path) -> SqlAlchemyStore: artifact_uri = tmp_path / "artifacts" artifact_uri.mkdir(exist_ok=True) if db_uri_env := MLFLOW_TRACKING_URI.get(): s = SqlAlchemyStore(db_uri_env, artifact_uri.as_uri()) yield s _cleanup_database(s) else: db_path = tmp_path / "mlflow.db" shutil.copy(cached_db, db_path) db_uri = f"sqlite:///{db_path}" s = SqlAlchemyStore(db_uri, artifact_uri.as_uri()) yield s