Repository: sktime/sktime

Issue:
[ENH] Extend tagging system to handle dependencies with alias
Currently, estimator tagging system failed to handle soft dependency that has alias. For example, `AlignerDTW` (dtw-python/dtw) from the alignment module and #4580 (scikit-optimize/skopt). 

The current solution is to use a private validation utility function, `_check_soft_dependencies` before calling `super().__init__()` to handle the shortfall. 


**Solution Descriptions**
Other solution that I have in mind is to add an additional tag, *dependencies_alias* and extend the `_check_estimator_dep` to check for alias and pass the alias to `_check_soft_dependecies` that is used under the hood. A quick code solution may look like

```python
class Estimator(BaseEstimator):

    _tags = {
        ...
        "python_dependencies": ["numpy", "pandas", "scipy", "scikit-learn"],
        "python_dependencies_alias": {"scikit-learn": "sklearn"},
    }

    def __init__(...):
        ...

# Improvement to check_estimator_dep 
# since _check_soft_dependecies already handle the alias, check_estimator_dep should be easy to extend the case.

def _check_estimator_deps(obj, msg=None, severity="error"):
    compatible = True

    ... object iteration check ...
    ... python version check ...

    pkg_deps = obj.get_class_tag("python_dependencies", None)
    pck_alias = obj.get_class_tag("python_dependencies_alias", None)
    if pkg_deps is not None and not isinstance(pkg_deps, list):
        pkg_deps = [pkg_deps]
    if pkg_deps is not None:
        pkg_deps_ok = _check_soft_dependencies(*pkg_deps, severity=severity, obj=obj, package_import_alias=pck_alias)
        compatible = compatible and pkg_deps_ok

    return compatible
```



Discussion:
Yes, I think this is the way to deal with this!

Nb, I would locate the aliases with the `BaseObject` - since the aliases are not estimator specific but universal.

Do you want to make a PR, in which you add the above code, and in which you remove the known workarounds?
 > I` would locate the aliases with the BaseObject

Noted

> Do you want to make a PR, in which you add the above code, and in which you remove the known workarounds?

Yup, absolutely. Will make a PR soon.

Resolving patch:
diff --git a/sktime/alignment/dtw_python.py b/sktime/alignment/dtw_python.py
--- a/sktime/alignment/dtw_python.py
+++ b/sktime/alignment/dtw_python.py
@@ -51,6 +51,7 @@ class AlignerDTW(BaseAligner):
         "capability:distance": True,  # does compute/return overall distance?
         "capability:distance-matrix": True,  # does compute/return distance matrix?
         "python_dependencies": "dtw-python",
+        "python_dependecies_alias": {"dtw-python": "dtw"},
     }
 
     def __init__(
@@ -65,13 +66,6 @@ def __init__(
         """Construct instance."""
         # added manually since dtw-python has an import alias
         # default check from super.__init__ does not allow aliases
-        _check_soft_dependencies(
-            "dtw-python",
-            package_import_alias={"dtw-python": "dtw"},
-            severity="error",
-            obj=self,
-            suppress_import_stdout=True,
-        )
         super().__init__()
 
         self.dist_method = dist_method
diff --git a/sktime/registry/_tags.py b/sktime/registry/_tags.py
--- a/sktime/registry/_tags.py
+++ b/sktime/registry/_tags.py
@@ -371,6 +371,13 @@
         ("list", "str"),
         "python dependencies of estimator as str or list of str",
     ),
+    (
+        "python_dependencies_alias",
+        "estimator",
+        "dict",
+        "should be provided if import name differs from package name, \
+        key-value pairs are package name, import name",
+    ),
     (
         "requires_cython",
         "estimator",
diff --git a/sktime/utils/validation/_dependencies.py b/sktime/utils/validation/_dependencies.py
--- a/sktime/utils/validation/_dependencies.py
+++ b/sktime/utils/validation/_dependencies.py
@@ -378,10 +378,13 @@ def _check_estimator_deps(obj, msg=None, severity="error"):
     compatible = compatible and _check_python_version(obj, severity=severity)
 
     pkg_deps = obj.get_class_tag("python_dependencies", None)
+    pck_alias = obj.get_class_tag("python_dependencies_alias", None)
     if pkg_deps is not None and not isinstance(pkg_deps, list):
         pkg_deps = [pkg_deps]
     if pkg_deps is not None:
-        pkg_deps_ok = _check_soft_dependencies(*pkg_deps, severity=severity, obj=obj)
+        pkg_deps_ok = _check_soft_dependencies(
+            *pkg_deps, severity=severity, obj=obj, package_import_alias=pck_alias
+        )
         compatible = compatible and pkg_deps_ok
 
     return compatible


Test patch: