bikes.io.registries

Savers, loaders, and registers for model registries.

  1"""Savers, loaders, and registers for model registries."""
  2
  3# %% IMPORTS
  4
  5import abc
  6import tempfile
  7import typing as T
  8from pathlib import Path
  9
 10import mlflow
 11import pandas as pd
 12import pydantic as pdt
 13from mlflow.pyfunc import PyFuncModel, PythonModel, PythonModelContext
 14
 15from bikes.core import models, schemas
 16from bikes.utils import signers
 17
 18# %% TYPES
 19
 20# Results of model registry operations
 21Info: T.TypeAlias = mlflow.models.model.ModelInfo
 22Alias: T.TypeAlias = mlflow.entities.model_registry.ModelVersion
 23Version: T.TypeAlias = mlflow.entities.model_registry.ModelVersion
 24
 25# %% HELPERS
 26
 27
 28def uri_for_model_alias(name: str, alias: str) -> str:
 29    """Create a model URI from a model name and an alias.
 30
 31    Args:
 32        name (str): name of the mlflow registered model.
 33        alias (str): alias of the registered model.
 34
 35    Returns:
 36        str: model URI as "models:/name@alias".
 37    """
 38    return f"models:/{name}@{alias}"
 39
 40
 41def uri_for_model_version(name: str, version: int) -> str:
 42    """Create a model URI from a model name and a version.
 43
 44    Args:
 45        name (str): name of the mlflow registered model.
 46        version (int): version of the registered model.
 47
 48    Returns:
 49        str: model URI as "models:/name/version."
 50    """
 51    return f"models:/{name}/{version}"
 52
 53
 54def uri_for_model_alias_or_version(name: str, alias_or_version: str | int) -> str:
 55    """Create a model URi from a model name and an alias or version.
 56
 57    Args:
 58        name (str): name of the mlflow registered model.
 59        alias_or_version (str | int): alias or version of the registered model.
 60
 61    Returns:
 62        str: model URI as "models:/name@alias" or "models:/name/version" based on input.
 63    """
 64    if isinstance(alias_or_version, int):
 65        return uri_for_model_version(name=name, version=alias_or_version)
 66    return uri_for_model_alias(name=name, alias=alias_or_version)
 67
 68
 69# %% SAVERS
 70
 71
 72class Saver(abc.ABC, pdt.BaseModel, strict=True, frozen=True, extra="forbid"):
 73    """Base class for saving models in registry.
 74
 75    Separate model definition from serialization.
 76    e.g., to switch between serialization flavors.
 77
 78    Parameters:
 79        path (str): model path inside the Mlflow store.
 80    """
 81
 82    KIND: str
 83
 84    path: str = "model"
 85
 86    @abc.abstractmethod
 87    def save(
 88        self,
 89        model: models.Model,
 90        signature: signers.Signature,
 91        input_example: schemas.Inputs,
 92    ) -> Info:
 93        """Save a model in the model registry.
 94
 95        Args:
 96            model (models.Model): project model to save.
 97            signature (signers.Signature): model signature.
 98            input_example (schemas.Inputs): sample of inputs.
 99
100        Returns:
101            Info: model saving information.
102        """
103
104
105class CustomSaver(Saver):
106    """Saver for project models using the Mlflow PyFunc module.
107
108    Nothing is pickled: the adapter is logged as code (MLflow "models from code"), the
109    model params as its `model_config`, and the fitted internal model as a skops file.
110
111    https://mlflow.org/docs/latest/ml/model/models-from-code/
112    """
113
114    KIND: T.Literal["CustomSaver"] = "CustomSaver"
115
116    # Code file that MLflow copies with the model and runs to rebuild the adapter.
117    CODE: T.ClassVar[Path] = Path(__file__).with_name("pyfunc_model.py")
118
119    class Adapter(PythonModel):  # type: ignore[misc]
120        """Adapt a custom model to the Mlflow PyFunc flavor.
121
122        https://mlflow.org/docs/latest/python_api/mlflow.pyfunc.html?#mlflow.pyfunc.PythonModel
123        """
124
125        model: models.Model
126
127        def load_context(self, context: PythonModelContext) -> None:
128            """Rebuild the project model from its params and its internal model file.
129
130            Args:
131                context (mlflow.PythonModelContext): mlflow context with config and artifacts.
132            """
133            model = pdt.TypeAdapter(models.ModelKind).validate_python(context.model_config)
134            self.model = model.load_internal_model(path=context.artifacts["model"])
135
136        def predict(
137            self,
138            context: PythonModelContext,  # noqa: ARG002  # required by mlflow PythonModel.predict
139            model_input: pd.DataFrame,
140            params: dict[str, T.Any] | None = None,  # noqa: ARG002  # required by mlflow PythonModel.predict
141        ) -> schemas.Outputs:
142            """Generate predictions with a custom model for the given inputs.
143
144            Args:
145                context (mlflow.PythonModelContext): mlflow context.
146                model_input (pd.DataFrame): inputs to validate for the mlflow model.
147                params (dict[str, T.Any] | None): additional parameters.
148
149            Returns:
150                schemas.Outputs: validated outputs of the project model.
151            """
152            # MLflow accepts pandas here and validates the logged signature;
153            # Pandera enforces the project's constraints before the typed model call.
154            inputs = schemas.InputsSchema.check(data=model_input)
155            return self.model.predict(inputs=inputs)
156
157    @T.override
158    def save(
159        self,
160        model: models.Model,
161        signature: signers.Signature,
162        input_example: schemas.Inputs,
163    ) -> Info:
164        with tempfile.TemporaryDirectory() as directory:
165            path = Path(directory) / "model.skops"
166            model.save_internal_model(path=str(path))
167            return mlflow.pyfunc.log_model(
168                name=self.path,
169                python_model=str(self.CODE),
170                artifacts={"model": str(path)},
171                model_config=model.model_dump(),
172                signature=signature,
173                input_example=input_example,
174            )
175
176
177class BuiltinSaver(Saver):
178    """Saver for built-in models using an Mlflow flavor module.
179
180    https://mlflow.org/docs/latest/models.html#built-in-model-flavors
181
182    Parameters:
183        flavor (str): Mlflow flavor module to use for the serialization.
184        kwargs (dict[str, T.Any]): flavor-specific options for `log_model`.
185            e.g., sklearn saves with skops, which refuses to reload tree models
186            unless their types are trusted: `{"skops_trusted_types": [...]}`.
187    """
188
189    KIND: T.Literal["BuiltinSaver"] = "BuiltinSaver"
190
191    flavor: str
192    kwargs: dict[str, T.Any] = {}
193
194    @T.override
195    def save(
196        self,
197        model: models.Model,
198        signature: signers.Signature,
199        input_example: schemas.Inputs,
200    ) -> Info:
201        builtin_model = model.get_internal_model()
202        module = getattr(mlflow, self.flavor)
203        return module.log_model(
204            builtin_model,
205            name=self.path,
206            signature=signature,
207            input_example=input_example,
208            **self.kwargs,
209        )
210
211
212SaverKind = CustomSaver | BuiltinSaver
213
214# %% LOADERS
215
216
217class Loader(abc.ABC, pdt.BaseModel, strict=True, frozen=True, extra="forbid"):
218    """Base class for loading models from registry.
219
220    Separate model definition from deserialization.
221    e.g., to switch between deserialization flavors.
222    """
223
224    KIND: str
225
226    class Adapter(abc.ABC):
227        """Adapt any model for the project inference."""
228
229        @abc.abstractmethod
230        def predict(self, inputs: schemas.Inputs) -> schemas.Outputs:
231            """Generate predictions with the internal model for the given inputs.
232
233            Args:
234                inputs (schemas.Inputs): validated inputs for the project model.
235
236            Returns:
237                schemas.Outputs: validated outputs of the project model.
238            """
239
240    @abc.abstractmethod
241    def load(self, uri: str) -> Loader.Adapter:
242        """Load a model from the model registry.
243
244        Args:
245            uri (str): URI of a model to load.
246
247        Returns:
248            Loader.Adapter: model loaded.
249        """
250
251
252class CustomLoader(Loader):
253    """Loader for custom models using the Mlflow PyFunc module.
254
255    https://mlflow.org/docs/latest/python_api/mlflow.pyfunc.html
256    """
257
258    KIND: T.Literal["CustomLoader"] = "CustomLoader"
259
260    class Adapter(Loader.Adapter):
261        """Adapt a custom model for the project inference."""
262
263        def __init__(self, model: PyFuncModel) -> None:
264            """Initialize the adapter from an mlflow pyfunc model.
265
266            Args:
267                model (PyFuncModel): mlflow pyfunc model.
268            """
269            self.model = model
270
271        @T.override
272        def predict(self, inputs: schemas.Inputs) -> schemas.Outputs:
273            # model validation is already done in predict
274            outputs = self.model.predict(data=inputs)
275            return T.cast(schemas.Outputs, outputs)
276
277    @T.override
278    def load(self, uri: str) -> CustomLoader.Adapter:
279        model = mlflow.pyfunc.load_model(model_uri=uri)
280        return CustomLoader.Adapter(model=model)
281
282
283class BuiltinLoader(Loader):
284    """Loader for built-in models using the Mlflow PyFunc module.
285
286    Note: use Mlflow PyFunc instead of flavors to use standard API.
287
288    https://mlflow.org/docs/latest/models.html#built-in-model-flavors
289    """
290
291    KIND: T.Literal["BuiltinLoader"] = "BuiltinLoader"
292
293    class Adapter(Loader.Adapter):
294        """Adapt a builtin model for the project inference."""
295
296        def __init__(self, model: PyFuncModel) -> None:
297            """Initialize the adapter from an mlflow pyfunc model.
298
299            Args:
300                model (PyFuncModel): mlflow pyfunc model.
301            """
302            self.model = model
303
304        @T.override
305        def predict(self, inputs: schemas.Inputs) -> schemas.Outputs:
306            columns = list(schemas.OutputsSchema.to_schema().columns)
307            outputs = self.model.predict(data=inputs)  # unchecked data!
308            return schemas.Outputs(outputs, columns=columns, index=inputs.index)
309
310    @T.override
311    def load(self, uri: str) -> BuiltinLoader.Adapter:
312        model = mlflow.pyfunc.load_model(model_uri=uri)
313        return BuiltinLoader.Adapter(model=model)
314
315
316LoaderKind = CustomLoader | BuiltinLoader
317
318# %% REGISTERS
319
320
321class Register(abc.ABC, pdt.BaseModel, strict=True, frozen=True, extra="forbid"):
322    """Base class for registring models to a location.
323
324    Separate model definition from its registration.
325    e.g., to change the model registry backend.
326
327    Parameters:
328        tags (dict[str, T.Any]): tags for the model.
329    """
330
331    KIND: str
332
333    tags: dict[str, T.Any] = {}
334
335    @abc.abstractmethod
336    def register(self, name: str, model_uri: str) -> Version:
337        """Register a model given its name and URI.
338
339        Args:
340            name (str): name of the model to register.
341            model_uri (str): URI of a model to register.
342
343        Returns:
344            Version: information about the registered model.
345        """
346
347
348class MlflowRegister(Register):
349    """Register for models in the Mlflow Model Registry.
350
351    https://mlflow.org/docs/latest/model-registry.html
352    """
353
354    KIND: T.Literal["MlflowRegister"] = "MlflowRegister"
355
356    @T.override
357    def register(self, name: str, model_uri: str) -> Version:
358        return mlflow.register_model(name=name, model_uri=model_uri, tags=self.tags)
359
360
361RegisterKind = MlflowRegister
Info: TypeAlias = mlflow.models.model.ModelInfo
Alias: TypeAlias = mlflow.entities.model_registry.model_version.ModelVersion
Version: TypeAlias = mlflow.entities.model_registry.model_version.ModelVersion
def uri_for_model_alias(name: str, alias: str) -> str:
29def uri_for_model_alias(name: str, alias: str) -> str:
30    """Create a model URI from a model name and an alias.
31
32    Args:
33        name (str): name of the mlflow registered model.
34        alias (str): alias of the registered model.
35
36    Returns:
37        str: model URI as "models:/name@alias".
38    """
39    return f"models:/{name}@{alias}"

