fix: don't convert sparse matrix formats

Situation:

TensorFlow doesn't handle all sparse matrix formats. Docs say it only supports sorted row major, but it seems to convert some others.
Scikit-Learn expects all formats to be handled or an error to be raised

Previously we were converting all matrices to lil, but as pointed out in https://github.com/adriangb/scikeras/pull/240#issuecomment-1193043558 that was a pretty terrible idea.
This tries to be a little bit smarter to minimize conversion / memory use while still complying with Scikit-Learn and TFs APIs.

@mattalhonte-srm can you give this a try please?

Codecov Report

Merging #282 (b8a4739) into master (0144439) will decrease coverage by 0.13%.
The diff coverage is 100.00%.

@@            Coverage Diff             @@
##           master     #282      +/-   ##
==========================================
- Coverage   98.28%   98.15%   -0.14%
==========================================
  Files           7        7
  Lines         759      758       -1
==========================================
- Hits          746      744       -2
- Misses         13       14       +1

Impacted Files
Coverage Δ

scikeras/wrappers.py
97.53% <100.00%> (-0.01%)
:arrow_down:

scikeras/_saving_utils.py
96.73% <0.00%> (-1.09%)
:arrow_down:

Help us with your feedback. Take ten seconds to tell us how you rate us.

Heya!  Tried this branch, blows up my container.  Would it be possible to just have an accept_sparse flag that lets the sparse matrix pass without anything being done do it?  Maybe with a warning that it can cause errors?

That's surprising. What happens if you comment out https://github.com/adriangb/scikeras/blob/b8a4739d775bc635c86ca8fa1948127000373afb/scikeras/wrappers.py#L672

Just tried!  No luck, still blew up the container.  Naively passing the CSR matrix and letting it pass unmolested is the only thing that's worked - some sort of "manual override" flag that just lets you do that would be ideal I think.

I'm a bit at a loss. After removing that line, there should be no processing being done. Is your branch that you said is working for you public? I would love to take a look.

It's not, but here's the monkeypatch that made it work:
"""Wrapper for using the Scikit-Learn API with Keras models.
"""
import inspect
import warnings

from collections import defaultdict
from typing import Any, Callable, Dict, Iterable, List, Mapping, Set, Tuple, Type, Union

import numpy as np
import tensorflow as tf

from sklearn.base import BaseEstimator, ClassifierMixin, RegressorMixin
from sklearn.exceptions import NotFittedError
from sklearn.metrics import accuracy_score as sklearn_accuracy_score
from sklearn.metrics import r2_score as sklearn_r2_score
from sklearn.preprocessing import FunctionTransformer
from sklearn.utils.class_weight import compute_sample_weight
from sklearn.utils.multiclass import type_of_target
from sklearn.utils.validation import _check_sample_weight, check_array, check_X_y
from tensorflow.keras import losses as losses_module
from tensorflow.keras.models import Model
from tensorflow.keras.utils import register_keras_serializable

from scikeras._utils import (
    accepts_kwargs,
    get_loss_class_function_or_string,
    get_metric_class,
    get_optimizer_class,
    has_param,
    route_params,
    try_to_convert_strings_to_classes,
    unflatten_params,
)
from scikeras.utils import loss_name, metric_name
from scikeras.utils.random_state import tensorflow_random_state
from scikeras.utils.transformers import ClassifierLabelEncoder, RegressorTargetEncoder

