mlrun 1.7.0rc4__py3-none-any.whl → 1.7.0rc20__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.
- mlrun/__init__.py +11 -1
- mlrun/__main__.py +25 -111
- mlrun/{datastore/helpers.py → alerts/__init__.py} +2 -5
- mlrun/alerts/alert.py +144 -0
- mlrun/api/schemas/__init__.py +4 -3
- mlrun/artifacts/__init__.py +8 -3
- mlrun/artifacts/base.py +38 -254
- mlrun/artifacts/dataset.py +9 -190
- mlrun/artifacts/manager.py +41 -47
- mlrun/artifacts/model.py +30 -158
- mlrun/artifacts/plots.py +23 -380
- mlrun/common/constants.py +68 -0
- mlrun/common/formatters/__init__.py +19 -0
- mlrun/{model_monitoring/stores/models/sqlite.py → common/formatters/artifact.py} +6 -8
- mlrun/common/formatters/base.py +78 -0
- mlrun/common/formatters/function.py +41 -0
- mlrun/common/formatters/pipeline.py +53 -0
- mlrun/common/formatters/project.py +51 -0
- mlrun/{runtimes → common/runtimes}/constants.py +32 -4
- mlrun/common/schemas/__init__.py +25 -4
- mlrun/common/schemas/alert.py +203 -0
- mlrun/common/schemas/api_gateway.py +148 -0
- mlrun/common/schemas/artifact.py +15 -5
- mlrun/common/schemas/auth.py +8 -2
- mlrun/common/schemas/client_spec.py +2 -0
- mlrun/common/schemas/frontend_spec.py +1 -0
- mlrun/common/schemas/function.py +4 -0
- mlrun/common/schemas/hub.py +7 -9
- mlrun/common/schemas/model_monitoring/__init__.py +19 -3
- mlrun/common/schemas/model_monitoring/constants.py +96 -26
- mlrun/common/schemas/model_monitoring/grafana.py +9 -5
- mlrun/common/schemas/model_monitoring/model_endpoints.py +86 -2
- mlrun/{runtimes/mpijob/v1alpha1.py → common/schemas/pagination.py} +10 -13
- mlrun/common/schemas/pipeline.py +0 -9
- mlrun/common/schemas/project.py +22 -21
- mlrun/common/types.py +7 -1
- mlrun/config.py +87 -19
- mlrun/data_types/data_types.py +4 -0
- mlrun/data_types/to_pandas.py +9 -9
- mlrun/datastore/__init__.py +5 -8
- mlrun/datastore/alibaba_oss.py +130 -0
- mlrun/datastore/azure_blob.py +4 -5
- mlrun/datastore/base.py +69 -30
- mlrun/datastore/datastore.py +10 -2
- mlrun/datastore/datastore_profile.py +90 -6
- mlrun/datastore/google_cloud_storage.py +1 -1
- mlrun/datastore/hdfs.py +5 -0
- mlrun/datastore/inmem.py +2 -2
- mlrun/datastore/redis.py +2 -2
- mlrun/datastore/s3.py +5 -0
- mlrun/datastore/snowflake_utils.py +43 -0
- mlrun/datastore/sources.py +172 -44
- mlrun/datastore/store_resources.py +7 -7
- mlrun/datastore/targets.py +285 -41
- mlrun/datastore/utils.py +68 -5
- mlrun/datastore/v3io.py +27 -50
- mlrun/db/auth_utils.py +152 -0
- mlrun/db/base.py +149 -14
- mlrun/db/factory.py +1 -1
- mlrun/db/httpdb.py +608 -178
- mlrun/db/nopdb.py +191 -7
- mlrun/errors.py +11 -0
- mlrun/execution.py +37 -20
- mlrun/feature_store/__init__.py +0 -2
- mlrun/feature_store/api.py +21 -52
- mlrun/feature_store/feature_set.py +48 -23
- mlrun/feature_store/feature_vector.py +2 -1
- mlrun/feature_store/ingestion.py +7 -6
- mlrun/feature_store/retrieval/base.py +9 -4
- mlrun/feature_store/retrieval/conversion.py +9 -9
- mlrun/feature_store/retrieval/dask_merger.py +2 -0
- mlrun/feature_store/retrieval/job.py +9 -3
- mlrun/feature_store/retrieval/local_merger.py +2 -0
- mlrun/feature_store/retrieval/spark_merger.py +34 -24
- mlrun/feature_store/steps.py +30 -19
- mlrun/features.py +4 -13
- mlrun/frameworks/_dl_common/loggers/tensorboard_logger.py +7 -12
- mlrun/frameworks/auto_mlrun/auto_mlrun.py +2 -2
- mlrun/frameworks/lgbm/__init__.py +1 -1
- mlrun/frameworks/lgbm/callbacks/callback.py +2 -4
- mlrun/frameworks/lgbm/model_handler.py +1 -1
- mlrun/frameworks/parallel_coordinates.py +2 -1
- mlrun/frameworks/pytorch/__init__.py +2 -2
- mlrun/frameworks/sklearn/__init__.py +1 -1
- mlrun/frameworks/tf_keras/__init__.py +5 -2
- mlrun/frameworks/tf_keras/callbacks/logging_callback.py +1 -1
- mlrun/frameworks/tf_keras/mlrun_interface.py +2 -2
- mlrun/frameworks/xgboost/__init__.py +1 -1
- mlrun/k8s_utils.py +10 -11
- mlrun/launcher/__init__.py +1 -1
- mlrun/launcher/base.py +6 -5
- mlrun/launcher/client.py +8 -6
- mlrun/launcher/factory.py +1 -1
- mlrun/launcher/local.py +9 -3
- mlrun/launcher/remote.py +9 -3
- mlrun/lists.py +6 -2
- mlrun/model.py +58 -19
- mlrun/model_monitoring/__init__.py +1 -1
- mlrun/model_monitoring/api.py +127 -301
- mlrun/model_monitoring/application.py +5 -296
- mlrun/model_monitoring/applications/__init__.py +11 -0
- mlrun/model_monitoring/applications/_application_steps.py +157 -0
- mlrun/model_monitoring/applications/base.py +282 -0
- mlrun/model_monitoring/applications/context.py +214 -0
- mlrun/model_monitoring/applications/evidently_base.py +211 -0
- mlrun/model_monitoring/applications/histogram_data_drift.py +224 -93
- mlrun/model_monitoring/applications/results.py +99 -0
- mlrun/model_monitoring/controller.py +30 -36
- mlrun/model_monitoring/db/__init__.py +18 -0
- mlrun/model_monitoring/{stores → db/stores}/__init__.py +43 -36
- mlrun/model_monitoring/db/stores/base/__init__.py +15 -0
- mlrun/model_monitoring/{stores/model_endpoint_store.py → db/stores/base/store.py} +58 -32
- mlrun/model_monitoring/db/stores/sqldb/__init__.py +13 -0
- mlrun/model_monitoring/db/stores/sqldb/models/__init__.py +71 -0
- mlrun/model_monitoring/{stores → db/stores/sqldb}/models/base.py +109 -5
- mlrun/model_monitoring/db/stores/sqldb/models/mysql.py +88 -0
- mlrun/model_monitoring/{stores/models/mysql.py → db/stores/sqldb/models/sqlite.py} +19 -13
- mlrun/model_monitoring/db/stores/sqldb/sql_store.py +684 -0
- mlrun/model_monitoring/db/stores/v3io_kv/__init__.py +13 -0
- mlrun/model_monitoring/{stores/kv_model_endpoint_store.py → db/stores/v3io_kv/kv_store.py} +302 -155
- mlrun/model_monitoring/db/tsdb/__init__.py +100 -0
- mlrun/model_monitoring/db/tsdb/base.py +329 -0
- mlrun/model_monitoring/db/tsdb/helpers.py +30 -0
- mlrun/model_monitoring/db/tsdb/tdengine/__init__.py +15 -0
- mlrun/model_monitoring/db/tsdb/tdengine/schemas.py +240 -0
- mlrun/model_monitoring/db/tsdb/tdengine/stream_graph_steps.py +45 -0
- mlrun/model_monitoring/db/tsdb/tdengine/tdengine_connector.py +397 -0
- mlrun/model_monitoring/db/tsdb/v3io/__init__.py +15 -0
- mlrun/model_monitoring/db/tsdb/v3io/stream_graph_steps.py +117 -0
- mlrun/model_monitoring/db/tsdb/v3io/v3io_connector.py +630 -0
- mlrun/model_monitoring/evidently_application.py +6 -118
- mlrun/model_monitoring/features_drift_table.py +34 -22
- mlrun/model_monitoring/helpers.py +100 -7
- mlrun/model_monitoring/model_endpoint.py +3 -2
- mlrun/model_monitoring/stream_processing.py +93 -228
- mlrun/model_monitoring/tracking_policy.py +7 -1
- mlrun/model_monitoring/writer.py +152 -124
- mlrun/package/packagers_manager.py +1 -0
- mlrun/package/utils/_formatter.py +2 -2
- mlrun/platforms/__init__.py +11 -10
- mlrun/platforms/iguazio.py +21 -202
- mlrun/projects/operations.py +30 -16
- mlrun/projects/pipelines.py +92 -99
- mlrun/projects/project.py +757 -268
- mlrun/render.py +15 -14
- mlrun/run.py +160 -162
- mlrun/runtimes/__init__.py +55 -3
- mlrun/runtimes/base.py +33 -19
- mlrun/runtimes/databricks_job/databricks_wrapper.py +1 -1
- mlrun/runtimes/funcdoc.py +0 -28
- mlrun/runtimes/kubejob.py +28 -122
- mlrun/runtimes/local.py +5 -2
- mlrun/runtimes/mpijob/__init__.py +0 -20
- mlrun/runtimes/mpijob/abstract.py +8 -8
- mlrun/runtimes/mpijob/v1.py +1 -1
- mlrun/runtimes/nuclio/__init__.py +1 -0
- mlrun/runtimes/nuclio/api_gateway.py +709 -0
- mlrun/runtimes/nuclio/application/__init__.py +15 -0
- mlrun/runtimes/nuclio/application/application.py +523 -0
- mlrun/runtimes/nuclio/application/reverse_proxy.go +95 -0
- mlrun/runtimes/nuclio/function.py +98 -58
- mlrun/runtimes/nuclio/serving.py +36 -42
- mlrun/runtimes/pod.py +196 -45
- mlrun/runtimes/remotesparkjob.py +1 -1
- mlrun/runtimes/sparkjob/spark3job.py +1 -1
- mlrun/runtimes/utils.py +6 -73
- mlrun/secrets.py +6 -2
- mlrun/serving/remote.py +2 -3
- mlrun/serving/routers.py +7 -4
- mlrun/serving/server.py +7 -8
- mlrun/serving/states.py +73 -43
- mlrun/serving/v2_serving.py +8 -7
- mlrun/track/tracker.py +2 -1
- mlrun/utils/async_http.py +25 -5
- mlrun/utils/helpers.py +141 -75
- mlrun/utils/http.py +1 -1
- mlrun/utils/logger.py +39 -7
- mlrun/utils/notifications/notification/__init__.py +14 -9
- mlrun/utils/notifications/notification/base.py +12 -0
- mlrun/utils/notifications/notification/console.py +2 -0
- mlrun/utils/notifications/notification/git.py +3 -1
- mlrun/utils/notifications/notification/ipython.py +2 -0
- mlrun/utils/notifications/notification/slack.py +101 -21
- mlrun/utils/notifications/notification/webhook.py +11 -1
- mlrun/utils/notifications/notification_pusher.py +147 -16
- mlrun/utils/retryer.py +3 -2
- mlrun/utils/v3io_clients.py +0 -1
- mlrun/utils/version/version.json +2 -2
- {mlrun-1.7.0rc4.dist-info → mlrun-1.7.0rc20.dist-info}/METADATA +33 -18
- mlrun-1.7.0rc20.dist-info/RECORD +353 -0
- {mlrun-1.7.0rc4.dist-info → mlrun-1.7.0rc20.dist-info}/WHEEL +1 -1
- mlrun/kfpops.py +0 -868
- mlrun/model_monitoring/batch.py +0 -974
- mlrun/model_monitoring/stores/models/__init__.py +0 -27
- mlrun/model_monitoring/stores/sql_model_endpoint_store.py +0 -382
- mlrun/platforms/other.py +0 -305
- mlrun-1.7.0rc4.dist-info/RECORD +0 -321
- {mlrun-1.7.0rc4.dist-info → mlrun-1.7.0rc20.dist-info}/LICENSE +0 -0
- {mlrun-1.7.0rc4.dist-info → mlrun-1.7.0rc20.dist-info}/entry_points.txt +0 -0
- {mlrun-1.7.0rc4.dist-info → mlrun-1.7.0rc20.dist-info}/top_level.txt +0 -0
mlrun/projects/project.py
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
|
+
|
|
14
15
|
import datetime
|
|
15
16
|
import getpass
|
|
16
17
|
import glob
|
|
@@ -30,20 +31,29 @@ from typing import Callable, Optional, Union
|
|
|
30
31
|
import dotenv
|
|
31
32
|
import git
|
|
32
33
|
import git.exc
|
|
33
|
-
import
|
|
34
|
-
import
|
|
34
|
+
import mlrun_pipelines.common.models
|
|
35
|
+
import mlrun_pipelines.mounts
|
|
36
|
+
import nuclio.utils
|
|
35
37
|
import requests
|
|
36
38
|
import yaml
|
|
39
|
+
from mlrun_pipelines.models import PipelineNodeWrapper
|
|
37
40
|
|
|
38
41
|
import mlrun.common.helpers
|
|
42
|
+
import mlrun.common.runtimes.constants
|
|
43
|
+
import mlrun.common.schemas.artifact
|
|
39
44
|
import mlrun.common.schemas.model_monitoring.constants as mm_constants
|
|
40
45
|
import mlrun.db
|
|
41
46
|
import mlrun.errors
|
|
42
47
|
import mlrun.k8s_utils
|
|
48
|
+
import mlrun.model_monitoring.applications as mm_app
|
|
43
49
|
import mlrun.runtimes
|
|
50
|
+
import mlrun.runtimes.nuclio.api_gateway
|
|
44
51
|
import mlrun.runtimes.pod
|
|
45
52
|
import mlrun.runtimes.utils
|
|
53
|
+
import mlrun.serving
|
|
46
54
|
import mlrun.utils.regex
|
|
55
|
+
from mlrun.alerts.alert import AlertConfig
|
|
56
|
+
from mlrun.common.schemas.alert import AlertTemplate
|
|
47
57
|
from mlrun.datastore.datastore_profile import DatastoreProfile, DatastoreProfile2Json
|
|
48
58
|
from mlrun.runtimes.nuclio.function import RemoteRuntime
|
|
49
59
|
|
|
@@ -52,15 +62,10 @@ from ..artifacts.manager import ArtifactManager, dict_to_artifact, extend_artifa
|
|
|
52
62
|
from ..datastore import store_manager
|
|
53
63
|
from ..features import Feature
|
|
54
64
|
from ..model import EntrypointParam, ImageBuilder, ModelObj
|
|
55
|
-
from ..model_monitoring.application import (
|
|
56
|
-
ModelMonitoringApplicationBase,
|
|
57
|
-
PushToMonitoringWriter,
|
|
58
|
-
)
|
|
59
65
|
from ..run import code_to_function, get_object, import_function, new_function
|
|
60
66
|
from ..secrets import SecretsStore
|
|
61
67
|
from ..utils import (
|
|
62
68
|
is_ipython,
|
|
63
|
-
is_legacy_artifact,
|
|
64
69
|
is_relative_path,
|
|
65
70
|
is_yaml_path,
|
|
66
71
|
logger,
|
|
@@ -73,7 +78,10 @@ from ..utils.clones import (
|
|
|
73
78
|
clone_zip,
|
|
74
79
|
get_repo_url,
|
|
75
80
|
)
|
|
76
|
-
from ..utils.helpers import
|
|
81
|
+
from ..utils.helpers import (
|
|
82
|
+
ensure_git_branch,
|
|
83
|
+
resolve_git_reference_from_source,
|
|
84
|
+
)
|
|
77
85
|
from ..utils.notifications import CustomNotificationPusher, NotificationTypes
|
|
78
86
|
from .operations import (
|
|
79
87
|
BuildStatus,
|
|
@@ -127,6 +135,7 @@ def new_project(
|
|
|
127
135
|
save: bool = True,
|
|
128
136
|
overwrite: bool = False,
|
|
129
137
|
parameters: dict = None,
|
|
138
|
+
default_function_node_selector: dict = None,
|
|
130
139
|
) -> "MlrunProject":
|
|
131
140
|
"""Create a new MLRun project, optionally load it from a yaml/zip/git template
|
|
132
141
|
|
|
@@ -137,11 +146,15 @@ def new_project(
|
|
|
137
146
|
example::
|
|
138
147
|
|
|
139
148
|
# create a project with local and hub functions, a workflow, and an artifact
|
|
140
|
-
project = mlrun.new_project(
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
project.
|
|
144
|
-
|
|
149
|
+
project = mlrun.new_project(
|
|
150
|
+
"myproj", "./", init_git=True, description="my new project"
|
|
151
|
+
)
|
|
152
|
+
project.set_function(
|
|
153
|
+
"prep_data.py", "prep-data", image="mlrun/mlrun", handler="prep_data"
|
|
154
|
+
)
|
|
155
|
+
project.set_function("hub://auto-trainer", "train")
|
|
156
|
+
project.set_artifact("data", Artifact(target_path=data_url))
|
|
157
|
+
project.set_workflow("main", "./myflow.py")
|
|
145
158
|
project.save()
|
|
146
159
|
|
|
147
160
|
# run the "main" workflow (watch=True to wait for run completion)
|
|
@@ -151,19 +164,25 @@ def new_project(
|
|
|
151
164
|
|
|
152
165
|
# create a new project from a zip template (can also use yaml/git templates)
|
|
153
166
|
# initialize a local git, and register the git remote path
|
|
154
|
-
project = mlrun.new_project(
|
|
155
|
-
|
|
156
|
-
|
|
167
|
+
project = mlrun.new_project(
|
|
168
|
+
"myproj",
|
|
169
|
+
"./",
|
|
170
|
+
init_git=True,
|
|
171
|
+
remote="git://github.com/mlrun/project-demo.git",
|
|
172
|
+
from_template="http://mysite/proj.zip",
|
|
173
|
+
)
|
|
157
174
|
project.run("main", watch=True)
|
|
158
175
|
|
|
159
176
|
|
|
160
177
|
example using project_setup.py to init the project objects::
|
|
161
178
|
|
|
162
179
|
def setup(project):
|
|
163
|
-
project.set_function(
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
project.
|
|
180
|
+
project.set_function(
|
|
181
|
+
"prep_data.py", "prep-data", image="mlrun/mlrun", handler="prep_data"
|
|
182
|
+
)
|
|
183
|
+
project.set_function("hub://auto-trainer", "train")
|
|
184
|
+
project.set_artifact("data", Artifact(target_path=data_url))
|
|
185
|
+
project.set_workflow("main", "./myflow.py")
|
|
167
186
|
return project
|
|
168
187
|
|
|
169
188
|
|
|
@@ -180,6 +199,7 @@ def new_project(
|
|
|
180
199
|
:param overwrite: overwrite project using 'cascade' deletion strategy (deletes project resources)
|
|
181
200
|
if project with name exists
|
|
182
201
|
:param parameters: key/value pairs to add to the project.spec.params
|
|
202
|
+
:param default_function_node_selector: defines the default node selector for scheduling functions within the project
|
|
183
203
|
|
|
184
204
|
:returns: project object
|
|
185
205
|
"""
|
|
@@ -192,14 +212,16 @@ def new_project(
|
|
|
192
212
|
"Unsupported option, cannot use subpath argument with project templates"
|
|
193
213
|
)
|
|
194
214
|
if from_template.endswith(".yaml"):
|
|
195
|
-
project = _load_project_file(
|
|
215
|
+
project = _load_project_file(
|
|
216
|
+
from_template, name, secrets, allow_cross_project=True
|
|
217
|
+
)
|
|
196
218
|
elif from_template.startswith("git://"):
|
|
197
219
|
clone_git(from_template, context, secrets, clone=True)
|
|
198
220
|
shutil.rmtree(path.join(context, ".git"))
|
|
199
|
-
project = _load_project_dir(context, name)
|
|
221
|
+
project = _load_project_dir(context, name, allow_cross_project=True)
|
|
200
222
|
elif from_template.endswith(".zip"):
|
|
201
223
|
clone_zip(from_template, context, secrets)
|
|
202
|
-
project = _load_project_dir(context, name)
|
|
224
|
+
project = _load_project_dir(context, name, allow_cross_project=True)
|
|
203
225
|
else:
|
|
204
226
|
raise ValueError("template must be a path to .yaml or .zip file")
|
|
205
227
|
project.metadata.name = name
|
|
@@ -226,6 +248,11 @@ def new_project(
|
|
|
226
248
|
project.spec.origin_url = url
|
|
227
249
|
if description:
|
|
228
250
|
project.spec.description = description
|
|
251
|
+
|
|
252
|
+
if default_function_node_selector:
|
|
253
|
+
for key, val in default_function_node_selector.items():
|
|
254
|
+
project.spec.default_function_node_selector[key] = val
|
|
255
|
+
|
|
229
256
|
if parameters:
|
|
230
257
|
# Enable setting project parameters at load time, can be used to customize the project_setup
|
|
231
258
|
for key, val in parameters.items():
|
|
@@ -276,6 +303,7 @@ def load_project(
|
|
|
276
303
|
save: bool = True,
|
|
277
304
|
sync_functions: bool = False,
|
|
278
305
|
parameters: dict = None,
|
|
306
|
+
allow_cross_project: bool = None,
|
|
279
307
|
) -> "MlrunProject":
|
|
280
308
|
"""Load an MLRun project from git or tar or dir
|
|
281
309
|
|
|
@@ -289,7 +317,7 @@ def load_project(
|
|
|
289
317
|
# When using git as the url source the context directory must be an empty or
|
|
290
318
|
# non-existent folder as the git repo will be cloned there
|
|
291
319
|
project = load_project("./demo_proj", "git://github.com/mlrun/project-demo.git")
|
|
292
|
-
project.run("main", arguments={
|
|
320
|
+
project.run("main", arguments={"data": data_url})
|
|
293
321
|
|
|
294
322
|
|
|
295
323
|
project_setup.py example::
|
|
@@ -322,6 +350,8 @@ def load_project(
|
|
|
322
350
|
:param save: whether to save the created project and artifact in the DB
|
|
323
351
|
:param sync_functions: sync the project's functions into the project object (will be saved to the DB if save=True)
|
|
324
352
|
:param parameters: key/value pairs to add to the project.spec.params
|
|
353
|
+
:param allow_cross_project: if True, override the loaded project name. This flag ensures awareness of
|
|
354
|
+
loading an existing project yaml as a baseline for a new project with a different name
|
|
325
355
|
|
|
326
356
|
:returns: project object
|
|
327
357
|
"""
|
|
@@ -337,7 +367,7 @@ def load_project(
|
|
|
337
367
|
if url:
|
|
338
368
|
url = str(url) # to support path objects
|
|
339
369
|
if is_yaml_path(url):
|
|
340
|
-
project = _load_project_file(url, name, secrets)
|
|
370
|
+
project = _load_project_file(url, name, secrets, allow_cross_project)
|
|
341
371
|
project.spec.context = context
|
|
342
372
|
elif url.startswith("git://"):
|
|
343
373
|
url, repo = clone_git(url, context, secrets, clone)
|
|
@@ -364,7 +394,7 @@ def load_project(
|
|
|
364
394
|
repo, url = init_repo(context, url, init_git)
|
|
365
395
|
|
|
366
396
|
if not project:
|
|
367
|
-
project = _load_project_dir(context, name, subpath)
|
|
397
|
+
project = _load_project_dir(context, name, subpath, allow_cross_project)
|
|
368
398
|
|
|
369
399
|
if not project.metadata.name:
|
|
370
400
|
raise ValueError("Project name must be specified")
|
|
@@ -418,6 +448,7 @@ def get_or_create_project(
|
|
|
418
448
|
from_template: str = None,
|
|
419
449
|
save: bool = True,
|
|
420
450
|
parameters: dict = None,
|
|
451
|
+
allow_cross_project: bool = None,
|
|
421
452
|
) -> "MlrunProject":
|
|
422
453
|
"""Load a project from MLRun DB, or create/import if it does not exist
|
|
423
454
|
|
|
@@ -428,9 +459,11 @@ def get_or_create_project(
|
|
|
428
459
|
Usage example::
|
|
429
460
|
|
|
430
461
|
# load project from the DB (if exist) or the source repo
|
|
431
|
-
project = get_or_create_project(
|
|
462
|
+
project = get_or_create_project(
|
|
463
|
+
"myproj", "./", "git://github.com/mlrun/demo-xgb-project.git"
|
|
464
|
+
)
|
|
432
465
|
project.pull("development") # pull the latest code from git
|
|
433
|
-
project.run("main", arguments={
|
|
466
|
+
project.run("main", arguments={"data": data_url}) # run the workflow "main"
|
|
434
467
|
|
|
435
468
|
|
|
436
469
|
project_setup.py example::
|
|
@@ -460,12 +493,12 @@ def get_or_create_project(
|
|
|
460
493
|
:param from_template: path to project YAML file that will be used as from_template (for new projects)
|
|
461
494
|
:param save: whether to save the created project in the DB
|
|
462
495
|
:param parameters: key/value pairs to add to the project.spec.params
|
|
496
|
+
:param allow_cross_project: if True, override the loaded project name. This flag ensures awareness of
|
|
497
|
+
loading an existing project yaml as a baseline for a new project with a different name
|
|
463
498
|
|
|
464
499
|
:returns: project object
|
|
465
500
|
"""
|
|
466
501
|
context = context or "./"
|
|
467
|
-
spec_path = path.join(context, subpath or "", "project.yaml")
|
|
468
|
-
load_from_path = url or path.isfile(spec_path)
|
|
469
502
|
try:
|
|
470
503
|
# load project from the DB.
|
|
471
504
|
# use `name` as `url` as we load the project from the DB
|
|
@@ -481,13 +514,15 @@ def get_or_create_project(
|
|
|
481
514
|
# only loading project from db so no need to save it
|
|
482
515
|
save=False,
|
|
483
516
|
parameters=parameters,
|
|
517
|
+
allow_cross_project=allow_cross_project,
|
|
484
518
|
)
|
|
485
519
|
logger.info("Project loaded successfully", project_name=name)
|
|
486
520
|
return project
|
|
487
|
-
|
|
488
521
|
except mlrun.errors.MLRunNotFoundError:
|
|
489
522
|
logger.debug("Project not found in db", project_name=name)
|
|
490
523
|
|
|
524
|
+
spec_path = path.join(context, subpath or "", "project.yaml")
|
|
525
|
+
load_from_path = url or path.isfile(spec_path)
|
|
491
526
|
# do not nest under "try" or else the exceptions raised below will be logged along with the "not found" message
|
|
492
527
|
if load_from_path:
|
|
493
528
|
# loads a project from archive or local project.yaml
|
|
@@ -503,6 +538,7 @@ def get_or_create_project(
|
|
|
503
538
|
user_project=user_project,
|
|
504
539
|
save=save,
|
|
505
540
|
parameters=parameters,
|
|
541
|
+
allow_cross_project=allow_cross_project,
|
|
506
542
|
)
|
|
507
543
|
|
|
508
544
|
logger.info(
|
|
@@ -577,7 +613,7 @@ def _run_project_setup(
|
|
|
577
613
|
return project
|
|
578
614
|
|
|
579
615
|
|
|
580
|
-
def _load_project_dir(context, name="", subpath=""):
|
|
616
|
+
def _load_project_dir(context, name="", subpath="", allow_cross_project=None):
|
|
581
617
|
subpath_str = subpath or ""
|
|
582
618
|
|
|
583
619
|
# support both .yaml and .yml file extensions
|
|
@@ -591,7 +627,7 @@ def _load_project_dir(context, name="", subpath=""):
|
|
|
591
627
|
with open(project_file_path) as fp:
|
|
592
628
|
data = fp.read()
|
|
593
629
|
struct = yaml.load(data, Loader=yaml.FullLoader)
|
|
594
|
-
project = _project_instance_from_struct(struct, name)
|
|
630
|
+
project = _project_instance_from_struct(struct, name, allow_cross_project)
|
|
595
631
|
project.spec.context = context
|
|
596
632
|
elif function_files := glob.glob(function_file_path):
|
|
597
633
|
function_path = function_files[0]
|
|
@@ -664,19 +700,41 @@ def _delete_project_from_db(project_name, secrets, deletion_strategy):
|
|
|
664
700
|
return db.delete_project(project_name, deletion_strategy=deletion_strategy)
|
|
665
701
|
|
|
666
702
|
|
|
667
|
-
def _load_project_file(url, name="", secrets=None):
|
|
703
|
+
def _load_project_file(url, name="", secrets=None, allow_cross_project=None):
|
|
668
704
|
try:
|
|
669
705
|
obj = get_object(url, secrets)
|
|
670
706
|
except FileNotFoundError as exc:
|
|
671
707
|
raise FileNotFoundError(f"cant find project file at {url}") from exc
|
|
672
708
|
struct = yaml.load(obj, Loader=yaml.FullLoader)
|
|
673
|
-
return _project_instance_from_struct(struct, name)
|
|
709
|
+
return _project_instance_from_struct(struct, name, allow_cross_project)
|
|
674
710
|
|
|
675
711
|
|
|
676
|
-
def _project_instance_from_struct(struct, name):
|
|
677
|
-
struct.
|
|
678
|
-
|
|
679
|
-
|
|
712
|
+
def _project_instance_from_struct(struct, name, allow_cross_project):
|
|
713
|
+
name_from_struct = struct.get("metadata", {}).get("name", "")
|
|
714
|
+
if name and name_from_struct and name_from_struct != name:
|
|
715
|
+
error_message = (
|
|
716
|
+
f"project name mismatch, {name_from_struct} != {name}, please do one of the following:\n"
|
|
717
|
+
"1. Set the `allow_cross_project=True` when loading the project.\n"
|
|
718
|
+
f"2. Delete the existing project yaml, or ensure its name is equal to {name}.\n"
|
|
719
|
+
"3. Use different project context dir."
|
|
720
|
+
)
|
|
721
|
+
|
|
722
|
+
if allow_cross_project is None:
|
|
723
|
+
# TODO: Remove this warning in version 1.9.0 and also fix cli to support allow_cross_project
|
|
724
|
+
logger.warn(
|
|
725
|
+
"Project name is different than specified on its project yaml."
|
|
726
|
+
"You should fix it until version 1.9.0",
|
|
727
|
+
description=error_message,
|
|
728
|
+
)
|
|
729
|
+
elif allow_cross_project:
|
|
730
|
+
logger.warn(
|
|
731
|
+
"Project name is different than specified on its project yaml. Overriding.",
|
|
732
|
+
existing_name=name_from_struct,
|
|
733
|
+
overriding_name=name,
|
|
734
|
+
)
|
|
735
|
+
else:
|
|
736
|
+
raise ValueError(error_message)
|
|
737
|
+
struct.setdefault("metadata", {})["name"] = name or name_from_struct
|
|
680
738
|
return MlrunProject.from_dict(struct)
|
|
681
739
|
|
|
682
740
|
|
|
@@ -759,6 +817,7 @@ class ProjectSpec(ModelObj):
|
|
|
759
817
|
default_image=None,
|
|
760
818
|
build=None,
|
|
761
819
|
custom_packagers: list[tuple[str, bool]] = None,
|
|
820
|
+
default_function_node_selector=None,
|
|
762
821
|
):
|
|
763
822
|
self.repo = None
|
|
764
823
|
|
|
@@ -798,6 +857,7 @@ class ProjectSpec(ModelObj):
|
|
|
798
857
|
# in a tuple where the first index is the packager module's path (str) and the second is a flag (bool) for
|
|
799
858
|
# whether it is mandatory for a run (raise exception on collection error) or not.
|
|
800
859
|
self.custom_packagers = custom_packagers or []
|
|
860
|
+
self.default_function_node_selector = default_function_node_selector or {}
|
|
801
861
|
|
|
802
862
|
@property
|
|
803
863
|
def source(self) -> str:
|
|
@@ -936,13 +996,9 @@ class ProjectSpec(ModelObj):
|
|
|
936
996
|
if not isinstance(artifact, dict) and not hasattr(artifact, "to_dict"):
|
|
937
997
|
raise ValueError("artifacts must be a dict or class")
|
|
938
998
|
if isinstance(artifact, dict):
|
|
939
|
-
|
|
940
|
-
if is_legacy_artifact(artifact) or _is_imported_artifact(artifact):
|
|
941
|
-
key = artifact.get("key")
|
|
942
|
-
else:
|
|
943
|
-
key = artifact.get("metadata").get("key", "")
|
|
999
|
+
key = artifact.get("metadata", {}).get("key", "")
|
|
944
1000
|
if not key:
|
|
945
|
-
raise ValueError('artifacts "key" must be specified')
|
|
1001
|
+
raise ValueError('artifacts "metadata.key" must be specified')
|
|
946
1002
|
else:
|
|
947
1003
|
key = artifact.key
|
|
948
1004
|
artifact = artifact.to_dict()
|
|
@@ -1219,6 +1275,14 @@ class MlrunProject(ModelObj):
|
|
|
1219
1275
|
def description(self, description):
|
|
1220
1276
|
self.spec.description = description
|
|
1221
1277
|
|
|
1278
|
+
@property
|
|
1279
|
+
def default_function_node_selector(self) -> dict:
|
|
1280
|
+
return self.spec.default_function_node_selector
|
|
1281
|
+
|
|
1282
|
+
@default_function_node_selector.setter
|
|
1283
|
+
def default_function_node_selector(self, default_function_node_selector):
|
|
1284
|
+
self.spec.default_function_node_selector = default_function_node_selector
|
|
1285
|
+
|
|
1222
1286
|
@property
|
|
1223
1287
|
def default_image(self) -> str:
|
|
1224
1288
|
return self.spec.default_image
|
|
@@ -1333,13 +1397,15 @@ class MlrunProject(ModelObj):
|
|
|
1333
1397
|
example::
|
|
1334
1398
|
|
|
1335
1399
|
# register a simple file artifact
|
|
1336
|
-
project.set_artifact(
|
|
1400
|
+
project.set_artifact("data", target_path=data_url)
|
|
1337
1401
|
# register a model artifact
|
|
1338
|
-
project.set_artifact(
|
|
1402
|
+
project.set_artifact(
|
|
1403
|
+
"model", ModelArtifact(model_file="model.pkl"), target_path=model_dir_url
|
|
1404
|
+
)
|
|
1339
1405
|
|
|
1340
1406
|
# register a path to artifact package (will be imported on project load)
|
|
1341
1407
|
# to generate such package use `artifact.export(target_path)`
|
|
1342
|
-
project.set_artifact(
|
|
1408
|
+
project.set_artifact("model", "https://mystuff.com/models/mymodel.zip")
|
|
1343
1409
|
|
|
1344
1410
|
:param key: artifact key/name
|
|
1345
1411
|
:param artifact: mlrun Artifact object/dict (or its subclasses) or path to artifact
|
|
@@ -1374,14 +1440,7 @@ class MlrunProject(ModelObj):
|
|
|
1374
1440
|
artifact_path = mlrun.utils.helpers.template_artifact_path(
|
|
1375
1441
|
self.spec.artifact_path or mlrun.mlconf.artifact_path, self.metadata.name
|
|
1376
1442
|
)
|
|
1377
|
-
|
|
1378
|
-
# we need to maintain the different trees that generated them
|
|
1379
|
-
producer = ArtifactProducer(
|
|
1380
|
-
"project",
|
|
1381
|
-
self.metadata.name,
|
|
1382
|
-
self.metadata.name,
|
|
1383
|
-
tag=self._get_hexsha() or str(uuid.uuid4()),
|
|
1384
|
-
)
|
|
1443
|
+
project_tag = self._get_project_tag()
|
|
1385
1444
|
for artifact_dict in self.spec.artifacts:
|
|
1386
1445
|
if _is_imported_artifact(artifact_dict):
|
|
1387
1446
|
import_from = artifact_dict["import_from"]
|
|
@@ -1401,8 +1460,23 @@ class MlrunProject(ModelObj):
|
|
|
1401
1460
|
artifact.src_path = path.join(
|
|
1402
1461
|
self.spec.get_code_path(), artifact.src_path
|
|
1403
1462
|
)
|
|
1463
|
+
producer, is_retained_producer = self._resolve_artifact_producer(
|
|
1464
|
+
artifact, project_tag
|
|
1465
|
+
)
|
|
1466
|
+
# log the artifact only if it doesn't already exist
|
|
1467
|
+
if (
|
|
1468
|
+
producer.name != self.metadata.name
|
|
1469
|
+
and self._resolve_existing_artifact(
|
|
1470
|
+
artifact,
|
|
1471
|
+
)
|
|
1472
|
+
):
|
|
1473
|
+
continue
|
|
1404
1474
|
artifact_manager.log_artifact(
|
|
1405
|
-
producer,
|
|
1475
|
+
producer,
|
|
1476
|
+
artifact,
|
|
1477
|
+
artifact_path=artifact_path,
|
|
1478
|
+
project=self.metadata.name,
|
|
1479
|
+
is_retained_producer=is_retained_producer,
|
|
1406
1480
|
)
|
|
1407
1481
|
|
|
1408
1482
|
def _get_artifact_manager(self):
|
|
@@ -1497,12 +1571,20 @@ class MlrunProject(ModelObj):
|
|
|
1497
1571
|
artifact_path = mlrun.utils.helpers.template_artifact_path(
|
|
1498
1572
|
artifact_path, self.metadata.name
|
|
1499
1573
|
)
|
|
1500
|
-
producer =
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
self.
|
|
1504
|
-
|
|
1505
|
-
|
|
1574
|
+
producer, is_retained_producer = self._resolve_artifact_producer(item)
|
|
1575
|
+
if producer.name != self.metadata.name:
|
|
1576
|
+
# the artifact producer is retained, log it only if it doesn't already exist
|
|
1577
|
+
if existing_artifact := self._resolve_existing_artifact(
|
|
1578
|
+
item,
|
|
1579
|
+
tag,
|
|
1580
|
+
):
|
|
1581
|
+
artifact_key = item if isinstance(item, str) else item.key
|
|
1582
|
+
logger.info(
|
|
1583
|
+
"Artifact already exists, skipping logging",
|
|
1584
|
+
key=artifact_key,
|
|
1585
|
+
tag=tag,
|
|
1586
|
+
)
|
|
1587
|
+
return existing_artifact
|
|
1506
1588
|
item = am.log_artifact(
|
|
1507
1589
|
producer,
|
|
1508
1590
|
item,
|
|
@@ -1514,10 +1596,29 @@ class MlrunProject(ModelObj):
|
|
|
1514
1596
|
upload=upload,
|
|
1515
1597
|
labels=labels,
|
|
1516
1598
|
target_path=target_path,
|
|
1599
|
+
project=self.metadata.name,
|
|
1600
|
+
is_retained_producer=is_retained_producer,
|
|
1517
1601
|
**kwargs,
|
|
1518
1602
|
)
|
|
1519
1603
|
return item
|
|
1520
1604
|
|
|
1605
|
+
def delete_artifact(
|
|
1606
|
+
self,
|
|
1607
|
+
item: Artifact,
|
|
1608
|
+
deletion_strategy: mlrun.common.schemas.artifact.ArtifactsDeletionStrategies = (
|
|
1609
|
+
mlrun.common.schemas.artifact.ArtifactsDeletionStrategies.metadata_only
|
|
1610
|
+
),
|
|
1611
|
+
secrets: dict = None,
|
|
1612
|
+
):
|
|
1613
|
+
"""Delete an artifact object in the DB and optionally delete the artifact data
|
|
1614
|
+
|
|
1615
|
+
:param item: Artifact object (can be any type, such as dataset, model, feature store).
|
|
1616
|
+
:param deletion_strategy: The artifact deletion strategy types.
|
|
1617
|
+
:param secrets: Credentials needed to access the artifact data.
|
|
1618
|
+
"""
|
|
1619
|
+
am = self._get_artifact_manager()
|
|
1620
|
+
am.delete_artifact(item, deletion_strategy, secrets)
|
|
1621
|
+
|
|
1521
1622
|
def log_dataset(
|
|
1522
1623
|
self,
|
|
1523
1624
|
key,
|
|
@@ -1548,7 +1649,9 @@ class MlrunProject(ModelObj):
|
|
|
1548
1649
|
"age": [42, 52, 36, 24, 73],
|
|
1549
1650
|
"testScore": [25, 94, 57, 62, 70],
|
|
1550
1651
|
}
|
|
1551
|
-
df = pd.DataFrame(
|
|
1652
|
+
df = pd.DataFrame(
|
|
1653
|
+
raw_data, columns=["first_name", "last_name", "age", "testScore"]
|
|
1654
|
+
)
|
|
1552
1655
|
project.log_dataset("mydf", df=df, stats=True)
|
|
1553
1656
|
|
|
1554
1657
|
:param key: artifact key
|
|
@@ -1622,13 +1725,16 @@ class MlrunProject(ModelObj):
|
|
|
1622
1725
|
|
|
1623
1726
|
example::
|
|
1624
1727
|
|
|
1625
|
-
project.log_model(
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1728
|
+
project.log_model(
|
|
1729
|
+
"model",
|
|
1730
|
+
body=dumps(model),
|
|
1731
|
+
model_file="model.pkl",
|
|
1732
|
+
metrics=context.results,
|
|
1733
|
+
training_set=training_df,
|
|
1734
|
+
label_column="label",
|
|
1735
|
+
feature_vector=feature_vector_uri,
|
|
1736
|
+
labels={"app": "fraud"},
|
|
1737
|
+
)
|
|
1632
1738
|
|
|
1633
1739
|
:param key: artifact key or artifact class ()
|
|
1634
1740
|
:param body: will use the body as the artifact content
|
|
@@ -1738,14 +1844,16 @@ class MlrunProject(ModelObj):
|
|
|
1738
1844
|
artifact = get_artifact(spec)
|
|
1739
1845
|
with open(f"{temp_dir}/_body", "rb") as fp:
|
|
1740
1846
|
artifact.spec._body = fp.read()
|
|
1741
|
-
artifact.target_path = ""
|
|
1742
1847
|
|
|
1743
1848
|
# if the dataitem is not a file, it means we downloaded it from a remote source to a temp file,
|
|
1744
1849
|
# so we need to remove it after we're done with it
|
|
1745
1850
|
dataitem.remove_local()
|
|
1746
1851
|
|
|
1747
1852
|
return self.log_artifact(
|
|
1748
|
-
artifact,
|
|
1853
|
+
artifact,
|
|
1854
|
+
local_path=temp_dir,
|
|
1855
|
+
artifact_path=artifact_path,
|
|
1856
|
+
upload=True,
|
|
1749
1857
|
)
|
|
1750
1858
|
|
|
1751
1859
|
else:
|
|
@@ -1763,10 +1871,18 @@ class MlrunProject(ModelObj):
|
|
|
1763
1871
|
"""
|
|
1764
1872
|
context = context or self.spec.context
|
|
1765
1873
|
if context:
|
|
1766
|
-
project = _load_project_dir(
|
|
1874
|
+
project = _load_project_dir(
|
|
1875
|
+
context,
|
|
1876
|
+
self.metadata.name,
|
|
1877
|
+
self.spec.subpath,
|
|
1878
|
+
allow_cross_project=False,
|
|
1879
|
+
)
|
|
1767
1880
|
else:
|
|
1768
1881
|
project = _load_project_file(
|
|
1769
|
-
self.spec.origin_url,
|
|
1882
|
+
self.spec.origin_url,
|
|
1883
|
+
self.metadata.name,
|
|
1884
|
+
self._secrets,
|
|
1885
|
+
allow_cross_project=None,
|
|
1770
1886
|
)
|
|
1771
1887
|
project.spec.source = self.spec.source
|
|
1772
1888
|
project.spec.repo = self.spec.repo
|
|
@@ -1795,7 +1911,11 @@ class MlrunProject(ModelObj):
|
|
|
1795
1911
|
def set_model_monitoring_function(
|
|
1796
1912
|
self,
|
|
1797
1913
|
func: typing.Union[str, mlrun.runtimes.BaseRuntime, None] = None,
|
|
1798
|
-
application_class: typing.Union[
|
|
1914
|
+
application_class: typing.Union[
|
|
1915
|
+
str,
|
|
1916
|
+
mm_app.ModelMonitoringApplicationBase,
|
|
1917
|
+
mm_app.ModelMonitoringApplicationBaseV2,
|
|
1918
|
+
] = None,
|
|
1799
1919
|
name: str = None,
|
|
1800
1920
|
image: str = None,
|
|
1801
1921
|
handler=None,
|
|
@@ -1833,11 +1953,6 @@ class MlrunProject(ModelObj):
|
|
|
1833
1953
|
monitoring application's constructor.
|
|
1834
1954
|
"""
|
|
1835
1955
|
|
|
1836
|
-
if name in mm_constants.MonitoringFunctionNames.all():
|
|
1837
|
-
raise mlrun.errors.MLRunInvalidArgumentError(
|
|
1838
|
-
f"Application name can not be on of the following name : "
|
|
1839
|
-
f"{mm_constants.MonitoringFunctionNames.all()}"
|
|
1840
|
-
)
|
|
1841
1956
|
function_object: RemoteRuntime = None
|
|
1842
1957
|
(
|
|
1843
1958
|
resolved_function_name,
|
|
@@ -1855,16 +1970,6 @@ class MlrunProject(ModelObj):
|
|
|
1855
1970
|
requirements_file,
|
|
1856
1971
|
**application_kwargs,
|
|
1857
1972
|
)
|
|
1858
|
-
models_names = "all"
|
|
1859
|
-
function_object.set_label(
|
|
1860
|
-
mm_constants.ModelMonitoringAppLabel.KEY,
|
|
1861
|
-
mm_constants.ModelMonitoringAppLabel.VAL,
|
|
1862
|
-
)
|
|
1863
|
-
function_object.set_label("models", models_names)
|
|
1864
|
-
|
|
1865
|
-
if not mlrun.mlconf.is_ce_mode():
|
|
1866
|
-
function_object.apply(mlrun.mount_v3io())
|
|
1867
|
-
|
|
1868
1973
|
# save to project spec
|
|
1869
1974
|
self.spec.set_function(resolved_function_name, function_object, func)
|
|
1870
1975
|
|
|
@@ -1873,7 +1978,11 @@ class MlrunProject(ModelObj):
|
|
|
1873
1978
|
def create_model_monitoring_function(
|
|
1874
1979
|
self,
|
|
1875
1980
|
func: str = None,
|
|
1876
|
-
application_class: typing.Union[
|
|
1981
|
+
application_class: typing.Union[
|
|
1982
|
+
str,
|
|
1983
|
+
mm_app.ModelMonitoringApplicationBase,
|
|
1984
|
+
mm_app.ModelMonitoringApplicationBaseV2,
|
|
1985
|
+
] = None,
|
|
1877
1986
|
name: str = None,
|
|
1878
1987
|
image: str = None,
|
|
1879
1988
|
handler: str = None,
|
|
@@ -1887,8 +1996,9 @@ class MlrunProject(ModelObj):
|
|
|
1887
1996
|
Create a monitoring function object without setting it to the project
|
|
1888
1997
|
|
|
1889
1998
|
examples::
|
|
1890
|
-
project.create_model_monitoring_function(
|
|
1891
|
-
|
|
1999
|
+
project.create_model_monitoring_function(
|
|
2000
|
+
application_class_name="MyApp", image="mlrun/mlrun", name="myApp"
|
|
2001
|
+
)
|
|
1892
2002
|
|
|
1893
2003
|
:param func: Code url, None refers to current Notebook
|
|
1894
2004
|
:param name: Name of the function, can be specified with a tag to support
|
|
@@ -1923,49 +2033,41 @@ class MlrunProject(ModelObj):
|
|
|
1923
2033
|
|
|
1924
2034
|
def _instantiate_model_monitoring_function(
|
|
1925
2035
|
self,
|
|
1926
|
-
func: typing.Union[str, mlrun.runtimes.BaseRuntime] = None,
|
|
1927
|
-
application_class: typing.Union[
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
2036
|
+
func: typing.Union[str, mlrun.runtimes.BaseRuntime, None] = None,
|
|
2037
|
+
application_class: typing.Union[
|
|
2038
|
+
str,
|
|
2039
|
+
mm_app.ModelMonitoringApplicationBase,
|
|
2040
|
+
mm_app.ModelMonitoringApplicationBaseV2,
|
|
2041
|
+
None,
|
|
2042
|
+
] = None,
|
|
2043
|
+
name: typing.Optional[str] = None,
|
|
2044
|
+
image: typing.Optional[str] = None,
|
|
2045
|
+
handler: typing.Optional[str] = None,
|
|
2046
|
+
with_repo: typing.Optional[bool] = None,
|
|
2047
|
+
tag: typing.Optional[str] = None,
|
|
2048
|
+
requirements: typing.Union[str, list[str], None] = None,
|
|
1934
2049
|
requirements_file: str = "",
|
|
1935
2050
|
**application_kwargs,
|
|
1936
2051
|
) -> tuple[str, mlrun.runtimes.BaseRuntime, dict]:
|
|
2052
|
+
import mlrun.model_monitoring.api
|
|
2053
|
+
|
|
1937
2054
|
function_object: RemoteRuntime = None
|
|
1938
2055
|
kind = None
|
|
1939
2056
|
if (isinstance(func, str) or func is None) and application_class is not None:
|
|
1940
|
-
kind =
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
2057
|
+
kind = mlrun.run.RuntimeKinds.serving
|
|
2058
|
+
func = mlrun.model_monitoring.api._create_model_monitoring_function_base(
|
|
2059
|
+
project=self.name,
|
|
2060
|
+
func=func,
|
|
2061
|
+
application_class=application_class,
|
|
1945
2062
|
name=name,
|
|
1946
|
-
project=self.metadata.name,
|
|
1947
|
-
tag=tag,
|
|
1948
|
-
kind=kind,
|
|
1949
2063
|
image=image,
|
|
2064
|
+
tag=tag,
|
|
1950
2065
|
requirements=requirements,
|
|
1951
2066
|
requirements_file=requirements_file,
|
|
2067
|
+
**application_kwargs,
|
|
1952
2068
|
)
|
|
1953
|
-
graph = func.set_topology("flow")
|
|
1954
|
-
if isinstance(application_class, str):
|
|
1955
|
-
first_step = graph.to(
|
|
1956
|
-
class_name=application_class, **application_kwargs
|
|
1957
|
-
)
|
|
1958
|
-
else:
|
|
1959
|
-
first_step = graph.to(class_name=application_class)
|
|
1960
|
-
first_step.to(
|
|
1961
|
-
class_name=PushToMonitoringWriter(
|
|
1962
|
-
project=self.metadata.name,
|
|
1963
|
-
writer_application_name=mm_constants.MonitoringFunctionNames.WRITER,
|
|
1964
|
-
stream_uri=None,
|
|
1965
|
-
),
|
|
1966
|
-
).respond()
|
|
1967
2069
|
elif isinstance(func, str) and isinstance(handler, str):
|
|
1968
|
-
kind =
|
|
2070
|
+
kind = mlrun.run.RuntimeKinds.nuclio
|
|
1969
2071
|
|
|
1970
2072
|
(
|
|
1971
2073
|
resolved_function_name,
|
|
@@ -1983,24 +2085,34 @@ class MlrunProject(ModelObj):
|
|
|
1983
2085
|
requirements,
|
|
1984
2086
|
requirements_file,
|
|
1985
2087
|
)
|
|
1986
|
-
models_names = "all"
|
|
1987
2088
|
function_object.set_label(
|
|
1988
2089
|
mm_constants.ModelMonitoringAppLabel.KEY,
|
|
1989
2090
|
mm_constants.ModelMonitoringAppLabel.VAL,
|
|
1990
2091
|
)
|
|
1991
|
-
function_object.set_label("models", models_names)
|
|
1992
2092
|
|
|
1993
2093
|
if not mlrun.mlconf.is_ce_mode():
|
|
1994
2094
|
function_object.apply(mlrun.mount_v3io())
|
|
1995
2095
|
|
|
1996
2096
|
return resolved_function_name, function_object, func
|
|
1997
2097
|
|
|
2098
|
+
def _wait_for_functions_deployment(self, function_names: list[str]) -> None:
|
|
2099
|
+
"""
|
|
2100
|
+
Wait for the deployment of functions on the backend.
|
|
2101
|
+
|
|
2102
|
+
:param function_names: A list of function names.
|
|
2103
|
+
"""
|
|
2104
|
+
for fn_name in function_names:
|
|
2105
|
+
fn = typing.cast(RemoteRuntime, self.get_function(key=fn_name))
|
|
2106
|
+
fn._wait_for_function_deployment(db=fn._get_db())
|
|
2107
|
+
|
|
1998
2108
|
def enable_model_monitoring(
|
|
1999
2109
|
self,
|
|
2000
2110
|
default_controller_image: str = "mlrun/mlrun",
|
|
2001
2111
|
base_period: int = 10,
|
|
2002
2112
|
image: str = "mlrun/mlrun",
|
|
2113
|
+
*,
|
|
2003
2114
|
deploy_histogram_data_drift_app: bool = True,
|
|
2115
|
+
wait_for_deployment: bool = False,
|
|
2004
2116
|
) -> None:
|
|
2005
2117
|
"""
|
|
2006
2118
|
Deploy model monitoring application controller, writer and stream functions.
|
|
@@ -2010,7 +2122,6 @@ class MlrunProject(ModelObj):
|
|
|
2010
2122
|
The stream function goal is to monitor the log of the data stream. It is triggered when a new log entry
|
|
2011
2123
|
is detected. It processes the new events into statistics that are then written to statistics databases.
|
|
2012
2124
|
|
|
2013
|
-
|
|
2014
2125
|
:param default_controller_image: Deprecated.
|
|
2015
2126
|
:param base_period: The time period in minutes in which the model monitoring controller
|
|
2016
2127
|
function is triggered. By default, the base period is 10 minutes.
|
|
@@ -2018,8 +2129,9 @@ class MlrunProject(ModelObj):
|
|
|
2018
2129
|
stream & histogram data drift functions, which are real time nuclio
|
|
2019
2130
|
functions. By default, the image is mlrun/mlrun.
|
|
2020
2131
|
:param deploy_histogram_data_drift_app: If true, deploy the default histogram-based data drift application.
|
|
2021
|
-
|
|
2022
|
-
|
|
2132
|
+
:param wait_for_deployment: If true, return only after the deployment is done on the backend.
|
|
2133
|
+
Otherwise, deploy the model monitoring infrastructure on the
|
|
2134
|
+
background, including the histogram data drift app if selected.
|
|
2023
2135
|
"""
|
|
2024
2136
|
if default_controller_image != "mlrun/mlrun":
|
|
2025
2137
|
# TODO: Remove this in 1.9.0
|
|
@@ -2034,34 +2146,58 @@ class MlrunProject(ModelObj):
|
|
|
2034
2146
|
project=self.name,
|
|
2035
2147
|
image=image,
|
|
2036
2148
|
base_period=base_period,
|
|
2149
|
+
deploy_histogram_data_drift_app=deploy_histogram_data_drift_app,
|
|
2037
2150
|
)
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2151
|
+
|
|
2152
|
+
if wait_for_deployment:
|
|
2153
|
+
deployment_functions = mm_constants.MonitoringFunctionNames.list()
|
|
2154
|
+
if deploy_histogram_data_drift_app:
|
|
2155
|
+
deployment_functions.append(
|
|
2156
|
+
mm_constants.HistogramDataDriftApplicationConstants.NAME
|
|
2157
|
+
)
|
|
2158
|
+
self._wait_for_functions_deployment(deployment_functions)
|
|
2159
|
+
|
|
2160
|
+
def deploy_histogram_data_drift_app(
|
|
2161
|
+
self,
|
|
2162
|
+
*,
|
|
2163
|
+
image: str = "mlrun/mlrun",
|
|
2164
|
+
db: Optional[mlrun.db.RunDBInterface] = None,
|
|
2165
|
+
wait_for_deployment: bool = False,
|
|
2166
|
+
) -> None:
|
|
2167
|
+
"""
|
|
2168
|
+
Deploy the histogram data drift application.
|
|
2169
|
+
|
|
2170
|
+
:param image: The image on which the application will run.
|
|
2171
|
+
:param db: An optional DB object.
|
|
2172
|
+
:param wait_for_deployment: If true, return only after the deployment is done on the backend.
|
|
2173
|
+
Otherwise, deploy the application on the background.
|
|
2174
|
+
"""
|
|
2175
|
+
if db is None:
|
|
2176
|
+
db = mlrun.db.get_run_db(secrets=self._secrets)
|
|
2177
|
+
db.deploy_histogram_data_drift_app(project=self.name, image=image)
|
|
2178
|
+
|
|
2179
|
+
if wait_for_deployment:
|
|
2180
|
+
self._wait_for_functions_deployment(
|
|
2181
|
+
[mm_constants.HistogramDataDriftApplicationConstants.NAME]
|
|
2047
2182
|
)
|
|
2048
|
-
fn.deploy()
|
|
2049
2183
|
|
|
2050
2184
|
def update_model_monitoring_controller(
|
|
2051
2185
|
self,
|
|
2052
2186
|
base_period: int = 10,
|
|
2053
2187
|
image: str = "mlrun/mlrun",
|
|
2188
|
+
*,
|
|
2189
|
+
wait_for_deployment: bool = False,
|
|
2054
2190
|
) -> None:
|
|
2055
2191
|
"""
|
|
2056
2192
|
Redeploy model monitoring application controller functions.
|
|
2057
2193
|
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2194
|
+
:param base_period: The time period in minutes in which the model monitoring controller function
|
|
2195
|
+
is triggered. By default, the base period is 10 minutes.
|
|
2196
|
+
:param image: The image of the model monitoring controller, writer & monitoring
|
|
2197
|
+
stream functions, which are real time nuclio functions.
|
|
2198
|
+
By default, the image is mlrun/mlrun.
|
|
2199
|
+
:param wait_for_deployment: If true, return only after the deployment is done on the backend.
|
|
2200
|
+
Otherwise, deploy the controller on the background.
|
|
2065
2201
|
"""
|
|
2066
2202
|
db = mlrun.db.get_run_db(secrets=self._secrets)
|
|
2067
2203
|
db.update_model_monitoring_controller(
|
|
@@ -2070,26 +2206,34 @@ class MlrunProject(ModelObj):
|
|
|
2070
2206
|
image=image,
|
|
2071
2207
|
)
|
|
2072
2208
|
|
|
2073
|
-
|
|
2209
|
+
if wait_for_deployment:
|
|
2210
|
+
self._wait_for_functions_deployment(
|
|
2211
|
+
[mm_constants.MonitoringFunctionNames.APPLICATION_CONTROLLER]
|
|
2212
|
+
)
|
|
2213
|
+
|
|
2214
|
+
def disable_model_monitoring(
|
|
2215
|
+
self, *, delete_histogram_data_drift_app: bool = True
|
|
2216
|
+
) -> None:
|
|
2217
|
+
"""
|
|
2218
|
+
Note: This method is currently not advised for use. See ML-3432.
|
|
2219
|
+
Disable model monitoring by deleting the underlying functions infrastructure from MLRun database.
|
|
2220
|
+
|
|
2221
|
+
:param delete_histogram_data_drift_app: Whether to delete the histogram data drift app.
|
|
2222
|
+
"""
|
|
2074
2223
|
db = mlrun.db.get_run_db(secrets=self._secrets)
|
|
2075
|
-
|
|
2076
|
-
project=self.name,
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
)
|
|
2083
|
-
db.delete_function(
|
|
2084
|
-
project=self.name,
|
|
2085
|
-
name=mm_constants.MonitoringFunctionNames.STREAM,
|
|
2086
|
-
)
|
|
2224
|
+
for fn_name in mm_constants.MonitoringFunctionNames.list():
|
|
2225
|
+
db.delete_function(project=self.name, name=fn_name)
|
|
2226
|
+
if delete_histogram_data_drift_app:
|
|
2227
|
+
db.delete_function(
|
|
2228
|
+
project=self.name,
|
|
2229
|
+
name=mm_constants.HistogramDataDriftApplicationConstants.NAME,
|
|
2230
|
+
)
|
|
2087
2231
|
|
|
2088
2232
|
def set_function(
|
|
2089
2233
|
self,
|
|
2090
2234
|
func: typing.Union[str, mlrun.runtimes.BaseRuntime] = None,
|
|
2091
2235
|
name: str = "",
|
|
2092
|
-
kind: str = "",
|
|
2236
|
+
kind: str = "job",
|
|
2093
2237
|
image: str = None,
|
|
2094
2238
|
handler: str = None,
|
|
2095
2239
|
with_repo: bool = None,
|
|
@@ -2109,19 +2253,20 @@ class MlrunProject(ModelObj):
|
|
|
2109
2253
|
examples::
|
|
2110
2254
|
|
|
2111
2255
|
proj.set_function(func_object)
|
|
2112
|
-
proj.set_function(
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
proj.set_function(
|
|
2116
|
-
proj.set_function(
|
|
2256
|
+
proj.set_function(
|
|
2257
|
+
"./src/mycode.py", "ingest", image="myrepo/ing:latest", with_repo=True
|
|
2258
|
+
)
|
|
2259
|
+
proj.set_function("http://.../mynb.ipynb", "train")
|
|
2260
|
+
proj.set_function("./func.yaml")
|
|
2261
|
+
proj.set_function("hub://get_toy_data", "getdata")
|
|
2117
2262
|
|
|
2118
2263
|
# set function requirements
|
|
2119
2264
|
|
|
2120
2265
|
# by providing a list of packages
|
|
2121
|
-
proj.set_function(
|
|
2266
|
+
proj.set_function("my.py", requirements=["requests", "pandas"])
|
|
2122
2267
|
|
|
2123
2268
|
# by providing a path to a pip requirements file
|
|
2124
|
-
proj.set_function(
|
|
2269
|
+
proj.set_function("my.py", requirements="requirements.txt")
|
|
2125
2270
|
|
|
2126
2271
|
:param func: Function object or spec/code url, None refers to current Notebook
|
|
2127
2272
|
:param name: Name of the function (under the project), can be specified with a tag to support
|
|
@@ -2176,16 +2321,13 @@ class MlrunProject(ModelObj):
|
|
|
2176
2321
|
if func is None and not _has_module(handler, kind):
|
|
2177
2322
|
# if function path is not provided and it is not a module (no ".")
|
|
2178
2323
|
# use the current notebook as default
|
|
2179
|
-
if
|
|
2180
|
-
|
|
2181
|
-
"Function path or module must be specified (when not running inside a Notebook)"
|
|
2182
|
-
)
|
|
2183
|
-
from IPython import get_ipython
|
|
2324
|
+
if is_ipython:
|
|
2325
|
+
from IPython import get_ipython
|
|
2184
2326
|
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2327
|
+
kernel = get_ipython()
|
|
2328
|
+
func = nuclio.utils.notebook_file_name(kernel)
|
|
2329
|
+
if func.startswith(path.abspath(self.spec.context)):
|
|
2330
|
+
func = path.relpath(func, self.spec.context)
|
|
2189
2331
|
|
|
2190
2332
|
func = func or ""
|
|
2191
2333
|
|
|
@@ -2402,13 +2544,47 @@ class MlrunProject(ModelObj):
|
|
|
2402
2544
|
clone_zip(url, self.spec.context, self._secrets)
|
|
2403
2545
|
|
|
2404
2546
|
def create_remote(self, url, name="origin", branch=None):
|
|
2405
|
-
"""
|
|
2547
|
+
"""Create remote for the project git
|
|
2548
|
+
|
|
2549
|
+
This method creates a new remote repository associated with the project's Git repository.
|
|
2550
|
+
If a remote with the specified name already exists, it will not be overwritten.
|
|
2551
|
+
|
|
2552
|
+
If you wish to update the URL of an existing remote, use the `set_remote` method instead.
|
|
2406
2553
|
|
|
2407
2554
|
:param url: remote git url
|
|
2408
2555
|
:param name: name for the remote (default is 'origin')
|
|
2409
2556
|
:param branch: Git branch to use as source
|
|
2410
2557
|
"""
|
|
2558
|
+
self.set_remote(url, name=name, branch=branch, overwrite=False)
|
|
2559
|
+
|
|
2560
|
+
def set_remote(self, url, name="origin", branch=None, overwrite=True):
|
|
2561
|
+
"""Create or update a remote for the project git repository.
|
|
2562
|
+
|
|
2563
|
+
This method allows you to manage remote repositories associated with the project.
|
|
2564
|
+
It checks if a remote with the specified name already exists.
|
|
2565
|
+
|
|
2566
|
+
If a remote with the same name does not exist, it will be created.
|
|
2567
|
+
If a remote with the same name already exists,
|
|
2568
|
+
the behavior depends on the value of the 'overwrite' flag.
|
|
2569
|
+
|
|
2570
|
+
:param url: remote git url
|
|
2571
|
+
:param name: name for the remote (default is 'origin')
|
|
2572
|
+
:param branch: Git branch to use as source
|
|
2573
|
+
:param overwrite: if True (default), updates the existing remote with the given URL if it already exists.
|
|
2574
|
+
if False, raises an error when attempting to create a remote with a name that already exists.
|
|
2575
|
+
:raises MLRunConflictError: If a remote with the same name already exists and overwrite
|
|
2576
|
+
is set to False.
|
|
2577
|
+
"""
|
|
2411
2578
|
self._ensure_git_repo()
|
|
2579
|
+
if self._remote_exists(name):
|
|
2580
|
+
if overwrite:
|
|
2581
|
+
self.spec.repo.delete_remote(name)
|
|
2582
|
+
else:
|
|
2583
|
+
raise mlrun.errors.MLRunConflictError(
|
|
2584
|
+
f"Remote '{name}' already exists in the project, "
|
|
2585
|
+
f"each remote in the project must have a unique name."
|
|
2586
|
+
"Use 'set_remote' with 'override=True' inorder to update the remote, or choose a different name."
|
|
2587
|
+
)
|
|
2412
2588
|
self.spec.repo.create_remote(name, url=url)
|
|
2413
2589
|
url = url.replace("https://", "git://")
|
|
2414
2590
|
if not branch:
|
|
@@ -2421,6 +2597,22 @@ class MlrunProject(ModelObj):
|
|
|
2421
2597
|
self.spec._source = self.spec.source or url
|
|
2422
2598
|
self.spec.origin_url = self.spec.origin_url or url
|
|
2423
2599
|
|
|
2600
|
+
def remove_remote(self, name):
|
|
2601
|
+
"""Remove a remote from the project's Git repository.
|
|
2602
|
+
|
|
2603
|
+
This method removes the remote repository associated with the specified name from the project's Git repository.
|
|
2604
|
+
|
|
2605
|
+
:param name: Name of the remote to remove.
|
|
2606
|
+
"""
|
|
2607
|
+
if self._remote_exists(name):
|
|
2608
|
+
self.spec.repo.delete_remote(name)
|
|
2609
|
+
else:
|
|
2610
|
+
logger.warning(f"The remote '{name}' does not exist. Nothing to remove.")
|
|
2611
|
+
|
|
2612
|
+
def _remote_exists(self, name):
|
|
2613
|
+
"""Check if a remote with the given name already exists"""
|
|
2614
|
+
return any(remote.name == name for remote in self.spec.repo.remotes)
|
|
2615
|
+
|
|
2424
2616
|
def _ensure_git_repo(self):
|
|
2425
2617
|
if self.spec.repo:
|
|
2426
2618
|
return
|
|
@@ -2552,9 +2744,9 @@ class MlrunProject(ModelObj):
|
|
|
2552
2744
|
|
|
2553
2745
|
read secrets from a source provider to be used in workflows, example::
|
|
2554
2746
|
|
|
2555
|
-
proj.with_secrets(
|
|
2556
|
-
proj.with_secrets(
|
|
2557
|
-
proj.with_secrets(
|
|
2747
|
+
proj.with_secrets("file", "file.txt")
|
|
2748
|
+
proj.with_secrets("inline", {"key": "val"})
|
|
2749
|
+
proj.with_secrets("env", "ENV1,ENV2", prefix="PFX_")
|
|
2558
2750
|
|
|
2559
2751
|
Vault secret source has several options::
|
|
2560
2752
|
|
|
@@ -2565,7 +2757,7 @@ class MlrunProject(ModelObj):
|
|
|
2565
2757
|
The 2nd option uses the current project name as context.
|
|
2566
2758
|
Can also use empty secret list::
|
|
2567
2759
|
|
|
2568
|
-
proj.with_secrets(
|
|
2760
|
+
proj.with_secrets("vault", [])
|
|
2569
2761
|
|
|
2570
2762
|
This will enable access to all secrets in vault registered to the current project.
|
|
2571
2763
|
|
|
@@ -2596,17 +2788,20 @@ class MlrunProject(ModelObj):
|
|
|
2596
2788
|
file_path: str = None,
|
|
2597
2789
|
provider: typing.Union[str, mlrun.common.schemas.SecretProviderName] = None,
|
|
2598
2790
|
):
|
|
2599
|
-
"""
|
|
2791
|
+
"""
|
|
2792
|
+
Set project secrets from dict or secrets env file
|
|
2600
2793
|
when using a secrets file it should have lines in the form KEY=VALUE, comment line start with "#"
|
|
2601
2794
|
V3IO paths/credentials and MLrun service API address are dropped from the secrets
|
|
2602
2795
|
|
|
2603
|
-
example secrets file
|
|
2796
|
+
example secrets file:
|
|
2797
|
+
|
|
2798
|
+
.. code-block:: shell
|
|
2604
2799
|
|
|
2605
2800
|
# this is an env file
|
|
2606
|
-
AWS_ACCESS_KEY_ID
|
|
2801
|
+
AWS_ACCESS_KEY_ID=XXXX
|
|
2607
2802
|
AWS_SECRET_ACCESS_KEY=YYYY
|
|
2608
2803
|
|
|
2609
|
-
usage
|
|
2804
|
+
usage:
|
|
2610
2805
|
|
|
2611
2806
|
# read env vars from dict or file and set as project secrets
|
|
2612
2807
|
project.set_secrets({"SECRET1": "value"})
|
|
@@ -2686,40 +2881,42 @@ class MlrunProject(ModelObj):
|
|
|
2686
2881
|
cleanup_ttl: int = None,
|
|
2687
2882
|
notifications: list[mlrun.model.Notification] = None,
|
|
2688
2883
|
) -> _PipelineRunStatus:
|
|
2689
|
-
"""
|
|
2690
|
-
|
|
2691
|
-
:param name:
|
|
2692
|
-
:param workflow_path:
|
|
2693
|
-
|
|
2694
|
-
:param
|
|
2695
|
-
|
|
2696
|
-
:param
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
:param
|
|
2700
|
-
|
|
2701
|
-
:param
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
:param
|
|
2705
|
-
:param engine: workflow engine running the workflow.
|
|
2706
|
-
supported values are 'kfp' (default), 'local' or 'remote'.
|
|
2707
|
-
for setting engine for remote running use 'remote:local' or 'remote:kfp'.
|
|
2708
|
-
:param local: run local pipeline with local functions (set local=True in function.run())
|
|
2884
|
+
"""Run a workflow using kubeflow pipelines
|
|
2885
|
+
|
|
2886
|
+
:param name: Name of the workflow
|
|
2887
|
+
:param workflow_path: URL to a workflow file, if not a project workflow
|
|
2888
|
+
:param arguments: Kubeflow pipelines arguments (parameters)
|
|
2889
|
+
:param artifact_path: Target path/URL for workflow artifacts, the string '{{workflow.uid}}' will be
|
|
2890
|
+
replaced by workflow id.
|
|
2891
|
+
:param workflow_handler: Workflow function handler (for running workflow function directly)
|
|
2892
|
+
:param namespace: Kubernetes namespace if other than default
|
|
2893
|
+
:param sync: Force functions sync before run
|
|
2894
|
+
:param watch: Wait for pipeline completion
|
|
2895
|
+
:param dirty: Allow running the workflow when the git repo is dirty
|
|
2896
|
+
:param engine: Workflow engine running the workflow.
|
|
2897
|
+
Supported values are 'kfp' (default), 'local' or 'remote'.
|
|
2898
|
+
For setting engine for remote running use 'remote:local' or 'remote:kfp'.
|
|
2899
|
+
:param local: Run local pipeline with local functions (set local=True in function.run())
|
|
2709
2900
|
:param schedule: ScheduleCronTrigger class instance or a standard crontab expression string
|
|
2710
2901
|
(which will be converted to the class using its `from_crontab` constructor),
|
|
2711
2902
|
see this link for help:
|
|
2712
2903
|
https://apscheduler.readthedocs.io/en/3.x/modules/triggers/cron.html#module-apscheduler.triggers.cron
|
|
2713
|
-
|
|
2714
|
-
:param timeout:
|
|
2715
|
-
:param source:
|
|
2716
|
-
|
|
2904
|
+
For using the pre-defined workflow's schedule, set `schedule=True`
|
|
2905
|
+
:param timeout: Timeout in seconds to wait for pipeline completion (watch will be activated)
|
|
2906
|
+
:param source: Source to use instead of the actual `project.spec.source` (used when engine is remote).
|
|
2907
|
+
Can be a one of:
|
|
2908
|
+
1. Remote URL which is loaded dynamically to the workflow runner.
|
|
2909
|
+
2. A path to the project's context on the workflow runner's image.
|
|
2910
|
+
Path can be absolute or relative to `project.spec.build.source_code_target_dir` if defined
|
|
2911
|
+
(enriched when building a project image with source, see `MlrunProject.build_image`).
|
|
2912
|
+
For other engines the source is used to validate that the code is up-to-date.
|
|
2717
2913
|
:param cleanup_ttl:
|
|
2718
|
-
|
|
2914
|
+
Pipeline cleanup ttl in secs (time to wait after workflow completion, at which point the
|
|
2719
2915
|
workflow and all its resources are deleted)
|
|
2720
2916
|
:param notifications:
|
|
2721
|
-
|
|
2722
|
-
|
|
2917
|
+
List of notifications to send for workflow completion
|
|
2918
|
+
|
|
2919
|
+
:returns: ~py:class:`~mlrun.projects.pipelines._PipelineRunStatus` instance
|
|
2723
2920
|
"""
|
|
2724
2921
|
|
|
2725
2922
|
arguments = arguments or {}
|
|
@@ -2736,12 +2933,14 @@ class MlrunProject(ModelObj):
|
|
|
2736
2933
|
"Remote repo is not defined, use .create_remote() + push()"
|
|
2737
2934
|
)
|
|
2738
2935
|
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2936
|
+
if engine not in ["remote"]:
|
|
2937
|
+
# for remote runs we don't require the functions to be synced as they can be loaded dynamically during run
|
|
2938
|
+
self.sync_functions(always=sync)
|
|
2939
|
+
if not self.spec._function_objects:
|
|
2940
|
+
raise ValueError(
|
|
2941
|
+
"There are no functions in the project."
|
|
2942
|
+
" Make sure you've set your functions with project.set_function()."
|
|
2943
|
+
)
|
|
2745
2944
|
|
|
2746
2945
|
if not name and not workflow_path and not workflow_handler:
|
|
2747
2946
|
raise ValueError("Workflow name, path, or handler must be specified")
|
|
@@ -2775,8 +2974,12 @@ class MlrunProject(ModelObj):
|
|
|
2775
2974
|
engine = "remote"
|
|
2776
2975
|
# The default engine is kfp if not given:
|
|
2777
2976
|
workflow_engine = get_workflow_engine(engine or workflow_spec.engine, local)
|
|
2778
|
-
if not inner_engine and engine == "remote":
|
|
2779
|
-
|
|
2977
|
+
if not inner_engine and workflow_engine.engine == "remote":
|
|
2978
|
+
# if inner engine is set to remote, assume kfp as the default inner engine with remote as the runner
|
|
2979
|
+
engine_kind = (
|
|
2980
|
+
workflow_spec.engine if workflow_spec.engine != "remote" else "kfp"
|
|
2981
|
+
)
|
|
2982
|
+
inner_engine = get_workflow_engine(engine_kind, local).engine
|
|
2780
2983
|
workflow_spec.engine = inner_engine or workflow_engine.engine
|
|
2781
2984
|
|
|
2782
2985
|
run = workflow_engine.run(
|
|
@@ -2791,7 +2994,7 @@ class MlrunProject(ModelObj):
|
|
|
2791
2994
|
notifications=notifications,
|
|
2792
2995
|
)
|
|
2793
2996
|
# run is None when scheduling
|
|
2794
|
-
if run and run.state ==
|
|
2997
|
+
if run and run.state == mlrun_pipelines.common.models.RunStatuses.failed:
|
|
2795
2998
|
return run
|
|
2796
2999
|
if not workflow_spec.schedule:
|
|
2797
3000
|
# Failure and schedule messages already logged
|
|
@@ -2800,14 +3003,17 @@ class MlrunProject(ModelObj):
|
|
|
2800
3003
|
)
|
|
2801
3004
|
workflow_spec.clear_tmp()
|
|
2802
3005
|
if (timeout or watch) and not workflow_spec.schedule:
|
|
3006
|
+
run_status_kwargs = {}
|
|
2803
3007
|
status_engine = run._engine
|
|
2804
3008
|
# run's engine gets replaced with inner engine if engine is remote,
|
|
2805
3009
|
# so in that case we need to get the status from the remote engine manually
|
|
2806
|
-
|
|
2807
|
-
if engine == "remote" and status_engine.engine != "local":
|
|
3010
|
+
if workflow_engine.engine == "remote":
|
|
2808
3011
|
status_engine = _RemoteRunner
|
|
3012
|
+
run_status_kwargs["inner_engine"] = run._engine
|
|
2809
3013
|
|
|
2810
|
-
status_engine.get_run_status(
|
|
3014
|
+
status_engine.get_run_status(
|
|
3015
|
+
project=self, run=run, timeout=timeout, **run_status_kwargs
|
|
3016
|
+
)
|
|
2811
3017
|
return run
|
|
2812
3018
|
|
|
2813
3019
|
def save_workflow(self, name, target, artifact_path=None, ttl=None):
|
|
@@ -2907,17 +3113,18 @@ class MlrunProject(ModelObj):
|
|
|
2907
3113
|
|
|
2908
3114
|
def set_model_monitoring_credentials(
|
|
2909
3115
|
self,
|
|
2910
|
-
access_key: str = None,
|
|
2911
|
-
endpoint_store_connection: str = None,
|
|
2912
|
-
stream_path: str = None,
|
|
3116
|
+
access_key: Optional[str] = None,
|
|
3117
|
+
endpoint_store_connection: Optional[str] = None,
|
|
3118
|
+
stream_path: Optional[str] = None,
|
|
3119
|
+
tsdb_connection: Optional[str] = None,
|
|
2913
3120
|
):
|
|
2914
3121
|
"""Set the credentials that will be used by the project's model monitoring
|
|
2915
3122
|
infrastructure functions.
|
|
2916
3123
|
|
|
2917
|
-
:param access_key: Model Monitoring access key for managing user permissions
|
|
2918
3124
|
:param access_key: Model Monitoring access key for managing user permissions
|
|
2919
3125
|
:param endpoint_store_connection: Endpoint store connection string
|
|
2920
3126
|
:param stream_path: Path to the model monitoring stream
|
|
3127
|
+
:param tsdb_connection: Connection string to the time series database
|
|
2921
3128
|
"""
|
|
2922
3129
|
|
|
2923
3130
|
secrets_dict = {}
|
|
@@ -2940,6 +3147,16 @@ class MlrunProject(ModelObj):
|
|
|
2940
3147
|
mlrun.common.schemas.model_monitoring.ProjectSecretKeys.STREAM_PATH
|
|
2941
3148
|
] = stream_path
|
|
2942
3149
|
|
|
3150
|
+
if tsdb_connection:
|
|
3151
|
+
if not tsdb_connection.startswith("taosws://"):
|
|
3152
|
+
raise mlrun.errors.MLRunInvalidArgumentError(
|
|
3153
|
+
"Currently only TDEngine websocket connection is supported for non-v3io TSDB,"
|
|
3154
|
+
"please provide a full URL (e.g. taosws://user:password@host:port)"
|
|
3155
|
+
)
|
|
3156
|
+
secrets_dict[
|
|
3157
|
+
mlrun.common.schemas.model_monitoring.ProjectSecretKeys.TSDB_CONNECTION
|
|
3158
|
+
] = tsdb_connection
|
|
3159
|
+
|
|
2943
3160
|
self.set_secrets(
|
|
2944
3161
|
secrets=secrets_dict,
|
|
2945
3162
|
provider=mlrun.common.schemas.SecretProviderName.kubernetes,
|
|
@@ -2968,7 +3185,7 @@ class MlrunProject(ModelObj):
|
|
|
2968
3185
|
notifications: list[mlrun.model.Notification] = None,
|
|
2969
3186
|
returns: Optional[list[Union[str, dict[str, str]]]] = None,
|
|
2970
3187
|
builder_env: Optional[dict] = None,
|
|
2971
|
-
) -> typing.Union[mlrun.model.RunObject,
|
|
3188
|
+
) -> typing.Union[mlrun.model.RunObject, PipelineNodeWrapper]:
|
|
2972
3189
|
"""Run a local or remote task as part of a local/kubeflow pipeline
|
|
2973
3190
|
|
|
2974
3191
|
example (use with project)::
|
|
@@ -2980,8 +3197,11 @@ class MlrunProject(ModelObj):
|
|
|
2980
3197
|
|
|
2981
3198
|
# run functions (refer to them by name)
|
|
2982
3199
|
run1 = project.run_function("myfunc", params={"x": 7})
|
|
2983
|
-
run2 = project.run_function(
|
|
2984
|
-
|
|
3200
|
+
run2 = project.run_function(
|
|
3201
|
+
"train",
|
|
3202
|
+
params={"label_columns": LABELS},
|
|
3203
|
+
inputs={"dataset": run1.outputs["data"]},
|
|
3204
|
+
)
|
|
2985
3205
|
|
|
2986
3206
|
:param function: name of the function (in the project) or function object
|
|
2987
3207
|
:param handler: name of the function handler
|
|
@@ -3021,7 +3241,7 @@ class MlrunProject(ModelObj):
|
|
|
3021
3241
|
artifact type can be given there. The artifact key must appear in the dictionary as
|
|
3022
3242
|
"key": "the_key".
|
|
3023
3243
|
:param builder_env: env vars dict for source archive config/credentials e.g. builder_env={"GIT_TOKEN": token}
|
|
3024
|
-
:return: MLRun RunObject or
|
|
3244
|
+
:return: MLRun RunObject or PipelineNodeWrapper
|
|
3025
3245
|
"""
|
|
3026
3246
|
return run_function(
|
|
3027
3247
|
function,
|
|
@@ -3064,7 +3284,7 @@ class MlrunProject(ModelObj):
|
|
|
3064
3284
|
requirements_file: str = None,
|
|
3065
3285
|
extra_args: str = None,
|
|
3066
3286
|
force_build: bool = False,
|
|
3067
|
-
) -> typing.Union[BuildStatus,
|
|
3287
|
+
) -> typing.Union[BuildStatus, PipelineNodeWrapper]:
|
|
3068
3288
|
"""deploy ML function, build container with its dependencies
|
|
3069
3289
|
|
|
3070
3290
|
:param function: name of the function (in the project) or function object
|
|
@@ -3118,6 +3338,7 @@ class MlrunProject(ModelObj):
|
|
|
3118
3338
|
requirements_file: str = None,
|
|
3119
3339
|
builder_env: dict = None,
|
|
3120
3340
|
extra_args: str = None,
|
|
3341
|
+
source_code_target_dir: str = None,
|
|
3121
3342
|
):
|
|
3122
3343
|
"""specify builder configuration for the project
|
|
3123
3344
|
|
|
@@ -3138,6 +3359,8 @@ class MlrunProject(ModelObj):
|
|
|
3138
3359
|
e.g. builder_env={"GIT_TOKEN": token}, does not work yet in KFP
|
|
3139
3360
|
:param extra_args: A string containing additional builder arguments in the format of command-line options,
|
|
3140
3361
|
e.g. extra_args="--skip-tls-verify --build-arg A=val"
|
|
3362
|
+
:param source_code_target_dir: Path on the image where source code would be extracted
|
|
3363
|
+
(by default `/home/mlrun_code`)
|
|
3141
3364
|
"""
|
|
3142
3365
|
if not overwrite_build_params:
|
|
3143
3366
|
# TODO: change overwrite_build_params default to True in 1.8.0
|
|
@@ -3161,6 +3384,7 @@ class MlrunProject(ModelObj):
|
|
|
3161
3384
|
overwrite=overwrite_build_params,
|
|
3162
3385
|
builder_env=builder_env,
|
|
3163
3386
|
extra_args=extra_args,
|
|
3387
|
+
source_code_target_dir=source_code_target_dir,
|
|
3164
3388
|
)
|
|
3165
3389
|
|
|
3166
3390
|
if set_as_default and image != self.default_image:
|
|
@@ -3171,7 +3395,6 @@ class MlrunProject(ModelObj):
|
|
|
3171
3395
|
image: str = None,
|
|
3172
3396
|
set_as_default: bool = True,
|
|
3173
3397
|
with_mlrun: bool = None,
|
|
3174
|
-
skip_deployed: bool = False,
|
|
3175
3398
|
base_image: str = None,
|
|
3176
3399
|
commands: list = None,
|
|
3177
3400
|
secret_name: str = None,
|
|
@@ -3182,7 +3405,7 @@ class MlrunProject(ModelObj):
|
|
|
3182
3405
|
requirements_file: str = None,
|
|
3183
3406
|
extra_args: str = None,
|
|
3184
3407
|
target_dir: str = None,
|
|
3185
|
-
) -> typing.Union[BuildStatus,
|
|
3408
|
+
) -> typing.Union[BuildStatus, PipelineNodeWrapper]:
|
|
3186
3409
|
"""Builder docker image for the project, based on the project's build config. Parameters allow to override
|
|
3187
3410
|
the build config.
|
|
3188
3411
|
If the project has a source configured and pull_at_runtime is not configured, this source will be cloned to the
|
|
@@ -3192,7 +3415,6 @@ class MlrunProject(ModelObj):
|
|
|
3192
3415
|
used. If not set, the `mlconf.default_project_image_name` value will be used
|
|
3193
3416
|
:param set_as_default: set `image` to be the project's default image (default False)
|
|
3194
3417
|
:param with_mlrun: add the current mlrun package to the container build
|
|
3195
|
-
:param skip_deployed: *Deprecated* parameter is ignored
|
|
3196
3418
|
:param base_image: base image name/path (commands and source code will be added to it) defaults to
|
|
3197
3419
|
mlrun.mlconf.default_base_image
|
|
3198
3420
|
:param commands: list of docker build (RUN) commands e.g. ['pip install pandas']
|
|
@@ -3207,7 +3429,7 @@ class MlrunProject(ModelObj):
|
|
|
3207
3429
|
* False: The new params are merged with the existing
|
|
3208
3430
|
* True: The existing params are replaced by the new ones
|
|
3209
3431
|
:param extra_args: A string containing additional builder arguments in the format of command-line options,
|
|
3210
|
-
e.g. extra_args="--skip-tls-verify --build-arg A=val"
|
|
3432
|
+
e.g. extra_args="--skip-tls-verify --build-arg A=val"
|
|
3211
3433
|
:param target_dir: Path on the image where source code would be extracted (by default `/home/mlrun_code`)
|
|
3212
3434
|
"""
|
|
3213
3435
|
if not base_image:
|
|
@@ -3217,14 +3439,6 @@ class MlrunProject(ModelObj):
|
|
|
3217
3439
|
base_image=base_image,
|
|
3218
3440
|
)
|
|
3219
3441
|
|
|
3220
|
-
if skip_deployed:
|
|
3221
|
-
warnings.warn(
|
|
3222
|
-
"The 'skip_deployed' parameter is deprecated and will be removed in 1.7.0. "
|
|
3223
|
-
"This parameter is ignored.",
|
|
3224
|
-
# TODO: remove in 1.7.0
|
|
3225
|
-
FutureWarning,
|
|
3226
|
-
)
|
|
3227
|
-
|
|
3228
3442
|
if not overwrite_build_params:
|
|
3229
3443
|
# TODO: change overwrite_build_params default to True in 1.8.0
|
|
3230
3444
|
warnings.warn(
|
|
@@ -3275,6 +3489,11 @@ class MlrunProject(ModelObj):
|
|
|
3275
3489
|
force_build=True,
|
|
3276
3490
|
)
|
|
3277
3491
|
|
|
3492
|
+
# Get the enriched target dir from the function
|
|
3493
|
+
self.spec.build.source_code_target_dir = (
|
|
3494
|
+
function.spec.build.source_code_target_dir
|
|
3495
|
+
)
|
|
3496
|
+
|
|
3278
3497
|
try:
|
|
3279
3498
|
mlrun.db.get_run_db(secrets=self._secrets).delete_function(
|
|
3280
3499
|
name=function.metadata.name
|
|
@@ -3283,7 +3502,7 @@ class MlrunProject(ModelObj):
|
|
|
3283
3502
|
logger.warning(
|
|
3284
3503
|
f"Image was successfully built, but failed to delete temporary function {function.metadata.name}."
|
|
3285
3504
|
" To remove the function, attempt to manually delete it.",
|
|
3286
|
-
exc=
|
|
3505
|
+
exc=mlrun.errors.err_to_str(exc),
|
|
3287
3506
|
)
|
|
3288
3507
|
|
|
3289
3508
|
return result
|
|
@@ -3297,7 +3516,7 @@ class MlrunProject(ModelObj):
|
|
|
3297
3516
|
verbose: bool = None,
|
|
3298
3517
|
builder_env: dict = None,
|
|
3299
3518
|
mock: bool = None,
|
|
3300
|
-
) -> typing.Union[DeployStatus,
|
|
3519
|
+
) -> typing.Union[DeployStatus, PipelineNodeWrapper]:
|
|
3301
3520
|
"""deploy real-time (nuclio based) functions
|
|
3302
3521
|
|
|
3303
3522
|
:param function: name of the function (in the project) or function object
|
|
@@ -3332,7 +3551,12 @@ class MlrunProject(ModelObj):
|
|
|
3332
3551
|
artifact = db.read_artifact(
|
|
3333
3552
|
key, tag, iter=iter, project=self.metadata.name, tree=tree
|
|
3334
3553
|
)
|
|
3335
|
-
|
|
3554
|
+
|
|
3555
|
+
# in tests, if an artifact is not found, the db returns None
|
|
3556
|
+
# in real usage, the db should raise an exception
|
|
3557
|
+
if artifact:
|
|
3558
|
+
return dict_to_artifact(artifact)
|
|
3559
|
+
return None
|
|
3336
3560
|
|
|
3337
3561
|
def list_artifacts(
|
|
3338
3562
|
self,
|
|
@@ -3355,9 +3579,9 @@ class MlrunProject(ModelObj):
|
|
|
3355
3579
|
Examples::
|
|
3356
3580
|
|
|
3357
3581
|
# Get latest version of all artifacts in project
|
|
3358
|
-
latest_artifacts = project.list_artifacts(
|
|
3582
|
+
latest_artifacts = project.list_artifacts("", tag="latest")
|
|
3359
3583
|
# check different artifact versions for a specific artifact, return as objects list
|
|
3360
|
-
result_versions = project.list_artifacts(
|
|
3584
|
+
result_versions = project.list_artifacts("results", tag="*").to_objects()
|
|
3361
3585
|
|
|
3362
3586
|
:param name: Name of artifacts to retrieve. Name with '~' prefix is used as a like query, and is not
|
|
3363
3587
|
case-sensitive. This means that querying for ``~name`` may return artifacts named
|
|
@@ -3407,7 +3631,7 @@ class MlrunProject(ModelObj):
|
|
|
3407
3631
|
Examples::
|
|
3408
3632
|
|
|
3409
3633
|
# Get latest version of all models in project
|
|
3410
|
-
latest_models = project.list_models(
|
|
3634
|
+
latest_models = project.list_models("", tag="latest")
|
|
3411
3635
|
|
|
3412
3636
|
|
|
3413
3637
|
:param name: Name of artifacts to retrieve. Name with '~' prefix is used as a like query, and is not
|
|
@@ -3477,9 +3701,7 @@ class MlrunProject(ModelObj):
|
|
|
3477
3701
|
:returns: List of function objects.
|
|
3478
3702
|
"""
|
|
3479
3703
|
|
|
3480
|
-
model_monitoring_labels_list = [
|
|
3481
|
-
f"{mm_constants.ModelMonitoringAppLabel.KEY}={mm_constants.ModelMonitoringAppLabel.VAL}"
|
|
3482
|
-
]
|
|
3704
|
+
model_monitoring_labels_list = [str(mm_constants.ModelMonitoringAppLabel())]
|
|
3483
3705
|
if labels:
|
|
3484
3706
|
model_monitoring_labels_list += labels
|
|
3485
3707
|
return self.list_functions(
|
|
@@ -3493,7 +3715,10 @@ class MlrunProject(ModelObj):
|
|
|
3493
3715
|
name: Optional[str] = None,
|
|
3494
3716
|
uid: Optional[Union[str, list[str]]] = None,
|
|
3495
3717
|
labels: Optional[Union[str, list[str]]] = None,
|
|
3496
|
-
state: Optional[
|
|
3718
|
+
state: Optional[
|
|
3719
|
+
mlrun.common.runtimes.constants.RunStates
|
|
3720
|
+
] = None, # Backward compatibility
|
|
3721
|
+
states: typing.Optional[list[mlrun.common.runtimes.constants.RunStates]] = None,
|
|
3497
3722
|
sort: bool = True,
|
|
3498
3723
|
last: int = 0,
|
|
3499
3724
|
iter: bool = False,
|
|
@@ -3512,14 +3737,14 @@ class MlrunProject(ModelObj):
|
|
|
3512
3737
|
Example::
|
|
3513
3738
|
|
|
3514
3739
|
# return a list of runs matching the name and label and compare
|
|
3515
|
-
runs = project.list_runs(name=
|
|
3740
|
+
runs = project.list_runs(name="download", labels="owner=admin")
|
|
3516
3741
|
runs.compare()
|
|
3517
3742
|
|
|
3518
3743
|
# multi-label filter can also be provided
|
|
3519
|
-
runs = project.list_runs(name=
|
|
3744
|
+
runs = project.list_runs(name="download", labels=["kind=job", "owner=admin"])
|
|
3520
3745
|
|
|
3521
3746
|
# If running in Jupyter, can use the .show() function to display the results
|
|
3522
|
-
project.list_runs(name=
|
|
3747
|
+
project.list_runs(name="").show()
|
|
3523
3748
|
|
|
3524
3749
|
|
|
3525
3750
|
:param name: Name of the run to retrieve.
|
|
@@ -3527,10 +3752,11 @@ class MlrunProject(ModelObj):
|
|
|
3527
3752
|
:param labels: A list of labels to filter by. Label filters work by either filtering a specific value
|
|
3528
3753
|
of a label (i.e. list("key=value")) or by looking for the existence of a given
|
|
3529
3754
|
key (i.e. "key").
|
|
3530
|
-
:param state: List only runs whose state is specified.
|
|
3755
|
+
:param state: Deprecated - List only runs whose state is specified.
|
|
3756
|
+
:param states: List only runs whose state is one of the provided states.
|
|
3531
3757
|
:param sort: Whether to sort the result according to their start time. Otherwise, results will be
|
|
3532
3758
|
returned by their internal order in the DB (order will not be guaranteed).
|
|
3533
|
-
:param last: Deprecated - currently not used (will be removed in 1.
|
|
3759
|
+
:param last: Deprecated - currently not used (will be removed in 1.9.0).
|
|
3534
3760
|
:param iter: If ``True`` return runs from all iterations. Otherwise, return only runs whose ``iter`` is 0.
|
|
3535
3761
|
:param start_time_from: Filter by run start time in ``[start_time_from, start_time_to]``.
|
|
3536
3762
|
:param start_time_to: Filter by run start time in ``[start_time_from, start_time_to]``.
|
|
@@ -3538,13 +3764,22 @@ class MlrunProject(ModelObj):
|
|
|
3538
3764
|
last_update_time_to)``.
|
|
3539
3765
|
:param last_update_time_to: Filter by run last update time in ``(last_update_time_from, last_update_time_to)``.
|
|
3540
3766
|
"""
|
|
3767
|
+
if state:
|
|
3768
|
+
# TODO: Remove this in 1.9.0
|
|
3769
|
+
warnings.warn(
|
|
3770
|
+
"'state' is deprecated and will be removed in 1.9.0. Use 'states' instead.",
|
|
3771
|
+
FutureWarning,
|
|
3772
|
+
)
|
|
3773
|
+
|
|
3541
3774
|
db = mlrun.db.get_run_db(secrets=self._secrets)
|
|
3542
3775
|
return db.list_runs(
|
|
3543
3776
|
name,
|
|
3544
3777
|
uid,
|
|
3545
3778
|
self.metadata.name,
|
|
3546
3779
|
labels=labels,
|
|
3547
|
-
|
|
3780
|
+
states=mlrun.utils.helpers.as_list(state)
|
|
3781
|
+
if state is not None
|
|
3782
|
+
else states or None,
|
|
3548
3783
|
sort=sort,
|
|
3549
3784
|
last=last,
|
|
3550
3785
|
iter=iter,
|
|
@@ -3625,12 +3860,179 @@ class MlrunProject(ModelObj):
|
|
|
3625
3860
|
"""
|
|
3626
3861
|
self.spec.remove_custom_packager(packager=packager)
|
|
3627
3862
|
|
|
3863
|
+
def store_api_gateway(
|
|
3864
|
+
self,
|
|
3865
|
+
api_gateway: mlrun.runtimes.nuclio.api_gateway.APIGateway,
|
|
3866
|
+
wait_for_readiness=True,
|
|
3867
|
+
max_wait_time=90,
|
|
3868
|
+
) -> mlrun.runtimes.nuclio.api_gateway.APIGateway:
|
|
3869
|
+
"""
|
|
3870
|
+
Creates or updates a Nuclio API Gateway using the provided APIGateway object.
|
|
3871
|
+
|
|
3872
|
+
This method interacts with the MLRun service to create/update a Nuclio API Gateway based on the provided
|
|
3873
|
+
APIGateway object. Once done, it returns the updated APIGateway object containing all fields propagated
|
|
3874
|
+
on MLRun and Nuclio sides, such as the 'host' attribute.
|
|
3875
|
+
Nuclio docs here: https://docs.nuclio.io/en/latest/reference/api-gateway/http.html
|
|
3876
|
+
|
|
3877
|
+
:param api_gateway: An instance of :py:class:`~mlrun.runtimes.nuclio.APIGateway` representing the configuration
|
|
3878
|
+
of the API Gateway to be created or updated.
|
|
3879
|
+
:param wait_for_readiness: (Optional) A boolean indicating whether to wait for the API Gateway to become ready
|
|
3880
|
+
after creation or update (default is True)
|
|
3881
|
+
:param max_wait_time: (Optional) Maximum time to wait for API Gateway readiness in seconds (default is 90s)
|
|
3882
|
+
|
|
3883
|
+
|
|
3884
|
+
@return: An instance of :py:class:`~mlrun.runtimes.nuclio.APIGateway` with all fields populated based on the
|
|
3885
|
+
information retrieved from the Nuclio API
|
|
3886
|
+
"""
|
|
3887
|
+
|
|
3888
|
+
api_gateway_json = mlrun.db.get_run_db().store_api_gateway(
|
|
3889
|
+
api_gateway=api_gateway,
|
|
3890
|
+
project=self.name,
|
|
3891
|
+
)
|
|
3892
|
+
|
|
3893
|
+
if api_gateway_json:
|
|
3894
|
+
# fill in all the fields in the user's api_gateway object
|
|
3895
|
+
api_gateway = mlrun.runtimes.nuclio.api_gateway.APIGateway.from_scheme(
|
|
3896
|
+
api_gateway_json
|
|
3897
|
+
)
|
|
3898
|
+
if wait_for_readiness:
|
|
3899
|
+
api_gateway.wait_for_readiness(max_wait_time=max_wait_time)
|
|
3900
|
+
|
|
3901
|
+
return api_gateway
|
|
3902
|
+
|
|
3903
|
+
def list_api_gateways(self) -> list[mlrun.runtimes.nuclio.api_gateway.APIGateway]:
|
|
3904
|
+
"""
|
|
3905
|
+
Retrieves a list of Nuclio API gateways associated with the project.
|
|
3906
|
+
|
|
3907
|
+
@return: List of :py:class:`~mlrun.runtimes.nuclio.api_gateway.APIGateway` objects representing
|
|
3908
|
+
the Nuclio API gateways associated with the project.
|
|
3909
|
+
"""
|
|
3910
|
+
gateways_list = mlrun.db.get_run_db().list_api_gateways(self.name)
|
|
3911
|
+
return [
|
|
3912
|
+
mlrun.runtimes.nuclio.api_gateway.APIGateway.from_scheme(gateway_dict)
|
|
3913
|
+
for gateway_dict in gateways_list.api_gateways.values()
|
|
3914
|
+
]
|
|
3915
|
+
|
|
3916
|
+
def get_api_gateway(
|
|
3917
|
+
self,
|
|
3918
|
+
name: str,
|
|
3919
|
+
) -> mlrun.runtimes.nuclio.api_gateway.APIGateway:
|
|
3920
|
+
"""
|
|
3921
|
+
Retrieves an API gateway by name instance.
|
|
3922
|
+
|
|
3923
|
+
:param name: The name of the API gateway to retrieve.
|
|
3924
|
+
|
|
3925
|
+
Returns:
|
|
3926
|
+
mlrun.runtimes.nuclio.APIGateway: An instance of APIGateway.
|
|
3927
|
+
"""
|
|
3928
|
+
|
|
3929
|
+
gateway = mlrun.db.get_run_db().get_api_gateway(name=name, project=self.name)
|
|
3930
|
+
return mlrun.runtimes.nuclio.api_gateway.APIGateway.from_scheme(gateway)
|
|
3931
|
+
|
|
3932
|
+
def delete_api_gateway(
|
|
3933
|
+
self,
|
|
3934
|
+
name: str,
|
|
3935
|
+
):
|
|
3936
|
+
"""
|
|
3937
|
+
Deletes an API gateway by name.
|
|
3938
|
+
|
|
3939
|
+
:param name: The name of the API gateway to delete.
|
|
3940
|
+
"""
|
|
3941
|
+
|
|
3942
|
+
mlrun.db.get_run_db().delete_api_gateway(name=name, project=self.name)
|
|
3943
|
+
|
|
3944
|
+
def store_alert_config(
|
|
3945
|
+
self, alert_data: AlertConfig, alert_name=None
|
|
3946
|
+
) -> AlertConfig:
|
|
3947
|
+
"""
|
|
3948
|
+
Create/modify an alert.
|
|
3949
|
+
:param alert_data: The data of the alert.
|
|
3950
|
+
:param alert_name: The name of the alert.
|
|
3951
|
+
:return: the created/modified alert.
|
|
3952
|
+
"""
|
|
3953
|
+
db = mlrun.db.get_run_db(secrets=self._secrets)
|
|
3954
|
+
if alert_name is None:
|
|
3955
|
+
alert_name = alert_data.name
|
|
3956
|
+
return db.store_alert_config(alert_name, alert_data, project=self.metadata.name)
|
|
3957
|
+
|
|
3958
|
+
def get_alert_config(self, alert_name: str) -> AlertConfig:
|
|
3959
|
+
"""
|
|
3960
|
+
Retrieve an alert.
|
|
3961
|
+
:param alert_name: The name of the alert to retrieve.
|
|
3962
|
+
:return: The alert object.
|
|
3963
|
+
"""
|
|
3964
|
+
db = mlrun.db.get_run_db(secrets=self._secrets)
|
|
3965
|
+
return db.get_alert_config(alert_name, self.metadata.name)
|
|
3966
|
+
|
|
3967
|
+
def list_alerts_configs(self) -> list[AlertConfig]:
|
|
3968
|
+
"""
|
|
3969
|
+
Retrieve list of alerts of a project.
|
|
3970
|
+
:return: All the alerts objects of the project.
|
|
3971
|
+
"""
|
|
3972
|
+
db = mlrun.db.get_run_db(secrets=self._secrets)
|
|
3973
|
+
return db.list_alerts_configs(self.metadata.name)
|
|
3974
|
+
|
|
3975
|
+
def delete_alert_config(
|
|
3976
|
+
self, alert_data: AlertConfig = None, alert_name: str = None
|
|
3977
|
+
):
|
|
3978
|
+
"""
|
|
3979
|
+
Delete an alert.
|
|
3980
|
+
:param alert_data: The data of the alert.
|
|
3981
|
+
:param alert_name: The name of the alert to delete.
|
|
3982
|
+
"""
|
|
3983
|
+
if alert_data is None and alert_name is None:
|
|
3984
|
+
raise ValueError(
|
|
3985
|
+
"At least one of alert_data or alert_name must be provided"
|
|
3986
|
+
)
|
|
3987
|
+
if alert_data and alert_name and alert_data.name != alert_name:
|
|
3988
|
+
raise ValueError("Alert_data name does not match the provided alert_name")
|
|
3989
|
+
db = mlrun.db.get_run_db(secrets=self._secrets)
|
|
3990
|
+
if alert_data:
|
|
3991
|
+
alert_name = alert_data.name
|
|
3992
|
+
db.delete_alert_config(alert_name, self.metadata.name)
|
|
3993
|
+
|
|
3994
|
+
def reset_alert_config(
|
|
3995
|
+
self, alert_data: AlertConfig = None, alert_name: str = None
|
|
3996
|
+
):
|
|
3997
|
+
"""
|
|
3998
|
+
Reset an alert.
|
|
3999
|
+
:param alert_data: The data of the alert.
|
|
4000
|
+
:param alert_name: The name of the alert to reset.
|
|
4001
|
+
"""
|
|
4002
|
+
if alert_data is None and alert_name is None:
|
|
4003
|
+
raise ValueError(
|
|
4004
|
+
"At least one of alert_data or alert_name must be provided"
|
|
4005
|
+
)
|
|
4006
|
+
if alert_data and alert_name and alert_data.name != alert_name:
|
|
4007
|
+
raise ValueError("Alert_data name does not match the provided alert_name")
|
|
4008
|
+
db = mlrun.db.get_run_db(secrets=self._secrets)
|
|
4009
|
+
if alert_data:
|
|
4010
|
+
alert_name = alert_data.name
|
|
4011
|
+
db.reset_alert_config(alert_name, self.metadata.name)
|
|
4012
|
+
|
|
4013
|
+
def get_alert_template(self, template_name: str) -> AlertTemplate:
|
|
4014
|
+
"""
|
|
4015
|
+
Retrieve a specific alert template.
|
|
4016
|
+
:param template_name: The name of the template to retrieve.
|
|
4017
|
+
:return: The template object.
|
|
4018
|
+
"""
|
|
4019
|
+
db = mlrun.db.get_run_db(secrets=self._secrets)
|
|
4020
|
+
return db.get_alert_template(template_name)
|
|
4021
|
+
|
|
4022
|
+
def list_alert_templates(self) -> list[AlertTemplate]:
|
|
4023
|
+
"""
|
|
4024
|
+
Retrieve list of all alert templates.
|
|
4025
|
+
:return: All the alert template objects in the database.
|
|
4026
|
+
"""
|
|
4027
|
+
db = mlrun.db.get_run_db(secrets=self._secrets)
|
|
4028
|
+
return db.list_alert_templates()
|
|
4029
|
+
|
|
3628
4030
|
def _run_authenticated_git_action(
|
|
3629
4031
|
self,
|
|
3630
4032
|
action: Callable,
|
|
3631
4033
|
remote: str,
|
|
3632
|
-
args: list =
|
|
3633
|
-
kwargs: dict =
|
|
4034
|
+
args: list = None,
|
|
4035
|
+
kwargs: dict = None,
|
|
3634
4036
|
secrets: Union[SecretsStore, dict] = None,
|
|
3635
4037
|
):
|
|
3636
4038
|
"""Run an arbitrary Git routine while the remote is enriched with secrets
|
|
@@ -3650,6 +4052,8 @@ class MlrunProject(ModelObj):
|
|
|
3650
4052
|
try:
|
|
3651
4053
|
if is_remote_enriched:
|
|
3652
4054
|
self.spec.repo.remotes[remote].set_url(enriched_remote, clean_remote)
|
|
4055
|
+
args = args or []
|
|
4056
|
+
kwargs = kwargs or {}
|
|
3653
4057
|
action(*args, **kwargs)
|
|
3654
4058
|
except RuntimeError as e:
|
|
3655
4059
|
raise mlrun.errors.MLRunRuntimeError(
|
|
@@ -3702,6 +4106,83 @@ class MlrunProject(ModelObj):
|
|
|
3702
4106
|
f"<project.spec.get_code_path()>/<{param_name}>)."
|
|
3703
4107
|
)
|
|
3704
4108
|
|
|
4109
|
+
def _resolve_artifact_producer(
|
|
4110
|
+
self,
|
|
4111
|
+
artifact: typing.Union[str, Artifact],
|
|
4112
|
+
project_producer_tag: str = None,
|
|
4113
|
+
) -> tuple[ArtifactProducer, bool]:
|
|
4114
|
+
"""
|
|
4115
|
+
Resolve the artifact producer of the given artifact.
|
|
4116
|
+
If the artifact's producer is a run, the artifact is registered with the original producer.
|
|
4117
|
+
Otherwise, the artifact is registered with the current project as the producer.
|
|
4118
|
+
|
|
4119
|
+
:param artifact: The artifact to resolve its producer.
|
|
4120
|
+
:param project_producer_tag: The tag to use for the project as the producer. If not provided, a tag will be
|
|
4121
|
+
generated for the project.
|
|
4122
|
+
:return: A tuple of the resolved producer and whether it is retained or not.
|
|
4123
|
+
"""
|
|
4124
|
+
|
|
4125
|
+
if not isinstance(artifact, str) and artifact.spec.producer:
|
|
4126
|
+
# if the artifact was imported from a yaml file, the producer can be a dict
|
|
4127
|
+
if isinstance(artifact.spec.producer, ArtifactProducer):
|
|
4128
|
+
producer_dict = artifact.spec.producer.get_meta()
|
|
4129
|
+
else:
|
|
4130
|
+
producer_dict = artifact.spec.producer
|
|
4131
|
+
|
|
4132
|
+
if producer_dict.get("kind", "") == "run":
|
|
4133
|
+
return ArtifactProducer(
|
|
4134
|
+
name=producer_dict.get("name", ""),
|
|
4135
|
+
kind=producer_dict.get("kind", ""),
|
|
4136
|
+
project=producer_dict.get("project", ""),
|
|
4137
|
+
tag=producer_dict.get("tag", ""),
|
|
4138
|
+
), True
|
|
4139
|
+
|
|
4140
|
+
# do not retain the artifact's producer, replace it with the project as the producer
|
|
4141
|
+
project_producer_tag = project_producer_tag or self._get_project_tag()
|
|
4142
|
+
return ArtifactProducer(
|
|
4143
|
+
kind="project",
|
|
4144
|
+
name=self.metadata.name,
|
|
4145
|
+
project=self.metadata.name,
|
|
4146
|
+
tag=project_producer_tag,
|
|
4147
|
+
), False
|
|
4148
|
+
|
|
4149
|
+
def _resolve_existing_artifact(
|
|
4150
|
+
self,
|
|
4151
|
+
item: typing.Union[str, Artifact],
|
|
4152
|
+
tag: str = None,
|
|
4153
|
+
) -> typing.Optional[Artifact]:
|
|
4154
|
+
"""
|
|
4155
|
+
Check if there is and existing artifact with the given item and tag.
|
|
4156
|
+
If there is, return the existing artifact. Otherwise, return None.
|
|
4157
|
+
|
|
4158
|
+
:param item: The item (or key) to check if there is an existing artifact for.
|
|
4159
|
+
:param tag: The tag to check if there is an existing artifact for.
|
|
4160
|
+
:return: The existing artifact if there is one, otherwise None.
|
|
4161
|
+
"""
|
|
4162
|
+
try:
|
|
4163
|
+
if isinstance(item, str):
|
|
4164
|
+
existing_artifact = self.get_artifact(key=item, tag=tag)
|
|
4165
|
+
else:
|
|
4166
|
+
existing_artifact = self.get_artifact(
|
|
4167
|
+
key=item.key,
|
|
4168
|
+
tag=item.tag,
|
|
4169
|
+
iter=item.iter,
|
|
4170
|
+
tree=item.tree,
|
|
4171
|
+
)
|
|
4172
|
+
if existing_artifact is not None:
|
|
4173
|
+
return existing_artifact.from_dict(existing_artifact)
|
|
4174
|
+
except mlrun.errors.MLRunNotFoundError:
|
|
4175
|
+
logger.debug(
|
|
4176
|
+
"No existing artifact was found",
|
|
4177
|
+
key=item if isinstance(item, str) else item.key,
|
|
4178
|
+
tag=tag if isinstance(item, str) else item.tag,
|
|
4179
|
+
tree=None if isinstance(item, str) else item.tree,
|
|
4180
|
+
)
|
|
4181
|
+
return None
|
|
4182
|
+
|
|
4183
|
+
def _get_project_tag(self):
|
|
4184
|
+
return self._get_hexsha() or str(uuid.uuid4())
|
|
4185
|
+
|
|
3705
4186
|
|
|
3706
4187
|
def _set_as_current_default_project(project: MlrunProject):
|
|
3707
4188
|
mlrun.mlconf.default_project = project.metadata.name
|
|
@@ -3724,10 +4205,6 @@ def _init_function_from_dict(
|
|
|
3724
4205
|
tag = f.get("tag", None)
|
|
3725
4206
|
|
|
3726
4207
|
has_module = _has_module(handler, kind)
|
|
3727
|
-
if not url and "spec" not in f and not has_module:
|
|
3728
|
-
# function must point to a file or a module or have a spec
|
|
3729
|
-
raise ValueError("Function missing a url or a spec or a module")
|
|
3730
|
-
|
|
3731
4208
|
relative_url = url
|
|
3732
4209
|
url, in_context = project.get_item_absolute_path(url)
|
|
3733
4210
|
|
|
@@ -3787,6 +4264,18 @@ def _init_function_from_dict(
|
|
|
3787
4264
|
tag=tag,
|
|
3788
4265
|
)
|
|
3789
4266
|
|
|
4267
|
+
elif kind in mlrun.runtimes.RuntimeKinds.nuclio_runtimes():
|
|
4268
|
+
func = new_function(
|
|
4269
|
+
name,
|
|
4270
|
+
command=relative_url,
|
|
4271
|
+
image=image,
|
|
4272
|
+
kind=kind,
|
|
4273
|
+
handler=handler,
|
|
4274
|
+
tag=tag,
|
|
4275
|
+
)
|
|
4276
|
+
if image and kind != mlrun.runtimes.RuntimeKinds.application:
|
|
4277
|
+
logger.info("Function code not specified, setting entry point to image")
|
|
4278
|
+
func.from_image(image)
|
|
3790
4279
|
else:
|
|
3791
4280
|
raise ValueError(f"Unsupported function url:handler {url}:{handler} or no spec")
|
|
3792
4281
|
|