Create a model URI from a model name and an alias.

Arguments:
  • name (str): name of the mlflow registered model.
  • alias (str): alias of the registered model.
Returns:

str: model URI as "models:/name@alias".

def uri_for_model_version(name: str, version: int) -> str:
42def uri_for_model_version(name: str, version: int) -> str:
43    """Create a model URI from a model name and a version.
44
45    Args:
46        name (str): name of the mlflow registered model.
47        version (int): version of the registered model.
48
49    Returns:
50        str: model URI as "models:/name/version."
51    """
52    return f"models:/{name}/{version}"

Create a model URI from a model name and a version.

Arguments:
  • name (str): name of the mlflow registered model.
  • version (int): version of the registered model.
Returns:

str: model URI as "models:/name/version."

def uri_for_model_alias_or_version(name: str, alias_or_version: str | int) -> str:
55def uri_for_model_alias_or_version(name: str, alias_or_version: str | int) -> str:
56    """Create a model URi from a model name and an alias or version.
57
58    Args:
59        name (str): name of the mlflow registered model.
60        alias_or_version (str | int): alias or version of the registered model.
61
62    Returns:
63        str: model URI as "models:/name@alias" or "models:/name/version" based on input.
64    """
65    if isinstance(alias_or_version, int):
66        return uri_for_model_version(name=name, version=alias_or_version)
67    return uri_for_model_alias(name=name, alias=alias_or_version)

