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