class BaseWrapper(BaseEstimator):
    """Implementation of the scikit-learn classifier API for Keras.

    Below are a list of SciKeras specific parameters. For details on other parameters,
    please see the see the `tf.keras.Model documentation <https://www.tensorflow.org/api_docs/python/tf/keras/Model>`_.

    Parameters
    ----------
    model : Union[None, Callable[..., tf.keras.Model], tf.keras.Model], default None
        Used to build the Keras Model. When called,
        must return a compiled instance of a Keras Model
        to be used by `fit`, `predict`, etc.
        If None, you must implement ``_keras_build_fn``.
    optimizer : Union[str, tf.keras.optimizers.Optimizer, Type[tf.keras.optimizers.Optimizer]], default "rmsprop"
        This can be a string for Keras' built in optimizers,
        an instance of tf.keras.optimizers.Optimizer
        or a class inheriting from tf.keras.optimizers.Optimizer.
        Only strings and classes support parameter routing.
    loss : Union[Union[str, tf.keras.losses.Loss, Type[tf.keras.losses.Loss], Callable], None], default None
        The loss function to use for training.
        This can be a string for Keras' built in losses,
        an instance of tf.keras.losses.Loss
        or a class inheriting from tf.keras.losses.Loss .
        Only strings and classes support parameter routing.
    random_state : Union[int, np.random.RandomState, None], default None
        Set the Tensorflow random number generators to a
        reproducible deterministic state using this seed.
        Pass an int for reproducible results across multiple
        function calls.
    warm_start : bool, default False
        If True, subsequent calls to fit will _not_ reset
        the model parameters but *will* reset the epoch to zero.
        If False, subsequent fit calls will reset the entire model.
        This has no impact on partial_fit, which always trains
        for a single epoch starting from the current epoch.
    batch_size : Union[int, None], default None
        Number of samples per gradient update.
        This will be applied to both `fit` and `predict`. To specify different numbers,
        pass `fit__batch_size=32` and `predict__batch_size=1000` (for example).
        To auto-adjust the batch size to use all samples, pass `batch_size=-1`.

    Attributes
    ----------
    model_ : tf.keras.Model
        The instantiated and compiled Keras Model. For pre-built models, this
        will just be a reference to the passed Model instance.
    history_ : Dict[str, List[Any]]
        Dictionary of the format ``{metric_str_name: [epoch_0_data, epoch_1_data, ..., epoch_n_data]}``.
    initialized_ : bool
        True if this estimator has been initialized (i.e. predict can be called upon it).
        Note that this does not guarantee that the model is "fitted": if ``BaseWrapper.initialize``
        was called instead of fit the model wil likely have random weights.
    target_encoder_ : sklearn-transformer
        Transformer used to pre/post process the target y.
    feature_encoder_ : sklearn-transformer
        Transformer used to pre/post process the features/input X.
    n_outputs_expected_ : int
        The number of outputs the Keras Model is expected to have, as determined by ``target_transformer_``.
    target_type_ : str
        One of:

        * 'continuous': y is an array-like of floats that are not all
          integers, and is 1d or a column vector.
        * 'continuous-multioutput': y is a 2d array of floats that are
          not all integers, and both dimensions are of size > 1.
        * 'binary': y contains <= 2 discrete values and is 1d or a column
          vector.
        * 'multiclass': y contains more than two discrete values, is not a
          sequence of sequences, and is 1d or a column vector.
        * 'multiclass-multioutput': y is a 2d array that contains more
          than two discrete values, is not a sequence of sequences, and both
          dimensions are of size > 1.
        * 'multilabel-indicator': y is a label indicator matrix, an array
          of two dimensions with at least two columns, and at most 2 unique
          values.
        * 'unknown': y is array-like but none of the above, such as a 3d
          array, sequence of sequences, or an array of non-sequence objects.
    y_shape_ : Tuple[int]
        Shape of the target y that the estimator was fitted on.
    y_dtype_ : np.dtype
        Dtype of the target y that the estimator was fitted on.
    X_shape_ : Tuple[int]
        Shape of the input X that the estimator was fitted on.
    X_dtype_ : np.dtype
        Dtype of the input X that the estimator was fitted on.
    n_features_in_ : int
        The number of features seen during `fit`.
    """

    _tags = {
        "poor_score": True,
        "multioutput": True,
    }

    _fit_kwargs = {
        # parameters destined to keras.Model.fit
        "batch_size",
        "epochs",
        "verbose",
        "validation_split",
        "shuffle",
        "class_weight",
        "sample_weight",
        "initial_epoch",
        "validation_steps",
        "validation_batch_size",
        "validation_freq",
    }

    _predict_kwargs = {
        # parameters destined to keras.Model.predict
        "batch_size",
        "verbose",
        "steps",
    }

    _compile_kwargs = {
        # parameters destined to keras.Model.compile
        "optimizer",
        "loss",
        "metrics",
        "loss_weights",
        "weighted_metrics",
        "run_eagerly",
    }

    _wrapper_params = {
        # parameters consumed by the wrappers themselves
        "warm_start",
        "random_state",
    }

    _routing_prefixes = {
        "model",
        "fit",
        "compile",
        "predict",
        "optimizer",
        "loss",
        "metrics",
    }

    def __init__(
        self,
        model: Union[None, Callable[..., tf.keras.Model], tf.keras.Model] = None,
        *,
        build_fn: Union[
            None, Callable[..., tf.keras.Model], tf.keras.Model
        ] = None,  # for backwards compatibility
        warm_start: bool = False,
        random_state: Union[int, np.random.RandomState, None] = None,
        optimizer: Union[
            str, tf.keras.optimizers.Optimizer, Type[tf.keras.optimizers.Optimizer]
        ] = "rmsprop",
        loss: Union[
            Union[str, tf.keras.losses.Loss, Type[tf.keras.losses.Loss], Callable], None
        ] = None,
        metrics: Union[
            List[
                Union[
                    str,
                    tf.keras.metrics.Metric,
                    Type[tf.keras.metrics.Metric],
                    Callable,
                ]
            ],
            None,
        ] = None,
        batch_size: Union[int, None] = None,
        validation_batch_size: Union[int, None] = None,
        verbose: int = 1,
        callbacks: Union[
            List[Union[tf.keras.callbacks.Callback, Type[tf.keras.callbacks.Callback]]],
            None,
        ] = None,
        validation_split: float = 0.0,
        shuffle: bool = True,
        run_eagerly: bool = False,
        epochs: int = 1,
        **kwargs,
    ):

        # Parse hardcoded params
        self.model = model
        self.build_fn = build_fn
        self.warm_start = warm_start
        self.random_state = random_state
        self.optimizer = optimizer
        self.loss = loss
        self.metrics = metrics
        self.batch_size = batch_size
        self.validation_batch_size = validation_batch_size
        self.verbose = verbose
        self.callbacks = callbacks
        self.validation_split = validation_split
        self.shuffle = shuffle
        self.run_eagerly = run_eagerly
        self.epochs = epochs

        # Unpack kwargs
        vars(self).update(**kwargs)

        # Save names of kwargs into set
        if kwargs:
            self._user_params = set(kwargs)

    @property
    def __name__(self):
        return "KerasClassifier"

    @property
    def current_epoch(self) -> int:
        """Returns the current training epoch.

        Returns
        -------
        int
            Current training epoch.
        """
        if not hasattr(self, "history_"):
            return 0
        return len(self.history_["loss"])

    @staticmethod
    def _validate_sample_weight(
        X: np.ndarray,
        y: np.ndarray,
        sample_weight: Union[np.ndarray, Iterable],
    ) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
        """Validate that the passed sample_weight and ensure it is a Numpy array."""
        sample_weight = _check_sample_weight(
            sample_weight, X, dtype=np.dtype(tf.keras.backend.floatx())
        )
        if np.all(sample_weight == 0):
            raise ValueError(
                "No training samples had any weight; only zeros were passed in sample_weight."
                " That means there's nothing to train on by definition, so training can not be completed."
            )
        # drop any zero sample weights
        # this helps mirror the behavior of sklearn estimators
        # which tend to have higher precisions
        not_dropped_samples = sample_weight != 0
        return (
            X[not_dropped_samples, ...],
            y[not_dropped_samples, ...],
            sample_weight[not_dropped_samples, ...],
        )

    def _check_model_param(self):
        """Checks ``model`` and returns model building
        function to use.

        Raises
        ------
            ValueError: if ``self.model`` is not valid.
        """
        model = self.model
        build_fn = self.build_fn
        if model is None and build_fn is not None:
            model = build_fn
            warnings.warn(
                "``build_fn`` will be renamed to ``model`` in a future release,"
                " at which point use of ``build_fn`` will raise an Error instead."
            )
        if model is None:
            # no model, use this class' _keras_build_fn
            if not hasattr(self, "_keras_build_fn"):
                raise ValueError(
                    "If not using the ``build_fn`` param, "
                    "you must implement ``_keras_build_fn``"
                )
            final_build_fn = self._keras_build_fn
        elif isinstance(model, Model):
            # pre-built Keras Model
            def final_build_fn():
                return model

        elif inspect.isfunction(model):
            if hasattr(self, "_keras_build_fn"):
                raise ValueError(
                    "This class cannot implement ``_keras_build_fn`` if"
                    " using the `model` parameter"
                )
            # a callable method/function
            final_build_fn = model
        else:
            raise TypeError(
                "``model`` must be a callable, a Keras Model instance or None"
            )

        return final_build_fn

    def _get_compile_kwargs(self):
        """Convert all __init__ params destined to
        `compile` into valid kwargs for `Model.compile` by parsing
        routed parameters and compiling optimizers, losses and metrics
        as needed.

        Returns
        -------
        dict
            Dictionary of kwargs for `Model.compile`.
        """
        init_params = self.get_params()
        compile_kwargs = route_params(
            init_params,
            destination="compile",
            pass_filter=self._compile_kwargs,
        )
        compile_kwargs["optimizer"] = try_to_convert_strings_to_classes(
            compile_kwargs["optimizer"], get_optimizer_class
        )
        compile_kwargs["optimizer"] = unflatten_params(
            items=compile_kwargs["optimizer"],
            params=route_params(
                init_params,
                destination="optimizer",
                pass_filter=set(),
                strict=True,
            ),
        )
        compile_kwargs["loss"] = try_to_convert_strings_to_classes(
            compile_kwargs["loss"], get_loss_class_function_or_string
        )
        compile_kwargs["loss"] = unflatten_params(
            items=compile_kwargs["loss"],
            params=route_params(
                init_params,
                destination="loss",
                pass_filter=set(),
                strict=False,
            ),
        )
        compile_kwargs["metrics"] = try_to_convert_strings_to_classes(
            compile_kwargs["metrics"], get_metric_class
        )
        compile_kwargs["metrics"] = unflatten_params(
            items=compile_kwargs["metrics"],
            params=route_params(
                init_params,
                destination="metrics",
                pass_filter=set(),
                strict=False,
            ),
        )
        return compile_kwargs

    def _build_keras_model(self):
        """Build the Keras model.

        This method will process all arguments and call the model building
        function with appropriate arguments.

        Returns
        -------
        tensorflow.keras.Model
            Instantiated and compiled keras Model.
        """
        # dynamically build model, i.e. final_build_fn builds a Keras model

        # determine what type of build_fn to use
        final_build_fn = self._check_model_param()

        # collect parameters
        params = self.get_params()
        build_params = route_params(
            params,
            destination="model",
            pass_filter=getattr(self, "_user_params", set()),
            strict=True,
        )
        compile_kwargs = None
        if has_param(final_build_fn, "meta") or accepts_kwargs(final_build_fn):
            # build_fn accepts `meta`, add it
            build_params["meta"] = self._get_metadata()
        if has_param(final_build_fn, "compile_kwargs") or accepts_kwargs(
            final_build_fn
        ):
            # build_fn accepts `compile_kwargs`, add it
            compile_kwargs = self._get_compile_kwargs()
            build_params["compile_kwargs"] = compile_kwargs
        if has_param(final_build_fn, "params") or accepts_kwargs(final_build_fn):
            # build_fn accepts `params`, i.e. all of get_params()
            build_params["params"] = self.get_params()

        # build model
        if self._random_state is not None:
            with tensorflow_random_state(self._random_state):
                model = final_build_fn(**build_params)
        else:
            model = final_build_fn(**build_params)

        return model

    def _ensure_compiled_model(self) -> None:
        # compile model if user gave us an un-compiled model
        if not (hasattr(self.model_, "loss") and hasattr(self.model_, "optimizer")):
            kw = self._get_compile_kwargs()
            self.model_.compile(**kw)

    def _fit_keras_model(
        self,
        X: Union[np.ndarray, List[np.ndarray], Dict[str, np.ndarray]],
        y: Union[np.ndarray, List[np.ndarray], Dict[str, np.ndarray]],
        sample_weight: Union[np.ndarray, None],
        warm_start: bool,
        epochs: int,
        initial_epoch: int,
        **kwargs,
    ) -> None:
        """Fits the Keras model.

        Parameters
        ----------
        X : Union[np.ndarray, List[np.ndarray], Dict[str, np.ndarray]]
            Training samples, as accepted by tf.keras.Model
        y : Union[np.ndarray, List[np.ndarray], Dict[str, np.ndarray]]
            Target data, as accepted by tf.keras.Model
        sample_weight : Union[np.ndarray, None]
            Sample weights. Ignored by Keras if None.
        warm_start : bool
            If True, don't don't overwrite
            the history_ attribute and append to it instead.
        epochs : int
            Number of epochs for which the model will be trained.
        initial_epoch : int
            Epoch at which to begin training.
        **kwargs : Dict[str, Any]
            Extra arguments to route to ``Model.fit``.

        Returns
        -------
        BaseWrapper
            A reference to the instance that can be chain called
            (ex: instance.fit(X,y).transform(X) )
        """

        # Make sure model has a loss function
        loss = self.model_.loss
        no_loss = False
        if isinstance(loss, list) and not any(
            callable(loss_) or isinstance(loss_, str) for loss_ in loss
        ):
            no_loss = True
        if isinstance(loss, dict) and not any(
            callable(loss_) or isinstance(loss_, str) for loss_ in loss.values()
        ):
            no_loss = True
        if no_loss:
            raise ValueError(
                "No valid loss function found."
                " You must provide a loss function to train."
                "\n\nTo resolve this issue, do one of the following:"
                "\n 1. Provide a loss function via the loss parameter."
                "\n 2. Compile your model with a loss function inside the"
                " model-building method."
                "\n\nSee https://www.adriangb.com/scikeras/stable/advanced.html#compilation-of-model"
                " for more information on compiling SciKeras models."
                "\n\nSee https://www.tensorflow.org/api_docs/python/tf/keras/losses"
                " for more information on Keras losses."
            )

        # collect parameters
        params = self.get_params()
        fit_args = route_params(params, destination="fit", pass_filter=self._fit_kwargs)
        fit_args["sample_weight"] = sample_weight
        fit_args["epochs"] = initial_epoch + epochs
        fit_args["initial_epoch"] = initial_epoch
        fit_args.update(kwargs)
        for bs_kwarg in ("batch_size", "validation_batch_size"):
            if bs_kwarg in fit_args:
                if fit_args[bs_kwarg] == -1:
                    try:
                        fit_args[bs_kwarg] = X.shape[0]
                    except AttributeError:
                        raise ValueError(
                            f"`{bs_kwarg}=-1` requires that `X` implement `shape`"
                        )
        fit_args = {k: v for k, v in fit_args.items() if not k.startswith("callbacks")}
        fit_args["callbacks"] = self._fit_callbacks

        if self._random_state is not None:
            with tensorflow_random_state(self._random_state):
                hist = self.model_.fit(x=X, y=y, **fit_args)
        else:
            hist = self.model_.fit(x=X, y=y, **fit_args)

        if not warm_start or not hasattr(self, "history_") or initial_epoch == 0:
            self.history_ = defaultdict(list)

        for key, val in hist.history.items():
            try:
                key = metric_name(key)
            except ValueError as e:
                # Keras puts keys like "val_accuracy" and "loss" and
                # "val_loss" in hist.history
                if "Unknown metric function" not in str(e):
                    raise e
            self.history_[key] += val

    def _check_model_compatibility(self, y: np.ndarray) -> None:
        """Checks that the model output number and y shape match.

        This is in place to avoid cryptic TF errors.
        """
        # check if this is a multi-output model
        if getattr(self, "n_outputs_expected_", None):
            # n_outputs_expected_ is generated by data transformers
            # we recognize the attribute but do not force it to be
            # generated
            if self.n_outputs_expected_ != len(self.model_.outputs):
                raise ValueError(
                    "Detected a Keras model input of size"
                    f" {self.n_outputs_expected_ }, but {self.model_} has"
                    f" {len(self.model_.outputs)} outputs"
                )
        # check that if the user gave us a loss function it ended up in
        # the actual model
        init_params = inspect.signature(self.__init__).parameters
        if "loss" in init_params:
            default_val = init_params["loss"].default
            if all(
                isinstance(x, (str, losses_module.Loss, type))
                for x in [self.loss, self.model_.loss]
            ):  # filter out loss list/dicts/etc.
                if default_val is not None:
                    default_val = loss_name(default_val)
                given = loss_name(self.loss)
                got = loss_name(self.model_.loss)
                if given != default_val and got != given:
                    raise ValueError(
                        f"loss={self.loss} but model compiled with {self.model_.loss}."
                        " Data may not match loss function!"
                    )

    def _validate_data(
        self, X=None, y=None, reset: bool = False, y_numeric: bool = False
    ) -> Tuple[np.ndarray, Union[np.ndarray, None]]:
        """Validate input arrays and set or check their meta-parameters.

        Parameters
        ----------
        X : Union[array-like, sparse matrix, dataframe] of shape \
           (n_samples, n_features)
            The input samples. If None, ``check_array`` is called on y and
            ``check_X_y`` is called otherwise.
        y : Union[array-like, sparse matrix, dataframe] of shape \
            (n_samples,), default=None
            The targets. If None, ``check_array`` is called on X and
            ``check_X_y`` is called otherwise.
        reset : bool, default=False
            If True, override all meta attributes.
            If False, verify that they haven't changed.
        y_numeric : bool, default = False
            If True, ensure y is a numeric dtype.
            If False, allow non-numeric y to pass through.

        Returns
        -------
        Tuple[np.ndarray, Union[np.ndarray, None]]
            The validated input.
        """

        def _check_array_dtype(arr, force_numeric):
            if not isinstance(arr, np.ndarray):
                return _check_array_dtype(np.asarray(arr), force_numeric=force_numeric)
            elif (
                arr.dtype.kind not in ("O", "U", "S") or not force_numeric
            ):  # object, unicode or string
                # already numeric
                return None  # check_array won't do any casting with dtype=None
            else:
                # default to TFs backend float type
                # instead of float64 (sklearn's default)
                return tf.keras.backend.floatx()

        if X is not None and y is not None:
            X, y = check_X_y(
                X,
                y,
                allow_nd=True,  # allow X to have more than 2 dimensions
                multi_output=True,  # allow y to be 2D
                dtype=None,
                accept_sparse=True,
            )

        if y is not None:
            y = check_array(
                y,
                ensure_2d=False,
                allow_nd=False,
                dtype=_check_array_dtype(y, force_numeric=y_numeric),
            )
            y_dtype_ = y.dtype
            y_ndim_ = y.ndim
            if reset:
                self.target_type_ = self._type_of_target(y)
                self.y_dtype_ = y_dtype_
                self.y_ndim_ = y_ndim_
            else:
                if not np.can_cast(y_dtype_, self.y_dtype_):
                    raise ValueError(
                        f"Got y with dtype {y_dtype_},"
                        f" but this {self.__name__} expected {self.y_dtype_}"
                        f" and casting from {y_dtype_} to {self.y_dtype_} is not safe!"
                    )
                if self.y_ndim_ != y_ndim_:
                    raise ValueError(
                        f"y has {y_ndim_} dimensions, but this {self.__name__}"
                        f" is expecting {self.y_ndim_} dimensions in y."
                    )
        if X is not None:
            X = check_array(
                X, allow_nd=True, dtype=_check_array_dtype(X, force_numeric=True)
            )
            X_dtype_ = X.dtype
            X_shape_ = X.shape
            n_features_in_ = X.shape[1]
            if reset:
                self.X_dtype_ = X_dtype_
                self.X_shape_ = X_shape_
                self.n_features_in_ = n_features_in_
            else:
                if not np.can_cast(X_dtype_, self.X_dtype_):
                    raise ValueError(
                        f"Got X with dtype {X_dtype_},"
                        f" but this {self.__name__} expected {self.X_dtype_}"
                        f" and casting from {X_dtype_} to {self.X_dtype_} is not safe!"
                    )
                if len(X_shape_) != len(self.X_shape_):
                    raise ValueError(
                        f"X has {len(X_shape_)} dimensions, but this {self.__name__}"
                        f" is expecting {len(self.X_shape_)} dimensions in X."
                    )
                if X_shape_[1:] != self.X_shape_[1:]:
                    raise ValueError(
                        f"X has shape {X_shape_[1:]}, but this {self.__name__}"
                        f" is expecting X of shape {self.X_shape_[1:]}"
                    )
        return X, y

    def _type_of_target(self, y: np.ndarray) -> str:
        return type_of_target(y)

    @property
    def target_encoder(self):
        """Retrieve a transformer for targets / y.

        Metadata will be collected from ``get_metadata`` if
        the transformer implements that method.
        Override this method to implement a custom data transformer
        for the target.

        Returns
        -------
        target_encoder
            Transformer implementing the sklearn transformer
            interface.
        """
        return FunctionTransformer()

    @property
    def feature_encoder(self):
        """Retrieve a transformer for features / X.

        Metadata will be collected from ``get_metadata`` if
        the transformer implements that method.
        Override this method to implement a custom data transformer
        for the features.

        Returns
        -------
        sklearn transformer
            Transformer implementing the sklearn transformer
            interface.
        """
        return FunctionTransformer()

    def fit(self, X, y, sample_weight=None, **kwargs) -> "BaseWrapper":
        """Constructs a new model with ``model`` & fit the model to ``(X, y)``.

        Parameters
        ----------
        X : Union[array-like, sparse matrix, dataframe] of shape (n_samples, n_features)
            Training samples, where n_samples is the number of samples
            and n_features is the number of features.
        y : Union[array-like, sparse matrix, dataframe] of shape (n_samples,) or (n_samples, n_outputs)
            True labels for X.
        sample_weight : array-like of shape (n_samples,), default=None
            Array of weights that are assigned to individual samples.
            If not provided, then each sample is given unit weight.
        **kwargs : Dict[str, Any]
            Extra arguments to route to ``Model.fit``.

        Warnings
        --------
            Passing estimator parameters as keyword arguments (aka as ``**kwargs``) to ``fit`` is not supported by the Scikit-Learn API,
            and will be removed in a future version of SciKeras.
            These parameters can also be specified by prefixing ``fit__`` to a parameter at initialization
            (``BaseWrapper(..., fit__batch_size=32, predict__batch_size=1000)``)
            or by using ``set_params`` (``est.set_params(fit__batch_size=32, predict__batch_size=1000)``).

        Returns
        -------
        BaseWrapper
            A reference to the instance that can be chain called (``est.fit(X,y).transform(X)``).
        """
        # epochs via kwargs > fit__epochs > epochs
        kwargs["epochs"] = kwargs.get(
            "epochs", getattr(self, "fit__epochs", self.epochs)
        )
        kwargs["initial_epoch"] = kwargs.get("initial_epoch", 0)

        self._fit(
            X=X,
            y=y,
            sample_weight=sample_weight,
            warm_start=self.warm_start,
            **kwargs,
        )

        return self

    @property
    def initialized_(self) -> bool:
        """Checks if the estimator is intialized.

        Returns
        -------
        bool
            True if the estimator is initialized (i.e., it can
            be used for inference or is ready to train),
            otherwise False.
        """
        return hasattr(self, "model_")

    def _initialize_callbacks(self) -> None:
        params = self.get_params()

        def initialize(destination: str):
            if params.get(destination) is not None:
                callback_kwargs = route_params(
                    params, destination=destination, pass_filter=set()
                )
                callbacks = unflatten_params(
                    items=params[destination], params=callback_kwargs
                )
                if isinstance(callbacks, Mapping):
                    # Keras does not officially support dicts, convert to a list
                    callbacks = list(callbacks.values())
                elif isinstance(callbacks, tf.keras.callbacks.Callback):
                    # a single instance, not officially supported so wrap in a list
                    callbacks = [callbacks]
                err = False
                if not isinstance(callbacks, List):
                    err = True
                for cb in callbacks:
                    if isinstance(cb, List):
                        for nested_cb in cb:
                            if not isinstance(nested_cb, tf.keras.callbacks.Callback):
                                err = True
                    elif not isinstance(cb, tf.keras.callbacks.Callback):
                        err = True
                if err:
                    raise TypeError(
                        "If specified, ``callbacks`` must be one of:"
                        "\n - A dict of string keys with callbacks or lists of callbacks as values"
                        "\n - A list of callbacks or lists of callbacks"
                        "\n - A single callback"
                        "\nWhere each callback can be a instance of `tf.keras.callbacks.Callback` or a sublass of it to be compiled by SciKeras"
                    )
            else:
                callbacks = []
            return callbacks

        all_callbacks = initialize("callbacks")
        self._fit_callbacks = all_callbacks + initialize("fit__callbacks")
        self._predict_callbacks = all_callbacks + initialize("predict__callbacks")

    def _initialize(
        self, X: np.ndarray, y: Union[np.ndarray, None] = None
    ) -> Tuple[np.ndarray, np.ndarray]:

        # Handle random state
        if isinstance(self.random_state, np.random.RandomState):
            # Keras needs an integer
            # we sample an integer and use that as a seed
            # Given the same RandomState, the seed will always be
            # the same, thus giving reproducible results
            state = self.random_state.get_state()
            r = np.random.RandomState()
            r.set_state(state)
            self._random_state = r.randint(low=1)
        else:
            # int or None
            self._random_state = self.random_state

        X, y = self._validate_data(X, y, reset=True)

        self.target_encoder_ = self.target_encoder.fit(y)
        target_metadata = getattr(self.target_encoder_, "get_metadata", dict)()
        vars(self).update(**target_metadata)
        self.feature_encoder_ = self.feature_encoder.fit(X)
        feature_meta = getattr(self.feature_encoder, "get_metadata", dict)()
        vars(self).update(**feature_meta)

        self.model_ = self._build_keras_model()
        self._initialize_callbacks()

        return X, y

    def initialize(self, X, y=None) -> "BaseWrapper":
        """Initialize the model without any fitting.

        You only need to call this model if you explicitly do not want to do any fitting
        (for example with a pretrained model). You should _not_ call this
        right before calling ``fit``, calling ``fit`` will do this automatically.

        Parameters
        ----------
        X : Union[array-like, sparse matrix, dataframe] of shape (n_samples, n_features)
                Training samples where n_samples is the number of samples
                and `n_features` is the number of features.
        y : Union[array-like, sparse matrix, dataframe] of shape \
            (n_samples,) or (n_samples, n_outputs), default None
            True labels for X.

        Returns
        -------
        BaseWrapper
            A reference to the BaseWrapper instance for chained calling.
        """
        self._initialize(X, y)
        return self  # to allow chained calls like initialize(...).predict(...)

    def _fit(
        self,
        X,
        y,
        sample_weight,
        warm_start: bool,
        epochs: int,
        initial_epoch: int,
        **kwargs,
    ) -> None:
        """Constructs a new model with ``model`` & fit the model to ``(X, y)``.

        Parameters
        ----------
        X : Union[array-like, sparse matrix, dataframe] of shape (n_samples, n_features)
                Training samples where `n_samples` is the number of samples
                and `n_features` is the number of features.
        y :Union[array-like, sparse matrix, dataframe] of shape (n_samples,) or (n_samples, n_outputs)
            True labels for X.
        sample_weight : array-like of shape (n_samples,), default=None
            Array of weights that are assigned to individual samples.
            If not provided, then each sample is given unit weight.
        warm_start : bool
            If True, don't rebuild the model.
        epochs : int
            Number of passes over the entire dataset for which to train the
            model.
        initial_epoch : int
            Epoch at which to begin training.
        **kwargs : Dict[str, Any]
            Extra arguments to route to ``Model.fit``.
        """
        # Data checks
        if not ((self.warm_start or warm_start) and self.initialized_):
            X, y = self._initialize(X, y)
        else:
            X, y = self._validate_data(X, y)
        self._ensure_compiled_model()

        if sample_weight is not None:
            X, y, sample_weight = self._validate_sample_weight(X, y, sample_weight)

        y = self.target_encoder_.transform(y)
        X = self.feature_encoder_.transform(X)

        self._check_model_compatibility(y)

        self._fit_keras_model(
            X,
            y,
            sample_weight=sample_weight,
            warm_start=warm_start,
            epochs=epochs,
            initial_epoch=initial_epoch,
            **kwargs,
        )

    def partial_fit(self, X, y, sample_weight=None, **kwargs) -> "BaseWrapper":
        """Fit the estimator for a single epoch, preserving the current
        training history and model parameters.

        Parameters
        ----------
        X : Union[array-like, sparse matrix, dataframe] of shape (n_samples, n_features)
            Training samples where n_samples is the number of samples
            and n_features is the number of features.
        y : Union[array-like, sparse matrix, dataframe] of shape \
            (n_samples,) or (n_samples, n_outputs)
            True labels for X.
        sample_weight : array-like of shape (n_samples,), default=None
            Array of weights that are assigned to individual samples.
            If not provided, then each sample is given unit weight.
        **kwargs : Dict[str, Any]
            Extra arguments to route to ``Model.fit``.

        Returns
        -------
        BaseWrapper
            A reference to the instance that can be chain called
            (ex: instance.partial_fit(X, y).transform(X) )
        """
        if "epochs" in kwargs:
            raise TypeError(
                "Invalid argument `epochs` to `partial_fit`: `partial_fit` always trains for 1 epoch"
            )
        if "initial_epoch" in kwargs:
            raise TypeError(
                "Invalid argument `initial_epoch` to `partial_fit`: `partial_fit` always trains for from the current epoch"
            )

        self._fit(
            X,
            y,
            sample_weight=sample_weight,
            warm_start=True,
            epochs=1,
            initial_epoch=self.current_epoch,
            **kwargs,
        )
        return self

    def _predict_raw(self, X, **kwargs):
        """Obtain raw predictions from Keras Model.

        For classification, this corresponds to predict_proba.
        For regression, this corresponds to predict.
        """
        # check if fitted
        if not self.initialized_:
            raise NotFittedError(
                "Estimator needs to be fit before `predict` " "can be called"
            )
        # basic input checks
        X, _ = self._validate_data(X=X, y=None)

        # pre process X
        X = self.feature_encoder_.transform(X)

        # filter kwargs and get attributes for predict
        params = self.get_params()
        pred_args = route_params(
            params, destination="predict", pass_filter=self._predict_kwargs, strict=True
        )
        pred_args = {
            k: v for k, v in pred_args.items() if not k.startswith("callbacks")
        }
        pred_args["callbacks"] = self._predict_callbacks
        pred_args.update(kwargs)
        if "batch_size" in pred_args:
            if pred_args["batch_size"] == -1:
                try:
                    pred_args["batch_size"] = X.shape[0]
                except AttributeError:
                    raise ValueError(
                        "`batch_size=-1` requires that `X` implement `shape`"
                    )

        # predict with Keras model
        y_pred = self.model_.predict(x=X, **pred_args)

        return y_pred

    def predict(self, X, **kwargs):
        """Returns predictions for the given test data.

        Parameters
        ----------
        X : Union[array-like, sparse matrix, dataframe] of shape (n_samples, n_features)
            Training samples where n_samples is the number of samples
            and n_features is the number of features.
        **kwargs : Dict[str, Any]
            Extra arguments to route to ``Model.predict``.

        Warnings
        --------
            Passing estimator parameters as keyword arguments (aka as ``**kwargs``) to ``predict`` is not supported by the Scikit-Learn API,
            and will be removed in a future version of SciKeras.
            These parameters can also be specified by prefixing ``predict__`` to a parameter at initialization
            (``BaseWrapper(..., fit__batch_size=32, predict__batch_size=1000)``)
            or by using ``set_params`` (``est.set_params(fit__batch_size=32, predict__batch_size=1000)``).

        Returns
        -------
        array-like
            Predictions, of shape shape (n_samples,) or (n_samples, n_outputs).
        """
        # predict with Keras model
        y_pred = self._predict_raw(X=X, **kwargs)

        # post process y
        y_pred = self.target_encoder_.inverse_transform(y_pred)

        return y_pred

    @staticmethod
    def scorer(y_true, y_pred, **kwargs) -> float:
        """Scoring function for model.

        This is not implemented in BaseWrapper, it exists
        as a stub for documentation.

        Parameters
        ----------
        y_true : array-like of shape (n_samples,) or (n_samples, n_outputs)
            True labels.
        y_pred : array-like of shape (n_samples,) or (n_samples, n_outputs)
            Predicted labels.
        **kwargs: dict
            Extra parameters passed to the scorer.

        Returns
        -------
        float
            Score for the test data set.
        """
        raise NotImplementedError("Scoring is not implemented on BaseWrapper.")

    def score(self, X, y, sample_weight=None) -> float:
        """Returns the score on the given test data and labels.

        No default scoring function is implemented in BaseWrapper,
        you must subclass and implement one.

        Parameters
        ----------
        X : Union[array-like, sparse matrix, dataframe] of shape (n_samples, n_features)
            Test input samples, where n_samples is the number of samples
            and n_features is the number of features.
        y : Union[array-like, sparse matrix, dataframe] of shape \
            (n_samples,) or (n_samples, n_outputs)
            True labels for X.
        sample_weight : array-like of shape (n_samples,), default=None
            Array of weights that are assigned to individual samples.
            If not provided, then each sample is given unit weight.

        Returns
        -------
        float
            Score for the test data set.
        """
        # validate y
        _, y = self._validate_data(X=None, y=y)

        # validate sample weights
        if sample_weight is not None:
            X, y, sample_weight = self._validate_sample_weight(
                X=X, y=y, sample_weight=sample_weight
            )

        # compute Keras model score
        y_pred = self.predict(X)

        # filter kwargs and get attributes for score
        params = self.get_params()
        score_args = route_params(params, destination="score", pass_filter=set())

        return self.scorer(y, y_pred, sample_weight=sample_weight, **score_args)

    def _get_metadata(self) -> Dict[str, Any]:
        """Meta parameters (parameters created by fit, like
        n_features_in_ or target_type_).

        Returns
        -------
        Dict[str, Any]
            Dictionary of meta parameters
        """
        return {
            k: v
            for k, v in self.__dict__.items()
            if (len(k) > 1 and k[-1] == "_" and k[-2] != "_" and k[0] != "_")
        }

    def set_params(self, **params) -> "BaseWrapper":
        """Set the parameters of this estimator.

        The method works on simple estimators as well as on nested objects
        (such as pipelines). The latter have parameters of the form
        ``<component>__<parameter>`` so that it's possible to update each
        component of a nested object.
        This also supports routed parameters, eg: ``classifier__optimizer__learning_rate``.

        Parameters
        ----------
        **params : dict
            Estimator parameters.

        Returns
        -------
        BaseWrapper
            Estimator instance.
        """
        for param, value in params.items():
            if any(
                param.startswith(prefix + "__") for prefix in self._routing_prefixes
            ):
                # routed param
                setattr(self, param, value)
            else:
                try:
                    super().set_params(**{param: value})
                except ValueError:
                    # Give a SciKeras specific user message to aid
                    # in moving from the Keras wrappers
                    raise ValueError(
                        f"Invalid parameter {param} for estimator {self.__name__}."
                        "\nThis issue can likely be resolved by setting this parameter"
                        f" in the {self.__name__} constructor:"
                        f"\n`{self.__name__}({param}={value})`"
                        "\nCheck the list of available parameters with"
                        " `estimator.get_params().keys()`"
                    ) from None
        return self

    def _get_param_names(self):
        """Get parameter names for the estimator"""
        return (
            k for k in self.__dict__ if not k.endswith("_") and not k.startswith("_")
        )

    def _more_tags(self):
        """Get sklearn tags for the estimator"""
        tags = super()._more_tags()
        tags.update(self._tags)
        return tags

    def __repr__(self):
        repr_ = str(self.__name__)
        repr_ += "("
        params = self.get_params()
        if params:
            repr_ += "\n"
        for key, val in params.items():
            repr_ += "\t" + key + "=" + str(val) + "\n"
        repr_ += ")"
        return repr_