Create a model URi from a model name and an alias or version.

Arguments:
  • name (str): name of the mlflow registered model.
  • alias_or_version (str | int): alias or version of the registered model.
Returns:

str: model URI as "models:/name@alias" or "models:/name/version" based on input.

class Saver(abc.ABC, pydantic.main.BaseModel):
 73class Saver(abc.ABC, pdt.BaseModel, strict=True, frozen=True, extra="forbid"):
 74    """Base class for saving models in registry.
 75
 76    Separate model definition from serialization.
 77    e.g., to switch between serialization flavors.
 78
 79    Parameters:
 80        path (str): model path inside the Mlflow store.
 81    """
 82
 83    KIND: str
 84
 85    path: str = "model"
 86
 87    @abc.abstractmethod
 88    def save(
 89        self,
 90        model: models.Model,
 91        signature: signers.Signature,
 92        input_example: schemas.Inputs,
 93    ) -> Info:
 94        """Save a model in the model registry.
 95
 96        Args:
 97            model (models.Model): project model to save.
 98            signature (signers.Signature): model signature.
 99            input_example (schemas.Inputs): sample of inputs.
100
101        Returns:
102            Info: model saving information.
103        """

Base class for saving models in registry.

Separate model definition from serialization. e.g., to switch between serialization flavors.

