class_docstr: Mixin class to endow scikit-learn classes with Step capabilities.
  
  Steps are defined by combining any class we would like to make a step from
  with this mixin class. This mixin, among other things, endows the class of
  interest with a ``__call__`` method, making the class callable on the outputs
  (``DataPlaceholder`` objects) of previous steps and optional targets (also
  ``DataPlaceholder`` objects). You can make a step from any class you like,
  so long that class implements the scikit-learn API.
  
  Instructions:
      1. Define a class that inherits from both this mixin and the class you
         wish to make a step of (in that order!).
      2. In the class ``__init__``, call ``super().__init__(...)`` and pass the
         appropriate step parameters.
  
  The base class may implement a predict/transform method (the compute function)
  that take multiple inputs and returns multiple outputs, and a fit method that
  takes multiple inputs and targets. In this case, the input/target arguments are
  expected to be a list of (typically) array-like objects, and the compute function
  is expected to return a list of array-like objects.
  
  Parameters
  ----------
  name
      Name of the step (optional). If no name is passed, a name will be
      automatically generated.
  
  n_outputs
      The number of outputs of the step's function (predict, transform, or
      any other callable passed in the ``compute_func`` argument).
  
  Examples
  --------
  ::
  
      import sklearn.linear_model
      # The order of inheritance is important!
      class LogisticRegression(Step, sklearn.linear_model.LogisticRegression):
          def __init__(self, *args, name=None, **kwargs):
              super().__init__(*args, name=name, **kwargs)
  
      logreg = LogisticRegression(C=2.0)
class_docstr_tok: ["Mixin","class","to","endow","scikit-learn","classes","with","Step","capabilities",".","Steps","are","defined","by","combining","any","class","we","would","like","to","make","a","step","from","with","this","mixin","class",".","This","mixin",",","among","other","things",",","endows","the","class","of","interest","with","a","``","__call__","``","method",",","making","the","class","callable","on","the","outputs","(","``","DataPlaceholder","``","objects",")","of","previous","steps","and","optional","targets","(","also","``","DataPlaceholder","``","objects",")",".","You","can","make","a","step","from","any","class","you","like",",","so","long","that","class","implements","the","scikit-learn","API",".","Instructions",":","1",".","Define","a","class","that","inherits","from","both","this","mixin","and","the","class","you","wish","to","make","a","step","of","(","in","that","order","!",")",".","2",".","In","the","class","``","__init__","``",",","call","``","super","(",")",".__init__","(","...",")","``","and","pass","the","appropriate","step","parameters",".","The","base","class","may","implement","a","predict\/transform","method","(","the","compute","function",")","that","take","multiple","inputs","and","returns","multiple","outputs",",","and","a","fit","method","that","takes","multiple","inputs","and","targets",".","In","this","case",",","the","input\/target","arguments","are","expected","to","be","a","list","of","(","typically",")","array-like","objects",",","and","the","compute","function","is","expected","to","return","a","list","of","array-like","objects",".","Parameters","--","--","--","--","--","name","Name","of","the","step","(","optional",")",".","If","no","name","is","passed",",","a","name","will","be","automatically","generated",".","n_outputs","The","number","of","outputs","of","the","step","'s","function","(","predict",",","transform",",","or","any","other","callable","passed","in","the","``","compute_func","``","argument",")",".","Examples","--","--","--","--",":",":","import","sklearn.linear_model","#","The","order","of","inheritance","is","important","!","class","LogisticRegression","(","Step",",","sklearn.linear_model.LogisticRegression",")",":","def","__init__","(","self",",","*","args",",","name=None",",","*","*","kwargs",")",":","super","(",")",".__init__","(","*","args",",","name=name",",","*","*","kwargs",")","logreg","=","LogisticRegression","(","C=2.0",")"]
docstr: Call the step on input(s) (from previous steps) and generates the
  output(s) to be used in further steps.
  
  You can call the same step on different inputs and targets to reuse the step
  (similar to the concept of shared layers and nodes in Keras), and specify a
  different ``compute_func``/``trainable`` configuration on each call. This is
  achieved via "ports": each call creates a new port and associates the given
  configuration to it. You may access the configuration at each port using the
  ``get_*_at(port)`` methods.
  
  Parameters
  ----------
  inputs
      Input(s) to the step.
  
  targets
      Target(s) to the step.
  
  compute_func
      Specifies which function must be used when computing the step during
      the model graph execution. If ``"auto"`` (default), it will use the ``predict``
      or the ``transform`` method (in that order). If a name string is passed,
      it will use the method that matches the given name. If a callable is
      passed, it will use that callable when computing the step.
  
      The number of inputs and outputs of the function must match those of the
      step (this is not checked, but will raise an error during graph
      execution if there is a mismatch).
  
      scikit-learn classes typically implement a ``predict`` method (Estimators)
      or a ``transform`` method (Transformers), but with this argument you can,
      for example, specify ``predict_proba`` as the compute function.
  
  fit_compute_func
      Specifies which function must be used when fitting AND computing the step
      during the model graph execution.
  
      If ``"auto"`` (default), it will use the ``fit_predict`` or the ``fit_transform``
      method (in that order) if they are implemented, otherwise it will be
      disabled. If a name string is passed, it will use the method that matches
      the given name. If a callable is passed, it will use that callable when
      fitting the step. If ``None`` is passed it will be ignored during graph
      execution.
  
      The number of inputs, outputs and targets, of the function must match those
      of the step (this is not checked, but will raise an error during graph
      execution if there is a mismatch).
  
      By default, when a model is fit, the graph engine will for each step
      1) execute ``fit`` to fit the step, and then 2) execute ``compute_func`` to
      compute the outputs required by successor steps. If a step specifies a
      ``fit_compute_func``, the graph execution will use that instead to fit and
      compute the outputs in a single call. This can be useful for
  
      1. leveraging implementations of ``fit_transform`` that are more efficient
         than calling ``fit`` and ``transform`` separately,
      2. using transductive estimators,
      3. implementing training protocols such as that of stacked classifiers,
         where the classifier in the first stage might compute out-of-fold
         predictions.
  
  trainable
      Whether the step is trainable (True) or not (False). This flag is only
      meaningful only for steps with a fit method. Setting ``trainable=False``
      allows to skip the step when fitting a Model. This is useful if you
      want to freeze some pre-trained steps.
  
  Returns
  -------
  DataPlaceholder
      Output(s) of the step.
