mlrun 1.7.2rc4__py3-none-any.whl → 1.8.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.

Potentially problematic release.


This version of mlrun might be problematic. Click here for more details.

Files changed (275) hide show
  1. mlrun/__init__.py +26 -22
  2. mlrun/__main__.py +15 -16
  3. mlrun/alerts/alert.py +150 -15
  4. mlrun/api/schemas/__init__.py +1 -9
  5. mlrun/artifacts/__init__.py +2 -3
  6. mlrun/artifacts/base.py +62 -19
  7. mlrun/artifacts/dataset.py +17 -17
  8. mlrun/artifacts/document.py +454 -0
  9. mlrun/artifacts/manager.py +28 -18
  10. mlrun/artifacts/model.py +91 -59
  11. mlrun/artifacts/plots.py +2 -2
  12. mlrun/common/constants.py +8 -0
  13. mlrun/common/formatters/__init__.py +1 -0
  14. mlrun/common/formatters/artifact.py +1 -1
  15. mlrun/common/formatters/feature_set.py +2 -0
  16. mlrun/common/formatters/function.py +1 -0
  17. mlrun/{model_monitoring/db/stores/v3io_kv/__init__.py → common/formatters/model_endpoint.py} +17 -0
  18. mlrun/common/formatters/pipeline.py +1 -2
  19. mlrun/common/formatters/project.py +9 -0
  20. mlrun/common/model_monitoring/__init__.py +0 -5
  21. mlrun/common/model_monitoring/helpers.py +12 -62
  22. mlrun/common/runtimes/constants.py +25 -4
  23. mlrun/common/schemas/__init__.py +9 -5
  24. mlrun/common/schemas/alert.py +114 -19
  25. mlrun/common/schemas/api_gateway.py +3 -3
  26. mlrun/common/schemas/artifact.py +22 -9
  27. mlrun/common/schemas/auth.py +8 -4
  28. mlrun/common/schemas/background_task.py +7 -7
  29. mlrun/common/schemas/client_spec.py +4 -4
  30. mlrun/common/schemas/clusterization_spec.py +2 -2
  31. mlrun/common/schemas/common.py +53 -3
  32. mlrun/common/schemas/constants.py +15 -0
  33. mlrun/common/schemas/datastore_profile.py +1 -1
  34. mlrun/common/schemas/feature_store.py +9 -9
  35. mlrun/common/schemas/frontend_spec.py +4 -4
  36. mlrun/common/schemas/function.py +10 -10
  37. mlrun/common/schemas/hub.py +1 -1
  38. mlrun/common/schemas/k8s.py +3 -3
  39. mlrun/common/schemas/memory_reports.py +3 -3
  40. mlrun/common/schemas/model_monitoring/__init__.py +4 -8
  41. mlrun/common/schemas/model_monitoring/constants.py +127 -46
  42. mlrun/common/schemas/model_monitoring/grafana.py +18 -12
  43. mlrun/common/schemas/model_monitoring/model_endpoints.py +154 -160
  44. mlrun/common/schemas/notification.py +24 -3
  45. mlrun/common/schemas/object.py +1 -1
  46. mlrun/common/schemas/pagination.py +4 -4
  47. mlrun/common/schemas/partition.py +142 -0
  48. mlrun/common/schemas/pipeline.py +3 -3
  49. mlrun/common/schemas/project.py +26 -18
  50. mlrun/common/schemas/runs.py +3 -3
  51. mlrun/common/schemas/runtime_resource.py +5 -5
  52. mlrun/common/schemas/schedule.py +1 -1
  53. mlrun/common/schemas/secret.py +1 -1
  54. mlrun/{model_monitoring/db/stores/sqldb/__init__.py → common/schemas/serving.py} +10 -1
  55. mlrun/common/schemas/tag.py +3 -3
  56. mlrun/common/schemas/workflow.py +6 -5
  57. mlrun/common/types.py +1 -0
  58. mlrun/config.py +157 -89
  59. mlrun/data_types/__init__.py +5 -3
  60. mlrun/data_types/infer.py +13 -3
  61. mlrun/data_types/spark.py +2 -1
  62. mlrun/datastore/__init__.py +59 -18
  63. mlrun/datastore/alibaba_oss.py +4 -1
  64. mlrun/datastore/azure_blob.py +4 -1
  65. mlrun/datastore/base.py +19 -24
  66. mlrun/datastore/datastore.py +10 -4
  67. mlrun/datastore/datastore_profile.py +178 -45
  68. mlrun/datastore/dbfs_store.py +4 -1
  69. mlrun/datastore/filestore.py +4 -1
  70. mlrun/datastore/google_cloud_storage.py +4 -1
  71. mlrun/datastore/hdfs.py +4 -1
  72. mlrun/datastore/inmem.py +4 -1
  73. mlrun/datastore/redis.py +4 -1
  74. mlrun/datastore/s3.py +14 -3
  75. mlrun/datastore/sources.py +89 -92
  76. mlrun/datastore/store_resources.py +7 -4
  77. mlrun/datastore/storeytargets.py +51 -16
  78. mlrun/datastore/targets.py +38 -31
  79. mlrun/datastore/utils.py +87 -4
  80. mlrun/datastore/v3io.py +4 -1
  81. mlrun/datastore/vectorstore.py +291 -0
  82. mlrun/datastore/wasbfs/fs.py +13 -12
  83. mlrun/db/base.py +286 -100
  84. mlrun/db/httpdb.py +1562 -490
  85. mlrun/db/nopdb.py +250 -83
  86. mlrun/errors.py +6 -2
  87. mlrun/execution.py +194 -50
  88. mlrun/feature_store/__init__.py +2 -10
  89. mlrun/feature_store/api.py +20 -458
  90. mlrun/feature_store/common.py +9 -9
  91. mlrun/feature_store/feature_set.py +20 -18
  92. mlrun/feature_store/feature_vector.py +105 -479
  93. mlrun/feature_store/feature_vector_utils.py +466 -0
  94. mlrun/feature_store/retrieval/base.py +15 -11
  95. mlrun/feature_store/retrieval/job.py +2 -1
  96. mlrun/feature_store/retrieval/storey_merger.py +1 -1
  97. mlrun/feature_store/steps.py +3 -3
  98. mlrun/features.py +30 -13
  99. mlrun/frameworks/__init__.py +1 -2
  100. mlrun/frameworks/_common/__init__.py +1 -2
  101. mlrun/frameworks/_common/artifacts_library.py +2 -2
  102. mlrun/frameworks/_common/mlrun_interface.py +10 -6
  103. mlrun/frameworks/_common/model_handler.py +31 -31
  104. mlrun/frameworks/_common/producer.py +3 -1
  105. mlrun/frameworks/_dl_common/__init__.py +1 -2
  106. mlrun/frameworks/_dl_common/loggers/__init__.py +1 -2
  107. mlrun/frameworks/_dl_common/loggers/mlrun_logger.py +4 -4
  108. mlrun/frameworks/_dl_common/loggers/tensorboard_logger.py +3 -3
  109. mlrun/frameworks/_ml_common/__init__.py +1 -2
  110. mlrun/frameworks/_ml_common/loggers/__init__.py +1 -2
  111. mlrun/frameworks/_ml_common/model_handler.py +21 -21
  112. mlrun/frameworks/_ml_common/plans/__init__.py +1 -2
  113. mlrun/frameworks/_ml_common/plans/confusion_matrix_plan.py +3 -1
  114. mlrun/frameworks/_ml_common/plans/dataset_plan.py +3 -3
  115. mlrun/frameworks/_ml_common/plans/roc_curve_plan.py +4 -4
  116. mlrun/frameworks/auto_mlrun/__init__.py +1 -2
  117. mlrun/frameworks/auto_mlrun/auto_mlrun.py +22 -15
  118. mlrun/frameworks/huggingface/__init__.py +1 -2
  119. mlrun/frameworks/huggingface/model_server.py +9 -9
  120. mlrun/frameworks/lgbm/__init__.py +47 -44
  121. mlrun/frameworks/lgbm/callbacks/__init__.py +1 -2
  122. mlrun/frameworks/lgbm/callbacks/logging_callback.py +4 -2
  123. mlrun/frameworks/lgbm/callbacks/mlrun_logging_callback.py +4 -2
  124. mlrun/frameworks/lgbm/mlrun_interfaces/__init__.py +1 -2
  125. mlrun/frameworks/lgbm/mlrun_interfaces/mlrun_interface.py +5 -5
  126. mlrun/frameworks/lgbm/model_handler.py +15 -11
  127. mlrun/frameworks/lgbm/model_server.py +11 -7
  128. mlrun/frameworks/lgbm/utils.py +2 -2
  129. mlrun/frameworks/onnx/__init__.py +1 -2
  130. mlrun/frameworks/onnx/dataset.py +3 -3
  131. mlrun/frameworks/onnx/mlrun_interface.py +2 -2
  132. mlrun/frameworks/onnx/model_handler.py +7 -5
  133. mlrun/frameworks/onnx/model_server.py +8 -6
  134. mlrun/frameworks/parallel_coordinates.py +11 -11
  135. mlrun/frameworks/pytorch/__init__.py +22 -23
  136. mlrun/frameworks/pytorch/callbacks/__init__.py +1 -2
  137. mlrun/frameworks/pytorch/callbacks/callback.py +2 -1
  138. mlrun/frameworks/pytorch/callbacks/logging_callback.py +15 -8
  139. mlrun/frameworks/pytorch/callbacks/mlrun_logging_callback.py +19 -12
  140. mlrun/frameworks/pytorch/callbacks/tensorboard_logging_callback.py +22 -15
  141. mlrun/frameworks/pytorch/callbacks_handler.py +36 -30
  142. mlrun/frameworks/pytorch/mlrun_interface.py +17 -17
  143. mlrun/frameworks/pytorch/model_handler.py +21 -17
  144. mlrun/frameworks/pytorch/model_server.py +13 -9
  145. mlrun/frameworks/sklearn/__init__.py +19 -18
  146. mlrun/frameworks/sklearn/estimator.py +2 -2
  147. mlrun/frameworks/sklearn/metric.py +3 -3
  148. mlrun/frameworks/sklearn/metrics_library.py +8 -6
  149. mlrun/frameworks/sklearn/mlrun_interface.py +3 -2
  150. mlrun/frameworks/sklearn/model_handler.py +4 -3
  151. mlrun/frameworks/tf_keras/__init__.py +11 -12
  152. mlrun/frameworks/tf_keras/callbacks/__init__.py +1 -2
  153. mlrun/frameworks/tf_keras/callbacks/logging_callback.py +17 -14
  154. mlrun/frameworks/tf_keras/callbacks/mlrun_logging_callback.py +15 -12
  155. mlrun/frameworks/tf_keras/callbacks/tensorboard_logging_callback.py +21 -18
  156. mlrun/frameworks/tf_keras/model_handler.py +17 -13
  157. mlrun/frameworks/tf_keras/model_server.py +12 -8
  158. mlrun/frameworks/xgboost/__init__.py +19 -18
  159. mlrun/frameworks/xgboost/model_handler.py +13 -9
  160. mlrun/k8s_utils.py +2 -5
  161. mlrun/launcher/base.py +3 -4
  162. mlrun/launcher/client.py +2 -2
  163. mlrun/launcher/local.py +6 -2
  164. mlrun/launcher/remote.py +1 -1
  165. mlrun/lists.py +8 -4
  166. mlrun/model.py +132 -46
  167. mlrun/model_monitoring/__init__.py +3 -5
  168. mlrun/model_monitoring/api.py +113 -98
  169. mlrun/model_monitoring/applications/__init__.py +0 -5
  170. mlrun/model_monitoring/applications/_application_steps.py +81 -50
  171. mlrun/model_monitoring/applications/base.py +467 -14
  172. mlrun/model_monitoring/applications/context.py +212 -134
  173. mlrun/model_monitoring/{db/stores/base → applications/evidently}/__init__.py +6 -2
  174. mlrun/model_monitoring/applications/evidently/base.py +146 -0
  175. mlrun/model_monitoring/applications/histogram_data_drift.py +89 -56
  176. mlrun/model_monitoring/applications/results.py +67 -15
  177. mlrun/model_monitoring/controller.py +701 -315
  178. mlrun/model_monitoring/db/__init__.py +0 -2
  179. mlrun/model_monitoring/db/_schedules.py +242 -0
  180. mlrun/model_monitoring/db/_stats.py +189 -0
  181. mlrun/model_monitoring/db/tsdb/__init__.py +33 -22
  182. mlrun/model_monitoring/db/tsdb/base.py +243 -49
  183. mlrun/model_monitoring/db/tsdb/tdengine/schemas.py +76 -36
  184. mlrun/model_monitoring/db/tsdb/tdengine/stream_graph_steps.py +33 -0
  185. mlrun/model_monitoring/db/tsdb/tdengine/tdengine_connection.py +213 -0
  186. mlrun/model_monitoring/db/tsdb/tdengine/tdengine_connector.py +534 -88
  187. mlrun/model_monitoring/db/tsdb/v3io/stream_graph_steps.py +1 -0
  188. mlrun/model_monitoring/db/tsdb/v3io/v3io_connector.py +436 -106
  189. mlrun/model_monitoring/helpers.py +356 -114
  190. mlrun/model_monitoring/stream_processing.py +190 -345
  191. mlrun/model_monitoring/tracking_policy.py +11 -4
  192. mlrun/model_monitoring/writer.py +49 -90
  193. mlrun/package/__init__.py +3 -6
  194. mlrun/package/context_handler.py +2 -2
  195. mlrun/package/packager.py +12 -9
  196. mlrun/package/packagers/__init__.py +0 -2
  197. mlrun/package/packagers/default_packager.py +14 -11
  198. mlrun/package/packagers/numpy_packagers.py +16 -7
  199. mlrun/package/packagers/pandas_packagers.py +18 -18
  200. mlrun/package/packagers/python_standard_library_packagers.py +25 -11
  201. mlrun/package/packagers_manager.py +35 -32
  202. mlrun/package/utils/__init__.py +0 -3
  203. mlrun/package/utils/_pickler.py +6 -6
  204. mlrun/platforms/__init__.py +47 -16
  205. mlrun/platforms/iguazio.py +4 -1
  206. mlrun/projects/operations.py +30 -30
  207. mlrun/projects/pipelines.py +116 -47
  208. mlrun/projects/project.py +1292 -329
  209. mlrun/render.py +5 -9
  210. mlrun/run.py +57 -14
  211. mlrun/runtimes/__init__.py +1 -3
  212. mlrun/runtimes/base.py +30 -22
  213. mlrun/runtimes/daskjob.py +9 -9
  214. mlrun/runtimes/databricks_job/databricks_runtime.py +6 -5
  215. mlrun/runtimes/function_reference.py +5 -2
  216. mlrun/runtimes/generators.py +3 -2
  217. mlrun/runtimes/kubejob.py +6 -7
  218. mlrun/runtimes/mounts.py +574 -0
  219. mlrun/runtimes/mpijob/__init__.py +0 -2
  220. mlrun/runtimes/mpijob/abstract.py +7 -6
  221. mlrun/runtimes/nuclio/api_gateway.py +7 -7
  222. mlrun/runtimes/nuclio/application/application.py +11 -13
  223. mlrun/runtimes/nuclio/application/reverse_proxy.go +66 -64
  224. mlrun/runtimes/nuclio/function.py +127 -70
  225. mlrun/runtimes/nuclio/serving.py +105 -37
  226. mlrun/runtimes/pod.py +159 -54
  227. mlrun/runtimes/remotesparkjob.py +3 -2
  228. mlrun/runtimes/sparkjob/__init__.py +0 -2
  229. mlrun/runtimes/sparkjob/spark3job.py +22 -12
  230. mlrun/runtimes/utils.py +7 -6
  231. mlrun/secrets.py +2 -2
  232. mlrun/serving/__init__.py +8 -0
  233. mlrun/serving/merger.py +7 -5
  234. mlrun/serving/remote.py +35 -22
  235. mlrun/serving/routers.py +186 -240
  236. mlrun/serving/server.py +41 -10
  237. mlrun/serving/states.py +432 -118
  238. mlrun/serving/utils.py +13 -2
  239. mlrun/serving/v1_serving.py +3 -2
  240. mlrun/serving/v2_serving.py +161 -203
  241. mlrun/track/__init__.py +1 -1
  242. mlrun/track/tracker.py +2 -2
  243. mlrun/track/trackers/mlflow_tracker.py +6 -5
  244. mlrun/utils/async_http.py +35 -22
  245. mlrun/utils/clones.py +7 -4
  246. mlrun/utils/helpers.py +511 -58
  247. mlrun/utils/logger.py +119 -13
  248. mlrun/utils/notifications/notification/__init__.py +22 -19
  249. mlrun/utils/notifications/notification/base.py +39 -15
  250. mlrun/utils/notifications/notification/console.py +6 -6
  251. mlrun/utils/notifications/notification/git.py +11 -11
  252. mlrun/utils/notifications/notification/ipython.py +10 -9
  253. mlrun/utils/notifications/notification/mail.py +176 -0
  254. mlrun/utils/notifications/notification/slack.py +16 -8
  255. mlrun/utils/notifications/notification/webhook.py +24 -8
  256. mlrun/utils/notifications/notification_pusher.py +191 -200
  257. mlrun/utils/regex.py +12 -2
  258. mlrun/utils/version/version.json +2 -2
  259. {mlrun-1.7.2rc4.dist-info → mlrun-1.8.0.dist-info}/METADATA +69 -54
  260. mlrun-1.8.0.dist-info/RECORD +351 -0
  261. {mlrun-1.7.2rc4.dist-info → mlrun-1.8.0.dist-info}/WHEEL +1 -1
  262. mlrun/model_monitoring/applications/evidently_base.py +0 -137
  263. mlrun/model_monitoring/db/stores/__init__.py +0 -136
  264. mlrun/model_monitoring/db/stores/base/store.py +0 -213
  265. mlrun/model_monitoring/db/stores/sqldb/models/__init__.py +0 -71
  266. mlrun/model_monitoring/db/stores/sqldb/models/base.py +0 -190
  267. mlrun/model_monitoring/db/stores/sqldb/models/mysql.py +0 -103
  268. mlrun/model_monitoring/db/stores/sqldb/models/sqlite.py +0 -40
  269. mlrun/model_monitoring/db/stores/sqldb/sql_store.py +0 -659
  270. mlrun/model_monitoring/db/stores/v3io_kv/kv_store.py +0 -726
  271. mlrun/model_monitoring/model_endpoint.py +0 -118
  272. mlrun-1.7.2rc4.dist-info/RECORD +0 -351
  273. {mlrun-1.7.2rc4.dist-info → mlrun-1.8.0.dist-info}/entry_points.txt +0 -0
  274. {mlrun-1.7.2rc4.dist-info → mlrun-1.8.0.dist-info/licenses}/LICENSE +0 -0
  275. {mlrun-1.7.2rc4.dist-info → mlrun-1.8.0.dist-info}/top_level.txt +0 -0
