Repository: sktime/sktime
Pull request #7955 (2025-03-09 17:56:54)

[ENH] remove `numpy 2` dependency handler from `BaseObject`
Half a year ago, we added the method `_handle_numpy2_softdeps` to handle dependencies that were not `numpy 2` compatible, but did not advertise that bound in their package dep bounds. The method was intended to be temporary, until packages move to declare their `numpy` bounds properly, or become compatible with `numpy 2`.

Most soft dependencies have moved to `numpy 2` compatibility, but `pmdarima` has not, and perhaps will not put this in their dependency bounds - it is the only such dependency remaining.

This PR removes the temporary utility and moves the bound into the `pmdarima` adapter.


Patch:
diff --git a/docs/source/developer_guide/add_estimators.rst b/docs/source/developer_guide/add_estimators.rst
index b6b1dafb8..cccbbba2f 100644
--- a/docs/source/developer_guide/add_estimators.rst
+++ b/docs/source/developer_guide/add_estimators.rst
@@ -176,7 +176,7 @@ A useful workflow for using ``check_estimator`` to debug an estimator is as foll
 
 1. Run ``check_estimator(MyEstimator)`` to find failing tests
 2. Subset to failing tests or fixtures using ``fixtures_to_run`` or ``tests_to_run``
-3. If the failure is not obvious, set ``raise_exceptions=True`` to raise the exception and inspecet the traceback.
+3. If the failure is not obvious, set ``raise_exceptions=True`` to raise the exception and inspect the traceback.
 4. If the failure is still not clear, use advanced debuggers on the line of code with ``check_estimator``.
 
 Running the test suite in a repository clone
diff --git a/sktime/base/_base.py b/sktime/base/_base.py
index 3ebe1eb75..ff6c131f1 100644
--- a/sktime/base/_base.py
+++ b/sktime/base/_base.py
@@ -151,59 +151,6 @@ class BaseObject(_HTMLDocumentationLinkMixin, _BaseObject):
         """,
     }
 
-    def __init__(self):
-        super().__init__()
-
-        # handle numpy 2 incompatible soft dependencies
-        # for rationale, see _handle_numpy2_softdeps
-        self._handle_numpy2_softdeps()
-
-    # TODO 0.36.0: check list of numpy 2 incompatible soft deps
-    # remove any from NOT_NP2_COMPATIBLE that become compatible
-    def _handle_numpy2_softdeps(self):
-        """Handle tags for soft deps that are not numpy 2 compatible.
-
-        A number of soft dependencies are not numpy 2 compatible yet,
-        but do not set the bound in their setup.py. This method is a patch over
-        those packages' missing bound setting to provide informative
-        errors to users.
-
-        This method does the following:
-
-        * checks if any soft dependencies in the python_dependencies tag
-          are in NOT_NP2_COMPATIBLE, this is a hard-coded
-          list of soft dependencies that are not numpy 2 compatible
-        * if any are found, adds a numpy<2.0 soft dependency to the list,
-          and sets it as a dynamic override of the python_dependencies tag
-        """
-        from packaging.requirements import Requirement
-
-        # pypi package names of soft dependencies that are not numpy 2 compatibleS
-        NOT_NP2_COMPATIBLE = ["pmdarima"]
-
-        softdeps = self.get_class_tag("python_dependencies", [])
-        if softdeps is None:
-            return None
-        if not isinstance(softdeps, list):
-            softdeps = [softdeps]
-        # make copy of list to avoid side effects
-        softdeps = softdeps.copy()
-
-        def _pkg_name(req):
-            """Get package name from requirement string."""
-            return Requirement(req).name
-
-        noncomp = False
-        for softdep in softdeps:
-            # variable: does any softdep string start with one of the non-compatibles
-            noncomp_sd = any([_pkg_name(softdep) == pkg for pkg in NOT_NP2_COMPATIBLE])
-            noncomp = noncomp or noncomp_sd
-
-        if noncomp:
-            softdeps = softdeps + ["numpy<2.0"]
-            self.set_tags(python_dependencies=softdeps)
-        return None
-
     def __eq__(self, other):
         """Equality dunder. Checks equal class and parameters.
 
diff --git a/sktime/forecasting/base/adapters/_pmdarima.py b/sktime/forecasting/base/adapters/_pmdarima.py
index 0733b1fbf..fd336da73 100644
--- a/sktime/forecasting/base/adapters/_pmdarima.py
+++ b/sktime/forecasting/base/adapters/_pmdarima.py
@@ -23,7 +23,8 @@ class _PmdArimaAdapter(BaseForecaster):
         "capability:pred_int:insample": True,
         "requires-fh-in-fit": False,
         "handles-missing-data": True,
-        "python_dependencies": "pmdarima",
+        # TODO 0.37.0: check if numpy 2 incompatiblity can be removed
+        "python_dependencies": ["pmdarima", "numpy<2"],
     }
 
     def __init__(self):
diff --git a/sktime/forecasting/base/adapters/_tbats.py b/sktime/forecasting/base/adapters/_tbats.py
index 34d90e1a7..dc24cf0fa 100644
--- a/sktime/forecasting/base/adapters/_tbats.py
+++ b/sktime/forecasting/base/adapters/_tbats.py
@@ -24,7 +24,7 @@ class _TbatsAdapter(BaseForecaster):
         "capability:pred_int:insample": True,
         "requires-fh-in-fit": False,
         "handles-missing-data": False,
-        "python_dependencies": "tbats",
+        "python_dependencies": ["tbats", "numpy<2"],
     }
 
     def __init__(


Test patch:
diff --git a/sktime/registry/tests/test_craft.py b/sktime/registry/tests/test_craft.py
index b9b6e4dc7..79d005524 100644
--- a/sktime/registry/tests/test_craft.py
+++ b/sktime/registry/tests/test_craft.py
@@ -102,7 +102,7 @@ def test_deps(spec):
     assert deps(pipe_spec_with_deps) == ["statsmodels"]
 
     # example with two dependencies, should be identified, order does not matter
-    assert set(deps(dunder_spec_with_deps)) == {"statsmodels", "pmdarima"}
+    assert set(deps(dunder_spec_with_deps)) == {"statsmodels", "pmdarima", "numpy<2"}
 
 
 def test_imports():
diff --git a/sktime/tests/_config.py b/sktime/tests/_config.py
index 1db91f5f2..3d2436b3b 100644
--- a/sktime/tests/_config.py
+++ b/sktime/tests/_config.py
@@ -247,6 +247,11 @@ EXCLUDED_TESTS = {
     ],
     # see PR 7921
     "RocketClassifier": ["test_classifier_on_basic_motions"],
+    # see bug report #6465 and #7958
+    "MACNNClassifier": [
+        "test_multioutput",
+        "test_classifier_on_unit_test_data",
+    ],
 }
 
 # exclude tests but keyed by test name