class KerasClassifier(BaseWrapper, ClassifierMixin):
    """Implementation of the scikit-learn classifier API for Keras.

    Below are a list of SciKeras specific parameters. For details on other parameters,
    please see the see the `tf.keras.Model documentation <https://www.tensorflow.org/api_docs/python/tf/keras/Model>`_.

    Parameters
    ----------
    model : Union[None, Callable[..., tf.keras.Model], tf.keras.Model], default None
        Used to build the Keras Model. When called,
        must return a compiled instance of a Keras Model
        to be used by `fit`, `predict`, etc.
        If None, you must implement ``_keras_build_fn``.
    optimizer : Union[str, tf.keras.optimizers.Optimizer, Type[tf.keras.optimizers.Optimizer]], default "rmsprop"
        This can be a string for Keras' built in optimizers,
        an instance of tf.keras.optimizers.Optimizer
        or a class inheriting from tf.keras.optimizers.Optimizer.
        Only strings and classes support parameter routing.
    loss : Union[Union[str, tf.keras.losses.Loss, Type[tf.keras.losses.Loss], Callable], None], default None
        The loss function to use for training.
        This can be a string for Keras' built in losses,
        an instance of tf.keras.losses.Loss
        or a class inheriting from tf.keras.losses.Loss .
        Only strings and classes support parameter routing.
    random_state : Union[int, np.random.RandomState, None], default None
        Set the Tensorflow random number generators to a
        reproducible deterministic state using this seed.
        Pass an int for reproducible results across multiple
        function calls.
    warm_start : bool, default False
        If True, subsequent calls to fit will _not_ reset
        the model parameters but *will* reset the epoch to zero.
        If False, subsequent fit calls will reset the entire model.
        This has no impact on partial_fit, which always trains
        for a single epoch starting from the current epoch.
    batch_size : Union[int, None], default None
        Number of samples per gradient update.
        This will be applied to both `fit` and `predict`. To specify different numbers,
        pass `fit__batch_size=32` and `predict__batch_size=1000` (for example).
        To auto-adjust the batch size to use all samples, pass `batch_size=-1`.
    class_weight : Union[Dict[Any, float], str, None], default None
        Weights associated with classes in the form ``{class_label: weight}``.
        If not given, all classes are supposed to have weight one.
        The "balanced" mode uses the values of y to automatically adjust
        weights inversely proportional to class frequencies in the input data
        as ``n_samples / (n_classes * np.bincount(y))``.
        Note that these weights will be multiplied with sample_weight (passed
        through the fit method) if sample_weight is specified.

    Attributes
    ----------
    model_ : tf.keras.Model
        The instantiated and compiled Keras Model. For pre-built models, this
        will just be a reference to the passed Model instance.
    history_ : Dict[str, List[Any]]
        Dictionary of the format ``{metric_str_name: [epoch_0_data, epoch_1_data, ..., epoch_n_data]}``.
    initialized_ : bool
        True if this estimator has been initialized (i.e. predict can be called upon it).
        Note that this does not guarantee that the model is "fitted": if ``BaseWrapper.initialize``
        was called instead of fit the model wil likely have random weights.
    target_encoder_ : sklearn-transformer
        Transformer used to pre/post process the target y.
    feature_encoder_ : sklearn-transformer
        Transformer used to pre/post process the features/input X.
    n_outputs_expected_ : int
        The number of outputs the Keras Model is expected to have, as determined by ``target_transformer_``.
    target_type_ : str
        One of:

        * 'continuous': y is an array-like of floats that are not all
          integers, and is 1d or a column vector.
        * 'continuous-multioutput': y is a 2d array of floats that are
          not all integers, and both dimensions are of size > 1.
        * 'binary': y contains <= 2 discrete values and is 1d or a column
          vector.
        * 'multiclass': y contains more than two discrete values, is not a
          sequence of sequences, and is 1d or a column vector.
        * 'multiclass-multioutput': y is a 2d array that contains more
          than two discrete values, is not a sequence of sequences, and both
          dimensions are of size > 1.
        * 'multilabel-indicator': y is a label indicator matrix, an array
          of two dimensions with at least two columns, and at most 2 unique
          values.
        * 'unknown': y is array-like but none of the above, such as a 3d
          array, sequence of sequences, or an array of non-sequence objects.
    y_shape_ : Tuple[int]
        Shape of the target y that the estimator was fitted on.
    y_dtype_ : np.dtype
        Dtype of the target y that the estimator was fitted on.
    X_shape_ : Tuple[int]
        Shape of the input X that the estimator was fitted on.
    X_dtype_ : np.dtype
        Dtype of the input X that the estimator was fitted on.
    n_features_in_ : int
        The number of features seen during `fit`.
    n_outputs_ : int
        Dimensions of y that the transformer was trained on.
    n_outputs_expected_ : int
        Number of outputs the Keras Model is expected to have.
    classes_ : Iterable
        The classes seen during `fit`.
    n_classes_ : int
        The number of classes seen during `fit`.
    """

    _estimator_type = "classifier"
    _tags = {
        "multilabel": True,
        "_xfail_checks": {
            "check_fit_idempotent": "tf does not use \
            sparse tensors",
            "check_no_attributes_set_in_init": "can only \
            pass if all params are hardcoded in __init__",
        },
        **BaseWrapper._tags,
    }

    def __init__(
        self,
        model: Union[None, Callable[..., tf.keras.Model], tf.keras.Model] = None,
        *,
        build_fn: Union[
            None, Callable[..., tf.keras.Model], tf.keras.Model
        ] = None,  # for backwards compatibility
        warm_start: bool = False,
        random_state: Union[int, np.random.RandomState, None] = None,
        optimizer: Union[
            str, tf.keras.optimizers.Optimizer, Type[tf.keras.optimizers.Optimizer]
        ] = "rmsprop",
        loss: Union[
            Union[str, tf.keras.losses.Loss, Type[tf.keras.losses.Loss], Callable], None
        ] = None,
        metrics: Union[
            List[
                Union[
                    str,
                    tf.keras.metrics.Metric,
                    Type[tf.keras.metrics.Metric],
                    Callable,
                ]
            ],
            None,
        ] = None,
        batch_size: Union[int, None] = None,
        validation_batch_size: Union[int, None] = None,
        verbose: int = 1,
        callbacks: Union[
            List[Union[tf.keras.callbacks.Callback, Type[tf.keras.callbacks.Callback]]],
            None,
        ] = None,
        validation_split: float = 0.0,
        shuffle: bool = True,
        run_eagerly: bool = False,
        epochs: int = 1,
        class_weight: Union[Dict[Any, float], str, None] = None,
        **kwargs,
    ):
        super().__init__(
            model=model,
            build_fn=build_fn,
            warm_start=warm_start,
            random_state=random_state,
            optimizer=optimizer,
            loss=loss,
            metrics=metrics,
            batch_size=batch_size,
            validation_batch_size=validation_batch_size,
            verbose=verbose,
            callbacks=callbacks,
            validation_split=validation_split,
            shuffle=shuffle,
            run_eagerly=run_eagerly,
            epochs=epochs,
            **kwargs,
        )
        self.class_weight = class_weight

    def _type_of_target(self, y: np.ndarray) -> str:
        target_type = type_of_target(y)
        if target_type == "binary" and self.classes_ is not None:
            # check that this is not a multiclass problem missing categories
            target_type = type_of_target(self.classes_)
        return target_type

    @property
    def _fit_kwargs(self) -> Set[str]:
        # remove class_weight since KerasClassifier re-processes it into sample_weight
        return BaseWrapper._fit_kwargs - {"class_weight"}

    @staticmethod
    def scorer(y_true, y_pred, **kwargs) -> float:
        """Scoring function for KerasClassifier.

        KerasClassifier uses ``sklearn_accuracy_score`` by default.
        To change this, override this method.

        Parameters
        ----------
        y_true : array-like of shape (n_samples,) or (n_samples, n_outputs)
            True labels.
        y_pred : array-like of shape (n_samples,) or (n_samples, n_outputs)
            Predicted labels.
        **kwargs: dict
            Extra parameters passed to the scorer.

        Returns
        -------
        float
            Score for the test data set.
        """
        return sklearn_accuracy_score(y_true, y_pred, **kwargs)

    @property
    def target_encoder(self):
        """Retrieve a transformer for targets / y.

        For ``KerasClassifier.predict_proba`` to
        work, this transformer must accept a ``return_proba``
        argument in ``inverse_transform`` with a default value
        of False.

        Metadata will be collected from ``get_metadata`` if
        the transformer implements that method.
        Override this method to implement a custom data transformer
        for the target.

        Returns
        -------
        sklearn-transformer
            Transformer implementing the sklearn transformer
            interface.
        """
        categories = "auto" if self.classes_ is None else [self.classes_]
        return ClassifierLabelEncoder(loss=self.loss, categories=categories)

    def initialize(self, X, y) -> "KerasClassifier":
        """Initialize the model without any fitting.
        You only need to call this model if you explicitly do not want to do any fitting
        (for example with a pretrained model). You should _not_ call this
        right before calling ``fit``, calling ``fit`` will do this automatically.

        Parameters
        ----------
        X : Union[array-like, sparse matrix, dataframe] of shape (n_samples, n_features)
                Training samples where n_samples is the number of samples
                and `n_features` is the number of features.
        y : Union[array-like, sparse matrix, dataframe] of shape \
            (n_samples,) or (n_samples, n_outputs), default None
            True labels for X.

        Returns
        -------
        KerasClassifier
            A reference to the KerasClassifier instance for chained calling.
        """
        self.classes_ = None
        super().initialize(X=X, y=y)
        return self

    def fit(self, X, y, sample_weight=None, **kwargs) -> "KerasClassifier":
        """Constructs a new classifier with ``model`` & fit the model to ``(X, y)``.

        Parameters
        ----------
        X : Union[array-like, sparse matrix, dataframe] of shape (n_samples, n_features)
            Training samples, where n_samples is the number of samples
            and n_features is the number of features.
        y : Union[array-like, sparse matrix, dataframe] of shape (n_samples,) or (n_samples, n_outputs)
            True labels for X.
        sample_weight : array-like of shape (n_samples,), default=None
            Array of weights that are assigned to individual samples.
            If not provided, then each sample is given unit weight.
        **kwargs : Dict[str, Any]
            Extra arguments to route to ``Model.fit``.

        Warnings
        --------
            Passing estimator parameters as keyword arguments (aka as ``**kwargs``) to ``fit`` is not supported by the Scikit-Learn API,
            and will be removed in a future version of SciKeras.
            These parameters can also be specified by prefixing ``fit__`` to a parameter at initialization
            (``KerasClassifier(..., fit__batch_size=32, predict__batch_size=1000)``)
            or by using ``set_params`` (``est.set_params(fit__batch_size=32, predict__batch_size=1000)``).

        Returns
        -------
        KerasClassifier
            A reference to the instance that can be chain called (``est.fit(X,y).transform(X)``).
        """
        self.classes_ = None
        if self.class_weight is not None:
            sample_weight = 1 if sample_weight is None else sample_weight
            sample_weight *= compute_sample_weight(class_weight=self.class_weight, y=y)
        super().fit(X=X, y=y, sample_weight=sample_weight, **kwargs)
        return self

    def partial_fit(
        self, X, y, classes=None, sample_weight=None, **kwargs
    ) -> "KerasClassifier":
        """Fit classifier for a single epoch, preserving the current epoch
        and all model parameters and state.

        Parameters
        ----------
        X : Union[array-like, sparse matrix, dataframe] of shape (n_samples, n_features)
            Training samples, where n_samples is the number of samples
            and n_features is the number of features.
        y : Union[array-like, sparse matrix, dataframe] of shape (n_samples,) or (n_samples, n_outputs)
            True labels for X.
        classes: ndarray of shape (n_classes,), default=None
            Classes across all calls to partial_fit. Can be obtained by via
            np.unique(y_all), where y_all is the target vector of the entire dataset.
            This argument is only needed for the first call to partial_fit and can be
            omitted in the subsequent calls. Note that y doesn’t need to contain
            all labels in classes. If you do not pass this argument, SciKeras
            will use ``classes=np.all(y)`` with the y passed in the first call.
        sample_weight : array-like of shape (n_samples,), default=None
            Array of weights that are assigned to individual samples.
            If not provided, then each sample is given unit weight.
        **kwargs : Dict[str, Any]
            Extra arguments to route to ``Model.fit``.

        Returns
        -------
        KerasClassifier
            A reference to the instance that can be chain called
            (ex: instance.fit(X,y).transform(X) )
        """
        self.classes_ = (
            classes if classes is not None else getattr(self, "classes_", None)
        )
        if self.class_weight is not None:
            sample_weight = 1 if sample_weight is None else sample_weight
            sample_weight *= compute_sample_weight(class_weight=self.class_weight, y=y)
        super().partial_fit(X, y, sample_weight=sample_weight, **kwargs)
        return self

    def predict_proba(self, X, **kwargs):
        """Returns class probability estimates for the given test data.

        Parameters
        ----------
        X : Union[array-like, sparse matrix, dataframe] of shape (n_samples, n_features)
            Training samples, where n_samples is the number of samples
            and n_features is the number of features.
        **kwargs : Dict[str, Any]
            Extra arguments to route to ``Model.predict``.

        Warnings
        --------
            Passing estimator parameters as keyword arguments (aka as ``**kwargs``) to ``predict_proba`` is not supported by the Scikit-Learn API,
            and will be removed in a future version of SciKeras.
            These parameters can also be specified by prefixing ``predict__`` to a parameter at initialization
            (``KerasClassifier(..., fit__batch_size=32, predict__batch_size=1000)``)
            or by using ``set_params`` (``est.set_params(fit__batch_size=32, predict__batch_size=1000)``).

        Returns
        -------
        array-like, shape (n_samples, n_outputs)
            Class probability estimates.
            In the case of binary classification,
            to match the scikit-learn API,
            SciKeras will return an array of shape (n_samples, 2)
            (instead of `(n_sample, 1)` as in Keras).
        """
        # call the Keras model's predict
        outputs = self._predict_raw(X=X, **kwargs)

        # post process y
        y = self.target_encoder_.inverse_transform(outputs, return_proba=True)

        return y