mlrun/serving/states.py CHANGED
@@ -25,11 +25,20 @@ import pathlib
25
25
  import traceback
26
26
  from copy import copy, deepcopy
27
27
  from inspect import getfullargspec, signature
28
- from typing import Any, Union
28
+ from typing import Any, Optional, Union, cast
29
29
 
30
30
  import storey.utils
31
31
 
32
32
  import mlrun
33
+ import mlrun.common.schemas as schemas
34
+ from mlrun.datastore.datastore_profile import (
35
+ DatastoreProfileKafkaSource,
36
+ DatastoreProfileKafkaTarget,
37
+ DatastoreProfileV3io,
38
+ datastore_profile_read,
39
+ )
40
+ from mlrun.datastore.storeytargets import KafkaStoreyTarget, StreamStoreyTarget
41
+ from mlrun.utils import logger
33
42
 
34
43
  from ..config import config
35
44
  from ..datastore import get_stream_pusher
@@ -48,6 +57,8 @@ path_splitter = "/"
48
57
  previous_step = "$prev"
49
58
  queue_class_names = [">>", "$queue"]
50
59
 
60
+ MAX_MODELS_PER_ROUTER = 5000
61
+
51
62
 
52
63
  class GraphError(Exception):
53
64
  """error in graph topology or configuration"""
