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
|
@@ -22,6 +22,7 @@ from requests.auth import HTTPBasicAuth
|
|
|
22
22
|
import mlrun
|
|
23
23
|
import mlrun.common.schemas
|
|
24
24
|
|
|
25
|
+
from ...model import ModelObj
|
|
25
26
|
from ..utils import logger
|
|
26
27
|
from .function import RemoteRuntime, get_fullname, min_nuclio_versions
|
|
27
28
|
from .serving import ServingRuntime
|
|
@@ -92,12 +93,50 @@ class BasicAuth(APIGatewayAuthenticator):
|
|
|
92
93
|
}
|
|
93
94
|
|
|
94
95
|
|
|
95
|
-
class
|
|
96
|
-
|
|
96
|
+
class APIGatewayMetadata(ModelObj):
|
|
97
|
+
_dict_fields = ["name", "namespace", "labels", "annotations", "creation_timestamp"]
|
|
98
|
+
|
|
97
99
|
def __init__(
|
|
98
100
|
self,
|
|
99
|
-
project,
|
|
100
101
|
name: str,
|
|
102
|
+
namespace: str = None,
|
|
103
|
+
labels: dict = None,
|
|
104
|
+
annotations: dict = None,
|
|
105
|
+
creation_timestamp: str = None,
|
|
106
|
+
):
|
|
107
|
+
"""
|
|
108
|
+
:param name: The name of the API gateway
|
|
109
|
+
:param namespace: The namespace of the API gateway
|
|
110
|
+
:param labels: The labels of the API gateway
|
|
111
|
+
:param annotations: The annotations of the API gateway
|
|
112
|
+
:param creation_timestamp: The creation timestamp of the API gateway
|
|
113
|
+
"""
|
|
114
|
+
self.name = name
|
|
115
|
+
self.namespace = namespace
|
|
116
|
+
self.labels = labels or {}
|
|
117
|
+
self.annotations = annotations or {}
|
|
118
|
+
self.creation_timestamp = creation_timestamp
|
|
119
|
+
|
|
120
|
+
if not self.name:
|
|
121
|
+
raise mlrun.errors.MLRunInvalidArgumentError(
|
|
122
|
+
"API Gateway name cannot be empty"
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class APIGatewaySpec(ModelObj):
|
|
127
|
+
_dict_fields = [
|
|
128
|
+
"functions",
|
|
129
|
+
"project",
|
|
130
|
+
"name",
|
|
131
|
+
"description",
|
|
132
|
+
"host",
|
|
133
|
+
"path",
|
|
134
|
+
"authentication",
|
|
135
|
+
"canary",
|
|
136
|
+
]
|
|
137
|
+
|
|
138
|
+
def __init__(
|
|
139
|
+
self,
|
|
101
140
|
functions: Union[
|
|
102
141
|
list[str],
|
|
103
142
|
Union[
|
|
@@ -107,26 +146,24 @@ class APIGateway:
|
|
|
107
146
|
ServingRuntime,
|
|
108
147
|
]
|
|
109
148
|
],
|
|
110
|
-
|
|
149
|
+
RemoteRuntime,
|
|
150
|
+
ServingRuntime,
|
|
111
151
|
],
|
|
112
152
|
],
|
|
153
|
+
project: str = None,
|
|
113
154
|
description: str = "",
|
|
155
|
+
host: str = None,
|
|
114
156
|
path: str = "/",
|
|
115
157
|
authentication: Optional[APIGatewayAuthenticator] = NoneAuth(),
|
|
116
|
-
host: Optional[str] = None,
|
|
117
158
|
canary: Optional[list[int]] = None,
|
|
118
159
|
):
|
|
119
160
|
"""
|
|
120
|
-
Initialize the APIGateway instance.
|
|
121
|
-
|
|
122
|
-
:param project: The project name
|
|
123
|
-
:param name: The name of the API gateway
|
|
124
161
|
:param functions: The list of functions associated with the API gateway
|
|
125
162
|
Can be a list of function names (["my-func1", "my-func2"])
|
|
126
163
|
or a list or a single entity of
|
|
127
164
|
:py:class:`~mlrun.runtimes.nuclio.function.RemoteRuntime` OR
|
|
128
165
|
:py:class:`~mlrun.runtimes.nuclio.serving.ServingRuntime`
|
|
129
|
-
|
|
166
|
+
:param project: The project name
|
|
130
167
|
:param description: Optional description of the API gateway
|
|
131
168
|
:param path: Optional path of the API gateway, default value is "/"
|
|
132
169
|
:param authentication: The authentication for the API gateway of type
|
|
@@ -134,23 +171,144 @@ class APIGateway:
|
|
|
134
171
|
:param host: The host of the API gateway (optional). If not set, it will be automatically generated
|
|
135
172
|
:param canary: The canary percents for the API gateway of type list[int]; for instance: [20,80]
|
|
136
173
|
"""
|
|
137
|
-
self.
|
|
138
|
-
self._validate(
|
|
139
|
-
project=project,
|
|
140
|
-
functions=functions,
|
|
141
|
-
name=name,
|
|
142
|
-
canary=canary,
|
|
143
|
-
)
|
|
144
|
-
self.project = project
|
|
145
|
-
self.name = name
|
|
174
|
+
self.description = description
|
|
146
175
|
self.host = host
|
|
147
|
-
|
|
148
176
|
self.path = path
|
|
149
|
-
self.description = description
|
|
150
|
-
self.canary = canary
|
|
151
177
|
self.authentication = authentication
|
|
178
|
+
self.functions = functions
|
|
179
|
+
self.canary = canary
|
|
180
|
+
self.project = project
|
|
181
|
+
|
|
182
|
+
self.validate(project=project, functions=functions, canary=canary)
|
|
183
|
+
|
|
184
|
+
def validate(
|
|
185
|
+
self,
|
|
186
|
+
project: str,
|
|
187
|
+
functions: Union[
|
|
188
|
+
list[str],
|
|
189
|
+
Union[
|
|
190
|
+
list[
|
|
191
|
+
Union[
|
|
192
|
+
RemoteRuntime,
|
|
193
|
+
ServingRuntime,
|
|
194
|
+
]
|
|
195
|
+
],
|
|
196
|
+
RemoteRuntime,
|
|
197
|
+
ServingRuntime,
|
|
198
|
+
],
|
|
199
|
+
],
|
|
200
|
+
canary: Optional[list[int]] = None,
|
|
201
|
+
):
|
|
202
|
+
self.functions = self._validate_functions(project=project, functions=functions)
|
|
203
|
+
|
|
204
|
+
# validating canary
|
|
205
|
+
if canary:
|
|
206
|
+
self.canary = self._validate_canary(canary)
|
|
207
|
+
|
|
208
|
+
def _validate_canary(self, canary: list[int]):
|
|
209
|
+
if len(self.functions) != len(canary):
|
|
210
|
+
raise mlrun.errors.MLRunInvalidArgumentError(
|
|
211
|
+
"Function and canary lists lengths do not match"
|
|
212
|
+
)
|
|
213
|
+
for canary_percent in canary:
|
|
214
|
+
if canary_percent < 0 or canary_percent > 100:
|
|
215
|
+
raise mlrun.errors.MLRunInvalidArgumentError(
|
|
216
|
+
"The percentage value must be in the range from 0 to 100"
|
|
217
|
+
)
|
|
218
|
+
if sum(canary) != 100:
|
|
219
|
+
raise mlrun.errors.MLRunInvalidArgumentError(
|
|
220
|
+
"The sum of canary function percents should be equal to 100"
|
|
221
|
+
)
|
|
222
|
+
return canary
|
|
223
|
+
|
|
224
|
+
@staticmethod
|
|
225
|
+
def _validate_functions(
|
|
226
|
+
project: str,
|
|
227
|
+
functions: Union[
|
|
228
|
+
list[str],
|
|
229
|
+
Union[
|
|
230
|
+
list[
|
|
231
|
+
Union[
|
|
232
|
+
RemoteRuntime,
|
|
233
|
+
ServingRuntime,
|
|
234
|
+
]
|
|
235
|
+
],
|
|
236
|
+
Union[RemoteRuntime, ServingRuntime],
|
|
237
|
+
],
|
|
238
|
+
],
|
|
239
|
+
):
|
|
240
|
+
if not isinstance(functions, list):
|
|
241
|
+
functions = [functions]
|
|
242
|
+
|
|
243
|
+
# validating functions
|
|
244
|
+
if not 1 <= len(functions) <= 2:
|
|
245
|
+
raise mlrun.errors.MLRunInvalidArgumentError(
|
|
246
|
+
f"Gateway can be created from one or two functions, "
|
|
247
|
+
f"the number of functions passed is {len(functions)}"
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
function_names = []
|
|
251
|
+
for func in functions:
|
|
252
|
+
if isinstance(func, str):
|
|
253
|
+
function_names.append(func)
|
|
254
|
+
continue
|
|
255
|
+
|
|
256
|
+
function_name = (
|
|
257
|
+
func.metadata.name if hasattr(func, "metadata") else func.name
|
|
258
|
+
)
|
|
259
|
+
if func.kind not in mlrun.runtimes.RuntimeKinds.nuclio_runtimes():
|
|
260
|
+
raise mlrun.errors.MLRunInvalidArgumentError(
|
|
261
|
+
f"Input function {function_name} is not a Nuclio function"
|
|
262
|
+
)
|
|
263
|
+
if func.metadata.project != project:
|
|
264
|
+
raise mlrun.errors.MLRunInvalidArgumentError(
|
|
265
|
+
f"input function {function_name} "
|
|
266
|
+
f"does not belong to this project"
|
|
267
|
+
)
|
|
268
|
+
nuclio_name = get_fullname(function_name, project, func.metadata.tag)
|
|
269
|
+
function_names.append(nuclio_name)
|
|
270
|
+
return function_names
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
class APIGateway(ModelObj):
|
|
274
|
+
_dict_fields = [
|
|
275
|
+
"metadata",
|
|
276
|
+
"spec",
|
|
277
|
+
"state",
|
|
278
|
+
]
|
|
279
|
+
|
|
280
|
+
@min_nuclio_versions("1.13.1")
|
|
281
|
+
def __init__(
|
|
282
|
+
self,
|
|
283
|
+
metadata: APIGatewayMetadata,
|
|
284
|
+
spec: APIGatewaySpec,
|
|
285
|
+
):
|
|
286
|
+
"""
|
|
287
|
+
Initialize the APIGateway instance.
|
|
288
|
+
|
|
289
|
+
:param metadata: (APIGatewayMetadata) The metadata of the API gateway.
|
|
290
|
+
:param spec: (APIGatewaySpec) The spec of the API gateway.
|
|
291
|
+
"""
|
|
292
|
+
self.metadata = metadata
|
|
293
|
+
self.spec = spec
|
|
152
294
|
self.state = ""
|
|
153
295
|
|
|
296
|
+
@property
|
|
297
|
+
def metadata(self) -> APIGatewayMetadata:
|
|
298
|
+
return self._metadata
|
|
299
|
+
|
|
300
|
+
@metadata.setter
|
|
301
|
+
def metadata(self, metadata):
|
|
302
|
+
self._metadata = self._verify_dict(metadata, "metadata", APIGatewayMetadata)
|
|
303
|
+
|
|
304
|
+
@property
|
|
305
|
+
def spec(self) -> APIGatewaySpec:
|
|
306
|
+
return self._spec
|
|
307
|
+
|
|
308
|
+
@spec.setter
|
|
309
|
+
def spec(self, spec):
|
|
310
|
+
self._spec = self._verify_dict(spec, "spec", APIGatewaySpec)
|
|
311
|
+
|
|
154
312
|
def invoke(
|
|
155
313
|
self,
|
|
156
314
|
method="POST",
|
|
@@ -181,7 +339,7 @@ class APIGateway:
|
|
|
181
339
|
)
|
|
182
340
|
|
|
183
341
|
if (
|
|
184
|
-
self.authentication.authentication_mode
|
|
342
|
+
self.spec.authentication.authentication_mode
|
|
185
343
|
== NUCLIO_API_GATEWAY_AUTHENTICATION_MODE_BASIC_AUTH
|
|
186
344
|
and not auth
|
|
187
345
|
):
|
|
@@ -227,15 +385,17 @@ class APIGateway:
|
|
|
227
385
|
"""
|
|
228
386
|
Synchronize the API gateway from the server.
|
|
229
387
|
"""
|
|
230
|
-
synced_gateway = mlrun.get_run_db().get_api_gateway(
|
|
388
|
+
synced_gateway = mlrun.get_run_db().get_api_gateway(
|
|
389
|
+
self.metadata.name, self.spec.project
|
|
390
|
+
)
|
|
231
391
|
synced_gateway = self.from_scheme(synced_gateway)
|
|
232
392
|
|
|
233
|
-
self.host = synced_gateway.host
|
|
234
|
-
self.path = synced_gateway.path
|
|
235
|
-
self.authentication = synced_gateway.authentication
|
|
236
|
-
self.functions = synced_gateway.functions
|
|
237
|
-
self.canary = synced_gateway.canary
|
|
238
|
-
self.description = synced_gateway.description
|
|
393
|
+
self.spec.host = synced_gateway.spec.host
|
|
394
|
+
self.spec.path = synced_gateway.spec.path
|
|
395
|
+
self.spec.authentication = synced_gateway.spec.authentication
|
|
396
|
+
self.spec.functions = synced_gateway.spec.functions
|
|
397
|
+
self.spec.canary = synced_gateway.spec.canary
|
|
398
|
+
self.spec.description = synced_gateway.spec.description
|
|
239
399
|
self.state = synced_gateway.state
|
|
240
400
|
|
|
241
401
|
def with_basic_auth(self, username: str, password: str):
|
|
@@ -245,7 +405,7 @@ class APIGateway:
|
|
|
245
405
|
:param username: (str) The username for basic authentication.
|
|
246
406
|
:param password: (str) The password for basic authentication.
|
|
247
407
|
"""
|
|
248
|
-
self.authentication = BasicAuth(username=username, password=password)
|
|
408
|
+
self.spec.authentication = BasicAuth(username=username, password=password)
|
|
249
409
|
|
|
250
410
|
def with_canary(
|
|
251
411
|
self,
|
|
@@ -276,8 +436,9 @@ class APIGateway:
|
|
|
276
436
|
f"Gateway with canary can be created only with two functions, "
|
|
277
437
|
f"the number of functions passed is {len(functions)}"
|
|
278
438
|
)
|
|
279
|
-
self.
|
|
280
|
-
|
|
439
|
+
self.spec.validate(
|
|
440
|
+
project=self.spec.project, functions=functions, canary=canary
|
|
441
|
+
)
|
|
281
442
|
|
|
282
443
|
@classmethod
|
|
283
444
|
def from_scheme(cls, api_gateway: mlrun.common.schemas.APIGateway):
|
|
@@ -288,54 +449,60 @@ class APIGateway:
|
|
|
288
449
|
if api_gateway.status
|
|
289
450
|
else mlrun.common.schemas.APIGatewayState.none
|
|
290
451
|
)
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
452
|
+
new_api_gateway = cls(
|
|
453
|
+
metadata=APIGatewayMetadata(
|
|
454
|
+
name=api_gateway.spec.name,
|
|
455
|
+
),
|
|
456
|
+
spec=APIGatewaySpec(
|
|
457
|
+
project=project,
|
|
458
|
+
description=api_gateway.spec.description,
|
|
459
|
+
host=api_gateway.spec.host,
|
|
460
|
+
path=api_gateway.spec.path,
|
|
461
|
+
authentication=APIGatewayAuthenticator.from_scheme(api_gateway.spec),
|
|
462
|
+
functions=functions,
|
|
463
|
+
canary=canary,
|
|
464
|
+
),
|
|
300
465
|
)
|
|
301
|
-
|
|
302
|
-
return
|
|
466
|
+
new_api_gateway.state = state
|
|
467
|
+
return new_api_gateway
|
|
303
468
|
|
|
304
469
|
def to_scheme(self) -> mlrun.common.schemas.APIGateway:
|
|
305
470
|
upstreams = (
|
|
306
471
|
[
|
|
307
472
|
mlrun.common.schemas.APIGatewayUpstream(
|
|
308
|
-
nucliofunction={"name": self.functions[0]},
|
|
309
|
-
percentage=self.canary[0],
|
|
473
|
+
nucliofunction={"name": self.spec.functions[0]},
|
|
474
|
+
percentage=self.spec.canary[0],
|
|
310
475
|
),
|
|
311
476
|
mlrun.common.schemas.APIGatewayUpstream(
|
|
312
477
|
# do not set percent for the second function,
|
|
313
478
|
# so we can define which function to display as a primary one in UI
|
|
314
|
-
nucliofunction={"name": self.functions[1]},
|
|
479
|
+
nucliofunction={"name": self.spec.functions[1]},
|
|
315
480
|
),
|
|
316
481
|
]
|
|
317
|
-
if self.canary
|
|
482
|
+
if self.spec.canary
|
|
318
483
|
else [
|
|
319
484
|
mlrun.common.schemas.APIGatewayUpstream(
|
|
320
485
|
nucliofunction={"name": function_name},
|
|
321
486
|
)
|
|
322
|
-
for function_name in self.functions
|
|
487
|
+
for function_name in self.spec.functions
|
|
323
488
|
]
|
|
324
489
|
)
|
|
325
490
|
api_gateway = mlrun.common.schemas.APIGateway(
|
|
326
|
-
metadata=mlrun.common.schemas.APIGatewayMetadata(
|
|
491
|
+
metadata=mlrun.common.schemas.APIGatewayMetadata(
|
|
492
|
+
name=self.metadata.name, labels={}
|
|
493
|
+
),
|
|
327
494
|
spec=mlrun.common.schemas.APIGatewaySpec(
|
|
328
|
-
name=self.name,
|
|
329
|
-
description=self.description,
|
|
330
|
-
host=self.host,
|
|
331
|
-
path=self.path,
|
|
495
|
+
name=self.metadata.name,
|
|
496
|
+
description=self.spec.description,
|
|
497
|
+
host=self.spec.host,
|
|
498
|
+
path=self.spec.path,
|
|
332
499
|
authenticationMode=mlrun.common.schemas.APIGatewayAuthenticationMode.from_str(
|
|
333
|
-
self.authentication.authentication_mode
|
|
500
|
+
self.spec.authentication.authentication_mode
|
|
334
501
|
),
|
|
335
502
|
upstreams=upstreams,
|
|
336
503
|
),
|
|
337
504
|
)
|
|
338
|
-
api_gateway.spec.authentication = self.authentication.to_scheme()
|
|
505
|
+
api_gateway.spec.authentication = self.spec.authentication.to_scheme()
|
|
339
506
|
return api_gateway
|
|
340
507
|
|
|
341
508
|
@property
|
|
@@ -347,103 +514,10 @@ class APIGateway:
|
|
|
347
514
|
|
|
348
515
|
:return: (str) The invoke URL.
|
|
349
516
|
"""
|
|
350
|
-
host = self.host
|
|
351
|
-
if not self.host.startswith("http"):
|
|
352
|
-
host = f"https://{self.host}"
|
|
353
|
-
return urljoin(host, self.path)
|
|
354
|
-
|
|
355
|
-
def _validate(
|
|
356
|
-
self,
|
|
357
|
-
name: str,
|
|
358
|
-
project: str,
|
|
359
|
-
functions: Union[
|
|
360
|
-
list[str],
|
|
361
|
-
Union[
|
|
362
|
-
list[
|
|
363
|
-
Union[
|
|
364
|
-
RemoteRuntime,
|
|
365
|
-
ServingRuntime,
|
|
366
|
-
]
|
|
367
|
-
],
|
|
368
|
-
Union[RemoteRuntime, ServingRuntime],
|
|
369
|
-
],
|
|
370
|
-
],
|
|
371
|
-
canary: Optional[list[int]] = None,
|
|
372
|
-
):
|
|
373
|
-
if not name:
|
|
374
|
-
raise mlrun.errors.MLRunInvalidArgumentError(
|
|
375
|
-
"API Gateway name cannot be empty"
|
|
376
|
-
)
|
|
377
|
-
|
|
378
|
-
self.functions = self._validate_functions(project=project, functions=functions)
|
|
379
|
-
|
|
380
|
-
# validating canary
|
|
381
|
-
if canary:
|
|
382
|
-
self._validate_canary(canary)
|
|
383
|
-
|
|
384
|
-
def _validate_canary(self, canary: list[int]):
|
|
385
|
-
if len(self.functions) != len(canary):
|
|
386
|
-
raise mlrun.errors.MLRunInvalidArgumentError(
|
|
387
|
-
"Function and canary lists lengths do not match"
|
|
388
|
-
)
|
|
389
|
-
for canary_percent in canary:
|
|
390
|
-
if canary_percent < 0 or canary_percent > 100:
|
|
391
|
-
raise mlrun.errors.MLRunInvalidArgumentError(
|
|
392
|
-
"The percentage value must be in the range from 0 to 100"
|
|
393
|
-
)
|
|
394
|
-
if sum(canary) != 100:
|
|
395
|
-
raise mlrun.errors.MLRunInvalidArgumentError(
|
|
396
|
-
"The sum of canary function percents should be equal to 100"
|
|
397
|
-
)
|
|
398
|
-
return canary
|
|
399
|
-
|
|
400
|
-
@staticmethod
|
|
401
|
-
def _validate_functions(
|
|
402
|
-
project: str,
|
|
403
|
-
functions: Union[
|
|
404
|
-
list[str],
|
|
405
|
-
Union[
|
|
406
|
-
list[
|
|
407
|
-
Union[
|
|
408
|
-
RemoteRuntime,
|
|
409
|
-
ServingRuntime,
|
|
410
|
-
]
|
|
411
|
-
],
|
|
412
|
-
Union[RemoteRuntime, ServingRuntime],
|
|
413
|
-
],
|
|
414
|
-
],
|
|
415
|
-
):
|
|
416
|
-
if not isinstance(functions, list):
|
|
417
|
-
functions = [functions]
|
|
418
|
-
|
|
419
|
-
# validating functions
|
|
420
|
-
if not 1 <= len(functions) <= 2:
|
|
421
|
-
raise mlrun.errors.MLRunInvalidArgumentError(
|
|
422
|
-
f"Gateway can be created from one or two functions, "
|
|
423
|
-
f"the number of functions passed is {len(functions)}"
|
|
424
|
-
)
|
|
425
|
-
|
|
426
|
-
function_names = []
|
|
427
|
-
for func in functions:
|
|
428
|
-
if isinstance(func, str):
|
|
429
|
-
function_names.append(func)
|
|
430
|
-
continue
|
|
431
|
-
|
|
432
|
-
function_name = (
|
|
433
|
-
func.metadata.name if hasattr(func, "metadata") else func.name
|
|
434
|
-
)
|
|
435
|
-
if func.kind not in mlrun.runtimes.RuntimeKinds.nuclio_runtimes():
|
|
436
|
-
raise mlrun.errors.MLRunInvalidArgumentError(
|
|
437
|
-
f"Input function {function_name} is not a Nuclio function"
|
|
438
|
-
)
|
|
439
|
-
if func.metadata.project != project:
|
|
440
|
-
raise mlrun.errors.MLRunInvalidArgumentError(
|
|
441
|
-
f"input function {function_name} "
|
|
442
|
-
f"does not belong to this project"
|
|
443
|
-
)
|
|
444
|
-
nuclio_name = get_fullname(function_name, project, func.metadata.tag)
|
|
445
|
-
function_names.append(nuclio_name)
|
|
446
|
-
return function_names
|
|
517
|
+
host = self.spec.host
|
|
518
|
+
if not self.spec.host.startswith("http"):
|
|
519
|
+
host = f"https://{self.spec.host}"
|
|
520
|
+
return urljoin(host, self.spec.path)
|
|
447
521
|
|
|
448
522
|
@staticmethod
|
|
449
523
|
def _generate_basic_auth(username: str, password: str):
|
|
@@ -475,3 +549,51 @@ class APIGateway:
|
|
|
475
549
|
else:
|
|
476
550
|
# Nuclio only supports 1 or 2 upstream functions
|
|
477
551
|
return None, None
|
|
552
|
+
|
|
553
|
+
@property
|
|
554
|
+
def name(self):
|
|
555
|
+
return self.metadata.name
|
|
556
|
+
|
|
557
|
+
@name.setter
|
|
558
|
+
def name(self, value):
|
|
559
|
+
self.metadata.name = value
|
|
560
|
+
|
|
561
|
+
@property
|
|
562
|
+
def project(self):
|
|
563
|
+
return self.spec.project
|
|
564
|
+
|
|
565
|
+
@project.setter
|
|
566
|
+
def project(self, value):
|
|
567
|
+
self.spec.project = value
|
|
568
|
+
|
|
569
|
+
@property
|
|
570
|
+
def description(self):
|
|
571
|
+
return self.spec.description
|
|
572
|
+
|
|
573
|
+
@description.setter
|
|
574
|
+
def description(self, value):
|
|
575
|
+
self.spec.description = value
|
|
576
|
+
|
|
577
|
+
@property
|
|
578
|
+
def host(self):
|
|
579
|
+
return self.spec.host
|
|
580
|
+
|
|
581
|
+
@host.setter
|
|
582
|
+
def host(self, value):
|
|
583
|
+
self.spec.host = value
|
|
584
|
+
|
|
585
|
+
@property
|
|
586
|
+
def path(self):
|
|
587
|
+
return self.spec.path
|
|
588
|
+
|
|
589
|
+
@path.setter
|
|
590
|
+
def path(self, value):
|
|
591
|
+
self.spec.path = value
|
|
592
|
+
|
|
593
|
+
@property
|
|
594
|
+
def authentication(self):
|
|
595
|
+
return self.spec.authentication
|
|
596
|
+
|
|
597
|
+
@authentication.setter
|
|
598
|
+
def authentication(self, value):
|
|
599
|
+
self.spec.authentication = value
|
mlrun/runtimes/pod.py
CHANGED
|
@@ -1086,12 +1086,12 @@ class KubeResource(BaseRuntime):
|
|
|
1086
1086
|
|
|
1087
1087
|
def _set_env(self, name, value=None, value_from=None):
|
|
1088
1088
|
new_var = k8s_client.V1EnvVar(name=name, value=value, value_from=value_from)
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1089
|
+
|
|
1090
|
+
# ensure we don't have duplicate env vars with the same name
|
|
1091
|
+
for env_index, value_item in enumerate(self.spec.env):
|
|
1092
|
+
if get_item_name(value_item) == name:
|
|
1093
|
+
self.spec.env[env_index] = new_var
|
|
1093
1094
|
return self
|
|
1094
|
-
i += 1
|
|
1095
1095
|
self.spec.env.append(new_var)
|
|
1096
1096
|
return self
|
|
1097
1097
|
|
mlrun/serving/states.py
CHANGED
|
@@ -19,7 +19,7 @@ import pathlib
|
|
|
19
19
|
import traceback
|
|
20
20
|
from copy import copy, deepcopy
|
|
21
21
|
from inspect import getfullargspec, signature
|
|
22
|
-
from typing import Union
|
|
22
|
+
from typing import Any, Union
|
|
23
23
|
|
|
24
24
|
import mlrun
|
|
25
25
|
|
|
@@ -327,7 +327,7 @@ class BaseStep(ModelObj):
|
|
|
327
327
|
parent = self._parent
|
|
328
328
|
else:
|
|
329
329
|
raise GraphError(
|
|
330
|
-
f"step {self.name} parent is not set or
|
|
330
|
+
f"step {self.name} parent is not set or it's not part of a graph"
|
|
331
331
|
)
|
|
332
332
|
|
|
333
333
|
name, step = params_to_step(
|
|
@@ -349,6 +349,36 @@ class BaseStep(ModelObj):
|
|
|
349
349
|
parent._last_added = step
|
|
350
350
|
return step
|
|
351
351
|
|
|
352
|
+
def set_flow(
|
|
353
|
+
self,
|
|
354
|
+
steps: list[Union[str, StepToDict, dict[str, Any]]],
|
|
355
|
+
force: bool = False,
|
|
356
|
+
):
|
|
357
|
+
"""set list of steps as downstream from this step, in the order specified. This will overwrite any existing
|
|
358
|
+
downstream steps.
|
|
359
|
+
|
|
360
|
+
:param steps: list of steps to follow this one
|
|
361
|
+
:param force: whether to overwrite existing downstream steps. If False, this method will fail if any downstream
|
|
362
|
+
steps have already been defined. Defaults to False.
|
|
363
|
+
:return: the last step added to the flow
|
|
364
|
+
|
|
365
|
+
example:
|
|
366
|
+
The below code sets the downstream nodes of step1 by using a list of steps (provided to `set_flow()`) and a
|
|
367
|
+
single step (provided to `to()`), resulting in the graph (step1 -> step2 -> step3 -> step4).
|
|
368
|
+
Notice that using `force=True` is required in case step1 already had downstream nodes (e.g. if the existing
|
|
369
|
+
graph is step1 -> step2_old) and that following the execution of this code the existing downstream steps
|
|
370
|
+
are removed. If the intention is to split the graph (and not to overwrite), please use `to()`.
|
|
371
|
+
|
|
372
|
+
step1.set_flow(
|
|
373
|
+
[
|
|
374
|
+
dict(name="step2", handler="step2_handler"),
|
|
375
|
+
dict(name="step3", class_name="Step3Class"),
|
|
376
|
+
],
|
|
377
|
+
force=True,
|
|
378
|
+
).to(dict(name="step4", class_name="Step4Class"))
|
|
379
|
+
"""
|
|
380
|
+
raise NotImplementedError("set_flow() can only be called on a FlowStep")
|
|
381
|
+
|
|
352
382
|
|
|
353
383
|
class TaskStep(BaseStep):
|
|
354
384
|
"""task execution step, runs a class or handler"""
|
|
@@ -1258,6 +1288,27 @@ class FlowStep(BaseStep):
|
|
|
1258
1288
|
)
|
|
1259
1289
|
self[step_name].after_step(name)
|
|
1260
1290
|
|
|
1291
|
+
def set_flow(
|
|
1292
|
+
self,
|
|
1293
|
+
steps: list[Union[str, StepToDict, dict[str, Any]]],
|
|
1294
|
+
force: bool = False,
|
|
1295
|
+
):
|
|
1296
|
+
if not force and self.steps:
|
|
1297
|
+
raise mlrun.errors.MLRunInvalidArgumentError(
|
|
1298
|
+
"set_flow() called on a step that already has downstream steps. "
|
|
1299
|
+
"If you want to overwrite existing steps, set force=True."
|
|
1300
|
+
)
|
|
1301
|
+
|
|
1302
|
+
self.steps = None
|
|
1303
|
+
step = self
|
|
1304
|
+
for next_step in steps:
|
|
1305
|
+
if isinstance(next_step, dict):
|
|
1306
|
+
step = step.to(**next_step)
|
|
1307
|
+
else:
|
|
1308
|
+
step = step.to(next_step)
|
|
1309
|
+
|
|
1310
|
+
return step
|
|
1311
|
+
|
|
1261
1312
|
|
|
1262
1313
|
class RootFlowStep(FlowStep):
|
|
1263
1314
|
"""root flow step"""
|