Arguments:
  • path (str): model path inside the Mlflow store.
KIND: str = PydanticUndefined
path: str = 'model'
@abc.abstractmethod
def save( self, model: bikes.core.models.Model, signature: mlflow.models.signature.ModelSignature, input_example: pandera.typing.pandas.DataFrame[bikes.core.schemas.InputsSchema]) -> mlflow.models.model.ModelInfo:
 87    @abc.abstractmethod
 88    def save(
 89        self,
 90        model: models.Model,
 91        signature: signers.Signature,
 92        input_example: schemas.Inputs,
 93    ) -> Info:
 94        """Save a model in the model registry.
 95
 96        Args:
 97            model (models.Model): project model to save.
 98            signature (signers.Signature): model signature.
 99            input_example (schemas.Inputs): sample of inputs.
100
101        Returns:
102            Info: model saving information.
103        """

Save a model in the model registry.

Arguments:
  • model (models.Model): project model to save.
  • signature (signers.Signature): model signature.
  • input_example (schemas.Inputs): sample of inputs.
Returns:

Info: model saving information.

class CustomSaver(Saver):
106class CustomSaver(Saver):
107    """Saver for project models using the Mlflow PyFunc module.
108
109    Nothing is pickled: the adapter is logged as code (MLflow "models from code"), the
110    model params as its `model_config`, and the fitted internal model as a skops file.
111
112    https://mlflow.org/docs/latest/ml/model/models-from-code/
113    """
114
115    KIND: T.Literal["CustomSaver"] = "CustomSaver"
116
117    # Code file that MLflow copies with the model and runs to rebuild the adapter.
118    CODE: T.ClassVar[Path] = Path(__file__).with_name("pyfunc_model.py")
119
120    class Adapter(PythonModel):  # type: ignore[misc]
121        """Adapt a custom model to the Mlflow PyFunc flavor.
122
123        https://mlflow.org/docs/latest/python_api/mlflow.pyfunc.html?#mlflow.pyfunc.PythonModel
124        """
125
126        model: models.Model
127
128        def load_context(self, context: PythonModelContext) -> None:
129            """Rebuild the project model from its params and its internal model file.
130
131            Args:
132                context (mlflow.PythonModelContext): mlflow context with config and artifacts.
133            """
134            model = pdt.TypeAdapter(models.ModelKind).validate_python(context.model_config)
135            self.model = model.load_internal_model(path=context.artifacts["model"])
136
137        def predict(
138            self,
139            context: PythonModelContext,  # noqa: ARG002  # required by mlflow PythonModel.predict
140            model_input: pd.DataFrame,
141            params: dict[str, T.Any] | None = None,  # noqa: ARG002  # required by mlflow PythonModel.predict
142        ) -> schemas.Outputs:
143            """Generate predictions with a custom model for the given inputs.
144
145            Args:
146                context (mlflow.PythonModelContext): mlflow context.
147                model_input (pd.DataFrame): inputs to validate for the mlflow model.
148                params (dict[str, T.Any] | None): additional parameters.
149
150            Returns:
151                schemas.Outputs: validated outputs of the project model.
152            """
153            # MLflow accepts pandas here and validates the logged signature;
154            # Pandera enforces the project's constraints before the typed model call.
155            inputs = schemas.InputsSchema.check(data=model_input)
156            return self.model.predict(inputs=inputs)
157
158    @T.override
159    def save(
160        self,
161        model: models.Model,
162        signature: signers.Signature,
163        input_example: schemas.Inputs,
164    ) -> Info:
165        with tempfile.TemporaryDirectory() as directory:
166            path = Path(directory) / "model.skops"
167            model.save_internal_model(path=str(path))
168            return mlflow.pyfunc.log_model(
169                name=self.path,
170                python_model=str(self.CODE),
171                artifacts={"model": str(path)},
172                model_config=model.model_dump(),
173                signature=signature,
174                input_example=input_example,
175            )

Saver for project models using the Mlflow PyFunc module.

Nothing is pickled: the adapter is logged as code (MLflow "models from code"), the model params as its model_config, and the fitted internal model as a skops file.

https://mlflow.org/docs/latest/ml/model/models-from-code/

KIND: Literal['CustomSaver'] = 'CustomSaver'
CODE: ClassVar[pathlib.Path] = PosixPath('/home/runner/work/mlops-python-package/mlops-python-package/src/bikes/io/pyfunc_model.py')
@T.override
def save( self, model: bikes.core.models.Model, signature: mlflow.models.signature.ModelSignature, input_example: pandera.typing.pandas.DataFrame[bikes.core.schemas.InputsSchema]) -> mlflow.models.model.ModelInfo:
158    @T.override
159    def save(
160        self,
161        model: models.Model,
162        signature: signers.Signature,
163        input_example: schemas.Inputs,
164    ) -> Info:
165        with tempfile.TemporaryDirectory() as directory:
166            path = Path(directory) / "model.skops"
167            model.save_internal_model(path=str(path))
168            return mlflow.pyfunc.log_model(
169                name=self.path,
170                python_model=str(self.CODE),
171                artifacts={"model": str(path)},
172                model_config=model.model_dump(),
173                signature=signature,
174                input_example=input_example,
175            )