@@ -81,30 +92,44 @@ _task_step_fields = [
81
92
  "responder",
82
93
  "input_path",
83
94
  "result_path",
95
+ "model_endpoint_creation_strategy",
96
+ "endpoint_type",
84
97
  ]
85
98
 
86
-
87
- MAX_ALLOWED_STEPS = 4500
88
-
89
-
90
- def new_model_endpoint(class_name, model_path, handler=None, **class_args):
91
- class_args = deepcopy(class_args)
92
- class_args["model_path"] = model_path
93
- return TaskStep(class_name, class_args, handler=handler)
99
+ _default_fields_to_strip_from_step = [
100
+ "model_endpoint_creation_strategy",
101
+ "endpoint_type",
102
+ ]
94
103
 
95
104
 
96
- def new_remote_endpoint(url, **class_args):
105
+ def new_remote_endpoint(
106
+ url: str,
107
+ creation_strategy: schemas.ModelEndpointCreationStrategy,
108
+ endpoint_type: schemas.EndpointType,
109
+ **class_args,
110
+ ):
97
111
  class_args = deepcopy(class_args)
98
112
  class_args["url"] = url
99
- return TaskStep("$remote", class_args)
113
+ return TaskStep(
114
+ "$remote",
115
+ class_args=class_args,
116
+ model_endpoint_creation_strategy=creation_strategy,
117
+ endpoint_type=endpoint_type,
118
+ )
100
119
 