code: def __call__(
      self,
      inputs: Union[DataPlaceholder, List[DataPlaceholder]],
      targets: Optional[
          Union[DataPlaceholder, List[DataPlaceholder]]
      ] = None,
      *,
      compute_func: Union[str, Callable[..., Any]] = "auto",
      fit_compute_func: Optional[
          Union[str, Callable[..., Any]]
      ] = "auto",
      trainable: bool = True,
  ) -> Union[DataPlaceholder, List[DataPlaceholder]]:
      """Call the step on input(s) (from previous steps) and generates the
      output(s) to be used in further steps.
  
      You can call the same step on different inputs and targets to reuse the step
      (similar to the concept of shared layers and nodes in Keras), and specify a
      different ``compute_func``/``trainable`` configuration on each call. This is
      achieved via "ports": each call creates a new port and associates the given
      configuration to it. You may access the configuration at each port using the
      ``get_*_at(port)`` methods.
  
      Parameters
      ----------
      inputs
          Input(s) to the step.
  
      targets
          Target(s) to the step.
  
      compute_func
          Specifies which function must be used when computing the step during
          the model graph execution. If ``"auto"`` (default), it will use the ``predict``
          or the ``transform`` method (in that order). If a name string is passed,
          it will use the method that matches the given name. If a callable is
          passed, it will use that callable when computing the step.
  
          The number of inputs and outputs of the function must match those of the
          step (this is not checked, but will raise an error during graph
          execution if there is a mismatch).
  
          scikit-learn classes typically implement a ``predict`` method (Estimators)
          or a ``transform`` method (Transformers), but with this argument you can,
          for example, specify ``predict_proba`` as the compute function.
  
      fit_compute_func
          Specifies which function must be used when fitting AND computing the step
          during the model graph execution.
  
          If ``"auto"`` (default), it will use the ``fit_predict`` or the ``fit_transform``
          method (in that order) if they are implemented, otherwise it will be
          disabled. If a name string is passed, it will use the method that matches
          the given name. If a callable is passed, it will use that callable when
          fitting the step. If ``None`` is passed it will be ignored during graph
          execution.
  
          The number of inputs, outputs and targets, of the function must match those
          of the step (this is not checked, but will raise an error during graph
          execution if there is a mismatch).
  
          By default, when a model is fit, the graph engine will for each step
          1) execute ``fit`` to fit the step, and then 2) execute ``compute_func`` to
          compute the outputs required by successor steps. If a step specifies a
          ``fit_compute_func``, the graph execution will use that instead to fit and
          compute the outputs in a single call. This can be useful for
  
          1. leveraging implementations of ``fit_transform`` that are more efficient
             than calling ``fit`` and ``transform`` separately,
          2. using transductive estimators,
          3. implementing training protocols such as that of stacked classifiers,
             where the classifier in the first stage might compute out-of-fold
             predictions.
  
      trainable
          Whether the step is trainable (True) or not (False). This flag is only
          meaningful only for steps with a fit method. Setting ``trainable=False``
          allows to skip the step when fitting a Model. This is useful if you
          want to freeze some pre-trained steps.
  
      Returns
      -------
      DataPlaceholder
          Output(s) of the step.
      """
  
      inputs = listify(inputs)
      if not is_data_placeholder_list(inputs):
          raise ValueError("inputs must be of type DataPlaceholder.")
  
      if targets is not None:
          if not hasattr(self, "fit"):
              raise RuntimeError(
                  "Cannot pass targets to steps that do not have a fit method."
              )
  
          # TODO: Consider inspecting the fit signature to determine whether the step
          # needs a target (i.e. fit(self, X, y)) or not (i.e. fit(self, X, y=None)).
          # The presence of a default of None for the target might not be reliable
          # though, as there could be estimators (perhaps semi-supervised) that can take
          # both target data and None. Also, sklearn has meta-estimators (e.g. Pipeline)
          # and meta-transformers (e.g. SelectFromModel) that accept both target data
          # and None.
          #
          # Adding this inspection, however, could simplify the API by rejecting early
          # unnecessary targets (e.g. passing targets to PCA) or warning missing targets
          # (e.g. not passing targets to LogisticRegression with trainable=True). This
          # also avoids unintuitive logic to allow superfluous targets during step call,
          # model instantiation and model fit.
          #
          # | requires target |   trainable   | passed target |   result   |
          # ----------------------------------------------------------------
          # |       yes       |      True     |      yes      |     ok     |
          # |       yes       |      True     |      no       |    warn    |
          # |       yes       |      False    |      yes      |    warn    |
          # |       yes       |      False    |      no       |     ok     |
          # |       no        |        -      |      yes      |    error   |
          # |       no        |        -      |      no       |     ok     |
  
          if not trainable:
              warnings.warn(
                  UserWarning(
                      "You are passing targets to a non-trainable step."
                  )
              )
  
          targets = listify(targets)
          if not is_data_placeholder_list(targets):
              raise ValueError(
                  "If specified, targets must be of type DataPlaceholder."
              )
  
      else:
          targets = []
  
      outputs = self._build_outputs()
  
      self._nodes.append(
          Node(
              self,
              inputs,
              outputs,
              targets,
              getattr(self, "fit", None),
              self._check_compute_func(compute_func),
              self._check_fit_compute_func(fit_compute_func),
              trainable,
          )
      )
  
      if self._n_outputs == 1:
          return outputs[0]
      else:
          # Return a shallow copy to avoid modifying self._outputs when
          # using the idiom of passing a variable holding an output to
          # another step and re-writing the variable with the new output:
          #     zs = SomeMultiOutputStep()(...)
          #     zs[i] = SomeStep()(zs[i])
          return list(outputs)
  
imports: import inspect
  import warnings
  from typing import Any, Callable, Dict, List, Optional, Union, TYPE_CHECKING
  from baikal._core.data_placeholder import DataPlaceholder, is_data_placeholder_list
  from baikal._core.utils import listify, make_name, make_repr
label_desc: Use image node_id 4 for calling the Step obj's underlying member method code with example usage: obj.__call__(inputs, targets) and returns: outputs, list