Scikit-learn v1.6.0 breaks SelectFromModel when using a non-sklearn model

### Describe the bug

There seem to be a bug introduced by v1.6.0 where the SelectFromModel must use a model for which the parent class also has a `__sklearn_tags__` method. This works with sklearn models but not with 3rd party models using a sklearn type API. 

It looks like folks working on xgboost are busy making some changes on their side as well.

### Steps/Code to Reproduce

```
import pandas as pd
from sklearn.pipeline import Pipeline
from sklearn.feature_selection import SelectFromModel
from xgboost import XGBClassifier

X = pd.DataFrame({"A": [1, 2, 3], "B": [5, 4, 3]})
y = pd.Series([1, 0, 1])
model = XGBClassifier()
pipeline = Pipeline(
    steps=[
        ("feature_selection", SelectFromModel(model)),
        ("classifier", model),
    ]
)

pipeline.fit(X, y)
```

### Expected Results

This was working fine up until v1.6.0.

### Actual Results

```
AttributeError: 'super' object has no attribute '__sklearn_tags__'
----> 1 pipeline.fit(X, y)
File .../MLWorkloadsInstrumentation/_sklearn.py:29, in _create_patch_function.<locals>.patch_function(self, *args, **kwargs)
     27 try:
     28     original_succeeded = False
---> 29     original_result = original(self, *args, **kwargs)
     30     original_succeeded = True
     31     return original_result
File .../python3.10/site-packages/sklearn/base.py:1389, in _fit_context.<locals>.decorator.<locals>.wrapper(estimator, *args, **kwargs)
   1382     estimator._validate_params()
   1384 with config_context(
   1385     skip_parameter_validation=(
   1386         prefer_skip_nested_validation or global_skip_validation
   1387     )
   1388 ):
-> 1389     return fit_method(estimator, *args, **kwargs)
File .../python3.10/site-packages/sklearn/pipeline.py:652, in Pipeline.fit(self, X, y, **params)
    645     raise ValueError(
    646         "The `transform_input` parameter can only be set if metadata "
    647         "routing is enabled. You can enable metadata routing using "
    648         "`sklearn.set_config(enable_metadata_routing=True)`."
    649     )
    651 routed_params = self._check_method_params(method="fit", props=params)
--> 652 Xt = self._fit(X, y, routed_params, raw_params=params)
    653 with _print_elapsed_time("Pipeline", self._log_message(len(self.steps) - 1)):
    654     if self._final_estimator != "passthrough":
File .../python3.10/site-packages/sklearn/pipeline.py:586, in Pipeline._fit(self, X, y, routed_params, raw_params)
    579 # Fit or load from cache the current transformer
    580 step_params = self._get_metadata_for_step(
    581     step_idx=step_idx,
    582     step_params=routed_params[name],
    583     all_params=raw_params,
    584 )
--> 586 X, fitted_transformer = fit_transform_one_cached(
    587     cloned_transformer,
    588     X,
    589     y,
    590     weight=None,
    591     message_clsname="Pipeline",
    592     message=self._log_message(step_idx),
    593     params=step_params,
    594 )
    595 # Replace the transformer of the step with the fitted
    596 # transformer. This is necessary when loading the transformer
    597 # from the cache.
    598 self.steps[step_idx] = (name, fitted_transformer)
File .../python3.10/site-packages/joblib/memory.py:349, in NotMemorizedFunc.__call__(self, *args, **kwargs)
    348 def __call__(self, *args, **kwargs):
--> 349     return self.func(*args, **kwargs)
File .../python3.10/site-packages/sklearn/pipeline.py:1540, in _fit_transform_one(transformer, X, y, weight, message_clsname, message, params)
   1538 with _print_elapsed_time(message_clsname, message):
   1539     if hasattr(transformer, "fit_transform"):
-> 1540         res = transformer.fit_transform(X, y, **params.get("fit_transform", {}))
   1541     else:
   1542         res = transformer.fit(X, y, **params.get("fit", {})).transform(
   1543             X, **params.get("transform", {})
   1544         )
File .../python3.10/site-packages/sklearn/utils/_set_output.py:319, in _wrap_method_output.<locals>.wrapped(self, X, *args, **kwargs)
    317 @wraps(f)
    318 def wrapped(self, X, *args, **kwargs):
--> 319     data_to_wrap = f(self, X, *args, **kwargs)
    320     if isinstance(data_to_wrap, tuple):
    321         # only wrap the first output for cross decomposition
    322         return_tuple = (
    323             _wrap_data_with_container(method, data_to_wrap[0], X, self),
    324             *data_to_wrap[1:],
    325         )
File .../python3.10/site-packages/sklearn/base.py:921, in TransformerMixin.fit_transform(self, X, y, **fit_params)
    918     return self.fit(X, **fit_params).transform(X)
    919 else:
    920     # fit method of arity 2 (supervised transformation)
--> 921     return self.fit(X, y, **fit_params).transform(X)
File.../python3.10/site-packages/sklearn/utils/_set_output.py:319, in _wrap_method_output.<locals>.wrapped(self, X, *args, **kwargs)
    317 @wraps(f)
    318 def wrapped(self, X, *args, **kwargs):
--> 319     data_to_wrap = f(self, X, *args, **kwargs)
    320     if isinstance(data_to_wrap, tuple):
    321         # only wrap the first output for cross decomposition
    322         return_tuple = (
    323             _wrap_data_with_container(method, data_to_wrap[0], X, self),
    324             *data_to_wrap[1:],
    325         )
File .../python3.10/site-packages/sklearn/feature_selection/_base.py:112, in SelectorMixin.transform(self, X)
    103 preserve_X = output_config_dense != "default" and _is_pandas_df(X)
    105 # note: we use get_tags instead of __sklearn_tags__ because this is a
    106 # public Mixin.
    107 X = validate_data(
    108     self,
    109     X,
    110     dtype=None,
    111     accept_sparse="csr",
--> 112     ensure_all_finite=not get_tags(self).input_tags.allow_nan,
    113     skip_check_array=preserve_X,
    114     reset=False,
    115 )
    116 return self._transform(X)
File .../python3.10/site-packages/sklearn/utils/_tags.py:396, in get_tags(estimator)
    393 tag_provider = _find_tags_provider(estimator)
    395 if tag_provider == "__sklearn_tags__":
--> 396     tags = estimator.__sklearn_tags__()
    397 else:
    398     # TODO(1.7): Remove this branch of the code
    399     # Let's go through the MRO and patch each class implementing _more_tags
    400     sklearn_tags_provider = {}
File .../python3.10/site-packages/sklearn/feature_selection/_from_model.py:504, in SelectFromModel.__sklearn_tags__(self)
    502 def __sklearn_tags__(self):
    503     tags = super().__sklearn_tags__()
--> 504     tags.input_tags.allow_nan = get_tags(self.estimator).input_tags.allow_nan
    505     return tags
File .../python3.10/site-packages/sklearn/utils/_tags.py:405, in get_tags(estimator)
    403 for klass in reversed(type(estimator).mro()):
    404     if "__sklearn_tags__" in vars(klass):
--> 405         sklearn_tags_provider[klass] = klass.__sklearn_tags__(estimator)  # type: ignore[attr-defined]
    406         class_order.append(klass)
    407     elif "_more_tags" in vars(klass):
File .../python3.10/site-packages/sklearn/base.py:540, in ClassifierMixin.__sklearn_tags__(self)
    539 def __sklearn_tags__(self):
--> 540     tags = super().__sklearn_tags__()
    541     tags.estimator_type = "classifier"
    542     tags.classifier_tags = ClassifierTags()
```

### Versions

```shell
System:
    python: 3.10.12 (main, Sep 11 2024, 15:47:36)

Python dependencies:
      sklearn: 1.6.0
          pip: 22.3.1
   setuptools: 65.6.3
        numpy: 1.23.5
        scipy: 1.10.0
       Cython: 0.29.32
       pandas: 1.5.3
   matplotlib: 3.7.0
       joblib: 1.2.0
threadpoolctl: 3.5.0

xgboost v2.1.3
```

COMMENT:
Great, thanks @betatim and @adrinjalali! I'll pin `scikit-learn<1.6` until xgboost's fix is released then.

Labels: Bug