2026-04-26
Session 46: Phase 6: delete pycaret/internal/pycaret_experiment/
Engineering log for session 46.
Engine MVP 1 is feature-complete for 4.0.0. The legacy directory and all five oop.py thin wrappers — ~22K LoC total — are deleted. The default 4.0 workflow runs without ever importing legacy code.
REMOVED — engine#
REMOVED, BREAKING—pycaret/internal/pycaret_experiment/directory (10K LoC acrosspycaret_experiment.py/tabular_experiment.py/supervised_experiment.py/non_ts_supervised_experiment.py/ts_supervised_experiment.py/unsupervised_experiment.py). Importing any submodule now raisesModuleNotFoundError.REMOVED, BREAKING—pycaret/classification/oop.py(3.3K LoC). The module-level export of the legacyClassificationExperimentclass is gone. Usefrom pycaret.classification import ClassificationExperiment(the 4.0 OOP class) — same import path, native implementation.REMOVED, BREAKING—pycaret/regression/oop.py(2.6K LoC). Same migration:from pycaret.regression import RegressionExperiment.REMOVED, BREAKING—pycaret/clustering/oop.py(351 LoC). Usefrom pycaret.clustering import ClusteringExperiment.REMOVED, BREAKING—pycaret/anomaly/oop.py(165 LoC). Usefrom pycaret.anomaly import AnomalyExperiment.REMOVED, BREAKING—pycaret/time_series/forecasting/oop.py(5.7K LoC). Usefrom pycaret.time_series import TimeSeriesExperiment. Theforecastingsubmodule is now an empty namespace kept only for back-compat.REMOVED—Experiment._snapshot_fit_statemethod,Experiment._build_legacy_setup_kwargs,Experiment._create_model_legacy,Experiment._predict_model_legacyincore/experiment.py. All only-used-by-legacy paths.
CHANGED — engine#
BREAKING—setup_kwargsraiseConfigurationError. Previously caller-supplied kwargs would fall through to the legacysetup()for power users. Phase 6 deleted that escape hatch. Power users with removed knobs pin to PyCaret 3.x or open an issue requesting a first-class constructor parameter.BREAKING— Six legacy-only verbs raiseNotImplementedErrorwith concrete migration pointers:plot_model/evaluate_model→ "Plotly rewrite is post-4.0.0".interpret_model→ "use SHAP directly:import shap; shap.Explainer(pipeline.steps[-1][1])(X_test)".automl→ "usecompare_models(n_select=N).bestthentune_model(best)".get_leaderboard→ "the leaderboard DataFrame is oncompare_models(...).leaderboard".check_stats→ "use sktime / statsmodels directly:from statsmodels.tsa.stattools import adfuller; adfuller(exp.y_train)".
CHANGED—Experiment.pull()returnsNonewhen no metrics-emitting verb has run yet (the legacy_legacy.pull()fallback was removed).CHANGED—Experiment.models()returns an emptyDataFramewhen_fit_state["model_registry"]is empty (no more legacy fallback).CHANGED—Experiment.get_metrics()returns an emptyDataFramewhen the metric registry is empty.CHANGED—Experiment.add_metric/Experiment.remove_metricraiseNotImplementedErrorfor tasks without a native metric registry (clustering / anomaly / time-series — though TS does build one natively in s40).CHANGED—SupervisedExperiment._compare_models_legacy/_tune_model_legacy/_ensemble_model_legacy/_blend_models_legacy/_stack_models_legacy/_finalize_model_legacyare now stubs that raiseNotImplementedError. Reachable only when called on a non-supervised, non-TS experiment that doesn't override the verb itself — i.e. clustering / anomaly's compare/tune/etc., which were never natively implemented.
ADDED — engine#
ADDED—_LegacyShimclass incore/experiment.py. Phase-6 placeholder for the deleted legacy experiment object:__slots__-bound namespace with all 27 verb names predefined as no-op identity callables. Exists only so the established drain-lock test pattern (monkeypatch.setattr(exp._legacy, "setup", _poison)) keeps working unchanged. Production code never reads off it.Experiment._build_legacy_experiment()returns a fresh_LegacyShim().Experiment.fit()andTimeSeriesExperiment.fit()setself._legacy = _LegacyShim()post-fit.
CHANGED — type aliases#
CHANGED—pycaret.containers.models.base_model._PyCaretExperimentis nowAny(wasfrom pycaret.internal.pycaret_experiment.pycaret_experiment import _PyCaretExperiment). The annotation is stringified byfrom __future__ import annotations; runtime never resolves it.CHANGED—pycaret.utils.generic._PyCaretExperiment(TYPE_CHECKING-guarded) →Any.CHANGED—pycaret/time_series/forecasting/__init__.pyis now an empty namespace; no morefrom pycaret.time_series.forecasting.oop import TSForecastingExperiment.
ADDED — tests#
ADDED—packages/engine/tests/test_session46_phase6_legacy_deletion.py— 14 new tests:- All six deleted modules raise
ModuleNotFoundErroron import (pycaret.internal.pycaret_experiment/pycaret.classification.oop/pycaret.regression.oop/pycaret.clustering.oop/pycaret.anomaly.oop/pycaret.time_series.forecasting.oop). - All five 4.0
Experimentclasses still importable and callable. - Each removed verb (
plot_model/evaluate_model/interpret_model/automl/get_leaderboard/check_stats) raisesNotImplementedErrorwith the expected message. _LegacyShimpredefines every verb name as a no-op callable.
- All six deleted modules raise
CHANGED — existing tests#
CHANGED—test_session35_native_setup::test_complex_preprocessing_falls_back_to_legacy→ renamed totest_setup_kwargs_raise_in_phase6. Inverted: now expectsConfigurationError.CHANGED—test_session39_native_setup_phase5::test_time_series_setup_kwargs_falls_back_to_legacy_with_state_snapshot→ renamed totest_time_series_setup_kwargs_raises_in_phase6. Same inversion.CHANGED—test_session45_ts_native_setup_full_drain::test_setup_kwargs_still_routes_to_legacy→ renamed totest_setup_kwargs_raises_in_phase6.
INTERNAL#
INTERNAL— Why_LegacyShiminstead of removingself._legacyentirely. The drain-lock test pattern (monkeypatch.setattr(exp._legacy, "setup", _poison)then verify the verb runs without firing the poison) is used across 8+ test files and 50+ test functions. Deletingself._legacywould mean rewriting every test. The shim is a 30-line no-op class that keeps the pattern working with zero test diffs — the structural drain assertion is unchanged because the poison is set on a real attribute, but production never calls anything on the shim.INTERNAL— Whysetup_kwargsraise rather than silently accept-and-warn. Silent acceptance would create a footgun: users passing the same kwargs they used in 3.x would get different behavior with no signal. Raising forces the migration to be explicit.INTERNAL— Why six verbs raise rather than being absent. Keeping the method signatures preserves discoverability (exp.plot_model?shows the docstring + migration hint) and lets sklearninspect-based tooling enumerate the surface. Absent methods wouldAttributeErrorwith no useful pointer.INTERNAL—from __future__ import annotationsinbase_model.py. The_PyCaretExperimentsymbol is referenced in two type annotations (_set_engineparameter + docstring). With future annotations, both become string literals that don't need to be importable at runtime — only at static-analysis time, where theAnyalias suffices.
Session 46 delta summary#
| Metric | Session 45 end | Session 46 end |
|---|---|---|
| Legacy LoC in repo | ~22K | 0 |
legacy.<anything> callsites in production | 0 (default flow) | 0 (every flow) |
| Engine tests (fast + slow) | 244 | 258 |