So you just added accept_sparse=True to check_X_y, right?

Right!

That is really surprising. If you look at the code in the this PR we are only calling 3 functions:

isspmatrix
Xs.getformat()
Xs_csr.sort_indices()

You uncommented (3) which leaves just the other 2. (1) is just an is instance check and (2) checks if the indices are sorted before sorting them
The only thing I can think of is that your indices are not sorted but TensorFlow knows how to handle that anyways despite what their documentation says and despite the suggestion in the SciPy docs.
I guess the things we can do here are:

Test/check what happens if we give TF unsorted indices
I'll make a branch that you can test that does not sort indices

@mattalhonte-srm  could you please try pip install git+https://github.com/adriangb/scikeras.git@sanity-check-sort-indices

Just tried it.  Container blew up.  The only thing that's worked is just passing the initial sparse matrix I loaded directly in, with no casting or sorting or anything else being done to it.

Heya!  That branch worked!

Thanks so much, this rules!

Heya!  Thanks again for this, it's been working perfectly!  Could it be merged into Master?  I wouldn't wanna miss out on new versions of the package!
Thanks!


DelegatingSslContext should also be able to configure the SslHandler

Motivation:
DelegatingSslContext at the moment intercept newEngine calls and allow to init the SslEngine after it is created. The problem here is that this may not work the SSLEngine that is wrapped in the SslHandler when calling newHandler(...). This is because some SslContext implementations not delegate to newEngine(...) when creating the SslHandler to allow some optimizations. For this we should also allow to init the SslHandler after its creation and by default just delegate to initEngine(...).
Modifications:
Allow the user to also init the SslHandler after creation while by default init its SSLEngine after creation.
Result:
More flexible and correct code.

