nextmv 0.26.3__py3-none-any.whl → 0.28.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
nextmv/model.py CHANGED
@@ -1,3 +1,20 @@
1
+ """
2
+ Model module for creating and saving decision models in Nextmv Cloud.
3
+
4
+ This module provides the base classes and functionality for creating decision models
5
+ that can be deployed and run in Nextmv Cloud. The main components are:
6
+
7
+ Classes
8
+ -------
9
+ Model
10
+ Base class for defining decision models.
11
+ ModelConfiguration
12
+ Configuration for packaging and deploying models.
13
+
14
+ Models defined using this module can be packaged with their dependencies and
15
+ deployed to Nextmv Cloud for execution.
16
+ """
17
+
1
18
  import logging
2
19
  import os
3
20
  import shutil
@@ -13,10 +30,44 @@ from nextmv.output import Output
13
30
  # The following block of code is used to suppress warnings from mlflow. We
14
31
  # suppress these warnings because they are not relevant to the user, and they
15
32
  # are not actionable.
16
- original_showwarning = warnings.showwarning
17
33
 
34
+ """
35
+ Module-level function and variable to suppress warnings from mlflow.
36
+ """
18
37
 
19
- def custom_showwarning(message, category, filename, lineno, file=None, line=None):
38
+ _original_showwarning = warnings.showwarning
39
+ """Original showwarning function from the warnings module."""
40
+
41
+
42
+ def _custom_showwarning(message, category, filename, lineno, file=None, line=None):
43
+ """
44
+ Custom warning handler that suppresses specific mlflow warnings.
45
+
46
+ This function filters out non-actionable warnings from the mlflow library
47
+ to keep the console output clean and relevant for the user.
48
+
49
+ Parameters
50
+ ----------
51
+ message : str
52
+ The warning message.
53
+ category : Warning
54
+ The warning category.
55
+ filename : str
56
+ The filename where the warning was raised.
57
+ lineno : int
58
+ The line number where the warning was raised.
59
+ file : file, optional
60
+ The file to write the warning to.
61
+ line : str, optional
62
+ The line of source code to be included in the warning message.
63
+
64
+ Returns
65
+ -------
66
+ None
67
+ If the warning matches certain patterns, the function returns early
68
+ without showing the warning. Otherwise, it delegates to the original
69
+ warning handler.
70
+ """
20
71
  # .../site-packages/mlflow/pyfunc/utils/data_validation.py:134: UserWarning:Add
21
72
  # type hints to the `predict` method to enable data validation and automatic
22
73
  # signature inference during model logging. Check