101
120
 
102
121
  class BaseStep(ModelObj):
103
122
  kind = "BaseStep"
104
123
  default_shape = "ellipse"
105
124
  _dict_fields = ["kind", "comment", "after", "on_error"]
125
+ _default_fields_to_strip = _default_fields_to_strip_from_step
106
126
 
107
- def __init__(self, name: str = None, after: list = None, shape: str = None):
127
+ def __init__(
128
+ self,
129
+ name: Optional[str] = None,
130
+ after: Optional[list] = None,
131
+ shape: Optional[str] = None,
132
+ ):
108
133
  self.name = name
109
134
  self._parent = None
110
135
  self.comment = None
@@ -114,6 +139,9 @@ class BaseStep(ModelObj):
114
139
  self.shape = shape
115
140
  self.on_error = None
116
141
  self._on_error_handler = None
142
+ self.model_endpoint_creation_strategy = (
143
+ schemas.ModelEndpointCreationStrategy.SKIP
144
+ )
117
145
 
118
146
  def get_shape(self):
119
147
  """graphviz shape"""
@@ -154,14 +182,14 @@ class BaseStep(ModelObj):
154
182
 
155
183
  def error_handler(
156
184
  self,
157
- name: str = None,
185
+ name: Optional[str] = None,
158
186
  class_name=None,
159
187
  handler=None,
160
188
  before=None,
161
189
  function=None,
162
- full_event: bool = None,
163
- input_path: str = None,
164
- result_path: str = None,
190
+ full_event: Optional[bool] = None,
191
+ input_path: Optional[str] = None,
192
+ result_path: Optional[str] = None,
165
193
  **class_args,
166
194
  ):
167
195
  """set error handler on a step or the entire graph (to be executed on failure/raise)
@@ -297,13 +325,16 @@ class BaseStep(ModelObj):
297
325
  def to(
298
326
  self,
299
327
  class_name: Union[str, StepToDict] = None,
300
- name: str = None,
301
- handler: str = None,
302
- graph_shape: str = None,
303
- function: str = None,
304
- full_event: bool = None,
305
- input_path: str = None,
306
- result_path: str = None,
328
+ name: Optional[str] = None,
329
+ handler: Optional[str] = None,
330
+ graph_shape: Optional[str] = None,
331
+ function: Optional[str] = None,
332
+ full_event: Optional[bool] = None,
333
+ input_path: Optional[str] = None,
334
+ result_path: Optional[str] = None,
335
+ model_endpoint_creation_strategy: Optional[
336
+ schemas.ModelEndpointCreationStrategy
337
+ ] = None,
307
338
  **class_args,
308
339
  ):
309
340
  """add a step right after this step and return the new step
@@ -331,6 +362,23 @@ class BaseStep(ModelObj):
331
362
  this require that the event body will behave like a dict, example:
332
363
  event: {"x": 5} , result_path="y" means the output of the step will be written
333
364
  to event["y"] resulting in {"x": 5, "y": <result>}
365
+ :param model_endpoint_creation_strategy: Strategy for creating or updating the model endpoint:
366
+
367
+ * **overwrite**:
368
+
369
+ 1. If model endpoints with the same name exist, delete the `latest` one.
370
+ 2. Create a new model endpoint entry and set it as `latest`.
371
+
372
+ * **inplace** (default):
373
+
374
+ 1. If model endpoints with the same name exist, update the `latest` entry.
375
+ 2. Otherwise, create a new entry.
376
+
377
+ * **archive**:
378
+
379
+ 1. If model endpoints with the same name exist, preserve them.
380
+ 2. Create a new model endpoint with the same name and set it to `latest`.
381
+
334
382
  :param class_args: class init arguments
