mlrun 1.6.4rc2__py3-none-any.whl → 1.7.0rc20__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of mlrun might be problematic. Click here for more details.

Files changed (291) hide show
  1. mlrun/__init__.py +11 -1
  2. mlrun/__main__.py +26 -112
  3. mlrun/alerts/__init__.py +15 -0
  4. mlrun/alerts/alert.py +144 -0
  5. mlrun/api/schemas/__init__.py +5 -4
  6. mlrun/artifacts/__init__.py +8 -3
  7. mlrun/artifacts/base.py +46 -257
  8. mlrun/artifacts/dataset.py +11 -192
  9. mlrun/artifacts/manager.py +47 -48
  10. mlrun/artifacts/model.py +31 -159
  11. mlrun/artifacts/plots.py +23 -380
  12. mlrun/common/constants.py +69 -0
  13. mlrun/common/db/sql_session.py +2 -3
  14. mlrun/common/formatters/__init__.py +19 -0
  15. mlrun/common/formatters/artifact.py +21 -0
  16. mlrun/common/formatters/base.py +78 -0
  17. mlrun/common/formatters/function.py +41 -0
  18. mlrun/common/formatters/pipeline.py +53 -0
  19. mlrun/common/formatters/project.py +51 -0
  20. mlrun/common/helpers.py +1 -2
  21. mlrun/common/model_monitoring/helpers.py +9 -5
  22. mlrun/{runtimes → common/runtimes}/constants.py +37 -9
  23. mlrun/common/schemas/__init__.py +24 -4
  24. mlrun/common/schemas/alert.py +203 -0
  25. mlrun/common/schemas/api_gateway.py +148 -0
  26. mlrun/common/schemas/artifact.py +18 -8
  27. mlrun/common/schemas/auth.py +11 -5
  28. mlrun/common/schemas/background_task.py +1 -1
  29. mlrun/common/schemas/client_spec.py +4 -1
  30. mlrun/common/schemas/feature_store.py +16 -16
  31. mlrun/common/schemas/frontend_spec.py +8 -7
  32. mlrun/common/schemas/function.py +5 -1
  33. mlrun/common/schemas/hub.py +11 -18
  34. mlrun/common/schemas/memory_reports.py +2 -2
  35. mlrun/common/schemas/model_monitoring/__init__.py +18 -3
  36. mlrun/common/schemas/model_monitoring/constants.py +83 -26
  37. mlrun/common/schemas/model_monitoring/grafana.py +13 -9
  38. mlrun/common/schemas/model_monitoring/model_endpoints.py +99 -16
  39. mlrun/common/schemas/notification.py +4 -4
  40. mlrun/common/schemas/object.py +2 -2
  41. mlrun/{runtimes/mpijob/v1alpha1.py → common/schemas/pagination.py} +10 -13
  42. mlrun/common/schemas/pipeline.py +1 -10
  43. mlrun/common/schemas/project.py +24 -23
  44. mlrun/common/schemas/runtime_resource.py +8 -12
  45. mlrun/common/schemas/schedule.py +3 -3
  46. mlrun/common/schemas/tag.py +1 -2
  47. mlrun/common/schemas/workflow.py +2 -2
  48. mlrun/common/types.py +7 -1
  49. mlrun/config.py +54 -17
  50. mlrun/data_types/to_pandas.py +10 -12
  51. mlrun/datastore/__init__.py +5 -8
  52. mlrun/datastore/alibaba_oss.py +130 -0
  53. mlrun/datastore/azure_blob.py +17 -5
  54. mlrun/datastore/base.py +62 -39
  55. mlrun/datastore/datastore.py +28 -9
  56. mlrun/datastore/datastore_profile.py +146 -20
  57. mlrun/datastore/filestore.py +0 -1
  58. mlrun/datastore/google_cloud_storage.py +6 -2
  59. mlrun/datastore/hdfs.py +56 -0
  60. mlrun/datastore/inmem.py +2 -2
  61. mlrun/datastore/redis.py +6 -2
  62. mlrun/datastore/s3.py +9 -0
  63. mlrun/datastore/snowflake_utils.py +43 -0
  64. mlrun/datastore/sources.py +201 -96
  65. mlrun/datastore/spark_utils.py +1 -2
  66. mlrun/datastore/store_resources.py +7 -7
  67. mlrun/datastore/targets.py +358 -104
  68. mlrun/datastore/utils.py +72 -58
  69. mlrun/datastore/v3io.py +5 -1
  70. mlrun/db/base.py +185 -35
  71. mlrun/db/factory.py +1 -1
  72. mlrun/db/httpdb.py +614 -179
  73. mlrun/db/nopdb.py +210 -26
  74. mlrun/errors.py +12 -1
  75. mlrun/execution.py +41 -24
  76. mlrun/feature_store/__init__.py +0 -2
  77. mlrun/feature_store/api.py +40 -72
  78. mlrun/feature_store/common.py +1 -1
  79. mlrun/feature_store/feature_set.py +76 -55
  80. mlrun/feature_store/feature_vector.py +28 -30
  81. mlrun/feature_store/ingestion.py +7 -6
  82. mlrun/feature_store/retrieval/base.py +16 -11
  83. mlrun/feature_store/retrieval/conversion.py +11 -13
  84. mlrun/feature_store/retrieval/dask_merger.py +2 -0
  85. mlrun/feature_store/retrieval/job.py +9 -3
  86. mlrun/feature_store/retrieval/local_merger.py +2 -0
  87. mlrun/feature_store/retrieval/spark_merger.py +34 -24
  88. mlrun/feature_store/steps.py +37 -34
  89. mlrun/features.py +9 -20
  90. mlrun/frameworks/_common/artifacts_library.py +9 -9
  91. mlrun/frameworks/_common/mlrun_interface.py +5 -5
  92. mlrun/frameworks/_common/model_handler.py +48 -48
  93. mlrun/frameworks/_common/plan.py +2 -3
  94. mlrun/frameworks/_common/producer.py +3 -4
  95. mlrun/frameworks/_common/utils.py +5 -5
  96. mlrun/frameworks/_dl_common/loggers/logger.py +6 -7
  97. mlrun/frameworks/_dl_common/loggers/mlrun_logger.py +9 -9
  98. mlrun/frameworks/_dl_common/loggers/tensorboard_logger.py +23 -47
  99. mlrun/frameworks/_ml_common/artifacts_library.py +1 -2
  100. mlrun/frameworks/_ml_common/loggers/logger.py +3 -4
  101. mlrun/frameworks/_ml_common/loggers/mlrun_logger.py +4 -5
  102. mlrun/frameworks/_ml_common/model_handler.py +24 -24
  103. mlrun/frameworks/_ml_common/pkl_model_server.py +2 -2
  104. mlrun/frameworks/_ml_common/plan.py +1 -1
  105. mlrun/frameworks/_ml_common/plans/calibration_curve_plan.py +2 -3
  106. mlrun/frameworks/_ml_common/plans/confusion_matrix_plan.py +2 -3
  107. mlrun/frameworks/_ml_common/plans/dataset_plan.py +3 -3
  108. mlrun/frameworks/_ml_common/plans/feature_importance_plan.py +3 -3
  109. mlrun/frameworks/_ml_common/plans/roc_curve_plan.py +4 -4
  110. mlrun/frameworks/_ml_common/utils.py +4 -4
  111. mlrun/frameworks/auto_mlrun/auto_mlrun.py +9 -9
  112. mlrun/frameworks/huggingface/model_server.py +4 -4
  113. mlrun/frameworks/lgbm/__init__.py +33 -33
  114. mlrun/frameworks/lgbm/callbacks/callback.py +2 -4
  115. mlrun/frameworks/lgbm/callbacks/logging_callback.py +4 -5
  116. mlrun/frameworks/lgbm/callbacks/mlrun_logging_callback.py +4 -5
  117. mlrun/frameworks/lgbm/mlrun_interfaces/booster_mlrun_interface.py +1 -3
  118. mlrun/frameworks/lgbm/mlrun_interfaces/mlrun_interface.py +6 -6
  119. mlrun/frameworks/lgbm/model_handler.py +10 -10
  120. mlrun/frameworks/lgbm/model_server.py +6 -6
  121. mlrun/frameworks/lgbm/utils.py +5 -5
  122. mlrun/frameworks/onnx/dataset.py +8 -8
  123. mlrun/frameworks/onnx/mlrun_interface.py +3 -3
  124. mlrun/frameworks/onnx/model_handler.py +6 -6
  125. mlrun/frameworks/onnx/model_server.py +7 -7
  126. mlrun/frameworks/parallel_coordinates.py +4 -3
  127. mlrun/frameworks/pytorch/__init__.py +18 -18
  128. mlrun/frameworks/pytorch/callbacks/callback.py +4 -5
  129. mlrun/frameworks/pytorch/callbacks/logging_callback.py +17 -17
  130. mlrun/frameworks/pytorch/callbacks/mlrun_logging_callback.py +11 -11
  131. mlrun/frameworks/pytorch/callbacks/tensorboard_logging_callback.py +23 -29
  132. mlrun/frameworks/pytorch/callbacks_handler.py +38 -38
  133. mlrun/frameworks/pytorch/mlrun_interface.py +20 -20
  134. mlrun/frameworks/pytorch/model_handler.py +17 -17
  135. mlrun/frameworks/pytorch/model_server.py +7 -7
  136. mlrun/frameworks/sklearn/__init__.py +13 -13
  137. mlrun/frameworks/sklearn/estimator.py +4 -4
  138. mlrun/frameworks/sklearn/metrics_library.py +14 -14
  139. mlrun/frameworks/sklearn/mlrun_interface.py +3 -6
  140. mlrun/frameworks/sklearn/model_handler.py +2 -2
  141. mlrun/frameworks/tf_keras/__init__.py +10 -7
  142. mlrun/frameworks/tf_keras/callbacks/logging_callback.py +15 -15
  143. mlrun/frameworks/tf_keras/callbacks/mlrun_logging_callback.py +11 -11
  144. mlrun/frameworks/tf_keras/callbacks/tensorboard_logging_callback.py +19 -23
  145. mlrun/frameworks/tf_keras/mlrun_interface.py +9 -11
  146. mlrun/frameworks/tf_keras/model_handler.py +14 -14
  147. mlrun/frameworks/tf_keras/model_server.py +6 -6
  148. mlrun/frameworks/xgboost/__init__.py +13 -13
  149. mlrun/frameworks/xgboost/model_handler.py +6 -6
  150. mlrun/k8s_utils.py +14 -16
  151. mlrun/launcher/__init__.py +1 -1
  152. mlrun/launcher/base.py +16 -15
  153. mlrun/launcher/client.py +8 -6
  154. mlrun/launcher/factory.py +1 -1
  155. mlrun/launcher/local.py +17 -11
  156. mlrun/launcher/remote.py +16 -10
  157. mlrun/lists.py +7 -6
  158. mlrun/model.py +238 -73
  159. mlrun/model_monitoring/__init__.py +1 -1
  160. mlrun/model_monitoring/api.py +138 -315
  161. mlrun/model_monitoring/application.py +5 -296
  162. mlrun/model_monitoring/applications/__init__.py +24 -0
  163. mlrun/model_monitoring/applications/_application_steps.py +157 -0
  164. mlrun/model_monitoring/applications/base.py +282 -0
  165. mlrun/model_monitoring/applications/context.py +214 -0
  166. mlrun/model_monitoring/applications/evidently_base.py +211 -0
  167. mlrun/model_monitoring/applications/histogram_data_drift.py +349 -0
  168. mlrun/model_monitoring/applications/results.py +99 -0
  169. mlrun/model_monitoring/controller.py +104 -84
  170. mlrun/model_monitoring/controller_handler.py +13 -5
  171. mlrun/model_monitoring/db/__init__.py +18 -0
  172. mlrun/model_monitoring/{stores → db/stores}/__init__.py +43 -36
  173. mlrun/model_monitoring/db/stores/base/__init__.py +15 -0
  174. mlrun/model_monitoring/{stores/model_endpoint_store.py → db/stores/base/store.py} +64 -40
  175. mlrun/model_monitoring/db/stores/sqldb/__init__.py +13 -0
  176. mlrun/model_monitoring/db/stores/sqldb/models/__init__.py +71 -0
  177. mlrun/model_monitoring/{stores → db/stores/sqldb}/models/base.py +109 -5
  178. mlrun/model_monitoring/db/stores/sqldb/models/mysql.py +88 -0
  179. mlrun/model_monitoring/{stores/models/mysql.py → db/stores/sqldb/models/sqlite.py} +19 -13
  180. mlrun/model_monitoring/db/stores/sqldb/sql_store.py +684 -0
  181. mlrun/model_monitoring/db/stores/v3io_kv/__init__.py +13 -0
  182. mlrun/model_monitoring/{stores/kv_model_endpoint_store.py → db/stores/v3io_kv/kv_store.py} +310 -165
  183. mlrun/model_monitoring/db/tsdb/__init__.py +100 -0
  184. mlrun/model_monitoring/db/tsdb/base.py +329 -0
  185. mlrun/model_monitoring/db/tsdb/helpers.py +30 -0
  186. mlrun/model_monitoring/db/tsdb/tdengine/__init__.py +15 -0
  187. mlrun/model_monitoring/db/tsdb/tdengine/schemas.py +240 -0
  188. mlrun/model_monitoring/db/tsdb/tdengine/stream_graph_steps.py +45 -0
  189. mlrun/model_monitoring/db/tsdb/tdengine/tdengine_connector.py +397 -0
  190. mlrun/model_monitoring/db/tsdb/v3io/__init__.py +15 -0
  191. mlrun/model_monitoring/db/tsdb/v3io/stream_graph_steps.py +117 -0
  192. mlrun/model_monitoring/db/tsdb/v3io/v3io_connector.py +630 -0
  193. mlrun/model_monitoring/evidently_application.py +6 -118
  194. mlrun/model_monitoring/features_drift_table.py +134 -106
  195. mlrun/model_monitoring/helpers.py +127 -28
  196. mlrun/model_monitoring/metrics/__init__.py +13 -0
  197. mlrun/model_monitoring/metrics/histogram_distance.py +127 -0
  198. mlrun/model_monitoring/model_endpoint.py +3 -2
  199. mlrun/model_monitoring/prometheus.py +1 -4
  200. mlrun/model_monitoring/stream_processing.py +62 -231
  201. mlrun/model_monitoring/tracking_policy.py +9 -2
  202. mlrun/model_monitoring/writer.py +152 -124
  203. mlrun/package/__init__.py +6 -6
  204. mlrun/package/context_handler.py +5 -5
  205. mlrun/package/packager.py +7 -7
  206. mlrun/package/packagers/default_packager.py +6 -6
  207. mlrun/package/packagers/numpy_packagers.py +15 -15
  208. mlrun/package/packagers/pandas_packagers.py +5 -5
  209. mlrun/package/packagers/python_standard_library_packagers.py +10 -10
  210. mlrun/package/packagers_manager.py +19 -23
  211. mlrun/package/utils/_formatter.py +6 -6
  212. mlrun/package/utils/_pickler.py +2 -2
  213. mlrun/package/utils/_supported_format.py +4 -4
  214. mlrun/package/utils/log_hint_utils.py +2 -2
  215. mlrun/package/utils/type_hint_utils.py +4 -9
  216. mlrun/platforms/__init__.py +11 -10
  217. mlrun/platforms/iguazio.py +24 -203
  218. mlrun/projects/operations.py +35 -21
  219. mlrun/projects/pipelines.py +68 -99
  220. mlrun/projects/project.py +830 -266
  221. mlrun/render.py +3 -11
  222. mlrun/run.py +162 -166
  223. mlrun/runtimes/__init__.py +62 -7
  224. mlrun/runtimes/base.py +39 -32
  225. mlrun/runtimes/daskjob.py +8 -8
  226. mlrun/runtimes/databricks_job/databricks_cancel_task.py +1 -1
  227. mlrun/runtimes/databricks_job/databricks_runtime.py +7 -7
  228. mlrun/runtimes/databricks_job/databricks_wrapper.py +1 -1
  229. mlrun/runtimes/funcdoc.py +0 -28
  230. mlrun/runtimes/function_reference.py +1 -1
  231. mlrun/runtimes/kubejob.py +28 -122
  232. mlrun/runtimes/local.py +6 -3
  233. mlrun/runtimes/mpijob/__init__.py +0 -20
  234. mlrun/runtimes/mpijob/abstract.py +9 -10
  235. mlrun/runtimes/mpijob/v1.py +1 -1
  236. mlrun/{model_monitoring/stores/models/sqlite.py → runtimes/nuclio/__init__.py} +7 -9
  237. mlrun/runtimes/nuclio/api_gateway.py +709 -0
  238. mlrun/runtimes/nuclio/application/__init__.py +15 -0
  239. mlrun/runtimes/nuclio/application/application.py +523 -0
  240. mlrun/runtimes/nuclio/application/reverse_proxy.go +95 -0
  241. mlrun/runtimes/{function.py → nuclio/function.py} +112 -73
  242. mlrun/runtimes/{nuclio.py → nuclio/nuclio.py} +6 -6
  243. mlrun/runtimes/{serving.py → nuclio/serving.py} +45 -51
  244. mlrun/runtimes/pod.py +286 -88
  245. mlrun/runtimes/remotesparkjob.py +2 -2
  246. mlrun/runtimes/sparkjob/spark3job.py +51 -34
  247. mlrun/runtimes/utils.py +7 -75
  248. mlrun/secrets.py +9 -5
  249. mlrun/serving/remote.py +2 -7
  250. mlrun/serving/routers.py +13 -10
  251. mlrun/serving/server.py +22 -26
  252. mlrun/serving/states.py +99 -25
  253. mlrun/serving/utils.py +3 -3
  254. mlrun/serving/v1_serving.py +6 -7
  255. mlrun/serving/v2_serving.py +59 -20
  256. mlrun/track/tracker.py +2 -1
  257. mlrun/track/tracker_manager.py +3 -3
  258. mlrun/track/trackers/mlflow_tracker.py +1 -2
  259. mlrun/utils/async_http.py +5 -7
  260. mlrun/utils/azure_vault.py +1 -1
  261. mlrun/utils/clones.py +1 -2
  262. mlrun/utils/condition_evaluator.py +3 -3
  263. mlrun/utils/db.py +3 -3
  264. mlrun/utils/helpers.py +183 -197
  265. mlrun/utils/http.py +2 -5
  266. mlrun/utils/logger.py +76 -14
  267. mlrun/utils/notifications/notification/__init__.py +17 -12
  268. mlrun/utils/notifications/notification/base.py +14 -2
  269. mlrun/utils/notifications/notification/console.py +2 -0
  270. mlrun/utils/notifications/notification/git.py +3 -1
  271. mlrun/utils/notifications/notification/ipython.py +3 -1
  272. mlrun/utils/notifications/notification/slack.py +101 -21
  273. mlrun/utils/notifications/notification/webhook.py +11 -1
  274. mlrun/utils/notifications/notification_pusher.py +155 -30
  275. mlrun/utils/retryer.py +208 -0
  276. mlrun/utils/singleton.py +1 -1
  277. mlrun/utils/v3io_clients.py +2 -4
  278. mlrun/utils/version/version.json +2 -2
  279. mlrun/utils/version/version.py +2 -6
  280. {mlrun-1.6.4rc2.dist-info → mlrun-1.7.0rc20.dist-info}/METADATA +31 -19
  281. mlrun-1.7.0rc20.dist-info/RECORD +353 -0
  282. mlrun/kfpops.py +0 -868
  283. mlrun/model_monitoring/batch.py +0 -1095
  284. mlrun/model_monitoring/stores/models/__init__.py +0 -27
  285. mlrun/model_monitoring/stores/sql_model_endpoint_store.py +0 -384
  286. mlrun/platforms/other.py +0 -306
  287. mlrun-1.6.4rc2.dist-info/RECORD +0 -314
  288. {mlrun-1.6.4rc2.dist-info → mlrun-1.7.0rc20.dist-info}/LICENSE +0 -0
  289. {mlrun-1.6.4rc2.dist-info → mlrun-1.7.0rc20.dist-info}/WHEEL +0 -0
  290. {mlrun-1.6.4rc2.dist-info → mlrun-1.7.0rc20.dist-info}/entry_points.txt +0 -0
  291. {mlrun-1.6.4rc2.dist-info → mlrun-1.7.0rc20.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,709 @@
1
+ # Copyright 2023 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
+ import base64
15
+ import typing
16
+ from typing import Optional, Union
17
+ from urllib.parse import urljoin
18
+
19
+ import requests
20
+ from nuclio.auth import AuthInfo as NuclioAuthInfo
21
+ from nuclio.auth import AuthKinds as NuclioAuthKinds
22
+
23
+ import mlrun
24
+ import mlrun.common.constants as mlrun_constants
25
+ import mlrun.common.schemas as schemas
26
+ import mlrun.common.types
27
+ from mlrun.model import ModelObj
28
+ from mlrun.platforms.iguazio import min_iguazio_versions
29
+ from mlrun.utils import logger
30
+
31
+ from .function import min_nuclio_versions
32
+
33
+
34
+ class Authenticator(typing.Protocol):
35
+ @property
36
+ def authentication_mode(self) -> str:
37
+ return schemas.APIGatewayAuthenticationMode.none.value
38
+
39
+ @classmethod
40
+ def from_scheme(cls, api_gateway_spec: schemas.APIGatewaySpec):
41
+ if (
42
+ api_gateway_spec.authenticationMode
43
+ == schemas.APIGatewayAuthenticationMode.basic.value
44
+ ):
45
+ if api_gateway_spec.authentication:
46
+ return BasicAuth(
47
+ username=api_gateway_spec.authentication.get("username", ""),
48
+ password=api_gateway_spec.authentication.get("password", ""),
49
+ )
50
+ else:
51
+ return BasicAuth()
52
+ elif (
53
+ api_gateway_spec.authenticationMode
54
+ == schemas.APIGatewayAuthenticationMode.access_key.value
55
+ ):
56
+ return AccessKeyAuth()
57
+ else:
58
+ return NoneAuth()
59
+
60
+ def to_scheme(
61
+ self,
62
+ ) -> Optional[dict[str, Optional[schemas.APIGatewayBasicAuth]]]:
63
+ return None
64
+
65
+
66
+ class APIGatewayAuthenticator(Authenticator, ModelObj):
67
+ _dict_fields = ["authentication_mode"]
68
+
69
+
70
+ class NoneAuth(APIGatewayAuthenticator):
71
+ """
72
+ An API gateway authenticator with no authentication.
73
+ """
74
+
75
+ pass
76
+
77
+
78
+ class BasicAuth(APIGatewayAuthenticator):
79
+ """
80
+ An API gateway authenticator with basic authentication.
81
+
82
+ :param username: (str) The username for basic authentication.
83
+ :param password: (str) The password for basic authentication.
84
+ """
85
+
86
+ def __init__(self, username=None, password=None):
87
+ self._username = username
88
+ self._password = password
89
+
90
+ @property
91
+ def authentication_mode(self) -> str:
92
+ return schemas.APIGatewayAuthenticationMode.basic.value
93
+
94
+ def to_scheme(
95
+ self,
96
+ ) -> Optional[dict[str, Optional[schemas.APIGatewayBasicAuth]]]:
97
+ return {
98
+ "basicAuth": schemas.APIGatewayBasicAuth(
99
+ username=self._username, password=self._password
100
+ )
101
+ }
102
+
103
+
104
+ class AccessKeyAuth(APIGatewayAuthenticator):
105
+ """
106
+ An API gateway authenticator with access key authentication.
107
+ """
108
+
109
+ @property
110
+ def authentication_mode(self) -> str:
111
+ return schemas.APIGatewayAuthenticationMode.access_key.value
112
+
113
+
114
+ class APIGatewayMetadata(ModelObj):
115
+ _dict_fields = ["name", "namespace", "labels", "annotations", "creation_timestamp"]
116
+
117
+ def __init__(
118
+ self,
119
+ name: str,
120
+ namespace: str = None,
121
+ labels: dict = None,
122
+ annotations: dict = None,
123
+ creation_timestamp: str = None,
124
+ ):
125
+ """
126
+ :param name: The name of the API gateway
127
+ :param namespace: The namespace of the API gateway
128
+ :param labels: The labels of the API gateway
129
+ :param annotations: The annotations of the API gateway
130
+ :param creation_timestamp: The creation timestamp of the API gateway
131
+ """
132
+ self.name = name
133
+ self.namespace = namespace
134
+ self.labels = labels or {}
135
+ self.annotations = annotations or {}
136
+ self.creation_timestamp = creation_timestamp
137
+
138
+ if not self.name:
139
+ raise mlrun.errors.MLRunInvalidArgumentError(
140
+ "API Gateway name cannot be empty"
141
+ )
142
+
143
+
144
+ class APIGatewaySpec(ModelObj):
145
+ _dict_fields = [
146
+ "functions",
147
+ "project",
148
+ "name",
149
+ "description",
150
+ "host",
151
+ "path",
152
+ "authentication",
153
+ "canary",
154
+ ]
155
+
156
+ def __init__(
157
+ self,
158
+ functions: Union[
159
+ list[
160
+ Union[
161
+ str,
162
+ "mlrun.runtimes.nuclio.function.RemoteRuntime",
163
+ "mlrun.runtimes.nuclio.serving.ServingRuntime",
164
+ "mlrun.runtimes.nuclio.application.ApplicationRuntime",
165
+ ]
166
+ ],
167
+ "mlrun.runtimes.nuclio.function.RemoteRuntime",
168
+ "mlrun.runtimes.nuclio.serving.ServingRuntime",
169
+ "mlrun.runtimes.nuclio.application.ApplicationRuntime",
170
+ ],
171
+ project: str = None,
172
+ description: str = "",
173
+ host: str = None,
174
+ path: str = "/",
175
+ authentication: Optional[APIGatewayAuthenticator] = NoneAuth(),
176
+ canary: Optional[list[int]] = None,
177
+ ports: Optional[list[int]] = None,
178
+ ):
179
+ """
180
+ :param functions: The list of functions associated with the API gateway
181
+ Can be a list of function names (["my-func1", "my-func2"])
182
+ or a list or a single entity of
183
+ :py:class:`~mlrun.runtimes.nuclio.function.RemoteRuntime` OR
184
+ :py:class:`~mlrun.runtimes.nuclio.serving.ServingRuntime` OR
185
+ :py:class:`~mlrun.runtimes.nuclio.application.ApplicationRuntime`
186
+ :param project: The project name
187
+ :param description: Optional description of the API gateway
188
+ :param path: Optional path of the API gateway, default value is "/"
189
+ :param authentication: The authentication for the API gateway of type
190
+ :py:class:`~mlrun.runtimes.nuclio.api_gateway.BasicAuth`
191
+ :param host: The host of the API gateway (optional). If not set, it will be automatically generated
192
+ :param canary: The canary percents for the API gateway of type list[int]; for instance: [20,80] (optional)
193
+ :param ports: The ports of the API gateway, as a list of integers that correspond to the functions in the
194
+ functions list. for instance: [8050] or [8050, 8081] (optional)
195
+ """
196
+ self.description = description
197
+ self.host = host
198
+ self.path = path
199
+ self.authentication = authentication
200
+ self.functions = functions
201
+ self.canary = canary
202
+ self.project = project
203
+ self.ports = ports
204
+
205
+ self.validate(project=project, functions=functions, canary=canary, ports=ports)
206
+
207
+ def validate(
208
+ self,
209
+ project: str,
210
+ functions: Union[
211
+ list[
212
+ Union[
213
+ str,
214
+ "mlrun.runtimes.nuclio.function.RemoteRuntime",
215
+ "mlrun.runtimes.nuclio.serving.ServingRuntime",
216
+ "mlrun.runtimes.nuclio.application.ApplicationRuntime",
217
+ ]
218
+ ],
219
+ "mlrun.runtimes.nuclio.function.RemoteRuntime",
220
+ "mlrun.runtimes.nuclio.serving.ServingRuntime",
221
+ "mlrun.runtimes.nuclio.application.ApplicationRuntime",
222
+ ],
223
+ canary: Optional[list[int]] = None,
224
+ ports: Optional[list[int]] = None,
225
+ ):
226
+ self.functions = self._validate_functions(project=project, functions=functions)
227
+
228
+ # validating canary
229
+ if canary:
230
+ self.canary = self._validate_canary(canary)
231
+
232
+ # validating ports
233
+ if ports:
234
+ self.ports = self._validate_ports(ports)
235
+
236
+ def _validate_canary(self, canary: list[int]):
237
+ if len(self.functions) != len(canary):
238
+ raise mlrun.errors.MLRunInvalidArgumentError(
239
+ "Function and canary lists lengths do not match"
240
+ )
241
+ for canary_percent in canary:
242
+ if canary_percent < 0 or canary_percent > 100:
243
+ raise mlrun.errors.MLRunInvalidArgumentError(
244
+ "The percentage value must be in the range from 0 to 100"
245
+ )
246
+ if sum(canary) != 100:
247
+ raise mlrun.errors.MLRunInvalidArgumentError(
248
+ "The sum of canary function percents should be equal to 100"
249
+ )
250
+ return canary
251
+
252
+ def _validate_ports(self, ports):
253
+ if len(self.functions) != len(ports):
254
+ raise mlrun.errors.MLRunInvalidArgumentError(
255
+ "Function and port lists lengths do not match"
256
+ )
257
+
258
+ return ports
259
+
260
+ @staticmethod
261
+ def _validate_functions(
262
+ project: str,
263
+ functions: Union[
264
+ list[
265
+ Union[
266
+ str,
267
+ "mlrun.runtimes.nuclio.function.RemoteRuntime",
268
+ "mlrun.runtimes.nuclio.serving.ServingRuntime",
269
+ "mlrun.runtimes.nuclio.application.ApplicationRuntime",
270
+ ]
271
+ ],
272
+ "mlrun.runtimes.nuclio.function.RemoteRuntime",
273
+ "mlrun.runtimes.nuclio.serving.ServingRuntime",
274
+ "mlrun.runtimes.nuclio.application.ApplicationRuntime",
275
+ ],
276
+ ):
277
+ if not isinstance(functions, list):
278
+ functions = [functions]
279
+
280
+ # validating functions
281
+ if not 1 <= len(functions) <= 2:
282
+ raise mlrun.errors.MLRunInvalidArgumentError(
283
+ f"Gateway can be created from one or two functions, "
284
+ f"the number of functions passed is {len(functions)}"
285
+ )
286
+
287
+ function_names = []
288
+ for func in functions:
289
+ if isinstance(func, str):
290
+ # check whether the function was passed as a URI or just a name
291
+ parsed_project, function_name, _, _ = (
292
+ mlrun.common.helpers.parse_versioned_object_uri(func)
293
+ )
294
+
295
+ if parsed_project and function_name:
296
+ # check that parsed project and passed project are the same
297
+ if parsed_project != project:
298
+ raise mlrun.errors.MLRunInvalidArgumentError(
299
+ "Function doesn't belong to passed project"
300
+ )
301
+ function_uri = func
302
+ else:
303
+ function_uri = mlrun.utils.generate_object_uri(project, func)
304
+ function_names.append(function_uri)
305
+ continue
306
+
307
+ function_name = (
308
+ func.metadata.name if hasattr(func, "metadata") else func.name
309
+ )
310
+ if func.kind not in mlrun.runtimes.RuntimeKinds.nuclio_runtimes():
311
+ raise mlrun.errors.MLRunInvalidArgumentError(
312
+ f"Input function {function_name} is not a Nuclio function"
313
+ )
314
+ if func.metadata.project != project:
315
+ raise mlrun.errors.MLRunInvalidArgumentError(
316
+ f"input function {function_name} "
317
+ f"does not belong to this project"
318
+ )
319
+ function_uri = mlrun.utils.generate_object_uri(
320
+ project,
321
+ function_name,
322
+ func.metadata.tag,
323
+ func.metadata.hash,
324
+ )
325
+ function_names.append(function_uri)
326
+ return function_names
327
+
328
+
329
+ class APIGateway(ModelObj):
330
+ _dict_fields = [
331
+ "metadata",
332
+ "spec",
333
+ "state",
334
+ ]
335
+
336
+ @min_nuclio_versions("1.13.1")
337
+ def __init__(
338
+ self,
339
+ metadata: APIGatewayMetadata,
340
+ spec: APIGatewaySpec,
341
+ ):
342
+ """
343
+ Initialize the APIGateway instance.
344
+
345
+ :param metadata: (APIGatewayMetadata) The metadata of the API gateway.
346
+ :param spec: (APIGatewaySpec) The spec of the API gateway.
347
+ """
348
+ self.metadata = metadata
349
+ self.spec = spec
350
+ self.state = ""
351
+
352
+ @property
353
+ def metadata(self) -> APIGatewayMetadata:
354
+ return self._metadata
355
+
356
+ @metadata.setter
357
+ def metadata(self, metadata):
358
+ self._metadata = self._verify_dict(metadata, "metadata", APIGatewayMetadata)
359
+
360
+ @property
361
+ def spec(self) -> APIGatewaySpec:
362
+ return self._spec
363
+
364
+ @spec.setter
365
+ def spec(self, spec):
366
+ self._spec = self._verify_dict(spec, "spec", APIGatewaySpec)
367
+
368
+ def invoke(
369
+ self,
370
+ method="POST",
371
+ headers: dict = None,
372
+ credentials: Optional[tuple[str, str]] = None,
373
+ path: Optional[str] = None,
374
+ **kwargs,
375
+ ):
376
+ """
377
+ Invoke the API gateway.
378
+
379
+ :param method: (str, optional) The HTTP method for the invocation.
380
+ :param headers: (dict, optional) The HTTP headers for the invocation.
381
+ :param credentials: (Optional[tuple[str, str]], optional) The (username,password) for the invocation if required
382
+ can also be set by the environment variable (_, V3IO_ACCESS_KEY) for access key authentication.
383
+ :param path: (str, optional) The sub-path for the invocation.
384
+ :param kwargs: (dict) Additional keyword arguments.
385
+
386
+ :return: The response from the API gateway invocation.
387
+ """
388
+ if not self.invoke_url:
389
+ # try to resolve invoke_url before fail
390
+ self.sync()
391
+ if not self.invoke_url:
392
+ raise mlrun.errors.MLRunInvalidArgumentError(
393
+ "Invocation url is not set. Set up gateway's `invoke_url` attribute."
394
+ )
395
+ if not self.is_ready():
396
+ raise mlrun.errors.MLRunPreconditionFailedError(
397
+ f"API gateway is not ready. " f"Current state: {self.state}"
398
+ )
399
+
400
+ auth = None
401
+
402
+ if (
403
+ self.spec.authentication.authentication_mode
404
+ == schemas.APIGatewayAuthenticationMode.basic.value
405
+ ):
406
+ if not credentials:
407
+ raise mlrun.errors.MLRunInvalidArgumentError(
408
+ "API Gateway invocation requires authentication. Please pass credentials"
409
+ )
410
+ auth = NuclioAuthInfo(
411
+ username=credentials[0], password=credentials[1]
412
+ ).to_requests_auth()
413
+
414
+ if (
415
+ self.spec.authentication.authentication_mode
416
+ == schemas.APIGatewayAuthenticationMode.access_key.value
417
+ ):
418
+ # inject access key from env
419
+ if credentials:
420
+ auth = NuclioAuthInfo(
421
+ username=credentials[0],
422
+ password=credentials[1],
423
+ mode=NuclioAuthKinds.iguazio,
424
+ ).to_requests_auth()
425
+ else:
426
+ auth = NuclioAuthInfo().from_envvar().to_requests_auth()
427
+ if not auth:
428
+ raise mlrun.errors.MLRunInvalidArgumentError(
429
+ "API Gateway invocation requires authentication. Please set V3IO_ACCESS_KEY env var"
430
+ )
431
+ url = urljoin(self.invoke_url, path or "")
432
+ return requests.request(
433
+ method=method,
434
+ url=url,
435
+ headers=headers or {},
436
+ auth=auth,
437
+ **kwargs,
438
+ )
439
+
440
+ def wait_for_readiness(self, max_wait_time=90):
441
+ """
442
+ Wait for the API gateway to become ready within the maximum wait time.
443
+
444
+ Parameters:
445
+ max_wait_time: int - Maximum time to wait in seconds (default is 90 seconds).
446
+
447
+ Returns:
448
+ bool: True if the entity becomes ready within the maximum wait time, False otherwise
449
+ """
450
+
451
+ def _ensure_ready():
452
+ if not self.is_ready():
453
+ raise AssertionError(
454
+ f"Waiting for gateway readiness is taking more than {max_wait_time} seconds"
455
+ )
456
+
457
+ return mlrun.utils.helpers.retry_until_successful(
458
+ 3, max_wait_time, logger, False, _ensure_ready
459
+ )
460
+
461
+ def is_ready(self):
462
+ if self.state is not schemas.api_gateway.APIGatewayState.ready:
463
+ # try to sync the state
464
+ self.sync()
465
+ return self.state == schemas.api_gateway.APIGatewayState.ready
466
+
467
+ def sync(self):
468
+ """
469
+ Synchronize the API gateway from the server.
470
+ """
471
+ synced_gateway = mlrun.get_run_db().get_api_gateway(
472
+ self.metadata.name, self.spec.project
473
+ )
474
+ synced_gateway = self.from_scheme(synced_gateway)
475
+
476
+ self.spec.host = synced_gateway.spec.host
477
+ self.spec.path = synced_gateway.spec.path
478
+ self.spec.authentication = synced_gateway.spec.authentication
479
+ self.spec.functions = synced_gateway.spec.functions
480
+ self.spec.canary = synced_gateway.spec.canary
481
+ self.spec.description = synced_gateway.spec.description
482
+ self.state = synced_gateway.state
483
+
484
+ def with_basic_auth(self, username: str, password: str):
485
+ """
486
+ Set basic authentication for the API gateway.
487
+
488
+ :param username: (str) The username for basic authentication.
489
+ :param password: (str) The password for basic authentication.
490
+ """
491
+ self.spec.authentication = BasicAuth(username=username, password=password)
492
+
493
+ @min_iguazio_versions("3.5.5")
494
+ def with_access_key_auth(self):
495
+ """
496
+ Set access key authentication for the API gateway.
497
+ """
498
+ self.spec.authentication = AccessKeyAuth()
499
+
500
+ def with_canary(
501
+ self,
502
+ functions: Union[
503
+ list[
504
+ Union[
505
+ str,
506
+ "mlrun.runtimes.nuclio.function.RemoteRuntime",
507
+ "mlrun.runtimes.nuclio.serving.ServingRuntime",
508
+ "mlrun.runtimes.nuclio.application.ApplicationRuntime",
509
+ ]
510
+ ],
511
+ "mlrun.runtimes.nuclio.function.RemoteRuntime",
512
+ "mlrun.runtimes.nuclio.serving.ServingRuntime",
513
+ "mlrun.runtimes.nuclio.application.ApplicationRuntime",
514
+ ],
515
+ canary: list[int],
516
+ ):
517
+ """
518
+ Set canary function for the API gateway
519
+
520
+ :param functions: The list of functions associated with the API gateway
521
+ Can be a list of function names (["my-func1", "my-func2"])
522
+ or a list of nuclio functions of types
523
+ :py:class:`~mlrun.runtimes.nuclio.function.RemoteRuntime` OR
524
+ :py:class:`~mlrun.runtimes.nuclio.serving.ServingRuntime` OR
525
+ :py:class:`~mlrun.runtimes.nuclio.application.ApplicationRuntime`
526
+ :param canary: The canary percents for the API gateway of type list[int]; for instance: [20,80]
527
+
528
+ """
529
+ if len(functions) != 2:
530
+ raise mlrun.errors.MLRunInvalidArgumentError(
531
+ f"Gateway with canary can be created only with two functions, "
532
+ f"the number of functions passed is {len(functions)}"
533
+ )
534
+ self.spec.validate(
535
+ project=self.spec.project, functions=functions, canary=canary
536
+ )
537
+
538
+ def with_ports(self, ports: list[int]):
539
+ """
540
+ Set ports for the API gateway
541
+
542
+ :param ports: The ports of the API gateway, as a list of integers that correspond to the functions in the
543
+ functions list. for instance: [8050] or [8050, 8081]
544
+ """
545
+ self.spec.validate(
546
+ project=self.spec.project, functions=self.spec.functions, ports=ports
547
+ )
548
+
549
+ @classmethod
550
+ def from_scheme(cls, api_gateway: schemas.APIGateway):
551
+ project = api_gateway.metadata.labels.get(
552
+ mlrun_constants.MLRunInternalLabels.nuclio_project_name
553
+ )
554
+ functions, canary = cls._resolve_canary(api_gateway.spec.upstreams)
555
+ state = (
556
+ api_gateway.status.state
557
+ if api_gateway.status
558
+ else schemas.APIGatewayState.none
559
+ )
560
+ new_api_gateway = cls(
561
+ metadata=APIGatewayMetadata(
562
+ name=api_gateway.spec.name,
563
+ ),
564
+ spec=APIGatewaySpec(
565
+ project=project,
566
+ description=api_gateway.spec.description,
567
+ host=api_gateway.spec.host,
568
+ path=api_gateway.spec.path,
569
+ authentication=APIGatewayAuthenticator.from_scheme(api_gateway.spec),
570
+ functions=functions,
571
+ canary=canary,
572
+ ),
573
+ )
574
+ new_api_gateway.state = state
575
+ return new_api_gateway
576
+
577
+ def to_scheme(self) -> schemas.APIGateway:
578
+ upstreams = (
579
+ [
580
+ schemas.APIGatewayUpstream(
581
+ nucliofunction={"name": self.spec.functions[0]},
582
+ percentage=self.spec.canary[0],
583
+ ),
584
+ schemas.APIGatewayUpstream(
585
+ # do not set percent for the second function,
586
+ # so we can define which function to display as a primary one in UI
587
+ nucliofunction={"name": self.spec.functions[1]},
588
+ ),
589
+ ]
590
+ if self.spec.canary
591
+ else [
592
+ schemas.APIGatewayUpstream(
593
+ nucliofunction={"name": function_name},
594
+ )
595
+ for function_name in self.spec.functions
596
+ ]
597
+ )
598
+ if self.spec.ports:
599
+ for i, port in enumerate(self.spec.ports):
600
+ upstreams[i].port = port
601
+
602
+ api_gateway = schemas.APIGateway(
603
+ metadata=schemas.APIGatewayMetadata(name=self.metadata.name, labels={}),
604
+ spec=schemas.APIGatewaySpec(
605
+ name=self.metadata.name,
606
+ description=self.spec.description,
607
+ host=self.spec.host,
608
+ path=self.spec.path,
609
+ authenticationMode=schemas.APIGatewayAuthenticationMode.from_str(
610
+ self.spec.authentication.authentication_mode
611
+ ),
612
+ upstreams=upstreams,
613
+ ),
614
+ )
615
+ api_gateway.spec.authentication = self.spec.authentication.to_scheme()
616
+ return api_gateway
617
+
618
+ @property
619
+ def invoke_url(
620
+ self,
621
+ ):
622
+ """
623
+ Get the invoke URL.
624
+
625
+ :return: (str) The invoke URL.
626
+ """
627
+ host = self.spec.host
628
+ if not self.spec.host.startswith("http"):
629
+ host = f"https://{self.spec.host}"
630
+ return urljoin(host, self.spec.path)
631
+
632
+ @staticmethod
633
+ def _generate_basic_auth(username: str, password: str):
634
+ token = base64.b64encode(f"{username}:{password}".encode()).decode()
635
+ return f"Basic {token}"
636
+
637
+ @staticmethod
638
+ def _resolve_canary(
639
+ upstreams: list[schemas.APIGatewayUpstream],
640
+ ) -> tuple[Union[list[str], None], Union[list[int], None]]:
641
+ if len(upstreams) == 1:
642
+ return [upstreams[0].nucliofunction.get("name")], None
643
+ elif len(upstreams) == 2:
644
+ canary = [0, 0]
645
+ functions = [
646
+ upstreams[0].nucliofunction.get("name"),
647
+ upstreams[1].nucliofunction.get("name"),
648
+ ]
649
+ percentage_1 = upstreams[0].percentage
650
+ percentage_2 = upstreams[1].percentage
651
+
652
+ if not percentage_1 and percentage_2:
653
+ percentage_1 = 100 - percentage_2
654
+ if not percentage_2 and percentage_1:
655
+ percentage_2 = 100 - percentage_1
656
+ if percentage_1 and percentage_2:
657
+ canary = [percentage_1, percentage_2]
658
+ return functions, canary
659
+ else:
660
+ # Nuclio only supports 1 or 2 upstream functions
661
+ return None, None
662
+
663
+ @property
664
+ def name(self):
665
+ return self.metadata.name
666
+
667
+ @name.setter
668
+ def name(self, value):
669
+ self.metadata.name = value
670
+
671
+ @property
672
+ def project(self):
673
+ return self.spec.project
674
+
675
+ @project.setter
676
+ def project(self, value):
677
+ self.spec.project = value
678
+
679
+ @property
680
+ def description(self):
681
+ return self.spec.description
682
+
683
+ @description.setter
684
+ def description(self, value):
685
+ self.spec.description = value
686
+
687
+ @property
688
+ def host(self):
689
+ return self.spec.host
690
+
691
+ @host.setter
692
+ def host(self, value):
693
+ self.spec.host = value
694
+
695
+ @property
696
+ def path(self):
697
+ return self.spec.path
698
+
699
+ @path.setter
700
+ def path(self, value):
701
+ self.spec.path = value
702
+
703
+ @property
704
+ def authentication(self):
705
+ return self.spec.authentication
706
+
707
+ @authentication.setter
708
+ def authentication(self, value):
709
+ self.spec.authentication = value