mlrun 1.6.4rc2__py3-none-any.whl → 1.7.0rc20__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of mlrun might be problematic. Click here for more details.
- mlrun/__init__.py +11 -1
- mlrun/__main__.py +26 -112
- mlrun/alerts/__init__.py +15 -0
- mlrun/alerts/alert.py +144 -0
- mlrun/api/schemas/__init__.py +5 -4
- mlrun/artifacts/__init__.py +8 -3
- mlrun/artifacts/base.py +46 -257
- mlrun/artifacts/dataset.py +11 -192
- mlrun/artifacts/manager.py +47 -48
- mlrun/artifacts/model.py +31 -159
- mlrun/artifacts/plots.py +23 -380
- mlrun/common/constants.py +69 -0
- mlrun/common/db/sql_session.py +2 -3
- mlrun/common/formatters/__init__.py +19 -0
- mlrun/common/formatters/artifact.py +21 -0
- mlrun/common/formatters/base.py +78 -0
- mlrun/common/formatters/function.py +41 -0
- mlrun/common/formatters/pipeline.py +53 -0
- mlrun/common/formatters/project.py +51 -0
- mlrun/common/helpers.py +1 -2
- mlrun/common/model_monitoring/helpers.py +9 -5
- mlrun/{runtimes → common/runtimes}/constants.py +37 -9
- mlrun/common/schemas/__init__.py +24 -4
- mlrun/common/schemas/alert.py +203 -0
- mlrun/common/schemas/api_gateway.py +148 -0
- mlrun/common/schemas/artifact.py +18 -8
- mlrun/common/schemas/auth.py +11 -5
- mlrun/common/schemas/background_task.py +1 -1
- mlrun/common/schemas/client_spec.py +4 -1
- mlrun/common/schemas/feature_store.py +16 -16
- mlrun/common/schemas/frontend_spec.py +8 -7
- mlrun/common/schemas/function.py +5 -1
- mlrun/common/schemas/hub.py +11 -18
- mlrun/common/schemas/memory_reports.py +2 -2
- mlrun/common/schemas/model_monitoring/__init__.py +18 -3
- mlrun/common/schemas/model_monitoring/constants.py +83 -26
- mlrun/common/schemas/model_monitoring/grafana.py +13 -9
- mlrun/common/schemas/model_monitoring/model_endpoints.py +99 -16
- mlrun/common/schemas/notification.py +4 -4
- mlrun/common/schemas/object.py +2 -2
- mlrun/{runtimes/mpijob/v1alpha1.py → common/schemas/pagination.py} +10 -13
- mlrun/common/schemas/pipeline.py +1 -10
- mlrun/common/schemas/project.py +24 -23
- mlrun/common/schemas/runtime_resource.py +8 -12
- mlrun/common/schemas/schedule.py +3 -3
- mlrun/common/schemas/tag.py +1 -2
- mlrun/common/schemas/workflow.py +2 -2
- mlrun/common/types.py +7 -1
- mlrun/config.py +54 -17
- mlrun/data_types/to_pandas.py +10 -12
- mlrun/datastore/__init__.py +5 -8
- mlrun/datastore/alibaba_oss.py +130 -0
- mlrun/datastore/azure_blob.py +17 -5
- mlrun/datastore/base.py +62 -39
- mlrun/datastore/datastore.py +28 -9
- mlrun/datastore/datastore_profile.py +146 -20
- mlrun/datastore/filestore.py +0 -1
- mlrun/datastore/google_cloud_storage.py +6 -2
- mlrun/datastore/hdfs.py +56 -0
- mlrun/datastore/inmem.py +2 -2
- mlrun/datastore/redis.py +6 -2
- mlrun/datastore/s3.py +9 -0
- mlrun/datastore/snowflake_utils.py +43 -0
- mlrun/datastore/sources.py +201 -96
- mlrun/datastore/spark_utils.py +1 -2
- mlrun/datastore/store_resources.py +7 -7
- mlrun/datastore/targets.py +358 -104
- mlrun/datastore/utils.py +72 -58
- mlrun/datastore/v3io.py +5 -1
- mlrun/db/base.py +185 -35
- mlrun/db/factory.py +1 -1
- mlrun/db/httpdb.py +614 -179
- mlrun/db/nopdb.py +210 -26
- mlrun/errors.py +12 -1
- mlrun/execution.py +41 -24
- mlrun/feature_store/__init__.py +0 -2
- mlrun/feature_store/api.py +40 -72
- mlrun/feature_store/common.py +1 -1
- mlrun/feature_store/feature_set.py +76 -55
- mlrun/feature_store/feature_vector.py +28 -30
- mlrun/feature_store/ingestion.py +7 -6
- mlrun/feature_store/retrieval/base.py +16 -11
- mlrun/feature_store/retrieval/conversion.py +11 -13
- mlrun/feature_store/retrieval/dask_merger.py +2 -0
- mlrun/feature_store/retrieval/job.py +9 -3
- mlrun/feature_store/retrieval/local_merger.py +2 -0
- mlrun/feature_store/retrieval/spark_merger.py +34 -24
- mlrun/feature_store/steps.py +37 -34
- mlrun/features.py +9 -20
- 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 +2 -3
- 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 +1 -1
- 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 +4 -3
- 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 +3 -6
- 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 +14 -16
- mlrun/launcher/__init__.py +1 -1
- mlrun/launcher/base.py +16 -15
- mlrun/launcher/client.py +8 -6
- mlrun/launcher/factory.py +1 -1
- mlrun/launcher/local.py +17 -11
- mlrun/launcher/remote.py +16 -10
- mlrun/lists.py +7 -6
- mlrun/model.py +238 -73
- mlrun/model_monitoring/__init__.py +1 -1
- mlrun/model_monitoring/api.py +138 -315
- mlrun/model_monitoring/application.py +5 -296
- mlrun/model_monitoring/applications/__init__.py +24 -0
- mlrun/model_monitoring/applications/_application_steps.py +157 -0
- mlrun/model_monitoring/applications/base.py +282 -0
- mlrun/model_monitoring/applications/context.py +214 -0
- mlrun/model_monitoring/applications/evidently_base.py +211 -0
- mlrun/model_monitoring/applications/histogram_data_drift.py +349 -0
- mlrun/model_monitoring/applications/results.py +99 -0
- mlrun/model_monitoring/controller.py +104 -84
- mlrun/model_monitoring/controller_handler.py +13 -5
- mlrun/model_monitoring/db/__init__.py +18 -0
- mlrun/model_monitoring/{stores → db/stores}/__init__.py +43 -36
- mlrun/model_monitoring/db/stores/base/__init__.py +15 -0
- mlrun/model_monitoring/{stores/model_endpoint_store.py → db/stores/base/store.py} +64 -40
- mlrun/model_monitoring/db/stores/sqldb/__init__.py +13 -0
- mlrun/model_monitoring/db/stores/sqldb/models/__init__.py +71 -0
- mlrun/model_monitoring/{stores → db/stores/sqldb}/models/base.py +109 -5
- mlrun/model_monitoring/db/stores/sqldb/models/mysql.py +88 -0
- mlrun/model_monitoring/{stores/models/mysql.py → db/stores/sqldb/models/sqlite.py} +19 -13
- mlrun/model_monitoring/db/stores/sqldb/sql_store.py +684 -0
- mlrun/model_monitoring/db/stores/v3io_kv/__init__.py +13 -0
- mlrun/model_monitoring/{stores/kv_model_endpoint_store.py → db/stores/v3io_kv/kv_store.py} +310 -165
- mlrun/model_monitoring/db/tsdb/__init__.py +100 -0
- mlrun/model_monitoring/db/tsdb/base.py +329 -0
- mlrun/model_monitoring/db/tsdb/helpers.py +30 -0
- mlrun/model_monitoring/db/tsdb/tdengine/__init__.py +15 -0
- mlrun/model_monitoring/db/tsdb/tdengine/schemas.py +240 -0
- mlrun/model_monitoring/db/tsdb/tdengine/stream_graph_steps.py +45 -0
- mlrun/model_monitoring/db/tsdb/tdengine/tdengine_connector.py +397 -0
- mlrun/model_monitoring/db/tsdb/v3io/__init__.py +15 -0
- mlrun/model_monitoring/db/tsdb/v3io/stream_graph_steps.py +117 -0
- mlrun/model_monitoring/db/tsdb/v3io/v3io_connector.py +630 -0
- mlrun/model_monitoring/evidently_application.py +6 -118
- mlrun/model_monitoring/features_drift_table.py +134 -106
- mlrun/model_monitoring/helpers.py +127 -28
- 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/prometheus.py +1 -4
- mlrun/model_monitoring/stream_processing.py +62 -231
- mlrun/model_monitoring/tracking_policy.py +9 -2
- mlrun/model_monitoring/writer.py +152 -124
- 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 +6 -6
- 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 +35 -21
- mlrun/projects/pipelines.py +68 -99
- mlrun/projects/project.py +830 -266
- mlrun/render.py +3 -11
- mlrun/run.py +162 -166
- mlrun/runtimes/__init__.py +62 -7
- mlrun/runtimes/base.py +39 -32
- mlrun/runtimes/daskjob.py +8 -8
- mlrun/runtimes/databricks_job/databricks_cancel_task.py +1 -1
- mlrun/runtimes/databricks_job/databricks_runtime.py +7 -7
- mlrun/runtimes/databricks_job/databricks_wrapper.py +1 -1
- mlrun/runtimes/funcdoc.py +0 -28
- mlrun/runtimes/function_reference.py +1 -1
- mlrun/runtimes/kubejob.py +28 -122
- mlrun/runtimes/local.py +6 -3
- 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 +709 -0
- mlrun/runtimes/nuclio/application/__init__.py +15 -0
- mlrun/runtimes/nuclio/application/application.py +523 -0
- mlrun/runtimes/nuclio/application/reverse_proxy.go +95 -0
- mlrun/runtimes/{function.py → nuclio/function.py} +112 -73
- mlrun/runtimes/{nuclio.py → nuclio/nuclio.py} +6 -6
- mlrun/runtimes/{serving.py → nuclio/serving.py} +45 -51
- mlrun/runtimes/pod.py +286 -88
- mlrun/runtimes/remotesparkjob.py +2 -2
- mlrun/runtimes/sparkjob/spark3job.py +51 -34
- mlrun/runtimes/utils.py +7 -75
- mlrun/secrets.py +9 -5
- mlrun/serving/remote.py +2 -7
- mlrun/serving/routers.py +13 -10
- mlrun/serving/server.py +22 -26
- mlrun/serving/states.py +99 -25
- mlrun/serving/utils.py +3 -3
- mlrun/serving/v1_serving.py +6 -7
- mlrun/serving/v2_serving.py +59 -20
- mlrun/track/tracker.py +2 -1
- mlrun/track/tracker_manager.py +3 -3
- mlrun/track/trackers/mlflow_tracker.py +1 -2
- mlrun/utils/async_http.py +5 -7
- mlrun/utils/azure_vault.py +1 -1
- mlrun/utils/clones.py +1 -2
- mlrun/utils/condition_evaluator.py +3 -3
- mlrun/utils/db.py +3 -3
- mlrun/utils/helpers.py +183 -197
- mlrun/utils/http.py +2 -5
- mlrun/utils/logger.py +76 -14
- mlrun/utils/notifications/notification/__init__.py +17 -12
- mlrun/utils/notifications/notification/base.py +14 -2
- mlrun/utils/notifications/notification/console.py +2 -0
- mlrun/utils/notifications/notification/git.py +3 -1
- mlrun/utils/notifications/notification/ipython.py +3 -1
- mlrun/utils/notifications/notification/slack.py +101 -21
- mlrun/utils/notifications/notification/webhook.py +11 -1
- mlrun/utils/notifications/notification_pusher.py +155 -30
- mlrun/utils/retryer.py +208 -0
- mlrun/utils/singleton.py +1 -1
- mlrun/utils/v3io_clients.py +2 -4
- mlrun/utils/version/version.json +2 -2
- mlrun/utils/version/version.py +2 -6
- {mlrun-1.6.4rc2.dist-info → mlrun-1.7.0rc20.dist-info}/METADATA +31 -19
- mlrun-1.7.0rc20.dist-info/RECORD +353 -0
- mlrun/kfpops.py +0 -868
- mlrun/model_monitoring/batch.py +0 -1095
- mlrun/model_monitoring/stores/models/__init__.py +0 -27
- mlrun/model_monitoring/stores/sql_model_endpoint_store.py +0 -384
- mlrun/platforms/other.py +0 -306
- mlrun-1.6.4rc2.dist-info/RECORD +0 -314
- {mlrun-1.6.4rc2.dist-info → mlrun-1.7.0rc20.dist-info}/LICENSE +0 -0
- {mlrun-1.6.4rc2.dist-info → mlrun-1.7.0rc20.dist-info}/WHEEL +0 -0
- {mlrun-1.6.4rc2.dist-info → mlrun-1.7.0rc20.dist-info}/entry_points.txt +0 -0
- {mlrun-1.6.4rc2.dist-info → mlrun-1.7.0rc20.dist-info}/top_level.txt +0 -0
|
@@ -22,9 +22,11 @@ from time import sleep
|
|
|
22
22
|
import nuclio
|
|
23
23
|
import nuclio.utils
|
|
24
24
|
import requests
|
|
25
|
-
import semver
|
|
26
25
|
from aiohttp.client import ClientSession
|
|
27
26
|
from kubernetes import client
|
|
27
|
+
from mlrun_pipelines.common.mounts import VolumeMount
|
|
28
|
+
from mlrun_pipelines.common.ops import deploy_op
|
|
29
|
+
from mlrun_pipelines.mounts import mount_v3io, v3io_cred
|
|
28
30
|
from nuclio.deploy import find_dashboard_url, get_deploy_status
|
|
29
31
|
from nuclio.triggers import V3IOStreamTrigger
|
|
30
32
|
|
|
@@ -34,56 +36,27 @@ import mlrun.k8s_utils
|
|
|
34
36
|
import mlrun.utils
|
|
35
37
|
import mlrun.utils.helpers
|
|
36
38
|
from mlrun.common.schemas import AuthInfo
|
|
37
|
-
|
|
38
|
-
from
|
|
39
|
-
from
|
|
40
|
-
from
|
|
41
|
-
from
|
|
42
|
-
from ..model import RunObject
|
|
43
|
-
from ..platforms.iguazio import (
|
|
44
|
-
VolumeMount,
|
|
45
|
-
mount_v3io,
|
|
39
|
+
from mlrun.config import config as mlconf
|
|
40
|
+
from mlrun.errors import err_to_str
|
|
41
|
+
from mlrun.lists import RunList
|
|
42
|
+
from mlrun.model import RunObject
|
|
43
|
+
from mlrun.platforms.iguazio import (
|
|
46
44
|
parse_path,
|
|
47
45
|
split_path,
|
|
48
|
-
v3io_cred,
|
|
49
46
|
)
|
|
50
|
-
from
|
|
51
|
-
from .
|
|
52
|
-
from .
|
|
53
|
-
from .utils import
|
|
47
|
+
from mlrun.runtimes.base import FunctionStatus, RunError
|
|
48
|
+
from mlrun.runtimes.pod import KubeResource, KubeResourceSpec
|
|
49
|
+
from mlrun.runtimes.utils import get_item_name, log_std
|
|
50
|
+
from mlrun.utils import get_in, logger, update_in
|
|
54
51
|
|
|
55
52
|
|
|
56
53
|
def validate_nuclio_version_compatibility(*min_versions):
|
|
57
54
|
"""
|
|
58
55
|
:param min_versions: Valid minimum version(s) required, assuming no 2 versions has equal major and minor.
|
|
59
56
|
"""
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
try:
|
|
64
|
-
parsed_current_version = semver.VersionInfo.parse(mlconf.nuclio_version)
|
|
65
|
-
except ValueError:
|
|
66
|
-
# only log when version is set but invalid
|
|
67
|
-
if mlconf.nuclio_version:
|
|
68
|
-
logger.warning(
|
|
69
|
-
"Unable to parse nuclio version, assuming compatibility",
|
|
70
|
-
nuclio_version=mlconf.nuclio_version,
|
|
71
|
-
min_versions=min_versions,
|
|
72
|
-
)
|
|
73
|
-
return True
|
|
74
|
-
|
|
75
|
-
parsed_min_versions.sort(reverse=True)
|
|
76
|
-
for parsed_min_version in parsed_min_versions:
|
|
77
|
-
if (
|
|
78
|
-
parsed_current_version.major == parsed_min_version.major
|
|
79
|
-
and parsed_current_version.minor == parsed_min_version.minor
|
|
80
|
-
and parsed_current_version.patch < parsed_min_version.patch
|
|
81
|
-
):
|
|
82
|
-
return False
|
|
83
|
-
|
|
84
|
-
if parsed_current_version >= parsed_min_version:
|
|
85
|
-
return True
|
|
86
|
-
return False
|
|
57
|
+
return mlrun.utils.helpers.validate_component_version_compatibility(
|
|
58
|
+
"nuclio", *min_versions
|
|
59
|
+
)
|
|
87
60
|
|
|
88
61
|
|
|
89
62
|
def min_nuclio_versions(*versions):
|
|
@@ -92,10 +65,7 @@ def min_nuclio_versions(*versions):
|
|
|
92
65
|
if validate_nuclio_version_compatibility(*versions):
|
|
93
66
|
return function(*args, **kwargs)
|
|
94
67
|
|
|
95
|
-
message = (
|
|
96
|
-
f"{function.__name__} is supported since nuclio {' or '.join(versions)}, currently using "
|
|
97
|
-
f"nuclio {mlconf.nuclio_version}, please upgrade."
|
|
98
|
-
)
|
|
68
|
+
message = f"'{function.__qualname__}' function requires Nuclio v{' or v'.join(versions)} or higher"
|
|
99
69
|
raise mlrun.errors.MLRunIncompatibleVersionError(message)
|
|
100
70
|
|
|
101
71
|
return wrapper
|
|
@@ -292,6 +262,9 @@ class RemoteRuntime(KubeResource):
|
|
|
292
262
|
def status(self, status):
|
|
293
263
|
self._status = self._verify_dict(status, "status", NuclioStatus)
|
|
294
264
|
|
|
265
|
+
def pre_deploy_validation(self):
|
|
266
|
+
pass
|
|
267
|
+
|
|
295
268
|
def set_config(self, key, value):
|
|
296
269
|
self.spec.config[key] = value
|
|
297
270
|
return self
|
|
@@ -343,17 +316,21 @@ class RemoteRuntime(KubeResource):
|
|
|
343
316
|
|
|
344
317
|
git::
|
|
345
318
|
|
|
346
|
-
fn.with_source_archive(
|
|
347
|
-
|
|
348
|
-
|
|
319
|
+
fn.with_source_archive(
|
|
320
|
+
"git://github.com/org/repo#my-branch",
|
|
321
|
+
handler="main:handler",
|
|
322
|
+
workdir="path/inside/repo",
|
|
323
|
+
)
|
|
349
324
|
|
|
350
325
|
s3::
|
|
351
326
|
|
|
352
327
|
fn.spec.nuclio_runtime = "golang"
|
|
353
|
-
fn.with_source_archive(
|
|
328
|
+
fn.with_source_archive(
|
|
329
|
+
"s3://my-bucket/path/in/bucket/my-functions-archive",
|
|
354
330
|
handler="my_func:Handler",
|
|
355
331
|
workdir="path/inside/functions/archive",
|
|
356
|
-
runtime="golang"
|
|
332
|
+
runtime="golang",
|
|
333
|
+
)
|
|
357
334
|
"""
|
|
358
335
|
self.spec.build.source = source
|
|
359
336
|
# update handler in function_handler
|
|
@@ -386,7 +363,7 @@ class RemoteRuntime(KubeResource):
|
|
|
386
363
|
workers: typing.Optional[int] = 8,
|
|
387
364
|
port: typing.Optional[int] = None,
|
|
388
365
|
host: typing.Optional[str] = None,
|
|
389
|
-
paths: typing.Optional[
|
|
366
|
+
paths: typing.Optional[list[str]] = None,
|
|
390
367
|
canary: typing.Optional[float] = None,
|
|
391
368
|
secret: typing.Optional[str] = None,
|
|
392
369
|
worker_timeout: typing.Optional[int] = None,
|
|
@@ -541,11 +518,16 @@ class RemoteRuntime(KubeResource):
|
|
|
541
518
|
:param project: project name
|
|
542
519
|
:param tag: function tag
|
|
543
520
|
:param verbose: set True for verbose logging
|
|
544
|
-
:param auth_info: service AuthInfo
|
|
521
|
+
:param auth_info: service AuthInfo (deprecated and ignored)
|
|
545
522
|
:param builder_env: env vars dict for source archive config/credentials e.g. builder_env={"GIT_TOKEN": token}
|
|
546
523
|
:param force_build: set True for force building the image
|
|
547
524
|
"""
|
|
548
|
-
|
|
525
|
+
if auth_info:
|
|
526
|
+
# TODO: remove in 1.9.0
|
|
527
|
+
warnings.warn(
|
|
528
|
+
"'auth_info' is deprecated for nuclio runtimes in 1.7.0 and will be removed in 1.9.0",
|
|
529
|
+
FutureWarning,
|
|
530
|
+
)
|
|
549
531
|
|
|
550
532
|
old_http_session = getattr(self, "_http_session", None)
|
|
551
533
|
if old_http_session:
|
|
@@ -562,15 +544,12 @@ class RemoteRuntime(KubeResource):
|
|
|
562
544
|
if tag:
|
|
563
545
|
self.metadata.tag = tag
|
|
564
546
|
|
|
565
|
-
save_record = False
|
|
566
547
|
# Attempt auto-mounting, before sending to remote build
|
|
567
548
|
self.try_auto_mount_based_on_config()
|
|
568
549
|
self._fill_credentials()
|
|
569
550
|
db = self._get_db()
|
|
570
551
|
logger.info("Starting remote function deploy")
|
|
571
|
-
data = db.
|
|
572
|
-
self, False, builder_env=builder_env, force_build=force_build
|
|
573
|
-
)
|
|
552
|
+
data = db.deploy_nuclio_function(func=self, builder_env=builder_env)
|
|
574
553
|
self.status = data["data"].get("status")
|
|
575
554
|
self._update_credentials_from_remote_build(data["data"])
|
|
576
555
|
|
|
@@ -582,15 +561,18 @@ class RemoteRuntime(KubeResource):
|
|
|
582
561
|
# now, functions can be not exposed (using service type ClusterIP) and hence
|
|
583
562
|
# for BC we first try to populate the external invocation url, and then
|
|
584
563
|
# if not exists, take the internal invocation url
|
|
585
|
-
if
|
|
564
|
+
if (
|
|
565
|
+
self.status.external_invocation_urls
|
|
566
|
+
and self.status.external_invocation_urls[0] != ""
|
|
567
|
+
):
|
|
586
568
|
self.spec.command = f"http://{self.status.external_invocation_urls[0]}"
|
|
587
|
-
|
|
588
|
-
|
|
569
|
+
elif (
|
|
570
|
+
self.status.internal_invocation_urls
|
|
571
|
+
and self.status.internal_invocation_urls[0] != ""
|
|
572
|
+
):
|
|
589
573
|
self.spec.command = f"http://{self.status.internal_invocation_urls[0]}"
|
|
590
|
-
|
|
591
|
-
elif self.status.address:
|
|
574
|
+
elif self.status.address and self.status.address != "":
|
|
592
575
|
self.spec.command = f"http://{self.status.address}"
|
|
593
|
-
save_record = True
|
|
594
576
|
|
|
595
577
|
logger.info(
|
|
596
578
|
"Successfully deployed function",
|
|
@@ -598,13 +580,11 @@ class RemoteRuntime(KubeResource):
|
|
|
598
580
|
external_invocation_urls=self.status.external_invocation_urls,
|
|
599
581
|
)
|
|
600
582
|
|
|
601
|
-
|
|
602
|
-
self.save(versioned=False)
|
|
583
|
+
self.save(versioned=False)
|
|
603
584
|
|
|
604
585
|
return self.spec.command
|
|
605
586
|
|
|
606
587
|
def _wait_for_function_deployment(self, db, verbose=False):
|
|
607
|
-
text = ""
|
|
608
588
|
state = ""
|
|
609
589
|
last_log_timestamp = 1
|
|
610
590
|
while state not in ["ready", "error", "unhealthy"]:
|
|
@@ -612,7 +592,7 @@ class RemoteRuntime(KubeResource):
|
|
|
612
592
|
int(mlrun.mlconf.httpdb.logs.nuclio.pull_deploy_status_default_interval)
|
|
613
593
|
)
|
|
614
594
|
try:
|
|
615
|
-
text, last_log_timestamp = db.
|
|
595
|
+
text, last_log_timestamp = db.get_nuclio_deploy_status(
|
|
616
596
|
self, last_log_timestamp=last_log_timestamp, verbose=verbose
|
|
617
597
|
)
|
|
618
598
|
except mlrun.db.RunDBError:
|
|
@@ -629,9 +609,9 @@ class RemoteRuntime(KubeResource):
|
|
|
629
609
|
def with_node_selection(
|
|
630
610
|
self,
|
|
631
611
|
node_name: typing.Optional[str] = None,
|
|
632
|
-
node_selector: typing.Optional[
|
|
612
|
+
node_selector: typing.Optional[dict[str, str]] = None,
|
|
633
613
|
affinity: typing.Optional[client.V1Affinity] = None,
|
|
634
|
-
tolerations: typing.Optional[
|
|
614
|
+
tolerations: typing.Optional[list[client.V1Toleration]] = None,
|
|
635
615
|
):
|
|
636
616
|
"""k8s node selection attributes"""
|
|
637
617
|
if tolerations and not validate_nuclio_version_compatibility("1.7.5"):
|
|
@@ -683,7 +663,7 @@ class RemoteRuntime(KubeResource):
|
|
|
683
663
|
|
|
684
664
|
def set_state_thresholds(
|
|
685
665
|
self,
|
|
686
|
-
state_thresholds:
|
|
666
|
+
state_thresholds: dict[str, int],
|
|
687
667
|
patch: bool = True,
|
|
688
668
|
):
|
|
689
669
|
raise NotImplementedError(
|
|
@@ -716,7 +696,7 @@ class RemoteRuntime(KubeResource):
|
|
|
716
696
|
raise_on_exception=True,
|
|
717
697
|
resolve_address=True,
|
|
718
698
|
auth_info: AuthInfo = None,
|
|
719
|
-
) ->
|
|
699
|
+
) -> tuple[str, str, typing.Optional[float]]:
|
|
720
700
|
if dashboard:
|
|
721
701
|
(
|
|
722
702
|
state,
|
|
@@ -770,10 +750,13 @@ class RemoteRuntime(KubeResource):
|
|
|
770
750
|
runtime_env["MLRUN_NAMESPACE"] = mlconf.namespace
|
|
771
751
|
if self.metadata.credentials.access_key:
|
|
772
752
|
runtime_env[
|
|
773
|
-
mlrun.runtimes.constants.FunctionEnvironmentVariables.auth_session
|
|
753
|
+
mlrun.common.runtimes.constants.FunctionEnvironmentVariables.auth_session
|
|
774
754
|
] = self.metadata.credentials.access_key
|
|
775
755
|
return runtime_env
|
|
776
756
|
|
|
757
|
+
def _get_serving_spec(self):
|
|
758
|
+
return None
|
|
759
|
+
|
|
777
760
|
def _get_nuclio_config_spec_env(self):
|
|
778
761
|
env_dict = {}
|
|
779
762
|
external_source_env_dict = {}
|
|
@@ -959,6 +942,62 @@ class RemoteRuntime(KubeResource):
|
|
|
959
942
|
data = json.loads(data)
|
|
960
943
|
return data
|
|
961
944
|
|
|
945
|
+
def with_sidecar(
|
|
946
|
+
self,
|
|
947
|
+
name: str = None,
|
|
948
|
+
image: str = None,
|
|
949
|
+
ports: typing.Optional[typing.Union[int, list[int]]] = None,
|
|
950
|
+
command: typing.Optional[str] = None,
|
|
951
|
+
args: typing.Optional[list[str]] = None,
|
|
952
|
+
):
|
|
953
|
+
"""
|
|
954
|
+
Add a sidecar container to the function pod
|
|
955
|
+
:param name: Sidecar container name.
|
|
956
|
+
:param image: Sidecar container image.
|
|
957
|
+
:param ports: Sidecar container ports to expose. Can be a single port or a list of ports.
|
|
958
|
+
:param command: Sidecar container command instead of the image entrypoint.
|
|
959
|
+
:param args: Sidecar container command args (requires command to be set).
|
|
960
|
+
"""
|
|
961
|
+
name = name or f"{self.metadata.name}-sidecar"
|
|
962
|
+
sidecar = self._set_sidecar(name)
|
|
963
|
+
if image:
|
|
964
|
+
sidecar["image"] = image
|
|
965
|
+
|
|
966
|
+
ports = mlrun.utils.helpers.as_list(ports)
|
|
967
|
+
# according to RFC-6335, port name should be less than 15 characters,
|
|
968
|
+
# so we truncate it if needed and leave room for the index
|
|
969
|
+
port_name = name[:13].rstrip("-_") if len(name) > 13 else name
|
|
970
|
+
sidecar["ports"] = [
|
|
971
|
+
{
|
|
972
|
+
"name": f"{port_name}-{i}",
|
|
973
|
+
"containerPort": port,
|
|
974
|
+
"protocol": "TCP",
|
|
975
|
+
}
|
|
976
|
+
for i, port in enumerate(ports)
|
|
977
|
+
]
|
|
978
|
+
|
|
979
|
+
# if it is a redeploy, 'command' might be set with the previous invocation url.
|
|
980
|
+
# in this case, we don't want to use it as the sidecar command
|
|
981
|
+
if command and not command.startswith("http"):
|
|
982
|
+
sidecar["command"] = mlrun.utils.helpers.as_list(command)
|
|
983
|
+
|
|
984
|
+
if args and sidecar["command"]:
|
|
985
|
+
sidecar["args"] = mlrun.utils.helpers.as_list(args)
|
|
986
|
+
|
|
987
|
+
# populate the sidecar resources from the function spec
|
|
988
|
+
if self.spec.resources:
|
|
989
|
+
sidecar["resources"] = self.spec.resources
|
|
990
|
+
|
|
991
|
+
def _set_sidecar(self, name: str) -> dict:
|
|
992
|
+
self.spec.config.setdefault("spec.sidecars", [])
|
|
993
|
+
sidecars = self.spec.config["spec.sidecars"]
|
|
994
|
+
for sidecar in sidecars:
|
|
995
|
+
if sidecar["name"] == name:
|
|
996
|
+
return sidecar
|
|
997
|
+
|
|
998
|
+
sidecars.append({"name": name})
|
|
999
|
+
return sidecars[-1]
|
|
1000
|
+
|
|
962
1001
|
def _trigger_of_kind_exists(self, kind: str) -> bool:
|
|
963
1002
|
if not self.spec.config:
|
|
964
1003
|
return False
|
|
@@ -17,13 +17,13 @@ import os
|
|
|
17
17
|
import socket
|
|
18
18
|
|
|
19
19
|
import mlrun.db
|
|
20
|
+
from mlrun.errors import err_to_str
|
|
21
|
+
from mlrun.execution import MLClientCtx
|
|
22
|
+
from mlrun.model import RunTemplate
|
|
23
|
+
from mlrun.runtimes.local import get_func_arg
|
|
24
|
+
from mlrun.serving.server import v2_serving_init
|
|
25
|
+
from mlrun.serving.v1_serving import nuclio_serving_init
|
|
20
26
|
|
|
21
|
-
from ..errors import err_to_str
|
|
22
|
-
from ..execution import MLClientCtx
|
|
23
|
-
from ..model import RunTemplate
|
|
24
|
-
from ..serving.server import v2_serving_init
|
|
25
|
-
from ..serving.v1_serving import nuclio_serving_init
|
|
26
|
-
from .local import get_func_arg
|
|
27
27
|
from .serving import serving_subkind
|
|
28
28
|
|
|
29
29
|
|
|
@@ -14,21 +14,21 @@
|
|
|
14
14
|
|
|
15
15
|
import json
|
|
16
16
|
import os
|
|
17
|
+
import warnings
|
|
17
18
|
from copy import deepcopy
|
|
18
|
-
from typing import
|
|
19
|
+
from typing import TYPE_CHECKING, Optional, Union
|
|
19
20
|
|
|
20
21
|
import nuclio
|
|
21
22
|
from nuclio import KafkaTrigger
|
|
22
23
|
|
|
23
24
|
import mlrun
|
|
24
25
|
import mlrun.common.schemas
|
|
25
|
-
from mlrun.
|
|
26
|
-
|
|
27
|
-
from
|
|
28
|
-
from
|
|
29
|
-
from
|
|
30
|
-
from
|
|
31
|
-
from ..serving.states import (
|
|
26
|
+
from mlrun.datastore import get_kafka_brokers_from_dict, parse_kafka_url
|
|
27
|
+
from mlrun.model import ObjectList
|
|
28
|
+
from mlrun.runtimes.function_reference import FunctionReference
|
|
29
|
+
from mlrun.secrets import SecretsStore
|
|
30
|
+
from mlrun.serving.server import GraphServer, create_graph_server
|
|
31
|
+
from mlrun.serving.states import (
|
|
32
32
|
RootFlowStep,
|
|
33
33
|
RouterStep,
|
|
34
34
|
StepKinds,
|
|
@@ -37,12 +37,16 @@ from ..serving.states import (
|
|
|
37
37
|
new_remote_endpoint,
|
|
38
38
|
params_to_step,
|
|
39
39
|
)
|
|
40
|
-
from
|
|
40
|
+
from mlrun.utils import get_caller_globals, logger, set_paths
|
|
41
|
+
|
|
41
42
|
from .function import NuclioSpec, RemoteRuntime
|
|
42
|
-
from .function_reference import FunctionReference
|
|
43
43
|
|
|
44
44
|
serving_subkind = "serving_v2"
|
|
45
45
|
|
|
46
|
+
if TYPE_CHECKING:
|
|
47
|
+
# remove this block in 1.9.0
|
|
48
|
+
from mlrun.model_monitoring import TrackingPolicy
|
|
49
|
+
|
|
46
50
|
|
|
47
51
|
def new_v2_model_server(
|
|
48
52
|
name,
|
|
@@ -216,12 +220,12 @@ class ServingSpec(NuclioSpec):
|
|
|
216
220
|
graph_root_setter(self, graph)
|
|
217
221
|
|
|
218
222
|
@property
|
|
219
|
-
def function_refs(self) ->
|
|
223
|
+
def function_refs(self) -> list[FunctionReference]:
|
|
220
224
|
"""function references, list of optional child function refs"""
|
|
221
225
|
return self._function_refs
|
|
222
226
|
|
|
223
227
|
@function_refs.setter
|
|
224
|
-
def function_refs(self, function_refs:
|
|
228
|
+
def function_refs(self, function_refs: list[FunctionReference]):
|
|
225
229
|
self._function_refs = ObjectList.from_list(FunctionReference, function_refs)
|
|
226
230
|
|
|
227
231
|
|
|
@@ -303,12 +307,12 @@ class ServingRuntime(RemoteRuntime):
|
|
|
303
307
|
|
|
304
308
|
def set_tracking(
|
|
305
309
|
self,
|
|
306
|
-
stream_path: str = None,
|
|
307
|
-
batch: int = None,
|
|
308
|
-
sample: int = None,
|
|
309
|
-
stream_args: dict = None,
|
|
310
|
-
tracking_policy: Union[TrackingPolicy, dict] = None,
|
|
311
|
-
):
|
|
310
|
+
stream_path: Optional[str] = None,
|
|
311
|
+
batch: Optional[int] = None,
|
|
312
|
+
sample: Optional[int] = None,
|
|
313
|
+
stream_args: Optional[dict] = None,
|
|
314
|
+
tracking_policy: Optional[Union["TrackingPolicy", dict]] = None,
|
|
315
|
+
) -> None:
|
|
312
316
|
"""apply on your serving function to monitor a deployed model, including real-time dashboards to detect drift
|
|
313
317
|
and analyze performance.
|
|
314
318
|
|
|
@@ -317,31 +321,17 @@ class ServingRuntime(RemoteRuntime):
|
|
|
317
321
|
:param batch: Micro batch size (send micro batches of N records at a time).
|
|
318
322
|
:param sample: Sample size (send only one of N records).
|
|
319
323
|
:param stream_args: Stream initialization parameters, e.g. shards, retention_in_hours, ..
|
|
320
|
-
:param tracking_policy: Tracking policy object or a dictionary that will be converted into a tracking policy
|
|
321
|
-
object. By using TrackingPolicy, the user can apply his model monitoring requirements,
|
|
322
|
-
such as setting the scheduling policy of the model monitoring batch job or changing
|
|
323
|
-
the image of the model monitoring stream.
|
|
324
324
|
|
|
325
325
|
example::
|
|
326
326
|
|
|
327
327
|
# initialize a new serving function
|
|
328
328
|
serving_fn = mlrun.import_function("hub://v2-model-server", new_name="serving")
|
|
329
|
-
# apply model monitoring
|
|
330
|
-
|
|
331
|
-
serving_fn.set_tracking(tracking_policy=tracking_policy)
|
|
329
|
+
# apply model monitoring
|
|
330
|
+
serving_fn.set_tracking()
|
|
332
331
|
|
|
333
332
|
"""
|
|
334
|
-
|
|
335
333
|
# Applying model monitoring configurations
|
|
336
334
|
self.spec.track_models = True
|
|
337
|
-
self.spec.tracking_policy = None
|
|
338
|
-
if tracking_policy:
|
|
339
|
-
if isinstance(tracking_policy, dict):
|
|
340
|
-
# Convert tracking policy dictionary into `model_monitoring.TrackingPolicy` object
|
|
341
|
-
self.spec.tracking_policy = TrackingPolicy.from_dict(tracking_policy)
|
|
342
|
-
else:
|
|
343
|
-
# Tracking_policy is already a `model_monitoring.TrackingPolicy` object
|
|
344
|
-
self.spec.tracking_policy = tracking_policy
|
|
345
335
|
|
|
346
336
|
if stream_path:
|
|
347
337
|
self.spec.parameters["log_stream"] = stream_path
|
|
@@ -351,6 +341,14 @@ class ServingRuntime(RemoteRuntime):
|
|
|
351
341
|
self.spec.parameters["log_stream_sample"] = sample
|
|
352
342
|
if stream_args:
|
|
353
343
|
self.spec.parameters["stream_args"] = stream_args
|
|
344
|
+
if tracking_policy is not None:
|
|
345
|
+
warnings.warn(
|
|
346
|
+
"The `tracking_policy` argument is deprecated from version 1.7.0 "
|
|
347
|
+
"and has no effect. It will be removed in 1.9.0.\n"
|
|
348
|
+
"To set the desired model monitoring time window and schedule, use "
|
|
349
|
+
"the `base_period` argument in `project.enable_model_monitoring()`.",
|
|
350
|
+
FutureWarning,
|
|
351
|
+
)
|
|
354
352
|
|
|
355
353
|
def add_model(
|
|
356
354
|
self,
|
|
@@ -367,8 +365,8 @@ class ServingRuntime(RemoteRuntime):
|
|
|
367
365
|
|
|
368
366
|
Example, create a function (from the notebook), add a model class, and deploy::
|
|
369
367
|
|
|
370
|
-
fn = code_to_function(kind=
|
|
371
|
-
fn.add_model(
|
|
368
|
+
fn = code_to_function(kind="serving")
|
|
369
|
+
fn.add_model("boost", model_path, model_class="MyClass", my_arg=5)
|
|
372
370
|
fn.deploy()
|
|
373
371
|
|
|
374
372
|
only works with router topology, for nested topologies (model under router under flow)
|
|
@@ -450,7 +448,7 @@ class ServingRuntime(RemoteRuntime):
|
|
|
450
448
|
|
|
451
449
|
example::
|
|
452
450
|
|
|
453
|
-
fn.add_child_function(
|
|
451
|
+
fn.add_child_function("enrich", "./enrich.ipynb", "mlrun/mlrun")
|
|
454
452
|
|
|
455
453
|
:param name: child function name
|
|
456
454
|
:param url: function/code url, support .py, .ipynb, .yaml extensions
|
|
@@ -489,11 +487,8 @@ class ServingRuntime(RemoteRuntime):
|
|
|
489
487
|
"worker_allocation_mode", "static"
|
|
490
488
|
)
|
|
491
489
|
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
or "kafka_bootstrap_servers" in stream.options
|
|
495
|
-
):
|
|
496
|
-
brokers = stream.options.get("kafka_bootstrap_servers")
|
|
490
|
+
brokers = get_kafka_brokers_from_dict(stream.options)
|
|
491
|
+
if stream.path.startswith("kafka://") or brokers:
|
|
497
492
|
if brokers:
|
|
498
493
|
brokers = brokers.split(",")
|
|
499
494
|
topic, brokers = parse_kafka_url(stream.path, brokers)
|
|
@@ -644,8 +639,7 @@ class ServingRuntime(RemoteRuntime):
|
|
|
644
639
|
force_build=force_build,
|
|
645
640
|
)
|
|
646
641
|
|
|
647
|
-
def
|
|
648
|
-
env = super()._get_runtime_env()
|
|
642
|
+
def _get_serving_spec(self):
|
|
649
643
|
function_name_uri_map = {f.name: f.uri(self) for f in self.spec.function_refs}
|
|
650
644
|
|
|
651
645
|
serving_spec = {
|
|
@@ -658,9 +652,7 @@ class ServingRuntime(RemoteRuntime):
|
|
|
658
652
|
"graph_initializer": self.spec.graph_initializer,
|
|
659
653
|
"error_stream": self.spec.error_stream,
|
|
660
654
|
"track_models": self.spec.track_models,
|
|
661
|
-
"tracking_policy":
|
|
662
|
-
if self.spec.tracking_policy
|
|
663
|
-
else None,
|
|
655
|
+
"tracking_policy": None,
|
|
664
656
|
"default_content_type": self.spec.default_content_type,
|
|
665
657
|
}
|
|
666
658
|
|
|
@@ -668,8 +660,7 @@ class ServingRuntime(RemoteRuntime):
|
|
|
668
660
|
self._secrets = SecretsStore.from_list(self.spec.secret_sources)
|
|
669
661
|
serving_spec["secret_sources"] = self._secrets.to_serial()
|
|
670
662
|
|
|
671
|
-
|
|
672
|
-
return env
|
|
663
|
+
return json.dumps(serving_spec)
|
|
673
664
|
|
|
674
665
|
def to_mock_server(
|
|
675
666
|
self,
|
|
@@ -735,8 +726,11 @@ class ServingRuntime(RemoteRuntime):
|
|
|
735
726
|
example::
|
|
736
727
|
|
|
737
728
|
serving_fn = mlrun.new_function("serving", image="mlrun/mlrun", kind="serving")
|
|
738
|
-
serving_fn.add_model(
|
|
739
|
-
|
|
729
|
+
serving_fn.add_model(
|
|
730
|
+
"my-classifier",
|
|
731
|
+
model_path=model_path,
|
|
732
|
+
class_name="mlrun.frameworks.sklearn.SklearnModelServer",
|
|
733
|
+
)
|
|
740
734
|
serving_fn.plot(rankdir="LR")
|
|
741
735
|
|
|
742
736
|
:param filename: target filepath for the image (None for the notebook)
|