@Scottmitch all addressed...

Cherry-picked into 4.1 (2beb5fc8ee) and 4. 0(cd80df7547)


Add payload samples to Automation Service documentation

Customer Brandon Hess of Huron sent an email to @pkazmir-sumo with the following request:

I also had one small piece of feedback as I started writing a few playbooks. It would be super helpful to see sample payloads included in the docs (i.e. what does the payload for an entity or insight look like when it's passed to the Automation service). Maybe it's there and I overlooked it. But for example, when I was writing a VirusTotal playbook to look up IP reputation, it wasn't immediately clear what field name would contain the value of the IP address. I needed to run the playbook first, let it fail, and then look at the input to discover it was the input.value field that contains the value of the IP address in question. Then I had to go back and update the configuration of the VirusTotal step to use that input.value field as the IP address.

We should add a new section containing payload examples to the Automation Service documentation under the Playbooks section:
https://help.sumologic.com/docs/cse/automation-service/automation-service-playbooks/

I'll get help from Boris Gurzhovski for the example payloads.

Closing this issue because it's now tracked by Jira ticket: https://sumologic.atlassian.net/browse/DOCS-91


feature request: support css string query in waitForElement

Currently one has to first get the element using agent.document.querySelector prior to being able to use agent.waitForElement. It seems like a common enough pattern that we might as well support the string that you would pass to querySelector directly to a override  version of waitForElement?
Might also entail that we need to in that case accept optional args on top of it.

See some comments on a similar idea at #290
For what it's worth, this idea has gained steam in Hero, but in a slightly different form. We've added extensions (in Hero) onto dom Elements querySelector('.big').$waitForVisible().

Nice. Feel free to close-as is in that case, I'm happy with that answer.
Will it also be possible for hero-users (so without having to fork it) to inject such extensions?

Cool! I'd very much like to figure out how to do that in the plugin structure. Databox (in ulixee project) has its own extenders. You can see that the structure is a bit complex to get typescript to recognize them.
https://github.com/ulixee/ulixee/blob/main/databox/client/lib/DomExtender.ts

Wouldn't be able to tell. But if you wish help with it in near future I do not mind helping to come with something, theoretical and/or as an implementation. For now it's like a sweet dream for me.


No pom.xml file in project root

"Under the Build section, select Add build step and choose Invoke top-level Maven targets. Enter package in the Goals field."
This causes Jenkins to call mvn package on the root folder of the project, which doesn't have a pom.xml file, so the build fails.
Logs:
Building in workspace /var/lib/jenkins/workspace/MyJavaApp
> git rev-parse --is-inside-work-tree # timeout=10
Fetching changes from the remote Git repository
> git config remote.origin.url https://github.com/daniel-rocha/gs-spring-boot-docker.git # timeout=10
Fetching upstream changes from https://github.com/daniel-rocha/gs-spring-boot-docker.git
> git --version # timeout=10
> git fetch --tags --progress https://github.com/daniel-rocha/gs-spring-boot-docker.git +refs/heads/:refs/remotes/origin/
> git rev-parse refs/remotes/origin/master^{commit} # timeout=10
> git rev-parse refs/remotes/origin/origin/master^{commit} # timeout=10
Checking out Revision 8958ec7ba2a768d4f9e30c4a80ee3ea892414bac (refs/remotes/origin/master)
> git config core.sparsecheckout # timeout=10
> git checkout -f 8958ec7ba2a768d4f9e30c4a80ee3ea892414bac
Commit message: "Use default unpacked dependency location in pom.xml"
> git rev-list --no-walk 8958ec7ba2a768d4f9e30c4a80ee3ea892414bac # timeout=10
[MyJavaApp] $ mvn package
[INFO] Scanning for projects...
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 0.096 s
[INFO] Finished at: 2018-11-14T13:27:36+00:00
[INFO] Final Memory: 5M/133M
[INFO] ------------------------------------------------------------------------
[ERROR] The goal you specified requires a project to execute but there is no POM in this directory (/var/lib/jenkins/workspace/MyJavaApp). Please verify you invoked Maven from the correct directory. -> [Help 1]
[ERROR]
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR]
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MissingProjectException
Build step 'Invoke top-level Maven targets' marked build as failure
Finished: FAILURE