Save a model in the model registry.

Arguments:
  • model (models.Model): project model to save.
  • signature (signers.Signature): model signature.
  • input_example (schemas.Inputs): sample of inputs.
Returns:

Info: model saving information.

Inherited Members
Saver
path
class CustomSaver.Adapter(mlflow.pyfunc.model.PythonModel):
120    class Adapter(PythonModel):  # type: ignore[misc]
121        """Adapt a custom model to the Mlflow PyFunc flavor.
122
123        https://mlflow.org/docs/latest/python_api/mlflow.pyfunc.html?#mlflow.pyfunc.PythonModel
124        """
125
126        model: models.Model
127
128        def load_context(self, context: PythonModelContext) -> None:
129            """Rebuild the project model from its params and its internal model file.
130
131            Args:
132                context (mlflow.PythonModelContext): mlflow context with config and artifacts.
133            """
134            model = pdt.TypeAdapter(models.ModelKind).validate_python(context.model_config)
135            self.model = model.load_internal_model(path=context.artifacts["model"])
136
137        def predict(
138            self,
139            context: PythonModelContext,  # noqa: ARG002  # required by mlflow PythonModel.predict
140            model_input: pd.DataFrame,
141            params: dict[str, T.Any] | None = None,  # noqa: ARG002  # required by mlflow PythonModel.predict
142        ) -> schemas.Outputs:
143            """Generate predictions with a custom model for the given inputs.
144
145            Args:
146                context (mlflow.PythonModelContext): mlflow context.
147                model_input (pd.DataFrame): inputs to validate for the mlflow model.
148                params (dict[str, T.Any] | None): additional parameters.
149
150            Returns:
151                schemas.Outputs: validated outputs of the project model.
152            """
153            # MLflow accepts pandas here and validates the logged signature;
154            # Pandera enforces the project's constraints before the typed model call.
155            inputs = schemas.InputsSchema.check(data=model_input)
156            return self.model.predict(inputs=inputs)
def load_context(self, context: mlflow.pyfunc.model.PythonModelContext) -> None:
128        def load_context(self, context: PythonModelContext) -> None:
129            """Rebuild the project model from its params and its internal model file.
130
131            Args:
132                context (mlflow.PythonModelContext): mlflow context with config and artifacts.
133            """
134            model = pdt.TypeAdapter(models.ModelKind).validate_python(context.model_config)
135            self.model = model.load_internal_model(path=context.artifacts["model"])

Rebuild the project model from its params and its internal model file.

Arguments:
  • context (mlflow.PythonModelContext): mlflow context with config and artifacts.
def predict( self, context: mlflow.pyfunc.model.PythonModelContext, model_input: pandas.DataFrame, params: dict[str, Any] | None = None) -> pandera.typing.pandas.DataFrame[bikes.core.schemas.OutputsSchema]:
137        def predict(
138            self,
139            context: PythonModelContext,  # noqa: ARG002  # required by mlflow PythonModel.predict
140            model_input: pd.DataFrame,
141            params: dict[str, T.Any] | None = None,  # noqa: ARG002  # required by mlflow PythonModel.predict
142        ) -> schemas.Outputs:
143            """Generate predictions with a custom model for the given inputs.
144
145            Args:
146                context (mlflow.PythonModelContext): mlflow context.
147                model_input (pd.DataFrame): inputs to validate for the mlflow model.
148                params (dict[str, T.Any] | None): additional parameters.
149
150            Returns:
151                schemas.Outputs: validated outputs of the project model.
152            """
153            # MLflow accepts pandas here and validates the logged signature;
154            # Pandera enforces the project's constraints before the typed model call.
155            inputs = schemas.InputsSchema.check(data=model_input)
156            return self.model.predict(inputs=inputs)

Generate predictions with a custom model for the given inputs.

Arguments:
  • context (mlflow.PythonModelContext): mlflow context.
  • model_input (pd.DataFrame): inputs to validate for the mlflow model.
  • params (dict[str, T.Any] | None): additional parameters.
Returns:

schemas.Outputs: validated outputs of the project model.

class BuiltinSaver(Saver):
178class BuiltinSaver(Saver):
179    """Saver for built-in models using an Mlflow flavor module.
180
181    https://mlflow.org/docs/latest/models.html#built-in-model-flavors
182
183    Parameters:
184        flavor (str): Mlflow flavor module to use for the serialization.
185        kwargs (dict[str, T.Any]): flavor-specific options for `log_model`.
186            e.g., sklearn saves with skops, which refuses to reload tree models
187            unless their types are trusted: `{"skops_trusted_types": [...]}`.
188    """
189
190    KIND: T.Literal["BuiltinSaver"] = "BuiltinSaver"
191
192    flavor: str
193    kwargs: dict[str, T.Any] = {}
194
195    @T.override
196    def save(
197        self,
198        model: models.Model,
199        signature: signers.Signature,
200        input_example: schemas.Inputs,
201    ) -> Info:
202        builtin_model = model.get_internal_model()
203        module = getattr(mlflow, self.flavor)
204        return module.log_model(
205            builtin_model,
206            name=self.path,
207            signature=signature,
208            input_example=input_example,
209            **self.kwargs,
210        )

