mlrun 1.7.0rc4__py3-none-any.whl → 1.7.2__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 +39 -121
- mlrun/{datastore/helpers.py → alerts/__init__.py} +2 -5
- mlrun/alerts/alert.py +248 -0
- mlrun/api/schemas/__init__.py +4 -3
- mlrun/artifacts/__init__.py +8 -3
- mlrun/artifacts/base.py +39 -254
- mlrun/artifacts/dataset.py +9 -190
- mlrun/artifacts/manager.py +73 -46
- mlrun/artifacts/model.py +30 -158
- mlrun/artifacts/plots.py +23 -380
- mlrun/common/constants.py +73 -1
- mlrun/common/db/sql_session.py +3 -2
- mlrun/common/formatters/__init__.py +21 -0
- mlrun/common/formatters/artifact.py +46 -0
- mlrun/common/formatters/base.py +113 -0
- mlrun/common/formatters/feature_set.py +44 -0
- mlrun/common/formatters/function.py +46 -0
- mlrun/common/formatters/pipeline.py +53 -0
- mlrun/common/formatters/project.py +51 -0
- mlrun/common/formatters/run.py +29 -0
- mlrun/common/helpers.py +11 -1
- mlrun/{runtimes → common/runtimes}/constants.py +32 -4
- mlrun/common/schemas/__init__.py +31 -4
- mlrun/common/schemas/alert.py +202 -0
- mlrun/common/schemas/api_gateway.py +196 -0
- mlrun/common/schemas/artifact.py +28 -1
- mlrun/common/schemas/auth.py +13 -2
- mlrun/common/schemas/client_spec.py +2 -1
- mlrun/common/schemas/common.py +7 -4
- mlrun/common/schemas/constants.py +3 -0
- mlrun/common/schemas/feature_store.py +58 -28
- mlrun/common/schemas/frontend_spec.py +8 -0
- mlrun/common/schemas/function.py +11 -0
- mlrun/common/schemas/hub.py +7 -9
- mlrun/common/schemas/model_monitoring/__init__.py +21 -4
- mlrun/common/schemas/model_monitoring/constants.py +136 -42
- mlrun/common/schemas/model_monitoring/grafana.py +9 -5
- mlrun/common/schemas/model_monitoring/model_endpoints.py +89 -41
- mlrun/common/schemas/notification.py +69 -12
- mlrun/{runtimes/mpijob/v1alpha1.py → common/schemas/pagination.py} +10 -13
- mlrun/common/schemas/pipeline.py +7 -0
- mlrun/common/schemas/project.py +67 -16
- mlrun/common/schemas/runs.py +17 -0
- mlrun/common/schemas/schedule.py +1 -1
- mlrun/common/schemas/workflow.py +10 -2
- mlrun/common/types.py +14 -1
- mlrun/config.py +233 -58
- mlrun/data_types/data_types.py +11 -1
- mlrun/data_types/spark.py +5 -4
- mlrun/data_types/to_pandas.py +75 -34
- mlrun/datastore/__init__.py +8 -10
- mlrun/datastore/alibaba_oss.py +131 -0
- mlrun/datastore/azure_blob.py +131 -43
- mlrun/datastore/base.py +107 -47
- mlrun/datastore/datastore.py +17 -7
- mlrun/datastore/datastore_profile.py +91 -7
- mlrun/datastore/dbfs_store.py +3 -7
- mlrun/datastore/filestore.py +1 -3
- mlrun/datastore/google_cloud_storage.py +92 -32
- mlrun/datastore/hdfs.py +5 -0
- mlrun/datastore/inmem.py +6 -3
- mlrun/datastore/redis.py +3 -2
- mlrun/datastore/s3.py +30 -12
- mlrun/datastore/snowflake_utils.py +45 -0
- mlrun/datastore/sources.py +274 -59
- mlrun/datastore/spark_utils.py +30 -0
- mlrun/datastore/store_resources.py +9 -7
- mlrun/datastore/storeytargets.py +151 -0
- mlrun/datastore/targets.py +387 -119
- mlrun/datastore/utils.py +68 -5
- mlrun/datastore/v3io.py +28 -50
- mlrun/db/auth_utils.py +152 -0
- mlrun/db/base.py +245 -20
- mlrun/db/factory.py +1 -4
- mlrun/db/httpdb.py +909 -231
- mlrun/db/nopdb.py +279 -14
- mlrun/errors.py +35 -5
- mlrun/execution.py +111 -38
- mlrun/feature_store/__init__.py +0 -2
- mlrun/feature_store/api.py +46 -53
- mlrun/feature_store/common.py +6 -11
- mlrun/feature_store/feature_set.py +48 -23
- mlrun/feature_store/feature_vector.py +13 -2
- mlrun/feature_store/ingestion.py +7 -6
- mlrun/feature_store/retrieval/base.py +9 -4
- mlrun/feature_store/retrieval/dask_merger.py +2 -0
- mlrun/feature_store/retrieval/job.py +13 -4
- mlrun/feature_store/retrieval/local_merger.py +2 -0
- mlrun/feature_store/retrieval/spark_merger.py +24 -32
- mlrun/feature_store/steps.py +38 -19
- mlrun/features.py +6 -14
- mlrun/frameworks/_common/plan.py +3 -3
- mlrun/frameworks/_dl_common/loggers/tensorboard_logger.py +7 -12
- mlrun/frameworks/_ml_common/plan.py +1 -1
- 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 +4 -4
- mlrun/frameworks/pytorch/__init__.py +2 -2
- mlrun/frameworks/sklearn/__init__.py +1 -1
- mlrun/frameworks/sklearn/mlrun_interface.py +13 -3
- 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 +57 -12
- mlrun/launcher/__init__.py +1 -1
- mlrun/launcher/base.py +6 -5
- mlrun/launcher/client.py +13 -11
- mlrun/launcher/factory.py +1 -1
- mlrun/launcher/local.py +15 -5
- mlrun/launcher/remote.py +10 -3
- mlrun/lists.py +6 -2
- mlrun/model.py +297 -48
- mlrun/model_monitoring/__init__.py +1 -1
- mlrun/model_monitoring/api.py +152 -357
- mlrun/model_monitoring/applications/__init__.py +10 -0
- mlrun/model_monitoring/applications/_application_steps.py +190 -0
- mlrun/model_monitoring/applications/base.py +108 -0
- mlrun/model_monitoring/applications/context.py +341 -0
- mlrun/model_monitoring/{evidently_application.py → applications/evidently_base.py} +27 -22
- mlrun/model_monitoring/applications/histogram_data_drift.py +227 -91
- mlrun/model_monitoring/applications/results.py +99 -0
- mlrun/model_monitoring/controller.py +130 -303
- mlrun/model_monitoring/{stores/models/sqlite.py → db/__init__.py} +5 -10
- mlrun/model_monitoring/db/stores/__init__.py +136 -0
- mlrun/model_monitoring/db/stores/base/__init__.py +15 -0
- mlrun/model_monitoring/db/stores/base/store.py +213 -0
- mlrun/model_monitoring/db/stores/sqldb/__init__.py +13 -0
- mlrun/model_monitoring/db/stores/sqldb/models/__init__.py +71 -0
- mlrun/model_monitoring/db/stores/sqldb/models/base.py +190 -0
- mlrun/model_monitoring/db/stores/sqldb/models/mysql.py +103 -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 +659 -0
- mlrun/model_monitoring/db/stores/v3io_kv/__init__.py +13 -0
- mlrun/model_monitoring/db/stores/v3io_kv/kv_store.py +726 -0
- mlrun/model_monitoring/db/tsdb/__init__.py +105 -0
- mlrun/model_monitoring/db/tsdb/base.py +448 -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 +298 -0
- mlrun/model_monitoring/db/tsdb/tdengine/stream_graph_steps.py +42 -0
- mlrun/model_monitoring/db/tsdb/tdengine/tdengine_connector.py +522 -0
- mlrun/model_monitoring/db/tsdb/v3io/__init__.py +15 -0
- mlrun/model_monitoring/db/tsdb/v3io/stream_graph_steps.py +158 -0
- mlrun/model_monitoring/db/tsdb/v3io/v3io_connector.py +849 -0
- mlrun/model_monitoring/features_drift_table.py +34 -22
- mlrun/model_monitoring/helpers.py +177 -39
- mlrun/model_monitoring/model_endpoint.py +3 -2
- mlrun/model_monitoring/stream_processing.py +165 -398
- mlrun/model_monitoring/tracking_policy.py +7 -1
- mlrun/model_monitoring/writer.py +161 -125
- mlrun/package/packagers/default_packager.py +2 -2
- mlrun/package/packagers_manager.py +1 -0
- mlrun/package/utils/_formatter.py +2 -2
- mlrun/platforms/__init__.py +11 -10
- mlrun/platforms/iguazio.py +67 -228
- mlrun/projects/__init__.py +6 -1
- mlrun/projects/operations.py +47 -20
- mlrun/projects/pipelines.py +396 -249
- mlrun/projects/project.py +1176 -406
- mlrun/render.py +28 -22
- mlrun/run.py +208 -181
- mlrun/runtimes/__init__.py +76 -11
- mlrun/runtimes/base.py +54 -24
- mlrun/runtimes/daskjob.py +9 -2
- mlrun/runtimes/databricks_job/databricks_runtime.py +1 -0
- mlrun/runtimes/databricks_job/databricks_wrapper.py +1 -1
- mlrun/runtimes/funcdoc.py +1 -29
- mlrun/runtimes/kubejob.py +34 -128
- mlrun/runtimes/local.py +39 -10
- 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 +769 -0
- mlrun/runtimes/nuclio/application/__init__.py +15 -0
- mlrun/runtimes/nuclio/application/application.py +758 -0
- mlrun/runtimes/nuclio/application/reverse_proxy.go +95 -0
- mlrun/runtimes/nuclio/function.py +188 -68
- mlrun/runtimes/nuclio/serving.py +57 -60
- mlrun/runtimes/pod.py +191 -58
- mlrun/runtimes/remotesparkjob.py +11 -8
- mlrun/runtimes/sparkjob/spark3job.py +17 -18
- mlrun/runtimes/utils.py +40 -73
- mlrun/secrets.py +6 -2
- mlrun/serving/__init__.py +8 -1
- mlrun/serving/remote.py +2 -3
- mlrun/serving/routers.py +89 -64
- mlrun/serving/server.py +54 -26
- mlrun/serving/states.py +187 -56
- mlrun/serving/utils.py +19 -11
- mlrun/serving/v2_serving.py +136 -63
- mlrun/track/tracker.py +2 -1
- mlrun/track/trackers/mlflow_tracker.py +5 -0
- mlrun/utils/async_http.py +26 -6
- mlrun/utils/db.py +18 -0
- mlrun/utils/helpers.py +375 -105
- mlrun/utils/http.py +2 -2
- mlrun/utils/logger.py +75 -9
- mlrun/utils/notifications/notification/__init__.py +14 -10
- mlrun/utils/notifications/notification/base.py +48 -0
- mlrun/utils/notifications/notification/console.py +2 -0
- mlrun/utils/notifications/notification/git.py +24 -1
- mlrun/utils/notifications/notification/ipython.py +2 -0
- mlrun/utils/notifications/notification/slack.py +96 -21
- mlrun/utils/notifications/notification/webhook.py +63 -2
- mlrun/utils/notifications/notification_pusher.py +146 -16
- mlrun/utils/regex.py +9 -0
- mlrun/utils/retryer.py +3 -2
- mlrun/utils/v3io_clients.py +2 -3
- mlrun/utils/version/version.json +2 -2
- mlrun-1.7.2.dist-info/METADATA +390 -0
- mlrun-1.7.2.dist-info/RECORD +351 -0
- {mlrun-1.7.0rc4.dist-info → mlrun-1.7.2.dist-info}/WHEEL +1 -1
- mlrun/feature_store/retrieval/conversion.py +0 -271
- mlrun/kfpops.py +0 -868
- mlrun/model_monitoring/application.py +0 -310
- mlrun/model_monitoring/batch.py +0 -974
- mlrun/model_monitoring/controller_handler.py +0 -37
- mlrun/model_monitoring/prometheus.py +0 -216
- mlrun/model_monitoring/stores/__init__.py +0 -111
- mlrun/model_monitoring/stores/kv_model_endpoint_store.py +0 -574
- mlrun/model_monitoring/stores/model_endpoint_store.py +0 -145
- mlrun/model_monitoring/stores/models/__init__.py +0 -27
- mlrun/model_monitoring/stores/models/base.py +0 -84
- mlrun/model_monitoring/stores/sql_model_endpoint_store.py +0 -382
- mlrun/platforms/other.py +0 -305
- mlrun-1.7.0rc4.dist-info/METADATA +0 -269
- mlrun-1.7.0rc4.dist-info/RECORD +0 -321
- {mlrun-1.7.0rc4.dist-info → mlrun-1.7.2.dist-info}/LICENSE +0 -0
- {mlrun-1.7.0rc4.dist-info → mlrun-1.7.2.dist-info}/entry_points.txt +0 -0
- {mlrun-1.7.0rc4.dist-info → mlrun-1.7.2.dist-info}/top_level.txt +0 -0
mlrun/projects/project.py
CHANGED
|
@@ -11,12 +11,14 @@
|
|
|
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
|
|
17
18
|
import http
|
|
18
19
|
import importlib.util as imputil
|
|
19
20
|
import json
|
|
21
|
+
import os
|
|
20
22
|
import pathlib
|
|
21
23
|
import shutil
|
|
22
24
|
import tempfile
|
|
@@ -24,26 +26,39 @@ import typing
|
|
|
24
26
|
import uuid
|
|
25
27
|
import warnings
|
|
26
28
|
import zipfile
|
|
29
|
+
from copy import deepcopy
|
|
27
30
|
from os import environ, makedirs, path
|
|
28
31
|
from typing import Callable, Optional, Union
|
|
29
32
|
|
|
30
33
|
import dotenv
|
|
31
34
|
import git
|
|
32
35
|
import git.exc
|
|
33
|
-
import
|
|
34
|
-
import
|
|
36
|
+
import mlrun_pipelines.common.models
|
|
37
|
+
import mlrun_pipelines.mounts
|
|
38
|
+
import nuclio.utils
|
|
35
39
|
import requests
|
|
36
40
|
import yaml
|
|
41
|
+
from mlrun_pipelines.models import PipelineNodeWrapper
|
|
37
42
|
|
|
43
|
+
import mlrun.common.formatters
|
|
38
44
|
import mlrun.common.helpers
|
|
45
|
+
import mlrun.common.runtimes.constants
|
|
46
|
+
import mlrun.common.schemas.artifact
|
|
39
47
|
import mlrun.common.schemas.model_monitoring.constants as mm_constants
|
|
40
48
|
import mlrun.db
|
|
41
49
|
import mlrun.errors
|
|
42
50
|
import mlrun.k8s_utils
|
|
51
|
+
import mlrun.lists
|
|
52
|
+
import mlrun.model_monitoring.applications as mm_app
|
|
43
53
|
import mlrun.runtimes
|
|
54
|
+
import mlrun.runtimes.nuclio.api_gateway
|
|
44
55
|
import mlrun.runtimes.pod
|
|
45
56
|
import mlrun.runtimes.utils
|
|
57
|
+
import mlrun.serving
|
|
58
|
+
import mlrun.utils
|
|
46
59
|
import mlrun.utils.regex
|
|
60
|
+
from mlrun.alerts.alert import AlertConfig
|
|
61
|
+
from mlrun.common.schemas.alert import AlertTemplate
|
|
47
62
|
from mlrun.datastore.datastore_profile import DatastoreProfile, DatastoreProfile2Json
|
|
48
63
|
from mlrun.runtimes.nuclio.function import RemoteRuntime
|
|
49
64
|
|
|
@@ -52,20 +67,9 @@ from ..artifacts.manager import ArtifactManager, dict_to_artifact, extend_artifa
|
|
|
52
67
|
from ..datastore import store_manager
|
|
53
68
|
from ..features import Feature
|
|
54
69
|
from ..model import EntrypointParam, ImageBuilder, ModelObj
|
|
55
|
-
from ..model_monitoring.application import (
|
|
56
|
-
ModelMonitoringApplicationBase,
|
|
57
|
-
PushToMonitoringWriter,
|
|
58
|
-
)
|
|
59
70
|
from ..run import code_to_function, get_object, import_function, new_function
|
|
60
71
|
from ..secrets import SecretsStore
|
|
61
|
-
from ..utils import
|
|
62
|
-
is_ipython,
|
|
63
|
-
is_legacy_artifact,
|
|
64
|
-
is_relative_path,
|
|
65
|
-
is_yaml_path,
|
|
66
|
-
logger,
|
|
67
|
-
update_in,
|
|
68
|
-
)
|
|
72
|
+
from ..utils import is_jupyter, is_relative_path, is_yaml_path, logger, update_in
|
|
69
73
|
from ..utils.clones import (
|
|
70
74
|
add_credentials_git_remote_url,
|
|
71
75
|
clone_git,
|
|
@@ -73,7 +77,10 @@ from ..utils.clones import (
|
|
|
73
77
|
clone_zip,
|
|
74
78
|
get_repo_url,
|
|
75
79
|
)
|
|
76
|
-
from ..utils.helpers import
|
|
80
|
+
from ..utils.helpers import (
|
|
81
|
+
ensure_git_branch,
|
|
82
|
+
resolve_git_reference_from_source,
|
|
83
|
+
)
|
|
77
84
|
from ..utils.notifications import CustomNotificationPusher, NotificationTypes
|
|
78
85
|
from .operations import (
|
|
79
86
|
BuildStatus,
|
|
@@ -127,6 +134,7 @@ def new_project(
|
|
|
127
134
|
save: bool = True,
|
|
128
135
|
overwrite: bool = False,
|
|
129
136
|
parameters: dict = None,
|
|
137
|
+
default_function_node_selector: dict = None,
|
|
130
138
|
) -> "MlrunProject":
|
|
131
139
|
"""Create a new MLRun project, optionally load it from a yaml/zip/git template
|
|
132
140
|
|
|
@@ -137,11 +145,15 @@ def new_project(
|
|
|
137
145
|
example::
|
|
138
146
|
|
|
139
147
|
# create a project with local and hub functions, a workflow, and an artifact
|
|
140
|
-
project = mlrun.new_project(
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
project.
|
|
144
|
-
|
|
148
|
+
project = mlrun.new_project(
|
|
149
|
+
"myproj", "./", init_git=True, description="my new project"
|
|
150
|
+
)
|
|
151
|
+
project.set_function(
|
|
152
|
+
"prep_data.py", "prep-data", image="mlrun/mlrun", handler="prep_data"
|
|
153
|
+
)
|
|
154
|
+
project.set_function("hub://auto-trainer", "train")
|
|
155
|
+
project.set_artifact("data", Artifact(target_path=data_url))
|
|
156
|
+
project.set_workflow("main", "./myflow.py")
|
|
145
157
|
project.save()
|
|
146
158
|
|
|
147
159
|
# run the "main" workflow (watch=True to wait for run completion)
|
|
@@ -151,19 +163,25 @@ def new_project(
|
|
|
151
163
|
|
|
152
164
|
# create a new project from a zip template (can also use yaml/git templates)
|
|
153
165
|
# initialize a local git, and register the git remote path
|
|
154
|
-
project = mlrun.new_project(
|
|
155
|
-
|
|
156
|
-
|
|
166
|
+
project = mlrun.new_project(
|
|
167
|
+
"myproj",
|
|
168
|
+
"./",
|
|
169
|
+
init_git=True,
|
|
170
|
+
remote="git://github.com/mlrun/project-demo.git",
|
|
171
|
+
from_template="http://mysite/proj.zip",
|
|
172
|
+
)
|
|
157
173
|
project.run("main", watch=True)
|
|
158
174
|
|
|
159
175
|
|
|
160
176
|
example using project_setup.py to init the project objects::
|
|
161
177
|
|
|
162
178
|
def setup(project):
|
|
163
|
-
project.set_function(
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
project.
|
|
179
|
+
project.set_function(
|
|
180
|
+
"prep_data.py", "prep-data", image="mlrun/mlrun", handler="prep_data"
|
|
181
|
+
)
|
|
182
|
+
project.set_function("hub://auto-trainer", "train")
|
|
183
|
+
project.set_artifact("data", Artifact(target_path=data_url))
|
|
184
|
+
project.set_workflow("main", "./myflow.py")
|
|
167
185
|
return project
|
|
168
186
|
|
|
169
187
|
|
|
@@ -180,6 +198,7 @@ def new_project(
|
|
|
180
198
|
:param overwrite: overwrite project using 'cascade' deletion strategy (deletes project resources)
|
|
181
199
|
if project with name exists
|
|
182
200
|
:param parameters: key/value pairs to add to the project.spec.params
|
|
201
|
+
:param default_function_node_selector: defines the default node selector for scheduling functions within the project
|
|
183
202
|
|
|
184
203
|
:returns: project object
|
|
185
204
|
"""
|
|
@@ -192,14 +211,16 @@ def new_project(
|
|
|
192
211
|
"Unsupported option, cannot use subpath argument with project templates"
|
|
193
212
|
)
|
|
194
213
|
if from_template.endswith(".yaml"):
|
|
195
|
-
project = _load_project_file(
|
|
214
|
+
project = _load_project_file(
|
|
215
|
+
from_template, name, secrets, allow_cross_project=True
|
|
216
|
+
)
|
|
196
217
|
elif from_template.startswith("git://"):
|
|
197
218
|
clone_git(from_template, context, secrets, clone=True)
|
|
198
219
|
shutil.rmtree(path.join(context, ".git"))
|
|
199
|
-
project = _load_project_dir(context, name)
|
|
220
|
+
project = _load_project_dir(context, name, allow_cross_project=True)
|
|
200
221
|
elif from_template.endswith(".zip"):
|
|
201
222
|
clone_zip(from_template, context, secrets)
|
|
202
|
-
project = _load_project_dir(context, name)
|
|
223
|
+
project = _load_project_dir(context, name, allow_cross_project=True)
|
|
203
224
|
else:
|
|
204
225
|
raise ValueError("template must be a path to .yaml or .zip file")
|
|
205
226
|
project.metadata.name = name
|
|
@@ -226,6 +247,10 @@ def new_project(
|
|
|
226
247
|
project.spec.origin_url = url
|
|
227
248
|
if description:
|
|
228
249
|
project.spec.description = description
|
|
250
|
+
|
|
251
|
+
if default_function_node_selector:
|
|
252
|
+
project.spec.default_function_node_selector = default_function_node_selector
|
|
253
|
+
|
|
229
254
|
if parameters:
|
|
230
255
|
# Enable setting project parameters at load time, can be used to customize the project_setup
|
|
231
256
|
for key, val in parameters.items():
|
|
@@ -276,6 +301,7 @@ def load_project(
|
|
|
276
301
|
save: bool = True,
|
|
277
302
|
sync_functions: bool = False,
|
|
278
303
|
parameters: dict = None,
|
|
304
|
+
allow_cross_project: bool = None,
|
|
279
305
|
) -> "MlrunProject":
|
|
280
306
|
"""Load an MLRun project from git or tar or dir
|
|
281
307
|
|
|
@@ -289,7 +315,7 @@ def load_project(
|
|
|
289
315
|
# When using git as the url source the context directory must be an empty or
|
|
290
316
|
# non-existent folder as the git repo will be cloned there
|
|
291
317
|
project = load_project("./demo_proj", "git://github.com/mlrun/project-demo.git")
|
|
292
|
-
project.run("main", arguments={
|
|
318
|
+
project.run("main", arguments={"data": data_url})
|
|
293
319
|
|
|
294
320
|
|
|
295
321
|
project_setup.py example::
|
|
@@ -322,6 +348,8 @@ def load_project(
|
|
|
322
348
|
:param save: whether to save the created project and artifact in the DB
|
|
323
349
|
:param sync_functions: sync the project's functions into the project object (will be saved to the DB if save=True)
|
|
324
350
|
:param parameters: key/value pairs to add to the project.spec.params
|
|
351
|
+
:param allow_cross_project: if True, override the loaded project name. This flag ensures awareness of
|
|
352
|
+
loading an existing project yaml as a baseline for a new project with a different name
|
|
325
353
|
|
|
326
354
|
:returns: project object
|
|
327
355
|
"""
|
|
@@ -337,7 +365,7 @@ def load_project(
|
|
|
337
365
|
if url:
|
|
338
366
|
url = str(url) # to support path objects
|
|
339
367
|
if is_yaml_path(url):
|
|
340
|
-
project = _load_project_file(url, name, secrets)
|
|
368
|
+
project = _load_project_file(url, name, secrets, allow_cross_project)
|
|
341
369
|
project.spec.context = context
|
|
342
370
|
elif url.startswith("git://"):
|
|
343
371
|
url, repo = clone_git(url, context, secrets, clone)
|
|
@@ -364,7 +392,7 @@ def load_project(
|
|
|
364
392
|
repo, url = init_repo(context, url, init_git)
|
|
365
393
|
|
|
366
394
|
if not project:
|
|
367
|
-
project = _load_project_dir(context, name, subpath)
|
|
395
|
+
project = _load_project_dir(context, name, subpath, allow_cross_project)
|
|
368
396
|
|
|
369
397
|
if not project.metadata.name:
|
|
370
398
|
raise ValueError("Project name must be specified")
|
|
@@ -418,6 +446,7 @@ def get_or_create_project(
|
|
|
418
446
|
from_template: str = None,
|
|
419
447
|
save: bool = True,
|
|
420
448
|
parameters: dict = None,
|
|
449
|
+
allow_cross_project: bool = None,
|
|
421
450
|
) -> "MlrunProject":
|
|
422
451
|
"""Load a project from MLRun DB, or create/import if it does not exist
|
|
423
452
|
|
|
@@ -428,9 +457,11 @@ def get_or_create_project(
|
|
|
428
457
|
Usage example::
|
|
429
458
|
|
|
430
459
|
# load project from the DB (if exist) or the source repo
|
|
431
|
-
project = get_or_create_project(
|
|
460
|
+
project = get_or_create_project(
|
|
461
|
+
"myproj", "./", "git://github.com/mlrun/demo-xgb-project.git"
|
|
462
|
+
)
|
|
432
463
|
project.pull("development") # pull the latest code from git
|
|
433
|
-
project.run("main", arguments={
|
|
464
|
+
project.run("main", arguments={"data": data_url}) # run the workflow "main"
|
|
434
465
|
|
|
435
466
|
|
|
436
467
|
project_setup.py example::
|
|
@@ -460,12 +491,12 @@ def get_or_create_project(
|
|
|
460
491
|
:param from_template: path to project YAML file that will be used as from_template (for new projects)
|
|
461
492
|
:param save: whether to save the created project in the DB
|
|
462
493
|
:param parameters: key/value pairs to add to the project.spec.params
|
|
494
|
+
:param allow_cross_project: if True, override the loaded project name. This flag ensures awareness of
|
|
495
|
+
loading an existing project yaml as a baseline for a new project with a different name
|
|
463
496
|
|
|
464
497
|
:returns: project object
|
|
465
498
|
"""
|
|
466
499
|
context = context or "./"
|
|
467
|
-
spec_path = path.join(context, subpath or "", "project.yaml")
|
|
468
|
-
load_from_path = url or path.isfile(spec_path)
|
|
469
500
|
try:
|
|
470
501
|
# load project from the DB.
|
|
471
502
|
# use `name` as `url` as we load the project from the DB
|
|
@@ -481,17 +512,26 @@ def get_or_create_project(
|
|
|
481
512
|
# only loading project from db so no need to save it
|
|
482
513
|
save=False,
|
|
483
514
|
parameters=parameters,
|
|
515
|
+
allow_cross_project=allow_cross_project,
|
|
484
516
|
)
|
|
485
|
-
logger.info("Project loaded successfully", project_name=name)
|
|
517
|
+
logger.info("Project loaded successfully", project_name=project.name)
|
|
486
518
|
return project
|
|
487
|
-
|
|
488
519
|
except mlrun.errors.MLRunNotFoundError:
|
|
489
|
-
logger.debug(
|
|
520
|
+
logger.debug(
|
|
521
|
+
"Project not found in db", project_name=name, user_project=user_project
|
|
522
|
+
)
|
|
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
|
|
494
|
-
logger.info(
|
|
529
|
+
logger.info(
|
|
530
|
+
"Loading project from path",
|
|
531
|
+
project_name=name,
|
|
532
|
+
user_project=user_project,
|
|
533
|
+
path=url or context,
|
|
534
|
+
)
|
|
495
535
|
project = load_project(
|
|
496
536
|
context,
|
|
497
537
|
url,
|
|
@@ -503,11 +543,12 @@ def get_or_create_project(
|
|
|
503
543
|
user_project=user_project,
|
|
504
544
|
save=save,
|
|
505
545
|
parameters=parameters,
|
|
546
|
+
allow_cross_project=allow_cross_project,
|
|
506
547
|
)
|
|
507
548
|
|
|
508
549
|
logger.info(
|
|
509
550
|
"Project loaded successfully",
|
|
510
|
-
project_name=name,
|
|
551
|
+
project_name=project.name,
|
|
511
552
|
path=url or context,
|
|
512
553
|
stored_in_db=save,
|
|
513
554
|
)
|
|
@@ -525,7 +566,9 @@ def get_or_create_project(
|
|
|
525
566
|
save=save,
|
|
526
567
|
parameters=parameters,
|
|
527
568
|
)
|
|
528
|
-
logger.info(
|
|
569
|
+
logger.info(
|
|
570
|
+
"Project created successfully", project_name=project.name, stored_in_db=save
|
|
571
|
+
)
|
|
529
572
|
return project
|
|
530
573
|
|
|
531
574
|
|
|
@@ -563,6 +606,10 @@ def _run_project_setup(
|
|
|
563
606
|
if hasattr(mod, "setup"):
|
|
564
607
|
try:
|
|
565
608
|
project = getattr(mod, "setup")(project)
|
|
609
|
+
if not project or not isinstance(project, mlrun.projects.MlrunProject):
|
|
610
|
+
raise ValueError(
|
|
611
|
+
"MLRun project_setup:setup() must return a project object"
|
|
612
|
+
)
|
|
566
613
|
except Exception as exc:
|
|
567
614
|
logger.error(
|
|
568
615
|
"Failed to run project_setup script",
|
|
@@ -573,11 +620,13 @@ def _run_project_setup(
|
|
|
573
620
|
if save:
|
|
574
621
|
project.save()
|
|
575
622
|
else:
|
|
576
|
-
logger.warn(
|
|
623
|
+
logger.warn(
|
|
624
|
+
f"skipping setup, setup() handler was not found in {path.basename(setup_file_path)}"
|
|
625
|
+
)
|
|
577
626
|
return project
|
|
578
627
|
|
|
579
628
|
|
|
580
|
-
def _load_project_dir(context, name="", subpath=""):
|
|
629
|
+
def _load_project_dir(context, name="", subpath="", allow_cross_project=None):
|
|
581
630
|
subpath_str = subpath or ""
|
|
582
631
|
|
|
583
632
|
# support both .yaml and .yml file extensions
|
|
@@ -591,7 +640,7 @@ def _load_project_dir(context, name="", subpath=""):
|
|
|
591
640
|
with open(project_file_path) as fp:
|
|
592
641
|
data = fp.read()
|
|
593
642
|
struct = yaml.load(data, Loader=yaml.FullLoader)
|
|
594
|
-
project = _project_instance_from_struct(struct, name)
|
|
643
|
+
project = _project_instance_from_struct(struct, name, allow_cross_project)
|
|
595
644
|
project.spec.context = context
|
|
596
645
|
elif function_files := glob.glob(function_file_path):
|
|
597
646
|
function_path = function_files[0]
|
|
@@ -661,22 +710,45 @@ def _load_project_from_db(url, secrets, user_project=False):
|
|
|
661
710
|
|
|
662
711
|
def _delete_project_from_db(project_name, secrets, deletion_strategy):
|
|
663
712
|
db = mlrun.db.get_run_db(secrets=secrets)
|
|
664
|
-
|
|
713
|
+
db.delete_project(project_name, deletion_strategy=deletion_strategy)
|
|
665
714
|
|
|
666
715
|
|
|
667
|
-
def _load_project_file(url, name="", secrets=None):
|
|
716
|
+
def _load_project_file(url, name="", secrets=None, allow_cross_project=None):
|
|
668
717
|
try:
|
|
669
718
|
obj = get_object(url, secrets)
|
|
670
719
|
except FileNotFoundError as exc:
|
|
671
720
|
raise FileNotFoundError(f"cant find project file at {url}") from exc
|
|
672
721
|
struct = yaml.load(obj, Loader=yaml.FullLoader)
|
|
673
|
-
return _project_instance_from_struct(struct, name)
|
|
674
|
-
|
|
722
|
+
return _project_instance_from_struct(struct, name, allow_cross_project)
|
|
723
|
+
|
|
724
|
+
|
|
725
|
+
def _project_instance_from_struct(struct, name, allow_cross_project):
|
|
726
|
+
name_from_struct = struct.get("metadata", {}).get("name", "")
|
|
727
|
+
if name and name_from_struct and name_from_struct != name:
|
|
728
|
+
error_message = (
|
|
729
|
+
f"Project name mismatch, {name_from_struct} != {name}, project is loaded from {name_from_struct} "
|
|
730
|
+
f"project yaml. To prevent/allow this, you can take one of the following actions:\n"
|
|
731
|
+
"1. Set the `allow_cross_project=True` when loading the project.\n"
|
|
732
|
+
f"2. Delete the existing project yaml, or ensure its name is equal to {name}.\n"
|
|
733
|
+
"3. Use different project context dir."
|
|
734
|
+
)
|
|
675
735
|
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
736
|
+
if allow_cross_project is None:
|
|
737
|
+
# TODO: Remove this warning in version 1.9.0 and also fix cli to support allow_cross_project
|
|
738
|
+
warnings.warn(
|
|
739
|
+
f"Project {name=} is different than specified on the context's project yaml. "
|
|
740
|
+
"This behavior is deprecated and will not be supported from version 1.9.0."
|
|
741
|
+
)
|
|
742
|
+
logger.warn(error_message)
|
|
743
|
+
elif allow_cross_project:
|
|
744
|
+
logger.debug(
|
|
745
|
+
"Project name is different than specified on the context's project yaml. Overriding.",
|
|
746
|
+
existing_name=name_from_struct,
|
|
747
|
+
overriding_name=name,
|
|
748
|
+
)
|
|
749
|
+
else:
|
|
750
|
+
raise ValueError(error_message)
|
|
751
|
+
struct.setdefault("metadata", {})["name"] = name or name_from_struct
|
|
680
752
|
return MlrunProject.from_dict(struct)
|
|
681
753
|
|
|
682
754
|
|
|
@@ -759,6 +831,7 @@ class ProjectSpec(ModelObj):
|
|
|
759
831
|
default_image=None,
|
|
760
832
|
build=None,
|
|
761
833
|
custom_packagers: list[tuple[str, bool]] = None,
|
|
834
|
+
default_function_node_selector=None,
|
|
762
835
|
):
|
|
763
836
|
self.repo = None
|
|
764
837
|
|
|
@@ -798,6 +871,7 @@ class ProjectSpec(ModelObj):
|
|
|
798
871
|
# in a tuple where the first index is the packager module's path (str) and the second is a flag (bool) for
|
|
799
872
|
# whether it is mandatory for a run (raise exception on collection error) or not.
|
|
800
873
|
self.custom_packagers = custom_packagers or []
|
|
874
|
+
self._default_function_node_selector = default_function_node_selector or None
|
|
801
875
|
|
|
802
876
|
@property
|
|
803
877
|
def source(self) -> str:
|
|
@@ -933,19 +1007,29 @@ class ProjectSpec(ModelObj):
|
|
|
933
1007
|
|
|
934
1008
|
artifacts_dict = {}
|
|
935
1009
|
for artifact in artifacts:
|
|
936
|
-
|
|
1010
|
+
invalid_object_type = not isinstance(artifact, dict) and not hasattr(
|
|
1011
|
+
artifact, "to_dict"
|
|
1012
|
+
)
|
|
1013
|
+
is_artifact_model = not isinstance(artifact, dict) and hasattr(
|
|
1014
|
+
artifact, "to_dict"
|
|
1015
|
+
)
|
|
1016
|
+
|
|
1017
|
+
if invalid_object_type:
|
|
937
1018
|
raise ValueError("artifacts must be a dict or class")
|
|
938
|
-
|
|
939
|
-
# Support legacy artifacts
|
|
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", "")
|
|
944
|
-
if not key:
|
|
945
|
-
raise ValueError('artifacts "key" must be specified')
|
|
946
|
-
else:
|
|
1019
|
+
elif is_artifact_model:
|
|
947
1020
|
key = artifact.key
|
|
948
1021
|
artifact = artifact.to_dict()
|
|
1022
|
+
else: # artifact is a dict
|
|
1023
|
+
# imported/legacy artifacts don't have metadata,spec,status fields
|
|
1024
|
+
key_field = (
|
|
1025
|
+
"key"
|
|
1026
|
+
if _is_imported_artifact(artifact)
|
|
1027
|
+
or mlrun.utils.is_legacy_artifact(artifact)
|
|
1028
|
+
else "metadata.key"
|
|
1029
|
+
)
|
|
1030
|
+
key = mlrun.utils.get_in(artifact, key_field, "")
|
|
1031
|
+
if not key:
|
|
1032
|
+
raise ValueError(f'artifacts "{key_field}" must be specified')
|
|
949
1033
|
|
|
950
1034
|
artifacts_dict[key] = artifact
|
|
951
1035
|
|
|
@@ -962,6 +1046,14 @@ class ProjectSpec(ModelObj):
|
|
|
962
1046
|
if key in self._artifacts:
|
|
963
1047
|
del self._artifacts[key]
|
|
964
1048
|
|
|
1049
|
+
@property
|
|
1050
|
+
def default_function_node_selector(self):
|
|
1051
|
+
return self._default_function_node_selector
|
|
1052
|
+
|
|
1053
|
+
@default_function_node_selector.setter
|
|
1054
|
+
def default_function_node_selector(self, node_selector: dict[str, str]):
|
|
1055
|
+
self._default_function_node_selector = deepcopy(node_selector)
|
|
1056
|
+
|
|
965
1057
|
@property
|
|
966
1058
|
def build(self) -> ImageBuilder:
|
|
967
1059
|
return self._build
|
|
@@ -1219,6 +1311,14 @@ class MlrunProject(ModelObj):
|
|
|
1219
1311
|
def description(self, description):
|
|
1220
1312
|
self.spec.description = description
|
|
1221
1313
|
|
|
1314
|
+
@property
|
|
1315
|
+
def default_function_node_selector(self) -> dict:
|
|
1316
|
+
return self.spec.default_function_node_selector
|
|
1317
|
+
|
|
1318
|
+
@default_function_node_selector.setter
|
|
1319
|
+
def default_function_node_selector(self, default_function_node_selector):
|
|
1320
|
+
self.spec.default_function_node_selector = default_function_node_selector
|
|
1321
|
+
|
|
1222
1322
|
@property
|
|
1223
1323
|
def default_image(self) -> str:
|
|
1224
1324
|
return self.spec.default_image
|
|
@@ -1333,13 +1433,15 @@ class MlrunProject(ModelObj):
|
|
|
1333
1433
|
example::
|
|
1334
1434
|
|
|
1335
1435
|
# register a simple file artifact
|
|
1336
|
-
project.set_artifact(
|
|
1436
|
+
project.set_artifact("data", target_path=data_url)
|
|
1337
1437
|
# register a model artifact
|
|
1338
|
-
project.set_artifact(
|
|
1438
|
+
project.set_artifact(
|
|
1439
|
+
"model", ModelArtifact(model_file="model.pkl"), target_path=model_dir_url
|
|
1440
|
+
)
|
|
1339
1441
|
|
|
1340
1442
|
# register a path to artifact package (will be imported on project load)
|
|
1341
1443
|
# to generate such package use `artifact.export(target_path)`
|
|
1342
|
-
project.set_artifact(
|
|
1444
|
+
project.set_artifact("model", "https://mystuff.com/models/mymodel.zip")
|
|
1343
1445
|
|
|
1344
1446
|
:param key: artifact key/name
|
|
1345
1447
|
:param artifact: mlrun Artifact object/dict (or its subclasses) or path to artifact
|
|
@@ -1374,14 +1476,7 @@ class MlrunProject(ModelObj):
|
|
|
1374
1476
|
artifact_path = mlrun.utils.helpers.template_artifact_path(
|
|
1375
1477
|
self.spec.artifact_path or mlrun.mlconf.artifact_path, self.metadata.name
|
|
1376
1478
|
)
|
|
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
|
-
)
|
|
1479
|
+
project_tag = self._get_project_tag()
|
|
1385
1480
|
for artifact_dict in self.spec.artifacts:
|
|
1386
1481
|
if _is_imported_artifact(artifact_dict):
|
|
1387
1482
|
import_from = artifact_dict["import_from"]
|
|
@@ -1401,8 +1496,23 @@ class MlrunProject(ModelObj):
|
|
|
1401
1496
|
artifact.src_path = path.join(
|
|
1402
1497
|
self.spec.get_code_path(), artifact.src_path
|
|
1403
1498
|
)
|
|
1499
|
+
producer, is_retained_producer = self._resolve_artifact_producer(
|
|
1500
|
+
artifact, project_tag
|
|
1501
|
+
)
|
|
1502
|
+
# log the artifact only if it doesn't already exist
|
|
1503
|
+
if (
|
|
1504
|
+
producer.name != self.metadata.name
|
|
1505
|
+
and self._resolve_existing_artifact(
|
|
1506
|
+
artifact,
|
|
1507
|
+
)
|
|
1508
|
+
):
|
|
1509
|
+
continue
|
|
1404
1510
|
artifact_manager.log_artifact(
|
|
1405
|
-
producer,
|
|
1511
|
+
producer,
|
|
1512
|
+
artifact,
|
|
1513
|
+
artifact_path=artifact_path,
|
|
1514
|
+
project=self.metadata.name,
|
|
1515
|
+
is_retained_producer=is_retained_producer,
|
|
1406
1516
|
)
|
|
1407
1517
|
|
|
1408
1518
|
def _get_artifact_manager(self):
|
|
@@ -1444,7 +1554,7 @@ class MlrunProject(ModelObj):
|
|
|
1444
1554
|
url = path.normpath(path.join(self.spec.get_code_path(), url))
|
|
1445
1555
|
|
|
1446
1556
|
if (not in_context or check_path_in_context) and not path.isfile(url):
|
|
1447
|
-
raise
|
|
1557
|
+
raise FileNotFoundError(f"{url} not found")
|
|
1448
1558
|
|
|
1449
1559
|
return url, in_context
|
|
1450
1560
|
|
|
@@ -1452,15 +1562,15 @@ class MlrunProject(ModelObj):
|
|
|
1452
1562
|
self,
|
|
1453
1563
|
item,
|
|
1454
1564
|
body=None,
|
|
1455
|
-
tag="",
|
|
1456
|
-
local_path="",
|
|
1457
|
-
artifact_path=None,
|
|
1458
|
-
format=None,
|
|
1459
|
-
upload=None,
|
|
1460
|
-
labels=None,
|
|
1461
|
-
target_path=None,
|
|
1565
|
+
tag: str = "",
|
|
1566
|
+
local_path: str = "",
|
|
1567
|
+
artifact_path: Optional[str] = None,
|
|
1568
|
+
format: Optional[str] = None,
|
|
1569
|
+
upload: Optional[bool] = None,
|
|
1570
|
+
labels: Optional[dict[str, str]] = None,
|
|
1571
|
+
target_path: Optional[str] = None,
|
|
1462
1572
|
**kwargs,
|
|
1463
|
-
):
|
|
1573
|
+
) -> Artifact:
|
|
1464
1574
|
"""Log an output artifact and optionally upload it to datastore
|
|
1465
1575
|
|
|
1466
1576
|
If the artifact already exists with the same key and tag, it will be overwritten.
|
|
@@ -1485,7 +1595,9 @@ class MlrunProject(ModelObj):
|
|
|
1485
1595
|
:param format: artifact file format: csv, png, ..
|
|
1486
1596
|
:param tag: version tag
|
|
1487
1597
|
:param target_path: absolute target path (instead of using artifact_path + local_path)
|
|
1488
|
-
:param upload: upload to datastore
|
|
1598
|
+
:param upload: Whether to upload the artifact to the datastore. If not provided, and the `local_path`
|
|
1599
|
+
is not a directory, upload occurs by default. Directories are uploaded only when this
|
|
1600
|
+
flag is explicitly set to `True`.
|
|
1489
1601
|
:param labels: a set of key/value labels to tag the artifact with
|
|
1490
1602
|
|
|
1491
1603
|
:returns: artifact object
|
|
@@ -1497,12 +1609,20 @@ class MlrunProject(ModelObj):
|
|
|
1497
1609
|
artifact_path = mlrun.utils.helpers.template_artifact_path(
|
|
1498
1610
|
artifact_path, self.metadata.name
|
|
1499
1611
|
)
|
|
1500
|
-
producer =
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
self.
|
|
1504
|
-
|
|
1505
|
-
|
|
1612
|
+
producer, is_retained_producer = self._resolve_artifact_producer(item)
|
|
1613
|
+
if producer.name != self.metadata.name:
|
|
1614
|
+
# the artifact producer is retained, log it only if it doesn't already exist
|
|
1615
|
+
if existing_artifact := self._resolve_existing_artifact(
|
|
1616
|
+
item,
|
|
1617
|
+
tag,
|
|
1618
|
+
):
|
|
1619
|
+
artifact_key = item if isinstance(item, str) else item.key
|
|
1620
|
+
logger.info(
|
|
1621
|
+
"Artifact already exists, skipping logging",
|
|
1622
|
+
key=artifact_key,
|
|
1623
|
+
tag=tag,
|
|
1624
|
+
)
|
|
1625
|
+
return existing_artifact
|
|
1506
1626
|
item = am.log_artifact(
|
|
1507
1627
|
producer,
|
|
1508
1628
|
item,
|
|
@@ -1514,10 +1634,29 @@ class MlrunProject(ModelObj):
|
|
|
1514
1634
|
upload=upload,
|
|
1515
1635
|
labels=labels,
|
|
1516
1636
|
target_path=target_path,
|
|
1637
|
+
project=self.metadata.name,
|
|
1638
|
+
is_retained_producer=is_retained_producer,
|
|
1517
1639
|
**kwargs,
|
|
1518
1640
|
)
|
|
1519
1641
|
return item
|
|
1520
1642
|
|
|
1643
|
+
def delete_artifact(
|
|
1644
|
+
self,
|
|
1645
|
+
item: Artifact,
|
|
1646
|
+
deletion_strategy: mlrun.common.schemas.artifact.ArtifactsDeletionStrategies = (
|
|
1647
|
+
mlrun.common.schemas.artifact.ArtifactsDeletionStrategies.metadata_only
|
|
1648
|
+
),
|
|
1649
|
+
secrets: dict = None,
|
|
1650
|
+
):
|
|
1651
|
+
"""Delete an artifact object in the DB and optionally delete the artifact data
|
|
1652
|
+
|
|
1653
|
+
:param item: Artifact object (can be any type, such as dataset, model, feature store).
|
|
1654
|
+
:param deletion_strategy: The artifact deletion strategy types.
|
|
1655
|
+
:param secrets: Credentials needed to access the artifact data.
|
|
1656
|
+
"""
|
|
1657
|
+
am = self._get_artifact_manager()
|
|
1658
|
+
am.delete_artifact(item, deletion_strategy, secrets)
|
|
1659
|
+
|
|
1521
1660
|
def log_dataset(
|
|
1522
1661
|
self,
|
|
1523
1662
|
key,
|
|
@@ -1532,7 +1671,7 @@ class MlrunProject(ModelObj):
|
|
|
1532
1671
|
stats=None,
|
|
1533
1672
|
target_path="",
|
|
1534
1673
|
extra_data=None,
|
|
1535
|
-
label_column: str = None,
|
|
1674
|
+
label_column: Optional[str] = None,
|
|
1536
1675
|
**kwargs,
|
|
1537
1676
|
) -> DatasetArtifact:
|
|
1538
1677
|
"""
|
|
@@ -1548,7 +1687,9 @@ class MlrunProject(ModelObj):
|
|
|
1548
1687
|
"age": [42, 52, 36, 24, 73],
|
|
1549
1688
|
"testScore": [25, 94, 57, 62, 70],
|
|
1550
1689
|
}
|
|
1551
|
-
df = pd.DataFrame(
|
|
1690
|
+
df = pd.DataFrame(
|
|
1691
|
+
raw_data, columns=["first_name", "last_name", "age", "testScore"]
|
|
1692
|
+
)
|
|
1552
1693
|
project.log_dataset("mydf", df=df, stats=True)
|
|
1553
1694
|
|
|
1554
1695
|
:param key: artifact key
|
|
@@ -1607,28 +1748,31 @@ class MlrunProject(ModelObj):
|
|
|
1607
1748
|
artifact_path=None,
|
|
1608
1749
|
upload=None,
|
|
1609
1750
|
labels=None,
|
|
1610
|
-
inputs: list[Feature] = None,
|
|
1611
|
-
outputs: list[Feature] = None,
|
|
1612
|
-
feature_vector: str = None,
|
|
1613
|
-
feature_weights: list = None,
|
|
1751
|
+
inputs: Optional[list[Feature]] = None,
|
|
1752
|
+
outputs: Optional[list[Feature]] = None,
|
|
1753
|
+
feature_vector: Optional[str] = None,
|
|
1754
|
+
feature_weights: Optional[list] = None,
|
|
1614
1755
|
training_set=None,
|
|
1615
1756
|
label_column=None,
|
|
1616
1757
|
extra_data=None,
|
|
1617
1758
|
**kwargs,
|
|
1618
|
-
):
|
|
1759
|
+
) -> ModelArtifact:
|
|
1619
1760
|
"""Log a model artifact and optionally upload it to datastore
|
|
1620
1761
|
|
|
1621
1762
|
If the model already exists with the same key and tag, it will be overwritten.
|
|
1622
1763
|
|
|
1623
1764
|
example::
|
|
1624
1765
|
|
|
1625
|
-
project.log_model(
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1766
|
+
project.log_model(
|
|
1767
|
+
"model",
|
|
1768
|
+
body=dumps(model),
|
|
1769
|
+
model_file="model.pkl",
|
|
1770
|
+
metrics=context.results,
|
|
1771
|
+
training_set=training_df,
|
|
1772
|
+
label_column="label",
|
|
1773
|
+
feature_vector=feature_vector_uri,
|
|
1774
|
+
labels={"app": "fraud"},
|
|
1775
|
+
)
|
|
1632
1776
|
|
|
1633
1777
|
:param key: artifact key or artifact class ()
|
|
1634
1778
|
:param body: will use the body as the artifact content
|
|
@@ -1738,14 +1882,16 @@ class MlrunProject(ModelObj):
|
|
|
1738
1882
|
artifact = get_artifact(spec)
|
|
1739
1883
|
with open(f"{temp_dir}/_body", "rb") as fp:
|
|
1740
1884
|
artifact.spec._body = fp.read()
|
|
1741
|
-
artifact.target_path = ""
|
|
1742
1885
|
|
|
1743
1886
|
# if the dataitem is not a file, it means we downloaded it from a remote source to a temp file,
|
|
1744
1887
|
# so we need to remove it after we're done with it
|
|
1745
1888
|
dataitem.remove_local()
|
|
1746
1889
|
|
|
1747
1890
|
return self.log_artifact(
|
|
1748
|
-
artifact,
|
|
1891
|
+
artifact,
|
|
1892
|
+
local_path=temp_dir,
|
|
1893
|
+
artifact_path=artifact_path,
|
|
1894
|
+
upload=True,
|
|
1749
1895
|
)
|
|
1750
1896
|
|
|
1751
1897
|
else:
|
|
@@ -1763,10 +1909,18 @@ class MlrunProject(ModelObj):
|
|
|
1763
1909
|
"""
|
|
1764
1910
|
context = context or self.spec.context
|
|
1765
1911
|
if context:
|
|
1766
|
-
project = _load_project_dir(
|
|
1912
|
+
project = _load_project_dir(
|
|
1913
|
+
context,
|
|
1914
|
+
self.metadata.name,
|
|
1915
|
+
self.spec.subpath,
|
|
1916
|
+
allow_cross_project=False,
|
|
1917
|
+
)
|
|
1767
1918
|
else:
|
|
1768
1919
|
project = _load_project_file(
|
|
1769
|
-
self.spec.origin_url,
|
|
1920
|
+
self.spec.origin_url,
|
|
1921
|
+
self.metadata.name,
|
|
1922
|
+
self._secrets,
|
|
1923
|
+
allow_cross_project=None,
|
|
1770
1924
|
)
|
|
1771
1925
|
project.spec.source = self.spec.source
|
|
1772
1926
|
project.spec.repo = self.spec.repo
|
|
@@ -1795,7 +1949,10 @@ class MlrunProject(ModelObj):
|
|
|
1795
1949
|
def set_model_monitoring_function(
|
|
1796
1950
|
self,
|
|
1797
1951
|
func: typing.Union[str, mlrun.runtimes.BaseRuntime, None] = None,
|
|
1798
|
-
application_class: typing.Union[
|
|
1952
|
+
application_class: typing.Union[
|
|
1953
|
+
str,
|
|
1954
|
+
mm_app.ModelMonitoringApplicationBase,
|
|
1955
|
+
] = None,
|
|
1799
1956
|
name: str = None,
|
|
1800
1957
|
image: str = None,
|
|
1801
1958
|
handler=None,
|
|
@@ -1811,6 +1968,7 @@ class MlrunProject(ModelObj):
|
|
|
1811
1968
|
call `fn.deploy()` where `fn` is the object returned by this method.
|
|
1812
1969
|
|
|
1813
1970
|
examples::
|
|
1971
|
+
|
|
1814
1972
|
project.set_model_monitoring_function(
|
|
1815
1973
|
name="myApp", application_class="MyApp", image="mlrun/mlrun"
|
|
1816
1974
|
)
|
|
@@ -1833,11 +1991,6 @@ class MlrunProject(ModelObj):
|
|
|
1833
1991
|
monitoring application's constructor.
|
|
1834
1992
|
"""
|
|
1835
1993
|
|
|
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
1994
|
function_object: RemoteRuntime = None
|
|
1842
1995
|
(
|
|
1843
1996
|
resolved_function_name,
|
|
@@ -1855,16 +2008,6 @@ class MlrunProject(ModelObj):
|
|
|
1855
2008
|
requirements_file,
|
|
1856
2009
|
**application_kwargs,
|
|
1857
2010
|
)
|
|
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
2011
|
# save to project spec
|
|
1869
2012
|
self.spec.set_function(resolved_function_name, function_object, func)
|
|
1870
2013
|
|
|
@@ -1873,7 +2016,10 @@ class MlrunProject(ModelObj):
|
|
|
1873
2016
|
def create_model_monitoring_function(
|
|
1874
2017
|
self,
|
|
1875
2018
|
func: str = None,
|
|
1876
|
-
application_class: typing.Union[
|
|
2019
|
+
application_class: typing.Union[
|
|
2020
|
+
str,
|
|
2021
|
+
mm_app.ModelMonitoringApplicationBase,
|
|
2022
|
+
] = None,
|
|
1877
2023
|
name: str = None,
|
|
1878
2024
|
image: str = None,
|
|
1879
2025
|
handler: str = None,
|
|
@@ -1887,8 +2033,10 @@ class MlrunProject(ModelObj):
|
|
|
1887
2033
|
Create a monitoring function object without setting it to the project
|
|
1888
2034
|
|
|
1889
2035
|
examples::
|
|
1890
|
-
|
|
1891
|
-
|
|
2036
|
+
|
|
2037
|
+
project.create_model_monitoring_function(
|
|
2038
|
+
application_class_name="MyApp", image="mlrun/mlrun", name="myApp"
|
|
2039
|
+
)
|
|
1892
2040
|
|
|
1893
2041
|
:param func: Code url, None refers to current Notebook
|
|
1894
2042
|
:param name: Name of the function, can be specified with a tag to support
|
|
@@ -1907,6 +2055,7 @@ class MlrunProject(ModelObj):
|
|
|
1907
2055
|
:param application_kwargs: Additional keyword arguments to be passed to the
|
|
1908
2056
|
monitoring application's constructor.
|
|
1909
2057
|
"""
|
|
2058
|
+
|
|
1910
2059
|
_, function_object, _ = self._instantiate_model_monitoring_function(
|
|
1911
2060
|
func,
|
|
1912
2061
|
application_class,
|
|
@@ -1923,49 +2072,40 @@ class MlrunProject(ModelObj):
|
|
|
1923
2072
|
|
|
1924
2073
|
def _instantiate_model_monitoring_function(
|
|
1925
2074
|
self,
|
|
1926
|
-
func: typing.Union[str, mlrun.runtimes.BaseRuntime] = None,
|
|
1927
|
-
application_class: typing.Union[
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
2075
|
+
func: typing.Union[str, mlrun.runtimes.BaseRuntime, None] = None,
|
|
2076
|
+
application_class: typing.Union[
|
|
2077
|
+
str,
|
|
2078
|
+
mm_app.ModelMonitoringApplicationBase,
|
|
2079
|
+
None,
|
|
2080
|
+
] = None,
|
|
2081
|
+
name: typing.Optional[str] = None,
|
|
2082
|
+
image: typing.Optional[str] = None,
|
|
2083
|
+
handler: typing.Optional[str] = None,
|
|
2084
|
+
with_repo: typing.Optional[bool] = None,
|
|
2085
|
+
tag: typing.Optional[str] = None,
|
|
2086
|
+
requirements: typing.Union[str, list[str], None] = None,
|
|
1934
2087
|
requirements_file: str = "",
|
|
1935
2088
|
**application_kwargs,
|
|
1936
2089
|
) -> tuple[str, mlrun.runtimes.BaseRuntime, dict]:
|
|
2090
|
+
import mlrun.model_monitoring.api
|
|
2091
|
+
|
|
1937
2092
|
function_object: RemoteRuntime = None
|
|
1938
2093
|
kind = None
|
|
1939
2094
|
if (isinstance(func, str) or func is None) and application_class is not None:
|
|
1940
|
-
kind =
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
2095
|
+
kind = mlrun.run.RuntimeKinds.serving
|
|
2096
|
+
func = mlrun.model_monitoring.api._create_model_monitoring_function_base(
|
|
2097
|
+
project=self.name,
|
|
2098
|
+
func=func,
|
|
2099
|
+
application_class=application_class,
|
|
1945
2100
|
name=name,
|
|
1946
|
-
project=self.metadata.name,
|
|
1947
|
-
tag=tag,
|
|
1948
|
-
kind=kind,
|
|
1949
2101
|
image=image,
|
|
2102
|
+
tag=tag,
|
|
1950
2103
|
requirements=requirements,
|
|
1951
2104
|
requirements_file=requirements_file,
|
|
2105
|
+
**application_kwargs,
|
|
1952
2106
|
)
|
|
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
2107
|
elif isinstance(func, str) and isinstance(handler, str):
|
|
1968
|
-
kind =
|
|
2108
|
+
kind = mlrun.run.RuntimeKinds.nuclio
|
|
1969
2109
|
|
|
1970
2110
|
(
|
|
1971
2111
|
resolved_function_name,
|
|
@@ -1983,24 +2123,36 @@ class MlrunProject(ModelObj):
|
|
|
1983
2123
|
requirements,
|
|
1984
2124
|
requirements_file,
|
|
1985
2125
|
)
|
|
1986
|
-
models_names = "all"
|
|
1987
2126
|
function_object.set_label(
|
|
1988
2127
|
mm_constants.ModelMonitoringAppLabel.KEY,
|
|
1989
2128
|
mm_constants.ModelMonitoringAppLabel.VAL,
|
|
1990
2129
|
)
|
|
1991
|
-
function_object.set_label("models", models_names)
|
|
1992
2130
|
|
|
1993
2131
|
if not mlrun.mlconf.is_ce_mode():
|
|
1994
2132
|
function_object.apply(mlrun.mount_v3io())
|
|
1995
2133
|
|
|
1996
2134
|
return resolved_function_name, function_object, func
|
|
1997
2135
|
|
|
2136
|
+
def _wait_for_functions_deployment(self, function_names: list[str]) -> None:
|
|
2137
|
+
"""
|
|
2138
|
+
Wait for the deployment of functions on the backend.
|
|
2139
|
+
|
|
2140
|
+
:param function_names: A list of function names.
|
|
2141
|
+
"""
|
|
2142
|
+
for fn_name in function_names:
|
|
2143
|
+
fn = typing.cast(RemoteRuntime, self.get_function(key=fn_name))
|
|
2144
|
+
fn._wait_for_function_deployment(db=fn._get_db())
|
|
2145
|
+
|
|
1998
2146
|
def enable_model_monitoring(
|
|
1999
2147
|
self,
|
|
2000
2148
|
default_controller_image: str = "mlrun/mlrun",
|
|
2001
2149
|
base_period: int = 10,
|
|
2002
2150
|
image: str = "mlrun/mlrun",
|
|
2151
|
+
*,
|
|
2003
2152
|
deploy_histogram_data_drift_app: bool = True,
|
|
2153
|
+
wait_for_deployment: bool = False,
|
|
2154
|
+
rebuild_images: bool = False,
|
|
2155
|
+
fetch_credentials_from_sys_config: bool = False,
|
|
2004
2156
|
) -> None:
|
|
2005
2157
|
"""
|
|
2006
2158
|
Deploy model monitoring application controller, writer and stream functions.
|
|
@@ -2010,16 +2162,19 @@ class MlrunProject(ModelObj):
|
|
|
2010
2162
|
The stream function goal is to monitor the log of the data stream. It is triggered when a new log entry
|
|
2011
2163
|
is detected. It processes the new events into statistics that are then written to statistics databases.
|
|
2012
2164
|
|
|
2013
|
-
|
|
2014
|
-
:param
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
:param image:
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
:param deploy_histogram_data_drift_app:
|
|
2021
|
-
|
|
2022
|
-
|
|
2165
|
+
:param default_controller_image: Deprecated.
|
|
2166
|
+
:param base_period: The time period in minutes in which the model monitoring controller
|
|
2167
|
+
function is triggered. By default, the base period is 10 minutes
|
|
2168
|
+
(which is also the minimum value for production environments).
|
|
2169
|
+
:param image: The image of the model monitoring controller, writer, monitoring
|
|
2170
|
+
stream & histogram data drift functions, which are real time nuclio
|
|
2171
|
+
functions. By default, the image is mlrun/mlrun.
|
|
2172
|
+
:param deploy_histogram_data_drift_app: If true, deploy the default histogram-based data drift application.
|
|
2173
|
+
:param wait_for_deployment: If true, return only after the deployment is done on the backend.
|
|
2174
|
+
Otherwise, deploy the model monitoring infrastructure on the
|
|
2175
|
+
background, including the histogram data drift app if selected.
|
|
2176
|
+
:param rebuild_images: If true, force rebuild of model monitoring infrastructure images.
|
|
2177
|
+
:param fetch_credentials_from_sys_config: If true, fetch the credentials from the system configuration.
|
|
2023
2178
|
"""
|
|
2024
2179
|
if default_controller_image != "mlrun/mlrun":
|
|
2025
2180
|
# TODO: Remove this in 1.9.0
|
|
@@ -2029,39 +2184,71 @@ class MlrunProject(ModelObj):
|
|
|
2029
2184
|
FutureWarning,
|
|
2030
2185
|
)
|
|
2031
2186
|
image = default_controller_image
|
|
2187
|
+
if base_period < 10:
|
|
2188
|
+
logger.warn(
|
|
2189
|
+
"enable_model_monitoring: 'base_period' < 10 minutes is not supported in production environments",
|
|
2190
|
+
project=self.name,
|
|
2191
|
+
)
|
|
2192
|
+
|
|
2032
2193
|
db = mlrun.db.get_run_db(secrets=self._secrets)
|
|
2033
2194
|
db.enable_model_monitoring(
|
|
2034
2195
|
project=self.name,
|
|
2035
2196
|
image=image,
|
|
2036
2197
|
base_period=base_period,
|
|
2198
|
+
deploy_histogram_data_drift_app=deploy_histogram_data_drift_app,
|
|
2199
|
+
rebuild_images=rebuild_images,
|
|
2200
|
+
fetch_credentials_from_sys_config=fetch_credentials_from_sys_config,
|
|
2037
2201
|
)
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2202
|
+
|
|
2203
|
+
if wait_for_deployment:
|
|
2204
|
+
deployment_functions = mm_constants.MonitoringFunctionNames.list()
|
|
2205
|
+
if deploy_histogram_data_drift_app:
|
|
2206
|
+
deployment_functions.append(
|
|
2207
|
+
mm_constants.HistogramDataDriftApplicationConstants.NAME
|
|
2208
|
+
)
|
|
2209
|
+
self._wait_for_functions_deployment(deployment_functions)
|
|
2210
|
+
|
|
2211
|
+
def deploy_histogram_data_drift_app(
|
|
2212
|
+
self,
|
|
2213
|
+
*,
|
|
2214
|
+
image: str = "mlrun/mlrun",
|
|
2215
|
+
db: Optional[mlrun.db.RunDBInterface] = None,
|
|
2216
|
+
wait_for_deployment: bool = False,
|
|
2217
|
+
) -> None:
|
|
2218
|
+
"""
|
|
2219
|
+
Deploy the histogram data drift application.
|
|
2220
|
+
|
|
2221
|
+
:param image: The image on which the application will run.
|
|
2222
|
+
:param db: An optional DB object.
|
|
2223
|
+
:param wait_for_deployment: If true, return only after the deployment is done on the backend.
|
|
2224
|
+
Otherwise, deploy the application on the background.
|
|
2225
|
+
"""
|
|
2226
|
+
if db is None:
|
|
2227
|
+
db = mlrun.db.get_run_db(secrets=self._secrets)
|
|
2228
|
+
db.deploy_histogram_data_drift_app(project=self.name, image=image)
|
|
2229
|
+
|
|
2230
|
+
if wait_for_deployment:
|
|
2231
|
+
self._wait_for_functions_deployment(
|
|
2232
|
+
[mm_constants.HistogramDataDriftApplicationConstants.NAME]
|
|
2047
2233
|
)
|
|
2048
|
-
fn.deploy()
|
|
2049
2234
|
|
|
2050
2235
|
def update_model_monitoring_controller(
|
|
2051
2236
|
self,
|
|
2052
2237
|
base_period: int = 10,
|
|
2053
2238
|
image: str = "mlrun/mlrun",
|
|
2239
|
+
*,
|
|
2240
|
+
wait_for_deployment: bool = False,
|
|
2054
2241
|
) -> None:
|
|
2055
2242
|
"""
|
|
2056
2243
|
Redeploy model monitoring application controller functions.
|
|
2057
2244
|
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2245
|
+
:param base_period: The time period in minutes in which the model monitoring controller function
|
|
2246
|
+
is triggered. By default, the base period is 10 minutes.
|
|
2247
|
+
:param image: The image of the model monitoring controller, writer & monitoring
|
|
2248
|
+
stream functions, which are real time nuclio functions.
|
|
2249
|
+
By default, the image is mlrun/mlrun.
|
|
2250
|
+
:param wait_for_deployment: If true, return only after the deployment is done on the backend.
|
|
2251
|
+
Otherwise, deploy the controller on the background.
|
|
2065
2252
|
"""
|
|
2066
2253
|
db = mlrun.db.get_run_db(secrets=self._secrets)
|
|
2067
2254
|
db.update_model_monitoring_controller(
|
|
@@ -2070,26 +2257,78 @@ class MlrunProject(ModelObj):
|
|
|
2070
2257
|
image=image,
|
|
2071
2258
|
)
|
|
2072
2259
|
|
|
2073
|
-
|
|
2260
|
+
if wait_for_deployment:
|
|
2261
|
+
self._wait_for_functions_deployment(
|
|
2262
|
+
[mm_constants.MonitoringFunctionNames.APPLICATION_CONTROLLER]
|
|
2263
|
+
)
|
|
2264
|
+
|
|
2265
|
+
def disable_model_monitoring(
|
|
2266
|
+
self,
|
|
2267
|
+
*,
|
|
2268
|
+
delete_resources: bool = True,
|
|
2269
|
+
delete_stream_function: bool = False,
|
|
2270
|
+
delete_histogram_data_drift_app: bool = True,
|
|
2271
|
+
delete_user_applications: bool = False,
|
|
2272
|
+
user_application_list: list[str] = None,
|
|
2273
|
+
) -> None:
|
|
2274
|
+
"""
|
|
2275
|
+
Disable model monitoring application controller, writer, stream, histogram data drift application
|
|
2276
|
+
and the user's applications functions, according to the given params.
|
|
2277
|
+
|
|
2278
|
+
:param delete_resources: If True, it would delete the model monitoring controller & writer
|
|
2279
|
+
functions. Default True
|
|
2280
|
+
:param delete_stream_function: If True, it would delete model monitoring stream function,
|
|
2281
|
+
need to use wisely because if you're deleting this function
|
|
2282
|
+
this can cause data loss in case you will want to
|
|
2283
|
+
enable the model monitoring capability to the project.
|
|
2284
|
+
Default False.
|
|
2285
|
+
:param delete_histogram_data_drift_app: If True, it would delete the default histogram-based data drift
|
|
2286
|
+
application. Default False.
|
|
2287
|
+
:param delete_user_applications: If True, it would delete the user's model monitoring
|
|
2288
|
+
application according to user_application_list, Default False.
|
|
2289
|
+
:param user_application_list: List of the user's model monitoring application to disable.
|
|
2290
|
+
Default all the applications.
|
|
2291
|
+
Note: you have to set delete_user_applications to True
|
|
2292
|
+
in order to delete the desired application.
|
|
2293
|
+
"""
|
|
2294
|
+
if not delete_user_applications and user_application_list:
|
|
2295
|
+
raise mlrun.errors.MLRunInvalidArgumentError(
|
|
2296
|
+
"user_application_list can be specified only if delete_user_applications is set to True"
|
|
2297
|
+
)
|
|
2298
|
+
|
|
2074
2299
|
db = mlrun.db.get_run_db(secrets=self._secrets)
|
|
2075
|
-
db.
|
|
2076
|
-
project=self.name,
|
|
2077
|
-
name=mm_constants.MonitoringFunctionNames.APPLICATION_CONTROLLER,
|
|
2078
|
-
)
|
|
2079
|
-
db.delete_function(
|
|
2300
|
+
succeed = db.disable_model_monitoring(
|
|
2080
2301
|
project=self.name,
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2302
|
+
delete_resources=delete_resources,
|
|
2303
|
+
delete_stream_function=delete_stream_function,
|
|
2304
|
+
delete_histogram_data_drift_app=delete_histogram_data_drift_app,
|
|
2305
|
+
delete_user_applications=delete_user_applications,
|
|
2306
|
+
user_application_list=user_application_list,
|
|
2086
2307
|
)
|
|
2308
|
+
if succeed and delete_resources:
|
|
2309
|
+
if delete_resources:
|
|
2310
|
+
logger.info("Model Monitoring disabled", project=self.name)
|
|
2311
|
+
if delete_user_applications:
|
|
2312
|
+
logger.info(
|
|
2313
|
+
"All the desired monitoring application were deleted",
|
|
2314
|
+
project=self.name,
|
|
2315
|
+
)
|
|
2316
|
+
else:
|
|
2317
|
+
if delete_resources:
|
|
2318
|
+
logger.info(
|
|
2319
|
+
"Model Monitoring was not disabled properly", project=self.name
|
|
2320
|
+
)
|
|
2321
|
+
if delete_user_applications:
|
|
2322
|
+
logger.info(
|
|
2323
|
+
"Some of the desired monitoring application were not deleted",
|
|
2324
|
+
project=self.name,
|
|
2325
|
+
)
|
|
2087
2326
|
|
|
2088
2327
|
def set_function(
|
|
2089
2328
|
self,
|
|
2090
2329
|
func: typing.Union[str, mlrun.runtimes.BaseRuntime] = None,
|
|
2091
2330
|
name: str = "",
|
|
2092
|
-
kind: str = "",
|
|
2331
|
+
kind: str = "job",
|
|
2093
2332
|
image: str = None,
|
|
2094
2333
|
handler: str = None,
|
|
2095
2334
|
with_repo: bool = None,
|
|
@@ -2097,31 +2336,52 @@ class MlrunProject(ModelObj):
|
|
|
2097
2336
|
requirements: typing.Union[str, list[str]] = None,
|
|
2098
2337
|
requirements_file: str = "",
|
|
2099
2338
|
) -> mlrun.runtimes.BaseRuntime:
|
|
2100
|
-
"""
|
|
2339
|
+
"""
|
|
2340
|
+
| Update or add a function object to the project.
|
|
2341
|
+
| Function can be provided as an object (func) or a .py/.ipynb/.yaml URL.
|
|
2101
2342
|
|
|
2102
|
-
function
|
|
2103
|
-
|
|
2343
|
+
| Creating a function from a single file is done by specifying ``func`` and disabling ``with_repo``.
|
|
2344
|
+
| Creating a function with project source (specify ``with_repo=True``):
|
|
2345
|
+
| 1. Specify a relative ``func`` path.
|
|
2346
|
+
| 2. Specify a module ``handler`` (e.g. ``handler=package.package.func``) without ``func``.
|
|
2347
|
+
| Creating a function with non project source is done by specifying a module ``handler`` and on the
|
|
2348
|
+
returned function set the source with ``function.with_source_archive(<source>)``.
|
|
2104
2349
|
|
|
2105
|
-
|
|
2106
|
-
MLRun DB e.g. db://project/func:ver
|
|
2107
|
-
functions hub/market: e.g. hub://auto-trainer:master
|
|
2350
|
+
Support URL prefixes:
|
|
2108
2351
|
|
|
2109
|
-
|
|
2352
|
+
| Object (s3://, v3io://, ..)
|
|
2353
|
+
| MLRun DB e.g. db://project/func:ver
|
|
2354
|
+
| Functions hub/market: e.g. hub://auto-trainer:master
|
|
2355
|
+
|
|
2356
|
+
Examples::
|
|
2110
2357
|
|
|
2111
2358
|
proj.set_function(func_object)
|
|
2112
|
-
proj.set_function(
|
|
2113
|
-
|
|
2114
|
-
proj.set_function(
|
|
2115
|
-
|
|
2116
|
-
|
|
2359
|
+
proj.set_function("http://.../mynb.ipynb", "train")
|
|
2360
|
+
proj.set_function("./func.yaml")
|
|
2361
|
+
proj.set_function("hub://get_toy_data", "getdata")
|
|
2362
|
+
|
|
2363
|
+
# Create a function from a single file
|
|
2364
|
+
proj.set_function("./src/mycode.py", "ingest")
|
|
2117
2365
|
|
|
2118
|
-
#
|
|
2366
|
+
# Creating a function with project source
|
|
2367
|
+
proj.set_function(
|
|
2368
|
+
"./src/mycode.py", "ingest", image="myrepo/ing:latest", with_repo=True
|
|
2369
|
+
)
|
|
2370
|
+
proj.set_function("ingest", handler="package.package.func", with_repo=True)
|
|
2371
|
+
|
|
2372
|
+
# Creating a function with non project source
|
|
2373
|
+
func = proj.set_function(
|
|
2374
|
+
"ingest", handler="package.package.func", with_repo=False
|
|
2375
|
+
)
|
|
2376
|
+
func.with_source_archive("git://github.com/mlrun/something.git")
|
|
2119
2377
|
|
|
2120
|
-
#
|
|
2121
|
-
proj.set_function('my.py', requirements=["requests", "pandas"])
|
|
2378
|
+
# Set function requirements
|
|
2122
2379
|
|
|
2123
|
-
#
|
|
2124
|
-
proj.set_function(
|
|
2380
|
+
# By providing a list of packages
|
|
2381
|
+
proj.set_function("my.py", requirements=["requests", "pandas"])
|
|
2382
|
+
|
|
2383
|
+
# By providing a path to a pip requirements file
|
|
2384
|
+
proj.set_function("my.py", requirements="requirements.txt")
|
|
2125
2385
|
|
|
2126
2386
|
:param func: Function object or spec/code url, None refers to current Notebook
|
|
2127
2387
|
:param name: Name of the function (under the project), can be specified with a tag to support
|
|
@@ -2132,14 +2392,15 @@ class MlrunProject(ModelObj):
|
|
|
2132
2392
|
Default: job
|
|
2133
2393
|
:param image: Docker image to be used, can also be specified in the function object/yaml
|
|
2134
2394
|
:param handler: Default function handler to invoke (can only be set with .py/.ipynb files)
|
|
2135
|
-
:param with_repo: Add (clone) the current repo to the build source
|
|
2395
|
+
:param with_repo: Add (clone) the current repo to the build source - use when the function code is in
|
|
2396
|
+
the project repo (project.spec.source).
|
|
2136
2397
|
:param tag: Function version tag to set (none for current or 'latest')
|
|
2137
2398
|
Specifying a tag as a parameter will update the project's tagged function
|
|
2138
2399
|
(myfunc:v1) and the untagged function (myfunc)
|
|
2139
2400
|
:param requirements: A list of python packages
|
|
2140
2401
|
:param requirements_file: Path to a python requirements file
|
|
2141
2402
|
|
|
2142
|
-
:returns:
|
|
2403
|
+
:returns: :py:class:`~mlrun.runtimes.BaseRuntime`
|
|
2143
2404
|
"""
|
|
2144
2405
|
(
|
|
2145
2406
|
resolved_function_name,
|
|
@@ -2173,19 +2434,20 @@ class MlrunProject(ModelObj):
|
|
|
2173
2434
|
requirements: typing.Union[str, list[str]] = None,
|
|
2174
2435
|
requirements_file: str = "",
|
|
2175
2436
|
) -> tuple[str, str, mlrun.runtimes.BaseRuntime, dict]:
|
|
2176
|
-
if
|
|
2437
|
+
if (
|
|
2438
|
+
func is None
|
|
2439
|
+
and not _has_module(handler, kind)
|
|
2440
|
+
and mlrun.runtimes.RuntimeKinds.supports_from_notebook(kind)
|
|
2441
|
+
):
|
|
2177
2442
|
# if function path is not provided and it is not a module (no ".")
|
|
2178
2443
|
# 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
|
|
2444
|
+
if is_jupyter:
|
|
2445
|
+
from IPython import get_ipython
|
|
2184
2446
|
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2447
|
+
kernel = get_ipython()
|
|
2448
|
+
func = nuclio.utils.notebook_file_name(kernel)
|
|
2449
|
+
if func.startswith(path.abspath(self.spec.context)):
|
|
2450
|
+
func = path.relpath(func, self.spec.context)
|
|
2189
2451
|
|
|
2190
2452
|
func = func or ""
|
|
2191
2453
|
|
|
@@ -2281,22 +2543,39 @@ class MlrunProject(ModelObj):
|
|
|
2281
2543
|
"""
|
|
2282
2544
|
self.spec.remove_function(name)
|
|
2283
2545
|
|
|
2284
|
-
def remove_model_monitoring_function(self, name):
|
|
2285
|
-
"""
|
|
2546
|
+
def remove_model_monitoring_function(self, name: Union[str, list[str]]):
|
|
2547
|
+
"""delete the specified model-monitoring-app function/s
|
|
2286
2548
|
|
|
2287
|
-
:param name: name of the model-monitoring-
|
|
2549
|
+
:param name: name of the model-monitoring-function/s (under the project)
|
|
2288
2550
|
"""
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2551
|
+
# TODO: Remove this in 1.9.0
|
|
2552
|
+
warnings.warn(
|
|
2553
|
+
"'remove_model_monitoring_function' is deprecated and will be removed in 1.9.0. "
|
|
2554
|
+
"Please use `delete_model_monitoring_function` instead.",
|
|
2555
|
+
FutureWarning,
|
|
2556
|
+
)
|
|
2557
|
+
self.delete_model_monitoring_function(name)
|
|
2558
|
+
|
|
2559
|
+
def delete_model_monitoring_function(self, name: Union[str, list[str]]):
|
|
2560
|
+
"""delete the specified model-monitoring-app function/s
|
|
2561
|
+
|
|
2562
|
+
:param name: name of the model-monitoring-function/s (under the project)
|
|
2563
|
+
"""
|
|
2564
|
+
db = mlrun.db.get_run_db(secrets=self._secrets)
|
|
2565
|
+
succeed = db.delete_model_monitoring_function(
|
|
2566
|
+
project=self.name,
|
|
2567
|
+
functions=name if isinstance(name, list) else [name],
|
|
2568
|
+
)
|
|
2569
|
+
if succeed:
|
|
2570
|
+
logger.info(
|
|
2571
|
+
"All the desired monitoring functions were deleted",
|
|
2572
|
+
project=self.name,
|
|
2573
|
+
functions=name,
|
|
2574
|
+
)
|
|
2297
2575
|
else:
|
|
2298
|
-
|
|
2299
|
-
|
|
2576
|
+
logger.info(
|
|
2577
|
+
"Some of the desired monitoring functions were not deleted",
|
|
2578
|
+
project=self.name,
|
|
2300
2579
|
)
|
|
2301
2580
|
|
|
2302
2581
|
def get_function(
|
|
@@ -2402,13 +2681,47 @@ class MlrunProject(ModelObj):
|
|
|
2402
2681
|
clone_zip(url, self.spec.context, self._secrets)
|
|
2403
2682
|
|
|
2404
2683
|
def create_remote(self, url, name="origin", branch=None):
|
|
2405
|
-
"""
|
|
2684
|
+
"""Create remote for the project git
|
|
2685
|
+
|
|
2686
|
+
This method creates a new remote repository associated with the project's Git repository.
|
|
2687
|
+
If a remote with the specified name already exists, it will not be overwritten.
|
|
2688
|
+
|
|
2689
|
+
If you wish to update the URL of an existing remote, use the `set_remote` method instead.
|
|
2406
2690
|
|
|
2407
2691
|
:param url: remote git url
|
|
2408
2692
|
:param name: name for the remote (default is 'origin')
|
|
2409
2693
|
:param branch: Git branch to use as source
|
|
2410
2694
|
"""
|
|
2695
|
+
self.set_remote(url, name=name, branch=branch, overwrite=False)
|
|
2696
|
+
|
|
2697
|
+
def set_remote(self, url, name="origin", branch=None, overwrite=True):
|
|
2698
|
+
"""Create or update a remote for the project git repository.
|
|
2699
|
+
|
|
2700
|
+
This method allows you to manage remote repositories associated with the project.
|
|
2701
|
+
It checks if a remote with the specified name already exists.
|
|
2702
|
+
|
|
2703
|
+
If a remote with the same name does not exist, it will be created.
|
|
2704
|
+
If a remote with the same name already exists,
|
|
2705
|
+
the behavior depends on the value of the 'overwrite' flag.
|
|
2706
|
+
|
|
2707
|
+
:param url: remote git url
|
|
2708
|
+
:param name: name for the remote (default is 'origin')
|
|
2709
|
+
:param branch: Git branch to use as source
|
|
2710
|
+
:param overwrite: if True (default), updates the existing remote with the given URL if it already exists.
|
|
2711
|
+
if False, raises an error when attempting to create a remote with a name that already exists.
|
|
2712
|
+
:raises MLRunConflictError: If a remote with the same name already exists and overwrite
|
|
2713
|
+
is set to False.
|
|
2714
|
+
"""
|
|
2411
2715
|
self._ensure_git_repo()
|
|
2716
|
+
if self._remote_exists(name):
|
|
2717
|
+
if overwrite:
|
|
2718
|
+
self.spec.repo.delete_remote(name)
|
|
2719
|
+
else:
|
|
2720
|
+
raise mlrun.errors.MLRunConflictError(
|
|
2721
|
+
f"Remote '{name}' already exists in the project, "
|
|
2722
|
+
f"each remote in the project must have a unique name."
|
|
2723
|
+
"Use 'set_remote' with 'override=True' inorder to update the remote, or choose a different name."
|
|
2724
|
+
)
|
|
2412
2725
|
self.spec.repo.create_remote(name, url=url)
|
|
2413
2726
|
url = url.replace("https://", "git://")
|
|
2414
2727
|
if not branch:
|
|
@@ -2421,6 +2734,22 @@ class MlrunProject(ModelObj):
|
|
|
2421
2734
|
self.spec._source = self.spec.source or url
|
|
2422
2735
|
self.spec.origin_url = self.spec.origin_url or url
|
|
2423
2736
|
|
|
2737
|
+
def remove_remote(self, name):
|
|
2738
|
+
"""Remove a remote from the project's Git repository.
|
|
2739
|
+
|
|
2740
|
+
This method removes the remote repository associated with the specified name from the project's Git repository.
|
|
2741
|
+
|
|
2742
|
+
:param name: Name of the remote to remove.
|
|
2743
|
+
"""
|
|
2744
|
+
if self._remote_exists(name):
|
|
2745
|
+
self.spec.repo.delete_remote(name)
|
|
2746
|
+
else:
|
|
2747
|
+
logger.warning(f"The remote '{name}' does not exist. Nothing to remove.")
|
|
2748
|
+
|
|
2749
|
+
def _remote_exists(self, name):
|
|
2750
|
+
"""Check if a remote with the given name already exists"""
|
|
2751
|
+
return any(remote.name == name for remote in self.spec.repo.remotes)
|
|
2752
|
+
|
|
2424
2753
|
def _ensure_git_repo(self):
|
|
2425
2754
|
if self.spec.repo:
|
|
2426
2755
|
return
|
|
@@ -2503,47 +2832,104 @@ class MlrunProject(ModelObj):
|
|
|
2503
2832
|
secrets=secrets or {},
|
|
2504
2833
|
)
|
|
2505
2834
|
|
|
2506
|
-
def sync_functions(
|
|
2507
|
-
|
|
2835
|
+
def sync_functions(
|
|
2836
|
+
self,
|
|
2837
|
+
names: list = None,
|
|
2838
|
+
always: bool = True,
|
|
2839
|
+
save: bool = False,
|
|
2840
|
+
silent: bool = False,
|
|
2841
|
+
):
|
|
2842
|
+
"""
|
|
2843
|
+
Reload function objects from specs and files.
|
|
2844
|
+
The function objects are synced against the definitions spec in `self.spec._function_definitions`.
|
|
2845
|
+
Referenced files/URLs in the function spec will be reloaded.
|
|
2846
|
+
Function definitions are parsed by the following precedence:
|
|
2847
|
+
|
|
2848
|
+
1. Contains runtime spec.
|
|
2849
|
+
2. Contains module in the project's context.
|
|
2850
|
+
3. Contains path to function definition (yaml, DB, Hub).
|
|
2851
|
+
4. Contains path to .ipynb or .py files.
|
|
2852
|
+
5. Contains a Nuclio/Serving function image / an 'Application' kind definition.
|
|
2853
|
+
|
|
2854
|
+
If function definition is already an object, some project metadata updates will apply however,
|
|
2855
|
+
it will not be reloaded.
|
|
2856
|
+
|
|
2857
|
+
:param names: Names of functions to reload, defaults to `self.spec._function_definitions.keys()`.
|
|
2858
|
+
:param always: Force reloading the functions.
|
|
2859
|
+
:param save: Whether to save the loaded functions or not.
|
|
2860
|
+
:param silent: Whether to raise an exception when a function fails to load.
|
|
2861
|
+
|
|
2862
|
+
:returns: Dictionary of function objects
|
|
2863
|
+
"""
|
|
2508
2864
|
if self._initialized and not always:
|
|
2509
2865
|
return self.spec._function_objects
|
|
2510
2866
|
|
|
2511
|
-
|
|
2867
|
+
functions = self.spec._function_objects
|
|
2512
2868
|
if not names:
|
|
2513
2869
|
names = self.spec._function_definitions.keys()
|
|
2514
|
-
|
|
2870
|
+
functions = {}
|
|
2871
|
+
|
|
2515
2872
|
origin = mlrun.runtimes.utils.add_code_metadata(self.spec.context)
|
|
2516
2873
|
for name in names:
|
|
2517
|
-
|
|
2518
|
-
if not
|
|
2519
|
-
|
|
2874
|
+
function_definition = self.spec._function_definitions.get(name)
|
|
2875
|
+
if not function_definition:
|
|
2876
|
+
if silent:
|
|
2877
|
+
logger.warn(
|
|
2878
|
+
"Function definition was not found, skipping reload", name=name
|
|
2879
|
+
)
|
|
2880
|
+
continue
|
|
2881
|
+
|
|
2882
|
+
raise ValueError(f"Function named {name} not found")
|
|
2883
|
+
|
|
2884
|
+
function_object = self.spec._function_objects.get(name, None)
|
|
2885
|
+
is_base_runtime = isinstance(
|
|
2886
|
+
function_object, mlrun.runtimes.base.BaseRuntime
|
|
2887
|
+
)
|
|
2520
2888
|
# If this function is already available locally, don't recreate it unless always=True
|
|
2521
|
-
if
|
|
2522
|
-
|
|
2523
|
-
self.spec._function_objects.get(name, None),
|
|
2524
|
-
mlrun.runtimes.base.BaseRuntime,
|
|
2525
|
-
)
|
|
2526
|
-
and not always
|
|
2527
|
-
):
|
|
2528
|
-
funcs[name] = self.spec._function_objects[name]
|
|
2889
|
+
if is_base_runtime and not always:
|
|
2890
|
+
functions[name] = function_object
|
|
2529
2891
|
continue
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2892
|
+
|
|
2893
|
+
# Reload the function
|
|
2894
|
+
if hasattr(function_definition, "to_dict"):
|
|
2895
|
+
name, func = _init_function_from_obj(function_definition, self, name)
|
|
2896
|
+
elif isinstance(function_definition, dict):
|
|
2535
2897
|
try:
|
|
2536
|
-
name, func = _init_function_from_dict(
|
|
2898
|
+
name, func = _init_function_from_dict(
|
|
2899
|
+
function_definition, self, name
|
|
2900
|
+
)
|
|
2537
2901
|
except FileNotFoundError as exc:
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2902
|
+
message = f"File {exc.filename} not found while syncing project functions."
|
|
2903
|
+
if silent:
|
|
2904
|
+
message += " Skipping function reload"
|
|
2905
|
+
logger.warn(message, name=name)
|
|
2906
|
+
continue
|
|
2907
|
+
|
|
2908
|
+
raise mlrun.errors.MLRunMissingDependencyError(message) from exc
|
|
2909
|
+
|
|
2910
|
+
except Exception as exc:
|
|
2911
|
+
if silent:
|
|
2912
|
+
logger.warn(
|
|
2913
|
+
"Failed to instantiate function",
|
|
2914
|
+
name=name,
|
|
2915
|
+
error=mlrun.utils.err_to_str(exc),
|
|
2916
|
+
)
|
|
2917
|
+
continue
|
|
2918
|
+
raise exc
|
|
2919
|
+
else:
|
|
2920
|
+
message = f"Function {name} must be an object or dict."
|
|
2921
|
+
if silent:
|
|
2922
|
+
message += " Skipping function reload"
|
|
2923
|
+
logger.warn(message, name=name)
|
|
2924
|
+
continue
|
|
2925
|
+
raise ValueError(message)
|
|
2926
|
+
|
|
2541
2927
|
func.spec.build.code_origin = origin
|
|
2542
|
-
|
|
2928
|
+
functions[name] = func
|
|
2543
2929
|
if save:
|
|
2544
2930
|
func.save(versioned=False)
|
|
2545
2931
|
|
|
2546
|
-
self.spec._function_objects =
|
|
2932
|
+
self.spec._function_objects = functions
|
|
2547
2933
|
self._initialized = True
|
|
2548
2934
|
return self.spec._function_objects
|
|
2549
2935
|
|
|
@@ -2552,9 +2938,9 @@ class MlrunProject(ModelObj):
|
|
|
2552
2938
|
|
|
2553
2939
|
read secrets from a source provider to be used in workflows, example::
|
|
2554
2940
|
|
|
2555
|
-
proj.with_secrets(
|
|
2556
|
-
proj.with_secrets(
|
|
2557
|
-
proj.with_secrets(
|
|
2941
|
+
proj.with_secrets("file", "file.txt")
|
|
2942
|
+
proj.with_secrets("inline", {"key": "val"})
|
|
2943
|
+
proj.with_secrets("env", "ENV1,ENV2", prefix="PFX_")
|
|
2558
2944
|
|
|
2559
2945
|
Vault secret source has several options::
|
|
2560
2946
|
|
|
@@ -2565,7 +2951,7 @@ class MlrunProject(ModelObj):
|
|
|
2565
2951
|
The 2nd option uses the current project name as context.
|
|
2566
2952
|
Can also use empty secret list::
|
|
2567
2953
|
|
|
2568
|
-
proj.with_secrets(
|
|
2954
|
+
proj.with_secrets("vault", [])
|
|
2569
2955
|
|
|
2570
2956
|
This will enable access to all secrets in vault registered to the current project.
|
|
2571
2957
|
|
|
@@ -2596,17 +2982,20 @@ class MlrunProject(ModelObj):
|
|
|
2596
2982
|
file_path: str = None,
|
|
2597
2983
|
provider: typing.Union[str, mlrun.common.schemas.SecretProviderName] = None,
|
|
2598
2984
|
):
|
|
2599
|
-
"""
|
|
2985
|
+
"""
|
|
2986
|
+
Set project secrets from dict or secrets env file
|
|
2600
2987
|
when using a secrets file it should have lines in the form KEY=VALUE, comment line start with "#"
|
|
2601
2988
|
V3IO paths/credentials and MLrun service API address are dropped from the secrets
|
|
2602
2989
|
|
|
2603
|
-
example secrets file
|
|
2990
|
+
example secrets file:
|
|
2991
|
+
|
|
2992
|
+
.. code-block:: shell
|
|
2604
2993
|
|
|
2605
2994
|
# this is an env file
|
|
2606
|
-
AWS_ACCESS_KEY_ID
|
|
2995
|
+
AWS_ACCESS_KEY_ID=XXXX
|
|
2607
2996
|
AWS_SECRET_ACCESS_KEY=YYYY
|
|
2608
2997
|
|
|
2609
|
-
usage
|
|
2998
|
+
usage:
|
|
2610
2999
|
|
|
2611
3000
|
# read env vars from dict or file and set as project secrets
|
|
2612
3001
|
project.set_secrets({"SECRET1": "value"})
|
|
@@ -2685,41 +3074,50 @@ class MlrunProject(ModelObj):
|
|
|
2685
3074
|
source: str = None,
|
|
2686
3075
|
cleanup_ttl: int = None,
|
|
2687
3076
|
notifications: list[mlrun.model.Notification] = None,
|
|
3077
|
+
workflow_runner_node_selector: typing.Optional[dict[str, str]] = None,
|
|
2688
3078
|
) -> _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())
|
|
3079
|
+
"""Run a workflow using kubeflow pipelines
|
|
3080
|
+
|
|
3081
|
+
:param name: Name of the workflow
|
|
3082
|
+
:param workflow_path: URL to a workflow file, if not a project workflow
|
|
3083
|
+
:param arguments: Kubeflow pipelines arguments (parameters)
|
|
3084
|
+
:param artifact_path: Target path/URL for workflow artifacts, the string '{{workflow.uid}}' will be
|
|
3085
|
+
replaced by workflow id.
|
|
3086
|
+
:param workflow_handler: Workflow function handler (for running workflow function directly)
|
|
3087
|
+
:param namespace: Kubernetes namespace if other than default
|
|
3088
|
+
:param sync: Force functions sync before run
|
|
3089
|
+
:param watch: Wait for pipeline completion
|
|
3090
|
+
:param dirty: Allow running the workflow when the git repo is dirty
|
|
3091
|
+
:param engine: Workflow engine running the workflow.
|
|
3092
|
+
Supported values are 'kfp' (default), 'local' or 'remote'.
|
|
3093
|
+
For setting engine for remote running use 'remote:local' or 'remote:kfp'.
|
|
3094
|
+
:param local: Run local pipeline with local functions (set local=True in function.run())
|
|
2709
3095
|
:param schedule: ScheduleCronTrigger class instance or a standard crontab expression string
|
|
2710
3096
|
(which will be converted to the class using its `from_crontab` constructor),
|
|
2711
3097
|
see this link for help:
|
|
2712
3098
|
https://apscheduler.readthedocs.io/en/3.x/modules/triggers/cron.html#module-apscheduler.triggers.cron
|
|
2713
|
-
|
|
2714
|
-
:param timeout:
|
|
2715
|
-
:param source:
|
|
2716
|
-
|
|
3099
|
+
For using the pre-defined workflow's schedule, set `schedule=True`
|
|
3100
|
+
:param timeout: Timeout in seconds to wait for pipeline completion (watch will be activated)
|
|
3101
|
+
:param source: Source to use instead of the actual `project.spec.source` (used when engine is remote).
|
|
3102
|
+
Can be one of:
|
|
3103
|
+
|
|
3104
|
+
* Remote URL which is loaded dynamically to the workflow runner.
|
|
3105
|
+
* A path to the project's context on the workflow runner's image.
|
|
3106
|
+
Path can be absolute or relative to `project.spec.build.source_code_target_dir` if defined
|
|
3107
|
+
(enriched when building a project image with source, see `MlrunProject.build_image`).
|
|
3108
|
+
For other engines the source is used to validate that the code is up-to-date.
|
|
3109
|
+
|
|
2717
3110
|
:param cleanup_ttl:
|
|
2718
|
-
|
|
3111
|
+
Pipeline cleanup ttl in secs (time to wait after workflow completion, at which point the
|
|
2719
3112
|
workflow and all its resources are deleted)
|
|
2720
3113
|
:param notifications:
|
|
2721
|
-
|
|
2722
|
-
:
|
|
3114
|
+
List of notifications to send for workflow completion
|
|
3115
|
+
:param workflow_runner_node_selector:
|
|
3116
|
+
Defines the node selector for the workflow runner pod when using a remote engine.
|
|
3117
|
+
This allows you to control and specify where the workflow runner pod will be scheduled.
|
|
3118
|
+
This setting is only relevant when the engine is set to 'remote' or for scheduled workflows,
|
|
3119
|
+
and it will be ignored if the workflow is not run on a remote engine.
|
|
3120
|
+
:returns: ~py:class:`~mlrun.projects.pipelines._PipelineRunStatus` instance
|
|
2723
3121
|
"""
|
|
2724
3122
|
|
|
2725
3123
|
arguments = arguments or {}
|
|
@@ -2736,12 +3134,14 @@ class MlrunProject(ModelObj):
|
|
|
2736
3134
|
"Remote repo is not defined, use .create_remote() + push()"
|
|
2737
3135
|
)
|
|
2738
3136
|
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
3137
|
+
if engine not in ["remote"] and not schedule:
|
|
3138
|
+
# For remote/scheduled runs there is no need to sync functions as they can be loaded dynamically during run
|
|
3139
|
+
self.sync_functions(always=sync, silent=True)
|
|
3140
|
+
if not self.spec._function_objects:
|
|
3141
|
+
logger.warn(
|
|
3142
|
+
"There are no functions in the project."
|
|
3143
|
+
" Make sure you've set your functions with project.set_function()."
|
|
3144
|
+
)
|
|
2745
3145
|
|
|
2746
3146
|
if not name and not workflow_path and not workflow_handler:
|
|
2747
3147
|
raise ValueError("Workflow name, path, or handler must be specified")
|
|
@@ -2775,9 +3175,23 @@ class MlrunProject(ModelObj):
|
|
|
2775
3175
|
engine = "remote"
|
|
2776
3176
|
# The default engine is kfp if not given:
|
|
2777
3177
|
workflow_engine = get_workflow_engine(engine or workflow_spec.engine, local)
|
|
2778
|
-
if not inner_engine and engine == "remote":
|
|
2779
|
-
|
|
3178
|
+
if not inner_engine and workflow_engine.engine == "remote":
|
|
3179
|
+
# if inner engine is set to remote, assume kfp as the default inner engine with remote as the runner
|
|
3180
|
+
engine_kind = (
|
|
3181
|
+
workflow_spec.engine if workflow_spec.engine != "remote" else "kfp"
|
|
3182
|
+
)
|
|
3183
|
+
inner_engine = get_workflow_engine(engine_kind, local).engine
|
|
2780
3184
|
workflow_spec.engine = inner_engine or workflow_engine.engine
|
|
3185
|
+
if workflow_runner_node_selector:
|
|
3186
|
+
if workflow_engine.engine == "remote":
|
|
3187
|
+
workflow_spec.workflow_runner_node_selector = (
|
|
3188
|
+
workflow_runner_node_selector
|
|
3189
|
+
)
|
|
3190
|
+
else:
|
|
3191
|
+
logger.warn(
|
|
3192
|
+
"'workflow_runner_node_selector' applies only to remote engines"
|
|
3193
|
+
" and is ignored for non-remote runs."
|
|
3194
|
+
)
|
|
2781
3195
|
|
|
2782
3196
|
run = workflow_engine.run(
|
|
2783
3197
|
self,
|
|
@@ -2791,7 +3205,7 @@ class MlrunProject(ModelObj):
|
|
|
2791
3205
|
notifications=notifications,
|
|
2792
3206
|
)
|
|
2793
3207
|
# run is None when scheduling
|
|
2794
|
-
if run and run.state ==
|
|
3208
|
+
if run and run.state == mlrun_pipelines.common.models.RunStatuses.failed:
|
|
2795
3209
|
return run
|
|
2796
3210
|
if not workflow_spec.schedule:
|
|
2797
3211
|
# Failure and schedule messages already logged
|
|
@@ -2800,14 +3214,17 @@ class MlrunProject(ModelObj):
|
|
|
2800
3214
|
)
|
|
2801
3215
|
workflow_spec.clear_tmp()
|
|
2802
3216
|
if (timeout or watch) and not workflow_spec.schedule:
|
|
3217
|
+
run_status_kwargs = {}
|
|
2803
3218
|
status_engine = run._engine
|
|
2804
3219
|
# run's engine gets replaced with inner engine if engine is remote,
|
|
2805
3220
|
# 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":
|
|
3221
|
+
if workflow_engine.engine == "remote":
|
|
2808
3222
|
status_engine = _RemoteRunner
|
|
3223
|
+
run_status_kwargs["inner_engine"] = run._engine
|
|
2809
3224
|
|
|
2810
|
-
status_engine.get_run_status(
|
|
3225
|
+
status_engine.get_run_status(
|
|
3226
|
+
project=self, run=run, timeout=timeout, **run_status_kwargs
|
|
3227
|
+
)
|
|
2811
3228
|
return run
|
|
2812
3229
|
|
|
2813
3230
|
def save_workflow(self, name, target, artifact_path=None, ttl=None):
|
|
@@ -2907,43 +3324,66 @@ class MlrunProject(ModelObj):
|
|
|
2907
3324
|
|
|
2908
3325
|
def set_model_monitoring_credentials(
|
|
2909
3326
|
self,
|
|
2910
|
-
access_key: str = None,
|
|
2911
|
-
endpoint_store_connection: str = None,
|
|
2912
|
-
stream_path: str = None,
|
|
3327
|
+
access_key: Optional[str] = None,
|
|
3328
|
+
endpoint_store_connection: Optional[str] = None,
|
|
3329
|
+
stream_path: Optional[str] = None,
|
|
3330
|
+
tsdb_connection: Optional[str] = None,
|
|
3331
|
+
replace_creds: bool = False,
|
|
2913
3332
|
):
|
|
2914
|
-
"""Set the credentials that will be used by the project's model monitoring
|
|
2915
|
-
infrastructure functions.
|
|
2916
|
-
|
|
2917
|
-
:param access_key: Model Monitoring access key for managing user permissions
|
|
2918
|
-
:param access_key: Model Monitoring access key for managing user permissions
|
|
2919
|
-
:param endpoint_store_connection: Endpoint store connection string
|
|
2920
|
-
:param stream_path: Path to the model monitoring stream
|
|
2921
3333
|
"""
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
3334
|
+
Set the credentials that will be used by the project's model monitoring
|
|
3335
|
+
infrastructure functions. Important to note that you have to set the credentials before deploying any
|
|
3336
|
+
model monitoring or serving function.
|
|
3337
|
+
|
|
3338
|
+
:param access_key: Model monitoring access key for managing user permissions.
|
|
3339
|
+
:param endpoint_store_connection: Endpoint store connection string. By default, None. Options:
|
|
3340
|
+
|
|
3341
|
+
* None - will be set from the system configuration.
|
|
3342
|
+
* v3io - for v3io endpoint store, pass `v3io` and the system will generate the
|
|
3343
|
+
exact path.
|
|
3344
|
+
* MySQL/SQLite - for SQL endpoint store, provide the full connection string,
|
|
3345
|
+
for example: mysql+pymysql://<username>:<password>@<host>:<port>/<db_name>
|
|
3346
|
+
:param stream_path: Path to the model monitoring stream. By default, None. Options:
|
|
3347
|
+
|
|
3348
|
+
* None - will be set from the system configuration.
|
|
3349
|
+
* v3io - for v3io stream, pass `v3io` and the system will generate the exact
|
|
3350
|
+
path.
|
|
3351
|
+
* Kafka - for Kafka stream, provide the full connection string without custom
|
|
3352
|
+
topic, for example kafka://<some_kafka_broker>:<port>.
|
|
3353
|
+
:param tsdb_connection: Connection string to the time series database. By default, None.
|
|
3354
|
+
Options:
|
|
3355
|
+
|
|
3356
|
+
* None - will be set from the system configuration.
|
|
3357
|
+
* v3io - for v3io stream, pass `v3io` and the system will generate the exact
|
|
3358
|
+
path.
|
|
3359
|
+
* TDEngine - for TDEngine tsdb, provide the full websocket connection URL,
|
|
3360
|
+
for example taosws://<username>:<password>@<host>:<port>.
|
|
3361
|
+
:param replace_creds: If True, will override the existing credentials.
|
|
3362
|
+
Please keep in mind that if you already enabled model monitoring on
|
|
3363
|
+
your project this action can cause data loose and will require redeploying
|
|
3364
|
+
all model monitoring functions & model monitoring infra
|
|
3365
|
+
& tracked model server.
|
|
3366
|
+
"""
|
|
3367
|
+
db = mlrun.db.get_run_db(secrets=self._secrets)
|
|
3368
|
+
db.set_model_monitoring_credentials(
|
|
3369
|
+
project=self.name,
|
|
3370
|
+
credentials={
|
|
3371
|
+
"access_key": access_key,
|
|
3372
|
+
"endpoint_store_connection": endpoint_store_connection,
|
|
3373
|
+
"stream_path": stream_path,
|
|
3374
|
+
"tsdb_connection": tsdb_connection,
|
|
3375
|
+
},
|
|
3376
|
+
replace_creds=replace_creds,
|
|
2946
3377
|
)
|
|
3378
|
+
if replace_creds:
|
|
3379
|
+
logger.info(
|
|
3380
|
+
"Model monitoring credentials were set successfully. "
|
|
3381
|
+
"Please keep in mind that if you already had model monitoring functions "
|
|
3382
|
+
"/ model monitoring infra / tracked model server "
|
|
3383
|
+
"deployed on your project, you will need to redeploy them."
|
|
3384
|
+
"For redeploying the model monitoring infra, please use `enable_model_monitoring` API "
|
|
3385
|
+
"and set `rebuild_images=True`"
|
|
3386
|
+
)
|
|
2947
3387
|
|
|
2948
3388
|
def run_function(
|
|
2949
3389
|
self,
|
|
@@ -2968,7 +3408,8 @@ class MlrunProject(ModelObj):
|
|
|
2968
3408
|
notifications: list[mlrun.model.Notification] = None,
|
|
2969
3409
|
returns: Optional[list[Union[str, dict[str, str]]]] = None,
|
|
2970
3410
|
builder_env: Optional[dict] = None,
|
|
2971
|
-
|
|
3411
|
+
reset_on_run: bool = None,
|
|
3412
|
+
) -> typing.Union[mlrun.model.RunObject, PipelineNodeWrapper]:
|
|
2972
3413
|
"""Run a local or remote task as part of a local/kubeflow pipeline
|
|
2973
3414
|
|
|
2974
3415
|
example (use with project)::
|
|
@@ -2980,8 +3421,11 @@ class MlrunProject(ModelObj):
|
|
|
2980
3421
|
|
|
2981
3422
|
# run functions (refer to them by name)
|
|
2982
3423
|
run1 = project.run_function("myfunc", params={"x": 7})
|
|
2983
|
-
run2 = project.run_function(
|
|
2984
|
-
|
|
3424
|
+
run2 = project.run_function(
|
|
3425
|
+
"train",
|
|
3426
|
+
params={"label_columns": LABELS},
|
|
3427
|
+
inputs={"dataset": run1.outputs["data"]},
|
|
3428
|
+
)
|
|
2985
3429
|
|
|
2986
3430
|
:param function: name of the function (in the project) or function object
|
|
2987
3431
|
:param handler: name of the function handler
|
|
@@ -3020,8 +3464,13 @@ class MlrunProject(ModelObj):
|
|
|
3020
3464
|
* A dictionary of configurations to use when logging. Further info per object type and
|
|
3021
3465
|
artifact type can be given there. The artifact key must appear in the dictionary as
|
|
3022
3466
|
"key": "the_key".
|
|
3023
|
-
:param builder_env:
|
|
3024
|
-
|
|
3467
|
+
:param builder_env: env vars dict for source archive config/credentials e.g. builder_env={"GIT_TOKEN":
|
|
3468
|
+
token}
|
|
3469
|
+
:param reset_on_run: When True, function python modules would reload prior to code execution.
|
|
3470
|
+
This ensures latest code changes are executed. This argument must be used in
|
|
3471
|
+
conjunction with the local=True argument.
|
|
3472
|
+
|
|
3473
|
+
:return: MLRun RunObject or PipelineNodeWrapper
|
|
3025
3474
|
"""
|
|
3026
3475
|
return run_function(
|
|
3027
3476
|
function,
|
|
@@ -3046,6 +3495,7 @@ class MlrunProject(ModelObj):
|
|
|
3046
3495
|
notifications=notifications,
|
|
3047
3496
|
returns=returns,
|
|
3048
3497
|
builder_env=builder_env,
|
|
3498
|
+
reset_on_run=reset_on_run,
|
|
3049
3499
|
)
|
|
3050
3500
|
|
|
3051
3501
|
def build_function(
|
|
@@ -3064,7 +3514,7 @@ class MlrunProject(ModelObj):
|
|
|
3064
3514
|
requirements_file: str = None,
|
|
3065
3515
|
extra_args: str = None,
|
|
3066
3516
|
force_build: bool = False,
|
|
3067
|
-
) -> typing.Union[BuildStatus,
|
|
3517
|
+
) -> typing.Union[BuildStatus, PipelineNodeWrapper]:
|
|
3068
3518
|
"""deploy ML function, build container with its dependencies
|
|
3069
3519
|
|
|
3070
3520
|
:param function: name of the function (in the project) or function object
|
|
@@ -3118,6 +3568,7 @@ class MlrunProject(ModelObj):
|
|
|
3118
3568
|
requirements_file: str = None,
|
|
3119
3569
|
builder_env: dict = None,
|
|
3120
3570
|
extra_args: str = None,
|
|
3571
|
+
source_code_target_dir: str = None,
|
|
3121
3572
|
):
|
|
3122
3573
|
"""specify builder configuration for the project
|
|
3123
3574
|
|
|
@@ -3138,6 +3589,8 @@ class MlrunProject(ModelObj):
|
|
|
3138
3589
|
e.g. builder_env={"GIT_TOKEN": token}, does not work yet in KFP
|
|
3139
3590
|
:param extra_args: A string containing additional builder arguments in the format of command-line options,
|
|
3140
3591
|
e.g. extra_args="--skip-tls-verify --build-arg A=val"
|
|
3592
|
+
:param source_code_target_dir: Path on the image where source code would be extracted
|
|
3593
|
+
(by default `/home/mlrun_code`)
|
|
3141
3594
|
"""
|
|
3142
3595
|
if not overwrite_build_params:
|
|
3143
3596
|
# TODO: change overwrite_build_params default to True in 1.8.0
|
|
@@ -3161,6 +3614,7 @@ class MlrunProject(ModelObj):
|
|
|
3161
3614
|
overwrite=overwrite_build_params,
|
|
3162
3615
|
builder_env=builder_env,
|
|
3163
3616
|
extra_args=extra_args,
|
|
3617
|
+
source_code_target_dir=source_code_target_dir,
|
|
3164
3618
|
)
|
|
3165
3619
|
|
|
3166
3620
|
if set_as_default and image != self.default_image:
|
|
@@ -3171,7 +3625,6 @@ class MlrunProject(ModelObj):
|
|
|
3171
3625
|
image: str = None,
|
|
3172
3626
|
set_as_default: bool = True,
|
|
3173
3627
|
with_mlrun: bool = None,
|
|
3174
|
-
skip_deployed: bool = False,
|
|
3175
3628
|
base_image: str = None,
|
|
3176
3629
|
commands: list = None,
|
|
3177
3630
|
secret_name: str = None,
|
|
@@ -3182,7 +3635,7 @@ class MlrunProject(ModelObj):
|
|
|
3182
3635
|
requirements_file: str = None,
|
|
3183
3636
|
extra_args: str = None,
|
|
3184
3637
|
target_dir: str = None,
|
|
3185
|
-
) -> typing.Union[BuildStatus,
|
|
3638
|
+
) -> typing.Union[BuildStatus, PipelineNodeWrapper]:
|
|
3186
3639
|
"""Builder docker image for the project, based on the project's build config. Parameters allow to override
|
|
3187
3640
|
the build config.
|
|
3188
3641
|
If the project has a source configured and pull_at_runtime is not configured, this source will be cloned to the
|
|
@@ -3192,7 +3645,6 @@ class MlrunProject(ModelObj):
|
|
|
3192
3645
|
used. If not set, the `mlconf.default_project_image_name` value will be used
|
|
3193
3646
|
:param set_as_default: set `image` to be the project's default image (default False)
|
|
3194
3647
|
:param with_mlrun: add the current mlrun package to the container build
|
|
3195
|
-
:param skip_deployed: *Deprecated* parameter is ignored
|
|
3196
3648
|
:param base_image: base image name/path (commands and source code will be added to it) defaults to
|
|
3197
3649
|
mlrun.mlconf.default_base_image
|
|
3198
3650
|
:param commands: list of docker build (RUN) commands e.g. ['pip install pandas']
|
|
@@ -3207,7 +3659,7 @@ class MlrunProject(ModelObj):
|
|
|
3207
3659
|
* False: The new params are merged with the existing
|
|
3208
3660
|
* True: The existing params are replaced by the new ones
|
|
3209
3661
|
: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"
|
|
3662
|
+
e.g. extra_args="--skip-tls-verify --build-arg A=val"
|
|
3211
3663
|
:param target_dir: Path on the image where source code would be extracted (by default `/home/mlrun_code`)
|
|
3212
3664
|
"""
|
|
3213
3665
|
if not base_image:
|
|
@@ -3217,14 +3669,6 @@ class MlrunProject(ModelObj):
|
|
|
3217
3669
|
base_image=base_image,
|
|
3218
3670
|
)
|
|
3219
3671
|
|
|
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
3672
|
if not overwrite_build_params:
|
|
3229
3673
|
# TODO: change overwrite_build_params default to True in 1.8.0
|
|
3230
3674
|
warnings.warn(
|
|
@@ -3275,6 +3719,11 @@ class MlrunProject(ModelObj):
|
|
|
3275
3719
|
force_build=True,
|
|
3276
3720
|
)
|
|
3277
3721
|
|
|
3722
|
+
# Get the enriched target dir from the function
|
|
3723
|
+
self.spec.build.source_code_target_dir = (
|
|
3724
|
+
function.spec.build.source_code_target_dir
|
|
3725
|
+
)
|
|
3726
|
+
|
|
3278
3727
|
try:
|
|
3279
3728
|
mlrun.db.get_run_db(secrets=self._secrets).delete_function(
|
|
3280
3729
|
name=function.metadata.name
|
|
@@ -3283,7 +3732,7 @@ class MlrunProject(ModelObj):
|
|
|
3283
3732
|
logger.warning(
|
|
3284
3733
|
f"Image was successfully built, but failed to delete temporary function {function.metadata.name}."
|
|
3285
3734
|
" To remove the function, attempt to manually delete it.",
|
|
3286
|
-
exc=
|
|
3735
|
+
exc=mlrun.errors.err_to_str(exc),
|
|
3287
3736
|
)
|
|
3288
3737
|
|
|
3289
3738
|
return result
|
|
@@ -3297,7 +3746,7 @@ class MlrunProject(ModelObj):
|
|
|
3297
3746
|
verbose: bool = None,
|
|
3298
3747
|
builder_env: dict = None,
|
|
3299
3748
|
mock: bool = None,
|
|
3300
|
-
) -> typing.Union[DeployStatus,
|
|
3749
|
+
) -> typing.Union[DeployStatus, PipelineNodeWrapper]:
|
|
3301
3750
|
"""deploy real-time (nuclio based) functions
|
|
3302
3751
|
|
|
3303
3752
|
:param function: name of the function (in the project) or function object
|
|
@@ -3332,7 +3781,12 @@ class MlrunProject(ModelObj):
|
|
|
3332
3781
|
artifact = db.read_artifact(
|
|
3333
3782
|
key, tag, iter=iter, project=self.metadata.name, tree=tree
|
|
3334
3783
|
)
|
|
3335
|
-
|
|
3784
|
+
|
|
3785
|
+
# in tests, if an artifact is not found, the db returns None
|
|
3786
|
+
# in real usage, the db should raise an exception
|
|
3787
|
+
if artifact:
|
|
3788
|
+
return dict_to_artifact(artifact)
|
|
3789
|
+
return None
|
|
3336
3790
|
|
|
3337
3791
|
def list_artifacts(
|
|
3338
3792
|
self,
|
|
@@ -3346,6 +3800,10 @@ class MlrunProject(ModelObj):
|
|
|
3346
3800
|
kind: str = None,
|
|
3347
3801
|
category: typing.Union[str, mlrun.common.schemas.ArtifactCategories] = None,
|
|
3348
3802
|
tree: str = None,
|
|
3803
|
+
limit: int = None,
|
|
3804
|
+
format_: Optional[
|
|
3805
|
+
mlrun.common.formatters.ArtifactFormat
|
|
3806
|
+
] = mlrun.common.formatters.ArtifactFormat.full,
|
|
3349
3807
|
) -> mlrun.lists.ArtifactList:
|
|
3350
3808
|
"""List artifacts filtered by various parameters.
|
|
3351
3809
|
|
|
@@ -3355,9 +3813,9 @@ class MlrunProject(ModelObj):
|
|
|
3355
3813
|
Examples::
|
|
3356
3814
|
|
|
3357
3815
|
# Get latest version of all artifacts in project
|
|
3358
|
-
latest_artifacts = project.list_artifacts(
|
|
3816
|
+
latest_artifacts = project.list_artifacts("", tag="latest")
|
|
3359
3817
|
# check different artifact versions for a specific artifact, return as objects list
|
|
3360
|
-
result_versions = project.list_artifacts(
|
|
3818
|
+
result_versions = project.list_artifacts("results", tag="*").to_objects()
|
|
3361
3819
|
|
|
3362
3820
|
:param name: Name of artifacts to retrieve. Name with '~' prefix is used as a like query, and is not
|
|
3363
3821
|
case-sensitive. This means that querying for ``~name`` may return artifacts named
|
|
@@ -3375,6 +3833,8 @@ class MlrunProject(ModelObj):
|
|
|
3375
3833
|
:param kind: Return artifacts of the requested kind.
|
|
3376
3834
|
:param category: Return artifacts of the requested category.
|
|
3377
3835
|
:param tree: Return artifacts of the requested tree.
|
|
3836
|
+
:param limit: Maximum number of artifacts to return.
|
|
3837
|
+
:param format_: The format in which to return the artifacts. Default is 'full'.
|
|
3378
3838
|
"""
|
|
3379
3839
|
db = mlrun.db.get_run_db(secrets=self._secrets)
|
|
3380
3840
|
return db.list_artifacts(
|
|
@@ -3389,6 +3849,8 @@ class MlrunProject(ModelObj):
|
|
|
3389
3849
|
kind=kind,
|
|
3390
3850
|
category=category,
|
|
3391
3851
|
tree=tree,
|
|
3852
|
+
format_=format_,
|
|
3853
|
+
limit=limit,
|
|
3392
3854
|
)
|
|
3393
3855
|
|
|
3394
3856
|
def list_models(
|
|
@@ -3401,13 +3863,17 @@ class MlrunProject(ModelObj):
|
|
|
3401
3863
|
iter: int = None,
|
|
3402
3864
|
best_iteration: bool = False,
|
|
3403
3865
|
tree: str = None,
|
|
3866
|
+
limit: int = None,
|
|
3867
|
+
format_: Optional[
|
|
3868
|
+
mlrun.common.formatters.ArtifactFormat
|
|
3869
|
+
] = mlrun.common.formatters.ArtifactFormat.full,
|
|
3404
3870
|
):
|
|
3405
3871
|
"""List models in project, filtered by various parameters.
|
|
3406
3872
|
|
|
3407
3873
|
Examples::
|
|
3408
3874
|
|
|
3409
3875
|
# Get latest version of all models in project
|
|
3410
|
-
latest_models = project.list_models(
|
|
3876
|
+
latest_models = project.list_models("", tag="latest")
|
|
3411
3877
|
|
|
3412
3878
|
|
|
3413
3879
|
:param name: Name of artifacts to retrieve. Name with '~' prefix is used as a like query, and is not
|
|
@@ -3424,6 +3890,8 @@ class MlrunProject(ModelObj):
|
|
|
3424
3890
|
artifacts generated from a hyper-param run. If only a single iteration exists, will return the artifact
|
|
3425
3891
|
from that iteration. If using ``best_iter``, the ``iter`` parameter must not be used.
|
|
3426
3892
|
:param tree: Return artifacts of the requested tree.
|
|
3893
|
+
:param limit: Maximum number of artifacts to return.
|
|
3894
|
+
:param format_: The format in which to return the artifacts. Default is 'full'.
|
|
3427
3895
|
"""
|
|
3428
3896
|
db = mlrun.db.get_run_db(secrets=self._secrets)
|
|
3429
3897
|
return db.list_artifacts(
|
|
@@ -3437,6 +3905,8 @@ class MlrunProject(ModelObj):
|
|
|
3437
3905
|
best_iteration=best_iteration,
|
|
3438
3906
|
kind="model",
|
|
3439
3907
|
tree=tree,
|
|
3908
|
+
limit=limit,
|
|
3909
|
+
format_=format_,
|
|
3440
3910
|
).to_objects()
|
|
3441
3911
|
|
|
3442
3912
|
def list_functions(self, name=None, tag=None, labels=None):
|
|
@@ -3448,7 +3918,7 @@ class MlrunProject(ModelObj):
|
|
|
3448
3918
|
|
|
3449
3919
|
|
|
3450
3920
|
:param name: Return only functions with a specific name.
|
|
3451
|
-
:param tag: Return function versions with specific tags.
|
|
3921
|
+
:param tag: Return function versions with specific tags. To return only tagged functions, set tag to ``"*"``.
|
|
3452
3922
|
:param labels: Return functions that have specific labels assigned to them.
|
|
3453
3923
|
:returns: List of function objects.
|
|
3454
3924
|
"""
|
|
@@ -3477,9 +3947,7 @@ class MlrunProject(ModelObj):
|
|
|
3477
3947
|
:returns: List of function objects.
|
|
3478
3948
|
"""
|
|
3479
3949
|
|
|
3480
|
-
model_monitoring_labels_list = [
|
|
3481
|
-
f"{mm_constants.ModelMonitoringAppLabel.KEY}={mm_constants.ModelMonitoringAppLabel.VAL}"
|
|
3482
|
-
]
|
|
3950
|
+
model_monitoring_labels_list = [str(mm_constants.ModelMonitoringAppLabel())]
|
|
3483
3951
|
if labels:
|
|
3484
3952
|
model_monitoring_labels_list += labels
|
|
3485
3953
|
return self.list_functions(
|
|
@@ -3493,7 +3961,10 @@ class MlrunProject(ModelObj):
|
|
|
3493
3961
|
name: Optional[str] = None,
|
|
3494
3962
|
uid: Optional[Union[str, list[str]]] = None,
|
|
3495
3963
|
labels: Optional[Union[str, list[str]]] = None,
|
|
3496
|
-
state: Optional[
|
|
3964
|
+
state: Optional[
|
|
3965
|
+
mlrun.common.runtimes.constants.RunStates
|
|
3966
|
+
] = None, # Backward compatibility
|
|
3967
|
+
states: typing.Optional[list[mlrun.common.runtimes.constants.RunStates]] = None,
|
|
3497
3968
|
sort: bool = True,
|
|
3498
3969
|
last: int = 0,
|
|
3499
3970
|
iter: bool = False,
|
|
@@ -3512,14 +3983,14 @@ class MlrunProject(ModelObj):
|
|
|
3512
3983
|
Example::
|
|
3513
3984
|
|
|
3514
3985
|
# return a list of runs matching the name and label and compare
|
|
3515
|
-
runs = project.list_runs(name=
|
|
3986
|
+
runs = project.list_runs(name="download", labels="owner=admin")
|
|
3516
3987
|
runs.compare()
|
|
3517
3988
|
|
|
3518
3989
|
# multi-label filter can also be provided
|
|
3519
|
-
runs = project.list_runs(name=
|
|
3990
|
+
runs = project.list_runs(name="download", labels=["kind=job", "owner=admin"])
|
|
3520
3991
|
|
|
3521
3992
|
# If running in Jupyter, can use the .show() function to display the results
|
|
3522
|
-
project.list_runs(name=
|
|
3993
|
+
project.list_runs(name="").show()
|
|
3523
3994
|
|
|
3524
3995
|
|
|
3525
3996
|
:param name: Name of the run to retrieve.
|
|
@@ -3527,10 +3998,11 @@ class MlrunProject(ModelObj):
|
|
|
3527
3998
|
:param labels: A list of labels to filter by. Label filters work by either filtering a specific value
|
|
3528
3999
|
of a label (i.e. list("key=value")) or by looking for the existence of a given
|
|
3529
4000
|
key (i.e. "key").
|
|
3530
|
-
:param state: List only runs whose state is specified.
|
|
4001
|
+
:param state: Deprecated - List only runs whose state is specified.
|
|
4002
|
+
:param states: List only runs whose state is one of the provided states.
|
|
3531
4003
|
:param sort: Whether to sort the result according to their start time. Otherwise, results will be
|
|
3532
4004
|
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.
|
|
4005
|
+
:param last: Deprecated - currently not used (will be removed in 1.9.0).
|
|
3534
4006
|
:param iter: If ``True`` return runs from all iterations. Otherwise, return only runs whose ``iter`` is 0.
|
|
3535
4007
|
:param start_time_from: Filter by run start time in ``[start_time_from, start_time_to]``.
|
|
3536
4008
|
:param start_time_to: Filter by run start time in ``[start_time_from, start_time_to]``.
|
|
@@ -3538,13 +4010,22 @@ class MlrunProject(ModelObj):
|
|
|
3538
4010
|
last_update_time_to)``.
|
|
3539
4011
|
:param last_update_time_to: Filter by run last update time in ``(last_update_time_from, last_update_time_to)``.
|
|
3540
4012
|
"""
|
|
4013
|
+
if state:
|
|
4014
|
+
# TODO: Remove this in 1.9.0
|
|
4015
|
+
warnings.warn(
|
|
4016
|
+
"'state' is deprecated and will be removed in 1.9.0. Use 'states' instead.",
|
|
4017
|
+
FutureWarning,
|
|
4018
|
+
)
|
|
4019
|
+
|
|
3541
4020
|
db = mlrun.db.get_run_db(secrets=self._secrets)
|
|
3542
4021
|
return db.list_runs(
|
|
3543
4022
|
name,
|
|
3544
4023
|
uid,
|
|
3545
4024
|
self.metadata.name,
|
|
3546
4025
|
labels=labels,
|
|
3547
|
-
|
|
4026
|
+
states=mlrun.utils.helpers.as_list(state)
|
|
4027
|
+
if state is not None
|
|
4028
|
+
else states or None,
|
|
3548
4029
|
sort=sort,
|
|
3549
4030
|
last=last,
|
|
3550
4031
|
iter=iter,
|
|
@@ -3625,12 +4106,194 @@ class MlrunProject(ModelObj):
|
|
|
3625
4106
|
"""
|
|
3626
4107
|
self.spec.remove_custom_packager(packager=packager)
|
|
3627
4108
|
|
|
4109
|
+
def store_api_gateway(
|
|
4110
|
+
self,
|
|
4111
|
+
api_gateway: mlrun.runtimes.nuclio.api_gateway.APIGateway,
|
|
4112
|
+
wait_for_readiness=True,
|
|
4113
|
+
max_wait_time=90,
|
|
4114
|
+
) -> mlrun.runtimes.nuclio.api_gateway.APIGateway:
|
|
4115
|
+
"""
|
|
4116
|
+
Creates or updates a Nuclio API Gateway using the provided APIGateway object.
|
|
4117
|
+
|
|
4118
|
+
This method interacts with the MLRun service to create/update a Nuclio API Gateway based on the provided
|
|
4119
|
+
APIGateway object. Once done, it returns the updated APIGateway object containing all fields propagated
|
|
4120
|
+
on MLRun and Nuclio sides, such as the 'host' attribute.
|
|
4121
|
+
Nuclio docs here: https://docs.nuclio.io/en/latest/reference/api-gateway/http.html
|
|
4122
|
+
|
|
4123
|
+
:param api_gateway: An instance of :py:class:`~mlrun.runtimes.nuclio.APIGateway` representing the
|
|
4124
|
+
configuration of the API Gateway to be created or updated.
|
|
4125
|
+
:param wait_for_readiness: (Optional) A boolean indicating whether to wait for the API Gateway to become
|
|
4126
|
+
ready after creation or update (default is True).
|
|
4127
|
+
:param max_wait_time: (Optional) Maximum time to wait for API Gateway readiness in seconds (default is 90s)
|
|
4128
|
+
|
|
4129
|
+
|
|
4130
|
+
:returns: An instance of :py:class:`~mlrun.runtimes.nuclio.APIGateway` with all fields populated based on the
|
|
4131
|
+
information retrieved from the Nuclio API
|
|
4132
|
+
"""
|
|
4133
|
+
|
|
4134
|
+
api_gateway_json = mlrun.db.get_run_db().store_api_gateway(
|
|
4135
|
+
api_gateway=api_gateway,
|
|
4136
|
+
project=self.name,
|
|
4137
|
+
)
|
|
4138
|
+
|
|
4139
|
+
if api_gateway_json:
|
|
4140
|
+
# fill in all the fields in the user's api_gateway object
|
|
4141
|
+
api_gateway = mlrun.runtimes.nuclio.api_gateway.APIGateway.from_scheme(
|
|
4142
|
+
api_gateway_json
|
|
4143
|
+
)
|
|
4144
|
+
if wait_for_readiness:
|
|
4145
|
+
api_gateway.wait_for_readiness(max_wait_time=max_wait_time)
|
|
4146
|
+
|
|
4147
|
+
return api_gateway
|
|
4148
|
+
|
|
4149
|
+
def list_api_gateways(self) -> list[mlrun.runtimes.nuclio.api_gateway.APIGateway]:
|
|
4150
|
+
"""
|
|
4151
|
+
Retrieves a list of Nuclio API gateways associated with the project.
|
|
4152
|
+
|
|
4153
|
+
:returns: List of :py:class:`~mlrun.runtimes.nuclio.api_gateway.APIGateway` objects representing
|
|
4154
|
+
the Nuclio API gateways associated with the project.
|
|
4155
|
+
"""
|
|
4156
|
+
gateways_list = mlrun.db.get_run_db().list_api_gateways(self.name)
|
|
4157
|
+
return [
|
|
4158
|
+
mlrun.runtimes.nuclio.api_gateway.APIGateway.from_scheme(gateway_dict)
|
|
4159
|
+
for gateway_dict in gateways_list.api_gateways.values()
|
|
4160
|
+
]
|
|
4161
|
+
|
|
4162
|
+
def get_api_gateway(
|
|
4163
|
+
self,
|
|
4164
|
+
name: str,
|
|
4165
|
+
) -> mlrun.runtimes.nuclio.api_gateway.APIGateway:
|
|
4166
|
+
"""
|
|
4167
|
+
Retrieves an API gateway by name instance.
|
|
4168
|
+
|
|
4169
|
+
:param name: The name of the API gateway to retrieve.
|
|
4170
|
+
|
|
4171
|
+
Returns:
|
|
4172
|
+
mlrun.runtimes.nuclio.APIGateway: An instance of APIGateway.
|
|
4173
|
+
"""
|
|
4174
|
+
|
|
4175
|
+
gateway = mlrun.db.get_run_db().get_api_gateway(name=name, project=self.name)
|
|
4176
|
+
return mlrun.runtimes.nuclio.api_gateway.APIGateway.from_scheme(gateway)
|
|
4177
|
+
|
|
4178
|
+
def delete_api_gateway(
|
|
4179
|
+
self,
|
|
4180
|
+
name: str,
|
|
4181
|
+
):
|
|
4182
|
+
"""
|
|
4183
|
+
Deletes an API gateway by name.
|
|
4184
|
+
|
|
4185
|
+
:param name: The name of the API gateway to delete.
|
|
4186
|
+
"""
|
|
4187
|
+
|
|
4188
|
+
mlrun.db.get_run_db().delete_api_gateway(name=name, project=self.name)
|
|
4189
|
+
|
|
4190
|
+
def store_alert_config(
|
|
4191
|
+
self, alert_data: AlertConfig, alert_name: typing.Optional[str] = None
|
|
4192
|
+
) -> AlertConfig:
|
|
4193
|
+
"""
|
|
4194
|
+
Create/modify an alert.
|
|
4195
|
+
|
|
4196
|
+
:param alert_data: The data of the alert.
|
|
4197
|
+
:param alert_name: The name of the alert.
|
|
4198
|
+
:return: the created/modified alert.
|
|
4199
|
+
"""
|
|
4200
|
+
if not alert_data:
|
|
4201
|
+
raise mlrun.errors.MLRunInvalidArgumentError("Alert data must be provided")
|
|
4202
|
+
|
|
4203
|
+
db = mlrun.db.get_run_db(secrets=self._secrets)
|
|
4204
|
+
alert_name = alert_name or alert_data.name
|
|
4205
|
+
if alert_data.project is not None and alert_data.project != self.metadata.name:
|
|
4206
|
+
logger.warn(
|
|
4207
|
+
"Project in alert does not match project in operation",
|
|
4208
|
+
project=alert_data.project,
|
|
4209
|
+
)
|
|
4210
|
+
alert_data.project = self.metadata.name
|
|
4211
|
+
return db.store_alert_config(alert_name, alert_data, project=self.metadata.name)
|
|
4212
|
+
|
|
4213
|
+
def get_alert_config(self, alert_name: str) -> AlertConfig:
|
|
4214
|
+
"""
|
|
4215
|
+
Retrieve an alert.
|
|
4216
|
+
|
|
4217
|
+
:param alert_name: The name of the alert to retrieve.
|
|
4218
|
+
:return: The alert object.
|
|
4219
|
+
"""
|
|
4220
|
+
db = mlrun.db.get_run_db(secrets=self._secrets)
|
|
4221
|
+
return db.get_alert_config(alert_name, self.metadata.name)
|
|
4222
|
+
|
|
4223
|
+
def list_alerts_configs(self) -> list[AlertConfig]:
|
|
4224
|
+
"""
|
|
4225
|
+
Retrieve list of alerts of a project.
|
|
4226
|
+
|
|
4227
|
+
:return: All the alerts objects of the project.
|
|
4228
|
+
"""
|
|
4229
|
+
db = mlrun.db.get_run_db(secrets=self._secrets)
|
|
4230
|
+
return db.list_alerts_configs(self.metadata.name)
|
|
4231
|
+
|
|
4232
|
+
def delete_alert_config(
|
|
4233
|
+
self, alert_data: AlertConfig = None, alert_name: str = None
|
|
4234
|
+
):
|
|
4235
|
+
"""
|
|
4236
|
+
Delete an alert.
|
|
4237
|
+
|
|
4238
|
+
:param alert_data: The data of the alert.
|
|
4239
|
+
:param alert_name: The name of the alert to delete.
|
|
4240
|
+
"""
|
|
4241
|
+
if alert_data is None and alert_name is None:
|
|
4242
|
+
raise ValueError(
|
|
4243
|
+
"At least one of alert_data or alert_name must be provided"
|
|
4244
|
+
)
|
|
4245
|
+
if alert_data and alert_name and alert_data.name != alert_name:
|
|
4246
|
+
raise ValueError("Alert_data name does not match the provided alert_name")
|
|
4247
|
+
db = mlrun.db.get_run_db(secrets=self._secrets)
|
|
4248
|
+
if alert_data:
|
|
4249
|
+
alert_name = alert_data.name
|
|
4250
|
+
db.delete_alert_config(alert_name, self.metadata.name)
|
|
4251
|
+
|
|
4252
|
+
def reset_alert_config(
|
|
4253
|
+
self, alert_data: AlertConfig = None, alert_name: str = None
|
|
4254
|
+
):
|
|
4255
|
+
"""
|
|
4256
|
+
Reset an alert.
|
|
4257
|
+
|
|
4258
|
+
:param alert_data: The data of the alert.
|
|
4259
|
+
:param alert_name: The name of the alert to reset.
|
|
4260
|
+
"""
|
|
4261
|
+
if alert_data is None and alert_name is None:
|
|
4262
|
+
raise ValueError(
|
|
4263
|
+
"At least one of alert_data or alert_name must be provided"
|
|
4264
|
+
)
|
|
4265
|
+
if alert_data and alert_name and alert_data.name != alert_name:
|
|
4266
|
+
raise ValueError("Alert_data name does not match the provided alert_name")
|
|
4267
|
+
db = mlrun.db.get_run_db(secrets=self._secrets)
|
|
4268
|
+
if alert_data:
|
|
4269
|
+
alert_name = alert_data.name
|
|
4270
|
+
db.reset_alert_config(alert_name, self.metadata.name)
|
|
4271
|
+
|
|
4272
|
+
def get_alert_template(self, template_name: str) -> AlertTemplate:
|
|
4273
|
+
"""
|
|
4274
|
+
Retrieve a specific alert template.
|
|
4275
|
+
|
|
4276
|
+
:param template_name: The name of the template to retrieve.
|
|
4277
|
+
:return: The template object.
|
|
4278
|
+
"""
|
|
4279
|
+
db = mlrun.db.get_run_db(secrets=self._secrets)
|
|
4280
|
+
return db.get_alert_template(template_name)
|
|
4281
|
+
|
|
4282
|
+
def list_alert_templates(self) -> list[AlertTemplate]:
|
|
4283
|
+
"""
|
|
4284
|
+
Retrieve list of all alert templates.
|
|
4285
|
+
|
|
4286
|
+
:return: All the alert template objects in the database.
|
|
4287
|
+
"""
|
|
4288
|
+
db = mlrun.db.get_run_db(secrets=self._secrets)
|
|
4289
|
+
return db.list_alert_templates()
|
|
4290
|
+
|
|
3628
4291
|
def _run_authenticated_git_action(
|
|
3629
4292
|
self,
|
|
3630
4293
|
action: Callable,
|
|
3631
4294
|
remote: str,
|
|
3632
|
-
args: list =
|
|
3633
|
-
kwargs: dict =
|
|
4295
|
+
args: list = None,
|
|
4296
|
+
kwargs: dict = None,
|
|
3634
4297
|
secrets: Union[SecretsStore, dict] = None,
|
|
3635
4298
|
):
|
|
3636
4299
|
"""Run an arbitrary Git routine while the remote is enriched with secrets
|
|
@@ -3650,6 +4313,8 @@ class MlrunProject(ModelObj):
|
|
|
3650
4313
|
try:
|
|
3651
4314
|
if is_remote_enriched:
|
|
3652
4315
|
self.spec.repo.remotes[remote].set_url(enriched_remote, clean_remote)
|
|
4316
|
+
args = args or []
|
|
4317
|
+
kwargs = kwargs or {}
|
|
3653
4318
|
action(*args, **kwargs)
|
|
3654
4319
|
except RuntimeError as e:
|
|
3655
4320
|
raise mlrun.errors.MLRunRuntimeError(
|
|
@@ -3702,6 +4367,97 @@ class MlrunProject(ModelObj):
|
|
|
3702
4367
|
f"<project.spec.get_code_path()>/<{param_name}>)."
|
|
3703
4368
|
)
|
|
3704
4369
|
|
|
4370
|
+
def _resolve_artifact_producer(
|
|
4371
|
+
self,
|
|
4372
|
+
artifact: typing.Union[str, Artifact],
|
|
4373
|
+
project_producer_tag: str = None,
|
|
4374
|
+
) -> tuple[ArtifactProducer, bool]:
|
|
4375
|
+
"""
|
|
4376
|
+
Resolve the artifact producer of the given artifact.
|
|
4377
|
+
If the artifact's producer is a run, the artifact is registered with the original producer.
|
|
4378
|
+
Otherwise, the artifact is registered with the current project as the producer.
|
|
4379
|
+
|
|
4380
|
+
:param artifact: The artifact to resolve its producer.
|
|
4381
|
+
:param project_producer_tag: The tag to use for the project as the producer. If not provided, a tag will be
|
|
4382
|
+
generated for the project.
|
|
4383
|
+
:return: A tuple of the resolved producer and whether it is retained or not.
|
|
4384
|
+
"""
|
|
4385
|
+
|
|
4386
|
+
if not isinstance(artifact, str) and artifact.spec.producer:
|
|
4387
|
+
# if the artifact was imported from a yaml file, the producer can be a dict
|
|
4388
|
+
if isinstance(artifact.spec.producer, ArtifactProducer):
|
|
4389
|
+
producer_dict = artifact.spec.producer.get_meta()
|
|
4390
|
+
else:
|
|
4391
|
+
producer_dict = artifact.spec.producer
|
|
4392
|
+
|
|
4393
|
+
producer_tag = producer_dict.get("tag", None)
|
|
4394
|
+
producer_project = producer_dict.get("project", None)
|
|
4395
|
+
if not producer_tag or not producer_project:
|
|
4396
|
+
# try resolving the producer tag from the uri
|
|
4397
|
+
producer_uri = artifact.spec.producer.get("uri", "")
|
|
4398
|
+
producer_project, producer_tag, _ = ArtifactProducer.parse_uri(
|
|
4399
|
+
producer_uri
|
|
4400
|
+
)
|
|
4401
|
+
|
|
4402
|
+
if producer_dict.get("kind", "") == "run":
|
|
4403
|
+
return ArtifactProducer(
|
|
4404
|
+
name=producer_dict.get("name", ""),
|
|
4405
|
+
kind=producer_dict.get("kind", ""),
|
|
4406
|
+
project=producer_project,
|
|
4407
|
+
tag=producer_tag,
|
|
4408
|
+
owner=producer_dict.get("owner", ""),
|
|
4409
|
+
), True
|
|
4410
|
+
|
|
4411
|
+
# do not retain the artifact's producer, replace it with the project as the producer
|
|
4412
|
+
project_producer_tag = project_producer_tag or self._get_project_tag()
|
|
4413
|
+
return ArtifactProducer(
|
|
4414
|
+
kind="project",
|
|
4415
|
+
name=self.metadata.name,
|
|
4416
|
+
project=self.metadata.name,
|
|
4417
|
+
tag=project_producer_tag,
|
|
4418
|
+
owner=self._resolve_artifact_owner(),
|
|
4419
|
+
), False
|
|
4420
|
+
|
|
4421
|
+
def _resolve_existing_artifact(
|
|
4422
|
+
self,
|
|
4423
|
+
item: typing.Union[str, Artifact],
|
|
4424
|
+
tag: str = None,
|
|
4425
|
+
) -> typing.Optional[Artifact]:
|
|
4426
|
+
"""
|
|
4427
|
+
Check if there is and existing artifact with the given item and tag.
|
|
4428
|
+
If there is, return the existing artifact. Otherwise, return None.
|
|
4429
|
+
|
|
4430
|
+
:param item: The item (or key) to check if there is an existing artifact for.
|
|
4431
|
+
:param tag: The tag to check if there is an existing artifact for.
|
|
4432
|
+
:return: The existing artifact if there is one, otherwise None.
|
|
4433
|
+
"""
|
|
4434
|
+
try:
|
|
4435
|
+
if isinstance(item, str):
|
|
4436
|
+
existing_artifact = self.get_artifact(key=item, tag=tag)
|
|
4437
|
+
else:
|
|
4438
|
+
existing_artifact = self.get_artifact(
|
|
4439
|
+
key=item.key,
|
|
4440
|
+
tag=item.tag,
|
|
4441
|
+
iter=item.iter,
|
|
4442
|
+
tree=item.tree,
|
|
4443
|
+
)
|
|
4444
|
+
if existing_artifact is not None:
|
|
4445
|
+
return existing_artifact.from_dict(existing_artifact)
|
|
4446
|
+
except mlrun.errors.MLRunNotFoundError:
|
|
4447
|
+
logger.debug(
|
|
4448
|
+
"No existing artifact was found",
|
|
4449
|
+
key=item if isinstance(item, str) else item.key,
|
|
4450
|
+
tag=tag if isinstance(item, str) else item.tag,
|
|
4451
|
+
tree=None if isinstance(item, str) else item.tree,
|
|
4452
|
+
)
|
|
4453
|
+
return None
|
|
4454
|
+
|
|
4455
|
+
def _get_project_tag(self):
|
|
4456
|
+
return self._get_hexsha() or str(uuid.uuid4())
|
|
4457
|
+
|
|
4458
|
+
def _resolve_artifact_owner(self):
|
|
4459
|
+
return os.getenv("V3IO_USERNAME") or self.spec.owner
|
|
4460
|
+
|
|
3705
4461
|
|
|
3706
4462
|
def _set_as_current_default_project(project: MlrunProject):
|
|
3707
4463
|
mlrun.mlconf.default_project = project.metadata.name
|
|
@@ -3724,10 +4480,6 @@ def _init_function_from_dict(
|
|
|
3724
4480
|
tag = f.get("tag", None)
|
|
3725
4481
|
|
|
3726
4482
|
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
4483
|
relative_url = url
|
|
3732
4484
|
url, in_context = project.get_item_absolute_path(url)
|
|
3733
4485
|
|
|
@@ -3757,18 +4509,17 @@ def _init_function_from_dict(
|
|
|
3757
4509
|
)
|
|
3758
4510
|
|
|
3759
4511
|
elif url.endswith(".py"):
|
|
3760
|
-
# when load_source_on_run is used we allow not providing image as code will be loaded pre-run. ML-4994
|
|
3761
|
-
if (
|
|
3762
|
-
not image
|
|
3763
|
-
and not project.default_image
|
|
3764
|
-
and kind != "local"
|
|
3765
|
-
and not project.spec.load_source_on_run
|
|
3766
|
-
):
|
|
3767
|
-
raise ValueError(
|
|
3768
|
-
"image must be provided with py code files which do not "
|
|
3769
|
-
"run on 'local' engine kind"
|
|
3770
|
-
)
|
|
3771
4512
|
if in_context and with_repo:
|
|
4513
|
+
# when load_source_on_run is used we allow not providing image as code will be loaded pre-run. ML-4994
|
|
4514
|
+
if (
|
|
4515
|
+
not image
|
|
4516
|
+
and not project.default_image
|
|
4517
|
+
and kind != "local"
|
|
4518
|
+
and not project.spec.load_source_on_run
|
|
4519
|
+
):
|
|
4520
|
+
raise ValueError(
|
|
4521
|
+
"image must be provided with py code files which do not run on 'local' engine kind"
|
|
4522
|
+
)
|
|
3772
4523
|
func = new_function(
|
|
3773
4524
|
name,
|
|
3774
4525
|
command=relative_url,
|
|
@@ -3787,6 +4538,17 @@ def _init_function_from_dict(
|
|
|
3787
4538
|
tag=tag,
|
|
3788
4539
|
)
|
|
3789
4540
|
|
|
4541
|
+
elif kind in mlrun.runtimes.RuntimeKinds.nuclio_runtimes():
|
|
4542
|
+
func = new_function(
|
|
4543
|
+
name,
|
|
4544
|
+
image=image,
|
|
4545
|
+
kind=kind,
|
|
4546
|
+
handler=handler,
|
|
4547
|
+
tag=tag,
|
|
4548
|
+
)
|
|
4549
|
+
if image and kind != mlrun.runtimes.RuntimeKinds.application:
|
|
4550
|
+
logger.info("Function code not specified, setting entry point to image")
|
|
4551
|
+
func.from_image(image)
|
|
3790
4552
|
else:
|
|
3791
4553
|
raise ValueError(f"Unsupported function url:handler {url}:{handler} or no spec")
|
|
3792
4554
|
|
|
@@ -3832,9 +4594,17 @@ def _init_function_from_obj(
|
|
|
3832
4594
|
def _has_module(handler, kind):
|
|
3833
4595
|
if not handler:
|
|
3834
4596
|
return False
|
|
3835
|
-
|
|
3836
|
-
|
|
3837
|
-
|
|
4597
|
+
|
|
4598
|
+
if (
|
|
4599
|
+
kind in mlrun.runtimes.RuntimeKinds.pure_nuclio_deployed_runtimes()
|
|
4600
|
+
and ":" in handler
|
|
4601
|
+
):
|
|
4602
|
+
return True
|
|
4603
|
+
|
|
4604
|
+
if "." in handler:
|
|
4605
|
+
return True
|
|
4606
|
+
|
|
4607
|
+
return False
|
|
3838
4608
|
|
|
3839
4609
|
|
|
3840
4610
|
def _is_imported_artifact(artifact):
|