Document Details
⚠ Do not edit this section. It is required for docs.microsoft.com ➟ GitHub issue linking.

ID: f0b98e34-a479-d004-a218-add7b1d38ccb
Version Independent ID: a2bdd39c-b087-c74c-d349-55b292a29109
Content: Use Jenkins to deploy your web apps to Azure
Content Source: articles/jenkins/java-deploy-webapp-tutorial.md
Service: jenkins
GitHub Login: @TomArcher
Microsoft Alias: tarcher

The answer here is to go to Advanced then point the step to the complete/pom.xml file.

@daniel-rocha Thanks for your comment. The feedback has been shared with the content owner for further review. Thanks for your patience.

@daniel-rocha I apologize for not following up on this. For some reason, I thought this Issue had been closed. I'll work on this and update the article and this Issue.

@daniel-rocha I'm unable to find the verbiage you quote, and I do find the pom.xml file in the root. Has this article been updated since you opened the Issue?

@daniel-rocha I'm unable to find the verbiage you quote, and I do find the pom.xml file in the root. Has this article been updated since you opened the Issue?

@femsulu #please-close the issue
@daniel-rocha  Let us know if you wish to reopen.

@daniel-rocha and @royal0527 I was able to complete the tutorial without errors. It's possible the article has been updated since you filed the issue and that's the reason I can complete the steps without reproducing the errors.
To confirm the article in question is Tutorial: Deploy to Azure App Service with Jenkins and the Azure CLI?
If that's correct, can you reply with which heading you were on when you encountered the error? I'll do my best to reproduce the issue so I can update the docs.
Thanks you!


[BUG] Email address link in the contact section is not working.

Description
The email address link in the contact section is not working. It means that when someone clicks on the email address it should be redirected to an email services provider like Gmail.
Screenshot

@Iamdivyak Please assign this issue to me under hacktoberfest label.


Fixes issue #570 for running jasmine on windwos

This issues fixes some path problems when running vim-test on a windows instance of vim/nvim

I don't know how to link this PR to issue #570.  This PR should resolve the issue on Windows

Thanks!  I've never written vimscript so I don't know how to debug it.  There were a lot of echom calls to figure out what was gonig on. haha!


usePublisherConnection in RabbitTemplate should default to true

According to docs in setUsePublisherConnection() setter in RabbitTemplate, usePublisherConnection field should default to true since version 2.1. For me it looks like this still defaults to false.

We need to change the javadoc instead. It turned out that true is not a safe default and will break many existing applications.
The problem is that any auto-delete, exclusive Queue beans in the application context will be declared on the producer connection and any listener containers that consume from those queues will fail to start because they can only be accessed on the connection on which they were declared.


How can I implement Puppeteer in Chrome Extension with React?

I have create a Chrome Extension with React. I want to control chrome tab (e.x: click button, go to new url, wait for element visible,...) by Puppeteer. Anyone know how to do this?

Hello
did u find anyway to use react with puppeteer?

Everybody: running in Chrome Extension is a bit of a journey. I'd recommend starting here to get a browser bundled version of Puppeteer.

For those revisiting this, the link above has been deleted but can be viewed from a past commit
Here's the pull request for the reasoning.


Do you have the scalable hyper-express server and client example?

Hi Karthik,
First of all, I haven't tried hyper-express or even uWebsockets. I normally use socket.io along with PM2 and Redis if scalable architecture needed. I liked your efforts and develop a hypre-express. I appreciate this middleware application and also that its on top of uWebsockets.js, which is high performance. I would like to know if there is a scalable example of server and client, say for the clients sending many messages and file streaming say about 100Mb (take any size).

It should allow the normal GET POST requests
It should allow creating two sockets one for normal message and another for file sharing in same client, with login check (point 5)
Ensuring that all cores are used and scalable if I install another machine, say like socket-io/pm2 and Redis adapters.
CORS requests configuration
session / cookie / any other way, with authentication like passport.js (standard email password use case, with email to new registration, validation of it, login and logout, along with session)
data from Redis and Sqlite database (based on whether the normal DB or in-memory DB is needed
the routing needed, obviously for GET POST requests

Nothing fancy needed, just
Can you help with that? I tried to check your contact details like email id but couldn't get it, so logging as an issue.
Keep up the great work! I am following you to check the updates.
You can delete this request and update your id, if you wish to connect.

Hi, so HyperExpress is meant to be very developer friendly and familiar to Node.js developers who have worked with Express.js in the past. I currently don't have an official example template repository for building such application but I would recommend you build out the application you proposed above the same way you would with just Express.js
In fact, HyperExpress is practically drop-in compatible for Express.js projects as HyperExpress implements the same properties/methods/patterns as Express for its functionality. So this means any existing codebases, templates, middlewares and packages should be compatible to be used with HyperExpress.

Thank you very much Karthik! Appreciate the response.


Signup emails update based on user type

Currently, the confirmation email sent when a user starts a new loop and joins an existing loop, is the same. Would be good to have to differentiate the copy based on the recipient's user type.

If the user type is a chainAdmin use the following copy:

Hello, and welcome to the Clothing Loop! Thank you for making the step to create a more sustainable world together.
We are super happy to have you. Please find our manual in the attachment to see how to proceed.
Right now everything is set up, but the loop is not live. When you are ready, login and move the slider to ‘visible’. Don’t wait too long, as someone else may beat you to the chase and start a loop in the same area. And even though there can be multiple loops in the same area, it may cause a bit of confusion.
The Clothing Loop team

If the user type is null:

Hello again!
You are now officially registered to the Clothing Loop!
What happens next?
Each Loop has a local host that coordinates the specific Loop voluntarily. He/she/they will receive your application, and get in touch with further info. Please be patient, sometimes this can take up to four weeks, depending on the available time of the host.
Of course, you want to start swapping right away, which we applaud obviously! What you can do in the meantime is start scanning your closet for items that are ready to go on adventures with somebody else. You can check if they need some repairs, depilling or a little wash, so they will become an even better addition to our travelling swap bags. Please note, it’s a mutual effort to keep the bag a surprise for everyone. This way we are not just passing on a bag of clothes but a bag of happy stories and care! Don’t worry if you do not find something in the very first bag
Furthermore, we love to grow as fast as possible to make the most impact. The sooner the better! So, another thing you can help us with is spread the word amongst your family, friends and neighbours. The earth will be eternally grateful.
Happy swapping!
The Clothing Loop team

Currently the email is sent on user signup which at the point user has not been assigned with any role (except Admin)
Then does that mean shall we shift the email sending to after loop is created?

Sorry for just noticing this. We shall we include the email verification link? Or this email will be sent separately from the email verification link (i.e. user will receive 2 emails)?

I think it should include the verification link, so we don't have to generate two separated emails. I'll need to ask to update the copies, as the ones above don't reference the verification link. I'll update you on this

Confirmation email has been replaced by a login verification email that it user type agnostic


Change upload button text using js

Is there a way i can change the upload button text using javascript.
I have buttons in my webpage to change language. I change all the label texts using jquery. I would like to do the same for the upload button. Please help

uploadStr:"Upload"
You can use
abortStr: "Abort",
cancelStr: "Cancel",
deletelStr: "Delete",
doneStr: "Done",
multiDragErrorStr: "Multiple File Drag & Drop is not allowed.",
extErrorStr: "is not allowed. Allowed extensions: ",
duplicateErrorStr: "is not allowed. File already exists.",
sizeErrorStr: "is not allowed. Allowed Max size: ",
uploadErrorStr: "Upload is not allowed",
maxFileCountErrorStr: " is not allowed. Maximum allowed files are:",
downloadStr: "Download",

Can this be done on a button click?

Can I change the texts on a button click or a jquery event?

there is a method. update(), you can chnage the values.
On 30 October 2015 at 12:57, kkbkrishna<EMAIL_ADDRESS>wrote:

Can I change the texts on a button click or a jquery event?
—
Reply to this email directly or view it on GitHub
https://github.com/hayageek/jquery-upload-file/issues/77#issuecomment-152415514
.


chore: update subcommandDescriptionsGenerated.swift

minor fix

This is generated file, you need to update docs/aerospace-focus.adoc
Fixed in bbb5c3264ad321b7030909b833c3b52b22c0fb25


goog.dom.classlist error with SVG

When trying to apply class for a SVG by calling goog.dom.classlist.enable it results calling:
goog.dom.classlist.add = function(element, className) {
  if (goog.dom.classlist.ALWAYS_USE_DOM_TOKEN_LIST || element.classList) {
    element.classList.add(className);
    return;
  }

  if (!goog.dom.classlist.contains(element, className)) {
    // Ensure we add a space if this is not the first class name added.
    element.className += element.className.length > 0 ?
        (' ' + className) : className;
  }
};

But SVG is having field 'className' not as a string, but as an object. So, as a result, class won't be applied for SVG.

Does the built-in Element.prototype.classList meet your needs?  goog.dom.classlist is basically just a shim for that and shouldn't really be used.  That said, if you can fix it to work, you're welcome to send a PR.

generally, goog.dom is for HTML and SVG support is limited at best.
On Thu, Jul 7, 2016 at 12:42 PM, Stephen Hicks<EMAIL_ADDRESS>wrote:

Does the built-in Element.prototype.classList meet your needs?
goog.dom.classlist is basically just a shim for that and shouldn't really
be used. That said, if you can fix it to work, you're welcome to send a PR.
—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
https://github.com/google/closure-library/issues/714#issuecomment-231185599,
or mute the thread
https://github.com/notifications/unsubscribe/ABMDKg2FjsYSc2S-W3JdfiYxXn0EtS5qks5qTVaagaJpZM4Iqn6B
.

This issue is being closed as a preparation step before archiving the repository.  See the README for more detail.


Chavic/fixture

Added  More Tests/Fixtures
Adding more tests to match the latest spec of Uniffi-rs v0.24 and make refactoring easier.
Because some features are implicitly covered by other tests, the total number of tests is reduced.
Next Steps

[ ] Implement futures
[ ] Incrementally refactoring

@chavic how come the test suite is still failing? Maybe we just want to disable broken tests for now so we can use the test suite from now on as an actual CI passing point?

@chavic how come the test suite is still failing? Maybe we just want to disable broken tests for now so we can use the test suite from now on as an actual CI passing point?

I looked at the errors for a few seconds... noticed issues with the tooling for example 'cargo fmt' wasn't found. Could work on fixing the CI.... it'll probably make things faster in the long run


Updating MIP38c2: Core Unit State

@Davidutro, @CPSTL, please review and merge

The format seems very piled on top of itself

Any way we can change it to be more of a list view? Or a table?
Example:

Yeah, you're right. I feel the same way. Those lists look good!
Gimme two minutes

As a general format rule, add a space after headers in markdown @blimpa
otherwise, that last commit looks great

As a general format rule, add a space after headers in markdown @blimpa
otherwise, that last commit looks great

I don't see where I contravened that rule :0

The headers for the CU names and the lists underneath are missing spaces.

@Davidutro , I totally misread what you wrote. Hah. Sorry

Heh no worries, always happy to explain 😃


The theme switcher always shows dark mode on initial load

showing dark mode, while it's clearly light mode

seems like an issue with SvelteKit SSR and mode-watcher's undefined $mode on server

The issue is identified (way before this comment is made lol)
It's that Svelte thinks the two SVG's on either side of the if are the same, thus not updating the element when $mode changes.
It is however not fixable without a workaround - we'd just make them different, one is wrapped with <span>, one isn't.


migrate to PF5 (HMS-4450)

Description
WIP
This PR migrate to PF5, but uses the deprecated PF4 for components.
This migration forces us to bump react to version 18.x, frontend-components, and react testing library, due to peer dependencies.
Type of change
What is it?

[ ] Bug fix (non-breaking change which fixes an issue)
[ ] New feature (non-breaking change which adds functionality)
[ ] Documentation update
[ ] Tests update

Looks like some nasty dependency error, let me know if I should help google solutions :)

Update on the Latest Test Commit
This service relies heavily on the snapshot testing mechanism, so extra caution is required during verification. I have carefully reviewed each snapshot that includes changes to PF4 styles. Additionally, I manually tested the main pages to ensure they are navigable and functioning as expected. I also verified that creating new images and updating them works as intended.

:exclamation: Outdated dependencies error

Installed version of react-router-dom (5.3.4) is older than required version (6.0.0)
Installed version of @redhat-cloud-services/frontend-components-config (4.7.3) is older than required version (6.0.0)


Add support to sound volume level through "MQTT Device Info Topic"

Hi!
It will be great if I can receive an information about the sound volume level of a device through MQTT, when the volume it is changed.
Thank you in advance!

This integration doesn't have any control over what Fully publishes to MQTT. If volume level is missing from Fully's MQTT output you'd need to contact the Fully Kiosk Browser developer. In my experience they're pretty quick to respond to messages and definitely willing to make adjustments.

Or (just re-read the post) are you saying the sound level is already available in MQTT and you'd like to see this integration add support for showing/controlling it?


stac_vrt missing when launching GPU-PyTorch profile for landcover.ipynb

When running the landcover tutorial, import stac_vrt failed.
After adding a cell block above the import and pip install stac-vrt the issue is fixed.

Fixed by the updated images released to the hub today. Thanks again for reporting it.


[bug] route resource provisioner doesn't support environment variable param

...
resources:
  env:
    type: environment
  route:
    type: route
    params:
      host: ${resources.env.EXTERNAL_HOSTNAME}
      port: web
      path: /

This throws an error like
Error: failed to provision: resource 'route.default#example.route': failed to provision: init template failed: failed to execute template: template: :4:64: executing "" at <fail "params.host must be a valid hostname">: error calling fail: params.host must be a valid hostname

Because params.host becomes ${EXTERNAL_HOSTNAME}.
We may need to adjust this so that env references actually resolve to their real values (or fail) when resolved in a resource param.

