mlrun 1.7.0rc5__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 -2
- 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 +21 -4
- mlrun/common/schemas/alert.py +202 -0
- mlrun/common/schemas/api_gateway.py +113 -2
- mlrun/common/schemas/artifact.py +28 -1
- mlrun/common/schemas/auth.py +11 -0
- 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 +224 -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 +374 -102
- mlrun/datastore/utils.py +68 -5
- mlrun/datastore/v3io.py +28 -50
- mlrun/db/auth_utils.py +152 -0
- mlrun/db/base.py +231 -22
- mlrun/db/factory.py +1 -4
- mlrun/db/httpdb.py +864 -228
- mlrun/db/nopdb.py +268 -16
- 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 +1125 -414
- mlrun/render.py +28 -22
- mlrun/run.py +207 -180
- mlrun/runtimes/__init__.py +76 -11
- mlrun/runtimes/base.py +40 -14
- 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/api_gateway.py +646 -177
- 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.0rc5.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.0rc5.dist-info/METADATA +0 -269
- mlrun-1.7.0rc5.dist-info/RECORD +0 -323
- {mlrun-1.7.0rc5.dist-info → mlrun-1.7.2.dist-info}/LICENSE +0 -0
- {mlrun-1.7.0rc5.dist-info → mlrun-1.7.2.dist-info}/entry_points.txt +0 -0
- {mlrun-1.7.0rc5.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,27 +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
|
|
44
54
|
import mlrun.runtimes.nuclio.api_gateway
|
|
45
55
|
import mlrun.runtimes.pod
|
|
46
56
|
import mlrun.runtimes.utils
|
|
57
|
+
import mlrun.serving
|
|
58
|
+
import mlrun.utils
|
|
47
59
|
import mlrun.utils.regex
|
|
60
|
+
from mlrun.alerts.alert import AlertConfig
|
|
61
|
+
from mlrun.common.schemas.alert import AlertTemplate
|
|
48
62
|
from mlrun.datastore.datastore_profile import DatastoreProfile, DatastoreProfile2Json
|
|
49
63
|
from mlrun.runtimes.nuclio.function import RemoteRuntime
|
|
50
64
|
|
|
@@ -53,20 +67,9 @@ from ..artifacts.manager import ArtifactManager, dict_to_artifact, extend_artifa
|
|
|
53
67
|
from ..datastore import store_manager
|
|
54
68
|
from ..features import Feature
|
|
55
69
|
from ..model import EntrypointParam, ImageBuilder, ModelObj
|
|
56
|
-
from ..model_monitoring.application import (
|
|
57
|
-
ModelMonitoringApplicationBase,
|
|
58
|
-
PushToMonitoringWriter,
|
|
59
|
-
)
|
|
60
70
|
from ..run import code_to_function, get_object, import_function, new_function
|
|
61
71
|
from ..secrets import SecretsStore
|
|
62
|
-
from ..utils import
|
|
63
|
-
is_ipython,
|
|
64
|
-
is_legacy_artifact,
|
|
65
|
-
is_relative_path,
|
|
66
|
-
is_yaml_path,
|
|
67
|
-
logger,
|
|
68
|
-
update_in,
|
|
69
|
-
)
|
|
72
|
+
from ..utils import is_jupyter, is_relative_path, is_yaml_path, logger, update_in
|
|
70
73
|
from ..utils.clones import (
|
|
71
74
|
add_credentials_git_remote_url,
|
|
72
75
|
clone_git,
|
|
@@ -74,7 +77,10 @@ from ..utils.clones import (
|
|
|
74
77
|
clone_zip,
|
|
75
78
|
get_repo_url,
|
|
76
79
|
)
|
|
77
|
-
from ..utils.helpers import
|
|
80
|
+
from ..utils.helpers import (
|
|
81
|
+
ensure_git_branch,
|
|
82
|
+
resolve_git_reference_from_source,
|
|
83
|
+
)
|
|
78
84
|
from ..utils.notifications import CustomNotificationPusher, NotificationTypes
|
|
79
85
|
from .operations import (
|
|
80
86
|
BuildStatus,
|
|
@@ -128,6 +134,7 @@ def new_project(
|
|
|
128
134
|
save: bool = True,
|
|
129
135
|
overwrite: bool = False,
|
|
130
136
|
parameters: dict = None,
|
|
137
|
+
default_function_node_selector: dict = None,
|
|
131
138
|
) -> "MlrunProject":
|
|
132
139
|
"""Create a new MLRun project, optionally load it from a yaml/zip/git template
|
|
133
140
|
|
|
@@ -138,11 +145,15 @@ def new_project(
|
|
|
138
145
|
example::
|
|
139
146
|
|
|
140
147
|
# create a project with local and hub functions, a workflow, and an artifact
|
|
141
|
-
project = mlrun.new_project(
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
project.
|
|
145
|
-
|
|
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")
|
|
146
157
|
project.save()
|
|
147
158
|
|
|
148
159
|
# run the "main" workflow (watch=True to wait for run completion)
|
|
@@ -152,19 +163,25 @@ def new_project(
|
|
|
152
163
|
|
|
153
164
|
# create a new project from a zip template (can also use yaml/git templates)
|
|
154
165
|
# initialize a local git, and register the git remote path
|
|
155
|
-
project = mlrun.new_project(
|
|
156
|
-
|
|
157
|
-
|
|
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
|
+
)
|
|
158
173
|
project.run("main", watch=True)
|
|
159
174
|
|
|
160
175
|
|
|
161
176
|
example using project_setup.py to init the project objects::
|
|
162
177
|
|
|
163
178
|
def setup(project):
|
|
164
|
-
project.set_function(
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
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")
|
|
168
185
|
return project
|
|
169
186
|
|
|
170
187
|
|
|
@@ -181,6 +198,7 @@ def new_project(
|
|
|
181
198
|
:param overwrite: overwrite project using 'cascade' deletion strategy (deletes project resources)
|
|
182
199
|
if project with name exists
|
|
183
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
|
|
184
202
|
|
|
185
203
|
:returns: project object
|
|
186
204
|
"""
|
|
@@ -193,14 +211,16 @@ def new_project(
|
|
|
193
211
|
"Unsupported option, cannot use subpath argument with project templates"
|
|
194
212
|
)
|
|
195
213
|
if from_template.endswith(".yaml"):
|
|
196
|
-
project = _load_project_file(
|
|
214
|
+
project = _load_project_file(
|
|
215
|
+
from_template, name, secrets, allow_cross_project=True
|
|
216
|
+
)
|
|
197
217
|
elif from_template.startswith("git://"):
|
|
198
218
|
clone_git(from_template, context, secrets, clone=True)
|
|
199
219
|
shutil.rmtree(path.join(context, ".git"))
|
|
200
|
-
project = _load_project_dir(context, name)
|
|
220
|
+
project = _load_project_dir(context, name, allow_cross_project=True)
|
|
201
221
|
elif from_template.endswith(".zip"):
|
|
202
222
|
clone_zip(from_template, context, secrets)
|
|
203
|
-
project = _load_project_dir(context, name)
|
|
223
|
+
project = _load_project_dir(context, name, allow_cross_project=True)
|
|
204
224
|
else:
|
|
205
225
|
raise ValueError("template must be a path to .yaml or .zip file")
|
|
206
226
|
project.metadata.name = name
|
|
@@ -227,6 +247,10 @@ def new_project(
|
|
|
227
247
|
project.spec.origin_url = url
|
|
228
248
|
if description:
|
|
229
249
|
project.spec.description = description
|
|
250
|
+
|
|
251
|
+
if default_function_node_selector:
|
|
252
|
+
project.spec.default_function_node_selector = default_function_node_selector
|
|
253
|
+
|
|
230
254
|
if parameters:
|
|
231
255
|
# Enable setting project parameters at load time, can be used to customize the project_setup
|
|
232
256
|
for key, val in parameters.items():
|
|
@@ -277,6 +301,7 @@ def load_project(
|
|
|
277
301
|
save: bool = True,
|
|
278
302
|
sync_functions: bool = False,
|
|
279
303
|
parameters: dict = None,
|
|
304
|
+
allow_cross_project: bool = None,
|
|
280
305
|
) -> "MlrunProject":
|
|
281
306
|
"""Load an MLRun project from git or tar or dir
|
|
282
307
|
|
|
@@ -290,7 +315,7 @@ def load_project(
|
|
|
290
315
|
# When using git as the url source the context directory must be an empty or
|
|
291
316
|
# non-existent folder as the git repo will be cloned there
|
|
292
317
|
project = load_project("./demo_proj", "git://github.com/mlrun/project-demo.git")
|
|
293
|
-
project.run("main", arguments={
|
|
318
|
+
project.run("main", arguments={"data": data_url})
|
|
294
319
|
|
|
295
320
|
|
|
296
321
|
project_setup.py example::
|
|
@@ -323,6 +348,8 @@ def load_project(
|
|
|
323
348
|
:param save: whether to save the created project and artifact in the DB
|
|
324
349
|
:param sync_functions: sync the project's functions into the project object (will be saved to the DB if save=True)
|
|
325
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
|
|
326
353
|
|
|
327
354
|
:returns: project object
|
|
328
355
|
"""
|
|
@@ -338,7 +365,7 @@ def load_project(
|
|
|
338
365
|
if url:
|
|
339
366
|
url = str(url) # to support path objects
|
|
340
367
|
if is_yaml_path(url):
|
|
341
|
-
project = _load_project_file(url, name, secrets)
|
|
368
|
+
project = _load_project_file(url, name, secrets, allow_cross_project)
|
|
342
369
|
project.spec.context = context
|
|
343
370
|
elif url.startswith("git://"):
|
|
344
371
|
url, repo = clone_git(url, context, secrets, clone)
|
|
@@ -365,7 +392,7 @@ def load_project(
|
|
|
365
392
|
repo, url = init_repo(context, url, init_git)
|
|
366
393
|
|
|
367
394
|
if not project:
|
|
368
|
-
project = _load_project_dir(context, name, subpath)
|
|
395
|
+
project = _load_project_dir(context, name, subpath, allow_cross_project)
|
|
369
396
|
|
|
370
397
|
if not project.metadata.name:
|
|
371
398
|
raise ValueError("Project name must be specified")
|
|
@@ -419,6 +446,7 @@ def get_or_create_project(
|
|
|
419
446
|
from_template: str = None,
|
|
420
447
|
save: bool = True,
|
|
421
448
|
parameters: dict = None,
|
|
449
|
+
allow_cross_project: bool = None,
|
|
422
450
|
) -> "MlrunProject":
|
|
423
451
|
"""Load a project from MLRun DB, or create/import if it does not exist
|
|
424
452
|
|
|
@@ -429,9 +457,11 @@ def get_or_create_project(
|
|
|
429
457
|
Usage example::
|
|
430
458
|
|
|
431
459
|
# load project from the DB (if exist) or the source repo
|
|
432
|
-
project = get_or_create_project(
|
|
460
|
+
project = get_or_create_project(
|
|
461
|
+
"myproj", "./", "git://github.com/mlrun/demo-xgb-project.git"
|
|
462
|
+
)
|
|
433
463
|
project.pull("development") # pull the latest code from git
|
|
434
|
-
project.run("main", arguments={
|
|
464
|
+
project.run("main", arguments={"data": data_url}) # run the workflow "main"
|
|
435
465
|
|
|
436
466
|
|
|
437
467
|
project_setup.py example::
|
|
@@ -461,12 +491,12 @@ def get_or_create_project(
|
|
|
461
491
|
:param from_template: path to project YAML file that will be used as from_template (for new projects)
|
|
462
492
|
:param save: whether to save the created project in the DB
|
|
463
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
|
|
464
496
|
|
|
465
497
|
:returns: project object
|
|
466
498
|
"""
|
|
467
499
|
context = context or "./"
|
|
468
|
-
spec_path = path.join(context, subpath or "", "project.yaml")
|
|
469
|
-
load_from_path = url or path.isfile(spec_path)
|
|
470
500
|
try:
|
|
471
501
|
# load project from the DB.
|
|
472
502
|
# use `name` as `url` as we load the project from the DB
|
|
@@ -482,17 +512,26 @@ def get_or_create_project(
|
|
|
482
512
|
# only loading project from db so no need to save it
|
|
483
513
|
save=False,
|
|
484
514
|
parameters=parameters,
|
|
515
|
+
allow_cross_project=allow_cross_project,
|
|
485
516
|
)
|
|
486
|
-
logger.info("Project loaded successfully", project_name=name)
|
|
517
|
+
logger.info("Project loaded successfully", project_name=project.name)
|
|
487
518
|
return project
|
|
488
|
-
|
|
489
519
|
except mlrun.errors.MLRunNotFoundError:
|
|
490
|
-
logger.debug(
|
|
520
|
+
logger.debug(
|
|
521
|
+
"Project not found in db", project_name=name, user_project=user_project
|
|
522
|
+
)
|
|
491
523
|
|
|
524
|
+
spec_path = path.join(context, subpath or "", "project.yaml")
|
|
525
|
+
load_from_path = url or path.isfile(spec_path)
|
|
492
526
|
# do not nest under "try" or else the exceptions raised below will be logged along with the "not found" message
|
|
493
527
|
if load_from_path:
|
|
494
528
|
# loads a project from archive or local project.yaml
|
|
495
|
-
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
|
+
)
|
|
496
535
|
project = load_project(
|
|
497
536
|
context,
|
|
498
537
|
url,
|
|
@@ -504,11 +543,12 @@ def get_or_create_project(
|
|
|
504
543
|
user_project=user_project,
|
|
505
544
|
save=save,
|
|
506
545
|
parameters=parameters,
|
|
546
|
+
allow_cross_project=allow_cross_project,
|
|
507
547
|
)
|
|
508
548
|
|
|
509
549
|
logger.info(
|
|
510
550
|
"Project loaded successfully",
|
|
511
|
-
project_name=name,
|
|
551
|
+
project_name=project.name,
|
|
512
552
|
path=url or context,
|
|
513
553
|
stored_in_db=save,
|
|
514
554
|
)
|
|
@@ -526,7 +566,9 @@ def get_or_create_project(
|
|
|
526
566
|
save=save,
|
|
527
567
|
parameters=parameters,
|
|
528
568
|
)
|
|
529
|
-
logger.info(
|
|
569
|
+
logger.info(
|
|
570
|
+
"Project created successfully", project_name=project.name, stored_in_db=save
|
|
571
|
+
)
|
|
530
572
|
return project
|
|
531
573
|
|
|
532
574
|
|
|
@@ -564,6 +606,10 @@ def _run_project_setup(
|
|
|
564
606
|
if hasattr(mod, "setup"):
|
|
565
607
|
try:
|
|
566
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
|
+
)
|
|
567
613
|
except Exception as exc:
|
|
568
614
|
logger.error(
|
|
569
615
|
"Failed to run project_setup script",
|
|
@@ -574,11 +620,13 @@ def _run_project_setup(
|
|
|
574
620
|
if save:
|
|
575
621
|
project.save()
|
|
576
622
|
else:
|
|
577
|
-
logger.warn(
|
|
623
|
+
logger.warn(
|
|
624
|
+
f"skipping setup, setup() handler was not found in {path.basename(setup_file_path)}"
|
|
625
|
+
)
|
|
578
626
|
return project
|
|
579
627
|
|
|
580
628
|
|
|
581
|
-
def _load_project_dir(context, name="", subpath=""):
|
|
629
|
+
def _load_project_dir(context, name="", subpath="", allow_cross_project=None):
|
|
582
630
|
subpath_str = subpath or ""
|
|
583
631
|
|
|
584
632
|
# support both .yaml and .yml file extensions
|
|
@@ -592,7 +640,7 @@ def _load_project_dir(context, name="", subpath=""):
|
|
|
592
640
|
with open(project_file_path) as fp:
|
|
593
641
|
data = fp.read()
|
|
594
642
|
struct = yaml.load(data, Loader=yaml.FullLoader)
|
|
595
|
-
project = _project_instance_from_struct(struct, name)
|
|
643
|
+
project = _project_instance_from_struct(struct, name, allow_cross_project)
|
|
596
644
|
project.spec.context = context
|
|
597
645
|
elif function_files := glob.glob(function_file_path):
|
|
598
646
|
function_path = function_files[0]
|
|
@@ -662,22 +710,45 @@ def _load_project_from_db(url, secrets, user_project=False):
|
|
|
662
710
|
|
|
663
711
|
def _delete_project_from_db(project_name, secrets, deletion_strategy):
|
|
664
712
|
db = mlrun.db.get_run_db(secrets=secrets)
|
|
665
|
-
|
|
713
|
+
db.delete_project(project_name, deletion_strategy=deletion_strategy)
|
|
666
714
|
|
|
667
715
|
|
|
668
|
-
def _load_project_file(url, name="", secrets=None):
|
|
716
|
+
def _load_project_file(url, name="", secrets=None, allow_cross_project=None):
|
|
669
717
|
try:
|
|
670
718
|
obj = get_object(url, secrets)
|
|
671
719
|
except FileNotFoundError as exc:
|
|
672
720
|
raise FileNotFoundError(f"cant find project file at {url}") from exc
|
|
673
721
|
struct = yaml.load(obj, Loader=yaml.FullLoader)
|
|
674
|
-
return _project_instance_from_struct(struct, name)
|
|
675
|
-
|
|
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
|
+
)
|
|
676
735
|
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
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
|
|
681
752
|
return MlrunProject.from_dict(struct)
|
|
682
753
|
|
|
683
754
|
|
|
@@ -760,6 +831,7 @@ class ProjectSpec(ModelObj):
|
|
|
760
831
|
default_image=None,
|
|
761
832
|
build=None,
|
|
762
833
|
custom_packagers: list[tuple[str, bool]] = None,
|
|
834
|
+
default_function_node_selector=None,
|
|
763
835
|
):
|
|
764
836
|
self.repo = None
|
|
765
837
|
|
|
@@ -799,6 +871,7 @@ class ProjectSpec(ModelObj):
|
|
|
799
871
|
# in a tuple where the first index is the packager module's path (str) and the second is a flag (bool) for
|
|
800
872
|
# whether it is mandatory for a run (raise exception on collection error) or not.
|
|
801
873
|
self.custom_packagers = custom_packagers or []
|
|
874
|
+
self._default_function_node_selector = default_function_node_selector or None
|
|
802
875
|
|
|
803
876
|
@property
|
|
804
877
|
def source(self) -> str:
|
|
@@ -934,19 +1007,29 @@ class ProjectSpec(ModelObj):
|
|
|
934
1007
|
|
|
935
1008
|
artifacts_dict = {}
|
|
936
1009
|
for artifact in artifacts:
|
|
937
|
-
|
|
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:
|
|
938
1018
|
raise ValueError("artifacts must be a dict or class")
|
|
939
|
-
|
|
940
|
-
# Support legacy artifacts
|
|
941
|
-
if is_legacy_artifact(artifact) or _is_imported_artifact(artifact):
|
|
942
|
-
key = artifact.get("key")
|
|
943
|
-
else:
|
|
944
|
-
key = artifact.get("metadata").get("key", "")
|
|
945
|
-
if not key:
|
|
946
|
-
raise ValueError('artifacts "key" must be specified')
|
|
947
|
-
else:
|
|
1019
|
+
elif is_artifact_model:
|
|
948
1020
|
key = artifact.key
|
|
949
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')
|
|
950
1033
|
|
|
951
1034
|
artifacts_dict[key] = artifact
|
|
952
1035
|
|
|
@@ -963,6 +1046,14 @@ class ProjectSpec(ModelObj):
|
|
|
963
1046
|
if key in self._artifacts:
|
|
964
1047
|
del self._artifacts[key]
|
|
965
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
|
+
|
|
966
1057
|
@property
|
|
967
1058
|
def build(self) -> ImageBuilder:
|
|
968
1059
|
return self._build
|
|
@@ -1220,6 +1311,14 @@ class MlrunProject(ModelObj):
|
|
|
1220
1311
|
def description(self, description):
|
|
1221
1312
|
self.spec.description = description
|
|
1222
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
|
+
|
|
1223
1322
|
@property
|
|
1224
1323
|
def default_image(self) -> str:
|
|
1225
1324
|
return self.spec.default_image
|
|
@@ -1334,13 +1433,15 @@ class MlrunProject(ModelObj):
|
|
|
1334
1433
|
example::
|
|
1335
1434
|
|
|
1336
1435
|
# register a simple file artifact
|
|
1337
|
-
project.set_artifact(
|
|
1436
|
+
project.set_artifact("data", target_path=data_url)
|
|
1338
1437
|
# register a model artifact
|
|
1339
|
-
project.set_artifact(
|
|
1438
|
+
project.set_artifact(
|
|
1439
|
+
"model", ModelArtifact(model_file="model.pkl"), target_path=model_dir_url
|
|
1440
|
+
)
|
|
1340
1441
|
|
|
1341
1442
|
# register a path to artifact package (will be imported on project load)
|
|
1342
1443
|
# to generate such package use `artifact.export(target_path)`
|
|
1343
|
-
project.set_artifact(
|
|
1444
|
+
project.set_artifact("model", "https://mystuff.com/models/mymodel.zip")
|
|
1344
1445
|
|
|
1345
1446
|
:param key: artifact key/name
|
|
1346
1447
|
:param artifact: mlrun Artifact object/dict (or its subclasses) or path to artifact
|
|
@@ -1375,14 +1476,7 @@ class MlrunProject(ModelObj):
|
|
|
1375
1476
|
artifact_path = mlrun.utils.helpers.template_artifact_path(
|
|
1376
1477
|
self.spec.artifact_path or mlrun.mlconf.artifact_path, self.metadata.name
|
|
1377
1478
|
)
|
|
1378
|
-
|
|
1379
|
-
# we need to maintain the different trees that generated them
|
|
1380
|
-
producer = ArtifactProducer(
|
|
1381
|
-
"project",
|
|
1382
|
-
self.metadata.name,
|
|
1383
|
-
self.metadata.name,
|
|
1384
|
-
tag=self._get_hexsha() or str(uuid.uuid4()),
|
|
1385
|
-
)
|
|
1479
|
+
project_tag = self._get_project_tag()
|
|
1386
1480
|
for artifact_dict in self.spec.artifacts:
|
|
1387
1481
|
if _is_imported_artifact(artifact_dict):
|
|
1388
1482
|
import_from = artifact_dict["import_from"]
|
|
@@ -1402,8 +1496,23 @@ class MlrunProject(ModelObj):
|
|
|
1402
1496
|
artifact.src_path = path.join(
|
|
1403
1497
|
self.spec.get_code_path(), artifact.src_path
|
|
1404
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
|
|
1405
1510
|
artifact_manager.log_artifact(
|
|
1406
|
-
producer,
|
|
1511
|
+
producer,
|
|
1512
|
+
artifact,
|
|
1513
|
+
artifact_path=artifact_path,
|
|
1514
|
+
project=self.metadata.name,
|
|
1515
|
+
is_retained_producer=is_retained_producer,
|
|
1407
1516
|
)
|
|
1408
1517
|
|
|
1409
1518
|
def _get_artifact_manager(self):
|
|
@@ -1445,7 +1554,7 @@ class MlrunProject(ModelObj):
|
|
|
1445
1554
|
url = path.normpath(path.join(self.spec.get_code_path(), url))
|
|
1446
1555
|
|
|
1447
1556
|
if (not in_context or check_path_in_context) and not path.isfile(url):
|
|
1448
|
-
raise
|
|
1557
|
+
raise FileNotFoundError(f"{url} not found")
|
|
1449
1558
|
|
|
1450
1559
|
return url, in_context
|
|
1451
1560
|
|
|
@@ -1453,15 +1562,15 @@ class MlrunProject(ModelObj):
|
|
|
1453
1562
|
self,
|
|
1454
1563
|
item,
|
|
1455
1564
|
body=None,
|
|
1456
|
-
tag="",
|
|
1457
|
-
local_path="",
|
|
1458
|
-
artifact_path=None,
|
|
1459
|
-
format=None,
|
|
1460
|
-
upload=None,
|
|
1461
|
-
labels=None,
|
|
1462
|
-
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,
|
|
1463
1572
|
**kwargs,
|
|
1464
|
-
):
|
|
1573
|
+
) -> Artifact:
|
|
1465
1574
|
"""Log an output artifact and optionally upload it to datastore
|
|
1466
1575
|
|
|
1467
1576
|
If the artifact already exists with the same key and tag, it will be overwritten.
|
|
@@ -1486,7 +1595,9 @@ class MlrunProject(ModelObj):
|
|
|
1486
1595
|
:param format: artifact file format: csv, png, ..
|
|
1487
1596
|
:param tag: version tag
|
|
1488
1597
|
:param target_path: absolute target path (instead of using artifact_path + local_path)
|
|
1489
|
-
: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`.
|
|
1490
1601
|
:param labels: a set of key/value labels to tag the artifact with
|
|
1491
1602
|
|
|
1492
1603
|
:returns: artifact object
|
|
@@ -1498,12 +1609,20 @@ class MlrunProject(ModelObj):
|
|
|
1498
1609
|
artifact_path = mlrun.utils.helpers.template_artifact_path(
|
|
1499
1610
|
artifact_path, self.metadata.name
|
|
1500
1611
|
)
|
|
1501
|
-
producer =
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
self.
|
|
1505
|
-
|
|
1506
|
-
|
|
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
|
|
1507
1626
|
item = am.log_artifact(
|
|
1508
1627
|
producer,
|
|
1509
1628
|
item,
|
|
@@ -1515,10 +1634,29 @@ class MlrunProject(ModelObj):
|
|
|
1515
1634
|
upload=upload,
|
|
1516
1635
|
labels=labels,
|
|
1517
1636
|
target_path=target_path,
|
|
1637
|
+
project=self.metadata.name,
|
|
1638
|
+
is_retained_producer=is_retained_producer,
|
|
1518
1639
|
**kwargs,
|
|
1519
1640
|
)
|
|
1520
1641
|
return item
|
|
1521
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
|
+
|
|
1522
1660
|
def log_dataset(
|
|
1523
1661
|
self,
|
|
1524
1662
|
key,
|
|
@@ -1533,7 +1671,7 @@ class MlrunProject(ModelObj):
|
|
|
1533
1671
|
stats=None,
|
|
1534
1672
|
target_path="",
|
|
1535
1673
|
extra_data=None,
|
|
1536
|
-
label_column: str = None,
|
|
1674
|
+
label_column: Optional[str] = None,
|
|
1537
1675
|
**kwargs,
|
|
1538
1676
|
) -> DatasetArtifact:
|
|
1539
1677
|
"""
|
|
@@ -1549,7 +1687,9 @@ class MlrunProject(ModelObj):
|
|
|
1549
1687
|
"age": [42, 52, 36, 24, 73],
|
|
1550
1688
|
"testScore": [25, 94, 57, 62, 70],
|
|
1551
1689
|
}
|
|
1552
|
-
df = pd.DataFrame(
|
|
1690
|
+
df = pd.DataFrame(
|
|
1691
|
+
raw_data, columns=["first_name", "last_name", "age", "testScore"]
|
|
1692
|
+
)
|
|
1553
1693
|
project.log_dataset("mydf", df=df, stats=True)
|
|
1554
1694
|
|
|
1555
1695
|
:param key: artifact key
|
|
@@ -1608,28 +1748,31 @@ class MlrunProject(ModelObj):
|
|
|
1608
1748
|
artifact_path=None,
|
|
1609
1749
|
upload=None,
|
|
1610
1750
|
labels=None,
|
|
1611
|
-
inputs: list[Feature] = None,
|
|
1612
|
-
outputs: list[Feature] = None,
|
|
1613
|
-
feature_vector: str = None,
|
|
1614
|
-
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,
|
|
1615
1755
|
training_set=None,
|
|
1616
1756
|
label_column=None,
|
|
1617
1757
|
extra_data=None,
|
|
1618
1758
|
**kwargs,
|
|
1619
|
-
):
|
|
1759
|
+
) -> ModelArtifact:
|
|
1620
1760
|
"""Log a model artifact and optionally upload it to datastore
|
|
1621
1761
|
|
|
1622
1762
|
If the model already exists with the same key and tag, it will be overwritten.
|
|
1623
1763
|
|
|
1624
1764
|
example::
|
|
1625
1765
|
|
|
1626
|
-
project.log_model(
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
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
|
+
)
|
|
1633
1776
|
|
|
1634
1777
|
:param key: artifact key or artifact class ()
|
|
1635
1778
|
:param body: will use the body as the artifact content
|
|
@@ -1739,14 +1882,16 @@ class MlrunProject(ModelObj):
|
|
|
1739
1882
|
artifact = get_artifact(spec)
|
|
1740
1883
|
with open(f"{temp_dir}/_body", "rb") as fp:
|
|
1741
1884
|
artifact.spec._body = fp.read()
|
|
1742
|
-
artifact.target_path = ""
|
|
1743
1885
|
|
|
1744
1886
|
# if the dataitem is not a file, it means we downloaded it from a remote source to a temp file,
|
|
1745
1887
|
# so we need to remove it after we're done with it
|
|
1746
1888
|
dataitem.remove_local()
|
|
1747
1889
|
|
|
1748
1890
|
return self.log_artifact(
|
|
1749
|
-
artifact,
|
|
1891
|
+
artifact,
|
|
1892
|
+
local_path=temp_dir,
|
|
1893
|
+
artifact_path=artifact_path,
|
|
1894
|
+
upload=True,
|
|
1750
1895
|
)
|
|
1751
1896
|
|
|
1752
1897
|
else:
|
|
@@ -1764,10 +1909,18 @@ class MlrunProject(ModelObj):
|
|
|
1764
1909
|
"""
|
|
1765
1910
|
context = context or self.spec.context
|
|
1766
1911
|
if context:
|
|
1767
|
-
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
|
+
)
|
|
1768
1918
|
else:
|
|
1769
1919
|
project = _load_project_file(
|
|
1770
|
-
self.spec.origin_url,
|
|
1920
|
+
self.spec.origin_url,
|
|
1921
|
+
self.metadata.name,
|
|
1922
|
+
self._secrets,
|
|
1923
|
+
allow_cross_project=None,
|
|
1771
1924
|
)
|
|
1772
1925
|
project.spec.source = self.spec.source
|
|
1773
1926
|
project.spec.repo = self.spec.repo
|
|
@@ -1796,7 +1949,10 @@ class MlrunProject(ModelObj):
|
|
|
1796
1949
|
def set_model_monitoring_function(
|
|
1797
1950
|
self,
|
|
1798
1951
|
func: typing.Union[str, mlrun.runtimes.BaseRuntime, None] = None,
|
|
1799
|
-
application_class: typing.Union[
|
|
1952
|
+
application_class: typing.Union[
|
|
1953
|
+
str,
|
|
1954
|
+
mm_app.ModelMonitoringApplicationBase,
|
|
1955
|
+
] = None,
|
|
1800
1956
|
name: str = None,
|
|
1801
1957
|
image: str = None,
|
|
1802
1958
|
handler=None,
|
|
@@ -1812,6 +1968,7 @@ class MlrunProject(ModelObj):
|
|
|
1812
1968
|
call `fn.deploy()` where `fn` is the object returned by this method.
|
|
1813
1969
|
|
|
1814
1970
|
examples::
|
|
1971
|
+
|
|
1815
1972
|
project.set_model_monitoring_function(
|
|
1816
1973
|
name="myApp", application_class="MyApp", image="mlrun/mlrun"
|
|
1817
1974
|
)
|
|
@@ -1834,11 +1991,6 @@ class MlrunProject(ModelObj):
|
|
|
1834
1991
|
monitoring application's constructor.
|
|
1835
1992
|
"""
|
|
1836
1993
|
|
|
1837
|
-
if name in mm_constants.MonitoringFunctionNames.all():
|
|
1838
|
-
raise mlrun.errors.MLRunInvalidArgumentError(
|
|
1839
|
-
f"Application name can not be on of the following name : "
|
|
1840
|
-
f"{mm_constants.MonitoringFunctionNames.all()}"
|
|
1841
|
-
)
|
|
1842
1994
|
function_object: RemoteRuntime = None
|
|
1843
1995
|
(
|
|
1844
1996
|
resolved_function_name,
|
|
@@ -1856,16 +2008,6 @@ class MlrunProject(ModelObj):
|
|
|
1856
2008
|
requirements_file,
|
|
1857
2009
|
**application_kwargs,
|
|
1858
2010
|
)
|
|
1859
|
-
models_names = "all"
|
|
1860
|
-
function_object.set_label(
|
|
1861
|
-
mm_constants.ModelMonitoringAppLabel.KEY,
|
|
1862
|
-
mm_constants.ModelMonitoringAppLabel.VAL,
|
|
1863
|
-
)
|
|
1864
|
-
function_object.set_label("models", models_names)
|
|
1865
|
-
|
|
1866
|
-
if not mlrun.mlconf.is_ce_mode():
|
|
1867
|
-
function_object.apply(mlrun.mount_v3io())
|
|
1868
|
-
|
|
1869
2011
|
# save to project spec
|
|
1870
2012
|
self.spec.set_function(resolved_function_name, function_object, func)
|
|
1871
2013
|
|
|
@@ -1874,7 +2016,10 @@ class MlrunProject(ModelObj):
|
|
|
1874
2016
|
def create_model_monitoring_function(
|
|
1875
2017
|
self,
|
|
1876
2018
|
func: str = None,
|
|
1877
|
-
application_class: typing.Union[
|
|
2019
|
+
application_class: typing.Union[
|
|
2020
|
+
str,
|
|
2021
|
+
mm_app.ModelMonitoringApplicationBase,
|
|
2022
|
+
] = None,
|
|
1878
2023
|
name: str = None,
|
|
1879
2024
|
image: str = None,
|
|
1880
2025
|
handler: str = None,
|
|
@@ -1888,8 +2033,10 @@ class MlrunProject(ModelObj):
|
|
|
1888
2033
|
Create a monitoring function object without setting it to the project
|
|
1889
2034
|
|
|
1890
2035
|
examples::
|
|
1891
|
-
|
|
1892
|
-
|
|
2036
|
+
|
|
2037
|
+
project.create_model_monitoring_function(
|
|
2038
|
+
application_class_name="MyApp", image="mlrun/mlrun", name="myApp"
|
|
2039
|
+
)
|
|
1893
2040
|
|
|
1894
2041
|
:param func: Code url, None refers to current Notebook
|
|
1895
2042
|
:param name: Name of the function, can be specified with a tag to support
|
|
@@ -1908,6 +2055,7 @@ class MlrunProject(ModelObj):
|
|
|
1908
2055
|
:param application_kwargs: Additional keyword arguments to be passed to the
|
|
1909
2056
|
monitoring application's constructor.
|
|
1910
2057
|
"""
|
|
2058
|
+
|
|
1911
2059
|
_, function_object, _ = self._instantiate_model_monitoring_function(
|
|
1912
2060
|
func,
|
|
1913
2061
|
application_class,
|
|
@@ -1924,49 +2072,40 @@ class MlrunProject(ModelObj):
|
|
|
1924
2072
|
|
|
1925
2073
|
def _instantiate_model_monitoring_function(
|
|
1926
2074
|
self,
|
|
1927
|
-
func: typing.Union[str, mlrun.runtimes.BaseRuntime] = None,
|
|
1928
|
-
application_class: typing.Union[
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
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,
|
|
1935
2087
|
requirements_file: str = "",
|
|
1936
2088
|
**application_kwargs,
|
|
1937
2089
|
) -> tuple[str, mlrun.runtimes.BaseRuntime, dict]:
|
|
2090
|
+
import mlrun.model_monitoring.api
|
|
2091
|
+
|
|
1938
2092
|
function_object: RemoteRuntime = None
|
|
1939
2093
|
kind = None
|
|
1940
2094
|
if (isinstance(func, str) or func is None) and application_class is not None:
|
|
1941
|
-
kind =
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
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,
|
|
1946
2100
|
name=name,
|
|
1947
|
-
project=self.metadata.name,
|
|
1948
|
-
tag=tag,
|
|
1949
|
-
kind=kind,
|
|
1950
2101
|
image=image,
|
|
2102
|
+
tag=tag,
|
|
1951
2103
|
requirements=requirements,
|
|
1952
2104
|
requirements_file=requirements_file,
|
|
2105
|
+
**application_kwargs,
|
|
1953
2106
|
)
|
|
1954
|
-
graph = func.set_topology("flow")
|
|
1955
|
-
if isinstance(application_class, str):
|
|
1956
|
-
first_step = graph.to(
|
|
1957
|
-
class_name=application_class, **application_kwargs
|
|
1958
|
-
)
|
|
1959
|
-
else:
|
|
1960
|
-
first_step = graph.to(class_name=application_class)
|
|
1961
|
-
first_step.to(
|
|
1962
|
-
class_name=PushToMonitoringWriter(
|
|
1963
|
-
project=self.metadata.name,
|
|
1964
|
-
writer_application_name=mm_constants.MonitoringFunctionNames.WRITER,
|
|
1965
|
-
stream_uri=None,
|
|
1966
|
-
),
|
|
1967
|
-
).respond()
|
|
1968
2107
|
elif isinstance(func, str) and isinstance(handler, str):
|
|
1969
|
-
kind =
|
|
2108
|
+
kind = mlrun.run.RuntimeKinds.nuclio
|
|
1970
2109
|
|
|
1971
2110
|
(
|
|
1972
2111
|
resolved_function_name,
|
|
@@ -1984,24 +2123,36 @@ class MlrunProject(ModelObj):
|
|
|
1984
2123
|
requirements,
|
|
1985
2124
|
requirements_file,
|
|
1986
2125
|
)
|
|
1987
|
-
models_names = "all"
|
|
1988
2126
|
function_object.set_label(
|
|
1989
2127
|
mm_constants.ModelMonitoringAppLabel.KEY,
|
|
1990
2128
|
mm_constants.ModelMonitoringAppLabel.VAL,
|
|
1991
2129
|
)
|
|
1992
|
-
function_object.set_label("models", models_names)
|
|
1993
2130
|
|
|
1994
2131
|
if not mlrun.mlconf.is_ce_mode():
|
|
1995
2132
|
function_object.apply(mlrun.mount_v3io())
|
|
1996
2133
|
|
|
1997
2134
|
return resolved_function_name, function_object, func
|
|
1998
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
|
+
|
|
1999
2146
|
def enable_model_monitoring(
|
|
2000
2147
|
self,
|
|
2001
2148
|
default_controller_image: str = "mlrun/mlrun",
|
|
2002
2149
|
base_period: int = 10,
|
|
2003
2150
|
image: str = "mlrun/mlrun",
|
|
2151
|
+
*,
|
|
2004
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,
|
|
2005
2156
|
) -> None:
|
|
2006
2157
|
"""
|
|
2007
2158
|
Deploy model monitoring application controller, writer and stream functions.
|
|
@@ -2011,16 +2162,19 @@ class MlrunProject(ModelObj):
|
|
|
2011
2162
|
The stream function goal is to monitor the log of the data stream. It is triggered when a new log entry
|
|
2012
2163
|
is detected. It processes the new events into statistics that are then written to statistics databases.
|
|
2013
2164
|
|
|
2014
|
-
|
|
2015
|
-
:param
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
:param image:
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
:param deploy_histogram_data_drift_app:
|
|
2022
|
-
|
|
2023
|
-
|
|
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.
|
|
2024
2178
|
"""
|
|
2025
2179
|
if default_controller_image != "mlrun/mlrun":
|
|
2026
2180
|
# TODO: Remove this in 1.9.0
|
|
@@ -2030,39 +2184,71 @@ class MlrunProject(ModelObj):
|
|
|
2030
2184
|
FutureWarning,
|
|
2031
2185
|
)
|
|
2032
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
|
+
|
|
2033
2193
|
db = mlrun.db.get_run_db(secrets=self._secrets)
|
|
2034
2194
|
db.enable_model_monitoring(
|
|
2035
2195
|
project=self.name,
|
|
2036
2196
|
image=image,
|
|
2037
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,
|
|
2038
2201
|
)
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
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]
|
|
2048
2233
|
)
|
|
2049
|
-
fn.deploy()
|
|
2050
2234
|
|
|
2051
2235
|
def update_model_monitoring_controller(
|
|
2052
2236
|
self,
|
|
2053
2237
|
base_period: int = 10,
|
|
2054
2238
|
image: str = "mlrun/mlrun",
|
|
2239
|
+
*,
|
|
2240
|
+
wait_for_deployment: bool = False,
|
|
2055
2241
|
) -> None:
|
|
2056
2242
|
"""
|
|
2057
2243
|
Redeploy model monitoring application controller functions.
|
|
2058
2244
|
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
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.
|
|
2066
2252
|
"""
|
|
2067
2253
|
db = mlrun.db.get_run_db(secrets=self._secrets)
|
|
2068
2254
|
db.update_model_monitoring_controller(
|
|
@@ -2071,26 +2257,78 @@ class MlrunProject(ModelObj):
|
|
|
2071
2257
|
image=image,
|
|
2072
2258
|
)
|
|
2073
2259
|
|
|
2074
|
-
|
|
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
|
+
|
|
2075
2299
|
db = mlrun.db.get_run_db(secrets=self._secrets)
|
|
2076
|
-
db.
|
|
2077
|
-
project=self.name,
|
|
2078
|
-
name=mm_constants.MonitoringFunctionNames.APPLICATION_CONTROLLER,
|
|
2079
|
-
)
|
|
2080
|
-
db.delete_function(
|
|
2081
|
-
project=self.name,
|
|
2082
|
-
name=mm_constants.MonitoringFunctionNames.WRITER,
|
|
2083
|
-
)
|
|
2084
|
-
db.delete_function(
|
|
2300
|
+
succeed = db.disable_model_monitoring(
|
|
2085
2301
|
project=self.name,
|
|
2086
|
-
|
|
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,
|
|
2087
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
|
+
)
|
|
2088
2326
|
|
|
2089
2327
|
def set_function(
|
|
2090
2328
|
self,
|
|
2091
2329
|
func: typing.Union[str, mlrun.runtimes.BaseRuntime] = None,
|
|
2092
2330
|
name: str = "",
|
|
2093
|
-
kind: str = "",
|
|
2331
|
+
kind: str = "job",
|
|
2094
2332
|
image: str = None,
|
|
2095
2333
|
handler: str = None,
|
|
2096
2334
|
with_repo: bool = None,
|
|
@@ -2098,31 +2336,52 @@ class MlrunProject(ModelObj):
|
|
|
2098
2336
|
requirements: typing.Union[str, list[str]] = None,
|
|
2099
2337
|
requirements_file: str = "",
|
|
2100
2338
|
) -> mlrun.runtimes.BaseRuntime:
|
|
2101
|
-
"""
|
|
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.
|
|
2102
2342
|
|
|
2103
|
-
function
|
|
2104
|
-
|
|
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>)``.
|
|
2105
2349
|
|
|
2106
|
-
|
|
2107
|
-
MLRun DB e.g. db://project/func:ver
|
|
2108
|
-
functions hub/market: e.g. hub://auto-trainer:master
|
|
2350
|
+
Support URL prefixes:
|
|
2109
2351
|
|
|
2110
|
-
|
|
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::
|
|
2111
2357
|
|
|
2112
2358
|
proj.set_function(func_object)
|
|
2113
|
-
proj.set_function(
|
|
2114
|
-
|
|
2115
|
-
proj.set_function(
|
|
2116
|
-
|
|
2117
|
-
|
|
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")
|
|
2365
|
+
|
|
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")
|
|
2118
2377
|
|
|
2119
|
-
#
|
|
2378
|
+
# Set function requirements
|
|
2120
2379
|
|
|
2121
|
-
#
|
|
2122
|
-
proj.set_function(
|
|
2380
|
+
# By providing a list of packages
|
|
2381
|
+
proj.set_function("my.py", requirements=["requests", "pandas"])
|
|
2123
2382
|
|
|
2124
|
-
#
|
|
2125
|
-
proj.set_function(
|
|
2383
|
+
# By providing a path to a pip requirements file
|
|
2384
|
+
proj.set_function("my.py", requirements="requirements.txt")
|
|
2126
2385
|
|
|
2127
2386
|
:param func: Function object or spec/code url, None refers to current Notebook
|
|
2128
2387
|
:param name: Name of the function (under the project), can be specified with a tag to support
|
|
@@ -2133,14 +2392,15 @@ class MlrunProject(ModelObj):
|
|
|
2133
2392
|
Default: job
|
|
2134
2393
|
:param image: Docker image to be used, can also be specified in the function object/yaml
|
|
2135
2394
|
:param handler: Default function handler to invoke (can only be set with .py/.ipynb files)
|
|
2136
|
-
: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).
|
|
2137
2397
|
:param tag: Function version tag to set (none for current or 'latest')
|
|
2138
2398
|
Specifying a tag as a parameter will update the project's tagged function
|
|
2139
2399
|
(myfunc:v1) and the untagged function (myfunc)
|
|
2140
2400
|
:param requirements: A list of python packages
|
|
2141
2401
|
:param requirements_file: Path to a python requirements file
|
|
2142
2402
|
|
|
2143
|
-
:returns:
|
|
2403
|
+
:returns: :py:class:`~mlrun.runtimes.BaseRuntime`
|
|
2144
2404
|
"""
|
|
2145
2405
|
(
|
|
2146
2406
|
resolved_function_name,
|
|
@@ -2174,19 +2434,20 @@ class MlrunProject(ModelObj):
|
|
|
2174
2434
|
requirements: typing.Union[str, list[str]] = None,
|
|
2175
2435
|
requirements_file: str = "",
|
|
2176
2436
|
) -> tuple[str, str, mlrun.runtimes.BaseRuntime, dict]:
|
|
2177
|
-
if
|
|
2437
|
+
if (
|
|
2438
|
+
func is None
|
|
2439
|
+
and not _has_module(handler, kind)
|
|
2440
|
+
and mlrun.runtimes.RuntimeKinds.supports_from_notebook(kind)
|
|
2441
|
+
):
|
|
2178
2442
|
# if function path is not provided and it is not a module (no ".")
|
|
2179
2443
|
# use the current notebook as default
|
|
2180
|
-
if
|
|
2181
|
-
|
|
2182
|
-
"Function path or module must be specified (when not running inside a Notebook)"
|
|
2183
|
-
)
|
|
2184
|
-
from IPython import get_ipython
|
|
2444
|
+
if is_jupyter:
|
|
2445
|
+
from IPython import get_ipython
|
|
2185
2446
|
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
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)
|
|
2190
2451
|
|
|
2191
2452
|
func = func or ""
|
|
2192
2453
|
|
|
@@ -2282,22 +2543,39 @@ class MlrunProject(ModelObj):
|
|
|
2282
2543
|
"""
|
|
2283
2544
|
self.spec.remove_function(name)
|
|
2284
2545
|
|
|
2285
|
-
def remove_model_monitoring_function(self, name):
|
|
2286
|
-
"""
|
|
2546
|
+
def remove_model_monitoring_function(self, name: Union[str, list[str]]):
|
|
2547
|
+
"""delete the specified model-monitoring-app function/s
|
|
2287
2548
|
|
|
2288
|
-
:param name: name of the model-monitoring-
|
|
2549
|
+
:param name: name of the model-monitoring-function/s (under the project)
|
|
2289
2550
|
"""
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
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
|
+
)
|
|
2298
2575
|
else:
|
|
2299
|
-
|
|
2300
|
-
|
|
2576
|
+
logger.info(
|
|
2577
|
+
"Some of the desired monitoring functions were not deleted",
|
|
2578
|
+
project=self.name,
|
|
2301
2579
|
)
|
|
2302
2580
|
|
|
2303
2581
|
def get_function(
|
|
@@ -2403,13 +2681,47 @@ class MlrunProject(ModelObj):
|
|
|
2403
2681
|
clone_zip(url, self.spec.context, self._secrets)
|
|
2404
2682
|
|
|
2405
2683
|
def create_remote(self, url, name="origin", branch=None):
|
|
2406
|
-
"""
|
|
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.
|
|
2407
2690
|
|
|
2408
2691
|
:param url: remote git url
|
|
2409
2692
|
:param name: name for the remote (default is 'origin')
|
|
2410
2693
|
:param branch: Git branch to use as source
|
|
2411
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
|
+
"""
|
|
2412
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
|
+
)
|
|
2413
2725
|
self.spec.repo.create_remote(name, url=url)
|
|
2414
2726
|
url = url.replace("https://", "git://")
|
|
2415
2727
|
if not branch:
|
|
@@ -2422,6 +2734,22 @@ class MlrunProject(ModelObj):
|
|
|
2422
2734
|
self.spec._source = self.spec.source or url
|
|
2423
2735
|
self.spec.origin_url = self.spec.origin_url or url
|
|
2424
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
|
+
|
|
2425
2753
|
def _ensure_git_repo(self):
|
|
2426
2754
|
if self.spec.repo:
|
|
2427
2755
|
return
|
|
@@ -2504,47 +2832,104 @@ class MlrunProject(ModelObj):
|
|
|
2504
2832
|
secrets=secrets or {},
|
|
2505
2833
|
)
|
|
2506
2834
|
|
|
2507
|
-
def sync_functions(
|
|
2508
|
-
|
|
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
|
+
"""
|
|
2509
2864
|
if self._initialized and not always:
|
|
2510
2865
|
return self.spec._function_objects
|
|
2511
2866
|
|
|
2512
|
-
|
|
2867
|
+
functions = self.spec._function_objects
|
|
2513
2868
|
if not names:
|
|
2514
2869
|
names = self.spec._function_definitions.keys()
|
|
2515
|
-
|
|
2870
|
+
functions = {}
|
|
2871
|
+
|
|
2516
2872
|
origin = mlrun.runtimes.utils.add_code_metadata(self.spec.context)
|
|
2517
2873
|
for name in names:
|
|
2518
|
-
|
|
2519
|
-
if not
|
|
2520
|
-
|
|
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
|
+
)
|
|
2521
2888
|
# If this function is already available locally, don't recreate it unless always=True
|
|
2522
|
-
if
|
|
2523
|
-
|
|
2524
|
-
self.spec._function_objects.get(name, None),
|
|
2525
|
-
mlrun.runtimes.base.BaseRuntime,
|
|
2526
|
-
)
|
|
2527
|
-
and not always
|
|
2528
|
-
):
|
|
2529
|
-
funcs[name] = self.spec._function_objects[name]
|
|
2889
|
+
if is_base_runtime and not always:
|
|
2890
|
+
functions[name] = function_object
|
|
2530
2891
|
continue
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
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):
|
|
2536
2897
|
try:
|
|
2537
|
-
name, func = _init_function_from_dict(
|
|
2898
|
+
name, func = _init_function_from_dict(
|
|
2899
|
+
function_definition, self, name
|
|
2900
|
+
)
|
|
2538
2901
|
except FileNotFoundError as exc:
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
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
|
+
|
|
2542
2927
|
func.spec.build.code_origin = origin
|
|
2543
|
-
|
|
2928
|
+
functions[name] = func
|
|
2544
2929
|
if save:
|
|
2545
2930
|
func.save(versioned=False)
|
|
2546
2931
|
|
|
2547
|
-
self.spec._function_objects =
|
|
2932
|
+
self.spec._function_objects = functions
|
|
2548
2933
|
self._initialized = True
|
|
2549
2934
|
return self.spec._function_objects
|
|
2550
2935
|
|
|
@@ -2553,9 +2938,9 @@ class MlrunProject(ModelObj):
|
|
|
2553
2938
|
|
|
2554
2939
|
read secrets from a source provider to be used in workflows, example::
|
|
2555
2940
|
|
|
2556
|
-
proj.with_secrets(
|
|
2557
|
-
proj.with_secrets(
|
|
2558
|
-
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_")
|
|
2559
2944
|
|
|
2560
2945
|
Vault secret source has several options::
|
|
2561
2946
|
|
|
@@ -2566,7 +2951,7 @@ class MlrunProject(ModelObj):
|
|
|
2566
2951
|
The 2nd option uses the current project name as context.
|
|
2567
2952
|
Can also use empty secret list::
|
|
2568
2953
|
|
|
2569
|
-
proj.with_secrets(
|
|
2954
|
+
proj.with_secrets("vault", [])
|
|
2570
2955
|
|
|
2571
2956
|
This will enable access to all secrets in vault registered to the current project.
|
|
2572
2957
|
|
|
@@ -2597,17 +2982,20 @@ class MlrunProject(ModelObj):
|
|
|
2597
2982
|
file_path: str = None,
|
|
2598
2983
|
provider: typing.Union[str, mlrun.common.schemas.SecretProviderName] = None,
|
|
2599
2984
|
):
|
|
2600
|
-
"""
|
|
2985
|
+
"""
|
|
2986
|
+
Set project secrets from dict or secrets env file
|
|
2601
2987
|
when using a secrets file it should have lines in the form KEY=VALUE, comment line start with "#"
|
|
2602
2988
|
V3IO paths/credentials and MLrun service API address are dropped from the secrets
|
|
2603
2989
|
|
|
2604
|
-
example secrets file
|
|
2990
|
+
example secrets file:
|
|
2991
|
+
|
|
2992
|
+
.. code-block:: shell
|
|
2605
2993
|
|
|
2606
2994
|
# this is an env file
|
|
2607
|
-
AWS_ACCESS_KEY_ID
|
|
2995
|
+
AWS_ACCESS_KEY_ID=XXXX
|
|
2608
2996
|
AWS_SECRET_ACCESS_KEY=YYYY
|
|
2609
2997
|
|
|
2610
|
-
usage
|
|
2998
|
+
usage:
|
|
2611
2999
|
|
|
2612
3000
|
# read env vars from dict or file and set as project secrets
|
|
2613
3001
|
project.set_secrets({"SECRET1": "value"})
|
|
@@ -2686,41 +3074,50 @@ class MlrunProject(ModelObj):
|
|
|
2686
3074
|
source: str = None,
|
|
2687
3075
|
cleanup_ttl: int = None,
|
|
2688
3076
|
notifications: list[mlrun.model.Notification] = None,
|
|
3077
|
+
workflow_runner_node_selector: typing.Optional[dict[str, str]] = None,
|
|
2689
3078
|
) -> _PipelineRunStatus:
|
|
2690
|
-
"""
|
|
2691
|
-
|
|
2692
|
-
:param name:
|
|
2693
|
-
:param workflow_path:
|
|
2694
|
-
|
|
2695
|
-
:param
|
|
2696
|
-
|
|
2697
|
-
:param
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
:param
|
|
2701
|
-
|
|
2702
|
-
:param
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
:param
|
|
2706
|
-
:param engine: workflow engine running the workflow.
|
|
2707
|
-
supported values are 'kfp' (default), 'local' or 'remote'.
|
|
2708
|
-
for setting engine for remote running use 'remote:local' or 'remote:kfp'.
|
|
2709
|
-
: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())
|
|
2710
3095
|
:param schedule: ScheduleCronTrigger class instance or a standard crontab expression string
|
|
2711
3096
|
(which will be converted to the class using its `from_crontab` constructor),
|
|
2712
3097
|
see this link for help:
|
|
2713
3098
|
https://apscheduler.readthedocs.io/en/3.x/modules/triggers/cron.html#module-apscheduler.triggers.cron
|
|
2714
|
-
|
|
2715
|
-
:param timeout:
|
|
2716
|
-
:param source:
|
|
2717
|
-
|
|
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
|
+
|
|
2718
3110
|
:param cleanup_ttl:
|
|
2719
|
-
|
|
3111
|
+
Pipeline cleanup ttl in secs (time to wait after workflow completion, at which point the
|
|
2720
3112
|
workflow and all its resources are deleted)
|
|
2721
3113
|
:param notifications:
|
|
2722
|
-
|
|
2723
|
-
:
|
|
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
|
|
2724
3121
|
"""
|
|
2725
3122
|
|
|
2726
3123
|
arguments = arguments or {}
|
|
@@ -2737,12 +3134,14 @@ class MlrunProject(ModelObj):
|
|
|
2737
3134
|
"Remote repo is not defined, use .create_remote() + push()"
|
|
2738
3135
|
)
|
|
2739
3136
|
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
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
|
+
)
|
|
2746
3145
|
|
|
2747
3146
|
if not name and not workflow_path and not workflow_handler:
|
|
2748
3147
|
raise ValueError("Workflow name, path, or handler must be specified")
|
|
@@ -2776,9 +3175,23 @@ class MlrunProject(ModelObj):
|
|
|
2776
3175
|
engine = "remote"
|
|
2777
3176
|
# The default engine is kfp if not given:
|
|
2778
3177
|
workflow_engine = get_workflow_engine(engine or workflow_spec.engine, local)
|
|
2779
|
-
if not inner_engine and engine == "remote":
|
|
2780
|
-
|
|
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
|
|
2781
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
|
+
)
|
|
2782
3195
|
|
|
2783
3196
|
run = workflow_engine.run(
|
|
2784
3197
|
self,
|
|
@@ -2792,7 +3205,7 @@ class MlrunProject(ModelObj):
|
|
|
2792
3205
|
notifications=notifications,
|
|
2793
3206
|
)
|
|
2794
3207
|
# run is None when scheduling
|
|
2795
|
-
if run and run.state ==
|
|
3208
|
+
if run and run.state == mlrun_pipelines.common.models.RunStatuses.failed:
|
|
2796
3209
|
return run
|
|
2797
3210
|
if not workflow_spec.schedule:
|
|
2798
3211
|
# Failure and schedule messages already logged
|
|
@@ -2801,14 +3214,17 @@ class MlrunProject(ModelObj):
|
|
|
2801
3214
|
)
|
|
2802
3215
|
workflow_spec.clear_tmp()
|
|
2803
3216
|
if (timeout or watch) and not workflow_spec.schedule:
|
|
3217
|
+
run_status_kwargs = {}
|
|
2804
3218
|
status_engine = run._engine
|
|
2805
3219
|
# run's engine gets replaced with inner engine if engine is remote,
|
|
2806
3220
|
# so in that case we need to get the status from the remote engine manually
|
|
2807
|
-
|
|
2808
|
-
if engine == "remote" and status_engine.engine != "local":
|
|
3221
|
+
if workflow_engine.engine == "remote":
|
|
2809
3222
|
status_engine = _RemoteRunner
|
|
3223
|
+
run_status_kwargs["inner_engine"] = run._engine
|
|
2810
3224
|
|
|
2811
|
-
status_engine.get_run_status(
|
|
3225
|
+
status_engine.get_run_status(
|
|
3226
|
+
project=self, run=run, timeout=timeout, **run_status_kwargs
|
|
3227
|
+
)
|
|
2812
3228
|
return run
|
|
2813
3229
|
|
|
2814
3230
|
def save_workflow(self, name, target, artifact_path=None, ttl=None):
|
|
@@ -2908,43 +3324,66 @@ class MlrunProject(ModelObj):
|
|
|
2908
3324
|
|
|
2909
3325
|
def set_model_monitoring_credentials(
|
|
2910
3326
|
self,
|
|
2911
|
-
access_key: str = None,
|
|
2912
|
-
endpoint_store_connection: str = None,
|
|
2913
|
-
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,
|
|
2914
3332
|
):
|
|
2915
|
-
"""Set the credentials that will be used by the project's model monitoring
|
|
2916
|
-
infrastructure functions.
|
|
2917
|
-
|
|
2918
|
-
:param access_key: Model Monitoring access key for managing user permissions
|
|
2919
|
-
:param access_key: Model Monitoring access key for managing user permissions
|
|
2920
|
-
:param endpoint_store_connection: Endpoint store connection string
|
|
2921
|
-
:param stream_path: Path to the model monitoring stream
|
|
2922
3333
|
"""
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
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,
|
|
2947
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
|
+
)
|
|
2948
3387
|
|
|
2949
3388
|
def run_function(
|
|
2950
3389
|
self,
|
|
@@ -2969,7 +3408,8 @@ class MlrunProject(ModelObj):
|
|
|
2969
3408
|
notifications: list[mlrun.model.Notification] = None,
|
|
2970
3409
|
returns: Optional[list[Union[str, dict[str, str]]]] = None,
|
|
2971
3410
|
builder_env: Optional[dict] = None,
|
|
2972
|
-
|
|
3411
|
+
reset_on_run: bool = None,
|
|
3412
|
+
) -> typing.Union[mlrun.model.RunObject, PipelineNodeWrapper]:
|
|
2973
3413
|
"""Run a local or remote task as part of a local/kubeflow pipeline
|
|
2974
3414
|
|
|
2975
3415
|
example (use with project)::
|
|
@@ -2981,8 +3421,11 @@ class MlrunProject(ModelObj):
|
|
|
2981
3421
|
|
|
2982
3422
|
# run functions (refer to them by name)
|
|
2983
3423
|
run1 = project.run_function("myfunc", params={"x": 7})
|
|
2984
|
-
run2 = project.run_function(
|
|
2985
|
-
|
|
3424
|
+
run2 = project.run_function(
|
|
3425
|
+
"train",
|
|
3426
|
+
params={"label_columns": LABELS},
|
|
3427
|
+
inputs={"dataset": run1.outputs["data"]},
|
|
3428
|
+
)
|
|
2986
3429
|
|
|
2987
3430
|
:param function: name of the function (in the project) or function object
|
|
2988
3431
|
:param handler: name of the function handler
|
|
@@ -3021,8 +3464,13 @@ class MlrunProject(ModelObj):
|
|
|
3021
3464
|
* A dictionary of configurations to use when logging. Further info per object type and
|
|
3022
3465
|
artifact type can be given there. The artifact key must appear in the dictionary as
|
|
3023
3466
|
"key": "the_key".
|
|
3024
|
-
:param builder_env:
|
|
3025
|
-
|
|
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
|
|
3026
3474
|
"""
|
|
3027
3475
|
return run_function(
|
|
3028
3476
|
function,
|
|
@@ -3047,6 +3495,7 @@ class MlrunProject(ModelObj):
|
|
|
3047
3495
|
notifications=notifications,
|
|
3048
3496
|
returns=returns,
|
|
3049
3497
|
builder_env=builder_env,
|
|
3498
|
+
reset_on_run=reset_on_run,
|
|
3050
3499
|
)
|
|
3051
3500
|
|
|
3052
3501
|
def build_function(
|
|
@@ -3065,7 +3514,7 @@ class MlrunProject(ModelObj):
|
|
|
3065
3514
|
requirements_file: str = None,
|
|
3066
3515
|
extra_args: str = None,
|
|
3067
3516
|
force_build: bool = False,
|
|
3068
|
-
) -> typing.Union[BuildStatus,
|
|
3517
|
+
) -> typing.Union[BuildStatus, PipelineNodeWrapper]:
|
|
3069
3518
|
"""deploy ML function, build container with its dependencies
|
|
3070
3519
|
|
|
3071
3520
|
:param function: name of the function (in the project) or function object
|
|
@@ -3119,6 +3568,7 @@ class MlrunProject(ModelObj):
|
|
|
3119
3568
|
requirements_file: str = None,
|
|
3120
3569
|
builder_env: dict = None,
|
|
3121
3570
|
extra_args: str = None,
|
|
3571
|
+
source_code_target_dir: str = None,
|
|
3122
3572
|
):
|
|
3123
3573
|
"""specify builder configuration for the project
|
|
3124
3574
|
|
|
@@ -3139,6 +3589,8 @@ class MlrunProject(ModelObj):
|
|
|
3139
3589
|
e.g. builder_env={"GIT_TOKEN": token}, does not work yet in KFP
|
|
3140
3590
|
:param extra_args: A string containing additional builder arguments in the format of command-line options,
|
|
3141
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`)
|
|
3142
3594
|
"""
|
|
3143
3595
|
if not overwrite_build_params:
|
|
3144
3596
|
# TODO: change overwrite_build_params default to True in 1.8.0
|
|
@@ -3162,6 +3614,7 @@ class MlrunProject(ModelObj):
|
|
|
3162
3614
|
overwrite=overwrite_build_params,
|
|
3163
3615
|
builder_env=builder_env,
|
|
3164
3616
|
extra_args=extra_args,
|
|
3617
|
+
source_code_target_dir=source_code_target_dir,
|
|
3165
3618
|
)
|
|
3166
3619
|
|
|
3167
3620
|
if set_as_default and image != self.default_image:
|
|
@@ -3172,7 +3625,6 @@ class MlrunProject(ModelObj):
|
|
|
3172
3625
|
image: str = None,
|
|
3173
3626
|
set_as_default: bool = True,
|
|
3174
3627
|
with_mlrun: bool = None,
|
|
3175
|
-
skip_deployed: bool = False,
|
|
3176
3628
|
base_image: str = None,
|
|
3177
3629
|
commands: list = None,
|
|
3178
3630
|
secret_name: str = None,
|
|
@@ -3183,7 +3635,7 @@ class MlrunProject(ModelObj):
|
|
|
3183
3635
|
requirements_file: str = None,
|
|
3184
3636
|
extra_args: str = None,
|
|
3185
3637
|
target_dir: str = None,
|
|
3186
|
-
) -> typing.Union[BuildStatus,
|
|
3638
|
+
) -> typing.Union[BuildStatus, PipelineNodeWrapper]:
|
|
3187
3639
|
"""Builder docker image for the project, based on the project's build config. Parameters allow to override
|
|
3188
3640
|
the build config.
|
|
3189
3641
|
If the project has a source configured and pull_at_runtime is not configured, this source will be cloned to the
|
|
@@ -3193,7 +3645,6 @@ class MlrunProject(ModelObj):
|
|
|
3193
3645
|
used. If not set, the `mlconf.default_project_image_name` value will be used
|
|
3194
3646
|
:param set_as_default: set `image` to be the project's default image (default False)
|
|
3195
3647
|
:param with_mlrun: add the current mlrun package to the container build
|
|
3196
|
-
:param skip_deployed: *Deprecated* parameter is ignored
|
|
3197
3648
|
:param base_image: base image name/path (commands and source code will be added to it) defaults to
|
|
3198
3649
|
mlrun.mlconf.default_base_image
|
|
3199
3650
|
:param commands: list of docker build (RUN) commands e.g. ['pip install pandas']
|
|
@@ -3208,7 +3659,7 @@ class MlrunProject(ModelObj):
|
|
|
3208
3659
|
* False: The new params are merged with the existing
|
|
3209
3660
|
* True: The existing params are replaced by the new ones
|
|
3210
3661
|
:param extra_args: A string containing additional builder arguments in the format of command-line options,
|
|
3211
|
-
e.g. extra_args="--skip-tls-verify --build-arg A=val"
|
|
3662
|
+
e.g. extra_args="--skip-tls-verify --build-arg A=val"
|
|
3212
3663
|
:param target_dir: Path on the image where source code would be extracted (by default `/home/mlrun_code`)
|
|
3213
3664
|
"""
|
|
3214
3665
|
if not base_image:
|
|
@@ -3218,14 +3669,6 @@ class MlrunProject(ModelObj):
|
|
|
3218
3669
|
base_image=base_image,
|
|
3219
3670
|
)
|
|
3220
3671
|
|
|
3221
|
-
if skip_deployed:
|
|
3222
|
-
warnings.warn(
|
|
3223
|
-
"The 'skip_deployed' parameter is deprecated and will be removed in 1.7.0. "
|
|
3224
|
-
"This parameter is ignored.",
|
|
3225
|
-
# TODO: remove in 1.7.0
|
|
3226
|
-
FutureWarning,
|
|
3227
|
-
)
|
|
3228
|
-
|
|
3229
3672
|
if not overwrite_build_params:
|
|
3230
3673
|
# TODO: change overwrite_build_params default to True in 1.8.0
|
|
3231
3674
|
warnings.warn(
|
|
@@ -3276,6 +3719,11 @@ class MlrunProject(ModelObj):
|
|
|
3276
3719
|
force_build=True,
|
|
3277
3720
|
)
|
|
3278
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
|
+
|
|
3279
3727
|
try:
|
|
3280
3728
|
mlrun.db.get_run_db(secrets=self._secrets).delete_function(
|
|
3281
3729
|
name=function.metadata.name
|
|
@@ -3284,7 +3732,7 @@ class MlrunProject(ModelObj):
|
|
|
3284
3732
|
logger.warning(
|
|
3285
3733
|
f"Image was successfully built, but failed to delete temporary function {function.metadata.name}."
|
|
3286
3734
|
" To remove the function, attempt to manually delete it.",
|
|
3287
|
-
exc=
|
|
3735
|
+
exc=mlrun.errors.err_to_str(exc),
|
|
3288
3736
|
)
|
|
3289
3737
|
|
|
3290
3738
|
return result
|
|
@@ -3298,7 +3746,7 @@ class MlrunProject(ModelObj):
|
|
|
3298
3746
|
verbose: bool = None,
|
|
3299
3747
|
builder_env: dict = None,
|
|
3300
3748
|
mock: bool = None,
|
|
3301
|
-
) -> typing.Union[DeployStatus,
|
|
3749
|
+
) -> typing.Union[DeployStatus, PipelineNodeWrapper]:
|
|
3302
3750
|
"""deploy real-time (nuclio based) functions
|
|
3303
3751
|
|
|
3304
3752
|
:param function: name of the function (in the project) or function object
|
|
@@ -3333,7 +3781,12 @@ class MlrunProject(ModelObj):
|
|
|
3333
3781
|
artifact = db.read_artifact(
|
|
3334
3782
|
key, tag, iter=iter, project=self.metadata.name, tree=tree
|
|
3335
3783
|
)
|
|
3336
|
-
|
|
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
|
|
3337
3790
|
|
|
3338
3791
|
def list_artifacts(
|
|
3339
3792
|
self,
|
|
@@ -3347,6 +3800,10 @@ class MlrunProject(ModelObj):
|
|
|
3347
3800
|
kind: str = None,
|
|
3348
3801
|
category: typing.Union[str, mlrun.common.schemas.ArtifactCategories] = None,
|
|
3349
3802
|
tree: str = None,
|
|
3803
|
+
limit: int = None,
|
|
3804
|
+
format_: Optional[
|
|
3805
|
+
mlrun.common.formatters.ArtifactFormat
|
|
3806
|
+
] = mlrun.common.formatters.ArtifactFormat.full,
|
|
3350
3807
|
) -> mlrun.lists.ArtifactList:
|
|
3351
3808
|
"""List artifacts filtered by various parameters.
|
|
3352
3809
|
|
|
@@ -3356,9 +3813,9 @@ class MlrunProject(ModelObj):
|
|
|
3356
3813
|
Examples::
|
|
3357
3814
|
|
|
3358
3815
|
# Get latest version of all artifacts in project
|
|
3359
|
-
latest_artifacts = project.list_artifacts(
|
|
3816
|
+
latest_artifacts = project.list_artifacts("", tag="latest")
|
|
3360
3817
|
# check different artifact versions for a specific artifact, return as objects list
|
|
3361
|
-
result_versions = project.list_artifacts(
|
|
3818
|
+
result_versions = project.list_artifacts("results", tag="*").to_objects()
|
|
3362
3819
|
|
|
3363
3820
|
:param name: Name of artifacts to retrieve. Name with '~' prefix is used as a like query, and is not
|
|
3364
3821
|
case-sensitive. This means that querying for ``~name`` may return artifacts named
|
|
@@ -3376,6 +3833,8 @@ class MlrunProject(ModelObj):
|
|
|
3376
3833
|
:param kind: Return artifacts of the requested kind.
|
|
3377
3834
|
:param category: Return artifacts of the requested category.
|
|
3378
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'.
|
|
3379
3838
|
"""
|
|
3380
3839
|
db = mlrun.db.get_run_db(secrets=self._secrets)
|
|
3381
3840
|
return db.list_artifacts(
|
|
@@ -3390,6 +3849,8 @@ class MlrunProject(ModelObj):
|
|
|
3390
3849
|
kind=kind,
|
|
3391
3850
|
category=category,
|
|
3392
3851
|
tree=tree,
|
|
3852
|
+
format_=format_,
|
|
3853
|
+
limit=limit,
|
|
3393
3854
|
)
|
|
3394
3855
|
|
|
3395
3856
|
def list_models(
|
|
@@ -3402,13 +3863,17 @@ class MlrunProject(ModelObj):
|
|
|
3402
3863
|
iter: int = None,
|
|
3403
3864
|
best_iteration: bool = False,
|
|
3404
3865
|
tree: str = None,
|
|
3866
|
+
limit: int = None,
|
|
3867
|
+
format_: Optional[
|
|
3868
|
+
mlrun.common.formatters.ArtifactFormat
|
|
3869
|
+
] = mlrun.common.formatters.ArtifactFormat.full,
|
|
3405
3870
|
):
|
|
3406
3871
|
"""List models in project, filtered by various parameters.
|
|
3407
3872
|
|
|
3408
3873
|
Examples::
|
|
3409
3874
|
|
|
3410
3875
|
# Get latest version of all models in project
|
|
3411
|
-
latest_models = project.list_models(
|
|
3876
|
+
latest_models = project.list_models("", tag="latest")
|
|
3412
3877
|
|
|
3413
3878
|
|
|
3414
3879
|
:param name: Name of artifacts to retrieve. Name with '~' prefix is used as a like query, and is not
|
|
@@ -3425,6 +3890,8 @@ class MlrunProject(ModelObj):
|
|
|
3425
3890
|
artifacts generated from a hyper-param run. If only a single iteration exists, will return the artifact
|
|
3426
3891
|
from that iteration. If using ``best_iter``, the ``iter`` parameter must not be used.
|
|
3427
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'.
|
|
3428
3895
|
"""
|
|
3429
3896
|
db = mlrun.db.get_run_db(secrets=self._secrets)
|
|
3430
3897
|
return db.list_artifacts(
|
|
@@ -3438,6 +3905,8 @@ class MlrunProject(ModelObj):
|
|
|
3438
3905
|
best_iteration=best_iteration,
|
|
3439
3906
|
kind="model",
|
|
3440
3907
|
tree=tree,
|
|
3908
|
+
limit=limit,
|
|
3909
|
+
format_=format_,
|
|
3441
3910
|
).to_objects()
|
|
3442
3911
|
|
|
3443
3912
|
def list_functions(self, name=None, tag=None, labels=None):
|
|
@@ -3449,7 +3918,7 @@ class MlrunProject(ModelObj):
|
|
|
3449
3918
|
|
|
3450
3919
|
|
|
3451
3920
|
:param name: Return only functions with a specific name.
|
|
3452
|
-
: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 ``"*"``.
|
|
3453
3922
|
:param labels: Return functions that have specific labels assigned to them.
|
|
3454
3923
|
:returns: List of function objects.
|
|
3455
3924
|
"""
|
|
@@ -3478,9 +3947,7 @@ class MlrunProject(ModelObj):
|
|
|
3478
3947
|
:returns: List of function objects.
|
|
3479
3948
|
"""
|
|
3480
3949
|
|
|
3481
|
-
model_monitoring_labels_list = [
|
|
3482
|
-
f"{mm_constants.ModelMonitoringAppLabel.KEY}={mm_constants.ModelMonitoringAppLabel.VAL}"
|
|
3483
|
-
]
|
|
3950
|
+
model_monitoring_labels_list = [str(mm_constants.ModelMonitoringAppLabel())]
|
|
3484
3951
|
if labels:
|
|
3485
3952
|
model_monitoring_labels_list += labels
|
|
3486
3953
|
return self.list_functions(
|
|
@@ -3494,7 +3961,10 @@ class MlrunProject(ModelObj):
|
|
|
3494
3961
|
name: Optional[str] = None,
|
|
3495
3962
|
uid: Optional[Union[str, list[str]]] = None,
|
|
3496
3963
|
labels: Optional[Union[str, list[str]]] = None,
|
|
3497
|
-
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,
|
|
3498
3968
|
sort: bool = True,
|
|
3499
3969
|
last: int = 0,
|
|
3500
3970
|
iter: bool = False,
|
|
@@ -3513,14 +3983,14 @@ class MlrunProject(ModelObj):
|
|
|
3513
3983
|
Example::
|
|
3514
3984
|
|
|
3515
3985
|
# return a list of runs matching the name and label and compare
|
|
3516
|
-
runs = project.list_runs(name=
|
|
3986
|
+
runs = project.list_runs(name="download", labels="owner=admin")
|
|
3517
3987
|
runs.compare()
|
|
3518
3988
|
|
|
3519
3989
|
# multi-label filter can also be provided
|
|
3520
|
-
runs = project.list_runs(name=
|
|
3990
|
+
runs = project.list_runs(name="download", labels=["kind=job", "owner=admin"])
|
|
3521
3991
|
|
|
3522
3992
|
# If running in Jupyter, can use the .show() function to display the results
|
|
3523
|
-
project.list_runs(name=
|
|
3993
|
+
project.list_runs(name="").show()
|
|
3524
3994
|
|
|
3525
3995
|
|
|
3526
3996
|
:param name: Name of the run to retrieve.
|
|
@@ -3528,10 +3998,11 @@ class MlrunProject(ModelObj):
|
|
|
3528
3998
|
:param labels: A list of labels to filter by. Label filters work by either filtering a specific value
|
|
3529
3999
|
of a label (i.e. list("key=value")) or by looking for the existence of a given
|
|
3530
4000
|
key (i.e. "key").
|
|
3531
|
-
: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.
|
|
3532
4003
|
:param sort: Whether to sort the result according to their start time. Otherwise, results will be
|
|
3533
4004
|
returned by their internal order in the DB (order will not be guaranteed).
|
|
3534
|
-
: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).
|
|
3535
4006
|
:param iter: If ``True`` return runs from all iterations. Otherwise, return only runs whose ``iter`` is 0.
|
|
3536
4007
|
:param start_time_from: Filter by run start time in ``[start_time_from, start_time_to]``.
|
|
3537
4008
|
:param start_time_to: Filter by run start time in ``[start_time_from, start_time_to]``.
|
|
@@ -3539,13 +4010,22 @@ class MlrunProject(ModelObj):
|
|
|
3539
4010
|
last_update_time_to)``.
|
|
3540
4011
|
:param last_update_time_to: Filter by run last update time in ``(last_update_time_from, last_update_time_to)``.
|
|
3541
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
|
+
|
|
3542
4020
|
db = mlrun.db.get_run_db(secrets=self._secrets)
|
|
3543
4021
|
return db.list_runs(
|
|
3544
4022
|
name,
|
|
3545
4023
|
uid,
|
|
3546
4024
|
self.metadata.name,
|
|
3547
4025
|
labels=labels,
|
|
3548
|
-
|
|
4026
|
+
states=mlrun.utils.helpers.as_list(state)
|
|
4027
|
+
if state is not None
|
|
4028
|
+
else states or None,
|
|
3549
4029
|
sort=sort,
|
|
3550
4030
|
last=last,
|
|
3551
4031
|
iter=iter,
|
|
@@ -3627,7 +4107,10 @@ class MlrunProject(ModelObj):
|
|
|
3627
4107
|
self.spec.remove_custom_packager(packager=packager)
|
|
3628
4108
|
|
|
3629
4109
|
def store_api_gateway(
|
|
3630
|
-
self,
|
|
4110
|
+
self,
|
|
4111
|
+
api_gateway: mlrun.runtimes.nuclio.api_gateway.APIGateway,
|
|
4112
|
+
wait_for_readiness=True,
|
|
4113
|
+
max_wait_time=90,
|
|
3631
4114
|
) -> mlrun.runtimes.nuclio.api_gateway.APIGateway:
|
|
3632
4115
|
"""
|
|
3633
4116
|
Creates or updates a Nuclio API Gateway using the provided APIGateway object.
|
|
@@ -3637,11 +4120,15 @@ class MlrunProject(ModelObj):
|
|
|
3637
4120
|
on MLRun and Nuclio sides, such as the 'host' attribute.
|
|
3638
4121
|
Nuclio docs here: https://docs.nuclio.io/en/latest/reference/api-gateway/http.html
|
|
3639
4122
|
|
|
3640
|
-
:param api_gateway:
|
|
3641
|
-
|
|
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
|
+
|
|
3642
4129
|
|
|
3643
|
-
|
|
3644
|
-
|
|
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
|
|
3645
4132
|
"""
|
|
3646
4133
|
|
|
3647
4134
|
api_gateway_json = mlrun.db.get_run_db().store_api_gateway(
|
|
@@ -3654,14 +4141,17 @@ class MlrunProject(ModelObj):
|
|
|
3654
4141
|
api_gateway = mlrun.runtimes.nuclio.api_gateway.APIGateway.from_scheme(
|
|
3655
4142
|
api_gateway_json
|
|
3656
4143
|
)
|
|
4144
|
+
if wait_for_readiness:
|
|
4145
|
+
api_gateway.wait_for_readiness(max_wait_time=max_wait_time)
|
|
4146
|
+
|
|
3657
4147
|
return api_gateway
|
|
3658
4148
|
|
|
3659
4149
|
def list_api_gateways(self) -> list[mlrun.runtimes.nuclio.api_gateway.APIGateway]:
|
|
3660
4150
|
"""
|
|
3661
4151
|
Retrieves a list of Nuclio API gateways associated with the project.
|
|
3662
4152
|
|
|
3663
|
-
|
|
3664
|
-
|
|
4153
|
+
:returns: List of :py:class:`~mlrun.runtimes.nuclio.api_gateway.APIGateway` objects representing
|
|
4154
|
+
the Nuclio API gateways associated with the project.
|
|
3665
4155
|
"""
|
|
3666
4156
|
gateways_list = mlrun.db.get_run_db().list_api_gateways(self.name)
|
|
3667
4157
|
return [
|
|
@@ -3682,14 +4172,128 @@ class MlrunProject(ModelObj):
|
|
|
3682
4172
|
mlrun.runtimes.nuclio.APIGateway: An instance of APIGateway.
|
|
3683
4173
|
"""
|
|
3684
4174
|
|
|
3685
|
-
|
|
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()
|
|
3686
4290
|
|
|
3687
4291
|
def _run_authenticated_git_action(
|
|
3688
4292
|
self,
|
|
3689
4293
|
action: Callable,
|
|
3690
4294
|
remote: str,
|
|
3691
|
-
args: list =
|
|
3692
|
-
kwargs: dict =
|
|
4295
|
+
args: list = None,
|
|
4296
|
+
kwargs: dict = None,
|
|
3693
4297
|
secrets: Union[SecretsStore, dict] = None,
|
|
3694
4298
|
):
|
|
3695
4299
|
"""Run an arbitrary Git routine while the remote is enriched with secrets
|
|
@@ -3709,6 +4313,8 @@ class MlrunProject(ModelObj):
|
|
|
3709
4313
|
try:
|
|
3710
4314
|
if is_remote_enriched:
|
|
3711
4315
|
self.spec.repo.remotes[remote].set_url(enriched_remote, clean_remote)
|
|
4316
|
+
args = args or []
|
|
4317
|
+
kwargs = kwargs or {}
|
|
3712
4318
|
action(*args, **kwargs)
|
|
3713
4319
|
except RuntimeError as e:
|
|
3714
4320
|
raise mlrun.errors.MLRunRuntimeError(
|
|
@@ -3761,6 +4367,97 @@ class MlrunProject(ModelObj):
|
|
|
3761
4367
|
f"<project.spec.get_code_path()>/<{param_name}>)."
|
|
3762
4368
|
)
|
|
3763
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
|
+
|
|
3764
4461
|
|
|
3765
4462
|
def _set_as_current_default_project(project: MlrunProject):
|
|
3766
4463
|
mlrun.mlconf.default_project = project.metadata.name
|
|
@@ -3783,10 +4480,6 @@ def _init_function_from_dict(
|
|
|
3783
4480
|
tag = f.get("tag", None)
|
|
3784
4481
|
|
|
3785
4482
|
has_module = _has_module(handler, kind)
|
|
3786
|
-
if not url and "spec" not in f and not has_module:
|
|
3787
|
-
# function must point to a file or a module or have a spec
|
|
3788
|
-
raise ValueError("Function missing a url or a spec or a module")
|
|
3789
|
-
|
|
3790
4483
|
relative_url = url
|
|
3791
4484
|
url, in_context = project.get_item_absolute_path(url)
|
|
3792
4485
|
|
|
@@ -3816,18 +4509,17 @@ def _init_function_from_dict(
|
|
|
3816
4509
|
)
|
|
3817
4510
|
|
|
3818
4511
|
elif url.endswith(".py"):
|
|
3819
|
-
# when load_source_on_run is used we allow not providing image as code will be loaded pre-run. ML-4994
|
|
3820
|
-
if (
|
|
3821
|
-
not image
|
|
3822
|
-
and not project.default_image
|
|
3823
|
-
and kind != "local"
|
|
3824
|
-
and not project.spec.load_source_on_run
|
|
3825
|
-
):
|
|
3826
|
-
raise ValueError(
|
|
3827
|
-
"image must be provided with py code files which do not "
|
|
3828
|
-
"run on 'local' engine kind"
|
|
3829
|
-
)
|
|
3830
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
|
+
)
|
|
3831
4523
|
func = new_function(
|
|
3832
4524
|
name,
|
|
3833
4525
|
command=relative_url,
|
|
@@ -3846,6 +4538,17 @@ def _init_function_from_dict(
|
|
|
3846
4538
|
tag=tag,
|
|
3847
4539
|
)
|
|
3848
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)
|
|
3849
4552
|
else:
|
|
3850
4553
|
raise ValueError(f"Unsupported function url:handler {url}:{handler} or no spec")
|
|
3851
4554
|
|
|
@@ -3891,9 +4594,17 @@ def _init_function_from_obj(
|
|
|
3891
4594
|
def _has_module(handler, kind):
|
|
3892
4595
|
if not handler:
|
|
3893
4596
|
return False
|
|
3894
|
-
|
|
3895
|
-
|
|
3896
|
-
|
|
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
|
|
3897
4608
|
|
|
3898
4609
|
|
|
3899
4610
|
def _is_imported_artifact(artifact):
|