Skip to content

Commit a10578e

Browse files
authored
[Application] Introducing application runtime phase I (mlrun#5234)
1 parent 0cfbc03 commit a10578e

File tree

14 files changed

+741
-82
lines changed

14 files changed

+741
-82
lines changed

mlrun/projects/project.py

+12
Original file line numberDiff line numberDiff line change
@@ -3988,6 +3988,18 @@ def _init_function_from_dict(
39883988
tag=tag,
39893989
)
39903990

3991+
elif image and kind in mlrun.runtimes.RuntimeKinds.nuclio_runtimes():
3992+
func = new_function(
3993+
name,
3994+
command=relative_url,
3995+
image=image,
3996+
kind=kind,
3997+
handler=handler,
3998+
tag=tag,
3999+
)
4000+
if kind != mlrun.runtimes.RuntimeKinds.application:
4001+
logger.info("Function code not specified, setting entry point to image")
4002+
func.from_image(image)
39914003
else:
39924004
raise ValueError(f"Unsupported function url:handler {url}:{handler} or no spec")
39934005

mlrun/run.py

+69-73
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@
3434
import mlrun.errors
3535
import mlrun.utils.helpers
3636
from mlrun.kfpops import format_summary_from_kfp_run, show_kfp_run
37-
from mlrun.runtimes.nuclio.serving import serving_subkind
3837

3938
from .common.helpers import parse_versioned_object_uri
4039
from .config import config as mlconf
@@ -58,6 +57,7 @@
5857
)
5958
from .runtimes.databricks_job.databricks_runtime import DatabricksRuntime
6059
from .runtimes.funcdoc import update_function_entry_points
60+
from .runtimes.nuclio.application import ApplicationRuntime
6161
from .runtimes.utils import add_code_metadata, global_context
6262
from .utils import (
6363
extend_hub_uri_if_needed,
@@ -425,19 +425,19 @@ def import_function_to_dict(url, secrets=None):
425425

426426

427427
def new_function(
428-
name: str = "",
429-
project: str = "",
430-
tag: str = "",
431-
kind: str = "",
432-
command: str = "",
433-
image: str = "",
434-
args: list = None,
435-
runtime=None,
436-
mode=None,
437-
handler: str = None,
438-
source: str = None,
428+
name: Optional[str] = "",
429+
project: Optional[str] = "",
430+
tag: Optional[str] = "",
431+
kind: Optional[str] = "",
432+
command: Optional[str] = "",
433+
image: Optional[str] = "",
434+
args: Optional[list] = None,
435+
runtime: Optional[Union[mlrun.runtimes.BaseRuntime, dict]] = None,
436+
mode: Optional[str] = None,
437+
handler: Optional[str] = None,
438+
source: Optional[str] = None,
439439
requirements: Union[str, list[str]] = None,
440-
kfp=None,
440+
kfp: Optional[bool] = None,
441441
requirements_file: str = "",
442442
):
443443
"""Create a new ML function from base properties
@@ -535,9 +535,9 @@ def new_function(
535535
if source:
536536
runner.spec.build.source = source
537537
if handler:
538-
if kind == RuntimeKinds.serving:
538+
if kind in [RuntimeKinds.serving, RuntimeKinds.application]:
539539
raise MLRunInvalidArgumentError(
540-
"cannot set the handler for serving runtime"
540+
f"Handler is not supported for {kind} runtime"
541541
)
542542
elif kind in RuntimeKinds.nuclio_runtimes():
543543
runner.spec.function_handler = handler
@@ -575,22 +575,22 @@ def _process_runtime(command, runtime, kind):
575575

576576

577577
def code_to_function(
578-
name: str = "",
579-
project: str = "",
580-
tag: str = "",
581-
filename: str = "",
582-
handler: str = "",
583-
kind: str = "",
584-
image: str = None,
585-
code_output: str = "",
578+
name: Optional[str] = "",
579+
project: Optional[str] = "",
580+
tag: Optional[str] = "",
581+
filename: Optional[str] = "",
582+
handler: Optional[str] = "",
583+
kind: Optional[str] = "",
584+
image: Optional[str] = None,
585+
code_output: Optional[str] = "",
586586
embed_code: bool = True,
587-
description: str = "",
588-
requirements: Union[str, list[str]] = None,
589-
categories: list[str] = None,
590-
labels: dict[str, str] = None,
591-
with_doc: bool = True,
592-
ignored_tags=None,
593-
requirements_file: str = "",
587+
description: Optional[str] = "",
588+
requirements: Optional[Union[str, list[str]]] = None,
589+
categories: Optional[list[str]] = None,
590+
labels: Optional[dict[str, str]] = None,
591+
with_doc: Optional[bool] = True,
592+
ignored_tags: Optional[str] = None,
593+
requirements_file: Optional[str] = "",
594594
) -> Union[
595595
MpiRuntimeV1Alpha1,
596596
MpiRuntimeV1,
@@ -602,6 +602,7 @@ def code_to_function(
602602
Spark3Runtime,
603603
RemoteSparkRuntime,
604604
DatabricksRuntime,
605+
ApplicationRuntime,
605606
]:
606607
"""Convenience function to insert code and configure an mlrun runtime.
607608
@@ -718,35 +719,34 @@ def update_common(fn, spec):
718719
fn.metadata.categories = categories
719720
fn.metadata.labels = labels or fn.metadata.labels
720721

721-
def resolve_nuclio_subkind(kind):
722-
is_nuclio = kind.startswith("nuclio")
723-
subkind = kind[kind.find(":") + 1 :] if is_nuclio and ":" in kind else None
724-
if kind == RuntimeKinds.serving:
725-
is_nuclio = True
726-
subkind = serving_subkind
727-
return is_nuclio, subkind
728-
729722
if (
730723
not embed_code
731724
and not code_output
732725
and (not filename or filename.endswith(".ipynb"))
733726
):
734727
raise ValueError(
735-
"a valid code file must be specified "
728+
"A valid code file must be specified "
736729
"when not using the embed_code option"
737730
)
738731

739732
if kind == RuntimeKinds.databricks and not embed_code:
740-
raise ValueError("databricks tasks only support embed_code=True")
733+
raise ValueError("Databricks tasks only support embed_code=True")
741734

742-
is_nuclio, subkind = resolve_nuclio_subkind(kind)
735+
if kind == RuntimeKinds.application:
736+
if handler:
737+
raise MLRunInvalidArgumentError(
738+
"Handler is not supported for application runtime"
739+
)
740+
filename, handler = ApplicationRuntime.get_filename_and_handler()
741+
742+
is_nuclio, sub_kind = RuntimeKinds.resolve_nuclio_sub_kind(kind)
743743
code_origin = add_name(add_code_metadata(filename), name)
744744

745745
name, spec, code = nuclio.build_file(
746746
filename,
747747
name=name,
748748
handler=handler or "handler",
749-
kind=subkind,
749+
kind=sub_kind,
750750
ignored_tags=ignored_tags,
751751
)
752752
spec["spec"]["env"].append(
@@ -759,14 +759,14 @@ def resolve_nuclio_subkind(kind):
759759
if not kind and spec_kind not in ["", "Function"]:
760760
kind = spec_kind.lower()
761761

762-
# if its a nuclio subkind, redo nb parsing
763-
is_nuclio, subkind = resolve_nuclio_subkind(kind)
762+
# if its a nuclio sub kind, redo nb parsing
763+
is_nuclio, sub_kind = RuntimeKinds.resolve_nuclio_sub_kind(kind)
764764
if is_nuclio:
765765
name, spec, code = nuclio.build_file(
766766
filename,
767767
name=name,
768768
handler=handler or "handler",
769-
kind=subkind,
769+
kind=sub_kind,
770770
ignored_tags=ignored_tags,
771771
)
772772

@@ -780,33 +780,29 @@ def resolve_nuclio_subkind(kind):
780780
raise ValueError("code_output option is only used with notebooks")
781781

782782
if is_nuclio:
783-
if subkind == serving_subkind:
784-
r = ServingRuntime()
785-
else:
786-
r = RemoteRuntime()
787-
r.spec.function_kind = subkind
788-
# default_handler is only used in :mlrun subkind, determine the handler to invoke in function.run()
789-
r.spec.default_handler = handler if subkind == "mlrun" else ""
790-
r.spec.function_handler = (
783+
runtime = RuntimeKinds.resolve_nuclio_runtime(kind, sub_kind)
784+
# default_handler is only used in :mlrun sub kind, determine the handler to invoke in function.run()
785+
runtime.spec.default_handler = handler if sub_kind == "mlrun" else ""
786+
runtime.spec.function_handler = (
791787
handler if handler and ":" in handler else get_in(spec, "spec.handler")
792788
)
793789
if not embed_code:
794-
r.spec.source = filename
790+
runtime.spec.source = filename
795791
nuclio_runtime = get_in(spec, "spec.runtime")
796792
if nuclio_runtime and not nuclio_runtime.startswith("py"):
797-
r.spec.nuclio_runtime = nuclio_runtime
793+
runtime.spec.nuclio_runtime = nuclio_runtime
798794
if not name:
799-
raise ValueError("name must be specified")
800-
r.metadata.name = name
801-
r.spec.build.code_origin = code_origin
802-
r.spec.build.origin_filename = filename or (name + ".ipynb")
803-
update_common(r, spec)
804-
return r
795+
raise ValueError("Missing required parameter: name")
796+
runtime.metadata.name = name
797+
runtime.spec.build.code_origin = code_origin
798+
runtime.spec.build.origin_filename = filename or (name + ".ipynb")
799+
update_common(runtime, spec)
800+
return runtime
805801

806802
if kind is None or kind in ["", "Function"]:
807803
raise ValueError("please specify the function kind")
808804
elif kind in RuntimeKinds.all():
809-
r = get_runtime_class(kind)()
805+
runtime = get_runtime_class(kind)()
810806
else:
811807
raise ValueError(f"unsupported runtime ({kind})")
812808

@@ -815,29 +811,29 @@ def resolve_nuclio_subkind(kind):
815811
if not name:
816812
raise ValueError("name must be specified")
817813
h = get_in(spec, "spec.handler", "").split(":")
818-
r.handler = h[0] if len(h) <= 1 else h[1]
819-
r.metadata = get_in(spec, "spec.metadata")
820-
r.metadata.name = name
821-
build = r.spec.build
814+
runtime.handler = h[0] if len(h) <= 1 else h[1]
815+
runtime.metadata = get_in(spec, "spec.metadata")
816+
runtime.metadata.name = name
817+
build = runtime.spec.build
822818
build.code_origin = code_origin
823819
build.origin_filename = filename or (name + ".ipynb")
824820
build.extra = get_in(spec, "spec.build.extra")
825821
build.extra_args = get_in(spec, "spec.build.extra_args")
826822
build.builder_env = get_in(spec, "spec.build.builder_env")
827823
if not embed_code:
828824
if code_output:
829-
r.spec.command = code_output
825+
runtime.spec.command = code_output
830826
else:
831-
r.spec.command = filename
827+
runtime.spec.command = filename
832828

833829
build.image = get_in(spec, "spec.build.image")
834-
update_common(r, spec)
835-
r.prepare_image_for_deploy()
830+
update_common(runtime, spec)
831+
runtime.prepare_image_for_deploy()
836832

837833
if with_doc:
838-
update_function_entry_points(r, code)
839-
r.spec.default_handler = handler
840-
return r
834+
update_function_entry_points(runtime, code)
835+
runtime.spec.default_handler = handler
836+
return runtime
841837

842838

843839
def _run_pipeline(

mlrun/runtimes/__init__.py

+35
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@
4343
new_v2_model_server,
4444
nuclio_init_hook,
4545
)
46+
from .nuclio.application import ApplicationRuntime
47+
from .nuclio.serving import serving_subkind
4648
from .remotesparkjob import RemoteSparkRuntime
4749
from .sparkjob import Spark3Runtime
4850

@@ -101,6 +103,7 @@ class RuntimeKinds:
101103
local = "local"
102104
handler = "handler"
103105
databricks = "databricks"
106+
application = "application"
104107

105108
@staticmethod
106109
def all():
@@ -115,6 +118,7 @@ def all():
115118
RuntimeKinds.mpijob,
116119
RuntimeKinds.local,
117120
RuntimeKinds.databricks,
121+
RuntimeKinds.application,
118122
]
119123

120124
@staticmethod
@@ -147,6 +151,7 @@ def nuclio_runtimes():
147151
RuntimeKinds.remote,
148152
RuntimeKinds.nuclio,
149153
RuntimeKinds.serving,
154+
RuntimeKinds.application,
150155
]
151156

152157
@staticmethod
@@ -211,6 +216,35 @@ def requires_image_name_for_execution(kind):
211216
# both spark and remote spark uses different mechanism for assigning images
212217
return kind not in [RuntimeKinds.spark, RuntimeKinds.remotespark]
213218

219+
@staticmethod
220+
def resolve_nuclio_runtime(kind: str, sub_kind: str):
221+
kind = kind.split(":")[0]
222+
if kind not in RuntimeKinds.nuclio_runtimes():
223+
raise ValueError(
224+
f"Kind {kind} is not a nuclio runtime, available runtimes are {RuntimeKinds.nuclio_runtimes()}"
225+
)
226+
227+
if sub_kind == serving_subkind:
228+
return ServingRuntime()
229+
230+
if kind == RuntimeKinds.application:
231+
return ApplicationRuntime()
232+
233+
runtime = RemoteRuntime()
234+
runtime.spec.function_kind = sub_kind
235+
return runtime
236+
237+
@staticmethod
238+
def resolve_nuclio_sub_kind(kind):
239+
is_nuclio = kind.startswith("nuclio")
240+
sub_kind = kind[kind.find(":") + 1 :] if is_nuclio and ":" in kind else None
241+
if kind == RuntimeKinds.serving:
242+
is_nuclio = True
243+
sub_kind = serving_subkind
244+
elif kind == RuntimeKinds.application:
245+
is_nuclio = True
246+
return is_nuclio, sub_kind
247+
214248

215249
def get_runtime_class(kind: str):
216250
if kind == RuntimeKinds.mpijob:
@@ -228,6 +262,7 @@ def get_runtime_class(kind: str):
228262
RuntimeKinds.local: LocalRuntime,
229263
RuntimeKinds.remotespark: RemoteSparkRuntime,
230264
RuntimeKinds.databricks: DatabricksRuntime,
265+
RuntimeKinds.application: ApplicationRuntime,
231266
}
232267

233268
return kind_runtime_map[kind]

mlrun/runtimes/base.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -786,7 +786,7 @@ def with_requirements(
786786
requirements: Optional[list[str]] = None,
787787
overwrite: bool = False,
788788
prepare_image_for_deploy: bool = True,
789-
requirements_file: str = "",
789+
requirements_file: Optional[str] = "",
790790
):
791791
"""add package requirements from file or list to build spec.
792792
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Copyright 2024 Iguazio
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
from .application import ApplicationRuntime

0 commit comments

Comments
 (0)