This is also the same when used in file content interpolation as well 🤔


Clean up TestContext when pack build unexpectedly succeeds

Moves the creation of the TestContext to before the invocation of run_pack_command so that the context will be cleaned up regardless of the outcome.
@fixes #586

@edmorley does #619 also resolve this issue? In any case, this PR here won't apply cleanly anymore after run_pack_command changed so much.:/

@Malax No #619 does not fix this directly, though it does give more options here as to how to fix this issue.
It seems the options here now are to:

(This is now only possible after #619) Make an explicit call to remove the Docker image on the "successful but panicing" branch here: https://github.com/heroku/libcnb.rs/blob/346a186b35a5941a593228b25d68a196c7e75116/libcnb-test/src/test_runner.rs#L170-L171
(As Richard suggested above) Add a new Image (or similar) struct, which has the Drop on it itself, which can be created sooner than the TestContext - and will handle the image removal itself, rather than on TestContext drop.
Move the creation of TestContext earlier (the current PR's implementation), but add a comment explaining why.

I think my preference is for option (1) or (2).
Thoughts?

Thoughts?

I would vote for option 1 here. I find Drop complicates the code and makes it hard to reason about. If we can avoid using it I think we should.

I would vote for option 1 here. I find Drop complicates the code and makes it hard to reason about. If we can avoid using it I think we should.

Works for me! I've opened #625.


Explicit Subscription is not being used if user has another subscription selected

After #1405 some users are reporting issues where the -Subscription they pass is being ignored if their AzContext was from a different subscription. We may need to explicitly Select-AzSubscription and/or make sure it's passed to Login-AzAccount if provided.
Workaround is to make sure they have selected the same subscription via Select-AzSubscription, or log out of all contexts and log back in with the right subscription if logged in multiple times.
Ideally, users shouldn't specify this. Just make sure you're logged into the right sub. This was meant for CIs.

I ran into this issue today - see below for list of commands I ran. I sync'ed with Heath offline and the only workaround that worked for me was to run Logout-AzAccount, Login-AzAccount, followed by running New-TestResources.ps1 -ServiceDirectory 'communication' -SubscriptionId <id>. Specifically, I had to explicitly pass subscription Id to the New-TestResources script.
`PS H:\apattath_repo\azure-sdk-for-net> Connect-AzAccount -SubscriptionId '196d0816-62a1-41ca-9035-eb00a9cda249'
Account                  SubscriptionName         TenantId                             Environment

<EMAIL_ADDRESS>Visual Studio Enterprise 59cbf18d-9aa7-4912-b2fd-90e6c930173e AzureCloud
PS H:\apattath_repo\azure-sdk-for-net> .\eng\common\TestResources\New-TestResources.ps1 -ServiceDirectory 'communication' -BaseName 'avinpSdkLiveTestACS'
3:29:10 PM - Detected .NET repository. Defaulting OutFile to true. Test environment settings would be stored into the file so you don't need to set environment variables manually.
3:29:10 PM - TestApplicationId was not specified; loading cached service principal '3fe34569-0714-4dc1-955c-9de53be2a0fa'
3:29:11 PM - Logging into service principal '3fe34569-0714-4dc1-955c-9de53be2a0fa'
WARNING: The access token is from the wrong issuer 'https://sts.windows.net/59cbf18d-9aa7-4912-b2fd-90e6c930173e/'. It must match the tenant 'https://sts.windows.net/72f988bf-86f1-41af-91ab-2d7cd011db47/' associated with this subscription. Please use the authority (URL) 'https://login.windows.net/72f988bf-86f1-41af-91ab-2d7cd011db47' to get the token. Note, if the subscription is transferred to another tenant there is no impact to the services, but information about new tenant could take time to propagate (up to an hour). If you just transferred your subscription and see this error message, please try back later.
WARNING: Attempt 1 failed: Exception calling "Invoke" with "0" argument(s): "The running command stopped because the preference variable "ErrorActionPreference" or common parameter is set to Stop: The provided account 3fe34569-0714-4dc1-955c-9de53be2a0fa does not have access to subscription ID "faa080af-c1d8-40ad-9cce-e1a450ca5b57". Please try logging in with different credentials or a different subscription ID.". Trying again in 10 seconds...`

I'm also testing this as documented using my personal MSA this time as well. I'll test against other subs in our Microsoft tenant as well to simulate other teams who don't have access to our subs for Azure SDK.
/cc @jsquire @benbp


HHH-12320 HHH-12975 - Fix for one-to-one foreign-key constraint name/mode

https://hibernate.atlassian.net/browse/HHH-12320
https://hibernate.atlassian.net/browse/HHH-12975

Looks good to me. I merged it.


Images does not match for the same hash but different sizes

Hello!
For some reason robohash generates different image for a hash when specifying different sizes.
I've tried different sizes and found, that images does not match for 64x and 63x sizes.
Yellow robot:

https://robohash.org/fd7a4c9e24ae4ed592abba0098c18859395aba2d?size=63x63
https://robohash.org/fd7a4c9e24ae4ed592abba0098c18859395aba2d?size=64x64

Purple robot:

https://robohash.org/fd7a4c9e24ae4ed592abba0098c18859395aba2d?size=16x16
https://robohash.org/fd7a4c9e24ae4ed592abba0098c18859395aba2d?size=65x65
https://robohash.org/fd7a4c9e24ae4ed592abba0098c18859395aba2d?size=128x128
... and all other sizes I've tried.

Thank you.

Sorry for the trouble!
It should be fixed for you now, but the solution isn't a clean one.
I believe it happened a bit ago, when I needed to add some additional servers to handle capacity.
It looks like the code that's sorting still isn't relying on filename order - It's using inodes someplace.
When I run the code on new systems, I get a different robot :/
For now, I've removed the offending systems.
I'll try to go back and fix the code sometime soon, but I'm offended looking at it - I did such a bad job of writing this ;)  I really ought to rewrite the whole thing.
But in any event, this should fix the problem for you now, until I can fix it right.

Hi @e1ven!
It's not a problem for me. I just played around the service and found this issue.
Thought it's a good idea to give you feedback at least :)
Great project! Good luck!


Update ViewGraph implementation

Codecov Report
Attention: Patch coverage is 1.60428% with 184 lines in your changes are missing coverage. Please review.

Project coverage is 13.52%. Comparing base (831c079) to head (0710ebb).

Files
Patch %
Lines

Sources/OpenSwiftUI/Core/View/ViewGraph.swift
0.00%
95 Missing :warning:

Sources/OpenSwiftUI/Core/Graph/GraphHost.swift
0.00%
21 Missing :warning:

.../LayoutAdjustments/Direction/LayoutDirection.swift
0.00%
21 Missing :warning:

...enSwiftUI/Data/Environment/CachedEnvironment.swift
0.00%
11 Missing :warning:

...tUI/Layout/LayoutAdjustments/Edge/EdgeInsets.swift
0.00%
10 Missing :warning:

...out/LayoutFundamentals/internal/ProposedSize.swift
30.00%
7 Missing :warning:

...ources/OpenSwiftUI/View/Debug/TODO/ViewDebug.swift
0.00%
7 Missing :warning:

Sources/OpenSwiftUI/Core/Data/VersionSeed.swift
0.00%
4 Missing :warning:

...penSwiftUI/Data/Preference/PreferencesInputs.swift
0.00%
3 Missing :warning:

Sources/OpenSwiftUI/View/View/EmptyView.swift
0.00%
2 Missing :warning:

... and 3 more

Additional details and impacted files
@@            Coverage Diff             @@
##             main      #58      +/-   ##
==========================================
- Coverage   14.04%   13.52%   -0.52%
==========================================
  Files         176      180       +4
  Lines        3859     4029     +170
==========================================
+ Hits          542      545       +3
- Misses       3317     3484     +167

:umbrella: View full report in Codecov by Sentry.
:loudspeaker: Have feedback on the report? Share it here.


Fix Application object in "Rolling out" documentation

As of v0.5.0-alpha.0, the example Application object in the documentation is broken. Now that chart version ranges are a thing (#105), we always try to get the repo's catalog, so Shipper fails with the following:
Event(v1.ObjectReference{Kind:"Application", Namespace:"default", Name:"super-server", UID:"647c0202-34a8-445a-988c-d4968dc23b54", APIVersion:"shipper.booking.com/v1alpha1", ResourceVersion:"3296014", FieldPath:""}): type: 'Warning' reason: 'FailedApplication' failed to fetch chart [name: "nginx", version: "0.0.1", repo: "https://storage.googleapis.com/shipper-demo"]: open /var/folders/1s/cn3tjbw52g14grptc443_k7r75kmg_/T/chart-cache/https-storage.googleapis.com-shipper-demo/index.yaml: no such file or directory

Updated in https://github.com/bookingcom/shipper/pull/182 + added a mock index.yaml to the storage.


Fallback to Unnamed when no name found, fixes #30

Description
Fallback to "Unnamed" as name for stars when NameGen fails
Alternate Designs
Introduce some scientific notation as described in #30 this however seem to much work since this should very rarly happen.
Benefits
No unwanted crashes
Possible Drawbacks
Badly named systems
Applicable Issues
#30

Codecov Report

Merging #36 into master will not change coverage.
The diff coverage is n/a.

@@           Coverage Diff           @@
##           master      #36   +/-   ##
=======================================
  Coverage   97.22%   97.22%
=======================================
  Files           4        4
  Lines         180      180
=======================================
  Hits          175      175
  Misses          5        5

Impacted Files
Coverage Δ

src/astronomicals.rs
100% <ø> (ø)
:arrow_up:

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 6949aa6...5018d91. Read the comment docs.


Have you try other open-source language model as the agent for planning. e.g. Llama3.1

Great work! Have you tried other open-source language models as the agent for planning? e.g. Llama3.1. How about the performance?

I have tried this myself, the llama3.1-8B can generate working planning on some simple examples. I will further try on the visual task in this paper.


artifact-download-url customization

Currently we only support hardcoded support for github artifact download urls, which makes installers gated behind github. We can solve this by just letting the user specify. This should possibly support some amount of templating, as seen in the config for cargo-binstall.
It might also help github enterprise users.
Theoretical usecases:

artifact-download-url = "{repo}/releases/downloads/{tag}/" (what we currently do for github)
artifact-download-url = "https://mywebsite.com/{tag}/"

Exposing things like "app-name" and "app-version" are sketchier with workspaces but we can maybe allow them for single-app workspaces?

interesting. this feels complicated- but complicated is fine if the end users are advanced. can we offer a way for an end user to validate/test what they've set up?

Ideally this would use the same solution we pick for https://github.com/axodotdev/oranda/pull/507