335
383
  """
336
384
  if hasattr(self, "steps"):
@@ -352,6 +400,7 @@ class BaseStep(ModelObj):
352
400
  input_path=input_path,
353
401
  result_path=result_path,
354
402
  class_args=class_args,
403
+ model_endpoint_creation_strategy=model_endpoint_creation_strategy,
355
404
  )
356
405
  step = parent._steps.update(name, step)
357
406
  step.set_parent(parent)
@@ -366,15 +415,18 @@ class BaseStep(ModelObj):
366
415
  steps: list[Union[str, StepToDict, dict[str, Any]]],
367
416
  force: bool = False,
368
417
  ):
369
- """set list of steps as downstream from this step, in the order specified. This will overwrite any existing
418
+ """
419
+ Set list of steps as downstream from this step, in the order specified. This will overwrite any existing
370
420
  downstream steps.
371
421
 
372
422
  :param steps: list of steps to follow this one
373
423
  :param force: whether to overwrite existing downstream steps. If False, this method will fail if any downstream
374
- steps have already been defined. Defaults to False.
424
+ steps have already been defined. Defaults to False.
425
+
375
426
  :return: the last step added to the flow
376
427
 
377
- example:
428
+ example::
429
+
378
430
  The below code sets the downstream nodes of step1 by using a list of steps (provided to `set_flow()`) and a
379
431
  single step (provided to `to()`), resulting in the graph (step1 -> step2 -> step3 -> step4).
380
432
  Notice that using `force=True` is required in case step1 already had downstream nodes (e.g. if the existing
@@ -404,16 +456,20 @@ class TaskStep(BaseStep):
404
456
 
405
457
  def __init__(
406
458
  self,
407
- class_name: Union[str, type] = None,
408
- class_args: dict = None,
409
- handler: str = None,
410
- name: str = None,
411
- after: list = None,
412
- full_event: bool = None,
413
- function: str = None,
414
- responder: bool = None,
415
- input_path: str = None,
416
- result_path: str = None,
459
+ class_name: Optional[Union[str, type]] = None,
460
+ class_args: Optional[dict] = None,
461
+ handler: Optional[str] = None,
462
+ name: Optional[str] = None,
463
+ after: Optional[list] = None,
464
+ full_event: Optional[bool] = None,
465
+ function: Optional[str] = None,
466
+ responder: Optional[bool] = None,
467
+ input_path: Optional[str] = None,
468
+ result_path: Optional[str] = None,
469
+ model_endpoint_creation_strategy: Optional[
470
+ schemas.ModelEndpointCreationStrategy
471
+ ] = schemas.ModelEndpointCreationStrategy.SKIP,
472
+ endpoint_type: Optional[schemas.EndpointType] = schemas.EndpointType.NODE_EP,
417
473
  ):
418
474
  super().__init__(name, after)
419
475
  self.class_name = class_name
@@ -433,6 +489,8 @@ class TaskStep(BaseStep):
433
489
  self.on_error = None
434
490
  self._inject_context = False
435
491
  self._call_with_event = False
492
+ self.model_endpoint_creation_strategy = model_endpoint_creation_strategy
493
+ self.endpoint_type = endpoint_type
436
494
 
437
495
  def init_object(self, context, namespace, mode="sync", reset=False, **extra_kwargs):
438
496
  self.context = context
@@ -549,9 +607,11 @@ class TaskStep(BaseStep):
549
607
 
550
608
  def _post_init(self, mode="sync"):
551
609
  if self._object and hasattr(self._object, "post_init"):
552
- self._object.post_init(mode)
553
- if hasattr(self._object, "model_endpoint_uid"):
554
- self.endpoint_uid = self._object.model_endpoint_uid
610
+ self._object.post_init(
611
+ mode,
612
+ creation_strategy=self.model_endpoint_creation_strategy,
613
+ endpoint_type=self.endpoint_type,
614
+ )
555
615
 
556
616
  def respond(self):
557
617
  """mark this step as the responder.
@@ -598,6 +658,27 @@ class TaskStep(BaseStep):
598
658
  raise exc
599
659
  return event
600
660
 
661
+ def to_dict(
662
+ self,
663
+ fields: Optional[list] = None,
664
+ exclude: Optional[list] = None,
665
+ strip: bool = False,
666
+ ) -> dict:
667
+ self.endpoint_type = (
668
+ self.endpoint_type.value
669
+ if isinstance(self.endpoint_type, schemas.EndpointType)
670
+ else self.endpoint_type
671
+ )
672
+ self.model_endpoint_creation_strategy = (
673
+ self.model_endpoint_creation_strategy.value
674
+ if isinstance(
675
+ self.model_endpoint_creation_strategy,
676
+ schemas.ModelEndpointCreationStrategy,
677
+ )
678
+ else self.model_endpoint_creation_strategy
679
+ )
680
+ return super().to_dict(fields, exclude, strip)
681
+
601
682
 
602
683
  class MonitoringApplicationStep(TaskStep):
603
684
  """monitoring application execution step, runs users class code"""
@@ -607,16 +688,16 @@ class MonitoringApplicationStep(TaskStep):
607
688
 
608
689
  def __init__(
609
690
  self,
610
- class_name: Union[str, type] = None,
611
- class_args: dict = None,
612
- handler: str = None,
613
- name: str = None,
614
- after: list = None,
615
- full_event: bool = None,
616
- function: str = None,
617
- responder: bool = None,
618
- input_path: str = None,
619
- result_path: str = None,
691
+ class_name: Optional[Union[str, type]] = None,
692
+ class_args: Optional[dict] = None,
693
+ handler: Optional[str] = None,
694
+ name: Optional[str] = None,
695
+ after: Optional[list] = None,
696
+ full_event: Optional[bool] = None,
697
+ function: Optional[str] = None,
698
+ responder: Optional[bool] = None,
699
+ input_path: Optional[str] = None,
700
+ result_path: Optional[str] = None,
620
701
  ):
