mlrun 1.7.0rc12__py3-none-any.whl → 1.7.0rc14__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/common/schemas/alert.py +1 -1
- mlrun/common/schemas/project.py +1 -0
- mlrun/config.py +12 -1
- mlrun/datastore/datastore_profile.py +17 -3
- mlrun/datastore/hdfs.py +5 -0
- mlrun/datastore/targets.py +52 -0
- mlrun/datastore/v3io.py +1 -1
- mlrun/db/auth_utils.py +152 -0
- mlrun/db/base.py +1 -1
- mlrun/db/httpdb.py +65 -29
- mlrun/model.py +18 -0
- mlrun/model_monitoring/helpers.py +7 -0
- mlrun/model_monitoring/stream_processing.py +1 -7
- mlrun/model_monitoring/writer.py +22 -4
- mlrun/projects/pipelines.py +24 -7
- mlrun/projects/project.py +112 -34
- mlrun/run.py +0 -1
- mlrun/runtimes/nuclio/api_gateway.py +275 -153
- mlrun/runtimes/pod.py +5 -5
- mlrun/serving/states.py +53 -2
- mlrun/utils/notifications/notification/slack.py +33 -8
- mlrun/utils/notifications/notification/webhook.py +1 -1
- mlrun/utils/version/version.json +2 -2
- {mlrun-1.7.0rc12.dist-info → mlrun-1.7.0rc14.dist-info}/METADATA +1 -1
- {mlrun-1.7.0rc12.dist-info → mlrun-1.7.0rc14.dist-info}/RECORD +29 -28
- {mlrun-1.7.0rc12.dist-info → mlrun-1.7.0rc14.dist-info}/LICENSE +0 -0
- {mlrun-1.7.0rc12.dist-info → mlrun-1.7.0rc14.dist-info}/WHEEL +0 -0
- {mlrun-1.7.0rc12.dist-info → mlrun-1.7.0rc14.dist-info}/entry_points.txt +0 -0
- {mlrun-1.7.0rc12.dist-info → mlrun-1.7.0rc14.dist-info}/top_level.txt +0 -0
mlrun/projects/pipelines.py
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
import abc
|
|
15
15
|
import builtins
|
|
16
|
+
import http
|
|
16
17
|
import importlib.util as imputil
|
|
17
18
|
import os
|
|
18
19
|
import tempfile
|
|
@@ -521,7 +522,7 @@ class _PipelineRunner(abc.ABC):
|
|
|
521
522
|
@staticmethod
|
|
522
523
|
def _get_handler(workflow_handler, workflow_spec, project, secrets):
|
|
523
524
|
if not (workflow_handler and callable(workflow_handler)):
|
|
524
|
-
workflow_file = workflow_spec.get_source_file(project.spec.
|
|
525
|
+
workflow_file = workflow_spec.get_source_file(project.spec.get_code_path())
|
|
525
526
|
workflow_handler = create_pipeline(
|
|
526
527
|
project,
|
|
527
528
|
workflow_file,
|
|
@@ -553,7 +554,7 @@ class _KFPRunner(_PipelineRunner):
|
|
|
553
554
|
@classmethod
|
|
554
555
|
def save(cls, project, workflow_spec: WorkflowSpec, target, artifact_path=None):
|
|
555
556
|
pipeline_context.set(project, workflow_spec)
|
|
556
|
-
workflow_file = workflow_spec.get_source_file(project.spec.
|
|
557
|
+
workflow_file = workflow_spec.get_source_file(project.spec.get_code_path())
|
|
557
558
|
functions = FunctionsDict(project)
|
|
558
559
|
pipeline = create_pipeline(
|
|
559
560
|
project,
|
|
@@ -882,17 +883,33 @@ class _RemoteRunner(_PipelineRunner):
|
|
|
882
883
|
get_workflow_id_timeout=get_workflow_id_timeout,
|
|
883
884
|
)
|
|
884
885
|
|
|
886
|
+
def _get_workflow_id_or_bail():
|
|
887
|
+
try:
|
|
888
|
+
return run_db.get_workflow_id(
|
|
889
|
+
project=project.name,
|
|
890
|
+
name=workflow_response.name,
|
|
891
|
+
run_id=workflow_response.run_id,
|
|
892
|
+
engine=workflow_spec.engine,
|
|
893
|
+
)
|
|
894
|
+
except mlrun.errors.MLRunHTTPStatusError as get_wf_exc:
|
|
895
|
+
# fail fast on specific errors
|
|
896
|
+
if get_wf_exc.error_status_code in [
|
|
897
|
+
http.HTTPStatus.PRECONDITION_FAILED
|
|
898
|
+
]:
|
|
899
|
+
raise mlrun.errors.MLRunFatalFailureError(
|
|
900
|
+
original_exception=get_wf_exc
|
|
901
|
+
)
|
|
902
|
+
|
|
903
|
+
# raise for a retry (on other errors)
|
|
904
|
+
raise
|
|
905
|
+
|
|
885
906
|
# Getting workflow id from run:
|
|
886
907
|
response = retry_until_successful(
|
|
887
908
|
1,
|
|
888
909
|
get_workflow_id_timeout,
|
|
889
910
|
logger,
|
|
890
911
|
False,
|
|
891
|
-
|
|
892
|
-
project=project.name,
|
|
893
|
-
name=workflow_response.name,
|
|
894
|
-
run_id=workflow_response.run_id,
|
|
895
|
-
engine=workflow_spec.engine,
|
|
912
|
+
_get_workflow_id_or_bail,
|
|
896
913
|
)
|
|
897
914
|
workflow_id = response.workflow_id
|
|
898
915
|
# After fetching the workflow_id the workflow executed successfully
|
mlrun/projects/project.py
CHANGED
|
@@ -207,14 +207,16 @@ def new_project(
|
|
|
207
207
|
"Unsupported option, cannot use subpath argument with project templates"
|
|
208
208
|
)
|
|
209
209
|
if from_template.endswith(".yaml"):
|
|
210
|
-
project = _load_project_file(
|
|
210
|
+
project = _load_project_file(
|
|
211
|
+
from_template, name, secrets, allow_cross_project=True
|
|
212
|
+
)
|
|
211
213
|
elif from_template.startswith("git://"):
|
|
212
214
|
clone_git(from_template, context, secrets, clone=True)
|
|
213
215
|
shutil.rmtree(path.join(context, ".git"))
|
|
214
|
-
project = _load_project_dir(context, name)
|
|
216
|
+
project = _load_project_dir(context, name, allow_cross_project=True)
|
|
215
217
|
elif from_template.endswith(".zip"):
|
|
216
218
|
clone_zip(from_template, context, secrets)
|
|
217
|
-
project = _load_project_dir(context, name)
|
|
219
|
+
project = _load_project_dir(context, name, allow_cross_project=True)
|
|
218
220
|
else:
|
|
219
221
|
raise ValueError("template must be a path to .yaml or .zip file")
|
|
220
222
|
project.metadata.name = name
|
|
@@ -296,6 +298,7 @@ def load_project(
|
|
|
296
298
|
save: bool = True,
|
|
297
299
|
sync_functions: bool = False,
|
|
298
300
|
parameters: dict = None,
|
|
301
|
+
allow_cross_project: bool = None,
|
|
299
302
|
) -> "MlrunProject":
|
|
300
303
|
"""Load an MLRun project from git or tar or dir
|
|
301
304
|
|
|
@@ -342,6 +345,8 @@ def load_project(
|
|
|
342
345
|
:param save: whether to save the created project and artifact in the DB
|
|
343
346
|
:param sync_functions: sync the project's functions into the project object (will be saved to the DB if save=True)
|
|
344
347
|
:param parameters: key/value pairs to add to the project.spec.params
|
|
348
|
+
:param allow_cross_project: if True, override the loaded project name. This flag ensures awareness of
|
|
349
|
+
loading an existing project yaml as a baseline for a new project with a different name
|
|
345
350
|
|
|
346
351
|
:returns: project object
|
|
347
352
|
"""
|
|
@@ -357,7 +362,7 @@ def load_project(
|
|
|
357
362
|
if url:
|
|
358
363
|
url = str(url) # to support path objects
|
|
359
364
|
if is_yaml_path(url):
|
|
360
|
-
project = _load_project_file(url, name, secrets)
|
|
365
|
+
project = _load_project_file(url, name, secrets, allow_cross_project)
|
|
361
366
|
project.spec.context = context
|
|
362
367
|
elif url.startswith("git://"):
|
|
363
368
|
url, repo = clone_git(url, context, secrets, clone)
|
|
@@ -384,7 +389,7 @@ def load_project(
|
|
|
384
389
|
repo, url = init_repo(context, url, init_git)
|
|
385
390
|
|
|
386
391
|
if not project:
|
|
387
|
-
project = _load_project_dir(context, name, subpath)
|
|
392
|
+
project = _load_project_dir(context, name, subpath, allow_cross_project)
|
|
388
393
|
|
|
389
394
|
if not project.metadata.name:
|
|
390
395
|
raise ValueError("Project name must be specified")
|
|
@@ -438,6 +443,7 @@ def get_or_create_project(
|
|
|
438
443
|
from_template: str = None,
|
|
439
444
|
save: bool = True,
|
|
440
445
|
parameters: dict = None,
|
|
446
|
+
allow_cross_project: bool = None,
|
|
441
447
|
) -> "MlrunProject":
|
|
442
448
|
"""Load a project from MLRun DB, or create/import if it does not exist
|
|
443
449
|
|
|
@@ -482,12 +488,12 @@ def get_or_create_project(
|
|
|
482
488
|
:param from_template: path to project YAML file that will be used as from_template (for new projects)
|
|
483
489
|
:param save: whether to save the created project in the DB
|
|
484
490
|
:param parameters: key/value pairs to add to the project.spec.params
|
|
491
|
+
:param allow_cross_project: if True, override the loaded project name. This flag ensures awareness of
|
|
492
|
+
loading an existing project yaml as a baseline for a new project with a different name
|
|
485
493
|
|
|
486
494
|
:returns: project object
|
|
487
495
|
"""
|
|
488
496
|
context = context or "./"
|
|
489
|
-
spec_path = path.join(context, subpath or "", "project.yaml")
|
|
490
|
-
load_from_path = url or path.isfile(spec_path)
|
|
491
497
|
try:
|
|
492
498
|
# load project from the DB.
|
|
493
499
|
# use `name` as `url` as we load the project from the DB
|
|
@@ -503,13 +509,15 @@ def get_or_create_project(
|
|
|
503
509
|
# only loading project from db so no need to save it
|
|
504
510
|
save=False,
|
|
505
511
|
parameters=parameters,
|
|
512
|
+
allow_cross_project=allow_cross_project,
|
|
506
513
|
)
|
|
507
514
|
logger.info("Project loaded successfully", project_name=name)
|
|
508
515
|
return project
|
|
509
|
-
|
|
510
516
|
except mlrun.errors.MLRunNotFoundError:
|
|
511
517
|
logger.debug("Project not found in db", project_name=name)
|
|
512
518
|
|
|
519
|
+
spec_path = path.join(context, subpath or "", "project.yaml")
|
|
520
|
+
load_from_path = url or path.isfile(spec_path)
|
|
513
521
|
# do not nest under "try" or else the exceptions raised below will be logged along with the "not found" message
|
|
514
522
|
if load_from_path:
|
|
515
523
|
# loads a project from archive or local project.yaml
|
|
@@ -525,6 +533,7 @@ def get_or_create_project(
|
|
|
525
533
|
user_project=user_project,
|
|
526
534
|
save=save,
|
|
527
535
|
parameters=parameters,
|
|
536
|
+
allow_cross_project=allow_cross_project,
|
|
528
537
|
)
|
|
529
538
|
|
|
530
539
|
logger.info(
|
|
@@ -599,7 +608,7 @@ def _run_project_setup(
|
|
|
599
608
|
return project
|
|
600
609
|
|
|
601
610
|
|
|
602
|
-
def _load_project_dir(context, name="", subpath=""):
|
|
611
|
+
def _load_project_dir(context, name="", subpath="", allow_cross_project=None):
|
|
603
612
|
subpath_str = subpath or ""
|
|
604
613
|
|
|
605
614
|
# support both .yaml and .yml file extensions
|
|
@@ -613,7 +622,7 @@ def _load_project_dir(context, name="", subpath=""):
|
|
|
613
622
|
with open(project_file_path) as fp:
|
|
614
623
|
data = fp.read()
|
|
615
624
|
struct = yaml.load(data, Loader=yaml.FullLoader)
|
|
616
|
-
project = _project_instance_from_struct(struct, name)
|
|
625
|
+
project = _project_instance_from_struct(struct, name, allow_cross_project)
|
|
617
626
|
project.spec.context = context
|
|
618
627
|
elif function_files := glob.glob(function_file_path):
|
|
619
628
|
function_path = function_files[0]
|
|
@@ -686,19 +695,41 @@ def _delete_project_from_db(project_name, secrets, deletion_strategy):
|
|
|
686
695
|
return db.delete_project(project_name, deletion_strategy=deletion_strategy)
|
|
687
696
|
|
|
688
697
|
|
|
689
|
-
def _load_project_file(url, name="", secrets=None):
|
|
698
|
+
def _load_project_file(url, name="", secrets=None, allow_cross_project=None):
|
|
690
699
|
try:
|
|
691
700
|
obj = get_object(url, secrets)
|
|
692
701
|
except FileNotFoundError as exc:
|
|
693
702
|
raise FileNotFoundError(f"cant find project file at {url}") from exc
|
|
694
703
|
struct = yaml.load(obj, Loader=yaml.FullLoader)
|
|
695
|
-
return _project_instance_from_struct(struct, name)
|
|
704
|
+
return _project_instance_from_struct(struct, name, allow_cross_project)
|
|
696
705
|
|
|
697
706
|
|
|
698
|
-
def _project_instance_from_struct(struct, name):
|
|
699
|
-
struct.
|
|
700
|
-
|
|
701
|
-
|
|
707
|
+
def _project_instance_from_struct(struct, name, allow_cross_project):
|
|
708
|
+
name_from_struct = struct.get("metadata", {}).get("name", "")
|
|
709
|
+
if name and name_from_struct and name_from_struct != name:
|
|
710
|
+
error_message = (
|
|
711
|
+
f"project name mismatch, {name_from_struct} != {name}, please do one of the following:\n"
|
|
712
|
+
"1. Set the `allow_cross_project=True` when loading the project.\n"
|
|
713
|
+
f"2. Delete the existing project yaml, or ensure its name is equal to {name}.\n"
|
|
714
|
+
"3. Use different project context dir."
|
|
715
|
+
)
|
|
716
|
+
|
|
717
|
+
if allow_cross_project is None:
|
|
718
|
+
# TODO: Remove this warning in version 1.9.0 and also fix cli to support allow_cross_project
|
|
719
|
+
logger.warn(
|
|
720
|
+
"Project name is different than specified on its project yaml."
|
|
721
|
+
"You should fix it until version 1.9.0",
|
|
722
|
+
description=error_message,
|
|
723
|
+
)
|
|
724
|
+
elif allow_cross_project:
|
|
725
|
+
logger.warn(
|
|
726
|
+
"Project name is different than specified on its project yaml. Overriding.",
|
|
727
|
+
existing_name=name_from_struct,
|
|
728
|
+
overriding_name=name,
|
|
729
|
+
)
|
|
730
|
+
else:
|
|
731
|
+
raise ValueError(error_message)
|
|
732
|
+
struct.setdefault("metadata", {})["name"] = name or name_from_struct
|
|
702
733
|
return MlrunProject.from_dict(struct)
|
|
703
734
|
|
|
704
735
|
|
|
@@ -1814,10 +1845,18 @@ class MlrunProject(ModelObj):
|
|
|
1814
1845
|
"""
|
|
1815
1846
|
context = context or self.spec.context
|
|
1816
1847
|
if context:
|
|
1817
|
-
project = _load_project_dir(
|
|
1848
|
+
project = _load_project_dir(
|
|
1849
|
+
context,
|
|
1850
|
+
self.metadata.name,
|
|
1851
|
+
self.spec.subpath,
|
|
1852
|
+
allow_cross_project=False,
|
|
1853
|
+
)
|
|
1818
1854
|
else:
|
|
1819
1855
|
project = _load_project_file(
|
|
1820
|
-
self.spec.origin_url,
|
|
1856
|
+
self.spec.origin_url,
|
|
1857
|
+
self.metadata.name,
|
|
1858
|
+
self._secrets,
|
|
1859
|
+
allow_cross_project=None,
|
|
1821
1860
|
)
|
|
1822
1861
|
project.spec.source = self.spec.source
|
|
1823
1862
|
project.spec.repo = self.spec.repo
|
|
@@ -2024,12 +2063,24 @@ class MlrunProject(ModelObj):
|
|
|
2024
2063
|
|
|
2025
2064
|
return resolved_function_name, function_object, func
|
|
2026
2065
|
|
|
2066
|
+
def _wait_for_functions_deployment(self, function_names: list[str]) -> None:
|
|
2067
|
+
"""
|
|
2068
|
+
Wait for the deployment of functions on the backend.
|
|
2069
|
+
|
|
2070
|
+
:param function_names: A list of function names.
|
|
2071
|
+
"""
|
|
2072
|
+
for fn_name in function_names:
|
|
2073
|
+
fn = typing.cast(RemoteRuntime, self.get_function(key=fn_name))
|
|
2074
|
+
fn._wait_for_function_deployment(db=fn._get_db())
|
|
2075
|
+
|
|
2027
2076
|
def enable_model_monitoring(
|
|
2028
2077
|
self,
|
|
2029
2078
|
default_controller_image: str = "mlrun/mlrun",
|
|
2030
2079
|
base_period: int = 10,
|
|
2031
2080
|
image: str = "mlrun/mlrun",
|
|
2081
|
+
*,
|
|
2032
2082
|
deploy_histogram_data_drift_app: bool = True,
|
|
2083
|
+
wait_for_deployment: bool = False,
|
|
2033
2084
|
) -> None:
|
|
2034
2085
|
"""
|
|
2035
2086
|
Deploy model monitoring application controller, writer and stream functions.
|
|
@@ -2039,7 +2090,6 @@ class MlrunProject(ModelObj):
|
|
|
2039
2090
|
The stream function goal is to monitor the log of the data stream. It is triggered when a new log entry
|
|
2040
2091
|
is detected. It processes the new events into statistics that are then written to statistics databases.
|
|
2041
2092
|
|
|
2042
|
-
|
|
2043
2093
|
:param default_controller_image: Deprecated.
|
|
2044
2094
|
:param base_period: The time period in minutes in which the model monitoring controller
|
|
2045
2095
|
function is triggered. By default, the base period is 10 minutes.
|
|
@@ -2047,6 +2097,9 @@ class MlrunProject(ModelObj):
|
|
|
2047
2097
|
stream & histogram data drift functions, which are real time nuclio
|
|
2048
2098
|
functions. By default, the image is mlrun/mlrun.
|
|
2049
2099
|
:param deploy_histogram_data_drift_app: If true, deploy the default histogram-based data drift application.
|
|
2100
|
+
:param wait_for_deployment: If true, return only after the deployment is done on the backend.
|
|
2101
|
+
Otherwise, deploy the model monitoring infrastructure on the
|
|
2102
|
+
background, including the histogram data drift app if selected.
|
|
2050
2103
|
"""
|
|
2051
2104
|
if default_controller_image != "mlrun/mlrun":
|
|
2052
2105
|
# TODO: Remove this in 1.9.0
|
|
@@ -2064,37 +2117,55 @@ class MlrunProject(ModelObj):
|
|
|
2064
2117
|
deploy_histogram_data_drift_app=deploy_histogram_data_drift_app,
|
|
2065
2118
|
)
|
|
2066
2119
|
|
|
2120
|
+
if wait_for_deployment:
|
|
2121
|
+
deployment_functions = mm_constants.MonitoringFunctionNames.list()
|
|
2122
|
+
if deploy_histogram_data_drift_app:
|
|
2123
|
+
deployment_functions.append(
|
|
2124
|
+
mm_constants.HistogramDataDriftApplicationConstants.NAME
|
|
2125
|
+
)
|
|
2126
|
+
self._wait_for_functions_deployment(deployment_functions)
|
|
2127
|
+
|
|
2067
2128
|
def deploy_histogram_data_drift_app(
|
|
2068
2129
|
self,
|
|
2069
2130
|
*,
|
|
2070
2131
|
image: str = "mlrun/mlrun",
|
|
2071
2132
|
db: Optional[mlrun.db.RunDBInterface] = None,
|
|
2133
|
+
wait_for_deployment: bool = False,
|
|
2072
2134
|
) -> None:
|
|
2073
2135
|
"""
|
|
2074
2136
|
Deploy the histogram data drift application.
|
|
2075
2137
|
|
|
2076
|
-
:param image:
|
|
2077
|
-
:param db:
|
|
2138
|
+
:param image: The image on which the application will run.
|
|
2139
|
+
:param db: An optional DB object.
|
|
2140
|
+
:param wait_for_deployment: If true, return only after the deployment is done on the backend.
|
|
2141
|
+
Otherwise, deploy the application on the background.
|
|
2078
2142
|
"""
|
|
2079
2143
|
if db is None:
|
|
2080
2144
|
db = mlrun.db.get_run_db(secrets=self._secrets)
|
|
2081
2145
|
db.deploy_histogram_data_drift_app(project=self.name, image=image)
|
|
2082
2146
|
|
|
2147
|
+
if wait_for_deployment:
|
|
2148
|
+
self._wait_for_functions_deployment(
|
|
2149
|
+
[mm_constants.HistogramDataDriftApplicationConstants.NAME]
|
|
2150
|
+
)
|
|
2151
|
+
|
|
2083
2152
|
def update_model_monitoring_controller(
|
|
2084
2153
|
self,
|
|
2085
2154
|
base_period: int = 10,
|
|
2086
2155
|
image: str = "mlrun/mlrun",
|
|
2156
|
+
*,
|
|
2157
|
+
wait_for_deployment: bool = False,
|
|
2087
2158
|
) -> None:
|
|
2088
2159
|
"""
|
|
2089
2160
|
Redeploy model monitoring application controller functions.
|
|
2090
2161
|
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2162
|
+
:param base_period: The time period in minutes in which the model monitoring controller function
|
|
2163
|
+
is triggered. By default, the base period is 10 minutes.
|
|
2164
|
+
:param image: The image of the model monitoring controller, writer & monitoring
|
|
2165
|
+
stream functions, which are real time nuclio functions.
|
|
2166
|
+
By default, the image is mlrun/mlrun.
|
|
2167
|
+
:param wait_for_deployment: If true, return only after the deployment is done on the backend.
|
|
2168
|
+
Otherwise, deploy the controller on the background.
|
|
2098
2169
|
"""
|
|
2099
2170
|
db = mlrun.db.get_run_db(secrets=self._secrets)
|
|
2100
2171
|
db.update_model_monitoring_controller(
|
|
@@ -2103,6 +2174,11 @@ class MlrunProject(ModelObj):
|
|
|
2103
2174
|
image=image,
|
|
2104
2175
|
)
|
|
2105
2176
|
|
|
2177
|
+
if wait_for_deployment:
|
|
2178
|
+
self._wait_for_functions_deployment(
|
|
2179
|
+
[mm_constants.MonitoringFunctionNames.APPLICATION_CONTROLLER]
|
|
2180
|
+
)
|
|
2181
|
+
|
|
2106
2182
|
def disable_model_monitoring(
|
|
2107
2183
|
self, *, delete_histogram_data_drift_app: bool = True
|
|
2108
2184
|
) -> None:
|
|
@@ -2824,12 +2900,14 @@ class MlrunProject(ModelObj):
|
|
|
2824
2900
|
"Remote repo is not defined, use .create_remote() + push()"
|
|
2825
2901
|
)
|
|
2826
2902
|
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2903
|
+
if engine not in ["remote"]:
|
|
2904
|
+
# for remote runs we don't require the functions to be synced as they can be loaded dynamically during run
|
|
2905
|
+
self.sync_functions(always=sync)
|
|
2906
|
+
if not self.spec._function_objects:
|
|
2907
|
+
raise ValueError(
|
|
2908
|
+
"There are no functions in the project."
|
|
2909
|
+
" Make sure you've set your functions with project.set_function()."
|
|
2910
|
+
)
|
|
2833
2911
|
|
|
2834
2912
|
if not name and not workflow_path and not workflow_handler:
|
|
2835
2913
|
raise ValueError("Workflow name, path, or handler must be specified")
|
mlrun/run.py
CHANGED
|
@@ -661,7 +661,6 @@ def code_to_function(
|
|
|
661
661
|
:param embed_code: indicates whether or not to inject the code directly into the function runtime spec,
|
|
662
662
|
defaults to True
|
|
663
663
|
:param description: short function description, defaults to ''
|
|
664
|
-
:param requirements: list of python packages or pip requirements file path, defaults to None
|
|
665
664
|
:param requirements: a list of python packages
|
|
666
665
|
:param requirements_file: path to a python requirements file
|
|
667
666
|
:param categories: list of categories for mlrun Function Hub, defaults to None
|