Saver for built-in models using an Mlflow flavor module.

https://mlflow.org/docs/latest/models.html#built-in-model-flavors

Arguments:
  • flavor (str): Mlflow flavor module to use for the serialization.
  • kwargs (dict[str, T.Any]): flavor-specific options for log_model. e.g., sklearn saves with skops, which refuses to reload tree models unless their types are trusted: {"skops_trusted_types": [...]}.
KIND: Literal['BuiltinSaver'] = 'BuiltinSaver'
flavor: str = PydanticUndefined
kwargs: dict[str, typing.Any] = {}
@T.override
def save( self, model: bikes.core.models.Model, signature: mlflow.models.signature.ModelSignature, input_example: pandera.typing.pandas.DataFrame[bikes.core.schemas.InputsSchema]) -> mlflow.models.model.ModelInfo:
195    @T.override
196    def save(
197        self,
198        model: models.Model,
199        signature: signers.Signature,
200        input_example: schemas.Inputs,
201    ) -> Info:
202        builtin_model = model.get_internal_model()
203        module = getattr(mlflow, self.flavor)
204        return module.log_model(
205            builtin_model,
206            name=self.path,
207            signature=signature,
208            input_example=input_example,
209            **self.kwargs,
210        )

Save a model in the model registry.

Arguments:
  • model (models.Model): project model to save.
  • signature (signers.Signature): model signature.
  • input_example (schemas.Inputs): sample of inputs.
Returns:

Info: model saving information.

Inherited Members
Saver
path
SaverKind = CustomSaver | BuiltinSaver
class Loader(abc.ABC, pydantic.main.BaseModel):
218class Loader(abc.ABC, pdt.BaseModel, strict=True, frozen=True, extra="forbid"):
219    """Base class for loading models from registry.
220
221    Separate model definition from deserialization.
222    e.g., to switch between deserialization flavors.
223    """
224
225    KIND: str
226
227    class Adapter(abc.ABC):
228        """Adapt any model for the project inference."""
229
230        @abc.abstractmethod
231        def predict(self, inputs: schemas.Inputs) -> schemas.Outputs:
232            """Generate predictions with the internal model for the given inputs.
233
234            Args:
235                inputs (schemas.Inputs): validated inputs for the project model.
236
237            Returns:
238                schemas.Outputs: validated outputs of the project model.
239            """
240
241    @abc.abstractmethod
242    def load(self, uri: str) -> Loader.Adapter:
243        """Load a model from the model registry.
244
245        Args:
246            uri (str): URI of a model to load.
247
248        Returns:
249            Loader.Adapter: model loaded.
250        """

Base class for loading models from registry.

Separate model definition from deserialization. e.g., to switch between deserialization flavors.

KIND: str = PydanticUndefined
@abc.abstractmethod
def load(self, uri: str) -> Loader.Adapter:
241    @abc.abstractmethod
242    def load(self, uri: str) -> Loader.Adapter:
243        """Load a model from the model registry.
244
245        Args:
246            uri (str): URI of a model to load.
247
248        Returns:
249            Loader.Adapter: model loaded.
250        """

Load a model from the model registry.

Arguments:
  • uri (str): URI of a model to load.
Returns:

Loader.Adapter: model loaded.

class Loader.Adapter(abc.ABC):
227    class Adapter(abc.ABC):
228        """Adapt any model for the project inference."""
229
230        @abc.abstractmethod
231        def predict(self, inputs: schemas.Inputs) -> schemas.Outputs:
232            """Generate predictions with the internal model for the given inputs.
233
234            Args:
235                inputs (schemas.Inputs): validated inputs for the project model.
236
237            Returns:
238                schemas.Outputs: validated outputs of the project model.
239            """

Adapt any model for the project inference.

@abc.abstractmethod
def predict( self, inputs: pandera.typing.pandas.DataFrame[bikes.core.schemas.InputsSchema]) -> pandera.typing.pandas.DataFrame[bikes.core.schemas.OutputsSchema]:
230        @abc.abstractmethod
231        def predict(self, inputs: schemas.Inputs) -> schemas.Outputs:
232            """Generate predictions with the internal model for the given inputs.
233
234            Args:
235                inputs (schemas.Inputs): validated inputs for the project model.
236
237            Returns:
238                schemas.Outputs: validated outputs of the project model.
239            """

Generate predictions with the internal model for the given inputs.

Arguments:
  • inputs (schemas.Inputs): validated inputs for the project model.
Returns:

schemas.Outputs: validated outputs of the project model.