621
702
  super().__init__(
622
703
  class_name=class_name,
@@ -641,16 +722,16 @@ class ErrorStep(TaskStep):
641
722
 
642
723
  def __init__(
643
724
  self,
644
- class_name: Union[str, type] = None,
645
- class_args: dict = None,
646
- handler: str = None,
647
- name: str = None,
648
- after: list = None,
649
- full_event: bool = None,
650
- function: str = None,
651
- responder: bool = None,
652
- input_path: str = None,
653
- result_path: str = None,
725
+ class_name: Optional[Union[str, type]] = None,
726
+ class_args: Optional[dict] = None,
727
+ handler: Optional[str] = None,
728
+ name: Optional[str] = None,
729
+ after: Optional[list] = None,
730
+ full_event: Optional[bool] = None,
731
+ function: Optional[str] = None,
732
+ responder: Optional[bool] = None,
733
+ input_path: Optional[str] = None,
734
+ result_path: Optional[str] = None,
654
735
  ):
655
736
  super().__init__(
656
737
  class_name=class_name,
@@ -673,31 +754,39 @@ class RouterStep(TaskStep):
673
754
 
674
755
  kind = "router"
675
756
  default_shape = "doubleoctagon"
676
- _dict_fields = _task_step_fields + ["routes"]
757
+ _dict_fields = _task_step_fields + ["routes", "name"]
677
758
  _default_class = "mlrun.serving.ModelRouter"
678
759
 
679
760
  def __init__(
680
761
  self,
681
- class_name: Union[str, type] = None,
682
- class_args: dict = None,
683
- handler: str = None,
684
- routes: list = None,
685
- name: str = None,
686
- function: str = None,
687
- input_path: str = None,
688
- result_path: str = None,
762
+ class_name: Optional[Union[str, type]] = None,
763
+ class_args: Optional[dict] = None,
764
+ handler: Optional[str] = None,
765
+ routes: Optional[list] = None,
766
+ name: Optional[str] = None,
767
+ function: Optional[str] = None,
768
+ input_path: Optional[str] = None,
769
+ result_path: Optional[str] = None,
689
770
  ):
690
771
  super().__init__(
691
772
  class_name,
692
773
  class_args,
693
774
  handler,
694
- name=name,
775
+ name=get_name(name, class_name or RouterStep.kind),
695
776
  function=function,
696
777
  input_path=input_path,
697
778
  result_path=result_path,
698
779
  )
699
780
  self._routes: ObjectDict = None
700
781
  self.routes = routes
782
+ self.endpoint_type = schemas.EndpointType.ROUTER
783
+ if isinstance(class_name, type):
784
+ class_name = class_name.__name__
785
+ self.model_endpoint_creation_strategy = (
786
+ schemas.ModelEndpointCreationStrategy.INPLACE
787
+ if class_name and "VotingEnsemble" in class_name
788
+ else schemas.ModelEndpointCreationStrategy.SKIP
789
+ )
701
790
 
702
791
  def get_children(self):
703
792
  """get child steps (routes)"""
@@ -719,9 +808,10 @@ class RouterStep(TaskStep):
719
808
  class_name=None,
720
809
  handler=None,
721
810
  function=None,
811
+ creation_strategy: schemas.ModelEndpointCreationStrategy = schemas.ModelEndpointCreationStrategy.INPLACE,
722
812
  **class_args,
723
813
  ):
724
- """add child route step or class to the router
814
+ """add child route step or class to the router, if key exists it will be updated
725
815
 
726
816
  :param key: unique name (and route path) for the child step
727
817
  :param route: child step object (Task, ..)
@@ -729,18 +819,46 @@ class RouterStep(TaskStep):
729
819
  :param class_args: class init arguments
730
820
  :param handler: class handler to invoke on run/event
731
821
  :param function: function this step should run in
822
+ :param creation_strategy: Strategy for creating or updating the model endpoint:
823
+
824
+ * **overwrite**:
825
+
826
+ 1. If model endpoints with the same name exist, delete the `latest` one.
827
+ 2. Create a new model endpoint entry and set it as `latest`.
828
+
829
+ * **inplace** (default):
830
+
831
+ 1. If model endpoints with the same name exist, update the `latest` entry.
832
+ 2. Otherwise, create a new entry.
833
+
834
+ * **archive**:
835
+
836
+ 1. If model endpoints with the same name exist, preserve them.
837
+ 2. Create a new model endpoint with the same name and set it to `latest`.
838
+
732
839
  """
733
840
 
841
+ if len(self.routes.keys()) >= MAX_MODELS_PER_ROUTER and key not in self.routes:
842
+ raise mlrun.errors.MLRunModelLimitExceededError(
843
+ f"Router cannot support more than {MAX_MODELS_PER_ROUTER} model endpoints. "
844
+ f"To add a new route, edit an existing one by passing the same key."
845
+ )
846
+ if key in self.routes:
847
+ logger.info(f"Model {key} already exists, updating it.")
734
848
  if not route and not class_name and not handler:
735
849
  raise MLRunInvalidArgumentError("route or class_name must be specified")
736
850
  if not route:
737
- route = TaskStep(class_name, class_args, handler=handler)
851
+ route = TaskStep(
852
+ class_name,
853
+ class_args,
854
+ handler=handler,
855
+ model_endpoint_creation_strategy=creation_strategy,
856
+ endpoint_type=schemas.EndpointType.LEAF_EP
857
+ if self.class_name and "serving.VotingEnsemble" in self.class_name
858
+ else schemas.EndpointType.NODE_EP,
859
+ )
738
860
  route.function = function or route.function
739
861
 
740
- if len(self._routes) >= MAX_ALLOWED_STEPS:
741
- raise mlrun.errors.MLRunInvalidArgumentError(
742
- f"Cannot create the serving graph: the maximum number of steps is {MAX_ALLOWED_STEPS}"
743
- )
744
862
  route = self._routes.update(key, route)
745
863
  route.set_parent(self)
746
864
  return route
@@ -753,6 +871,10 @@ class RouterStep(TaskStep):
753
871
  del self._routes[key]
754
872
 
755
873
  def init_object(self, context, namespace, mode="sync", reset=False, **extra_kwargs):
874
+ if not self.routes:
875
+ raise mlrun.errors.MLRunRuntimeError(
876
+ "You have to add models to the router step before initializing it"
877
+ )
756
878
  if not self._is_local_function(context):
757
879
  return
758
880
 
@@ -798,7 +920,119 @@ class RouterStep(TaskStep):
798
920
  )
799
921
 
800
922
 
801
- class QueueStep(BaseStep):
923
+ class Model(storey.ParallelExecutionRunnable):
924
+ def load(self) -> None:
925
+ """Override to load model if needed."""
926
+ pass
927
+
928
+ def init(self):
929
+ self.load()
930
+
931
+ def predict(self, body: Any) -> Any:
932
+ """Override to implement prediction logic. If the logic requires asyncio, override predict_async() instead."""
933
+ return body
934
+
935
+ async def predict_async(self, body: Any) -> Any:
936
+ """Override to implement prediction logic if the logic requires asyncio."""
937
+ return body
938
+
939
+ def run(self, body: Any, path: str) -> Any:
940
+ return self.predict(body)
941
+
942
+ async def run_async(self, body: Any, path: str) -> Any:
943
+ return self.predict(body)
944
+
945
+
946
+ class ModelSelector:
947
+ """Used to select which models to run on each event."""
948
+
949
+ def select(
950
+ self, event, available_models: list[Model]
951
+ ) -> Union[list[str], list[Model]]:
952
+ """
953
+ Given an event, returns a list of model names or a list of model objects to run on the event.
954
+ If None is returned, all models will be run.
955
+
956
+ :param event: The full event
957
+ :param available_models: List of available models
958
+ """
959
+ pass
960
+
961
+
962
+ class ModelRunner(storey.ParallelExecution):
963
+ """
964
+ Runs multiple Models on each event. See ModelRunnerStep.
965
+
966
+ :param model_selector: ModelSelector instance whose select() method will be used to select models to run on each
967
+ event. Optional. If not passed, all models will be run.
968
+ """
969
+
970
+ def __init__(self, *args, model_selector: Optional[ModelSelector] = None, **kwargs):
971
+ super().__init__(*args, **kwargs)
972
+ self.model_selector = model_selector or ModelSelector()
973
+
974
+ def select_runnables(self, event):
975
+ models = cast(list[Model], self.runnables)
976
+ return self.model_selector.select(event, models)
977
+
978
+
979
+ class ModelRunnerStep(TaskStep, StepToDict):
980
+ """
981
+ Runs multiple Models on each event.
982
+
983
+ example::
984
+
985
+ model_runner_step = ModelRunnerStep(name="my_model_runner")
986
+ model_runner_step.add_model(MyModel(name="my_model"))
987
+ graph.to(model_runner_step)
988
+
989
+ :param model_selector: ModelSelector instance whose select() method will be used to select models to run on each
990
+ event. Optional. If not passed, all models will be run.
991
+ """
992
+
993
+ kind = "model_runner"
994
+
995
+ def __init__(
996
+ self,
997
+ *args,
998
+ model_selector: Optional[Union[str, ModelSelector]] = None,
999
+ **kwargs,
1000
+ ):
1001
+ super().__init__(
1002
+ *args,
1003
+ class_name="mlrun.serving.ModelRunner",
1004
+ class_args=dict(model_selector=model_selector),
1005
+ **kwargs,
1006
+ )
1007
+
1008
+ def add_model(self, model: Union[str, Model], **model_parameters) -> None:
1009
+ """
1010
+ Add a Model to this ModelRunner.
1011
+
1012
+ :param model: Model class name or object
1013
+ :param model_parameters: Parameters for model instantiation
1014
+ """
1015
+ models = self.class_args.get("models", [])
1016
+ models.append((model, model_parameters))
1017
+ self.class_args["models"] = models
1018
+
1019
+ def init_object(self, context, namespace, mode="sync", reset=False, **extra_kwargs):
1020
+ model_selector = self.class_args.get("model_selector")
1021
+ models = self.class_args.get("models")
1022
+ if isinstance(model_selector, str):
1023
+ model_selector = get_class(model_selector, namespace)()
1024
+ model_objects = []
1025
+ for model, model_params in models:
1026
+ if not isinstance(model, Model):
1027
+ model = get_class(model, namespace)(**model_params)
1028
+ model_objects.append(model)
1029
+ self._async_object = ModelRunner(
1030
+ model_selector=model_selector,
1031
+ runnables=model_objects,
1032
+ )
1033
+
1034
+
1035
+ class QueueStep(BaseStep, StepToDict):
802
1036
  """queue step, implement an async queue or represent a stream"""
803
1037
 
804
1038
  kind = "queue"
@@ -813,12 +1047,12 @@ class QueueStep(BaseStep):
813
1047
 
814
1048
  def __init__(
815
1049
  self,
816
- name: str = None,
817
- path: str = None,
818
- after: list = None,
819
- shards: int = None,
820
- retention_in_hours: int = None,
821
- trigger_args: dict = None,
1050
+ name: Optional[str] = None,
1051
+ path: Optional[str] = None,
1052
+ after: Optional[list] = None,
1053
+ shards: Optional[int] = None,
1054
+ retention_in_hours: Optional[int] = None,
1055
+ trigger_args: Optional[dict] = None,
822
1056
  **options,
823
1057
  ):
824
1058
  super().__init__(name, after)
@@ -850,13 +1084,16 @@ class QueueStep(BaseStep):
850
1084
  def to(
851
1085
  self,
852
1086
  class_name: Union[str, StepToDict] = None,
853
- name: str = None,
854
- handler: str = None,
855
- graph_shape: str = None,
856
- function: str = None,
857
- full_event: bool = None,
858
- input_path: str = None,
859
- result_path: str = None,
1087
+ name: Optional[str] = None,
1088
+ handler: Optional[str] = None,
1089
+ graph_shape: Optional[str] = None,
1090
+ function: Optional[str] = None,
1091
+ full_event: Optional[bool] = None,
1092
+ input_path: Optional[str] = None,
1093
+ result_path: Optional[str] = None,
1094
+ model_endpoint_creation_strategy: Optional[
1095
+ schemas.ModelEndpointCreationStrategy
1096
+ ] = None,
860
1097
  **class_args,
861
1098
  ):
862
1099
  if not function:
@@ -873,6 +1110,7 @@ class QueueStep(BaseStep):
873
1110
  full_event,
874
1111
  input_path,
875
1112
  result_path,
1113
+ model_endpoint_creation_strategy,
876
1114
  **class_args,
877
1115
  )
878
1116
 
@@ -905,7 +1143,7 @@ class FlowStep(BaseStep):
905
1143
  self,
906
1144
  name=None,
907
1145
  steps=None,
908
- after: list = None,
1146
+ after: Optional[list] = None,
909
1147
  engine=None,
910
1148
  final_step=None,
911
1149
  ):
@@ -948,9 +1186,12 @@ class FlowStep(BaseStep):
948
1186
  before=None,
949
1187
  graph_shape=None,
950
1188
  function=None,
951
- full_event: bool = None,
952
- input_path: str = None,
953
- result_path: str = None,
1189
+ full_event: Optional[bool] = None,
1190
+ input_path: Optional[str] = None,
1191
+ result_path: Optional[str] = None,
1192
+ model_endpoint_creation_strategy: Optional[
1193
+ schemas.ModelEndpointCreationStrategy
1194
+ ] = None,
954
1195
  **class_args,
955
1196
  ):
956
1197
  """add task, queue or router step/class to the flow
@@ -982,6 +1223,23 @@ class FlowStep(BaseStep):
982
1223
  this require that the event body will behave like a dict, example:
983
1224
  event: {"x": 5} , result_path="y" means the output of the step will be written
984
1225
  to event["y"] resulting in {"x": 5, "y": <result>}
1226
+ :param model_endpoint_creation_strategy: Strategy for creating or updating the model endpoint:
1227
+
1228
+ * **overwrite**:
1229
+
1230
+ 1. If model endpoints with the same name exist, delete the `latest` one.
1231
+ 2. Create a new model endpoint entry and set it as `latest`.
1232
+
1233
+ * **inplace** (default):
1234
+
1235
+ 1. If model endpoints with the same name exist, update the `latest` entry.
1236
+ 2. Otherwise, create a new entry.
1237
+
1238
+ * **archive**:
1239
+
1240
+ 1. If model endpoints with the same name exist, preserve them.
1241
+ 2. Create a new model endpoint with the same name and set it to `latest`.
1242
+
985
1243
  :param class_args: class init arguments
986
1244
  """
987
1245
 
@@ -994,6 +1252,7 @@ class FlowStep(BaseStep):
994
1252
  full_event=full_event,
995
1253
  input_path=input_path,
996
1254
  result_path=result_path,
1255
+ model_endpoint_creation_strategy=model_endpoint_creation_strategy,
997
1256
  class_args=class_args,
998
1257
  )
999
1258
 
@@ -1037,7 +1296,7 @@ class FlowStep(BaseStep):
1037
1296
  self._last_added = step
1038
1297
  return step
1039
1298
 
1040
- def clear_children(self, steps: list = None):
1299
+ def clear_children(self, steps: Optional[list] = None):
1041
1300
  """remove some or all of the states, empty/None for all"""
1042
1301
  if not steps:
1043
1302
  steps = self._steps.keys()
@@ -1282,11 +1541,19 @@ class FlowStep(BaseStep):
1282
1541
  if self._controller:
1283
1542
  # async flow (using storey)
1284
1543
  event._awaitable_result = None
1285
- resp = self._controller.emit(
1286
- event, return_awaitable_result=self._wait_for_result
1287
- )
1288
- if self._wait_for_result and resp:
1289
- return resp.await_result()
1544
+ if self.context.is_mock:
1545
+ resp = self._controller.emit(
1546
+ event, return_awaitable_result=self._wait_for_result
1547
+ )
1548
+ if self._wait_for_result and resp:
1549
+ return resp.await_result()
1550
+ else:
1551
+ resp_awaitable = self._controller.emit(
1552
+ event, await_result=self._wait_for_result
1553
+ )
1554
+ if self._wait_for_result:
1555
+ return resp_awaitable
1556
+ return self._await_and_return_id(resp_awaitable, event)
1290
1557
  event = copy(event)
1291
1558
  event.body = {"id": event.id}
1292
1559
  return event
@@ -1329,8 +1596,9 @@ class FlowStep(BaseStep):
1329
1596
 
1330
1597
  if self._controller:
1331
1598
  if hasattr(self._controller, "terminate"):
1332
- self._controller.terminate()
1333
- return self._controller.await_termination()
1599
+ return self._controller.terminate(wait=True)
1600
+ else:
1601
+ return self._controller.await_termination()
1334
1602
 
1335
1603
  def plot(self, filename=None, format=None, source=None, targets=None, **kw):
1336
1604
  """plot/save graph using graphviz
@@ -1418,6 +1686,7 @@ classes_map = {
1418
1686
  "queue": QueueStep,
1419
1687
  "error_step": ErrorStep,
1420
1688
  "monitoring_application": MonitoringApplicationStep,
1689
+ "model_runner": ModelRunnerStep,
1421
1690
  }
1422
1691
 
1423
1692
 
@@ -1544,7 +1813,7 @@ def get_name(name, class_name):
1544
1813
  raise MLRunInvalidArgumentError("name or class_name must be provided")
1545
1814
  if isinstance(class_name, type):
1546
1815
  return class_name.__name__
1547
- return class_name
1816
+ return class_name.split(".")[-1]
1548
1817
 
1549
1818
 
1550
1819
  def params_to_step(
@@ -1554,26 +1823,25 @@ def params_to_step(
1554
1823
  graph_shape=None,
1555
1824
  function=None,
1556
1825
  full_event=None,
1557
- input_path: str = None,
1558
- result_path: str = None,
1826
+ input_path: Optional[str] = None,
1827
+ result_path: Optional[str] = None,
1559
1828
  class_args=None,
1829
+ model_endpoint_creation_strategy: Optional[
1830
+ schemas.ModelEndpointCreationStrategy
1831
+ ] = None,
1832
+ endpoint_type: Optional[schemas.EndpointType] = None,
1560
1833
  ):
1561
1834
  """return step object from provided params or classes/objects"""
1562
1835
 
1563
1836
  class_args = class_args or {}
1564
1837
 
1565
- if class_name and hasattr(class_name, "to_dict"):
1566
- struct = class_name.to_dict()
1567
- kind = struct.get("kind", StepKinds.task)
1568
- name = name or struct.get("name", struct.get("class_name"))
1569
- cls = classes_map.get(kind, RootFlowStep)
1570
- step = cls.from_dict(struct)
1571
- step.function = function
1572
- step.full_event = full_event or step.full_event
1573
- step.input_path = input_path or step.input_path
1574
- step.result_path = result_path or step.result_path
1838
+ if isinstance(class_name, QueueStep):
1839
+ if not (name or class_name.name):
1840
+ raise MLRunInvalidArgumentError("queue name must be specified")
1841
+
1842
+ step = class_name
1575
1843
 
1576
- elif class_name and class_name in queue_class_names:
1844
+ elif class_name in queue_class_names:
1577
1845
  if "path" not in class_args:
1578
1846
  raise MLRunInvalidArgumentError(
1579
1847
  "path=<stream path or None> must be specified for queues"
@@ -1586,6 +1854,24 @@ def params_to_step(
1586
1854
  class_args["full_event"] = full_event
1587
1855
  step = QueueStep(name, **class_args)
1588
1856
 
1857
+ elif class_name and hasattr(class_name, "to_dict"):
1858
+ struct = class_name.to_dict()
1859
+ kind = struct.get("kind", StepKinds.task)
1860
+ name = (
1861
+ name
1862
+ or struct.get("name", struct.get("class_name"))
1863
+ or class_name.to_dict(["name"]).get("name")
1864
+ )
1865
+ cls = classes_map.get(kind, RootFlowStep)
1866
+ step = cls.from_dict(struct)
1867
+ step.function = function
1868
+ step.full_event = full_event or step.full_event
1869
+ step.input_path = input_path or step.input_path
1870
+ step.result_path = result_path or step.result_path
1871
+ if kind == StepKinds.task:
1872
+ step.model_endpoint_creation_strategy = model_endpoint_creation_strategy
1873
+ step.endpoint_type = endpoint_type
1874
+
1589
1875
  elif class_name and class_name.startswith("*"):
1590
1876
  routes = class_args.get("routes", None)
1591
1877
  class_name = class_name[1:]
@@ -1612,6 +1898,8 @@ def params_to_step(
1612
1898
  full_event=full_event,
1613
1899
  input_path=input_path,
1614
1900
  result_path=result_path,
1901
+ model_endpoint_creation_strategy=model_endpoint_creation_strategy,
1902
+ endpoint_type=endpoint_type,
1615
1903
  )
1616
1904
  else:
1617
1905
  raise MLRunInvalidArgumentError("class_name or handler must be provided")
@@ -1650,7 +1938,29 @@ def _init_async_objects(context, steps):
1650
1938
 
1651
1939
  kafka_brokers = get_kafka_brokers_from_dict(options, pop=True)
1652
1940
 
1653
- if stream_path.startswith("kafka://") or kafka_brokers:
1941
+ if stream_path and stream_path.startswith("ds://"):
1942
+ datastore_profile = datastore_profile_read(stream_path)
1943
+ if isinstance(
1944
+ datastore_profile,
1945
+ (DatastoreProfileKafkaTarget, DatastoreProfileKafkaSource),
1946
+ ):
1947
+ step._async_object = KafkaStoreyTarget(
1948
+ path=stream_path,
1949
+ context=context,
1950
+ **options,
1951
+ )
1952
+ elif isinstance(datastore_profile, DatastoreProfileV3io):
1953
+ step._async_object = StreamStoreyTarget(
1954
+ stream_path=stream_path,
1955
+ context=context,
1956
+ **options,
1957
+ )
1958
+ else:
1959
+ raise mlrun.errors.MLRunValueError(
1960
+ f"Received an unexpected stream profile type: {type(datastore_profile)}\n"
1961
+ "Expects `DatastoreProfileV3io` or `DatastoreProfileKafkaSource`."
1962
+ )
1963
+ elif stream_path.startswith("kafka://") or kafka_brokers:
1654
1964
  topic, brokers = parse_kafka_url(stream_path, kafka_brokers)
1655
1965
 
1656
1966
  kafka_producer_options = options.pop(
@@ -1703,8 +2013,12 @@ def _init_async_objects(context, steps):
1703
2013
  is_explicit_ack_supported(context) and mlrun.mlconf.is_explicit_ack_enabled()
1704
2014
  )
1705
2015
 
1706
- # TODO: Change to AsyncEmitSource once we can drop support for nuclio<1.12.10
1707
- default_source = storey.SyncEmitSource(
2016
+ if context.is_mock:
2017
+ source_class = storey.SyncEmitSource
2018
+ else:
2019
+ source_class = storey.AsyncEmitSource
2020
+
2021
+ default_source = source_class(
1708
2022
  context=context,
1709
2023
  explicit_ack=explicit_ack,
1710
2024
  **source_args,