@@ -33,10 +84,10 @@ def custom_showwarning(message, category, filename, lineno, file=None, line=None
33
84
  if "mlflow/pyfunc/__init__.py" in filename:
34
85
  return
35
86
 
36
- original_showwarning(message, category, filename, lineno, file, line)
87
+ _original_showwarning(message, category, filename, lineno, file, line)
37
88
 
38
89
 
39
- warnings.showwarning = custom_showwarning
90
+ warnings.showwarning = _custom_showwarning
40
91
 
41
92
  # When working with the `Model`, we expect to be working in a notebook
42
93
  # environment, and not interact with the local filesystem a lot. We use the
@@ -47,29 +98,49 @@ warnings.showwarning = custom_showwarning
47
98
  # the model requires and that we install and bundle with the app.
48
99
  _REQUIREMENTS_FILE = "model_requirements.txt"
49
100
 
50
- # When working in a notebook environment, we dont really create a `main.py`
101
+ # When working in a notebook environment, we don't really create a `main.py`
51
102
  # file with the main entrypoint of the program. Because the logic is mostly
52
103
  # encoded inside the `Model` class, we need to create a `main.py` file that we
53
104
  # can run in Nextmv Cloud. This file is used as that entrypoint.
54
105
  _ENTRYPOINT_FILE = "__entrypoint__.py"
55
106
 
107
+
108
+ # Required mlflow dependency version for model packaging.
56
109
  _MLFLOW_DEPENDENCY = "mlflow>=2.18.0"
57
110
 
58
111
 
59
112
  @dataclass
60
113
  class ModelConfiguration:
61
114
  """
62
- ModelConfiguration is a class that holds the configuration for a
63
- model. It is used to define how a Python model is encoded and loaded.
115
+ Configuration class for Nextmv models.
64
116
 
65
- The `name` is required, and should be a personalized name for the model.
117
+ You can import the `ModelConfiguration` class directly from `nextmv`:
66
118
 
67
- You may specify the `requirements` that your decision model requires. This
68
- is done by passing a list of requirements, as if they were lines in a
69
- `requirements.txt` file. An example of this is `["nextmv==0.1.0"]`.
70
-
71
- Lastly, if your decision model requires options, you may specify them by
72
- passing an instance of `Options`.
119
+ ```python
120
+ from nextmv import ModelConfiguration
121
+ ```
122
+
123
+ This class holds the configuration for a model, defining how a Python model
124
+ is encoded and loaded for use in Nextmv Cloud.
125
+
126
+ Parameters
127
+ ----------
128
+ name : str
129
+ A personalized name for the model. This is required.
130
+ requirements : list[str], optional
131
+ A list of Python dependencies that the decision model requires,
132
+ formatted as they would appear in a requirements.txt file.
133
+ options : Options, optional
134
+ Options that the decision model requires.
135
+
136
+ Examples
137
+ --------
138
+ >>> from nextmv import ModelConfiguration, Options
139
+ >>> config = ModelConfiguration(
140
+ ... name="my_routing_model",
141
+ ... requirements=["nextroute>=1.0.0"],
142
+ ... options=Options({"max_time": 60})
143
+ ... )
73
144
  """
74
145
 
75
146
  name: str
@@ -83,41 +154,50 @@ class ModelConfiguration:
83
154
 
84
155
  class Model:
85
156
  """
86
- Model is the base class for defining a decision model that runs in Nextmv
87
- Cloud. You must create a subclass of this class and implement the `solve`
88
- method. The `solve` method is the main entry point of your model and should
89
- return an output with a solution (decision).
157
+ Base class for defining decision models that run in Nextmv Cloud.
90
158
 
91
- Example
92
- -------
93
- ```python
94
- import nextroute
95
-
96
- import nextmv
159
+ You can import the `Model` class directly from `nextmv`:
97
160
 
161
+ ```python
162
+ from nextmv import Model
163
+ ```
98
164
 
99
- # Define the model that makes decisions. This model uses the Nextroute library
100
- # to solve a routing problem.
101
- class DecisionModel(nextmv.Model):
102
- def solve(self, input: nextmv.Input) -> nextmv.Output:
103
- nextroute_input = nextroute.schema.Input.from_dict(input.data)
104
- nextroute_options = nextroute.Options.extract_from_dict(input.options.to_dict())
105
- nextroute_output = nextroute.solve(nextroute_input, nextroute_options)
165
+ This class serves as a foundation for creating decision models that can be
166
+ deployed to Nextmv Cloud. Subclasses must implement the `solve` method,
167
+ which is the main entry point for processing inputs and producing decisions.
106
168
 
107
- return nextmv.Output(
108
- options=input.options,
109
- solution=nextroute_output.solutions[0].to_dict(),
110
- statistics=nextroute_output.statistics.to_dict(),
111
- )
112
- ```
169
+ Methods
170
+ -------
171
+ solve(input)
172
+ Process input data and produce a decision output.
173
+ save(model_dir, configuration)
174
+ Save the model to the filesystem for deployment.
175
+
176
+ Examples
177
+ --------
178
+ >>> import nextroute
179
+ >>> import nextmv
180
+ >>>
181
+ >>> class DecisionModel(nextmv.Model):
182
+ ... def solve(self, input: nextmv.Input) -> nextmv.Output:
183
+ ... nextroute_input = nextroute.schema.Input.from_dict(input.data)
184
+ ... nextroute_options = nextroute.Options.extract_from_dict(input.options.to_dict())
185
+ ... nextroute_output = nextroute.solve(nextroute_input, nextroute_options)
186
+ ...
187
+ ... return nextmv.Output(
188
+ ... options=input.options,
189
+ ... solution=nextroute_output.solutions[0].to_dict(),
190
+ ... statistics=nextroute_output.statistics.to_dict(),
191
+ ... )
113
192
  """
114
193
 
115
194
  def solve(self, input: Input) -> Output:
116
195
  """
117
- The `solve` method is the main entry point of your model. You must
118
- implement this method yourself. It receives a `nextmv.Input` and should
119
- process it to produce a `nextmv.Output`, which is the solution to the
120
- decision model/problem.
196
+ Process input data and produce a decision output.
197
+
198
+ This is the main entry point of your model that you must implement in
199
+ subclasses. It receives input data and should process it to produce an
200
+ output containing the solution to the decision problem.
121
201
 
122
202
  Parameters
123
203
  ----------
@@ -129,26 +209,66 @@ class Model:
129
209
  Output
130
210
  The output of the model, which is the solution to the decision
131
211
  model/problem.
212
+
213
+ Raises
214
+ ------
215
+ NotImplementedError
216
+ When called on the base Model class, as this method must be
217
+ implemented by subclasses.
218
+
219
+ Examples
220
+ --------
221
+ >>> def solve(self, input: Input) -> Output:
222
+ ... # Process input data
223
+ ... result = self._process_data(input.data)
224
+ ...
225
+ ... # Return formatted output
226
+ ... return Output(
227
+ ... options=input.options,
228
+ ... solution=result,
229
+ ... statistics={"processing_time": 0.5}
230
+ ... )
132
231
  """
133
232
 
134
233
  raise NotImplementedError
135
234
 
136
235
  def save(model_self, model_dir: str, configuration: ModelConfiguration) -> None:
137
236
  """
138
- Save the model to the local filesystem, in the location given by `dir`.
139
- The model is saved according to the configuration provided, which is of
140
- type `ModelConfiguration`.
237
+ Save the model to the local filesystem for deployment.
238
+
239
+ This method packages the model according to the provided configuration,
240
+ creating all necessary files and dependencies for deployment to Nextmv
241
+ Cloud.
141
242
 
142
243
  Parameters
143
244
  ----------
144
- dir : str
245
+ model_dir : str
145
246
  The directory where the model will be saved.
146
247
  configuration : ModelConfiguration
147
248
  The configuration of the model, which defines how the model is
148
249
  saved and loaded.
250
+
251
+ Raises
252
+ ------
253
+ ImportError
254
+ If mlflow is not installed, which is required for model packaging.
255
+
256
+ Notes
257
+ -----
258
+ This method uses mlflow for model packaging, creating the necessary
259
+ files and directory structure for deployment.
260
+
261
+ Examples
262
+ --------
263
+ >>> model = MyDecisionModel()
264
+ >>> config = ModelConfiguration(
265
+ ... name="routing_model",
266
+ ... requirements=["pandas", "numpy"]
267
+ ... )
268
+ >>> model.save("/tmp/my_model", config)
149
269
  """
150
270
 
151
- # mlflow is a big package. We dont want to make it a dependency of
271
+ # mlflow is a big package. We don't want to make it a dependency of
152
272
  # `nextmv` because it is not always needed. We only need it if we are
153
273
  # working with the "app from model" logic, which involves working with
154
274
  # this `Model` class.
@@ -165,12 +285,15 @@ class Model:
165
285
 
166
286
  class MLFlowModel(PythonModel):
167
287
  """
168
- The `MLFlowModel` class exists as a transient class to translate a
169
- Nextmv `DecisionModel` into an `mlflow.pyfunc.PythonModel`. This
170
- class must comply with the inference API of mlflow, which is why it
171
- has a `predict` method. The translation happens by having this
172
- `predict` method call the user-defined `solve` method of the
173
- `DecisionModel`.
288
+ Transient class to translate a Nextmv Decision Model into an MLflow PythonModel.
289
+
290
+ This class complies with the MLflow inference API, implementing a `predict`
291
+ method that calls the user-defined `solve` method of the Nextmv Decision Model.
292
+
293
+ Methods
294
+ -------
295
+ predict(context, model_input, params)
296
+ MLflow-compliant predict method that delegates to the Nextmv model's solve method.
174
297
  """
175
298
 
176
299
  def predict(
@@ -180,11 +303,28 @@ class Model:
180
303
  params: Optional[dict[str, Any]] = None,
181
304
  ) -> Any:
182
305
  """
183
- The predict method allows us to work with mlflow’s [python_function]
184
- model flavor. Warning: This method should not be used or overridden
185
- directly. Instead, you should implement the `solve` method.
186
-
187
- [python_function]: https://mlflow.org/docs/latest/python_api/mlflow.pyfunc.html
306
+ MLflow-compliant prediction method that calls the Nextmv model's solve method.
307
+
308
+ This method enables compatibility with MLflow's python_function model flavor.
309
+
310
+ Parameters
311
+ ----------
312
+ context : mlflow.pyfunc.PythonModelContext
313
+ The MLflow model context.
314
+ model_input : Any
315
+ The input data for prediction, passed to the solve method.
316
+ params : Optional[dict[str, Any]], optional
317
+ Additional parameters for prediction.
318
+
319
+ Returns
320
+ -------
321
+ Any
322
+ The result from the Nextmv model's solve method.
323
+
324
+ Notes
325
+ -----
326
+ This method should not be used or overridden directly. Instead,
327
+ implement the `solve` method in your Nextmv Model subclass.
188
328
  """
189
329
 
190
330
  return model_self.solve(model_input)
@@ -231,7 +371,34 @@ def _cleanup_python_model(
231
371
  model_configuration: Optional[ModelConfiguration] = None,
232
372
  verbose: bool = False,
233
373
  ) -> None:
234
- """Cleans up the Python-specific model packaging logic."""
374
+ """
375
+ Clean up Python-specific model packaging artifacts.
376
+
377
+ This function removes temporary files and directories created during the
378
+ model packaging process.
379
+
380
+ Parameters
381
+ ----------
382
+ model_dir : str
383
+ The directory where the model was saved.
384
+ model_configuration : Optional[ModelConfiguration], optional
385
+ The configuration of the model. If None, the function returns early.
386
+ verbose : bool, default=False
387
+ If True, log a message when cleanup is complete.
388
+
389
+ Returns
390
+ -------
391
+ None
392
+ This function does not return anything.
393
+
394
+ Notes
395
+ -----
396
+ Files and directories removed include:
397
+ - The model directory itself
398
+ - The mlruns directory created by MLflow
399
+ - The requirements file
400
+ - The main.py file
401
+ """
235
402
 
236
403
  if model_configuration is None:
237
404
  return