mlrun 1.8.0rc31__py3-none-any.whl → 1.8.0rc33__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/__main__.py +3 -3
- mlrun/config.py +2 -13
- mlrun/db/base.py +1 -1
- mlrun/db/httpdb.py +5 -4
- mlrun/db/nopdb.py +1 -1
- mlrun/model_monitoring/api.py +17 -16
- mlrun/model_monitoring/applications/__init__.py +0 -5
- mlrun/model_monitoring/applications/base.py +71 -13
- mlrun/model_monitoring/applications/evidently/__init__.py +19 -0
- mlrun/model_monitoring/db/tsdb/tdengine/tdengine_connector.py +17 -10
- mlrun/model_monitoring/helpers.py +2 -1
- mlrun/projects/pipelines.py +1 -1
- mlrun/projects/project.py +4 -4
- mlrun/runtimes/base.py +1 -4
- mlrun/runtimes/databricks_job/databricks_runtime.py +4 -3
- mlrun/runtimes/function_reference.py +2 -2
- mlrun/runtimes/pod.py +139 -33
- mlrun/utils/helpers.py +28 -1
- mlrun/utils/version/version.json +2 -2
- {mlrun-1.8.0rc31.dist-info → mlrun-1.8.0rc33.dist-info}/METADATA +9 -9
- {mlrun-1.8.0rc31.dist-info → mlrun-1.8.0rc33.dist-info}/RECORD +26 -25
- /mlrun/model_monitoring/applications/{evidently_base.py → evidently/base.py} +0 -0
- {mlrun-1.8.0rc31.dist-info → mlrun-1.8.0rc33.dist-info}/LICENSE +0 -0
- {mlrun-1.8.0rc31.dist-info → mlrun-1.8.0rc33.dist-info}/WHEEL +0 -0
- {mlrun-1.8.0rc31.dist-info → mlrun-1.8.0rc33.dist-info}/entry_points.txt +0 -0
- {mlrun-1.8.0rc31.dist-info → mlrun-1.8.0rc33.dist-info}/top_level.txt +0 -0
mlrun/__main__.py
CHANGED
|
@@ -19,7 +19,7 @@ import socket
|
|
|
19
19
|
import traceback
|
|
20
20
|
import warnings
|
|
21
21
|
from ast import literal_eval
|
|
22
|
-
from base64 import b64decode
|
|
22
|
+
from base64 import b64decode
|
|
23
23
|
from os import environ, path, remove
|
|
24
24
|
from pprint import pprint
|
|
25
25
|
|
|
@@ -298,7 +298,7 @@ def run(
|
|
|
298
298
|
if url_file and path.isfile(url_file):
|
|
299
299
|
with open(url_file) as fp:
|
|
300
300
|
body = fp.read()
|
|
301
|
-
based =
|
|
301
|
+
based = mlrun.utils.helpers.encode_user_code(body)
|
|
302
302
|
logger.info(f"packing code at {url_file}")
|
|
303
303
|
update_in(runtime, "spec.build.functionSourceCode", based)
|
|
304
304
|
url = f"main{pathlib.Path(url_file).suffix} {url_args}"
|
|
@@ -557,7 +557,7 @@ def build(
|
|
|
557
557
|
exit(1)
|
|
558
558
|
with open(source) as fp:
|
|
559
559
|
body = fp.read()
|
|
560
|
-
based =
|
|
560
|
+
based = mlrun.utils.helpers.encode_user_code(body)
|
|
561
561
|
logger.info(f"Packing code at {source}")
|
|
562
562
|
b.functionSourceCode = based
|
|
563
563
|
func.spec.command = ""
|
mlrun/config.py
CHANGED
|
@@ -30,7 +30,6 @@ import typing
|
|
|
30
30
|
import warnings
|
|
31
31
|
from collections.abc import Mapping
|
|
32
32
|
from datetime import timedelta
|
|
33
|
-
from distutils.util import strtobool
|
|
34
33
|
from os.path import expanduser
|
|
35
34
|
from threading import Lock
|
|
36
35
|
|
|
@@ -105,7 +104,7 @@ default_config = {
|
|
|
105
104
|
# custom logger format, workes only with log_formatter: custom
|
|
106
105
|
# Note that your custom format must include those 4 fields - timestamp, level, message and more
|
|
107
106
|
"log_format_override": None,
|
|
108
|
-
"submit_timeout": "
|
|
107
|
+
"submit_timeout": "280", # timeout when submitting a new k8s resource
|
|
109
108
|
# runtimes cleanup interval in seconds
|
|
110
109
|
"runtimes_cleanup_interval": "300",
|
|
111
110
|
"monitoring": {
|
|
@@ -267,6 +266,7 @@ default_config = {
|
|
|
267
266
|
# When the module is reloaded, the maximum depth recursion configuration for the recursive reload
|
|
268
267
|
# function is used to prevent infinite loop
|
|
269
268
|
"reload_max_recursion_depth": 100,
|
|
269
|
+
"source_code_max_bytes": 10000,
|
|
270
270
|
},
|
|
271
271
|
"databricks": {
|
|
272
272
|
"artifact_directory_path": "/mlrun_databricks_runtime/artifacts_dictionaries"
|
|
@@ -1472,17 +1472,6 @@ def _convert_resources_to_str(config: typing.Optional[dict] = None):
|
|
|
1472
1472
|
resource_requirement[resource_type] = str(value)
|
|
1473
1473
|
|
|
1474
1474
|
|
|
1475
|
-
def _convert_str(value, typ):
|
|
1476
|
-
if typ in (str, _none_type):
|
|
1477
|
-
return value
|
|
1478
|
-
|
|
1479
|
-
if typ is bool:
|
|
1480
|
-
return strtobool(value)
|
|
1481
|
-
|
|
1482
|
-
# e.g. int('8080') → 8080
|
|
1483
|
-
return typ(value)
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
1475
|
def _configure_ssl_verification(verify_ssl: bool) -> None:
|
|
1487
1476
|
"""Configure SSL verification warnings based on the setting."""
|
|
1488
1477
|
if not verify_ssl:
|
mlrun/db/base.py
CHANGED
|
@@ -720,7 +720,7 @@ class RunDBInterface(ABC):
|
|
|
720
720
|
def list_model_endpoints(
|
|
721
721
|
self,
|
|
722
722
|
project: str,
|
|
723
|
-
|
|
723
|
+
names: Optional[Union[str, list[str]]] = None,
|
|
724
724
|
function_name: Optional[str] = None,
|
|
725
725
|
function_tag: Optional[str] = None,
|
|
726
726
|
model_name: Optional[str] = None,
|
mlrun/db/httpdb.py
CHANGED
|
@@ -3765,7 +3765,7 @@ class HTTPRunDB(RunDBInterface):
|
|
|
3765
3765
|
def list_model_endpoints(
|
|
3766
3766
|
self,
|
|
3767
3767
|
project: str,
|
|
3768
|
-
|
|
3768
|
+
names: Optional[Union[str, list[str]]] = None,
|
|
3769
3769
|
function_name: Optional[str] = None,
|
|
3770
3770
|
function_tag: Optional[str] = None,
|
|
3771
3771
|
model_name: Optional[str] = None,
|
|
@@ -3782,7 +3782,7 @@ class HTTPRunDB(RunDBInterface):
|
|
|
3782
3782
|
List model endpoints with optional filtering by name, function name, model name, labels, and time range.
|
|
3783
3783
|
|
|
3784
3784
|
:param project: The name of the project
|
|
3785
|
-
:param
|
|
3785
|
+
:param names: The name of the model endpoint, or list of names of the model endpoints
|
|
3786
3786
|
:param function_name: The name of the function
|
|
3787
3787
|
:param function_tag: The tag of the function
|
|
3788
3788
|
:param model_name: The name of the model
|
|
@@ -3798,12 +3798,13 @@ class HTTPRunDB(RunDBInterface):
|
|
|
3798
3798
|
"""
|
|
3799
3799
|
path = f"projects/{project}/model-endpoints"
|
|
3800
3800
|
labels = self._parse_labels(labels)
|
|
3801
|
-
|
|
3801
|
+
if names and isinstance(names, str):
|
|
3802
|
+
names = [names]
|
|
3802
3803
|
response = self.api_call(
|
|
3803
3804
|
method=mlrun.common.types.HTTPMethod.GET,
|
|
3804
3805
|
path=path,
|
|
3805
3806
|
params={
|
|
3806
|
-
"name":
|
|
3807
|
+
"name": names,
|
|
3807
3808
|
"model_name": model_name,
|
|
3808
3809
|
"model_tag": model_tag,
|
|
3809
3810
|
"function_name": function_name,
|
mlrun/db/nopdb.py
CHANGED
|
@@ -617,7 +617,7 @@ class NopDB(RunDBInterface):
|
|
|
617
617
|
def list_model_endpoints(
|
|
618
618
|
self,
|
|
619
619
|
project: str,
|
|
620
|
-
|
|
620
|
+
names: Optional[Union[str, list[str]]] = None,
|
|
621
621
|
function_name: Optional[str] = None,
|
|
622
622
|
function_tag: Optional[str] = None,
|
|
623
623
|
model_name: Optional[str] = None,
|
mlrun/model_monitoring/api.py
CHANGED
|
@@ -88,19 +88,24 @@ def get_or_create_model_endpoint(
|
|
|
88
88
|
# Generate a runtime database
|
|
89
89
|
db_session = mlrun.get_run_db()
|
|
90
90
|
model_endpoint = None
|
|
91
|
+
if not function_name and context:
|
|
92
|
+
function_name = FunctionURI.from_string(
|
|
93
|
+
context.to_dict()["spec"]["function"]
|
|
94
|
+
).function
|
|
91
95
|
try:
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
96
|
+
model_endpoint = db_session.get_model_endpoint(
|
|
97
|
+
project=project,
|
|
98
|
+
name=model_endpoint_name,
|
|
99
|
+
endpoint_id=endpoint_id,
|
|
100
|
+
function_name=function_name,
|
|
101
|
+
function_tag=function_tag or "latest",
|
|
102
|
+
)
|
|
103
|
+
# If other fields provided, validate that they are correspond to the existing model endpoint data
|
|
104
|
+
_model_endpoint_validations(
|
|
105
|
+
model_endpoint=model_endpoint,
|
|
106
|
+
model_path=model_path,
|
|
107
|
+
sample_set_statistics=sample_set_statistics,
|
|
108
|
+
)
|
|
104
109
|
|
|
105
110
|
except mlrun.errors.MLRunNotFoundError:
|
|
106
111
|
# Create a new model endpoint with the provided details
|
|
@@ -361,10 +366,6 @@ def _generate_model_endpoint(
|
|
|
361
366
|
|
|
362
367
|
:return `mlrun.common.schemas.ModelEndpoint` object.
|
|
363
368
|
"""
|
|
364
|
-
if not function_name and context:
|
|
365
|
-
function_name = FunctionURI.from_string(
|
|
366
|
-
context.to_dict()["spec"]["function"]
|
|
367
|
-
).function
|
|
368
369
|
model_obj = None
|
|
369
370
|
if model_path:
|
|
370
371
|
model_obj: mlrun.artifacts.ModelArtifact = (
|
|
@@ -15,9 +15,4 @@
|
|
|
15
15
|
|
|
16
16
|
from .base import ModelMonitoringApplicationBase
|
|
17
17
|
from .context import MonitoringApplicationContext
|
|
18
|
-
from .evidently_base import (
|
|
19
|
-
_HAS_EVIDENTLY,
|
|
20
|
-
SUPPORTED_EVIDENTLY_VERSION,
|
|
21
|
-
EvidentlyModelMonitoringApplicationBase,
|
|
22
|
-
)
|
|
23
18
|
from .results import ModelMonitoringApplicationMetric, ModelMonitoringApplicationResult
|
|
@@ -28,6 +28,7 @@ import mlrun.model_monitoring.api as mm_api
|
|
|
28
28
|
import mlrun.model_monitoring.applications.context as mm_context
|
|
29
29
|
import mlrun.model_monitoring.applications.results as mm_results
|
|
30
30
|
from mlrun.serving.utils import MonitoringApplicationToDict
|
|
31
|
+
from mlrun.utils import logger
|
|
31
32
|
|
|
32
33
|
|
|
33
34
|
class ModelMonitoringApplicationBase(MonitoringApplicationToDict, ABC):
|
|
@@ -94,7 +95,7 @@ class ModelMonitoringApplicationBase(MonitoringApplicationToDict, ABC):
|
|
|
94
95
|
context: "mlrun.MLClientCtx",
|
|
95
96
|
sample_data: Optional[pd.DataFrame] = None,
|
|
96
97
|
reference_data: Optional[pd.DataFrame] = None,
|
|
97
|
-
endpoints: Optional[list[tuple[str, str]]] = None,
|
|
98
|
+
endpoints: Optional[Union[list[tuple[str, str]], list[str], str]] = None,
|
|
98
99
|
start: Optional[str] = None,
|
|
99
100
|
end: Optional[str] = None,
|
|
100
101
|
base_period: Optional[int] = None,
|
|
@@ -137,14 +138,62 @@ class ModelMonitoringApplicationBase(MonitoringApplicationToDict, ABC):
|
|
|
137
138
|
}
|
|
138
139
|
)
|
|
139
140
|
result_key = (
|
|
140
|
-
f"{endpoint_name}_{window_start.isoformat()}_{window_end.isoformat()}"
|
|
141
|
+
f"{endpoint_name}-{endpoint_id}_{window_start.isoformat()}_{window_end.isoformat()}"
|
|
141
142
|
if window_start and window_end
|
|
142
|
-
else endpoint_name
|
|
143
|
+
else f"{endpoint_name}-{endpoint_id}"
|
|
143
144
|
)
|
|
144
145
|
context.log_result(result_key, result)
|
|
145
146
|
else:
|
|
146
147
|
return call_do_tracking()
|
|
147
148
|
|
|
149
|
+
@staticmethod
|
|
150
|
+
def _handle_endpoints_type_evaluate(
|
|
151
|
+
project: str,
|
|
152
|
+
endpoints: Union[list[tuple[str, str]], list[str], str, None],
|
|
153
|
+
) -> list[tuple[str, str]]:
|
|
154
|
+
if endpoints:
|
|
155
|
+
if isinstance(endpoints, str) or (
|
|
156
|
+
isinstance(endpoints, list) and isinstance(endpoints[0], str)
|
|
157
|
+
):
|
|
158
|
+
endpoints_list = (
|
|
159
|
+
mlrun.get_run_db()
|
|
160
|
+
.list_model_endpoints(
|
|
161
|
+
project,
|
|
162
|
+
names=endpoints,
|
|
163
|
+
latest_only=True,
|
|
164
|
+
)
|
|
165
|
+
.endpoints
|
|
166
|
+
)
|
|
167
|
+
if endpoints_list:
|
|
168
|
+
list_endpoints_result = [
|
|
169
|
+
(endpoint.metadata.name, endpoint.metadata.uid)
|
|
170
|
+
for endpoint in endpoints_list
|
|
171
|
+
]
|
|
172
|
+
retrieve_ep_names = list(
|
|
173
|
+
map(lambda endpoint: endpoint[0], list_endpoints_result)
|
|
174
|
+
)
|
|
175
|
+
missing = set(
|
|
176
|
+
[endpoints] if isinstance(endpoints, str) else endpoints
|
|
177
|
+
) - set(retrieve_ep_names)
|
|
178
|
+
if missing:
|
|
179
|
+
logger.warning(
|
|
180
|
+
"Could not list all the required endpoints.",
|
|
181
|
+
missing_endpoint=missing,
|
|
182
|
+
endpoints=list_endpoints_result,
|
|
183
|
+
)
|
|
184
|
+
else:
|
|
185
|
+
raise mlrun.errors.MLRunNotFoundError(
|
|
186
|
+
f"Did not find any model_endpoint named ' {endpoints}'"
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
if not (
|
|
190
|
+
isinstance(endpoints, list) and isinstance(endpoints[0], (list, tuple))
|
|
191
|
+
):
|
|
192
|
+
raise mlrun.errors.MLRunInvalidArgumentError(
|
|
193
|
+
"Could not resolve endpoints as list of [(name, uid)]"
|
|
194
|
+
)
|
|
195
|
+
return endpoints
|
|
196
|
+
|
|
148
197
|
@staticmethod
|
|
149
198
|
def _window_generator(
|
|
150
199
|
start: Optional[str], end: Optional[str], base_period: Optional[int]
|
|
@@ -337,7 +386,7 @@ class ModelMonitoringApplicationBase(MonitoringApplicationToDict, ABC):
|
|
|
337
386
|
class_handler: Optional[str] = None,
|
|
338
387
|
requirements: Optional[Union[str, list[str]]] = None,
|
|
339
388
|
requirements_file: str = "",
|
|
340
|
-
endpoints: Optional[list[tuple[str, str]]] = None,
|
|
389
|
+
endpoints: Optional[Union[list[tuple[str, str]], list[str], str]] = None,
|
|
341
390
|
start: Optional[datetime] = None,
|
|
342
391
|
end: Optional[datetime] = None,
|
|
343
392
|
base_period: Optional[int] = None,
|
|
@@ -365,24 +414,29 @@ class ModelMonitoringApplicationBase(MonitoringApplicationToDict, ABC):
|
|
|
365
414
|
:param requirements: List of Python requirements to be installed in the image.
|
|
366
415
|
:param requirements_file: Path to a Python requirements file to be installed in the image.
|
|
367
416
|
:param endpoints: A list of tuples of the model endpoint (name, uid) to get the data from.
|
|
368
|
-
|
|
369
|
-
|
|
417
|
+
allow providing a list of model_endpoint names or name for a single model_endpoint.
|
|
418
|
+
Note: provide names retrieves the model all the active model endpoints using those
|
|
419
|
+
names (cross function model endpoints)
|
|
420
|
+
If provided, and ``sample_data`` is not ``None``, you have to provide also the
|
|
421
|
+
``start`` and ``end`` times of the data to analyze from the model endpoints.
|
|
370
422
|
:param start: The start time of the endpoint's data, not included.
|
|
371
423
|
If you want the model endpoint's data at ``start`` included, you need to subtract a
|
|
372
424
|
small ``datetime.timedelta`` from it.
|
|
373
425
|
:param end: The end time of the endpoint's data, included.
|
|
374
426
|
Please note: when ``start`` and ``end`` are set, they create a left-open time interval
|
|
375
|
-
("window") :math:`(\\
|
|
376
|
-
``start`` and includes the data at ``end``:
|
|
377
|
-
:math:`\\
|
|
378
|
-
window's data.
|
|
427
|
+
("window") :math:`(\\operatorname{start}, \\operatorname{end}]` that excludes the
|
|
428
|
+
endpoint's data at ``start`` and includes the data at ``end``:
|
|
429
|
+
:math:`\\operatorname{start} < t \\leq \\operatorname{end}`, :math:`t` is the time
|
|
430
|
+
taken in the window's data.
|
|
379
431
|
:param base_period: The window length in minutes. If ``None``, the whole window from ``start`` to ``end``
|
|
380
432
|
is taken. If an integer is specified, the application is run from ``start`` to ``end``
|
|
381
433
|
in ``base_period`` length windows, except for the last window that ends at ``end`` and
|
|
382
434
|
therefore may be shorter:
|
|
383
|
-
:math:`(\\
|
|
384
|
-
(\\
|
|
385
|
-
|
|
435
|
+
:math:`(\\operatorname{start}, \\operatorname{start} + \\operatorname{base\\_period}],
|
|
436
|
+
(\\operatorname{start} + \\operatorname{base\\_period},
|
|
437
|
+
\\operatorname{start} + 2\\cdot\\operatorname{base\\_period}],
|
|
438
|
+
..., (\\operatorname{start} +
|
|
439
|
+
m\\cdot\\operatorname{base\\_period}, \\operatorname{end}]`,
|
|
386
440
|
where :math:`m` is some positive integer.
|
|
387
441
|
|
|
388
442
|
:returns: The output of the
|
|
@@ -405,6 +459,10 @@ class ModelMonitoringApplicationBase(MonitoringApplicationToDict, ABC):
|
|
|
405
459
|
|
|
406
460
|
params: dict[str, Union[list[tuple[str, str]], str, int, None]] = {}
|
|
407
461
|
if endpoints:
|
|
462
|
+
endpoints = cls._handle_endpoints_type_evaluate(
|
|
463
|
+
project=project.name,
|
|
464
|
+
endpoints=endpoints,
|
|
465
|
+
)
|
|
408
466
|
params["endpoints"] = endpoints
|
|
409
467
|
if sample_data is None:
|
|
410
468
|
if start is None or end is None:
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Copyright 2025 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 .base import (
|
|
16
|
+
_HAS_EVIDENTLY,
|
|
17
|
+
SUPPORTED_EVIDENTLY_VERSION,
|
|
18
|
+
EvidentlyModelMonitoringApplicationBase,
|
|
19
|
+
)
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
import asyncio
|
|
15
15
|
from datetime import datetime, timedelta
|
|
16
|
+
from threading import Lock
|
|
16
17
|
from typing import Callable, Literal, Optional, Union
|
|
17
18
|
|
|
18
19
|
import pandas as pd
|
|
@@ -30,6 +31,9 @@ from mlrun.model_monitoring.db import TSDBConnector
|
|
|
30
31
|
from mlrun.model_monitoring.helpers import get_invocations_fqn
|
|
31
32
|
from mlrun.utils import logger
|
|
32
33
|
|
|
34
|
+
_connection = None
|
|
35
|
+
_connection_lock = Lock()
|
|
36
|
+
|
|
33
37
|
|
|
34
38
|
class TDEngineConnector(TSDBConnector):
|
|
35
39
|
"""
|
|
@@ -37,23 +41,18 @@ class TDEngineConnector(TSDBConnector):
|
|
|
37
41
|
"""
|
|
38
42
|
|
|
39
43
|
type: str = mm_schemas.TSDBTarget.TDEngine
|
|
44
|
+
database = f"{tdengine_schemas._MODEL_MONITORING_DATABASE}_{mlrun.mlconf.system_id}"
|
|
40
45
|
|
|
41
46
|
def __init__(
|
|
42
47
|
self,
|
|
43
48
|
project: str,
|
|
44
49
|
profile: DatastoreProfile,
|
|
45
|
-
database: Optional[str] = None,
|
|
46
50
|
**kwargs,
|
|
47
51
|
):
|
|
48
52
|
super().__init__(project=project)
|
|
49
53
|
|
|
50
54
|
self._tdengine_connection_profile = profile
|
|
51
|
-
self.database = (
|
|
52
|
-
database
|
|
53
|
-
or f"{tdengine_schemas._MODEL_MONITORING_DATABASE}_{mlrun.mlconf.system_id}"
|
|
54
|
-
)
|
|
55
55
|
|
|
56
|
-
self._connection = None
|
|
57
56
|
self._init_super_tables()
|
|
58
57
|
|
|
59
58
|
self._timeout = mlrun.mlconf.model_endpoint_monitoring.tdengine.timeout
|
|
@@ -61,9 +60,16 @@ class TDEngineConnector(TSDBConnector):
|
|
|
61
60
|
|
|
62
61
|
@property
|
|
63
62
|
def connection(self) -> TDEngineConnection:
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
63
|
+
global _connection
|
|
64
|
+
|
|
65
|
+
if _connection:
|
|
66
|
+
return _connection
|
|
67
|
+
|
|
68
|
+
with _connection_lock:
|
|
69
|
+
if not _connection:
|
|
70
|
+
_connection = self._create_connection()
|
|
71
|
+
|
|
72
|
+
return _connection
|
|
67
73
|
|
|
68
74
|
def _create_connection(self) -> TDEngineConnection:
|
|
69
75
|
"""Establish a connection to the TSDB server."""
|
|
@@ -99,7 +105,8 @@ class TDEngineConnector(TSDBConnector):
|
|
|
99
105
|
"""Create TDEngine supertables."""
|
|
100
106
|
for table in self.tables:
|
|
101
107
|
create_table_query = self.tables[table]._create_super_table_query()
|
|
102
|
-
self.connection
|
|
108
|
+
conn = self.connection
|
|
109
|
+
conn.run(
|
|
103
110
|
statements=create_table_query,
|
|
104
111
|
timeout=self._timeout,
|
|
105
112
|
retries=self._retries,
|
|
@@ -141,7 +141,8 @@ def get_stream_path(
|
|
|
141
141
|
elif isinstance(
|
|
142
142
|
profile, mlrun.datastore.datastore_profile.DatastoreProfileKafkaSource
|
|
143
143
|
):
|
|
144
|
-
|
|
144
|
+
attributes = profile.attributes()
|
|
145
|
+
stream_uri = f"kafka://{attributes['brokers'][0]}"
|
|
145
146
|
else:
|
|
146
147
|
raise mlrun.errors.MLRunValueError(
|
|
147
148
|
f"Received an unexpected stream profile type: {type(profile)}\n"
|
mlrun/projects/pipelines.py
CHANGED
|
@@ -629,7 +629,7 @@ class _KFPRunner(_PipelineRunner):
|
|
|
629
629
|
secret_params=notification.secret_params,
|
|
630
630
|
)
|
|
631
631
|
|
|
632
|
-
|
|
632
|
+
project.spec.notifications = project.notifiers.server_notifications
|
|
633
633
|
|
|
634
634
|
run_id = _run_pipeline(
|
|
635
635
|
workflow_handler,
|
mlrun/projects/project.py
CHANGED
|
@@ -2155,7 +2155,7 @@ class MlrunProject(ModelObj):
|
|
|
2155
2155
|
|
|
2156
2156
|
For example:
|
|
2157
2157
|
[`app1.result-*`, `*.result1`]
|
|
2158
|
-
will match "
|
|
2158
|
+
will match "mep_uid1.app1.result.result-1" and "mep_uid1.app2.result.result1".
|
|
2159
2159
|
A specific result_name (not a wildcard) will always create a new alert
|
|
2160
2160
|
config, regardless of whether the result name exists.
|
|
2161
2161
|
:param severity: Severity of the alert.
|
|
@@ -3786,7 +3786,7 @@ class MlrunProject(ModelObj):
|
|
|
3786
3786
|
|
|
3787
3787
|
def list_model_endpoints(
|
|
3788
3788
|
self,
|
|
3789
|
-
|
|
3789
|
+
names: Optional[Union[str, list[str]]] = None,
|
|
3790
3790
|
model_name: Optional[str] = None,
|
|
3791
3791
|
model_tag: Optional[str] = None,
|
|
3792
3792
|
function_name: Optional[str] = None,
|
|
@@ -3816,7 +3816,7 @@ class MlrunProject(ModelObj):
|
|
|
3816
3816
|
In addition, this functions provides a facade for listing endpoint related metrics. This facade is time-based
|
|
3817
3817
|
and depends on the 'start' and 'end' parameters.
|
|
3818
3818
|
|
|
3819
|
-
:param
|
|
3819
|
+
:param names: The name of the model to filter by
|
|
3820
3820
|
:param model_name: The name of the model to filter by
|
|
3821
3821
|
:param function_name: The name of the function to filter by
|
|
3822
3822
|
:param function_tag: The tag of the function to filter by
|
|
@@ -3837,7 +3837,7 @@ class MlrunProject(ModelObj):
|
|
|
3837
3837
|
db = mlrun.db.get_run_db(secrets=self._secrets)
|
|
3838
3838
|
return db.list_model_endpoints(
|
|
3839
3839
|
project=self.name,
|
|
3840
|
-
|
|
3840
|
+
names=names,
|
|
3841
3841
|
model_name=model_name,
|
|
3842
3842
|
model_tag=model_tag,
|
|
3843
3843
|
function_name=function_name,
|
mlrun/runtimes/base.py
CHANGED
|
@@ -16,7 +16,6 @@ import http
|
|
|
16
16
|
import re
|
|
17
17
|
import typing
|
|
18
18
|
import warnings
|
|
19
|
-
from base64 import b64encode
|
|
20
19
|
from os import environ
|
|
21
20
|
from typing import Callable, Optional, Union
|
|
22
21
|
|
|
@@ -795,9 +794,7 @@ class BaseRuntime(ModelObj):
|
|
|
795
794
|
mlrun.runtimes.nuclio.serving.serving_subkind
|
|
796
795
|
)
|
|
797
796
|
|
|
798
|
-
self.spec.build.functionSourceCode =
|
|
799
|
-
"utf-8"
|
|
800
|
-
)
|
|
797
|
+
self.spec.build.functionSourceCode = mlrun.utils.helpers.encode_user_code(body)
|
|
801
798
|
if with_doc:
|
|
802
799
|
update_function_entry_points(self, body)
|
|
803
800
|
return self
|
|
@@ -13,12 +13,13 @@
|
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
|
|
15
15
|
from ast import FunctionDef, parse, unparse
|
|
16
|
-
from base64 import b64decode
|
|
16
|
+
from base64 import b64decode
|
|
17
17
|
from typing import Callable, Optional, Union
|
|
18
18
|
|
|
19
19
|
import mlrun
|
|
20
20
|
import mlrun.runtimes.kubejob as kubejob
|
|
21
21
|
import mlrun.runtimes.pod as pod
|
|
22
|
+
import mlrun.utils.helpers
|
|
22
23
|
from mlrun.errors import MLRunInvalidArgumentError
|
|
23
24
|
from mlrun.model import HyperParamOptions, RunObject
|
|
24
25
|
|
|
@@ -162,7 +163,7 @@ class DatabricksRuntime(kubejob.KubejobRuntime):
|
|
|
162
163
|
if original_handler:
|
|
163
164
|
decoded_code += f"\nresult = {original_handler}(**handler_arguments)\n"
|
|
164
165
|
decoded_code += _return_artifacts_code
|
|
165
|
-
return
|
|
166
|
+
return mlrun.utils.helpers.encode_user_code(decoded_code)
|
|
166
167
|
|
|
167
168
|
def get_internal_parameters(self, runobj: RunObject):
|
|
168
169
|
"""
|
|
@@ -202,7 +203,7 @@ from mlrun.runtimes.databricks_job import databricks_wrapper
|
|
|
202
203
|
def run_mlrun_databricks_job(context,task_parameters: dict, **kwargs):
|
|
203
204
|
databricks_wrapper.run_mlrun_databricks_job(context, task_parameters, **kwargs)
|
|
204
205
|
"""
|
|
205
|
-
wrap_code =
|
|
206
|
+
wrap_code = mlrun.utils.helpers.encode_user_code(wrap_code)
|
|
206
207
|
self.spec.build.functionSourceCode = wrap_code
|
|
207
208
|
runspec.spec.handler = "run_mlrun_databricks_job"
|
|
208
209
|
|
|
@@ -13,11 +13,11 @@
|
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
|
|
15
15
|
import os
|
|
16
|
-
from base64 import b64encode
|
|
17
16
|
|
|
18
17
|
from nuclio.build import mlrun_footer
|
|
19
18
|
|
|
20
19
|
import mlrun
|
|
20
|
+
import mlrun.utils.helpers
|
|
21
21
|
|
|
22
22
|
from ..model import ModelObj
|
|
23
23
|
from ..utils import generate_object_uri
|
|
@@ -116,7 +116,7 @@ class FunctionReference(ModelObj):
|
|
|
116
116
|
func = mlrun.new_function(
|
|
117
117
|
self.name, kind=kind, image=self.image or default_image
|
|
118
118
|
)
|
|
119
|
-
data =
|
|
119
|
+
data = mlrun.utils.helpers.encode_user_code(code)
|
|
120
120
|
func.spec.build.functionSourceCode = data
|
|
121
121
|
if kind not in mlrun.runtimes.RuntimeKinds.nuclio_runtimes():
|
|
122
122
|
func.spec.default_handler = "handler"
|
mlrun/runtimes/pod.py
CHANGED
|
@@ -17,6 +17,7 @@ import os
|
|
|
17
17
|
import re
|
|
18
18
|
import time
|
|
19
19
|
import typing
|
|
20
|
+
import warnings
|
|
20
21
|
from collections.abc import Iterable
|
|
21
22
|
from enum import Enum
|
|
22
23
|
|
|
@@ -703,29 +704,7 @@ class KubeResourceSpec(FunctionSpec):
|
|
|
703
704
|
),
|
|
704
705
|
affinity_field_name=affinity_field_name,
|
|
705
706
|
)
|
|
706
|
-
# purge any affinity / anti-affinity preemption related configuration and enrich with preemptible tolerations
|
|
707
707
|
elif self_preemption_mode == PreemptionModes.allow.value:
|
|
708
|
-
# remove preemptible anti-affinity
|
|
709
|
-
self._prune_affinity_node_selector_requirement(
|
|
710
|
-
generate_preemptible_node_selector_requirements(
|
|
711
|
-
NodeSelectorOperator.node_selector_op_not_in.value
|
|
712
|
-
),
|
|
713
|
-
affinity_field_name=affinity_field_name,
|
|
714
|
-
)
|
|
715
|
-
# remove preemptible affinity
|
|
716
|
-
self._prune_affinity_node_selector_requirement(
|
|
717
|
-
generate_preemptible_node_selector_requirements(
|
|
718
|
-
NodeSelectorOperator.node_selector_op_in.value
|
|
719
|
-
),
|
|
720
|
-
affinity_field_name=affinity_field_name,
|
|
721
|
-
)
|
|
722
|
-
|
|
723
|
-
# remove preemptible nodes constrain
|
|
724
|
-
self._prune_node_selector(
|
|
725
|
-
mlconf.get_preemptible_node_selector(),
|
|
726
|
-
node_selector_field_name=node_selector_field_name,
|
|
727
|
-
)
|
|
728
|
-
|
|
729
708
|
# enrich with tolerations
|
|
730
709
|
self._merge_tolerations(
|
|
731
710
|
generate_preemptible_tolerations(),
|
|
@@ -1201,6 +1180,132 @@ class KubeResource(BaseRuntime):
|
|
|
1201
1180
|
"""
|
|
1202
1181
|
self.spec.with_requests(mem, cpu, patch=patch)
|
|
1203
1182
|
|
|
1183
|
+
def detect_preemptible_node_selector(
|
|
1184
|
+
self, node_selector: dict[str, str]
|
|
1185
|
+
) -> list[str]:
|
|
1186
|
+
"""
|
|
1187
|
+
Checks if any provided node selector matches the preemptible node selectors.
|
|
1188
|
+
Issues a warning if a selector may be pruned at runtime depending on preemption mode.
|
|
1189
|
+
|
|
1190
|
+
:param node_selector: The user-provided node selector dictionary.
|
|
1191
|
+
"""
|
|
1192
|
+
preemptible_node_selector = mlconf.get_preemptible_node_selector()
|
|
1193
|
+
|
|
1194
|
+
return [
|
|
1195
|
+
f"'{key}': '{val}'"
|
|
1196
|
+
for key, val in node_selector.items()
|
|
1197
|
+
if preemptible_node_selector.get(key) == val
|
|
1198
|
+
]
|
|
1199
|
+
|
|
1200
|
+
def detect_preemptible_tolerations(
|
|
1201
|
+
self, tolerations: list[k8s_client.V1Toleration]
|
|
1202
|
+
) -> list[str]:
|
|
1203
|
+
"""
|
|
1204
|
+
Checks if any provided toleration matches preemptible tolerations.
|
|
1205
|
+
Issues a warning if a toleration may be pruned at runtime depending on preemption mode.
|
|
1206
|
+
|
|
1207
|
+
:param tolerations: The user-provided list of tolerations.
|
|
1208
|
+
"""
|
|
1209
|
+
preemptible_tolerations = [
|
|
1210
|
+
k8s_client.V1Toleration(
|
|
1211
|
+
key=toleration.get("key"),
|
|
1212
|
+
value=toleration.get("value"),
|
|
1213
|
+
effect=toleration.get("effect"),
|
|
1214
|
+
)
|
|
1215
|
+
for toleration in mlconf.get_preemptible_tolerations()
|
|
1216
|
+
]
|
|
1217
|
+
|
|
1218
|
+
def _format_toleration(toleration):
|
|
1219
|
+
return f"'{toleration.key}'='{toleration.value}' (effect: '{toleration.effect}')"
|
|
1220
|
+
|
|
1221
|
+
return [
|
|
1222
|
+
_format_toleration(toleration)
|
|
1223
|
+
for toleration in tolerations
|
|
1224
|
+
if toleration in preemptible_tolerations
|
|
1225
|
+
]
|
|
1226
|
+
|
|
1227
|
+
def detect_preemptible_affinity(self, affinity: k8s_client.V1Affinity) -> list[str]:
|
|
1228
|
+
"""
|
|
1229
|
+
Checks if any provided affinity rules match preemptible affinity configurations.
|
|
1230
|
+
Issues a warning if an affinity rule may be pruned at runtime depending on preemption mode.
|
|
1231
|
+
|
|
1232
|
+
:param affinity: The user-provided affinity object.
|
|
1233
|
+
"""
|
|
1234
|
+
|
|
1235
|
+
preemptible_affinity_terms = generate_preemptible_nodes_affinity_terms()
|
|
1236
|
+
conflicting_affinities = []
|
|
1237
|
+
|
|
1238
|
+
if (
|
|
1239
|
+
affinity
|
|
1240
|
+
and affinity.node_affinity
|
|
1241
|
+
and affinity.node_affinity.required_during_scheduling_ignored_during_execution
|
|
1242
|
+
):
|
|
1243
|
+
user_terms = affinity.node_affinity.required_during_scheduling_ignored_during_execution.node_selector_terms
|
|
1244
|
+
for user_term in user_terms:
|
|
1245
|
+
user_expressions = {
|
|
1246
|
+
(expr.key, expr.operator, tuple(expr.values or []))
|
|
1247
|
+
for expr in user_term.match_expressions or []
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
for preemptible_term in preemptible_affinity_terms:
|
|
1251
|
+
preemptible_expressions = {
|
|
1252
|
+
(expr.key, expr.operator, tuple(expr.values or []))
|
|
1253
|
+
for expr in preemptible_term.match_expressions or []
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
# Ensure operators match and preemptible expressions are present
|
|
1257
|
+
common_exprs = user_expressions & preemptible_expressions
|
|
1258
|
+
if common_exprs:
|
|
1259
|
+
formatted = ", ".join(
|
|
1260
|
+
f"'{key} {operator} {list(values)}'"
|
|
1261
|
+
for key, operator, values in common_exprs
|
|
1262
|
+
)
|
|
1263
|
+
conflicting_affinities.append(formatted)
|
|
1264
|
+
return conflicting_affinities
|
|
1265
|
+
|
|
1266
|
+
def raise_preemptible_warning(
|
|
1267
|
+
self,
|
|
1268
|
+
node_selector: typing.Optional[dict[str, str]],
|
|
1269
|
+
tolerations: typing.Optional[list[k8s_client.V1Toleration]],
|
|
1270
|
+
affinity: typing.Optional[k8s_client.V1Affinity],
|
|
1271
|
+
) -> None:
|
|
1272
|
+
"""
|
|
1273
|
+
Detects conflicts and issues a single warning if necessary.
|
|
1274
|
+
|
|
1275
|
+
:param node_selector: The user-provided node selector dictionary.
|
|
1276
|
+
:param tolerations: The user-provided list of tolerations.
|
|
1277
|
+
:param affinity: The user-provided affinity object.
|
|
1278
|
+
"""
|
|
1279
|
+
conflict_messages = []
|
|
1280
|
+
|
|
1281
|
+
if node_selector:
|
|
1282
|
+
ns_conflicts = ", ".join(
|
|
1283
|
+
self.detect_preemptible_node_selector(node_selector)
|
|
1284
|
+
)
|
|
1285
|
+
if ns_conflicts:
|
|
1286
|
+
conflict_messages.append(f"Node selectors: {ns_conflicts}")
|
|
1287
|
+
|
|
1288
|
+
if tolerations:
|
|
1289
|
+
tol_conflicts = ", ".join(self.detect_preemptible_tolerations(tolerations))
|
|
1290
|
+
if tol_conflicts:
|
|
1291
|
+
conflict_messages.append(f"Tolerations: {tol_conflicts}")
|
|
1292
|
+
|
|
1293
|
+
if affinity:
|
|
1294
|
+
affinity_conflicts = ", ".join(self.detect_preemptible_affinity(affinity))
|
|
1295
|
+
if affinity_conflicts:
|
|
1296
|
+
conflict_messages.append(f"Affinity: {affinity_conflicts}")
|
|
1297
|
+
|
|
1298
|
+
if conflict_messages:
|
|
1299
|
+
warning_componentes = "; \n".join(conflict_messages)
|
|
1300
|
+
warnings.warn(
|
|
1301
|
+
f"Warning: based on the preemptible node settings configured in your MLRun configuration,\n"
|
|
1302
|
+
f"{warning_componentes}\n"
|
|
1303
|
+
f" may be removed or adjusted at runtime.\n"
|
|
1304
|
+
"This adjustment depends on the function's preemption mode. \n"
|
|
1305
|
+
"The list of potential adjusted preemptible selectors can be viewed here: "
|
|
1306
|
+
"mlrun.mlconf.get_preemptible_node_selector() and mlrun.mlconf.get_preemptible_tolerations()."
|
|
1307
|
+
)
|
|
1308
|
+
|
|
1204
1309
|
def with_node_selection(
|
|
1205
1310
|
self,
|
|
1206
1311
|
node_name: typing.Optional[str] = None,
|
|
@@ -1209,19 +1314,14 @@ class KubeResource(BaseRuntime):
|
|
|
1209
1314
|
tolerations: typing.Optional[list[k8s_client.V1Toleration]] = None,
|
|
1210
1315
|
):
|
|
1211
1316
|
"""
|
|
1212
|
-
Enables
|
|
1213
|
-
|
|
1214
|
-
:param node_name: The name of the k8s node
|
|
1215
|
-
:param node_selector: Label selector, only nodes with matching labels will be eligible to be picked
|
|
1216
|
-
:param affinity: Expands the types of constraints you can express - see
|
|
1217
|
-
https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#affinity-and-anti-affinity
|
|
1218
|
-
for details
|
|
1219
|
-
:param tolerations: Tolerations are applied to pods, and allow (but do not require) the pods to schedule
|
|
1220
|
-
onto nodes with matching taints - see
|
|
1221
|
-
https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration
|
|
1222
|
-
for details
|
|
1317
|
+
Enables control over which Kubernetes node the job will run on.
|
|
1223
1318
|
|
|
1319
|
+
:param node_name: The name of the Kubernetes node.
|
|
1320
|
+
:param node_selector: Label selector, only nodes with matching labels will be eligible.
|
|
1321
|
+
:param affinity: Defines scheduling constraints.
|
|
1322
|
+
:param tolerations: Allows scheduling onto nodes with matching taints.
|
|
1224
1323
|
"""
|
|
1324
|
+
# Apply values as before
|
|
1225
1325
|
if node_name:
|
|
1226
1326
|
self.spec.node_name = node_name
|
|
1227
1327
|
if node_selector is not None:
|
|
@@ -1232,6 +1332,12 @@ class KubeResource(BaseRuntime):
|
|
|
1232
1332
|
if tolerations is not None:
|
|
1233
1333
|
self.spec.tolerations = tolerations
|
|
1234
1334
|
|
|
1335
|
+
self.raise_preemptible_warning(
|
|
1336
|
+
node_selector=self.spec.node_selector,
|
|
1337
|
+
tolerations=self.spec.tolerations,
|
|
1338
|
+
affinity=self.spec.affinity,
|
|
1339
|
+
)
|
|
1340
|
+
|
|
1235
1341
|
def with_priority_class(self, name: typing.Optional[str] = None):
|
|
1236
1342
|
"""
|
|
1237
1343
|
Enables to control the priority of the pod
|
mlrun/utils/helpers.py
CHANGED
|
@@ -1329,7 +1329,11 @@ def get_handler_extended(
|
|
|
1329
1329
|
def datetime_from_iso(time_str: str) -> Optional[datetime]:
|
|
1330
1330
|
if not time_str:
|
|
1331
1331
|
return
|
|
1332
|
-
|
|
1332
|
+
dt = parser.isoparse(time_str)
|
|
1333
|
+
if dt.tzinfo is None:
|
|
1334
|
+
dt = dt.replace(tzinfo=timezone.utc)
|
|
1335
|
+
# ensure the datetime is in UTC, converting if necessary
|
|
1336
|
+
return dt.astimezone(timezone.utc)
|
|
1333
1337
|
|
|
1334
1338
|
|
|
1335
1339
|
def datetime_to_iso(time_obj: Optional[datetime]) -> Optional[str]:
|
|
@@ -1459,6 +1463,16 @@ def str_to_timestamp(time_str: str, now_time: Timestamp = None):
|
|
|
1459
1463
|
return Timestamp(time_str)
|
|
1460
1464
|
|
|
1461
1465
|
|
|
1466
|
+
def str_to_bool(value: str) -> bool:
|
|
1467
|
+
"""Convert a string to a boolean value."""
|
|
1468
|
+
value = value.lower()
|
|
1469
|
+
if value in ("true", "1", "t", "y", "yes", "on"):
|
|
1470
|
+
return True
|
|
1471
|
+
if value in ("false", "0", "f", "n", "no", "off"):
|
|
1472
|
+
return False
|
|
1473
|
+
raise ValueError(f"invalid boolean value: {value}")
|
|
1474
|
+
|
|
1475
|
+
|
|
1462
1476
|
def is_link_artifact(artifact):
|
|
1463
1477
|
if isinstance(artifact, dict):
|
|
1464
1478
|
return (
|
|
@@ -2129,3 +2143,16 @@ def as_dict(data: typing.Union[dict, str]) -> dict:
|
|
|
2129
2143
|
if isinstance(data, str):
|
|
2130
2144
|
return json.loads(data)
|
|
2131
2145
|
return data
|
|
2146
|
+
|
|
2147
|
+
|
|
2148
|
+
def encode_user_code(
|
|
2149
|
+
user_code: str, max_len_warning: typing.Optional[int] = None
|
|
2150
|
+
) -> str:
|
|
2151
|
+
max_len_warning = max_len_warning or config.function.spec.source_code_max_bytes
|
|
2152
|
+
encoded = base64.b64encode(user_code.encode("utf-8")).decode("utf-8")
|
|
2153
|
+
if len(encoded) > max_len_warning:
|
|
2154
|
+
logger.warning(
|
|
2155
|
+
f"User code exceeds the maximum allowed size of {max_len_warning} bytes for non remote source. "
|
|
2156
|
+
"Consider using `with_source_archive` to add user code as a remote source to the function."
|
|
2157
|
+
)
|
|
2158
|
+
return encoded
|
mlrun/utils/version/version.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: mlrun
|
|
3
|
-
Version: 1.8.
|
|
3
|
+
Version: 1.8.0rc33
|
|
4
4
|
Summary: Tracking and config of machine learning runs
|
|
5
5
|
Home-page: https://github.com/mlrun/mlrun
|
|
6
6
|
Author: Yaron Haviv
|
|
@@ -51,8 +51,8 @@ Requires-Dist: setuptools>=75.2
|
|
|
51
51
|
Requires-Dist: deprecated~=1.2
|
|
52
52
|
Requires-Dist: jinja2>=3.1.3,~=3.1
|
|
53
53
|
Requires-Dist: orjson<4,>=3.9.15
|
|
54
|
-
Requires-Dist: mlrun-pipelines-kfp-common~=0.3.
|
|
55
|
-
Requires-Dist: mlrun-pipelines-kfp-v1-8~=0.3.
|
|
54
|
+
Requires-Dist: mlrun-pipelines-kfp-common~=0.3.11
|
|
55
|
+
Requires-Dist: mlrun-pipelines-kfp-v1-8~=0.3.7; python_version < "3.11"
|
|
56
56
|
Requires-Dist: docstring_parser~=0.16
|
|
57
57
|
Requires-Dist: aiosmtplib~=3.0
|
|
58
58
|
Provides-Extra: s3
|
|
@@ -99,7 +99,7 @@ Requires-Dist: ossfs==2023.12.0; extra == "alibaba-oss"
|
|
|
99
99
|
Requires-Dist: oss2==2.18.1; extra == "alibaba-oss"
|
|
100
100
|
Provides-Extra: tdengine
|
|
101
101
|
Requires-Dist: taos-ws-py==0.3.2; extra == "tdengine"
|
|
102
|
-
Requires-Dist: taoswswrap~=0.3.
|
|
102
|
+
Requires-Dist: taoswswrap~=0.3.2; extra == "tdengine"
|
|
103
103
|
Provides-Extra: snowflake
|
|
104
104
|
Requires-Dist: snowflake-connector-python~=3.7; extra == "snowflake"
|
|
105
105
|
Provides-Extra: kfp18
|
|
@@ -119,7 +119,7 @@ Requires-Dist: timelength~=1.1; extra == "api"
|
|
|
119
119
|
Requires-Dist: memray~=1.12; sys_platform != "win32" and extra == "api"
|
|
120
120
|
Requires-Dist: aiosmtplib~=3.0; extra == "api"
|
|
121
121
|
Requires-Dist: pydantic<2,>=1; extra == "api"
|
|
122
|
-
Requires-Dist: mlrun-pipelines-kfp-v1-8[kfp]~=0.3.
|
|
122
|
+
Requires-Dist: mlrun-pipelines-kfp-v1-8[kfp]~=0.3.7; python_version < "3.11" and extra == "api"
|
|
123
123
|
Requires-Dist: grpcio~=1.70.0; extra == "api"
|
|
124
124
|
Provides-Extra: all
|
|
125
125
|
Requires-Dist: adlfs==2023.9.0; extra == "all"
|
|
@@ -152,7 +152,7 @@ Requires-Dist: s3fs<2024.7,>=2023.9.2; extra == "all"
|
|
|
152
152
|
Requires-Dist: snowflake-connector-python~=3.7; extra == "all"
|
|
153
153
|
Requires-Dist: sqlalchemy~=1.4; extra == "all"
|
|
154
154
|
Requires-Dist: taos-ws-py==0.3.2; extra == "all"
|
|
155
|
-
Requires-Dist: taoswswrap~=0.3.
|
|
155
|
+
Requires-Dist: taoswswrap~=0.3.2; extra == "all"
|
|
156
156
|
Provides-Extra: complete
|
|
157
157
|
Requires-Dist: adlfs==2023.9.0; extra == "complete"
|
|
158
158
|
Requires-Dist: aiobotocore<2.16,>=2.5.0; extra == "complete"
|
|
@@ -184,7 +184,7 @@ Requires-Dist: s3fs<2024.7,>=2023.9.2; extra == "complete"
|
|
|
184
184
|
Requires-Dist: snowflake-connector-python~=3.7; extra == "complete"
|
|
185
185
|
Requires-Dist: sqlalchemy~=1.4; extra == "complete"
|
|
186
186
|
Requires-Dist: taos-ws-py==0.3.2; extra == "complete"
|
|
187
|
-
Requires-Dist: taoswswrap~=0.3.
|
|
187
|
+
Requires-Dist: taoswswrap~=0.3.2; extra == "complete"
|
|
188
188
|
Provides-Extra: complete-api
|
|
189
189
|
Requires-Dist: adlfs==2023.9.0; extra == "complete-api"
|
|
190
190
|
Requires-Dist: aiobotocore<2.16,>=2.5.0; extra == "complete-api"
|
|
@@ -215,7 +215,7 @@ Requires-Dist: igz-mgmt~=0.4.1; extra == "complete-api"
|
|
|
215
215
|
Requires-Dist: kafka-python~=2.0; extra == "complete-api"
|
|
216
216
|
Requires-Dist: memray~=1.12; sys_platform != "win32" and extra == "complete-api"
|
|
217
217
|
Requires-Dist: mlflow~=2.16; extra == "complete-api"
|
|
218
|
-
Requires-Dist: mlrun-pipelines-kfp-v1-8[kfp]~=0.3.
|
|
218
|
+
Requires-Dist: mlrun-pipelines-kfp-v1-8[kfp]~=0.3.7; python_version < "3.11" and extra == "complete-api"
|
|
219
219
|
Requires-Dist: msrest~=0.6.21; extra == "complete-api"
|
|
220
220
|
Requires-Dist: objgraph~=3.6; extra == "complete-api"
|
|
221
221
|
Requires-Dist: oss2==2.18.1; extra == "complete-api"
|
|
@@ -229,7 +229,7 @@ Requires-Dist: s3fs<2024.7,>=2023.9.2; extra == "complete-api"
|
|
|
229
229
|
Requires-Dist: snowflake-connector-python~=3.7; extra == "complete-api"
|
|
230
230
|
Requires-Dist: sqlalchemy~=1.4; extra == "complete-api"
|
|
231
231
|
Requires-Dist: taos-ws-py==0.3.2; extra == "complete-api"
|
|
232
|
-
Requires-Dist: taoswswrap~=0.3.
|
|
232
|
+
Requires-Dist: taoswswrap~=0.3.2; extra == "complete-api"
|
|
233
233
|
Requires-Dist: timelength~=1.1; extra == "complete-api"
|
|
234
234
|
Requires-Dist: uvicorn~=0.32.1; extra == "complete-api"
|
|
235
235
|
Dynamic: author
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
mlrun/__init__.py,sha256=Cqm9U9eCEdLpMejhU2BEhubu0mHL71igJJIwYa738EA,7450
|
|
2
|
-
mlrun/__main__.py,sha256=
|
|
3
|
-
mlrun/config.py,sha256=
|
|
2
|
+
mlrun/__main__.py,sha256=xYWflUbfSRpo8F4uzrHhBN1dcaBUADmfoK5Q-iqEBeQ,46181
|
|
3
|
+
mlrun/config.py,sha256=hATfrO5ZtykMiDJ6TNtkIDVgq6YwcgVrZf7H3doh5UE,71077
|
|
4
4
|
mlrun/errors.py,sha256=LkcbXTLANGdsgo2CRX2pdbyNmt--lMsjGv0XZMgP-Nc,8222
|
|
5
5
|
mlrun/execution.py,sha256=FUktsD3puSFjc3LZJU35b-OmFBrBPBNntViCLQVuwnk,50008
|
|
6
6
|
mlrun/features.py,sha256=ReBaNGsBYXqcbgI012n-SO_j6oHIbk_Vpv0CGPXbUmo,15842
|
|
@@ -108,10 +108,10 @@ mlrun/datastore/wasbfs/__init__.py,sha256=s5Ul-0kAhYqFjKDR2X0O2vDGDbLQQduElb32Ev
|
|
|
108
108
|
mlrun/datastore/wasbfs/fs.py,sha256=ge8NK__5vTcFT-krI155_8RDUywQw4SIRX6BWATXy9Q,6299
|
|
109
109
|
mlrun/db/__init__.py,sha256=WqJ4x8lqJ7ZoKbhEyFqkYADd9P6E3citckx9e9ZLcIU,1163
|
|
110
110
|
mlrun/db/auth_utils.py,sha256=hpg8D2r82oN0BWabuWN04BTNZ7jYMAF242YSUpK7LFM,5211
|
|
111
|
-
mlrun/db/base.py,sha256=
|
|
111
|
+
mlrun/db/base.py,sha256=pFf33ey6vlTN7cEr8EpsSQhdydDhSXtDz5ukK5o25f8,30697
|
|
112
112
|
mlrun/db/factory.py,sha256=yP2vVmveUE7LYTCHbS6lQIxP9rW--zdISWuPd_I3d_4,2111
|
|
113
|
-
mlrun/db/httpdb.py,sha256=
|
|
114
|
-
mlrun/db/nopdb.py,sha256=
|
|
113
|
+
mlrun/db/httpdb.py,sha256=Pl9BdXDUJRLfwLdOYgbh-Bs-wxUoE-9v5l2gXWH7Mec,230329
|
|
114
|
+
mlrun/db/nopdb.py,sha256=h_tgZDWr-7ri_gbgD3FOb9i4X_W9e4rr7va7S10kInI,27132
|
|
115
115
|
mlrun/feature_store/__init__.py,sha256=AVnY2AFUNc2dKxLLUMx2K3Wo1eGviv0brDcYlDnmtf4,1506
|
|
116
116
|
mlrun/feature_store/api.py,sha256=qkojZpzqGAn3r9ww0ynBRKOs8ji8URaK4DSYD4SE-CE,50395
|
|
117
117
|
mlrun/feature_store/common.py,sha256=Z7USI-d1fo0iwBMsqMBtJflJfyuiV3BLoDXQPSAoBAs,12826
|
|
@@ -217,20 +217,21 @@ mlrun/launcher/factory.py,sha256=RW7mfzEFi8fR0M-4W1JQg1iq3_muUU6OTqT_3l4Ubrk,233
|
|
|
217
217
|
mlrun/launcher/local.py,sha256=775HY-8S9LFUX5ubGXrLO0N1lVh8bn-DHFmNYuNqQPA,11451
|
|
218
218
|
mlrun/launcher/remote.py,sha256=rLJW4UAnUT5iUb4BsGBOAV3K4R29a0X4lFtRkVKlyYU,7709
|
|
219
219
|
mlrun/model_monitoring/__init__.py,sha256=ELy7njEtZnz09Dc6PGZSFFEGtnwI15bJNWM3Pj4_YIs,753
|
|
220
|
-
mlrun/model_monitoring/api.py,sha256=
|
|
220
|
+
mlrun/model_monitoring/api.py,sha256=w6jjrWhlptm7MruzWIJOqCSi4W_zZSq5kkUgTjvqrOk,28508
|
|
221
221
|
mlrun/model_monitoring/controller.py,sha256=j6hqNYKhrw37PJZBcW4BgjsCpG7PtVMvFTpnZO95QVQ,29078
|
|
222
222
|
mlrun/model_monitoring/features_drift_table.py,sha256=c6GpKtpOJbuT1u5uMWDL_S-6N4YPOmlktWMqPme3KFY,25308
|
|
223
|
-
mlrun/model_monitoring/helpers.py,sha256=
|
|
223
|
+
mlrun/model_monitoring/helpers.py,sha256=fx2mCQkDu_PgO9LT7ykJ3qcZ7BwELaPqCt_MejqeVxo,22450
|
|
224
224
|
mlrun/model_monitoring/stream_processing.py,sha256=NcvUdfVzveFzmphU65sFGfxp9Jh5ZKq2tSiWWftML9A,34531
|
|
225
225
|
mlrun/model_monitoring/tracking_policy.py,sha256=PBIGrUYWrwcE5gwXupBIVzOb0QRRwPJsgQm_yLGQxB4,5595
|
|
226
226
|
mlrun/model_monitoring/writer.py,sha256=vbL7bqTyNu8q4bNcebX72sUMybVDAoTWg-CXq4fov3Y,8429
|
|
227
|
-
mlrun/model_monitoring/applications/__init__.py,sha256=
|
|
227
|
+
mlrun/model_monitoring/applications/__init__.py,sha256=xDBxkBjl-whHSG_4t1mLkxiypLH-fzn8TmAW9Mjo2uI,759
|
|
228
228
|
mlrun/model_monitoring/applications/_application_steps.py,sha256=97taCEkfGx-QO-gD9uKnRF1PDIxQhY7sjPg85GxgIpA,6628
|
|
229
|
-
mlrun/model_monitoring/applications/base.py,sha256=
|
|
229
|
+
mlrun/model_monitoring/applications/base.py,sha256=1jdZAreSqugcVd2xD0-dT6DAt16MO7WKQEK71G6Q6qs,24067
|
|
230
230
|
mlrun/model_monitoring/applications/context.py,sha256=xqbKS61iXE6jBekyW8zjo_E3lxe2D8VepuXG_BA5y2k,14931
|
|
231
|
-
mlrun/model_monitoring/applications/evidently_base.py,sha256=hRjXuXf6xf8sbjGt9yYfGDUGnvS5rV3W7tkJroF3QJA,5098
|
|
232
231
|
mlrun/model_monitoring/applications/histogram_data_drift.py,sha256=G26_4gQfcwDZe3S6SIZ4Uc_qyrHAJ6lDTFOQGkbfQR8,14455
|
|
233
232
|
mlrun/model_monitoring/applications/results.py,sha256=_qmj6TWT0SR2bi7gUyRKBU418eGgGoLW2_hTJ7S-ock,5782
|
|
233
|
+
mlrun/model_monitoring/applications/evidently/__init__.py,sha256=-DqdPnBSrjZhFvKOu_Ie3MiFvlur9sPTZpZ1u0_1AE8,690
|
|
234
|
+
mlrun/model_monitoring/applications/evidently/base.py,sha256=hRjXuXf6xf8sbjGt9yYfGDUGnvS5rV3W7tkJroF3QJA,5098
|
|
234
235
|
mlrun/model_monitoring/db/__init__.py,sha256=r47xPGZpIfMuv8J3PQCZTSqVPMhUta4sSJCZFKcS7FM,644
|
|
235
236
|
mlrun/model_monitoring/db/_schedules.py,sha256=AKyCJBAt0opNE3K3pg2TjCoD_afk1LKw5TY88rLQ2VA,6097
|
|
236
237
|
mlrun/model_monitoring/db/_stats.py,sha256=VVMWLMqG3Us3ozBkLaokJF22Ewv8WKmVE1-OvS_g9vA,6943
|
|
@@ -240,7 +241,7 @@ mlrun/model_monitoring/db/tsdb/helpers.py,sha256=0oUXc4aUkYtP2SGP6jTb3uPPKImIUsV
|
|
|
240
241
|
mlrun/model_monitoring/db/tsdb/tdengine/__init__.py,sha256=vgBdsKaXUURKqIf3M0y4sRatmSVA4CQiJs7J5dcVBkQ,620
|
|
241
242
|
mlrun/model_monitoring/db/tsdb/tdengine/schemas.py,sha256=qfKDUZhgteL0mp2A1aP1iMmcthgUMKmZqMUidZjQktQ,12649
|
|
242
243
|
mlrun/model_monitoring/db/tsdb/tdengine/stream_graph_steps.py,sha256=Uadj0UvAmln2MxDWod-kAzau1uNlqZh981rPhbUH_5M,2857
|
|
243
|
-
mlrun/model_monitoring/db/tsdb/tdengine/tdengine_connector.py,sha256=
|
|
244
|
+
mlrun/model_monitoring/db/tsdb/tdengine/tdengine_connector.py,sha256=OgIA-S-DZ3DM455zOk24rUsmM2EdrKbliuKs1Lzcpuw,32793
|
|
244
245
|
mlrun/model_monitoring/db/tsdb/v3io/__init__.py,sha256=aL3bfmQsUQ-sbvKGdNihFj8gLCK3mSys0qDcXtYOwgc,616
|
|
245
246
|
mlrun/model_monitoring/db/tsdb/v3io/stream_graph_steps.py,sha256=_-zo9relCDtjGgievxAcAP9gVN9nDWs8BzGtFwTjb9M,6284
|
|
246
247
|
mlrun/model_monitoring/db/tsdb/v3io/v3io_connector.py,sha256=foxYWx7OjOfat2SHmzYrG8bIfaQ5NDnBtpDZua_NVGE,41141
|
|
@@ -267,23 +268,23 @@ mlrun/platforms/__init__.py,sha256=ZuyeHCHHUxYEoZRmaJqzFSfwhaTyUdBZXMeVp75ql1w,3
|
|
|
267
268
|
mlrun/platforms/iguazio.py,sha256=6VBTq8eQ3mzT96tzjYhAtcMQ2VjF4x8LpIPW5DAcX2Q,13749
|
|
268
269
|
mlrun/projects/__init__.py,sha256=0Krf0WIKfnZa71WthYOg0SoaTodGg3sV_hK3f_OlTPI,1220
|
|
269
270
|
mlrun/projects/operations.py,sha256=VXUlMrouFTls-I-bMhdN5pPfQ34TR7bFQ-NUSWNvl84,20029
|
|
270
|
-
mlrun/projects/pipelines.py,sha256=
|
|
271
|
-
mlrun/projects/project.py,sha256=
|
|
271
|
+
mlrun/projects/pipelines.py,sha256=QH2nEhaJxhafJdT0AXPzpDhTniyHtc0Cg74Spdz6Oeg,48255
|
|
272
|
+
mlrun/projects/project.py,sha256=89fKq2kdfV1mtMMj0EIT55eI_F3IUKk_dCeAk7S-5AA,234506
|
|
272
273
|
mlrun/runtimes/__init__.py,sha256=J9Sy2HiyMlztNv6VUurMzF5H2XzttNil8nRsWDsqLyg,8923
|
|
273
|
-
mlrun/runtimes/base.py,sha256=
|
|
274
|
+
mlrun/runtimes/base.py,sha256=K5-zfFrE_HR6AaHWs2figaOTr7eosw3-4bELkYzpRk4,37789
|
|
274
275
|
mlrun/runtimes/daskjob.py,sha256=JwuGvOiPsxEDHHMMUS4Oie4hLlYYIZwihAl6DjroTY0,19521
|
|
275
276
|
mlrun/runtimes/funcdoc.py,sha256=zRFHrJsV8rhDLJwoUhcfZ7Cs0j-tQ76DxwUqdXV_Wyc,9810
|
|
276
|
-
mlrun/runtimes/function_reference.py,sha256=
|
|
277
|
+
mlrun/runtimes/function_reference.py,sha256=CLvRY-wXX9qhI9YEzSl0VWt8piH_-5FQYQ8ObUYLLDc,4911
|
|
277
278
|
mlrun/runtimes/generators.py,sha256=X8NDlCEPveDDPOHtOGcSpbl3pAVM3DP7fuPj5xVhxEY,7290
|
|
278
279
|
mlrun/runtimes/kubejob.py,sha256=gJnlAJ0RJw65yeiIPuLEjxJkDYfbpRgS3lyWkDDFXTk,8797
|
|
279
280
|
mlrun/runtimes/local.py,sha256=yedo3R1c46cB1mX7aOz8zORXswQPvX86U-_fYxXoqTY,22717
|
|
280
281
|
mlrun/runtimes/mounts.py,sha256=pGQlnsNTUxAhFMWLS_53E784z-IH9a6oQjKjSp1gbJE,18733
|
|
281
|
-
mlrun/runtimes/pod.py,sha256=
|
|
282
|
+
mlrun/runtimes/pod.py,sha256=hOIXw6X5y7Vkzd6lvt-2reeh2ockj3Bf7xVBUn6xGeo,71824
|
|
282
283
|
mlrun/runtimes/remotesparkjob.py,sha256=dod99nqz3GdRfmnBoQKfwFCXTetfuCScd2pKH3HJyoY,7394
|
|
283
284
|
mlrun/runtimes/utils.py,sha256=3_Vu_OHlhi8f0vh_w9ii2eTKgS5dh6RVi1HwX9oDKuU,15675
|
|
284
285
|
mlrun/runtimes/databricks_job/__init__.py,sha256=kXGBqhLN0rlAx0kTXhozGzFsIdSqW0uTSKMmsLgq_is,569
|
|
285
286
|
mlrun/runtimes/databricks_job/databricks_cancel_task.py,sha256=sIqIg5DQAf4j0wCPA-G0GoxY6vacRddxCy5KDUZszek,2245
|
|
286
|
-
mlrun/runtimes/databricks_job/databricks_runtime.py,sha256=
|
|
287
|
+
mlrun/runtimes/databricks_job/databricks_runtime.py,sha256=THzAuVMdGWeqTiXGj6Dy0d8SZpHbUZdDKvoxKYO5fTM,12816
|
|
287
288
|
mlrun/runtimes/databricks_job/databricks_wrapper.py,sha256=oJzym54jD957yzxRXiSYpituSV8JV_XJh90YTKIwapY,8684
|
|
288
289
|
mlrun/runtimes/mpijob/__init__.py,sha256=6sUPQRFwigi4mqjDVZmRE-qgaLw2ILY5NbneVUuMKto,947
|
|
289
290
|
mlrun/runtimes/mpijob/abstract.py,sha256=JGMjcJ4dvpJbctF6psU9UvYyNCutMxTMgBQeTlzpkro,9249
|
|
@@ -319,7 +320,7 @@ mlrun/utils/azure_vault.py,sha256=IEFizrDGDbAaoWwDr1WoA88S_EZ0T--vjYtY-i0cvYQ,34
|
|
|
319
320
|
mlrun/utils/clones.py,sha256=y3zC9QS7z5mLuvyQ6vFd6sJnikbgtDwrBvieQq0sovY,7359
|
|
320
321
|
mlrun/utils/condition_evaluator.py,sha256=-nGfRmZzivn01rHTroiGY4rqEv8T1irMyhzxEei-sKc,1897
|
|
321
322
|
mlrun/utils/db.py,sha256=blQgkWMfFH9lcN4sgJQcPQgEETz2Dl_zwbVA0SslpFg,2186
|
|
322
|
-
mlrun/utils/helpers.py,sha256=
|
|
323
|
+
mlrun/utils/helpers.py,sha256=adXqZYrCWXhbNOGI4J1K4WBXsSAIkv-FWaNayt424xs,73279
|
|
323
324
|
mlrun/utils/http.py,sha256=t6FrXQstZm9xVVjxqIGiLzrwZNCR4CSienSOuVgNIcI,8706
|
|
324
325
|
mlrun/utils/logger.py,sha256=RG0m1rx6gfkJ-2C1r_p41MMpPiaDYqaYM2lYHDlNZEU,14767
|
|
325
326
|
mlrun/utils/regex.py,sha256=jbR7IiOp6OO0mg9Fl_cVZCpWb9fL9nTPONCUxCDNWXg,5201
|
|
@@ -338,11 +339,11 @@ mlrun/utils/notifications/notification/mail.py,sha256=ZyJ3eqd8simxffQmXzqd3bgbAq
|
|
|
338
339
|
mlrun/utils/notifications/notification/slack.py,sha256=eQvmctTh6wIG5xVOesLLV9S1-UUCu5UEQ9JIJOor3ts,7183
|
|
339
340
|
mlrun/utils/notifications/notification/webhook.py,sha256=NeyIMSBojjjTJaUHmPbxMByp34GxYkl1-16NqzU27fU,4943
|
|
340
341
|
mlrun/utils/version/__init__.py,sha256=7kkrB7hEZ3cLXoWj1kPoDwo4MaswsI2JVOBpbKgPAgc,614
|
|
341
|
-
mlrun/utils/version/version.json,sha256=
|
|
342
|
+
mlrun/utils/version/version.json,sha256=HiW1MAPj8GSuC6bLGzBX2dEOLzDlgCTLe3iWUM7fm1s,89
|
|
342
343
|
mlrun/utils/version/version.py,sha256=eEW0tqIAkU9Xifxv8Z9_qsYnNhn3YH7NRAfM-pPLt1g,1878
|
|
343
|
-
mlrun-1.8.
|
|
344
|
-
mlrun-1.8.
|
|
345
|
-
mlrun-1.8.
|
|
346
|
-
mlrun-1.8.
|
|
347
|
-
mlrun-1.8.
|
|
348
|
-
mlrun-1.8.
|
|
344
|
+
mlrun-1.8.0rc33.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
345
|
+
mlrun-1.8.0rc33.dist-info/METADATA,sha256=CKvUMuVAhnREX815xtMEgz1TMTjllhP0ywgeXRFctiw,25986
|
|
346
|
+
mlrun-1.8.0rc33.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
|
347
|
+
mlrun-1.8.0rc33.dist-info/entry_points.txt,sha256=1Owd16eAclD5pfRCoJpYC2ZJSyGNTtUr0nCELMioMmU,46
|
|
348
|
+
mlrun-1.8.0rc33.dist-info/top_level.txt,sha256=NObLzw3maSF9wVrgSeYBv-fgnHkAJ1kEkh12DLdd5KM,6
|
|
349
|
+
mlrun-1.8.0rc33.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|