mlrun 1.6.4rc7__py3-none-any.whl → 1.7.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of mlrun might be problematic. Click here for more details.
- mlrun/__init__.py +11 -1
- mlrun/__main__.py +40 -122
- mlrun/alerts/__init__.py +15 -0
- mlrun/alerts/alert.py +248 -0
- mlrun/api/schemas/__init__.py +5 -4
- mlrun/artifacts/__init__.py +8 -3
- mlrun/artifacts/base.py +47 -257
- mlrun/artifacts/dataset.py +11 -192
- mlrun/artifacts/manager.py +79 -47
- mlrun/artifacts/model.py +31 -159
- mlrun/artifacts/plots.py +23 -380
- mlrun/common/constants.py +74 -1
- mlrun/common/db/sql_session.py +5 -5
- mlrun/common/formatters/__init__.py +21 -0
- mlrun/common/formatters/artifact.py +45 -0
- mlrun/common/formatters/base.py +113 -0
- mlrun/common/formatters/feature_set.py +33 -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 +12 -3
- mlrun/common/model_monitoring/helpers.py +9 -5
- mlrun/{runtimes → common/runtimes}/constants.py +37 -9
- mlrun/common/schemas/__init__.py +31 -5
- mlrun/common/schemas/alert.py +202 -0
- mlrun/common/schemas/api_gateway.py +196 -0
- mlrun/common/schemas/artifact.py +25 -4
- mlrun/common/schemas/auth.py +16 -5
- mlrun/common/schemas/background_task.py +1 -1
- mlrun/common/schemas/client_spec.py +4 -2
- mlrun/common/schemas/common.py +7 -4
- mlrun/common/schemas/constants.py +3 -0
- mlrun/common/schemas/feature_store.py +74 -44
- mlrun/common/schemas/frontend_spec.py +15 -7
- mlrun/common/schemas/function.py +12 -1
- mlrun/common/schemas/hub.py +11 -18
- mlrun/common/schemas/memory_reports.py +2 -2
- mlrun/common/schemas/model_monitoring/__init__.py +20 -4
- mlrun/common/schemas/model_monitoring/constants.py +123 -42
- mlrun/common/schemas/model_monitoring/grafana.py +13 -9
- mlrun/common/schemas/model_monitoring/model_endpoints.py +101 -54
- mlrun/common/schemas/notification.py +71 -14
- mlrun/common/schemas/object.py +2 -2
- mlrun/{model_monitoring/controller_handler.py → common/schemas/pagination.py} +9 -12
- mlrun/common/schemas/pipeline.py +8 -1
- mlrun/common/schemas/project.py +69 -18
- mlrun/common/schemas/runs.py +7 -1
- mlrun/common/schemas/runtime_resource.py +8 -12
- mlrun/common/schemas/schedule.py +4 -4
- mlrun/common/schemas/tag.py +1 -2
- mlrun/common/schemas/workflow.py +12 -4
- mlrun/common/types.py +14 -1
- mlrun/config.py +154 -69
- mlrun/data_types/data_types.py +6 -1
- mlrun/data_types/spark.py +2 -2
- mlrun/data_types/to_pandas.py +67 -37
- mlrun/datastore/__init__.py +6 -8
- mlrun/datastore/alibaba_oss.py +131 -0
- mlrun/datastore/azure_blob.py +143 -42
- mlrun/datastore/base.py +102 -58
- mlrun/datastore/datastore.py +34 -13
- mlrun/datastore/datastore_profile.py +146 -20
- mlrun/datastore/dbfs_store.py +3 -7
- mlrun/datastore/filestore.py +1 -4
- mlrun/datastore/google_cloud_storage.py +97 -33
- mlrun/datastore/hdfs.py +56 -0
- mlrun/datastore/inmem.py +6 -3
- mlrun/datastore/redis.py +7 -2
- mlrun/datastore/s3.py +34 -12
- mlrun/datastore/snowflake_utils.py +45 -0
- mlrun/datastore/sources.py +303 -111
- mlrun/datastore/spark_utils.py +31 -2
- mlrun/datastore/store_resources.py +9 -7
- mlrun/datastore/storeytargets.py +151 -0
- mlrun/datastore/targets.py +453 -176
- mlrun/datastore/utils.py +72 -58
- mlrun/datastore/v3io.py +6 -1
- mlrun/db/base.py +274 -41
- mlrun/db/factory.py +1 -1
- mlrun/db/httpdb.py +893 -225
- mlrun/db/nopdb.py +291 -33
- mlrun/errors.py +36 -6
- mlrun/execution.py +115 -42
- mlrun/feature_store/__init__.py +0 -2
- mlrun/feature_store/api.py +65 -73
- mlrun/feature_store/common.py +7 -12
- mlrun/feature_store/feature_set.py +76 -55
- mlrun/feature_store/feature_vector.py +39 -31
- mlrun/feature_store/ingestion.py +7 -6
- mlrun/feature_store/retrieval/base.py +16 -11
- 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 +45 -34
- mlrun/features.py +11 -21
- mlrun/frameworks/_common/artifacts_library.py +9 -9
- mlrun/frameworks/_common/mlrun_interface.py +5 -5
- mlrun/frameworks/_common/model_handler.py +48 -48
- mlrun/frameworks/_common/plan.py +5 -6
- mlrun/frameworks/_common/producer.py +3 -4
- mlrun/frameworks/_common/utils.py +5 -5
- mlrun/frameworks/_dl_common/loggers/logger.py +6 -7
- mlrun/frameworks/_dl_common/loggers/mlrun_logger.py +9 -9
- mlrun/frameworks/_dl_common/loggers/tensorboard_logger.py +23 -47
- mlrun/frameworks/_ml_common/artifacts_library.py +1 -2
- mlrun/frameworks/_ml_common/loggers/logger.py +3 -4
- mlrun/frameworks/_ml_common/loggers/mlrun_logger.py +4 -5
- mlrun/frameworks/_ml_common/model_handler.py +24 -24
- mlrun/frameworks/_ml_common/pkl_model_server.py +2 -2
- mlrun/frameworks/_ml_common/plan.py +2 -2
- mlrun/frameworks/_ml_common/plans/calibration_curve_plan.py +2 -3
- mlrun/frameworks/_ml_common/plans/confusion_matrix_plan.py +2 -3
- mlrun/frameworks/_ml_common/plans/dataset_plan.py +3 -3
- mlrun/frameworks/_ml_common/plans/feature_importance_plan.py +3 -3
- mlrun/frameworks/_ml_common/plans/roc_curve_plan.py +4 -4
- mlrun/frameworks/_ml_common/utils.py +4 -4
- mlrun/frameworks/auto_mlrun/auto_mlrun.py +9 -9
- mlrun/frameworks/huggingface/model_server.py +4 -4
- mlrun/frameworks/lgbm/__init__.py +33 -33
- mlrun/frameworks/lgbm/callbacks/callback.py +2 -4
- mlrun/frameworks/lgbm/callbacks/logging_callback.py +4 -5
- mlrun/frameworks/lgbm/callbacks/mlrun_logging_callback.py +4 -5
- mlrun/frameworks/lgbm/mlrun_interfaces/booster_mlrun_interface.py +1 -3
- mlrun/frameworks/lgbm/mlrun_interfaces/mlrun_interface.py +6 -6
- mlrun/frameworks/lgbm/model_handler.py +10 -10
- mlrun/frameworks/lgbm/model_server.py +6 -6
- mlrun/frameworks/lgbm/utils.py +5 -5
- mlrun/frameworks/onnx/dataset.py +8 -8
- mlrun/frameworks/onnx/mlrun_interface.py +3 -3
- mlrun/frameworks/onnx/model_handler.py +6 -6
- mlrun/frameworks/onnx/model_server.py +7 -7
- mlrun/frameworks/parallel_coordinates.py +6 -6
- mlrun/frameworks/pytorch/__init__.py +18 -18
- mlrun/frameworks/pytorch/callbacks/callback.py +4 -5
- mlrun/frameworks/pytorch/callbacks/logging_callback.py +17 -17
- mlrun/frameworks/pytorch/callbacks/mlrun_logging_callback.py +11 -11
- mlrun/frameworks/pytorch/callbacks/tensorboard_logging_callback.py +23 -29
- mlrun/frameworks/pytorch/callbacks_handler.py +38 -38
- mlrun/frameworks/pytorch/mlrun_interface.py +20 -20
- mlrun/frameworks/pytorch/model_handler.py +17 -17
- mlrun/frameworks/pytorch/model_server.py +7 -7
- mlrun/frameworks/sklearn/__init__.py +13 -13
- mlrun/frameworks/sklearn/estimator.py +4 -4
- mlrun/frameworks/sklearn/metrics_library.py +14 -14
- mlrun/frameworks/sklearn/mlrun_interface.py +16 -9
- mlrun/frameworks/sklearn/model_handler.py +2 -2
- mlrun/frameworks/tf_keras/__init__.py +10 -7
- mlrun/frameworks/tf_keras/callbacks/logging_callback.py +15 -15
- mlrun/frameworks/tf_keras/callbacks/mlrun_logging_callback.py +11 -11
- mlrun/frameworks/tf_keras/callbacks/tensorboard_logging_callback.py +19 -23
- mlrun/frameworks/tf_keras/mlrun_interface.py +9 -11
- mlrun/frameworks/tf_keras/model_handler.py +14 -14
- mlrun/frameworks/tf_keras/model_server.py +6 -6
- mlrun/frameworks/xgboost/__init__.py +13 -13
- mlrun/frameworks/xgboost/model_handler.py +6 -6
- mlrun/k8s_utils.py +61 -17
- mlrun/launcher/__init__.py +1 -1
- mlrun/launcher/base.py +16 -15
- mlrun/launcher/client.py +13 -11
- mlrun/launcher/factory.py +1 -1
- mlrun/launcher/local.py +23 -13
- mlrun/launcher/remote.py +17 -10
- mlrun/lists.py +7 -6
- mlrun/model.py +478 -103
- mlrun/model_monitoring/__init__.py +1 -1
- mlrun/model_monitoring/api.py +163 -371
- mlrun/{runtimes/mpijob/v1alpha1.py → model_monitoring/applications/__init__.py} +9 -15
- mlrun/model_monitoring/applications/_application_steps.py +188 -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 +354 -0
- mlrun/model_monitoring/applications/results.py +99 -0
- mlrun/model_monitoring/controller.py +131 -278
- mlrun/model_monitoring/db/__init__.py +18 -0
- 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 +279 -0
- mlrun/model_monitoring/db/tsdb/tdengine/stream_graph_steps.py +42 -0
- mlrun/model_monitoring/db/tsdb/tdengine/tdengine_connector.py +507 -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 +134 -106
- mlrun/model_monitoring/helpers.py +199 -55
- mlrun/model_monitoring/metrics/__init__.py +13 -0
- mlrun/model_monitoring/metrics/histogram_distance.py +127 -0
- mlrun/model_monitoring/model_endpoint.py +3 -2
- mlrun/model_monitoring/stream_processing.py +131 -398
- mlrun/model_monitoring/tracking_policy.py +9 -2
- mlrun/model_monitoring/writer.py +161 -125
- mlrun/package/__init__.py +6 -6
- mlrun/package/context_handler.py +5 -5
- mlrun/package/packager.py +7 -7
- mlrun/package/packagers/default_packager.py +8 -8
- mlrun/package/packagers/numpy_packagers.py +15 -15
- mlrun/package/packagers/pandas_packagers.py +5 -5
- mlrun/package/packagers/python_standard_library_packagers.py +10 -10
- mlrun/package/packagers_manager.py +19 -23
- mlrun/package/utils/_formatter.py +6 -6
- mlrun/package/utils/_pickler.py +2 -2
- mlrun/package/utils/_supported_format.py +4 -4
- mlrun/package/utils/log_hint_utils.py +2 -2
- mlrun/package/utils/type_hint_utils.py +4 -9
- mlrun/platforms/__init__.py +11 -10
- mlrun/platforms/iguazio.py +24 -203
- mlrun/projects/operations.py +52 -25
- mlrun/projects/pipelines.py +191 -197
- mlrun/projects/project.py +1227 -400
- mlrun/render.py +16 -19
- mlrun/run.py +209 -184
- mlrun/runtimes/__init__.py +83 -15
- mlrun/runtimes/base.py +51 -35
- mlrun/runtimes/daskjob.py +17 -10
- mlrun/runtimes/databricks_job/databricks_cancel_task.py +1 -1
- mlrun/runtimes/databricks_job/databricks_runtime.py +8 -7
- mlrun/runtimes/databricks_job/databricks_wrapper.py +1 -1
- mlrun/runtimes/funcdoc.py +1 -29
- mlrun/runtimes/function_reference.py +1 -1
- mlrun/runtimes/kubejob.py +34 -128
- mlrun/runtimes/local.py +40 -11
- mlrun/runtimes/mpijob/__init__.py +0 -20
- mlrun/runtimes/mpijob/abstract.py +9 -10
- mlrun/runtimes/mpijob/v1.py +1 -1
- mlrun/{model_monitoring/stores/models/sqlite.py → runtimes/nuclio/__init__.py} +7 -9
- mlrun/runtimes/nuclio/api_gateway.py +769 -0
- mlrun/runtimes/nuclio/application/__init__.py +15 -0
- mlrun/runtimes/nuclio/application/application.py +758 -0
- mlrun/runtimes/nuclio/application/reverse_proxy.go +95 -0
- mlrun/runtimes/{function.py → nuclio/function.py} +200 -83
- mlrun/runtimes/{nuclio.py → nuclio/nuclio.py} +6 -6
- mlrun/runtimes/{serving.py → nuclio/serving.py} +65 -68
- mlrun/runtimes/pod.py +281 -101
- mlrun/runtimes/remotesparkjob.py +12 -9
- mlrun/runtimes/sparkjob/spark3job.py +67 -51
- mlrun/runtimes/utils.py +41 -75
- mlrun/secrets.py +9 -5
- mlrun/serving/__init__.py +8 -1
- mlrun/serving/remote.py +2 -7
- mlrun/serving/routers.py +85 -69
- mlrun/serving/server.py +69 -44
- mlrun/serving/states.py +209 -36
- mlrun/serving/utils.py +22 -14
- mlrun/serving/v1_serving.py +6 -7
- mlrun/serving/v2_serving.py +129 -54
- mlrun/track/tracker.py +2 -1
- mlrun/track/tracker_manager.py +3 -3
- mlrun/track/trackers/mlflow_tracker.py +6 -2
- mlrun/utils/async_http.py +6 -8
- mlrun/utils/azure_vault.py +1 -1
- mlrun/utils/clones.py +1 -2
- mlrun/utils/condition_evaluator.py +3 -3
- mlrun/utils/db.py +21 -3
- mlrun/utils/helpers.py +405 -225
- mlrun/utils/http.py +3 -6
- mlrun/utils/logger.py +112 -16
- mlrun/utils/notifications/notification/__init__.py +17 -13
- mlrun/utils/notifications/notification/base.py +50 -2
- mlrun/utils/notifications/notification/console.py +2 -0
- mlrun/utils/notifications/notification/git.py +24 -1
- mlrun/utils/notifications/notification/ipython.py +3 -1
- mlrun/utils/notifications/notification/slack.py +96 -21
- mlrun/utils/notifications/notification/webhook.py +59 -2
- mlrun/utils/notifications/notification_pusher.py +149 -30
- mlrun/utils/regex.py +9 -0
- mlrun/utils/retryer.py +208 -0
- mlrun/utils/singleton.py +1 -1
- mlrun/utils/v3io_clients.py +4 -6
- mlrun/utils/version/version.json +2 -2
- mlrun/utils/version/version.py +2 -6
- mlrun-1.7.0.dist-info/METADATA +378 -0
- mlrun-1.7.0.dist-info/RECORD +351 -0
- {mlrun-1.6.4rc7.dist-info → mlrun-1.7.0.dist-info}/WHEEL +1 -1
- mlrun/feature_store/retrieval/conversion.py +0 -273
- mlrun/kfpops.py +0 -868
- mlrun/model_monitoring/application.py +0 -310
- mlrun/model_monitoring/batch.py +0 -1095
- mlrun/model_monitoring/prometheus.py +0 -219
- mlrun/model_monitoring/stores/__init__.py +0 -111
- mlrun/model_monitoring/stores/kv_model_endpoint_store.py +0 -576
- mlrun/model_monitoring/stores/model_endpoint_store.py +0 -147
- 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 -384
- mlrun/platforms/other.py +0 -306
- mlrun-1.6.4rc7.dist-info/METADATA +0 -272
- mlrun-1.6.4rc7.dist-info/RECORD +0 -314
- {mlrun-1.6.4rc7.dist-info → mlrun-1.7.0.dist-info}/LICENSE +0 -0
- {mlrun-1.6.4rc7.dist-info → mlrun-1.7.0.dist-info}/entry_points.txt +0 -0
- {mlrun-1.6.4rc7.dist-info → mlrun-1.7.0.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,49 +26,48 @@ 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
|
-
from typing import Callable,
|
|
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
|
|
|
38
43
|
import mlrun.common.helpers
|
|
39
|
-
import mlrun.common.
|
|
44
|
+
import mlrun.common.runtimes.constants
|
|
45
|
+
import mlrun.common.schemas.artifact
|
|
40
46
|
import mlrun.common.schemas.model_monitoring.constants as mm_constants
|
|
41
47
|
import mlrun.db
|
|
42
48
|
import mlrun.errors
|
|
43
49
|
import mlrun.k8s_utils
|
|
50
|
+
import mlrun.model_monitoring.applications as mm_app
|
|
44
51
|
import mlrun.runtimes
|
|
52
|
+
import mlrun.runtimes.nuclio.api_gateway
|
|
45
53
|
import mlrun.runtimes.pod
|
|
46
54
|
import mlrun.runtimes.utils
|
|
55
|
+
import mlrun.serving
|
|
56
|
+
import mlrun.utils
|
|
47
57
|
import mlrun.utils.regex
|
|
58
|
+
from mlrun.alerts.alert import AlertConfig
|
|
59
|
+
from mlrun.common.schemas.alert import AlertTemplate
|
|
48
60
|
from mlrun.datastore.datastore_profile import DatastoreProfile, DatastoreProfile2Json
|
|
61
|
+
from mlrun.runtimes.nuclio.function import RemoteRuntime
|
|
49
62
|
|
|
50
63
|
from ..artifacts import Artifact, ArtifactProducer, DatasetArtifact, ModelArtifact
|
|
51
64
|
from ..artifacts.manager import ArtifactManager, dict_to_artifact, extend_artifact_path
|
|
52
65
|
from ..datastore import store_manager
|
|
53
66
|
from ..features import Feature
|
|
54
67
|
from ..model import EntrypointParam, ImageBuilder, ModelObj
|
|
55
|
-
from ..model_monitoring.application import (
|
|
56
|
-
ModelMonitoringApplicationBase,
|
|
57
|
-
PushToMonitoringWriter,
|
|
58
|
-
)
|
|
59
68
|
from ..run import code_to_function, get_object, import_function, new_function
|
|
60
|
-
from ..runtimes.function import RemoteRuntime
|
|
61
69
|
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
|
-
)
|
|
70
|
+
from ..utils import is_jupyter, is_relative_path, is_yaml_path, logger, update_in
|
|
70
71
|
from ..utils.clones import (
|
|
71
72
|
add_credentials_git_remote_url,
|
|
72
73
|
clone_git,
|
|
@@ -74,7 +75,10 @@ from ..utils.clones import (
|
|
|
74
75
|
clone_zip,
|
|
75
76
|
get_repo_url,
|
|
76
77
|
)
|
|
77
|
-
from ..utils.helpers import
|
|
78
|
+
from ..utils.helpers import (
|
|
79
|
+
ensure_git_branch,
|
|
80
|
+
resolve_git_reference_from_source,
|
|
81
|
+
)
|
|
78
82
|
from ..utils.notifications import CustomNotificationPusher, NotificationTypes
|
|
79
83
|
from .operations import (
|
|
80
84
|
BuildStatus,
|
|
@@ -128,6 +132,7 @@ def new_project(
|
|
|
128
132
|
save: bool = True,
|
|
129
133
|
overwrite: bool = False,
|
|
130
134
|
parameters: dict = None,
|
|
135
|
+
default_function_node_selector: dict = None,
|
|
131
136
|
) -> "MlrunProject":
|
|
132
137
|
"""Create a new MLRun project, optionally load it from a yaml/zip/git template
|
|
133
138
|
|
|
@@ -138,11 +143,15 @@ def new_project(
|
|
|
138
143
|
example::
|
|
139
144
|
|
|
140
145
|
# create a project with local and hub functions, a workflow, and an artifact
|
|
141
|
-
project = mlrun.new_project(
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
project.
|
|
145
|
-
|
|
146
|
+
project = mlrun.new_project(
|
|
147
|
+
"myproj", "./", init_git=True, description="my new project"
|
|
148
|
+
)
|
|
149
|
+
project.set_function(
|
|
150
|
+
"prep_data.py", "prep-data", image="mlrun/mlrun", handler="prep_data"
|
|
151
|
+
)
|
|
152
|
+
project.set_function("hub://auto-trainer", "train")
|
|
153
|
+
project.set_artifact("data", Artifact(target_path=data_url))
|
|
154
|
+
project.set_workflow("main", "./myflow.py")
|
|
146
155
|
project.save()
|
|
147
156
|
|
|
148
157
|
# run the "main" workflow (watch=True to wait for run completion)
|
|
@@ -152,19 +161,25 @@ def new_project(
|
|
|
152
161
|
|
|
153
162
|
# create a new project from a zip template (can also use yaml/git templates)
|
|
154
163
|
# initialize a local git, and register the git remote path
|
|
155
|
-
project = mlrun.new_project(
|
|
156
|
-
|
|
157
|
-
|
|
164
|
+
project = mlrun.new_project(
|
|
165
|
+
"myproj",
|
|
166
|
+
"./",
|
|
167
|
+
init_git=True,
|
|
168
|
+
remote="git://github.com/mlrun/project-demo.git",
|
|
169
|
+
from_template="http://mysite/proj.zip",
|
|
170
|
+
)
|
|
158
171
|
project.run("main", watch=True)
|
|
159
172
|
|
|
160
173
|
|
|
161
174
|
example using project_setup.py to init the project objects::
|
|
162
175
|
|
|
163
176
|
def setup(project):
|
|
164
|
-
project.set_function(
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
project.
|
|
177
|
+
project.set_function(
|
|
178
|
+
"prep_data.py", "prep-data", image="mlrun/mlrun", handler="prep_data"
|
|
179
|
+
)
|
|
180
|
+
project.set_function("hub://auto-trainer", "train")
|
|
181
|
+
project.set_artifact("data", Artifact(target_path=data_url))
|
|
182
|
+
project.set_workflow("main", "./myflow.py")
|
|
168
183
|
return project
|
|
169
184
|
|
|
170
185
|
|
|
@@ -181,6 +196,7 @@ def new_project(
|
|
|
181
196
|
:param overwrite: overwrite project using 'cascade' deletion strategy (deletes project resources)
|
|
182
197
|
if project with name exists
|
|
183
198
|
:param parameters: key/value pairs to add to the project.spec.params
|
|
199
|
+
:param default_function_node_selector: defines the default node selector for scheduling functions within the project
|
|
184
200
|
|
|
185
201
|
:returns: project object
|
|
186
202
|
"""
|
|
@@ -193,14 +209,16 @@ def new_project(
|
|
|
193
209
|
"Unsupported option, cannot use subpath argument with project templates"
|
|
194
210
|
)
|
|
195
211
|
if from_template.endswith(".yaml"):
|
|
196
|
-
project = _load_project_file(
|
|
212
|
+
project = _load_project_file(
|
|
213
|
+
from_template, name, secrets, allow_cross_project=True
|
|
214
|
+
)
|
|
197
215
|
elif from_template.startswith("git://"):
|
|
198
216
|
clone_git(from_template, context, secrets, clone=True)
|
|
199
217
|
shutil.rmtree(path.join(context, ".git"))
|
|
200
|
-
project = _load_project_dir(context, name)
|
|
218
|
+
project = _load_project_dir(context, name, allow_cross_project=True)
|
|
201
219
|
elif from_template.endswith(".zip"):
|
|
202
220
|
clone_zip(from_template, context, secrets)
|
|
203
|
-
project = _load_project_dir(context, name)
|
|
221
|
+
project = _load_project_dir(context, name, allow_cross_project=True)
|
|
204
222
|
else:
|
|
205
223
|
raise ValueError("template must be a path to .yaml or .zip file")
|
|
206
224
|
project.metadata.name = name
|
|
@@ -227,6 +245,10 @@ def new_project(
|
|
|
227
245
|
project.spec.origin_url = url
|
|
228
246
|
if description:
|
|
229
247
|
project.spec.description = description
|
|
248
|
+
|
|
249
|
+
if default_function_node_selector:
|
|
250
|
+
project.spec.default_function_node_selector = default_function_node_selector
|
|
251
|
+
|
|
230
252
|
if parameters:
|
|
231
253
|
# Enable setting project parameters at load time, can be used to customize the project_setup
|
|
232
254
|
for key, val in parameters.items():
|
|
@@ -277,6 +299,7 @@ def load_project(
|
|
|
277
299
|
save: bool = True,
|
|
278
300
|
sync_functions: bool = False,
|
|
279
301
|
parameters: dict = None,
|
|
302
|
+
allow_cross_project: bool = None,
|
|
280
303
|
) -> "MlrunProject":
|
|
281
304
|
"""Load an MLRun project from git or tar or dir
|
|
282
305
|
|
|
@@ -290,7 +313,7 @@ def load_project(
|
|
|
290
313
|
# When using git as the url source the context directory must be an empty or
|
|
291
314
|
# non-existent folder as the git repo will be cloned there
|
|
292
315
|
project = load_project("./demo_proj", "git://github.com/mlrun/project-demo.git")
|
|
293
|
-
project.run("main", arguments={
|
|
316
|
+
project.run("main", arguments={"data": data_url})
|
|
294
317
|
|
|
295
318
|
|
|
296
319
|
project_setup.py example::
|
|
@@ -323,6 +346,8 @@ def load_project(
|
|
|
323
346
|
:param save: whether to save the created project and artifact in the DB
|
|
324
347
|
:param sync_functions: sync the project's functions into the project object (will be saved to the DB if save=True)
|
|
325
348
|
:param parameters: key/value pairs to add to the project.spec.params
|
|
349
|
+
:param allow_cross_project: if True, override the loaded project name. This flag ensures awareness of
|
|
350
|
+
loading an existing project yaml as a baseline for a new project with a different name
|
|
326
351
|
|
|
327
352
|
:returns: project object
|
|
328
353
|
"""
|
|
@@ -338,7 +363,7 @@ def load_project(
|
|
|
338
363
|
if url:
|
|
339
364
|
url = str(url) # to support path objects
|
|
340
365
|
if is_yaml_path(url):
|
|
341
|
-
project = _load_project_file(url, name, secrets)
|
|
366
|
+
project = _load_project_file(url, name, secrets, allow_cross_project)
|
|
342
367
|
project.spec.context = context
|
|
343
368
|
elif url.startswith("git://"):
|
|
344
369
|
url, repo = clone_git(url, context, secrets, clone)
|
|
@@ -365,7 +390,7 @@ def load_project(
|
|
|
365
390
|
repo, url = init_repo(context, url, init_git)
|
|
366
391
|
|
|
367
392
|
if not project:
|
|
368
|
-
project = _load_project_dir(context, name, subpath)
|
|
393
|
+
project = _load_project_dir(context, name, subpath, allow_cross_project)
|
|
369
394
|
|
|
370
395
|
if not project.metadata.name:
|
|
371
396
|
raise ValueError("Project name must be specified")
|
|
@@ -419,6 +444,7 @@ def get_or_create_project(
|
|
|
419
444
|
from_template: str = None,
|
|
420
445
|
save: bool = True,
|
|
421
446
|
parameters: dict = None,
|
|
447
|
+
allow_cross_project: bool = None,
|
|
422
448
|
) -> "MlrunProject":
|
|
423
449
|
"""Load a project from MLRun DB, or create/import if it does not exist
|
|
424
450
|
|
|
@@ -429,9 +455,11 @@ def get_or_create_project(
|
|
|
429
455
|
Usage example::
|
|
430
456
|
|
|
431
457
|
# load project from the DB (if exist) or the source repo
|
|
432
|
-
project = get_or_create_project(
|
|
458
|
+
project = get_or_create_project(
|
|
459
|
+
"myproj", "./", "git://github.com/mlrun/demo-xgb-project.git"
|
|
460
|
+
)
|
|
433
461
|
project.pull("development") # pull the latest code from git
|
|
434
|
-
project.run("main", arguments={
|
|
462
|
+
project.run("main", arguments={"data": data_url}) # run the workflow "main"
|
|
435
463
|
|
|
436
464
|
|
|
437
465
|
project_setup.py example::
|
|
@@ -461,12 +489,12 @@ def get_or_create_project(
|
|
|
461
489
|
:param from_template: path to project YAML file that will be used as from_template (for new projects)
|
|
462
490
|
:param save: whether to save the created project in the DB
|
|
463
491
|
:param parameters: key/value pairs to add to the project.spec.params
|
|
492
|
+
:param allow_cross_project: if True, override the loaded project name. This flag ensures awareness of
|
|
493
|
+
loading an existing project yaml as a baseline for a new project with a different name
|
|
464
494
|
|
|
465
495
|
:returns: project object
|
|
466
496
|
"""
|
|
467
497
|
context = context or "./"
|
|
468
|
-
spec_path = path.join(context, subpath or "", "project.yaml")
|
|
469
|
-
load_from_path = url or path.isfile(spec_path)
|
|
470
498
|
try:
|
|
471
499
|
# load project from the DB.
|
|
472
500
|
# use `name` as `url` as we load the project from the DB
|
|
@@ -482,17 +510,26 @@ def get_or_create_project(
|
|
|
482
510
|
# only loading project from db so no need to save it
|
|
483
511
|
save=False,
|
|
484
512
|
parameters=parameters,
|
|
513
|
+
allow_cross_project=allow_cross_project,
|
|
485
514
|
)
|
|
486
|
-
logger.info("Project loaded successfully", project_name=name)
|
|
515
|
+
logger.info("Project loaded successfully", project_name=project.name)
|
|
487
516
|
return project
|
|
488
|
-
|
|
489
517
|
except mlrun.errors.MLRunNotFoundError:
|
|
490
|
-
logger.debug(
|
|
518
|
+
logger.debug(
|
|
519
|
+
"Project not found in db", project_name=name, user_project=user_project
|
|
520
|
+
)
|
|
491
521
|
|
|
522
|
+
spec_path = path.join(context, subpath or "", "project.yaml")
|
|
523
|
+
load_from_path = url or path.isfile(spec_path)
|
|
492
524
|
# do not nest under "try" or else the exceptions raised below will be logged along with the "not found" message
|
|
493
525
|
if load_from_path:
|
|
494
526
|
# loads a project from archive or local project.yaml
|
|
495
|
-
logger.info(
|
|
527
|
+
logger.info(
|
|
528
|
+
"Loading project from path",
|
|
529
|
+
project_name=name,
|
|
530
|
+
user_project=user_project,
|
|
531
|
+
path=url or context,
|
|
532
|
+
)
|
|
496
533
|
project = load_project(
|
|
497
534
|
context,
|
|
498
535
|
url,
|
|
@@ -504,11 +541,12 @@ def get_or_create_project(
|
|
|
504
541
|
user_project=user_project,
|
|
505
542
|
save=save,
|
|
506
543
|
parameters=parameters,
|
|
544
|
+
allow_cross_project=allow_cross_project,
|
|
507
545
|
)
|
|
508
546
|
|
|
509
547
|
logger.info(
|
|
510
548
|
"Project loaded successfully",
|
|
511
|
-
project_name=name,
|
|
549
|
+
project_name=project.name,
|
|
512
550
|
path=url or context,
|
|
513
551
|
stored_in_db=save,
|
|
514
552
|
)
|
|
@@ -526,7 +564,9 @@ def get_or_create_project(
|
|
|
526
564
|
save=save,
|
|
527
565
|
parameters=parameters,
|
|
528
566
|
)
|
|
529
|
-
logger.info(
|
|
567
|
+
logger.info(
|
|
568
|
+
"Project created successfully", project_name=project.name, stored_in_db=save
|
|
569
|
+
)
|
|
530
570
|
return project
|
|
531
571
|
|
|
532
572
|
|
|
@@ -564,6 +604,10 @@ def _run_project_setup(
|
|
|
564
604
|
if hasattr(mod, "setup"):
|
|
565
605
|
try:
|
|
566
606
|
project = getattr(mod, "setup")(project)
|
|
607
|
+
if not project or not isinstance(project, mlrun.projects.MlrunProject):
|
|
608
|
+
raise ValueError(
|
|
609
|
+
"MLRun project_setup:setup() must return a project object"
|
|
610
|
+
)
|
|
567
611
|
except Exception as exc:
|
|
568
612
|
logger.error(
|
|
569
613
|
"Failed to run project_setup script",
|
|
@@ -574,30 +618,44 @@ def _run_project_setup(
|
|
|
574
618
|
if save:
|
|
575
619
|
project.save()
|
|
576
620
|
else:
|
|
577
|
-
logger.warn(
|
|
621
|
+
logger.warn(
|
|
622
|
+
f"skipping setup, setup() handler was not found in {path.basename(setup_file_path)}"
|
|
623
|
+
)
|
|
578
624
|
return project
|
|
579
625
|
|
|
580
626
|
|
|
581
|
-
def _load_project_dir(context, name="", subpath=""):
|
|
627
|
+
def _load_project_dir(context, name="", subpath="", allow_cross_project=None):
|
|
582
628
|
subpath_str = subpath or ""
|
|
583
|
-
|
|
629
|
+
|
|
630
|
+
# support both .yaml and .yml file extensions
|
|
631
|
+
project_file_path = path.join(context, subpath_str, "project.y*ml")
|
|
632
|
+
function_file_path = path.join(context, subpath_str, "function.y*ml")
|
|
584
633
|
setup_file_path = path.join(context, subpath_str, "project_setup.py")
|
|
585
|
-
|
|
586
|
-
|
|
634
|
+
|
|
635
|
+
if project_files := glob.glob(project_file_path):
|
|
636
|
+
# if there are multiple project files, use the first one
|
|
637
|
+
project_file_path = project_files[0]
|
|
638
|
+
with open(project_file_path) as fp:
|
|
587
639
|
data = fp.read()
|
|
588
640
|
struct = yaml.load(data, Loader=yaml.FullLoader)
|
|
589
|
-
project = _project_instance_from_struct(struct, name)
|
|
641
|
+
project = _project_instance_from_struct(struct, name, allow_cross_project)
|
|
590
642
|
project.spec.context = context
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
func = import_function(
|
|
643
|
+
elif function_files := glob.glob(function_file_path):
|
|
644
|
+
function_path = function_files[0]
|
|
645
|
+
func = import_function(function_path)
|
|
646
|
+
function_file_name = path.basename(path.normpath(function_path))
|
|
594
647
|
project = MlrunProject.from_dict(
|
|
595
648
|
{
|
|
596
649
|
"metadata": {
|
|
597
650
|
"name": func.metadata.project,
|
|
598
651
|
},
|
|
599
652
|
"spec": {
|
|
600
|
-
"functions": [
|
|
653
|
+
"functions": [
|
|
654
|
+
{
|
|
655
|
+
"url": function_file_name,
|
|
656
|
+
"name": func.metadata.name,
|
|
657
|
+
},
|
|
658
|
+
],
|
|
601
659
|
},
|
|
602
660
|
}
|
|
603
661
|
)
|
|
@@ -650,22 +708,45 @@ def _load_project_from_db(url, secrets, user_project=False):
|
|
|
650
708
|
|
|
651
709
|
def _delete_project_from_db(project_name, secrets, deletion_strategy):
|
|
652
710
|
db = mlrun.db.get_run_db(secrets=secrets)
|
|
653
|
-
|
|
711
|
+
db.delete_project(project_name, deletion_strategy=deletion_strategy)
|
|
654
712
|
|
|
655
713
|
|
|
656
|
-
def _load_project_file(url, name="", secrets=None):
|
|
714
|
+
def _load_project_file(url, name="", secrets=None, allow_cross_project=None):
|
|
657
715
|
try:
|
|
658
716
|
obj = get_object(url, secrets)
|
|
659
717
|
except FileNotFoundError as exc:
|
|
660
718
|
raise FileNotFoundError(f"cant find project file at {url}") from exc
|
|
661
719
|
struct = yaml.load(obj, Loader=yaml.FullLoader)
|
|
662
|
-
return _project_instance_from_struct(struct, name)
|
|
663
|
-
|
|
720
|
+
return _project_instance_from_struct(struct, name, allow_cross_project)
|
|
721
|
+
|
|
722
|
+
|
|
723
|
+
def _project_instance_from_struct(struct, name, allow_cross_project):
|
|
724
|
+
name_from_struct = struct.get("metadata", {}).get("name", "")
|
|
725
|
+
if name and name_from_struct and name_from_struct != name:
|
|
726
|
+
error_message = (
|
|
727
|
+
f"Project name mismatch, {name_from_struct} != {name}, project is loaded from {name_from_struct} "
|
|
728
|
+
f"project yaml. To prevent/allow this, you can take one of the following actions:\n"
|
|
729
|
+
"1. Set the `allow_cross_project=True` when loading the project.\n"
|
|
730
|
+
f"2. Delete the existing project yaml, or ensure its name is equal to {name}.\n"
|
|
731
|
+
"3. Use different project context dir."
|
|
732
|
+
)
|
|
664
733
|
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
734
|
+
if allow_cross_project is None:
|
|
735
|
+
# TODO: Remove this warning in version 1.9.0 and also fix cli to support allow_cross_project
|
|
736
|
+
warnings.warn(
|
|
737
|
+
f"Project {name=} is different than specified on the context's project yaml. "
|
|
738
|
+
"This behavior is deprecated and will not be supported from version 1.9.0."
|
|
739
|
+
)
|
|
740
|
+
logger.warn(error_message)
|
|
741
|
+
elif allow_cross_project:
|
|
742
|
+
logger.debug(
|
|
743
|
+
"Project name is different than specified on the context's project yaml. Overriding.",
|
|
744
|
+
existing_name=name_from_struct,
|
|
745
|
+
overriding_name=name,
|
|
746
|
+
)
|
|
747
|
+
else:
|
|
748
|
+
raise ValueError(error_message)
|
|
749
|
+
struct.setdefault("metadata", {})["name"] = name or name_from_struct
|
|
669
750
|
return MlrunProject.from_dict(struct)
|
|
670
751
|
|
|
671
752
|
|
|
@@ -740,14 +821,15 @@ class ProjectSpec(ModelObj):
|
|
|
740
821
|
origin_url=None,
|
|
741
822
|
goals=None,
|
|
742
823
|
load_source_on_run=None,
|
|
743
|
-
default_requirements: typing.Union[str,
|
|
824
|
+
default_requirements: typing.Union[str, list[str]] = None,
|
|
744
825
|
desired_state=mlrun.common.schemas.ProjectState.online.value,
|
|
745
826
|
owner=None,
|
|
746
827
|
disable_auto_mount=None,
|
|
747
828
|
workdir=None,
|
|
748
829
|
default_image=None,
|
|
749
830
|
build=None,
|
|
750
|
-
custom_packagers:
|
|
831
|
+
custom_packagers: list[tuple[str, bool]] = None,
|
|
832
|
+
default_function_node_selector=None,
|
|
751
833
|
):
|
|
752
834
|
self.repo = None
|
|
753
835
|
|
|
@@ -787,6 +869,7 @@ class ProjectSpec(ModelObj):
|
|
|
787
869
|
# in a tuple where the first index is the packager module's path (str) and the second is a flag (bool) for
|
|
788
870
|
# whether it is mandatory for a run (raise exception on collection error) or not.
|
|
789
871
|
self.custom_packagers = custom_packagers or []
|
|
872
|
+
self._default_function_node_selector = default_function_node_selector or None
|
|
790
873
|
|
|
791
874
|
@property
|
|
792
875
|
def source(self) -> str:
|
|
@@ -864,14 +947,14 @@ class ProjectSpec(ModelObj):
|
|
|
864
947
|
del self._function_definitions[name]
|
|
865
948
|
|
|
866
949
|
@property
|
|
867
|
-
def workflows(self) ->
|
|
950
|
+
def workflows(self) -> list[dict]:
|
|
868
951
|
"""
|
|
869
952
|
:returns: list of workflows specs dicts used in this project
|
|
870
953
|
"""
|
|
871
954
|
return [workflow.to_dict() for workflow in self._workflows.values()]
|
|
872
955
|
|
|
873
956
|
@workflows.setter
|
|
874
|
-
def workflows(self, workflows:
|
|
957
|
+
def workflows(self, workflows: list[typing.Union[dict, WorkflowSpec]]):
|
|
875
958
|
if not workflows:
|
|
876
959
|
workflows = []
|
|
877
960
|
if not isinstance(workflows, list):
|
|
@@ -922,19 +1005,29 @@ class ProjectSpec(ModelObj):
|
|
|
922
1005
|
|
|
923
1006
|
artifacts_dict = {}
|
|
924
1007
|
for artifact in artifacts:
|
|
925
|
-
|
|
1008
|
+
invalid_object_type = not isinstance(artifact, dict) and not hasattr(
|
|
1009
|
+
artifact, "to_dict"
|
|
1010
|
+
)
|
|
1011
|
+
is_artifact_model = not isinstance(artifact, dict) and hasattr(
|
|
1012
|
+
artifact, "to_dict"
|
|
1013
|
+
)
|
|
1014
|
+
|
|
1015
|
+
if invalid_object_type:
|
|
926
1016
|
raise ValueError("artifacts must be a dict or class")
|
|
927
|
-
|
|
928
|
-
# Support legacy artifacts
|
|
929
|
-
if is_legacy_artifact(artifact) or _is_imported_artifact(artifact):
|
|
930
|
-
key = artifact.get("key")
|
|
931
|
-
else:
|
|
932
|
-
key = artifact.get("metadata").get("key", "")
|
|
933
|
-
if not key:
|
|
934
|
-
raise ValueError('artifacts "key" must be specified')
|
|
935
|
-
else:
|
|
1017
|
+
elif is_artifact_model:
|
|
936
1018
|
key = artifact.key
|
|
937
1019
|
artifact = artifact.to_dict()
|
|
1020
|
+
else: # artifact is a dict
|
|
1021
|
+
# imported/legacy artifacts don't have metadata,spec,status fields
|
|
1022
|
+
key_field = (
|
|
1023
|
+
"key"
|
|
1024
|
+
if _is_imported_artifact(artifact)
|
|
1025
|
+
or mlrun.utils.is_legacy_artifact(artifact)
|
|
1026
|
+
else "metadata.key"
|
|
1027
|
+
)
|
|
1028
|
+
key = mlrun.utils.get_in(artifact, key_field, "")
|
|
1029
|
+
if not key:
|
|
1030
|
+
raise ValueError(f'artifacts "{key_field}" must be specified')
|
|
938
1031
|
|
|
939
1032
|
artifacts_dict[key] = artifact
|
|
940
1033
|
|
|
@@ -951,6 +1044,14 @@ class ProjectSpec(ModelObj):
|
|
|
951
1044
|
if key in self._artifacts:
|
|
952
1045
|
del self._artifacts[key]
|
|
953
1046
|
|
|
1047
|
+
@property
|
|
1048
|
+
def default_function_node_selector(self):
|
|
1049
|
+
return self._default_function_node_selector
|
|
1050
|
+
|
|
1051
|
+
@default_function_node_selector.setter
|
|
1052
|
+
def default_function_node_selector(self, node_selector: dict[str, str]):
|
|
1053
|
+
self._default_function_node_selector = deepcopy(node_selector)
|
|
1054
|
+
|
|
954
1055
|
@property
|
|
955
1056
|
def build(self) -> ImageBuilder:
|
|
956
1057
|
return self._build
|
|
@@ -987,7 +1088,7 @@ class ProjectSpec(ModelObj):
|
|
|
987
1088
|
:raise MLRunInvalidArgumentError: In case the packager was not in the list.
|
|
988
1089
|
"""
|
|
989
1090
|
# Look for the packager tuple in the list to remove it:
|
|
990
|
-
packager_tuple:
|
|
1091
|
+
packager_tuple: tuple[str, bool] = None
|
|
991
1092
|
for custom_packager in self.custom_packagers:
|
|
992
1093
|
if custom_packager[0] == packager:
|
|
993
1094
|
packager_tuple = custom_packager
|
|
@@ -1038,8 +1139,8 @@ class MlrunProject(ModelObj):
|
|
|
1038
1139
|
|
|
1039
1140
|
def __init__(
|
|
1040
1141
|
self,
|
|
1041
|
-
metadata: Optional[Union[ProjectMetadata,
|
|
1042
|
-
spec: Optional[Union[ProjectSpec,
|
|
1142
|
+
metadata: Optional[Union[ProjectMetadata, dict]] = None,
|
|
1143
|
+
spec: Optional[Union[ProjectSpec, dict]] = None,
|
|
1043
1144
|
):
|
|
1044
1145
|
self.metadata: ProjectMetadata = metadata
|
|
1045
1146
|
self.spec: ProjectSpec = spec
|
|
@@ -1208,6 +1309,14 @@ class MlrunProject(ModelObj):
|
|
|
1208
1309
|
def description(self, description):
|
|
1209
1310
|
self.spec.description = description
|
|
1210
1311
|
|
|
1312
|
+
@property
|
|
1313
|
+
def default_function_node_selector(self) -> dict:
|
|
1314
|
+
return self.spec.default_function_node_selector
|
|
1315
|
+
|
|
1316
|
+
@default_function_node_selector.setter
|
|
1317
|
+
def default_function_node_selector(self, default_function_node_selector):
|
|
1318
|
+
self.spec.default_function_node_selector = default_function_node_selector
|
|
1319
|
+
|
|
1211
1320
|
@property
|
|
1212
1321
|
def default_image(self) -> str:
|
|
1213
1322
|
return self.spec.default_image
|
|
@@ -1287,7 +1396,7 @@ class MlrunProject(ModelObj):
|
|
|
1287
1396
|
and not workflow_path.startswith(self.context)
|
|
1288
1397
|
):
|
|
1289
1398
|
workflow_path = path.join(self.context, workflow_path)
|
|
1290
|
-
with open(workflow_path
|
|
1399
|
+
with open(workflow_path) as fp:
|
|
1291
1400
|
txt = fp.read()
|
|
1292
1401
|
workflow = {"name": name, "code": txt}
|
|
1293
1402
|
else:
|
|
@@ -1322,13 +1431,15 @@ class MlrunProject(ModelObj):
|
|
|
1322
1431
|
example::
|
|
1323
1432
|
|
|
1324
1433
|
# register a simple file artifact
|
|
1325
|
-
project.set_artifact(
|
|
1434
|
+
project.set_artifact("data", target_path=data_url)
|
|
1326
1435
|
# register a model artifact
|
|
1327
|
-
project.set_artifact(
|
|
1436
|
+
project.set_artifact(
|
|
1437
|
+
"model", ModelArtifact(model_file="model.pkl"), target_path=model_dir_url
|
|
1438
|
+
)
|
|
1328
1439
|
|
|
1329
1440
|
# register a path to artifact package (will be imported on project load)
|
|
1330
1441
|
# to generate such package use `artifact.export(target_path)`
|
|
1331
|
-
project.set_artifact(
|
|
1442
|
+
project.set_artifact("model", "https://mystuff.com/models/mymodel.zip")
|
|
1332
1443
|
|
|
1333
1444
|
:param key: artifact key/name
|
|
1334
1445
|
:param artifact: mlrun Artifact object/dict (or its subclasses) or path to artifact
|
|
@@ -1363,14 +1474,7 @@ class MlrunProject(ModelObj):
|
|
|
1363
1474
|
artifact_path = mlrun.utils.helpers.template_artifact_path(
|
|
1364
1475
|
self.spec.artifact_path or mlrun.mlconf.artifact_path, self.metadata.name
|
|
1365
1476
|
)
|
|
1366
|
-
|
|
1367
|
-
# we need to maintain the different trees that generated them
|
|
1368
|
-
producer = ArtifactProducer(
|
|
1369
|
-
"project",
|
|
1370
|
-
self.metadata.name,
|
|
1371
|
-
self.metadata.name,
|
|
1372
|
-
tag=self._get_hexsha() or str(uuid.uuid4()),
|
|
1373
|
-
)
|
|
1477
|
+
project_tag = self._get_project_tag()
|
|
1374
1478
|
for artifact_dict in self.spec.artifacts:
|
|
1375
1479
|
if _is_imported_artifact(artifact_dict):
|
|
1376
1480
|
import_from = artifact_dict["import_from"]
|
|
@@ -1390,8 +1494,23 @@ class MlrunProject(ModelObj):
|
|
|
1390
1494
|
artifact.src_path = path.join(
|
|
1391
1495
|
self.spec.get_code_path(), artifact.src_path
|
|
1392
1496
|
)
|
|
1497
|
+
producer, is_retained_producer = self._resolve_artifact_producer(
|
|
1498
|
+
artifact, project_tag
|
|
1499
|
+
)
|
|
1500
|
+
# log the artifact only if it doesn't already exist
|
|
1501
|
+
if (
|
|
1502
|
+
producer.name != self.metadata.name
|
|
1503
|
+
and self._resolve_existing_artifact(
|
|
1504
|
+
artifact,
|
|
1505
|
+
)
|
|
1506
|
+
):
|
|
1507
|
+
continue
|
|
1393
1508
|
artifact_manager.log_artifact(
|
|
1394
|
-
producer,
|
|
1509
|
+
producer,
|
|
1510
|
+
artifact,
|
|
1511
|
+
artifact_path=artifact_path,
|
|
1512
|
+
project=self.metadata.name,
|
|
1513
|
+
is_retained_producer=is_retained_producer,
|
|
1395
1514
|
)
|
|
1396
1515
|
|
|
1397
1516
|
def _get_artifact_manager(self):
|
|
@@ -1414,7 +1533,7 @@ class MlrunProject(ModelObj):
|
|
|
1414
1533
|
self,
|
|
1415
1534
|
url: str,
|
|
1416
1535
|
check_path_in_context: bool = False,
|
|
1417
|
-
) ->
|
|
1536
|
+
) -> tuple[str, bool]:
|
|
1418
1537
|
"""
|
|
1419
1538
|
Get the absolute path of the artifact or function file
|
|
1420
1539
|
:param url: remote url, absolute path or relative path
|
|
@@ -1433,7 +1552,7 @@ class MlrunProject(ModelObj):
|
|
|
1433
1552
|
url = path.normpath(path.join(self.spec.get_code_path(), url))
|
|
1434
1553
|
|
|
1435
1554
|
if (not in_context or check_path_in_context) and not path.isfile(url):
|
|
1436
|
-
raise
|
|
1555
|
+
raise FileNotFoundError(f"{url} not found")
|
|
1437
1556
|
|
|
1438
1557
|
return url, in_context
|
|
1439
1558
|
|
|
@@ -1441,15 +1560,15 @@ class MlrunProject(ModelObj):
|
|
|
1441
1560
|
self,
|
|
1442
1561
|
item,
|
|
1443
1562
|
body=None,
|
|
1444
|
-
tag="",
|
|
1445
|
-
local_path="",
|
|
1446
|
-
artifact_path=None,
|
|
1447
|
-
format=None,
|
|
1448
|
-
upload=None,
|
|
1449
|
-
labels=None,
|
|
1450
|
-
target_path=None,
|
|
1563
|
+
tag: str = "",
|
|
1564
|
+
local_path: str = "",
|
|
1565
|
+
artifact_path: Optional[str] = None,
|
|
1566
|
+
format: Optional[str] = None,
|
|
1567
|
+
upload: Optional[bool] = None,
|
|
1568
|
+
labels: Optional[dict[str, str]] = None,
|
|
1569
|
+
target_path: Optional[str] = None,
|
|
1451
1570
|
**kwargs,
|
|
1452
|
-
):
|
|
1571
|
+
) -> Artifact:
|
|
1453
1572
|
"""Log an output artifact and optionally upload it to datastore
|
|
1454
1573
|
|
|
1455
1574
|
If the artifact already exists with the same key and tag, it will be overwritten.
|
|
@@ -1474,7 +1593,9 @@ class MlrunProject(ModelObj):
|
|
|
1474
1593
|
:param format: artifact file format: csv, png, ..
|
|
1475
1594
|
:param tag: version tag
|
|
1476
1595
|
:param target_path: absolute target path (instead of using artifact_path + local_path)
|
|
1477
|
-
:param upload: upload to datastore
|
|
1596
|
+
:param upload: Whether to upload the artifact to the datastore. If not provided, and the `local_path`
|
|
1597
|
+
is not a directory, upload occurs by default. Directories are uploaded only when this
|
|
1598
|
+
flag is explicitly set to `True`.
|
|
1478
1599
|
:param labels: a set of key/value labels to tag the artifact with
|
|
1479
1600
|
|
|
1480
1601
|
:returns: artifact object
|
|
@@ -1486,12 +1607,20 @@ class MlrunProject(ModelObj):
|
|
|
1486
1607
|
artifact_path = mlrun.utils.helpers.template_artifact_path(
|
|
1487
1608
|
artifact_path, self.metadata.name
|
|
1488
1609
|
)
|
|
1489
|
-
producer =
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
self.
|
|
1493
|
-
|
|
1494
|
-
|
|
1610
|
+
producer, is_retained_producer = self._resolve_artifact_producer(item)
|
|
1611
|
+
if producer.name != self.metadata.name:
|
|
1612
|
+
# the artifact producer is retained, log it only if it doesn't already exist
|
|
1613
|
+
if existing_artifact := self._resolve_existing_artifact(
|
|
1614
|
+
item,
|
|
1615
|
+
tag,
|
|
1616
|
+
):
|
|
1617
|
+
artifact_key = item if isinstance(item, str) else item.key
|
|
1618
|
+
logger.info(
|
|
1619
|
+
"Artifact already exists, skipping logging",
|
|
1620
|
+
key=artifact_key,
|
|
1621
|
+
tag=tag,
|
|
1622
|
+
)
|
|
1623
|
+
return existing_artifact
|
|
1495
1624
|
item = am.log_artifact(
|
|
1496
1625
|
producer,
|
|
1497
1626
|
item,
|
|
@@ -1503,10 +1632,29 @@ class MlrunProject(ModelObj):
|
|
|
1503
1632
|
upload=upload,
|
|
1504
1633
|
labels=labels,
|
|
1505
1634
|
target_path=target_path,
|
|
1635
|
+
project=self.metadata.name,
|
|
1636
|
+
is_retained_producer=is_retained_producer,
|
|
1506
1637
|
**kwargs,
|
|
1507
1638
|
)
|
|
1508
1639
|
return item
|
|
1509
1640
|
|
|
1641
|
+
def delete_artifact(
|
|
1642
|
+
self,
|
|
1643
|
+
item: Artifact,
|
|
1644
|
+
deletion_strategy: mlrun.common.schemas.artifact.ArtifactsDeletionStrategies = (
|
|
1645
|
+
mlrun.common.schemas.artifact.ArtifactsDeletionStrategies.metadata_only
|
|
1646
|
+
),
|
|
1647
|
+
secrets: dict = None,
|
|
1648
|
+
):
|
|
1649
|
+
"""Delete an artifact object in the DB and optionally delete the artifact data
|
|
1650
|
+
|
|
1651
|
+
:param item: Artifact object (can be any type, such as dataset, model, feature store).
|
|
1652
|
+
:param deletion_strategy: The artifact deletion strategy types.
|
|
1653
|
+
:param secrets: Credentials needed to access the artifact data.
|
|
1654
|
+
"""
|
|
1655
|
+
am = self._get_artifact_manager()
|
|
1656
|
+
am.delete_artifact(item, deletion_strategy, secrets)
|
|
1657
|
+
|
|
1510
1658
|
def log_dataset(
|
|
1511
1659
|
self,
|
|
1512
1660
|
key,
|
|
@@ -1521,7 +1669,7 @@ class MlrunProject(ModelObj):
|
|
|
1521
1669
|
stats=None,
|
|
1522
1670
|
target_path="",
|
|
1523
1671
|
extra_data=None,
|
|
1524
|
-
label_column: str = None,
|
|
1672
|
+
label_column: Optional[str] = None,
|
|
1525
1673
|
**kwargs,
|
|
1526
1674
|
) -> DatasetArtifact:
|
|
1527
1675
|
"""
|
|
@@ -1537,7 +1685,9 @@ class MlrunProject(ModelObj):
|
|
|
1537
1685
|
"age": [42, 52, 36, 24, 73],
|
|
1538
1686
|
"testScore": [25, 94, 57, 62, 70],
|
|
1539
1687
|
}
|
|
1540
|
-
df = pd.DataFrame(
|
|
1688
|
+
df = pd.DataFrame(
|
|
1689
|
+
raw_data, columns=["first_name", "last_name", "age", "testScore"]
|
|
1690
|
+
)
|
|
1541
1691
|
project.log_dataset("mydf", df=df, stats=True)
|
|
1542
1692
|
|
|
1543
1693
|
:param key: artifact key
|
|
@@ -1596,28 +1746,31 @@ class MlrunProject(ModelObj):
|
|
|
1596
1746
|
artifact_path=None,
|
|
1597
1747
|
upload=None,
|
|
1598
1748
|
labels=None,
|
|
1599
|
-
inputs:
|
|
1600
|
-
outputs:
|
|
1601
|
-
feature_vector: str = None,
|
|
1602
|
-
feature_weights: list = None,
|
|
1749
|
+
inputs: Optional[list[Feature]] = None,
|
|
1750
|
+
outputs: Optional[list[Feature]] = None,
|
|
1751
|
+
feature_vector: Optional[str] = None,
|
|
1752
|
+
feature_weights: Optional[list] = None,
|
|
1603
1753
|
training_set=None,
|
|
1604
1754
|
label_column=None,
|
|
1605
1755
|
extra_data=None,
|
|
1606
1756
|
**kwargs,
|
|
1607
|
-
):
|
|
1757
|
+
) -> ModelArtifact:
|
|
1608
1758
|
"""Log a model artifact and optionally upload it to datastore
|
|
1609
1759
|
|
|
1610
1760
|
If the model already exists with the same key and tag, it will be overwritten.
|
|
1611
1761
|
|
|
1612
1762
|
example::
|
|
1613
1763
|
|
|
1614
|
-
project.log_model(
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1764
|
+
project.log_model(
|
|
1765
|
+
"model",
|
|
1766
|
+
body=dumps(model),
|
|
1767
|
+
model_file="model.pkl",
|
|
1768
|
+
metrics=context.results,
|
|
1769
|
+
training_set=training_df,
|
|
1770
|
+
label_column="label",
|
|
1771
|
+
feature_vector=feature_vector_uri,
|
|
1772
|
+
labels={"app": "fraud"},
|
|
1773
|
+
)
|
|
1621
1774
|
|
|
1622
1775
|
:param key: artifact key or artifact class ()
|
|
1623
1776
|
:param body: will use the body as the artifact content
|
|
@@ -1721,20 +1874,22 @@ class MlrunProject(ModelObj):
|
|
|
1721
1874
|
with tempfile.TemporaryDirectory() as temp_dir:
|
|
1722
1875
|
with zipfile.ZipFile(item_file, "r") as zf:
|
|
1723
1876
|
zf.extractall(temp_dir)
|
|
1724
|
-
with open(f"{temp_dir}/_spec.yaml"
|
|
1877
|
+
with open(f"{temp_dir}/_spec.yaml") as fp:
|
|
1725
1878
|
data = fp.read()
|
|
1726
1879
|
spec = yaml.load(data, Loader=yaml.FullLoader)
|
|
1727
1880
|
artifact = get_artifact(spec)
|
|
1728
1881
|
with open(f"{temp_dir}/_body", "rb") as fp:
|
|
1729
1882
|
artifact.spec._body = fp.read()
|
|
1730
|
-
artifact.target_path = ""
|
|
1731
1883
|
|
|
1732
1884
|
# if the dataitem is not a file, it means we downloaded it from a remote source to a temp file,
|
|
1733
1885
|
# so we need to remove it after we're done with it
|
|
1734
1886
|
dataitem.remove_local()
|
|
1735
1887
|
|
|
1736
1888
|
return self.log_artifact(
|
|
1737
|
-
artifact,
|
|
1889
|
+
artifact,
|
|
1890
|
+
local_path=temp_dir,
|
|
1891
|
+
artifact_path=artifact_path,
|
|
1892
|
+
upload=True,
|
|
1738
1893
|
)
|
|
1739
1894
|
|
|
1740
1895
|
else:
|
|
@@ -1752,10 +1907,18 @@ class MlrunProject(ModelObj):
|
|
|
1752
1907
|
"""
|
|
1753
1908
|
context = context or self.spec.context
|
|
1754
1909
|
if context:
|
|
1755
|
-
project = _load_project_dir(
|
|
1910
|
+
project = _load_project_dir(
|
|
1911
|
+
context,
|
|
1912
|
+
self.metadata.name,
|
|
1913
|
+
self.spec.subpath,
|
|
1914
|
+
allow_cross_project=False,
|
|
1915
|
+
)
|
|
1756
1916
|
else:
|
|
1757
1917
|
project = _load_project_file(
|
|
1758
|
-
self.spec.origin_url,
|
|
1918
|
+
self.spec.origin_url,
|
|
1919
|
+
self.metadata.name,
|
|
1920
|
+
self._secrets,
|
|
1921
|
+
allow_cross_project=None,
|
|
1759
1922
|
)
|
|
1760
1923
|
project.spec.source = self.spec.source
|
|
1761
1924
|
project.spec.repo = self.spec.repo
|
|
@@ -1784,22 +1947,29 @@ class MlrunProject(ModelObj):
|
|
|
1784
1947
|
def set_model_monitoring_function(
|
|
1785
1948
|
self,
|
|
1786
1949
|
func: typing.Union[str, mlrun.runtimes.BaseRuntime, None] = None,
|
|
1787
|
-
application_class: typing.Union[
|
|
1950
|
+
application_class: typing.Union[
|
|
1951
|
+
str,
|
|
1952
|
+
mm_app.ModelMonitoringApplicationBase,
|
|
1953
|
+
] = None,
|
|
1788
1954
|
name: str = None,
|
|
1789
1955
|
image: str = None,
|
|
1790
1956
|
handler=None,
|
|
1791
1957
|
with_repo: bool = None,
|
|
1792
1958
|
tag: str = None,
|
|
1793
|
-
requirements: typing.Union[str,
|
|
1959
|
+
requirements: typing.Union[str, list[str]] = None,
|
|
1794
1960
|
requirements_file: str = "",
|
|
1795
1961
|
**application_kwargs,
|
|
1796
1962
|
) -> mlrun.runtimes.BaseRuntime:
|
|
1797
1963
|
"""
|
|
1798
1964
|
Update or add a monitoring function to the project.
|
|
1965
|
+
Note: to deploy the function after linking it to the project,
|
|
1966
|
+
call `fn.deploy()` where `fn` is the object returned by this method.
|
|
1799
1967
|
|
|
1800
1968
|
examples::
|
|
1801
|
-
|
|
1802
|
-
|
|
1969
|
+
|
|
1970
|
+
project.set_model_monitoring_function(
|
|
1971
|
+
name="myApp", application_class="MyApp", image="mlrun/mlrun"
|
|
1972
|
+
)
|
|
1803
1973
|
|
|
1804
1974
|
:param func: Function object or spec/code url, None refers to current Notebook
|
|
1805
1975
|
:param name: Name of the function (under the project), can be specified with a tag to support
|
|
@@ -1814,7 +1984,7 @@ class MlrunProject(ModelObj):
|
|
|
1814
1984
|
will be enriched with the tag value. (i.e. 'function-name:tag')
|
|
1815
1985
|
:param requirements: A list of python packages
|
|
1816
1986
|
:param requirements_file: Path to a python requirements file
|
|
1817
|
-
:param application_class: Name or an Instance of a class that
|
|
1987
|
+
:param application_class: Name or an Instance of a class that implements the monitoring application.
|
|
1818
1988
|
:param application_kwargs: Additional keyword arguments to be passed to the
|
|
1819
1989
|
monitoring application's constructor.
|
|
1820
1990
|
"""
|
|
@@ -1836,16 +2006,6 @@ class MlrunProject(ModelObj):
|
|
|
1836
2006
|
requirements_file,
|
|
1837
2007
|
**application_kwargs,
|
|
1838
2008
|
)
|
|
1839
|
-
models_names = "all"
|
|
1840
|
-
function_object.set_label(
|
|
1841
|
-
mm_constants.ModelMonitoringAppLabel.KEY,
|
|
1842
|
-
mm_constants.ModelMonitoringAppLabel.VAL,
|
|
1843
|
-
)
|
|
1844
|
-
function_object.set_label("models", models_names)
|
|
1845
|
-
|
|
1846
|
-
if not mlrun.mlconf.is_ce_mode():
|
|
1847
|
-
function_object.apply(mlrun.mount_v3io())
|
|
1848
|
-
|
|
1849
2009
|
# save to project spec
|
|
1850
2010
|
self.spec.set_function(resolved_function_name, function_object, func)
|
|
1851
2011
|
|
|
@@ -1854,13 +2014,16 @@ class MlrunProject(ModelObj):
|
|
|
1854
2014
|
def create_model_monitoring_function(
|
|
1855
2015
|
self,
|
|
1856
2016
|
func: str = None,
|
|
1857
|
-
application_class: typing.Union[
|
|
2017
|
+
application_class: typing.Union[
|
|
2018
|
+
str,
|
|
2019
|
+
mm_app.ModelMonitoringApplicationBase,
|
|
2020
|
+
] = None,
|
|
1858
2021
|
name: str = None,
|
|
1859
2022
|
image: str = None,
|
|
1860
2023
|
handler: str = None,
|
|
1861
2024
|
with_repo: bool = None,
|
|
1862
2025
|
tag: str = None,
|
|
1863
|
-
requirements: typing.Union[str,
|
|
2026
|
+
requirements: typing.Union[str, list[str]] = None,
|
|
1864
2027
|
requirements_file: str = "",
|
|
1865
2028
|
**application_kwargs,
|
|
1866
2029
|
) -> mlrun.runtimes.BaseRuntime:
|
|
@@ -1868,8 +2031,10 @@ class MlrunProject(ModelObj):
|
|
|
1868
2031
|
Create a monitoring function object without setting it to the project
|
|
1869
2032
|
|
|
1870
2033
|
examples::
|
|
1871
|
-
|
|
1872
|
-
|
|
2034
|
+
|
|
2035
|
+
project.create_model_monitoring_function(
|
|
2036
|
+
application_class_name="MyApp", image="mlrun/mlrun", name="myApp"
|
|
2037
|
+
)
|
|
1873
2038
|
|
|
1874
2039
|
:param func: Code url, None refers to current Notebook
|
|
1875
2040
|
:param name: Name of the function, can be specified with a tag to support
|
|
@@ -1888,6 +2053,7 @@ class MlrunProject(ModelObj):
|
|
|
1888
2053
|
:param application_kwargs: Additional keyword arguments to be passed to the
|
|
1889
2054
|
monitoring application's constructor.
|
|
1890
2055
|
"""
|
|
2056
|
+
|
|
1891
2057
|
_, function_object, _ = self._instantiate_model_monitoring_function(
|
|
1892
2058
|
func,
|
|
1893
2059
|
application_class,
|
|
@@ -1904,49 +2070,40 @@ class MlrunProject(ModelObj):
|
|
|
1904
2070
|
|
|
1905
2071
|
def _instantiate_model_monitoring_function(
|
|
1906
2072
|
self,
|
|
1907
|
-
func: typing.Union[str, mlrun.runtimes.BaseRuntime] = None,
|
|
1908
|
-
application_class: typing.Union[
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
2073
|
+
func: typing.Union[str, mlrun.runtimes.BaseRuntime, None] = None,
|
|
2074
|
+
application_class: typing.Union[
|
|
2075
|
+
str,
|
|
2076
|
+
mm_app.ModelMonitoringApplicationBase,
|
|
2077
|
+
None,
|
|
2078
|
+
] = None,
|
|
2079
|
+
name: typing.Optional[str] = None,
|
|
2080
|
+
image: typing.Optional[str] = None,
|
|
2081
|
+
handler: typing.Optional[str] = None,
|
|
2082
|
+
with_repo: typing.Optional[bool] = None,
|
|
2083
|
+
tag: typing.Optional[str] = None,
|
|
2084
|
+
requirements: typing.Union[str, list[str], None] = None,
|
|
1915
2085
|
requirements_file: str = "",
|
|
1916
2086
|
**application_kwargs,
|
|
1917
|
-
) ->
|
|
2087
|
+
) -> tuple[str, mlrun.runtimes.BaseRuntime, dict]:
|
|
2088
|
+
import mlrun.model_monitoring.api
|
|
2089
|
+
|
|
1918
2090
|
function_object: RemoteRuntime = None
|
|
1919
2091
|
kind = None
|
|
1920
2092
|
if (isinstance(func, str) or func is None) and application_class is not None:
|
|
1921
|
-
kind =
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
2093
|
+
kind = mlrun.run.RuntimeKinds.serving
|
|
2094
|
+
func = mlrun.model_monitoring.api._create_model_monitoring_function_base(
|
|
2095
|
+
project=self.name,
|
|
2096
|
+
func=func,
|
|
2097
|
+
application_class=application_class,
|
|
1926
2098
|
name=name,
|
|
1927
|
-
project=self.metadata.name,
|
|
1928
|
-
tag=tag,
|
|
1929
|
-
kind=kind,
|
|
1930
2099
|
image=image,
|
|
2100
|
+
tag=tag,
|
|
1931
2101
|
requirements=requirements,
|
|
1932
2102
|
requirements_file=requirements_file,
|
|
2103
|
+
**application_kwargs,
|
|
1933
2104
|
)
|
|
1934
|
-
graph = func.set_topology("flow")
|
|
1935
|
-
if isinstance(application_class, str):
|
|
1936
|
-
first_step = graph.to(
|
|
1937
|
-
class_name=application_class, **application_kwargs
|
|
1938
|
-
)
|
|
1939
|
-
else:
|
|
1940
|
-
first_step = graph.to(class_name=application_class)
|
|
1941
|
-
first_step.to(
|
|
1942
|
-
class_name=PushToMonitoringWriter(
|
|
1943
|
-
project=self.metadata.name,
|
|
1944
|
-
writer_application_name=mm_constants.MonitoringFunctionNames.WRITER,
|
|
1945
|
-
stream_uri=None,
|
|
1946
|
-
),
|
|
1947
|
-
).respond()
|
|
1948
2105
|
elif isinstance(func, str) and isinstance(handler, str):
|
|
1949
|
-
kind =
|
|
2106
|
+
kind = mlrun.run.RuntimeKinds.nuclio
|
|
1950
2107
|
|
|
1951
2108
|
(
|
|
1952
2109
|
resolved_function_name,
|
|
@@ -1964,88 +2121,265 @@ class MlrunProject(ModelObj):
|
|
|
1964
2121
|
requirements,
|
|
1965
2122
|
requirements_file,
|
|
1966
2123
|
)
|
|
1967
|
-
models_names = "all"
|
|
1968
2124
|
function_object.set_label(
|
|
1969
2125
|
mm_constants.ModelMonitoringAppLabel.KEY,
|
|
1970
2126
|
mm_constants.ModelMonitoringAppLabel.VAL,
|
|
1971
2127
|
)
|
|
1972
|
-
function_object.set_label("models", models_names)
|
|
1973
2128
|
|
|
1974
2129
|
if not mlrun.mlconf.is_ce_mode():
|
|
1975
2130
|
function_object.apply(mlrun.mount_v3io())
|
|
1976
2131
|
|
|
1977
2132
|
return resolved_function_name, function_object, func
|
|
1978
2133
|
|
|
2134
|
+
def _wait_for_functions_deployment(self, function_names: list[str]) -> None:
|
|
2135
|
+
"""
|
|
2136
|
+
Wait for the deployment of functions on the backend.
|
|
2137
|
+
|
|
2138
|
+
:param function_names: A list of function names.
|
|
2139
|
+
"""
|
|
2140
|
+
for fn_name in function_names:
|
|
2141
|
+
fn = typing.cast(RemoteRuntime, self.get_function(key=fn_name))
|
|
2142
|
+
fn._wait_for_function_deployment(db=fn._get_db())
|
|
2143
|
+
|
|
1979
2144
|
def enable_model_monitoring(
|
|
1980
2145
|
self,
|
|
1981
2146
|
default_controller_image: str = "mlrun/mlrun",
|
|
1982
2147
|
base_period: int = 10,
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
2148
|
+
image: str = "mlrun/mlrun",
|
|
2149
|
+
*,
|
|
2150
|
+
deploy_histogram_data_drift_app: bool = True,
|
|
2151
|
+
wait_for_deployment: bool = False,
|
|
2152
|
+
rebuild_images: bool = False,
|
|
2153
|
+
fetch_credentials_from_sys_config: bool = False,
|
|
2154
|
+
) -> None:
|
|
2155
|
+
"""
|
|
2156
|
+
Deploy model monitoring application controller, writer and stream functions.
|
|
2157
|
+
While the main goal of the controller function is to handle the monitoring processing and triggering
|
|
2158
|
+
applications, the goal of the model monitoring writer function is to write all the monitoring
|
|
2159
|
+
application results to the databases.
|
|
2160
|
+
The stream function goal is to monitor the log of the data stream. It is triggered when a new log entry
|
|
2161
|
+
is detected. It processes the new events into statistics that are then written to statistics databases.
|
|
2162
|
+
|
|
2163
|
+
:param default_controller_image: Deprecated.
|
|
2164
|
+
:param base_period: The time period in minutes in which the model monitoring controller
|
|
2165
|
+
function is triggered. By default, the base period is 10 minutes
|
|
2166
|
+
(which is also the minimum value for production environments).
|
|
2167
|
+
:param image: The image of the model monitoring controller, writer, monitoring
|
|
2168
|
+
stream & histogram data drift functions, which are real time nuclio
|
|
2169
|
+
functions. By default, the image is mlrun/mlrun.
|
|
2170
|
+
:param deploy_histogram_data_drift_app: If true, deploy the default histogram-based data drift application.
|
|
2171
|
+
:param wait_for_deployment: If true, return only after the deployment is done on the backend.
|
|
2172
|
+
Otherwise, deploy the model monitoring infrastructure on the
|
|
2173
|
+
background, including the histogram data drift app if selected.
|
|
2174
|
+
:param rebuild_images: If true, force rebuild of model monitoring infrastructure images.
|
|
2175
|
+
:param fetch_credentials_from_sys_config: If true, fetch the credentials from the system configuration.
|
|
2176
|
+
"""
|
|
2177
|
+
if default_controller_image != "mlrun/mlrun":
|
|
2178
|
+
# TODO: Remove this in 1.9.0
|
|
2179
|
+
warnings.warn(
|
|
2180
|
+
"'default_controller_image' is deprecated and will be removed in 1.9.0, "
|
|
2181
|
+
"use 'image' instead",
|
|
2182
|
+
FutureWarning,
|
|
2183
|
+
)
|
|
2184
|
+
image = default_controller_image
|
|
2185
|
+
if base_period < 10:
|
|
2186
|
+
logger.warn(
|
|
2187
|
+
"enable_model_monitoring: 'base_period' < 10 minutes is not supported in production environments",
|
|
2188
|
+
project=self.name,
|
|
2189
|
+
)
|
|
2190
|
+
|
|
2191
|
+
db = mlrun.db.get_run_db(secrets=self._secrets)
|
|
2192
|
+
db.enable_model_monitoring(
|
|
2193
|
+
project=self.name,
|
|
2194
|
+
image=image,
|
|
2195
|
+
base_period=base_period,
|
|
2196
|
+
deploy_histogram_data_drift_app=deploy_histogram_data_drift_app,
|
|
2197
|
+
rebuild_images=rebuild_images,
|
|
2198
|
+
fetch_credentials_from_sys_config=fetch_credentials_from_sys_config,
|
|
2199
|
+
)
|
|
2200
|
+
|
|
2201
|
+
if wait_for_deployment:
|
|
2202
|
+
deployment_functions = mm_constants.MonitoringFunctionNames.list()
|
|
2203
|
+
if deploy_histogram_data_drift_app:
|
|
2204
|
+
deployment_functions.append(
|
|
2205
|
+
mm_constants.HistogramDataDriftApplicationConstants.NAME
|
|
2206
|
+
)
|
|
2207
|
+
self._wait_for_functions_deployment(deployment_functions)
|
|
2208
|
+
|
|
2209
|
+
def deploy_histogram_data_drift_app(
|
|
2210
|
+
self,
|
|
2211
|
+
*,
|
|
2212
|
+
image: str = "mlrun/mlrun",
|
|
2213
|
+
db: Optional[mlrun.db.RunDBInterface] = None,
|
|
2214
|
+
wait_for_deployment: bool = False,
|
|
2215
|
+
) -> None:
|
|
2216
|
+
"""
|
|
2217
|
+
Deploy the histogram data drift application.
|
|
2218
|
+
|
|
2219
|
+
:param image: The image on which the application will run.
|
|
2220
|
+
:param db: An optional DB object.
|
|
2221
|
+
:param wait_for_deployment: If true, return only after the deployment is done on the backend.
|
|
2222
|
+
Otherwise, deploy the application on the background.
|
|
2223
|
+
"""
|
|
2224
|
+
if db is None:
|
|
2225
|
+
db = mlrun.db.get_run_db(secrets=self._secrets)
|
|
2226
|
+
db.deploy_histogram_data_drift_app(project=self.name, image=image)
|
|
2227
|
+
|
|
2228
|
+
if wait_for_deployment:
|
|
2229
|
+
self._wait_for_functions_deployment(
|
|
2230
|
+
[mm_constants.HistogramDataDriftApplicationConstants.NAME]
|
|
2231
|
+
)
|
|
2232
|
+
|
|
2233
|
+
def update_model_monitoring_controller(
|
|
2234
|
+
self,
|
|
2235
|
+
base_period: int = 10,
|
|
2236
|
+
image: str = "mlrun/mlrun",
|
|
2237
|
+
*,
|
|
2238
|
+
wait_for_deployment: bool = False,
|
|
2239
|
+
) -> None:
|
|
2240
|
+
"""
|
|
2241
|
+
Redeploy model monitoring application controller functions.
|
|
2242
|
+
|
|
2243
|
+
:param base_period: The time period in minutes in which the model monitoring controller function
|
|
2244
|
+
is triggered. By default, the base period is 10 minutes.
|
|
2245
|
+
:param image: The image of the model monitoring controller, writer & monitoring
|
|
2246
|
+
stream functions, which are real time nuclio functions.
|
|
2247
|
+
By default, the image is mlrun/mlrun.
|
|
2248
|
+
:param wait_for_deployment: If true, return only after the deployment is done on the backend.
|
|
2249
|
+
Otherwise, deploy the controller on the background.
|
|
1997
2250
|
"""
|
|
1998
2251
|
db = mlrun.db.get_run_db(secrets=self._secrets)
|
|
1999
|
-
|
|
2252
|
+
db.update_model_monitoring_controller(
|
|
2000
2253
|
project=self.name,
|
|
2001
|
-
default_controller_image=default_controller_image,
|
|
2002
2254
|
base_period=base_period,
|
|
2255
|
+
image=image,
|
|
2003
2256
|
)
|
|
2004
2257
|
|
|
2005
|
-
|
|
2258
|
+
if wait_for_deployment:
|
|
2259
|
+
self._wait_for_functions_deployment(
|
|
2260
|
+
[mm_constants.MonitoringFunctionNames.APPLICATION_CONTROLLER]
|
|
2261
|
+
)
|
|
2262
|
+
|
|
2263
|
+
def disable_model_monitoring(
|
|
2264
|
+
self,
|
|
2265
|
+
*,
|
|
2266
|
+
delete_resources: bool = True,
|
|
2267
|
+
delete_stream_function: bool = False,
|
|
2268
|
+
delete_histogram_data_drift_app: bool = True,
|
|
2269
|
+
delete_user_applications: bool = False,
|
|
2270
|
+
user_application_list: list[str] = None,
|
|
2271
|
+
) -> None:
|
|
2272
|
+
"""
|
|
2273
|
+
Disable model monitoring application controller, writer, stream, histogram data drift application
|
|
2274
|
+
and the user's applications functions, according to the given params.
|
|
2275
|
+
|
|
2276
|
+
:param delete_resources: If True, it would delete the model monitoring controller & writer
|
|
2277
|
+
functions. Default True
|
|
2278
|
+
:param delete_stream_function: If True, it would delete model monitoring stream function,
|
|
2279
|
+
need to use wisely because if you're deleting this function
|
|
2280
|
+
this can cause data loss in case you will want to
|
|
2281
|
+
enable the model monitoring capability to the project.
|
|
2282
|
+
Default False.
|
|
2283
|
+
:param delete_histogram_data_drift_app: If True, it would delete the default histogram-based data drift
|
|
2284
|
+
application. Default False.
|
|
2285
|
+
:param delete_user_applications: If True, it would delete the user's model monitoring
|
|
2286
|
+
application according to user_application_list, Default False.
|
|
2287
|
+
:param user_application_list: List of the user's model monitoring application to disable.
|
|
2288
|
+
Default all the applications.
|
|
2289
|
+
Note: you have to set delete_user_applications to True
|
|
2290
|
+
in order to delete the desired application.
|
|
2291
|
+
"""
|
|
2292
|
+
if not delete_user_applications and user_application_list:
|
|
2293
|
+
raise mlrun.errors.MLRunInvalidArgumentError(
|
|
2294
|
+
"user_application_list can be specified only if delete_user_applications is set to True"
|
|
2295
|
+
)
|
|
2296
|
+
|
|
2006
2297
|
db = mlrun.db.get_run_db(secrets=self._secrets)
|
|
2007
|
-
db.
|
|
2298
|
+
succeed = db.disable_model_monitoring(
|
|
2008
2299
|
project=self.name,
|
|
2009
|
-
|
|
2300
|
+
delete_resources=delete_resources,
|
|
2301
|
+
delete_stream_function=delete_stream_function,
|
|
2302
|
+
delete_histogram_data_drift_app=delete_histogram_data_drift_app,
|
|
2303
|
+
delete_user_applications=delete_user_applications,
|
|
2304
|
+
user_application_list=user_application_list,
|
|
2010
2305
|
)
|
|
2306
|
+
if succeed and delete_resources:
|
|
2307
|
+
if delete_resources:
|
|
2308
|
+
logger.info("Model Monitoring disabled", project=self.name)
|
|
2309
|
+
if delete_user_applications:
|
|
2310
|
+
logger.info(
|
|
2311
|
+
"All the desired monitoring application were deleted",
|
|
2312
|
+
project=self.name,
|
|
2313
|
+
)
|
|
2314
|
+
else:
|
|
2315
|
+
if delete_resources:
|
|
2316
|
+
logger.info(
|
|
2317
|
+
"Model Monitoring was not disabled properly", project=self.name
|
|
2318
|
+
)
|
|
2319
|
+
if delete_user_applications:
|
|
2320
|
+
logger.info(
|
|
2321
|
+
"Some of the desired monitoring application were not deleted",
|
|
2322
|
+
project=self.name,
|
|
2323
|
+
)
|
|
2011
2324
|
|
|
2012
2325
|
def set_function(
|
|
2013
2326
|
self,
|
|
2014
2327
|
func: typing.Union[str, mlrun.runtimes.BaseRuntime] = None,
|
|
2015
2328
|
name: str = "",
|
|
2016
|
-
kind: str = "",
|
|
2329
|
+
kind: str = "job",
|
|
2017
2330
|
image: str = None,
|
|
2018
2331
|
handler: str = None,
|
|
2019
2332
|
with_repo: bool = None,
|
|
2020
2333
|
tag: str = None,
|
|
2021
|
-
requirements: typing.Union[str,
|
|
2334
|
+
requirements: typing.Union[str, list[str]] = None,
|
|
2022
2335
|
requirements_file: str = "",
|
|
2023
2336
|
) -> mlrun.runtimes.BaseRuntime:
|
|
2024
|
-
"""
|
|
2337
|
+
"""
|
|
2338
|
+
| Update or add a function object to the project.
|
|
2339
|
+
| Function can be provided as an object (func) or a .py/.ipynb/.yaml URL.
|
|
2025
2340
|
|
|
2026
|
-
function
|
|
2027
|
-
|
|
2341
|
+
| Creating a function from a single file is done by specifying ``func`` and disabling ``with_repo``.
|
|
2342
|
+
| Creating a function with project source (specify ``with_repo=True``):
|
|
2343
|
+
| 1. Specify a relative ``func`` path.
|
|
2344
|
+
| 2. Specify a module ``handler`` (e.g. ``handler=package.package.func``) without ``func``.
|
|
2345
|
+
| Creating a function with non project source is done by specifying a module ``handler`` and on the
|
|
2346
|
+
returned function set the source with ``function.with_source_archive(<source>)``.
|
|
2028
2347
|
|
|
2029
|
-
|
|
2030
|
-
MLRun DB e.g. db://project/func:ver
|
|
2031
|
-
functions hub/market: e.g. hub://auto-trainer:master
|
|
2348
|
+
Support URL prefixes:
|
|
2032
2349
|
|
|
2033
|
-
|
|
2350
|
+
| Object (s3://, v3io://, ..)
|
|
2351
|
+
| MLRun DB e.g. db://project/func:ver
|
|
2352
|
+
| Functions hub/market: e.g. hub://auto-trainer:master
|
|
2353
|
+
|
|
2354
|
+
Examples::
|
|
2034
2355
|
|
|
2035
2356
|
proj.set_function(func_object)
|
|
2036
|
-
proj.set_function(
|
|
2037
|
-
|
|
2038
|
-
proj.set_function(
|
|
2039
|
-
proj.set_function('./func.yaml')
|
|
2040
|
-
proj.set_function('hub://get_toy_data', 'getdata')
|
|
2357
|
+
proj.set_function("http://.../mynb.ipynb", "train")
|
|
2358
|
+
proj.set_function("./func.yaml")
|
|
2359
|
+
proj.set_function("hub://get_toy_data", "getdata")
|
|
2041
2360
|
|
|
2042
|
-
#
|
|
2361
|
+
# Create a function from a single file
|
|
2362
|
+
proj.set_function("./src/mycode.py", "ingest")
|
|
2043
2363
|
|
|
2044
|
-
#
|
|
2045
|
-
proj.set_function(
|
|
2364
|
+
# Creating a function with project source
|
|
2365
|
+
proj.set_function(
|
|
2366
|
+
"./src/mycode.py", "ingest", image="myrepo/ing:latest", with_repo=True
|
|
2367
|
+
)
|
|
2368
|
+
proj.set_function("ingest", handler="package.package.func", with_repo=True)
|
|
2369
|
+
|
|
2370
|
+
# Creating a function with non project source
|
|
2371
|
+
func = proj.set_function(
|
|
2372
|
+
"ingest", handler="package.package.func", with_repo=False
|
|
2373
|
+
)
|
|
2374
|
+
func.with_source_archive("git://github.com/mlrun/something.git")
|
|
2046
2375
|
|
|
2047
|
-
#
|
|
2048
|
-
|
|
2376
|
+
# Set function requirements
|
|
2377
|
+
|
|
2378
|
+
# By providing a list of packages
|
|
2379
|
+
proj.set_function("my.py", requirements=["requests", "pandas"])
|
|
2380
|
+
|
|
2381
|
+
# By providing a path to a pip requirements file
|
|
2382
|
+
proj.set_function("my.py", requirements="requirements.txt")
|
|
2049
2383
|
|
|
2050
2384
|
:param func: Function object or spec/code url, None refers to current Notebook
|
|
2051
2385
|
:param name: Name of the function (under the project), can be specified with a tag to support
|
|
@@ -2056,14 +2390,15 @@ class MlrunProject(ModelObj):
|
|
|
2056
2390
|
Default: job
|
|
2057
2391
|
:param image: Docker image to be used, can also be specified in the function object/yaml
|
|
2058
2392
|
:param handler: Default function handler to invoke (can only be set with .py/.ipynb files)
|
|
2059
|
-
:param with_repo: Add (clone) the current repo to the build source
|
|
2393
|
+
:param with_repo: Add (clone) the current repo to the build source - use when the function code is in
|
|
2394
|
+
the project repo (project.spec.source).
|
|
2060
2395
|
:param tag: Function version tag to set (none for current or 'latest')
|
|
2061
2396
|
Specifying a tag as a parameter will update the project's tagged function
|
|
2062
2397
|
(myfunc:v1) and the untagged function (myfunc)
|
|
2063
2398
|
:param requirements: A list of python packages
|
|
2064
2399
|
:param requirements_file: Path to a python requirements file
|
|
2065
2400
|
|
|
2066
|
-
:returns:
|
|
2401
|
+
:returns: :py:class:`~mlrun.runtimes.BaseRuntime`
|
|
2067
2402
|
"""
|
|
2068
2403
|
(
|
|
2069
2404
|
resolved_function_name,
|
|
@@ -2094,22 +2429,23 @@ class MlrunProject(ModelObj):
|
|
|
2094
2429
|
handler: str = None,
|
|
2095
2430
|
with_repo: bool = None,
|
|
2096
2431
|
tag: str = None,
|
|
2097
|
-
requirements: typing.Union[str,
|
|
2432
|
+
requirements: typing.Union[str, list[str]] = None,
|
|
2098
2433
|
requirements_file: str = "",
|
|
2099
|
-
) ->
|
|
2100
|
-
if
|
|
2434
|
+
) -> tuple[str, str, mlrun.runtimes.BaseRuntime, dict]:
|
|
2435
|
+
if (
|
|
2436
|
+
func is None
|
|
2437
|
+
and not _has_module(handler, kind)
|
|
2438
|
+
and mlrun.runtimes.RuntimeKinds.supports_from_notebook(kind)
|
|
2439
|
+
):
|
|
2101
2440
|
# if function path is not provided and it is not a module (no ".")
|
|
2102
2441
|
# use the current notebook as default
|
|
2103
|
-
if
|
|
2104
|
-
|
|
2105
|
-
"Function path or module must be specified (when not running inside a Notebook)"
|
|
2106
|
-
)
|
|
2107
|
-
from IPython import get_ipython
|
|
2442
|
+
if is_jupyter:
|
|
2443
|
+
from IPython import get_ipython
|
|
2108
2444
|
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2445
|
+
kernel = get_ipython()
|
|
2446
|
+
func = nuclio.utils.notebook_file_name(kernel)
|
|
2447
|
+
if func.startswith(path.abspath(self.spec.context)):
|
|
2448
|
+
func = path.relpath(func, self.spec.context)
|
|
2113
2449
|
|
|
2114
2450
|
func = func or ""
|
|
2115
2451
|
|
|
@@ -2205,22 +2541,39 @@ class MlrunProject(ModelObj):
|
|
|
2205
2541
|
"""
|
|
2206
2542
|
self.spec.remove_function(name)
|
|
2207
2543
|
|
|
2208
|
-
def remove_model_monitoring_function(self, name):
|
|
2209
|
-
"""
|
|
2544
|
+
def remove_model_monitoring_function(self, name: Union[str, list[str]]):
|
|
2545
|
+
"""delete the specified model-monitoring-app function/s
|
|
2210
2546
|
|
|
2211
|
-
:param name: name of the model-monitoring-
|
|
2547
|
+
:param name: name of the model-monitoring-function/s (under the project)
|
|
2212
2548
|
"""
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2549
|
+
# TODO: Remove this in 1.9.0
|
|
2550
|
+
warnings.warn(
|
|
2551
|
+
"'remove_model_monitoring_function' is deprecated and will be removed in 1.9.0. "
|
|
2552
|
+
"Please use `delete_model_monitoring_function` instead.",
|
|
2553
|
+
FutureWarning,
|
|
2554
|
+
)
|
|
2555
|
+
self.delete_model_monitoring_function(name)
|
|
2556
|
+
|
|
2557
|
+
def delete_model_monitoring_function(self, name: Union[str, list[str]]):
|
|
2558
|
+
"""delete the specified model-monitoring-app function/s
|
|
2559
|
+
|
|
2560
|
+
:param name: name of the model-monitoring-function/s (under the project)
|
|
2561
|
+
"""
|
|
2562
|
+
db = mlrun.db.get_run_db(secrets=self._secrets)
|
|
2563
|
+
succeed = db.delete_model_monitoring_function(
|
|
2564
|
+
project=self.name,
|
|
2565
|
+
functions=name if isinstance(name, list) else [name],
|
|
2566
|
+
)
|
|
2567
|
+
if succeed:
|
|
2568
|
+
logger.info(
|
|
2569
|
+
"All the desired monitoring functions were deleted",
|
|
2570
|
+
project=self.name,
|
|
2571
|
+
functions=name,
|
|
2572
|
+
)
|
|
2221
2573
|
else:
|
|
2222
|
-
|
|
2223
|
-
|
|
2574
|
+
logger.info(
|
|
2575
|
+
"Some of the desired monitoring functions were not deleted",
|
|
2576
|
+
project=self.name,
|
|
2224
2577
|
)
|
|
2225
2578
|
|
|
2226
2579
|
def get_function(
|
|
@@ -2293,7 +2646,7 @@ class MlrunProject(ModelObj):
|
|
|
2293
2646
|
self.sync_functions()
|
|
2294
2647
|
return FunctionsDict(self)
|
|
2295
2648
|
|
|
2296
|
-
def get_function_names(self) ->
|
|
2649
|
+
def get_function_names(self) -> list[str]:
|
|
2297
2650
|
"""get a list of all the project function names"""
|
|
2298
2651
|
return [func["name"] for func in self.spec.functions]
|
|
2299
2652
|
|
|
@@ -2326,13 +2679,47 @@ class MlrunProject(ModelObj):
|
|
|
2326
2679
|
clone_zip(url, self.spec.context, self._secrets)
|
|
2327
2680
|
|
|
2328
2681
|
def create_remote(self, url, name="origin", branch=None):
|
|
2329
|
-
"""
|
|
2682
|
+
"""Create remote for the project git
|
|
2683
|
+
|
|
2684
|
+
This method creates a new remote repository associated with the project's Git repository.
|
|
2685
|
+
If a remote with the specified name already exists, it will not be overwritten.
|
|
2686
|
+
|
|
2687
|
+
If you wish to update the URL of an existing remote, use the `set_remote` method instead.
|
|
2330
2688
|
|
|
2331
2689
|
:param url: remote git url
|
|
2332
2690
|
:param name: name for the remote (default is 'origin')
|
|
2333
2691
|
:param branch: Git branch to use as source
|
|
2334
2692
|
"""
|
|
2693
|
+
self.set_remote(url, name=name, branch=branch, overwrite=False)
|
|
2694
|
+
|
|
2695
|
+
def set_remote(self, url, name="origin", branch=None, overwrite=True):
|
|
2696
|
+
"""Create or update a remote for the project git repository.
|
|
2697
|
+
|
|
2698
|
+
This method allows you to manage remote repositories associated with the project.
|
|
2699
|
+
It checks if a remote with the specified name already exists.
|
|
2700
|
+
|
|
2701
|
+
If a remote with the same name does not exist, it will be created.
|
|
2702
|
+
If a remote with the same name already exists,
|
|
2703
|
+
the behavior depends on the value of the 'overwrite' flag.
|
|
2704
|
+
|
|
2705
|
+
:param url: remote git url
|
|
2706
|
+
:param name: name for the remote (default is 'origin')
|
|
2707
|
+
:param branch: Git branch to use as source
|
|
2708
|
+
:param overwrite: if True (default), updates the existing remote with the given URL if it already exists.
|
|
2709
|
+
if False, raises an error when attempting to create a remote with a name that already exists.
|
|
2710
|
+
:raises MLRunConflictError: If a remote with the same name already exists and overwrite
|
|
2711
|
+
is set to False.
|
|
2712
|
+
"""
|
|
2335
2713
|
self._ensure_git_repo()
|
|
2714
|
+
if self._remote_exists(name):
|
|
2715
|
+
if overwrite:
|
|
2716
|
+
self.spec.repo.delete_remote(name)
|
|
2717
|
+
else:
|
|
2718
|
+
raise mlrun.errors.MLRunConflictError(
|
|
2719
|
+
f"Remote '{name}' already exists in the project, "
|
|
2720
|
+
f"each remote in the project must have a unique name."
|
|
2721
|
+
"Use 'set_remote' with 'override=True' inorder to update the remote, or choose a different name."
|
|
2722
|
+
)
|
|
2336
2723
|
self.spec.repo.create_remote(name, url=url)
|
|
2337
2724
|
url = url.replace("https://", "git://")
|
|
2338
2725
|
if not branch:
|
|
@@ -2345,6 +2732,22 @@ class MlrunProject(ModelObj):
|
|
|
2345
2732
|
self.spec._source = self.spec.source or url
|
|
2346
2733
|
self.spec.origin_url = self.spec.origin_url or url
|
|
2347
2734
|
|
|
2735
|
+
def remove_remote(self, name):
|
|
2736
|
+
"""Remove a remote from the project's Git repository.
|
|
2737
|
+
|
|
2738
|
+
This method removes the remote repository associated with the specified name from the project's Git repository.
|
|
2739
|
+
|
|
2740
|
+
:param name: Name of the remote to remove.
|
|
2741
|
+
"""
|
|
2742
|
+
if self._remote_exists(name):
|
|
2743
|
+
self.spec.repo.delete_remote(name)
|
|
2744
|
+
else:
|
|
2745
|
+
logger.warning(f"The remote '{name}' does not exist. Nothing to remove.")
|
|
2746
|
+
|
|
2747
|
+
def _remote_exists(self, name):
|
|
2748
|
+
"""Check if a remote with the given name already exists"""
|
|
2749
|
+
return any(remote.name == name for remote in self.spec.repo.remotes)
|
|
2750
|
+
|
|
2348
2751
|
def _ensure_git_repo(self):
|
|
2349
2752
|
if self.spec.repo:
|
|
2350
2753
|
return
|
|
@@ -2427,37 +2830,104 @@ class MlrunProject(ModelObj):
|
|
|
2427
2830
|
secrets=secrets or {},
|
|
2428
2831
|
)
|
|
2429
2832
|
|
|
2430
|
-
def sync_functions(
|
|
2431
|
-
|
|
2833
|
+
def sync_functions(
|
|
2834
|
+
self,
|
|
2835
|
+
names: list = None,
|
|
2836
|
+
always: bool = True,
|
|
2837
|
+
save: bool = False,
|
|
2838
|
+
silent: bool = False,
|
|
2839
|
+
):
|
|
2840
|
+
"""
|
|
2841
|
+
Reload function objects from specs and files.
|
|
2842
|
+
The function objects are synced against the definitions spec in `self.spec._function_definitions`.
|
|
2843
|
+
Referenced files/URLs in the function spec will be reloaded.
|
|
2844
|
+
Function definitions are parsed by the following precedence:
|
|
2845
|
+
|
|
2846
|
+
1. Contains runtime spec.
|
|
2847
|
+
2. Contains module in the project's context.
|
|
2848
|
+
3. Contains path to function definition (yaml, DB, Hub).
|
|
2849
|
+
4. Contains path to .ipynb or .py files.
|
|
2850
|
+
5. Contains a Nuclio/Serving function image / an 'Application' kind definition.
|
|
2851
|
+
|
|
2852
|
+
If function definition is already an object, some project metadata updates will apply however,
|
|
2853
|
+
it will not be reloaded.
|
|
2854
|
+
|
|
2855
|
+
:param names: Names of functions to reload, defaults to `self.spec._function_definitions.keys()`.
|
|
2856
|
+
:param always: Force reloading the functions.
|
|
2857
|
+
:param save: Whether to save the loaded functions or not.
|
|
2858
|
+
:param silent: Whether to raise an exception when a function fails to load.
|
|
2859
|
+
|
|
2860
|
+
:returns: Dictionary of function objects
|
|
2861
|
+
"""
|
|
2432
2862
|
if self._initialized and not always:
|
|
2433
2863
|
return self.spec._function_objects
|
|
2434
2864
|
|
|
2435
|
-
|
|
2865
|
+
functions = self.spec._function_objects
|
|
2436
2866
|
if not names:
|
|
2437
2867
|
names = self.spec._function_definitions.keys()
|
|
2438
|
-
|
|
2868
|
+
functions = {}
|
|
2869
|
+
|
|
2439
2870
|
origin = mlrun.runtimes.utils.add_code_metadata(self.spec.context)
|
|
2440
2871
|
for name in names:
|
|
2441
|
-
|
|
2442
|
-
if not
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2872
|
+
function_definition = self.spec._function_definitions.get(name)
|
|
2873
|
+
if not function_definition:
|
|
2874
|
+
if silent:
|
|
2875
|
+
logger.warn(
|
|
2876
|
+
"Function definition was not found, skipping reload", name=name
|
|
2877
|
+
)
|
|
2878
|
+
continue
|
|
2879
|
+
|
|
2880
|
+
raise ValueError(f"Function named {name} not found")
|
|
2881
|
+
|
|
2882
|
+
function_object = self.spec._function_objects.get(name, None)
|
|
2883
|
+
is_base_runtime = isinstance(
|
|
2884
|
+
function_object, mlrun.runtimes.base.BaseRuntime
|
|
2885
|
+
)
|
|
2886
|
+
# If this function is already available locally, don't recreate it unless always=True
|
|
2887
|
+
if is_base_runtime and not always:
|
|
2888
|
+
functions[name] = function_object
|
|
2889
|
+
continue
|
|
2890
|
+
|
|
2891
|
+
# Reload the function
|
|
2892
|
+
if hasattr(function_definition, "to_dict"):
|
|
2893
|
+
name, func = _init_function_from_obj(function_definition, self, name)
|
|
2894
|
+
elif isinstance(function_definition, dict):
|
|
2449
2895
|
try:
|
|
2450
|
-
name, func = _init_function_from_dict(
|
|
2896
|
+
name, func = _init_function_from_dict(
|
|
2897
|
+
function_definition, self, name
|
|
2898
|
+
)
|
|
2451
2899
|
except FileNotFoundError as exc:
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2900
|
+
message = f"File {exc.filename} not found while syncing project functions."
|
|
2901
|
+
if silent:
|
|
2902
|
+
message += " Skipping function reload"
|
|
2903
|
+
logger.warn(message, name=name)
|
|
2904
|
+
continue
|
|
2905
|
+
|
|
2906
|
+
raise mlrun.errors.MLRunMissingDependencyError(message) from exc
|
|
2907
|
+
|
|
2908
|
+
except Exception as exc:
|
|
2909
|
+
if silent:
|
|
2910
|
+
logger.warn(
|
|
2911
|
+
"Failed to instantiate function",
|
|
2912
|
+
name=name,
|
|
2913
|
+
error=mlrun.utils.err_to_str(exc),
|
|
2914
|
+
)
|
|
2915
|
+
continue
|
|
2916
|
+
raise exc
|
|
2917
|
+
else:
|
|
2918
|
+
message = f"Function {name} must be an object or dict."
|
|
2919
|
+
if silent:
|
|
2920
|
+
message += " Skipping function reload"
|
|
2921
|
+
logger.warn(message, name=name)
|
|
2922
|
+
continue
|
|
2923
|
+
raise ValueError(message)
|
|
2924
|
+
|
|
2455
2925
|
func.spec.build.code_origin = origin
|
|
2456
|
-
|
|
2926
|
+
functions[name] = func
|
|
2457
2927
|
if save:
|
|
2458
2928
|
func.save(versioned=False)
|
|
2459
2929
|
|
|
2460
|
-
self.spec._function_objects =
|
|
2930
|
+
self.spec._function_objects = functions
|
|
2461
2931
|
self._initialized = True
|
|
2462
2932
|
return self.spec._function_objects
|
|
2463
2933
|
|
|
@@ -2466,9 +2936,9 @@ class MlrunProject(ModelObj):
|
|
|
2466
2936
|
|
|
2467
2937
|
read secrets from a source provider to be used in workflows, example::
|
|
2468
2938
|
|
|
2469
|
-
proj.with_secrets(
|
|
2470
|
-
proj.with_secrets(
|
|
2471
|
-
proj.with_secrets(
|
|
2939
|
+
proj.with_secrets("file", "file.txt")
|
|
2940
|
+
proj.with_secrets("inline", {"key": "val"})
|
|
2941
|
+
proj.with_secrets("env", "ENV1,ENV2", prefix="PFX_")
|
|
2472
2942
|
|
|
2473
2943
|
Vault secret source has several options::
|
|
2474
2944
|
|
|
@@ -2479,7 +2949,7 @@ class MlrunProject(ModelObj):
|
|
|
2479
2949
|
The 2nd option uses the current project name as context.
|
|
2480
2950
|
Can also use empty secret list::
|
|
2481
2951
|
|
|
2482
|
-
proj.with_secrets(
|
|
2952
|
+
proj.with_secrets("vault", [])
|
|
2483
2953
|
|
|
2484
2954
|
This will enable access to all secrets in vault registered to the current project.
|
|
2485
2955
|
|
|
@@ -2510,17 +2980,20 @@ class MlrunProject(ModelObj):
|
|
|
2510
2980
|
file_path: str = None,
|
|
2511
2981
|
provider: typing.Union[str, mlrun.common.schemas.SecretProviderName] = None,
|
|
2512
2982
|
):
|
|
2513
|
-
"""
|
|
2983
|
+
"""
|
|
2984
|
+
Set project secrets from dict or secrets env file
|
|
2514
2985
|
when using a secrets file it should have lines in the form KEY=VALUE, comment line start with "#"
|
|
2515
2986
|
V3IO paths/credentials and MLrun service API address are dropped from the secrets
|
|
2516
2987
|
|
|
2517
|
-
example secrets file
|
|
2988
|
+
example secrets file:
|
|
2989
|
+
|
|
2990
|
+
.. code-block:: shell
|
|
2518
2991
|
|
|
2519
2992
|
# this is an env file
|
|
2520
|
-
AWS_ACCESS_KEY_ID
|
|
2993
|
+
AWS_ACCESS_KEY_ID=XXXX
|
|
2521
2994
|
AWS_SECRET_ACCESS_KEY=YYYY
|
|
2522
2995
|
|
|
2523
|
-
usage
|
|
2996
|
+
usage:
|
|
2524
2997
|
|
|
2525
2998
|
# read env vars from dict or file and set as project secrets
|
|
2526
2999
|
project.set_secrets({"SECRET1": "value"})
|
|
@@ -2583,7 +3056,7 @@ class MlrunProject(ModelObj):
|
|
|
2583
3056
|
self,
|
|
2584
3057
|
name: str = None,
|
|
2585
3058
|
workflow_path: str = None,
|
|
2586
|
-
arguments:
|
|
3059
|
+
arguments: dict[str, typing.Any] = None,
|
|
2587
3060
|
artifact_path: str = None,
|
|
2588
3061
|
workflow_handler: typing.Union[str, typing.Callable] = None,
|
|
2589
3062
|
namespace: str = None,
|
|
@@ -2598,7 +3071,8 @@ class MlrunProject(ModelObj):
|
|
|
2598
3071
|
timeout: int = None,
|
|
2599
3072
|
source: str = None,
|
|
2600
3073
|
cleanup_ttl: int = None,
|
|
2601
|
-
notifications:
|
|
3074
|
+
notifications: list[mlrun.model.Notification] = None,
|
|
3075
|
+
workflow_runner_node_selector: typing.Optional[dict[str, str]] = None,
|
|
2602
3076
|
) -> _PipelineRunStatus:
|
|
2603
3077
|
"""Run a workflow using kubeflow pipelines
|
|
2604
3078
|
|
|
@@ -2623,16 +3097,24 @@ class MlrunProject(ModelObj):
|
|
|
2623
3097
|
For using the pre-defined workflow's schedule, set `schedule=True`
|
|
2624
3098
|
:param timeout: Timeout in seconds to wait for pipeline completion (watch will be activated)
|
|
2625
3099
|
:param source: Source to use instead of the actual `project.spec.source` (used when engine is remote).
|
|
2626
|
-
Can be
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
:param
|
|
2635
|
-
|
|
3100
|
+
Can be one of:
|
|
3101
|
+
|
|
3102
|
+
* Remote URL which is loaded dynamically to the workflow runner.
|
|
3103
|
+
* A path to the project's context on the workflow runner's image.
|
|
3104
|
+
Path can be absolute or relative to `project.spec.build.source_code_target_dir` if defined
|
|
3105
|
+
(enriched when building a project image with source, see `MlrunProject.build_image`).
|
|
3106
|
+
For other engines the source is used to validate that the code is up-to-date.
|
|
3107
|
+
|
|
3108
|
+
:param cleanup_ttl:
|
|
3109
|
+
Pipeline cleanup ttl in secs (time to wait after workflow completion, at which point the
|
|
3110
|
+
workflow and all its resources are deleted)
|
|
3111
|
+
:param notifications:
|
|
3112
|
+
List of notifications to send for workflow completion
|
|
3113
|
+
:param workflow_runner_node_selector:
|
|
3114
|
+
Defines the node selector for the workflow runner pod when using a remote engine.
|
|
3115
|
+
This allows you to control and specify where the workflow runner pod will be scheduled.
|
|
3116
|
+
This setting is only relevant when the engine is set to 'remote' or for scheduled workflows,
|
|
3117
|
+
and it will be ignored if the workflow is not run on a remote engine.
|
|
2636
3118
|
:returns: ~py:class:`~mlrun.projects.pipelines._PipelineRunStatus` instance
|
|
2637
3119
|
"""
|
|
2638
3120
|
|
|
@@ -2650,11 +3132,11 @@ class MlrunProject(ModelObj):
|
|
|
2650
3132
|
"Remote repo is not defined, use .create_remote() + push()"
|
|
2651
3133
|
)
|
|
2652
3134
|
|
|
2653
|
-
if engine not in ["remote"]:
|
|
2654
|
-
#
|
|
2655
|
-
self.sync_functions(always=sync)
|
|
3135
|
+
if engine not in ["remote"] and not schedule:
|
|
3136
|
+
# For remote/scheduled runs there is no need to sync functions as they can be loaded dynamically during run
|
|
3137
|
+
self.sync_functions(always=sync, silent=True)
|
|
2656
3138
|
if not self.spec._function_objects:
|
|
2657
|
-
|
|
3139
|
+
logger.warn(
|
|
2658
3140
|
"There are no functions in the project."
|
|
2659
3141
|
" Make sure you've set your functions with project.set_function()."
|
|
2660
3142
|
)
|
|
@@ -2691,9 +3173,23 @@ class MlrunProject(ModelObj):
|
|
|
2691
3173
|
engine = "remote"
|
|
2692
3174
|
# The default engine is kfp if not given:
|
|
2693
3175
|
workflow_engine = get_workflow_engine(engine or workflow_spec.engine, local)
|
|
2694
|
-
if not inner_engine and engine == "remote":
|
|
2695
|
-
|
|
3176
|
+
if not inner_engine and workflow_engine.engine == "remote":
|
|
3177
|
+
# if inner engine is set to remote, assume kfp as the default inner engine with remote as the runner
|
|
3178
|
+
engine_kind = (
|
|
3179
|
+
workflow_spec.engine if workflow_spec.engine != "remote" else "kfp"
|
|
3180
|
+
)
|
|
3181
|
+
inner_engine = get_workflow_engine(engine_kind, local).engine
|
|
2696
3182
|
workflow_spec.engine = inner_engine or workflow_engine.engine
|
|
3183
|
+
if workflow_runner_node_selector:
|
|
3184
|
+
if workflow_engine.engine == "remote":
|
|
3185
|
+
workflow_spec.workflow_runner_node_selector = (
|
|
3186
|
+
workflow_runner_node_selector
|
|
3187
|
+
)
|
|
3188
|
+
else:
|
|
3189
|
+
logger.warn(
|
|
3190
|
+
"'workflow_runner_node_selector' applies only to remote engines"
|
|
3191
|
+
" and is ignored for non-remote runs."
|
|
3192
|
+
)
|
|
2697
3193
|
|
|
2698
3194
|
run = workflow_engine.run(
|
|
2699
3195
|
self,
|
|
@@ -2707,7 +3203,7 @@ class MlrunProject(ModelObj):
|
|
|
2707
3203
|
notifications=notifications,
|
|
2708
3204
|
)
|
|
2709
3205
|
# run is None when scheduling
|
|
2710
|
-
if run and run.state ==
|
|
3206
|
+
if run and run.state == mlrun_pipelines.common.models.RunStatuses.failed:
|
|
2711
3207
|
return run
|
|
2712
3208
|
if not workflow_spec.schedule:
|
|
2713
3209
|
# Failure and schedule messages already logged
|
|
@@ -2716,14 +3212,17 @@ class MlrunProject(ModelObj):
|
|
|
2716
3212
|
)
|
|
2717
3213
|
workflow_spec.clear_tmp()
|
|
2718
3214
|
if (timeout or watch) and not workflow_spec.schedule:
|
|
3215
|
+
run_status_kwargs = {}
|
|
2719
3216
|
status_engine = run._engine
|
|
2720
3217
|
# run's engine gets replaced with inner engine if engine is remote,
|
|
2721
3218
|
# so in that case we need to get the status from the remote engine manually
|
|
2722
|
-
|
|
2723
|
-
if engine == "remote" and status_engine.engine != "local":
|
|
3219
|
+
if workflow_engine.engine == "remote":
|
|
2724
3220
|
status_engine = _RemoteRunner
|
|
3221
|
+
run_status_kwargs["inner_engine"] = run._engine
|
|
2725
3222
|
|
|
2726
|
-
status_engine.get_run_status(
|
|
3223
|
+
status_engine.get_run_status(
|
|
3224
|
+
project=self, run=run, timeout=timeout, **run_status_kwargs
|
|
3225
|
+
)
|
|
2727
3226
|
return run
|
|
2728
3227
|
|
|
2729
3228
|
def save_workflow(self, name, target, artifact_path=None, ttl=None):
|
|
@@ -2823,43 +3322,66 @@ class MlrunProject(ModelObj):
|
|
|
2823
3322
|
|
|
2824
3323
|
def set_model_monitoring_credentials(
|
|
2825
3324
|
self,
|
|
2826
|
-
access_key: str = None,
|
|
2827
|
-
endpoint_store_connection: str = None,
|
|
2828
|
-
stream_path: str = None,
|
|
3325
|
+
access_key: Optional[str] = None,
|
|
3326
|
+
endpoint_store_connection: Optional[str] = None,
|
|
3327
|
+
stream_path: Optional[str] = None,
|
|
3328
|
+
tsdb_connection: Optional[str] = None,
|
|
3329
|
+
replace_creds: bool = False,
|
|
2829
3330
|
):
|
|
2830
|
-
"""Set the credentials that will be used by the project's model monitoring
|
|
2831
|
-
infrastructure functions.
|
|
2832
|
-
|
|
2833
|
-
:param access_key: Model Monitoring access key for managing user permissions
|
|
2834
|
-
:param access_key: Model Monitoring access key for managing user permissions
|
|
2835
|
-
:param endpoint_store_connection: Endpoint store connection string
|
|
2836
|
-
:param stream_path: Path to the model monitoring stream
|
|
2837
3331
|
"""
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
3332
|
+
Set the credentials that will be used by the project's model monitoring
|
|
3333
|
+
infrastructure functions. Important to note that you have to set the credentials before deploying any
|
|
3334
|
+
model monitoring or serving function.
|
|
3335
|
+
|
|
3336
|
+
:param access_key: Model monitoring access key for managing user permissions.
|
|
3337
|
+
:param endpoint_store_connection: Endpoint store connection string. By default, None. Options:
|
|
3338
|
+
|
|
3339
|
+
* None - will be set from the system configuration.
|
|
3340
|
+
* v3io - for v3io endpoint store, pass `v3io` and the system will generate the
|
|
3341
|
+
exact path.
|
|
3342
|
+
* MySQL/SQLite - for SQL endpoint store, provide the full connection string,
|
|
3343
|
+
for example: mysql+pymysql://<username>:<password>@<host>:<port>/<db_name>
|
|
3344
|
+
:param stream_path: Path to the model monitoring stream. By default, None. Options:
|
|
3345
|
+
|
|
3346
|
+
* None - will be set from the system configuration.
|
|
3347
|
+
* v3io - for v3io stream, pass `v3io` and the system will generate the exact
|
|
3348
|
+
path.
|
|
3349
|
+
* Kafka - for Kafka stream, provide the full connection string without custom
|
|
3350
|
+
topic, for example kafka://<some_kafka_broker>:<port>.
|
|
3351
|
+
:param tsdb_connection: Connection string to the time series database. By default, None.
|
|
3352
|
+
Options:
|
|
3353
|
+
|
|
3354
|
+
* None - will be set from the system configuration.
|
|
3355
|
+
* v3io - for v3io stream, pass `v3io` and the system will generate the exact
|
|
3356
|
+
path.
|
|
3357
|
+
* TDEngine - for TDEngine tsdb, provide the full websocket connection URL,
|
|
3358
|
+
for example taosws://<username>:<password>@<host>:<port>.
|
|
3359
|
+
:param replace_creds: If True, will override the existing credentials.
|
|
3360
|
+
Please keep in mind that if you already enabled model monitoring on
|
|
3361
|
+
your project this action can cause data loose and will require redeploying
|
|
3362
|
+
all model monitoring functions & model monitoring infra
|
|
3363
|
+
& tracked model server.
|
|
3364
|
+
"""
|
|
3365
|
+
db = mlrun.db.get_run_db(secrets=self._secrets)
|
|
3366
|
+
db.set_model_monitoring_credentials(
|
|
3367
|
+
project=self.name,
|
|
3368
|
+
credentials={
|
|
3369
|
+
"access_key": access_key,
|
|
3370
|
+
"endpoint_store_connection": endpoint_store_connection,
|
|
3371
|
+
"stream_path": stream_path,
|
|
3372
|
+
"tsdb_connection": tsdb_connection,
|
|
3373
|
+
},
|
|
3374
|
+
replace_creds=replace_creds,
|
|
2862
3375
|
)
|
|
3376
|
+
if replace_creds:
|
|
3377
|
+
logger.info(
|
|
3378
|
+
"Model monitoring credentials were set successfully. "
|
|
3379
|
+
"Please keep in mind that if you already had model monitoring functions "
|
|
3380
|
+
"/ model monitoring infra / tracked model server "
|
|
3381
|
+
"deployed on your project, you will need to redeploy them."
|
|
3382
|
+
"For redeploying the model monitoring infra, please use `enable_model_monitoring` API "
|
|
3383
|
+
"and set `rebuild_images=True`"
|
|
3384
|
+
)
|
|
2863
3385
|
|
|
2864
3386
|
def run_function(
|
|
2865
3387
|
self,
|
|
@@ -2870,7 +3392,7 @@ class MlrunProject(ModelObj):
|
|
|
2870
3392
|
hyperparams: dict = None,
|
|
2871
3393
|
hyper_param_options: mlrun.model.HyperParamOptions = None,
|
|
2872
3394
|
inputs: dict = None,
|
|
2873
|
-
outputs:
|
|
3395
|
+
outputs: list[str] = None,
|
|
2874
3396
|
workdir: str = "",
|
|
2875
3397
|
labels: dict = None,
|
|
2876
3398
|
base_task: mlrun.model.RunTemplate = None,
|
|
@@ -2881,10 +3403,11 @@ class MlrunProject(ModelObj):
|
|
|
2881
3403
|
auto_build: bool = None,
|
|
2882
3404
|
schedule: typing.Union[str, mlrun.common.schemas.ScheduleCronTrigger] = None,
|
|
2883
3405
|
artifact_path: str = None,
|
|
2884
|
-
notifications:
|
|
2885
|
-
returns: Optional[
|
|
3406
|
+
notifications: list[mlrun.model.Notification] = None,
|
|
3407
|
+
returns: Optional[list[Union[str, dict[str, str]]]] = None,
|
|
2886
3408
|
builder_env: Optional[dict] = None,
|
|
2887
|
-
|
|
3409
|
+
reset_on_run: bool = None,
|
|
3410
|
+
) -> typing.Union[mlrun.model.RunObject, PipelineNodeWrapper]:
|
|
2888
3411
|
"""Run a local or remote task as part of a local/kubeflow pipeline
|
|
2889
3412
|
|
|
2890
3413
|
example (use with project)::
|
|
@@ -2896,8 +3419,11 @@ class MlrunProject(ModelObj):
|
|
|
2896
3419
|
|
|
2897
3420
|
# run functions (refer to them by name)
|
|
2898
3421
|
run1 = project.run_function("myfunc", params={"x": 7})
|
|
2899
|
-
run2 = project.run_function(
|
|
2900
|
-
|
|
3422
|
+
run2 = project.run_function(
|
|
3423
|
+
"train",
|
|
3424
|
+
params={"label_columns": LABELS},
|
|
3425
|
+
inputs={"dataset": run1.outputs["data"]},
|
|
3426
|
+
)
|
|
2901
3427
|
|
|
2902
3428
|
:param function: name of the function (in the project) or function object
|
|
2903
3429
|
:param handler: name of the function handler
|
|
@@ -2936,8 +3462,13 @@ class MlrunProject(ModelObj):
|
|
|
2936
3462
|
* A dictionary of configurations to use when logging. Further info per object type and
|
|
2937
3463
|
artifact type can be given there. The artifact key must appear in the dictionary as
|
|
2938
3464
|
"key": "the_key".
|
|
2939
|
-
:param builder_env:
|
|
2940
|
-
|
|
3465
|
+
:param builder_env: env vars dict for source archive config/credentials e.g. builder_env={"GIT_TOKEN":
|
|
3466
|
+
token}
|
|
3467
|
+
:param reset_on_run: When True, function python modules would reload prior to code execution.
|
|
3468
|
+
This ensures latest code changes are executed. This argument must be used in
|
|
3469
|
+
conjunction with the local=True argument.
|
|
3470
|
+
|
|
3471
|
+
:return: MLRun RunObject or PipelineNodeWrapper
|
|
2941
3472
|
"""
|
|
2942
3473
|
return run_function(
|
|
2943
3474
|
function,
|
|
@@ -2962,6 +3493,7 @@ class MlrunProject(ModelObj):
|
|
|
2962
3493
|
notifications=notifications,
|
|
2963
3494
|
returns=returns,
|
|
2964
3495
|
builder_env=builder_env,
|
|
3496
|
+
reset_on_run=reset_on_run,
|
|
2965
3497
|
)
|
|
2966
3498
|
|
|
2967
3499
|
def build_function(
|
|
@@ -2973,14 +3505,14 @@ class MlrunProject(ModelObj):
|
|
|
2973
3505
|
base_image: str = None,
|
|
2974
3506
|
commands: list = None,
|
|
2975
3507
|
secret_name: str = None,
|
|
2976
|
-
requirements: typing.Union[str,
|
|
3508
|
+
requirements: typing.Union[str, list[str]] = None,
|
|
2977
3509
|
mlrun_version_specifier: str = None,
|
|
2978
3510
|
builder_env: dict = None,
|
|
2979
3511
|
overwrite_build_params: bool = False,
|
|
2980
3512
|
requirements_file: str = None,
|
|
2981
3513
|
extra_args: str = None,
|
|
2982
3514
|
force_build: bool = False,
|
|
2983
|
-
) -> typing.Union[BuildStatus,
|
|
3515
|
+
) -> typing.Union[BuildStatus, PipelineNodeWrapper]:
|
|
2984
3516
|
"""deploy ML function, build container with its dependencies
|
|
2985
3517
|
|
|
2986
3518
|
:param function: name of the function (in the project) or function object
|
|
@@ -3029,7 +3561,7 @@ class MlrunProject(ModelObj):
|
|
|
3029
3561
|
base_image: str = None,
|
|
3030
3562
|
commands: list = None,
|
|
3031
3563
|
secret_name: str = None,
|
|
3032
|
-
requirements: typing.Union[str,
|
|
3564
|
+
requirements: typing.Union[str, list[str]] = None,
|
|
3033
3565
|
overwrite_build_params: bool = False,
|
|
3034
3566
|
requirements_file: str = None,
|
|
3035
3567
|
builder_env: dict = None,
|
|
@@ -3091,18 +3623,17 @@ class MlrunProject(ModelObj):
|
|
|
3091
3623
|
image: str = None,
|
|
3092
3624
|
set_as_default: bool = True,
|
|
3093
3625
|
with_mlrun: bool = None,
|
|
3094
|
-
skip_deployed: bool = False,
|
|
3095
3626
|
base_image: str = None,
|
|
3096
3627
|
commands: list = None,
|
|
3097
3628
|
secret_name: str = None,
|
|
3098
|
-
requirements: typing.Union[str,
|
|
3629
|
+
requirements: typing.Union[str, list[str]] = None,
|
|
3099
3630
|
mlrun_version_specifier: str = None,
|
|
3100
3631
|
builder_env: dict = None,
|
|
3101
3632
|
overwrite_build_params: bool = False,
|
|
3102
3633
|
requirements_file: str = None,
|
|
3103
3634
|
extra_args: str = None,
|
|
3104
3635
|
target_dir: str = None,
|
|
3105
|
-
) -> typing.Union[BuildStatus,
|
|
3636
|
+
) -> typing.Union[BuildStatus, PipelineNodeWrapper]:
|
|
3106
3637
|
"""Builder docker image for the project, based on the project's build config. Parameters allow to override
|
|
3107
3638
|
the build config.
|
|
3108
3639
|
If the project has a source configured and pull_at_runtime is not configured, this source will be cloned to the
|
|
@@ -3112,7 +3643,6 @@ class MlrunProject(ModelObj):
|
|
|
3112
3643
|
used. If not set, the `mlconf.default_project_image_name` value will be used
|
|
3113
3644
|
:param set_as_default: set `image` to be the project's default image (default False)
|
|
3114
3645
|
:param with_mlrun: add the current mlrun package to the container build
|
|
3115
|
-
:param skip_deployed: *Deprecated* parameter is ignored
|
|
3116
3646
|
:param base_image: base image name/path (commands and source code will be added to it) defaults to
|
|
3117
3647
|
mlrun.mlconf.default_base_image
|
|
3118
3648
|
:param commands: list of docker build (RUN) commands e.g. ['pip install pandas']
|
|
@@ -3137,14 +3667,6 @@ class MlrunProject(ModelObj):
|
|
|
3137
3667
|
base_image=base_image,
|
|
3138
3668
|
)
|
|
3139
3669
|
|
|
3140
|
-
if skip_deployed:
|
|
3141
|
-
warnings.warn(
|
|
3142
|
-
"The 'skip_deployed' parameter is deprecated and will be removed in 1.7.0. "
|
|
3143
|
-
"This parameter is ignored.",
|
|
3144
|
-
# TODO: remove in 1.7.0
|
|
3145
|
-
FutureWarning,
|
|
3146
|
-
)
|
|
3147
|
-
|
|
3148
3670
|
if not overwrite_build_params:
|
|
3149
3671
|
# TODO: change overwrite_build_params default to True in 1.8.0
|
|
3150
3672
|
warnings.warn(
|
|
@@ -3208,7 +3730,7 @@ class MlrunProject(ModelObj):
|
|
|
3208
3730
|
logger.warning(
|
|
3209
3731
|
f"Image was successfully built, but failed to delete temporary function {function.metadata.name}."
|
|
3210
3732
|
" To remove the function, attempt to manually delete it.",
|
|
3211
|
-
exc=
|
|
3733
|
+
exc=mlrun.errors.err_to_str(exc),
|
|
3212
3734
|
)
|
|
3213
3735
|
|
|
3214
3736
|
return result
|
|
@@ -3222,7 +3744,7 @@ class MlrunProject(ModelObj):
|
|
|
3222
3744
|
verbose: bool = None,
|
|
3223
3745
|
builder_env: dict = None,
|
|
3224
3746
|
mock: bool = None,
|
|
3225
|
-
) -> typing.Union[DeployStatus,
|
|
3747
|
+
) -> typing.Union[DeployStatus, PipelineNodeWrapper]:
|
|
3226
3748
|
"""deploy real-time (nuclio based) functions
|
|
3227
3749
|
|
|
3228
3750
|
:param function: name of the function (in the project) or function object
|
|
@@ -3257,13 +3779,18 @@ class MlrunProject(ModelObj):
|
|
|
3257
3779
|
artifact = db.read_artifact(
|
|
3258
3780
|
key, tag, iter=iter, project=self.metadata.name, tree=tree
|
|
3259
3781
|
)
|
|
3260
|
-
|
|
3782
|
+
|
|
3783
|
+
# in tests, if an artifact is not found, the db returns None
|
|
3784
|
+
# in real usage, the db should raise an exception
|
|
3785
|
+
if artifact:
|
|
3786
|
+
return dict_to_artifact(artifact)
|
|
3787
|
+
return None
|
|
3261
3788
|
|
|
3262
3789
|
def list_artifacts(
|
|
3263
3790
|
self,
|
|
3264
3791
|
name=None,
|
|
3265
3792
|
tag=None,
|
|
3266
|
-
labels: Optional[Union[
|
|
3793
|
+
labels: Optional[Union[dict[str, str], list[str]]] = None,
|
|
3267
3794
|
since=None,
|
|
3268
3795
|
until=None,
|
|
3269
3796
|
iter: int = None,
|
|
@@ -3281,9 +3808,9 @@ class MlrunProject(ModelObj):
|
|
|
3281
3808
|
Examples::
|
|
3282
3809
|
|
|
3283
3810
|
# Get latest version of all artifacts in project
|
|
3284
|
-
latest_artifacts = project.list_artifacts(
|
|
3811
|
+
latest_artifacts = project.list_artifacts("", tag="latest")
|
|
3285
3812
|
# check different artifact versions for a specific artifact, return as objects list
|
|
3286
|
-
result_versions = project.list_artifacts(
|
|
3813
|
+
result_versions = project.list_artifacts("results", tag="*").to_objects()
|
|
3287
3814
|
|
|
3288
3815
|
:param name: Name of artifacts to retrieve. Name with '~' prefix is used as a like query, and is not
|
|
3289
3816
|
case-sensitive. This means that querying for ``~name`` may return artifacts named
|
|
@@ -3323,7 +3850,7 @@ class MlrunProject(ModelObj):
|
|
|
3323
3850
|
self,
|
|
3324
3851
|
name=None,
|
|
3325
3852
|
tag=None,
|
|
3326
|
-
labels: Optional[Union[
|
|
3853
|
+
labels: Optional[Union[dict[str, str], list[str]]] = None,
|
|
3327
3854
|
since=None,
|
|
3328
3855
|
until=None,
|
|
3329
3856
|
iter: int = None,
|
|
@@ -3335,7 +3862,7 @@ class MlrunProject(ModelObj):
|
|
|
3335
3862
|
Examples::
|
|
3336
3863
|
|
|
3337
3864
|
# Get latest version of all models in project
|
|
3338
|
-
latest_models = project.list_models(
|
|
3865
|
+
latest_models = project.list_models("", tag="latest")
|
|
3339
3866
|
|
|
3340
3867
|
|
|
3341
3868
|
:param name: Name of artifacts to retrieve. Name with '~' prefix is used as a like query, and is not
|
|
@@ -3376,7 +3903,7 @@ class MlrunProject(ModelObj):
|
|
|
3376
3903
|
|
|
3377
3904
|
|
|
3378
3905
|
:param name: Return only functions with a specific name.
|
|
3379
|
-
:param tag: Return function versions with specific tags.
|
|
3906
|
+
:param tag: Return function versions with specific tags. To return only tagged functions, set tag to ``"*"``.
|
|
3380
3907
|
:param labels: Return functions that have specific labels assigned to them.
|
|
3381
3908
|
:returns: List of function objects.
|
|
3382
3909
|
"""
|
|
@@ -3405,9 +3932,7 @@ class MlrunProject(ModelObj):
|
|
|
3405
3932
|
:returns: List of function objects.
|
|
3406
3933
|
"""
|
|
3407
3934
|
|
|
3408
|
-
model_monitoring_labels_list = [
|
|
3409
|
-
f"{mm_constants.ModelMonitoringAppLabel.KEY}={mm_constants.ModelMonitoringAppLabel.VAL}"
|
|
3410
|
-
]
|
|
3935
|
+
model_monitoring_labels_list = [str(mm_constants.ModelMonitoringAppLabel())]
|
|
3411
3936
|
if labels:
|
|
3412
3937
|
model_monitoring_labels_list += labels
|
|
3413
3938
|
return self.list_functions(
|
|
@@ -3419,9 +3944,12 @@ class MlrunProject(ModelObj):
|
|
|
3419
3944
|
def list_runs(
|
|
3420
3945
|
self,
|
|
3421
3946
|
name: Optional[str] = None,
|
|
3422
|
-
uid: Optional[Union[str,
|
|
3423
|
-
labels: Optional[Union[str,
|
|
3424
|
-
state: Optional[
|
|
3947
|
+
uid: Optional[Union[str, list[str]]] = None,
|
|
3948
|
+
labels: Optional[Union[str, list[str]]] = None,
|
|
3949
|
+
state: Optional[
|
|
3950
|
+
mlrun.common.runtimes.constants.RunStates
|
|
3951
|
+
] = None, # Backward compatibility
|
|
3952
|
+
states: typing.Optional[list[mlrun.common.runtimes.constants.RunStates]] = None,
|
|
3425
3953
|
sort: bool = True,
|
|
3426
3954
|
last: int = 0,
|
|
3427
3955
|
iter: bool = False,
|
|
@@ -3440,14 +3968,14 @@ class MlrunProject(ModelObj):
|
|
|
3440
3968
|
Example::
|
|
3441
3969
|
|
|
3442
3970
|
# return a list of runs matching the name and label and compare
|
|
3443
|
-
runs = project.list_runs(name=
|
|
3971
|
+
runs = project.list_runs(name="download", labels="owner=admin")
|
|
3444
3972
|
runs.compare()
|
|
3445
3973
|
|
|
3446
3974
|
# multi-label filter can also be provided
|
|
3447
|
-
runs = project.list_runs(name=
|
|
3975
|
+
runs = project.list_runs(name="download", labels=["kind=job", "owner=admin"])
|
|
3448
3976
|
|
|
3449
3977
|
# If running in Jupyter, can use the .show() function to display the results
|
|
3450
|
-
project.list_runs(name=
|
|
3978
|
+
project.list_runs(name="").show()
|
|
3451
3979
|
|
|
3452
3980
|
|
|
3453
3981
|
:param name: Name of the run to retrieve.
|
|
@@ -3455,10 +3983,11 @@ class MlrunProject(ModelObj):
|
|
|
3455
3983
|
:param labels: A list of labels to filter by. Label filters work by either filtering a specific value
|
|
3456
3984
|
of a label (i.e. list("key=value")) or by looking for the existence of a given
|
|
3457
3985
|
key (i.e. "key").
|
|
3458
|
-
:param state: List only runs whose state is specified.
|
|
3986
|
+
:param state: Deprecated - List only runs whose state is specified.
|
|
3987
|
+
:param states: List only runs whose state is one of the provided states.
|
|
3459
3988
|
:param sort: Whether to sort the result according to their start time. Otherwise, results will be
|
|
3460
3989
|
returned by their internal order in the DB (order will not be guaranteed).
|
|
3461
|
-
:param last: Deprecated - currently not used (will be removed in 1.
|
|
3990
|
+
:param last: Deprecated - currently not used (will be removed in 1.9.0).
|
|
3462
3991
|
:param iter: If ``True`` return runs from all iterations. Otherwise, return only runs whose ``iter`` is 0.
|
|
3463
3992
|
:param start_time_from: Filter by run start time in ``[start_time_from, start_time_to]``.
|
|
3464
3993
|
:param start_time_to: Filter by run start time in ``[start_time_from, start_time_to]``.
|
|
@@ -3466,13 +3995,22 @@ class MlrunProject(ModelObj):
|
|
|
3466
3995
|
last_update_time_to)``.
|
|
3467
3996
|
:param last_update_time_to: Filter by run last update time in ``(last_update_time_from, last_update_time_to)``.
|
|
3468
3997
|
"""
|
|
3998
|
+
if state:
|
|
3999
|
+
# TODO: Remove this in 1.9.0
|
|
4000
|
+
warnings.warn(
|
|
4001
|
+
"'state' is deprecated and will be removed in 1.9.0. Use 'states' instead.",
|
|
4002
|
+
FutureWarning,
|
|
4003
|
+
)
|
|
4004
|
+
|
|
3469
4005
|
db = mlrun.db.get_run_db(secrets=self._secrets)
|
|
3470
4006
|
return db.list_runs(
|
|
3471
4007
|
name,
|
|
3472
4008
|
uid,
|
|
3473
4009
|
self.metadata.name,
|
|
3474
4010
|
labels=labels,
|
|
3475
|
-
|
|
4011
|
+
states=mlrun.utils.helpers.as_list(state)
|
|
4012
|
+
if state is not None
|
|
4013
|
+
else states or None,
|
|
3476
4014
|
sort=sort,
|
|
3477
4015
|
last=last,
|
|
3478
4016
|
iter=iter,
|
|
@@ -3508,7 +4046,7 @@ class MlrunProject(ModelObj):
|
|
|
3508
4046
|
profile, self.name
|
|
3509
4047
|
)
|
|
3510
4048
|
|
|
3511
|
-
def list_datastore_profiles(self) ->
|
|
4049
|
+
def list_datastore_profiles(self) -> list[DatastoreProfile]:
|
|
3512
4050
|
"""
|
|
3513
4051
|
Returns a list of datastore profiles associated with the project.
|
|
3514
4052
|
The information excludes private details, showcasing only public data.
|
|
@@ -3517,7 +4055,7 @@ class MlrunProject(ModelObj):
|
|
|
3517
4055
|
self.name
|
|
3518
4056
|
)
|
|
3519
4057
|
|
|
3520
|
-
def get_custom_packagers(self) ->
|
|
4058
|
+
def get_custom_packagers(self) -> list[tuple[str, bool]]:
|
|
3521
4059
|
"""
|
|
3522
4060
|
Get the custom packagers registered in the project.
|
|
3523
4061
|
|
|
@@ -3553,12 +4091,194 @@ class MlrunProject(ModelObj):
|
|
|
3553
4091
|
"""
|
|
3554
4092
|
self.spec.remove_custom_packager(packager=packager)
|
|
3555
4093
|
|
|
4094
|
+
def store_api_gateway(
|
|
4095
|
+
self,
|
|
4096
|
+
api_gateway: mlrun.runtimes.nuclio.api_gateway.APIGateway,
|
|
4097
|
+
wait_for_readiness=True,
|
|
4098
|
+
max_wait_time=90,
|
|
4099
|
+
) -> mlrun.runtimes.nuclio.api_gateway.APIGateway:
|
|
4100
|
+
"""
|
|
4101
|
+
Creates or updates a Nuclio API Gateway using the provided APIGateway object.
|
|
4102
|
+
|
|
4103
|
+
This method interacts with the MLRun service to create/update a Nuclio API Gateway based on the provided
|
|
4104
|
+
APIGateway object. Once done, it returns the updated APIGateway object containing all fields propagated
|
|
4105
|
+
on MLRun and Nuclio sides, such as the 'host' attribute.
|
|
4106
|
+
Nuclio docs here: https://docs.nuclio.io/en/latest/reference/api-gateway/http.html
|
|
4107
|
+
|
|
4108
|
+
:param api_gateway: An instance of :py:class:`~mlrun.runtimes.nuclio.APIGateway` representing the
|
|
4109
|
+
configuration of the API Gateway to be created or updated.
|
|
4110
|
+
:param wait_for_readiness: (Optional) A boolean indicating whether to wait for the API Gateway to become
|
|
4111
|
+
ready after creation or update (default is True).
|
|
4112
|
+
:param max_wait_time: (Optional) Maximum time to wait for API Gateway readiness in seconds (default is 90s)
|
|
4113
|
+
|
|
4114
|
+
|
|
4115
|
+
:returns: An instance of :py:class:`~mlrun.runtimes.nuclio.APIGateway` with all fields populated based on the
|
|
4116
|
+
information retrieved from the Nuclio API
|
|
4117
|
+
"""
|
|
4118
|
+
|
|
4119
|
+
api_gateway_json = mlrun.db.get_run_db().store_api_gateway(
|
|
4120
|
+
api_gateway=api_gateway,
|
|
4121
|
+
project=self.name,
|
|
4122
|
+
)
|
|
4123
|
+
|
|
4124
|
+
if api_gateway_json:
|
|
4125
|
+
# fill in all the fields in the user's api_gateway object
|
|
4126
|
+
api_gateway = mlrun.runtimes.nuclio.api_gateway.APIGateway.from_scheme(
|
|
4127
|
+
api_gateway_json
|
|
4128
|
+
)
|
|
4129
|
+
if wait_for_readiness:
|
|
4130
|
+
api_gateway.wait_for_readiness(max_wait_time=max_wait_time)
|
|
4131
|
+
|
|
4132
|
+
return api_gateway
|
|
4133
|
+
|
|
4134
|
+
def list_api_gateways(self) -> list[mlrun.runtimes.nuclio.api_gateway.APIGateway]:
|
|
4135
|
+
"""
|
|
4136
|
+
Retrieves a list of Nuclio API gateways associated with the project.
|
|
4137
|
+
|
|
4138
|
+
:returns: List of :py:class:`~mlrun.runtimes.nuclio.api_gateway.APIGateway` objects representing
|
|
4139
|
+
the Nuclio API gateways associated with the project.
|
|
4140
|
+
"""
|
|
4141
|
+
gateways_list = mlrun.db.get_run_db().list_api_gateways(self.name)
|
|
4142
|
+
return [
|
|
4143
|
+
mlrun.runtimes.nuclio.api_gateway.APIGateway.from_scheme(gateway_dict)
|
|
4144
|
+
for gateway_dict in gateways_list.api_gateways.values()
|
|
4145
|
+
]
|
|
4146
|
+
|
|
4147
|
+
def get_api_gateway(
|
|
4148
|
+
self,
|
|
4149
|
+
name: str,
|
|
4150
|
+
) -> mlrun.runtimes.nuclio.api_gateway.APIGateway:
|
|
4151
|
+
"""
|
|
4152
|
+
Retrieves an API gateway by name instance.
|
|
4153
|
+
|
|
4154
|
+
:param name: The name of the API gateway to retrieve.
|
|
4155
|
+
|
|
4156
|
+
Returns:
|
|
4157
|
+
mlrun.runtimes.nuclio.APIGateway: An instance of APIGateway.
|
|
4158
|
+
"""
|
|
4159
|
+
|
|
4160
|
+
gateway = mlrun.db.get_run_db().get_api_gateway(name=name, project=self.name)
|
|
4161
|
+
return mlrun.runtimes.nuclio.api_gateway.APIGateway.from_scheme(gateway)
|
|
4162
|
+
|
|
4163
|
+
def delete_api_gateway(
|
|
4164
|
+
self,
|
|
4165
|
+
name: str,
|
|
4166
|
+
):
|
|
4167
|
+
"""
|
|
4168
|
+
Deletes an API gateway by name.
|
|
4169
|
+
|
|
4170
|
+
:param name: The name of the API gateway to delete.
|
|
4171
|
+
"""
|
|
4172
|
+
|
|
4173
|
+
mlrun.db.get_run_db().delete_api_gateway(name=name, project=self.name)
|
|
4174
|
+
|
|
4175
|
+
def store_alert_config(
|
|
4176
|
+
self, alert_data: AlertConfig, alert_name: typing.Optional[str] = None
|
|
4177
|
+
) -> AlertConfig:
|
|
4178
|
+
"""
|
|
4179
|
+
Create/modify an alert.
|
|
4180
|
+
|
|
4181
|
+
:param alert_data: The data of the alert.
|
|
4182
|
+
:param alert_name: The name of the alert.
|
|
4183
|
+
:return: the created/modified alert.
|
|
4184
|
+
"""
|
|
4185
|
+
if not alert_data:
|
|
4186
|
+
raise mlrun.errors.MLRunInvalidArgumentError("Alert data must be provided")
|
|
4187
|
+
|
|
4188
|
+
db = mlrun.db.get_run_db(secrets=self._secrets)
|
|
4189
|
+
alert_name = alert_name or alert_data.name
|
|
4190
|
+
if alert_data.project is not None and alert_data.project != self.metadata.name:
|
|
4191
|
+
logger.warn(
|
|
4192
|
+
"Project in alert does not match project in operation",
|
|
4193
|
+
project=alert_data.project,
|
|
4194
|
+
)
|
|
4195
|
+
alert_data.project = self.metadata.name
|
|
4196
|
+
return db.store_alert_config(alert_name, alert_data, project=self.metadata.name)
|
|
4197
|
+
|
|
4198
|
+
def get_alert_config(self, alert_name: str) -> AlertConfig:
|
|
4199
|
+
"""
|
|
4200
|
+
Retrieve an alert.
|
|
4201
|
+
|
|
4202
|
+
:param alert_name: The name of the alert to retrieve.
|
|
4203
|
+
:return: The alert object.
|
|
4204
|
+
"""
|
|
4205
|
+
db = mlrun.db.get_run_db(secrets=self._secrets)
|
|
4206
|
+
return db.get_alert_config(alert_name, self.metadata.name)
|
|
4207
|
+
|
|
4208
|
+
def list_alerts_configs(self) -> list[AlertConfig]:
|
|
4209
|
+
"""
|
|
4210
|
+
Retrieve list of alerts of a project.
|
|
4211
|
+
|
|
4212
|
+
:return: All the alerts objects of the project.
|
|
4213
|
+
"""
|
|
4214
|
+
db = mlrun.db.get_run_db(secrets=self._secrets)
|
|
4215
|
+
return db.list_alerts_configs(self.metadata.name)
|
|
4216
|
+
|
|
4217
|
+
def delete_alert_config(
|
|
4218
|
+
self, alert_data: AlertConfig = None, alert_name: str = None
|
|
4219
|
+
):
|
|
4220
|
+
"""
|
|
4221
|
+
Delete an alert.
|
|
4222
|
+
|
|
4223
|
+
:param alert_data: The data of the alert.
|
|
4224
|
+
:param alert_name: The name of the alert to delete.
|
|
4225
|
+
"""
|
|
4226
|
+
if alert_data is None and alert_name is None:
|
|
4227
|
+
raise ValueError(
|
|
4228
|
+
"At least one of alert_data or alert_name must be provided"
|
|
4229
|
+
)
|
|
4230
|
+
if alert_data and alert_name and alert_data.name != alert_name:
|
|
4231
|
+
raise ValueError("Alert_data name does not match the provided alert_name")
|
|
4232
|
+
db = mlrun.db.get_run_db(secrets=self._secrets)
|
|
4233
|
+
if alert_data:
|
|
4234
|
+
alert_name = alert_data.name
|
|
4235
|
+
db.delete_alert_config(alert_name, self.metadata.name)
|
|
4236
|
+
|
|
4237
|
+
def reset_alert_config(
|
|
4238
|
+
self, alert_data: AlertConfig = None, alert_name: str = None
|
|
4239
|
+
):
|
|
4240
|
+
"""
|
|
4241
|
+
Reset an alert.
|
|
4242
|
+
|
|
4243
|
+
:param alert_data: The data of the alert.
|
|
4244
|
+
:param alert_name: The name of the alert to reset.
|
|
4245
|
+
"""
|
|
4246
|
+
if alert_data is None and alert_name is None:
|
|
4247
|
+
raise ValueError(
|
|
4248
|
+
"At least one of alert_data or alert_name must be provided"
|
|
4249
|
+
)
|
|
4250
|
+
if alert_data and alert_name and alert_data.name != alert_name:
|
|
4251
|
+
raise ValueError("Alert_data name does not match the provided alert_name")
|
|
4252
|
+
db = mlrun.db.get_run_db(secrets=self._secrets)
|
|
4253
|
+
if alert_data:
|
|
4254
|
+
alert_name = alert_data.name
|
|
4255
|
+
db.reset_alert_config(alert_name, self.metadata.name)
|
|
4256
|
+
|
|
4257
|
+
def get_alert_template(self, template_name: str) -> AlertTemplate:
|
|
4258
|
+
"""
|
|
4259
|
+
Retrieve a specific alert template.
|
|
4260
|
+
|
|
4261
|
+
:param template_name: The name of the template to retrieve.
|
|
4262
|
+
:return: The template object.
|
|
4263
|
+
"""
|
|
4264
|
+
db = mlrun.db.get_run_db(secrets=self._secrets)
|
|
4265
|
+
return db.get_alert_template(template_name)
|
|
4266
|
+
|
|
4267
|
+
def list_alert_templates(self) -> list[AlertTemplate]:
|
|
4268
|
+
"""
|
|
4269
|
+
Retrieve list of all alert templates.
|
|
4270
|
+
|
|
4271
|
+
:return: All the alert template objects in the database.
|
|
4272
|
+
"""
|
|
4273
|
+
db = mlrun.db.get_run_db(secrets=self._secrets)
|
|
4274
|
+
return db.list_alert_templates()
|
|
4275
|
+
|
|
3556
4276
|
def _run_authenticated_git_action(
|
|
3557
4277
|
self,
|
|
3558
4278
|
action: Callable,
|
|
3559
4279
|
remote: str,
|
|
3560
|
-
args: list =
|
|
3561
|
-
kwargs: dict =
|
|
4280
|
+
args: list = None,
|
|
4281
|
+
kwargs: dict = None,
|
|
3562
4282
|
secrets: Union[SecretsStore, dict] = None,
|
|
3563
4283
|
):
|
|
3564
4284
|
"""Run an arbitrary Git routine while the remote is enriched with secrets
|
|
@@ -3578,6 +4298,8 @@ class MlrunProject(ModelObj):
|
|
|
3578
4298
|
try:
|
|
3579
4299
|
if is_remote_enriched:
|
|
3580
4300
|
self.spec.repo.remotes[remote].set_url(enriched_remote, clean_remote)
|
|
4301
|
+
args = args or []
|
|
4302
|
+
kwargs = kwargs or {}
|
|
3581
4303
|
action(*args, **kwargs)
|
|
3582
4304
|
except RuntimeError as e:
|
|
3583
4305
|
raise mlrun.errors.MLRunRuntimeError(
|
|
@@ -3630,6 +4352,97 @@ class MlrunProject(ModelObj):
|
|
|
3630
4352
|
f"<project.spec.get_code_path()>/<{param_name}>)."
|
|
3631
4353
|
)
|
|
3632
4354
|
|
|
4355
|
+
def _resolve_artifact_producer(
|
|
4356
|
+
self,
|
|
4357
|
+
artifact: typing.Union[str, Artifact],
|
|
4358
|
+
project_producer_tag: str = None,
|
|
4359
|
+
) -> tuple[ArtifactProducer, bool]:
|
|
4360
|
+
"""
|
|
4361
|
+
Resolve the artifact producer of the given artifact.
|
|
4362
|
+
If the artifact's producer is a run, the artifact is registered with the original producer.
|
|
4363
|
+
Otherwise, the artifact is registered with the current project as the producer.
|
|
4364
|
+
|
|
4365
|
+
:param artifact: The artifact to resolve its producer.
|
|
4366
|
+
:param project_producer_tag: The tag to use for the project as the producer. If not provided, a tag will be
|
|
4367
|
+
generated for the project.
|
|
4368
|
+
:return: A tuple of the resolved producer and whether it is retained or not.
|
|
4369
|
+
"""
|
|
4370
|
+
|
|
4371
|
+
if not isinstance(artifact, str) and artifact.spec.producer:
|
|
4372
|
+
# if the artifact was imported from a yaml file, the producer can be a dict
|
|
4373
|
+
if isinstance(artifact.spec.producer, ArtifactProducer):
|
|
4374
|
+
producer_dict = artifact.spec.producer.get_meta()
|
|
4375
|
+
else:
|
|
4376
|
+
producer_dict = artifact.spec.producer
|
|
4377
|
+
|
|
4378
|
+
producer_tag = producer_dict.get("tag", None)
|
|
4379
|
+
producer_project = producer_dict.get("project", None)
|
|
4380
|
+
if not producer_tag or not producer_project:
|
|
4381
|
+
# try resolving the producer tag from the uri
|
|
4382
|
+
producer_uri = artifact.spec.producer.get("uri", "")
|
|
4383
|
+
producer_project, producer_tag, _ = ArtifactProducer.parse_uri(
|
|
4384
|
+
producer_uri
|
|
4385
|
+
)
|
|
4386
|
+
|
|
4387
|
+
if producer_dict.get("kind", "") == "run":
|
|
4388
|
+
return ArtifactProducer(
|
|
4389
|
+
name=producer_dict.get("name", ""),
|
|
4390
|
+
kind=producer_dict.get("kind", ""),
|
|
4391
|
+
project=producer_project,
|
|
4392
|
+
tag=producer_tag,
|
|
4393
|
+
owner=producer_dict.get("owner", ""),
|
|
4394
|
+
), True
|
|
4395
|
+
|
|
4396
|
+
# do not retain the artifact's producer, replace it with the project as the producer
|
|
4397
|
+
project_producer_tag = project_producer_tag or self._get_project_tag()
|
|
4398
|
+
return ArtifactProducer(
|
|
4399
|
+
kind="project",
|
|
4400
|
+
name=self.metadata.name,
|
|
4401
|
+
project=self.metadata.name,
|
|
4402
|
+
tag=project_producer_tag,
|
|
4403
|
+
owner=self._resolve_artifact_owner(),
|
|
4404
|
+
), False
|
|
4405
|
+
|
|
4406
|
+
def _resolve_existing_artifact(
|
|
4407
|
+
self,
|
|
4408
|
+
item: typing.Union[str, Artifact],
|
|
4409
|
+
tag: str = None,
|
|
4410
|
+
) -> typing.Optional[Artifact]:
|
|
4411
|
+
"""
|
|
4412
|
+
Check if there is and existing artifact with the given item and tag.
|
|
4413
|
+
If there is, return the existing artifact. Otherwise, return None.
|
|
4414
|
+
|
|
4415
|
+
:param item: The item (or key) to check if there is an existing artifact for.
|
|
4416
|
+
:param tag: The tag to check if there is an existing artifact for.
|
|
4417
|
+
:return: The existing artifact if there is one, otherwise None.
|
|
4418
|
+
"""
|
|
4419
|
+
try:
|
|
4420
|
+
if isinstance(item, str):
|
|
4421
|
+
existing_artifact = self.get_artifact(key=item, tag=tag)
|
|
4422
|
+
else:
|
|
4423
|
+
existing_artifact = self.get_artifact(
|
|
4424
|
+
key=item.key,
|
|
4425
|
+
tag=item.tag,
|
|
4426
|
+
iter=item.iter,
|
|
4427
|
+
tree=item.tree,
|
|
4428
|
+
)
|
|
4429
|
+
if existing_artifact is not None:
|
|
4430
|
+
return existing_artifact.from_dict(existing_artifact)
|
|
4431
|
+
except mlrun.errors.MLRunNotFoundError:
|
|
4432
|
+
logger.debug(
|
|
4433
|
+
"No existing artifact was found",
|
|
4434
|
+
key=item if isinstance(item, str) else item.key,
|
|
4435
|
+
tag=tag if isinstance(item, str) else item.tag,
|
|
4436
|
+
tree=None if isinstance(item, str) else item.tree,
|
|
4437
|
+
)
|
|
4438
|
+
return None
|
|
4439
|
+
|
|
4440
|
+
def _get_project_tag(self):
|
|
4441
|
+
return self._get_hexsha() or str(uuid.uuid4())
|
|
4442
|
+
|
|
4443
|
+
def _resolve_artifact_owner(self):
|
|
4444
|
+
return os.getenv("V3IO_USERNAME") or self.spec.owner
|
|
4445
|
+
|
|
3633
4446
|
|
|
3634
4447
|
def _set_as_current_default_project(project: MlrunProject):
|
|
3635
4448
|
mlrun.mlconf.default_project = project.metadata.name
|
|
@@ -3640,7 +4453,7 @@ def _init_function_from_dict(
|
|
|
3640
4453
|
f: dict,
|
|
3641
4454
|
project: MlrunProject,
|
|
3642
4455
|
name: typing.Optional[str] = None,
|
|
3643
|
-
) ->
|
|
4456
|
+
) -> tuple[str, mlrun.runtimes.BaseRuntime]:
|
|
3644
4457
|
name = name or f.get("name", "")
|
|
3645
4458
|
url = f.get("url", "")
|
|
3646
4459
|
kind = f.get("kind", "")
|
|
@@ -3652,10 +4465,6 @@ def _init_function_from_dict(
|
|
|
3652
4465
|
tag = f.get("tag", None)
|
|
3653
4466
|
|
|
3654
4467
|
has_module = _has_module(handler, kind)
|
|
3655
|
-
if not url and "spec" not in f and not has_module:
|
|
3656
|
-
# function must point to a file or a module or have a spec
|
|
3657
|
-
raise ValueError("Function missing a url or a spec or a module")
|
|
3658
|
-
|
|
3659
4468
|
relative_url = url
|
|
3660
4469
|
url, in_context = project.get_item_absolute_path(url)
|
|
3661
4470
|
|
|
@@ -3685,18 +4494,17 @@ def _init_function_from_dict(
|
|
|
3685
4494
|
)
|
|
3686
4495
|
|
|
3687
4496
|
elif url.endswith(".py"):
|
|
3688
|
-
# when load_source_on_run is used we allow not providing image as code will be loaded pre-run. ML-4994
|
|
3689
|
-
if (
|
|
3690
|
-
not image
|
|
3691
|
-
and not project.default_image
|
|
3692
|
-
and kind != "local"
|
|
3693
|
-
and not project.spec.load_source_on_run
|
|
3694
|
-
):
|
|
3695
|
-
raise ValueError(
|
|
3696
|
-
"image must be provided with py code files which do not "
|
|
3697
|
-
"run on 'local' engine kind"
|
|
3698
|
-
)
|
|
3699
4497
|
if in_context and with_repo:
|
|
4498
|
+
# when load_source_on_run is used we allow not providing image as code will be loaded pre-run. ML-4994
|
|
4499
|
+
if (
|
|
4500
|
+
not image
|
|
4501
|
+
and not project.default_image
|
|
4502
|
+
and kind != "local"
|
|
4503
|
+
and not project.spec.load_source_on_run
|
|
4504
|
+
):
|
|
4505
|
+
raise ValueError(
|
|
4506
|
+
"image must be provided with py code files which do not run on 'local' engine kind"
|
|
4507
|
+
)
|
|
3700
4508
|
func = new_function(
|
|
3701
4509
|
name,
|
|
3702
4510
|
command=relative_url,
|
|
@@ -3715,6 +4523,17 @@ def _init_function_from_dict(
|
|
|
3715
4523
|
tag=tag,
|
|
3716
4524
|
)
|
|
3717
4525
|
|
|
4526
|
+
elif kind in mlrun.runtimes.RuntimeKinds.nuclio_runtimes():
|
|
4527
|
+
func = new_function(
|
|
4528
|
+
name,
|
|
4529
|
+
image=image,
|
|
4530
|
+
kind=kind,
|
|
4531
|
+
handler=handler,
|
|
4532
|
+
tag=tag,
|
|
4533
|
+
)
|
|
4534
|
+
if image and kind != mlrun.runtimes.RuntimeKinds.application:
|
|
4535
|
+
logger.info("Function code not specified, setting entry point to image")
|
|
4536
|
+
func.from_image(image)
|
|
3718
4537
|
else:
|
|
3719
4538
|
raise ValueError(f"Unsupported function url:handler {url}:{handler} or no spec")
|
|
3720
4539
|
|
|
@@ -3735,7 +4554,7 @@ def _init_function_from_obj(
|
|
|
3735
4554
|
func: mlrun.runtimes.BaseRuntime,
|
|
3736
4555
|
project: MlrunProject,
|
|
3737
4556
|
name: typing.Optional[str] = None,
|
|
3738
|
-
) ->
|
|
4557
|
+
) -> tuple[str, mlrun.runtimes.BaseRuntime]:
|
|
3739
4558
|
build = func.spec.build
|
|
3740
4559
|
if project.spec.origin_url:
|
|
3741
4560
|
origin = project.spec.origin_url
|
|
@@ -3760,9 +4579,17 @@ def _init_function_from_obj(
|
|
|
3760
4579
|
def _has_module(handler, kind):
|
|
3761
4580
|
if not handler:
|
|
3762
4581
|
return False
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
|
|
4582
|
+
|
|
4583
|
+
if (
|
|
4584
|
+
kind in mlrun.runtimes.RuntimeKinds.pure_nuclio_deployed_runtimes()
|
|
4585
|
+
and ":" in handler
|
|
4586
|
+
):
|
|
4587
|
+
return True
|
|
4588
|
+
|
|
4589
|
+
if "." in handler:
|
|
4590
|
+
return True
|
|
4591
|
+
|
|
4592
|
+
return False
|
|
3766
4593
|
|
|
3767
4594
|
|
|
3768
4595
|
def _is_imported_artifact(artifact):
|