class CustomLoader(Loader):
253class CustomLoader(Loader):
254    """Loader for custom models using the Mlflow PyFunc module.
255
256    https://mlflow.org/docs/latest/python_api/mlflow.pyfunc.html
257    """
258
259    KIND: T.Literal["CustomLoader"] = "CustomLoader"
260
261    class Adapter(Loader.Adapter):
262        """Adapt a custom model for the project inference."""
263
264        def __init__(self, model: PyFuncModel) -> None:
265            """Initialize the adapter from an mlflow pyfunc model.
266
267            Args:
268                model (PyFuncModel): mlflow pyfunc model.
269            """
270            self.model = model
271
272        @T.override
273        def predict(self, inputs: schemas.Inputs) -> schemas.Outputs:
274            # model validation is already done in predict
275            outputs = self.model.predict(data=inputs)
276            return T.cast(schemas.Outputs, outputs)
277
278    @T.override
279    def load(self, uri: str) -> CustomLoader.Adapter:
280        model = mlflow.pyfunc.load_model(model_uri=uri)
281        return CustomLoader.Adapter(model=model)

Loader for custom models using the Mlflow PyFunc module.

https://mlflow.org/docs/latest/python_api/mlflow.pyfunc.html

KIND: Literal['CustomLoader'] = 'CustomLoader'
@T.override
def load(self, uri: str) -> CustomLoader.Adapter:
278    @T.override
279    def load(self, uri: str) -> CustomLoader.Adapter:
280        model = mlflow.pyfunc.load_model(model_uri=uri)
281        return CustomLoader.Adapter(model=model)

Load a model from the model registry.

Arguments:
  • uri (str): URI of a model to load.
Returns:

Loader.Adapter: model loaded.

class CustomLoader.Adapter(Loader.Adapter):
261    class Adapter(Loader.Adapter):
262        """Adapt a custom model for the project inference."""
263
264        def __init__(self, model: PyFuncModel) -> None:
265            """Initialize the adapter from an mlflow pyfunc model.
266
267            Args:
268                model (PyFuncModel): mlflow pyfunc model.
269            """
270            self.model = model
271
272        @T.override
273        def predict(self, inputs: schemas.Inputs) -> schemas.Outputs:
274            # model validation is already done in predict
275            outputs = self.model.predict(data=inputs)
276            return T.cast(schemas.Outputs, outputs)

Adapt a custom model for the project inference.

CustomLoader.Adapter(model: mlflow.pyfunc.PyFuncModel)
264        def __init__(self, model: PyFuncModel) -> None:
265            """Initialize the adapter from an mlflow pyfunc model.
266
267            Args:
268                model (PyFuncModel): mlflow pyfunc model.
269            """
270            self.model = model

Initialize the adapter from an mlflow pyfunc model.

Arguments:
  • model (PyFuncModel): mlflow pyfunc model.
model
@T.override
def predict( self, inputs: pandera.typing.pandas.DataFrame[bikes.core.schemas.InputsSchema]) -> pandera.typing.pandas.DataFrame[bikes.core.schemas.OutputsSchema]:
272        @T.override
273        def predict(self, inputs: schemas.Inputs) -> schemas.Outputs:
274            # model validation is already done in predict
275            outputs = self.model.predict(data=inputs)
276            return T.cast(schemas.Outputs, outputs)

Generate predictions with the internal model for the given inputs.

Arguments:
  • inputs (schemas.Inputs): validated inputs for the project model.
Returns:

schemas.Outputs: validated outputs of the project model.

class BuiltinLoader(Loader):
284class BuiltinLoader(Loader):
285    """Loader for built-in models using the Mlflow PyFunc module.
286
287    Note: use Mlflow PyFunc instead of flavors to use standard API.
288
289    https://mlflow.org/docs/latest/models.html#built-in-model-flavors
290    """
291
292    KIND: T.Literal["BuiltinLoader"] = "BuiltinLoader"
293
294    class Adapter(Loader.Adapter):
295        """Adapt a builtin model for the project inference."""
296
297        def __init__(self, model: PyFuncModel) -> None:
298            """Initialize the adapter from an mlflow pyfunc model.
299
300            Args:
301                model (PyFuncModel): mlflow pyfunc model.
302            """
303            self.model = model
304
305        @T.override
306        def predict(self, inputs: schemas.Inputs) -> schemas.Outputs:
307            columns = list(schemas.OutputsSchema.to_schema().columns)
308            outputs = self.model.predict(data=inputs)  # unchecked data!
309            return schemas.Outputs(outputs, columns=columns, index=inputs.index)
310
311    @T.override
312    def load(self, uri: str) -> BuiltinLoader.Adapter:
313        model = mlflow.pyfunc.load_model(model_uri=uri)
314        return BuiltinLoader.Adapter(model=model)

Loader for built-in models using the Mlflow PyFunc module.

Note: use Mlflow PyFunc instead of flavors to use standard API.

https://mlflow.org/docs/latest/models.html#built-in-model-flavors

KIND: Literal['BuiltinLoader'] = 'BuiltinLoader'
@T.override
def load(self, uri: str) -> BuiltinLoader.Adapter:
311    @T.override
312    def load(self, uri: str) -> BuiltinLoader.Adapter:
313        model = mlflow.pyfunc.load_model(model_uri=uri)
314        return BuiltinLoader.Adapter(model=model)

