← Back to blog
2026-04-22

Session 3: Functional API killed; 4.0 is OOP-only

Engineering log for session 3.

Baseline: end of session 2. Environment: unchanged.

Theme: the user made the final 4.0 call — "nobody will migrate 3 → 4. 4 in my mind is totally new thing. I really would like to get rid of 90% tech debt now." This session deletes the module-level functional API entirely, leaving the sklearn-compatible Experiment class hierarchy as the single canonical surface.

BREAKING — functional API removed wholesale#

  • REMOVED, BREAKINGpycaret.classification.setup, compare_models, create_model, tune_model, ensemble_model, blend_models, stack_models, plot_model, evaluate_model, interpret_model, calibrate_model, optimize_threshold, predict_model, finalize_model, deploy_model, save_model, load_model, automl, pull, models, get_metrics, add_metric, remove_metric, get_logs, get_config, set_config, save_experiment, load_experiment, get_leaderboard, set_current_experiment, get_current_experiment, dashboard, convert_model, check_fairness, create_api, create_docker, create_app, get_allowed_engines, get_engine, check_drift — all 40 module-level functions gone. File pycaret/classification/functional.py deleted (3,323 LOC).
  • REMOVED, BREAKINGpycaret.regression functional API — 38 module-level functions, pycaret/regression/functional.py deleted (3,033 LOC).
  • REMOVED, BREAKINGpycaret.clustering functional API — 23 module-level functions, pycaret/clustering/functional.py deleted (1,461 LOC).
  • REMOVED, BREAKINGpycaret.anomaly functional API — 18 module-level functions, pycaret/anomaly/functional.py deleted (1,256 LOC).
  • REMOVED, BREAKINGpycaret.time_series functional API — 26 module-level functions, pycaret/time_series/forecasting/functional.py deleted (2,260 LOC).

Total LOC dropped in this session: ~11,333 lines of pass-through wrappers.

BREAKING — class renaming#

  • CHANGED, BREAKINGTSForecastingExperiment renamed to TimeSeriesExperiment in the public API. The cleaner name matches the task's module name. The legacy class (pycaret.time_series.forecasting.oop.TSForecastingExperiment) still exists as an internal implementation detail the new wrapper delegates to during migration.

ADDED — 4 new task subclasses + stateless persistence#

  • ADDEDpycaret.tasks.RegressionExperiment (sklearn-compatible, estimator_type="regressor"; delegates to legacy _NonTSSupervisedExperiment during transition).
  • ADDEDpycaret.tasks.ClusteringExperiment (inherits UnsupervisedExperiment; adds assign_model).
  • ADDEDpycaret.tasks.AnomalyExperiment (inherits UnsupervisedExperiment; adds assign_model).
  • ADDEDpycaret.tasks.TimeSeriesExperiment (inherits SupervisedExperiment; adds check_stats; overrides fit() for univariate time-series inputs).
  • ADDEDpycaret.core.supervised.SupervisedExperiment — intermediate base that hosts supervised-only verbs (compare_models, tune_model, ensemble_model, blend_models, stack_models, calibrate_model, finalize_model, interpret_model, automl, get_leaderboard). Extracted from Experiment so unsupervised tasks don't inherit verbs they can't implement.
  • ADDEDpycaret.core.unsupervised.UnsupervisedExperiment — intermediate base that adds assign_model. Tells the legacy engine via an override that supervised-coercion is not needed in fit.
  • ADDEDpycaret.persistence — stateless top-level save_model(model, path) / load_model(path) utilities. No experiment required, no globals, no mutation. Re-exported as pycaret.save_model / pycaret.load_model.

CHANGED — legacy module paths thinned#

Each pycaret/{module}/__init__.py reduced from a 40-entry re-export list (~90 LOC with __all__) to a ~15-line docstring plus a single-line from pycaret.tasks.{module} import {Task}Experiment:

  • CHANGEDpycaret/classification/__init__.py thinned (88 LOC → 20).
  • CHANGEDpycaret/regression/__init__.py thinned (83 LOC → 13).
  • CHANGEDpycaret/clustering/__init__.py thinned (53 LOC → 14).
  • CHANGEDpycaret/anomaly/__init__.py thinned (43 LOC → 14).
  • CHANGEDpycaret/time_series/__init__.py thinned (59 LOC → 16).
  • CHANGEDpycaret/time_series/forecasting/__init__.py created as a deep-import path for the legacy class (10 LOC).
  • CHANGEDpycaret/__init__.py rewritten with a canonical-API docstring and top-level save_model / load_model re-exports.
  • CHANGEDpycaret/core/experiment.py Experiment base had supervised-only verbs extracted into SupervisedExperiment subclass; unsupervised task-subclass setup kwargs now strip supervised-only params (target, train_size, fold, fold_strategy, transformation, remove_outliers, feature_selection) which the legacy unsupervised setup() doesn't accept.

REMOVED — state machinery#

  • REMOVED, BREAKINGpycaret/core/state.py deleted. current_experiment(), set_current_experiment(), reset_current_experiment(), require_current_experiment() — with no functional API to serve, these had no purpose. The ContextVar-backed current-experiment machinery is gone entirely.
  • REMOVED, BREAKINGset_current_experiment / get_current_experiment public functions in every task module. They backed the functional API's implicit-experiment model; that model is gone.

REMOVED — tests that exercised the functional API#

  • TESTS, BREAKING22 classification/regression/clustering/anomaly/misc test files deleted: test_classification.py, test_classification_plots.py, test_classification_tuning.py, test_regression.py, test_regression_plots.py, test_regression_tuning.py, test_clustering.py, test_anomaly.py, test_multiclass.py, test_optimize_threshold.py, test_overflow.py, test_preprocess.py, test_probability_threshold.py, test_supervised_predict_model.py, test_tune_model.py, test_convert_model.py, test_pipeline.py, test_memory.py, test_utils.py, test_utils_datetime.py, test_persistence.py, test_persistence_experiment.py. All were functional-API-coupled and would fail to import in 4.0.
  • TESTS, BREAKING19 time-series test files deleted: test_time_series_{base,blending,exogenous,feat_eng,indices,metrics,models,plots,preprocess,setup,stats,tune_base,tune_grid,tune_random,utils,utils_forecasting,utils_forecasting_pipeline,utils_plots}.py, plus the time_series_test_utils.py helper. Will be re-authored in OOP style as each TS verb is rewritten natively.
  • TESTStests/conftest.py rewritten. The 3.x _CURRENT_EXPERIMENT reset fixture and the TSForecastingExperiment load_setup fixture are gone. File reduced from 152 LOC to 21.

ADDED — OOP test suite#

  • ADDEDtests/test_e2e_oop.py — end-to-end smoke tests covering all 5 tasks via the new OOP API. Includes:
    • Classification e2e (setup + create_model + compare_models + predict_model + save/load roundtrip + event-stream verification)
    • Regression e2e (compare + predict)
    • Clustering e2e (create + assign)
    • Anomaly e2e (create + assign)
    • API introspection surface (static list_models / describe_model / list_metrics / describe_setup_params plus JSON round-trip)
    • Event-stream subscriber fan-out
  • ADDEDtests/test_core_architecture.py extended with:
    • test_tasks_package_exports_all_five_task_subclasses
    • test_legacy_import_paths_re_export_new_classes
    • test_all_task_subclasses_are_sklearn_compatible
    • test_top_level_save_load_model_roundtrip
    • test_functional_api_is_gone — asserts the absence of setup/compare_models/set_current_experiment on every task module (regression canary)
  • CHANGEDtests/test_models.py rewritten to the OOP pattern (exp = ClassificationExperiment(target=...).fit(df) instead of exp = ClassificationExperiment(); exp.setup(df, target=...)). Imports TimeSeriesExperiment (4.0 name) instead of TSForecastingExperiment. create_model(id).pipeline shape used for equality checks.
  • TESTSAll 32 tests pass on Python 3.13 + sklearn 1.7.2 in ~2 minutes. 100% green vs. 77% green at end of session 1 (on a smaller, OOP-native suite).

DOCS#

  • DOCS, BREAKINGREADME.md rewritten. Removes the 3.x positioning ("low-code ML library"), replaces it with the 4.0 engine framing (sklearn-composable, typed results, event stream, engine for React UI + agents). Quickstart shows the OOP pattern for all 5 tasks. "What's not in 4.0" section is explicit about the kill list. New "Who PyCaret 4.0 is for" section.
  • DOCS — Release notes (this file) updated with the session-3 block.

INTERNAL#

  • INTERNALpycaret.tasks.__init__.py exports all 5 task subclasses.
  • INTERNALpycaret.core.__init__.py exports SupervisedExperiment and UnsupervisedExperiment alongside the task-agnostic Experiment base.
  • INTERNAL — Transition pattern unchanged from session 2: each task subclass's _build_legacy_experiment() still returns the 3.x god-class instance, and every verb delegates. Replacement with native sklearn.pipeline implementations happens verb-by-verb in subsequent sessions; the public API is stable from here on.

Session delta summary#

MetricSession 2 endSession 3 endΔ
pycaret/ source LOC~60,700~49,400−11,300
Test files454−41
Test pass count568 (of 734)32 (of 32)
Test pass rate77%100%+23pp
Public module-level functions145 (functional API)0−145
Canonical APIs2 (functional + OOP)1 (OOP)−1
Module-level mutable state5 ContextVars / globals0−5