Load a model from the model registry.

Arguments:
  • uri (str): URI of a model to load.
Returns:

Loader.Adapter: model loaded.

class BuiltinLoader.Adapter(Loader.Adapter):
294    class Adapter(Loader.Adapter):
295        """Adapt a builtin model for the project inference."""
296
297        def __init__(self, model: PyFuncModel) -> None:
298            """Initialize the adapter from an mlflow pyfunc model.
299
300            Args:
301                model (PyFuncModel): mlflow pyfunc model.
302            """
303            self.model = model
304
305        @T.override
306        def predict(self, inputs: schemas.Inputs) -> schemas.Outputs:
307            columns = list(schemas.OutputsSchema.to_schema().columns)
308            outputs = self.model.predict(data=inputs)  # unchecked data!
309            return schemas.Outputs(outputs, columns=columns, index=inputs.index)

Adapt a builtin model for the project inference.

BuiltinLoader.Adapter(model: mlflow.pyfunc.PyFuncModel)
297        def __init__(self, model: PyFuncModel) -> None:
298            """Initialize the adapter from an mlflow pyfunc model.
299
300            Args:
301                model (PyFuncModel): mlflow pyfunc model.
302            """
303            self.model = model

Initialize the adapter from an mlflow pyfunc model.

Arguments:
  • model (PyFuncModel): mlflow pyfunc model.
model
@T.override
def predict( self, inputs: pandera.typing.pandas.DataFrame[bikes.core.schemas.InputsSchema]) -> pandera.typing.pandas.DataFrame[bikes.core.schemas.OutputsSchema]:
305        @T.override
306        def predict(self, inputs: schemas.Inputs) -> schemas.Outputs:
307            columns = list(schemas.OutputsSchema.to_schema().columns)
308            outputs = self.model.predict(data=inputs)  # unchecked data!
309            return schemas.Outputs(outputs, columns=columns, index=inputs.index)

Generate predictions with the internal model for the given inputs.

Arguments:
  • inputs (schemas.Inputs): validated inputs for the project model.
Returns:

schemas.Outputs: validated outputs of the project model.

LoaderKind = CustomLoader | BuiltinLoader
class Register(abc.ABC, pydantic.main.BaseModel):
322class Register(abc.ABC, pdt.BaseModel, strict=True, frozen=True, extra="forbid"):
323    """Base class for registring models to a location.
324
325    Separate model definition from its registration.
326    e.g., to change the model registry backend.
327
328    Parameters:
329        tags (dict[str, T.Any]): tags for the model.
330    """
331
332    KIND: str
333
334    tags: dict[str, T.Any] = {}
335
336    @abc.abstractmethod
337    def register(self, name: str, model_uri: str) -> Version:
338        """Register a model given its name and URI.
339
340        Args:
341            name (str): name of the model to register.
342            model_uri (str): URI of a model to register.
343
344        Returns:
345            Version: information about the registered model.
346        """

Base class for registring models to a location.

Separate model definition from its registration. e.g., to change the model registry backend.

Arguments:
  • tags (dict[str, T.Any]): tags for the model.
KIND: str = PydanticUndefined
tags: dict[str, typing.Any] = {}
@abc.abstractmethod
def register( self, name: str, model_uri: str) -> mlflow.entities.model_registry.model_version.ModelVersion:
336    @abc.abstractmethod
337    def register(self, name: str, model_uri: str) -> Version:
338        """Register a model given its name and URI.
339
340        Args:
341            name (str): name of the model to register.
342            model_uri (str): URI of a model to register.
343
344        Returns:
345            Version: information about the registered model.
346        """

Register a model given its name and URI.

Arguments:
  • name (str): name of the model to register.
  • model_uri (str): URI of a model to register.
Returns:

Version: information about the registered model.

class MlflowRegister(Register):
349class MlflowRegister(Register):
350    """Register for models in the Mlflow Model Registry.
351
352    https://mlflow.org/docs/latest/model-registry.html
353    """
354
355    KIND: T.Literal["MlflowRegister"] = "MlflowRegister"
356
357    @T.override
358    def register(self, name: str, model_uri: str) -> Version:
359        return mlflow.register_model(name=name, model_uri=model_uri, tags=self.tags)

Register for models in the Mlflow Model Registry.

https://mlflow.org/docs/latest/model-registry.html

KIND: Literal['MlflowRegister'] = 'MlflowRegister'
@T.override
def register( self, name: str, model_uri: str) -> mlflow.entities.model_registry.model_version.ModelVersion:
357    @T.override
358    def register(self, name: str, model_uri: str) -> Version:
359        return mlflow.register_model(name=name, model_uri=model_uri, tags=self.tags)

Register a model given its name and URI.

Arguments:
  • name (str): name of the model to register.
  • model_uri (str): URI of a model to register.
Returns:

Version: information about the registered model.

Inherited Members
Register
tags
RegisterKind = <class 'MlflowRegister'>