mlrun 1.10.0rc18__py3-none-any.whl → 1.11.0rc16__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 (167) hide show
  1. mlrun/__init__.py +24 -3
  2. mlrun/__main__.py +0 -4
  3. mlrun/artifacts/dataset.py +2 -2
  4. mlrun/artifacts/document.py +6 -1
  5. mlrun/artifacts/llm_prompt.py +21 -15
  6. mlrun/artifacts/model.py +3 -3
  7. mlrun/artifacts/plots.py +1 -1
  8. mlrun/{model_monitoring/db/tsdb/tdengine → auth}/__init__.py +2 -3
  9. mlrun/auth/nuclio.py +89 -0
  10. mlrun/auth/providers.py +429 -0
  11. mlrun/auth/utils.py +415 -0
  12. mlrun/common/constants.py +14 -0
  13. mlrun/common/model_monitoring/helpers.py +123 -0
  14. mlrun/common/runtimes/constants.py +28 -0
  15. mlrun/common/schemas/__init__.py +14 -3
  16. mlrun/common/schemas/alert.py +2 -2
  17. mlrun/common/schemas/api_gateway.py +3 -0
  18. mlrun/common/schemas/auth.py +12 -10
  19. mlrun/common/schemas/client_spec.py +4 -0
  20. mlrun/common/schemas/constants.py +25 -0
  21. mlrun/common/schemas/frontend_spec.py +1 -8
  22. mlrun/common/schemas/function.py +34 -0
  23. mlrun/common/schemas/hub.py +33 -20
  24. mlrun/common/schemas/model_monitoring/__init__.py +2 -1
  25. mlrun/common/schemas/model_monitoring/constants.py +12 -15
  26. mlrun/common/schemas/model_monitoring/functions.py +13 -4
  27. mlrun/common/schemas/model_monitoring/model_endpoints.py +11 -0
  28. mlrun/common/schemas/pipeline.py +1 -1
  29. mlrun/common/schemas/secret.py +17 -2
  30. mlrun/common/secrets.py +95 -1
  31. mlrun/common/types.py +10 -10
  32. mlrun/config.py +69 -19
  33. mlrun/data_types/infer.py +2 -2
  34. mlrun/datastore/__init__.py +12 -5
  35. mlrun/datastore/azure_blob.py +162 -47
  36. mlrun/datastore/base.py +274 -10
  37. mlrun/datastore/datastore.py +7 -2
  38. mlrun/datastore/datastore_profile.py +84 -22
  39. mlrun/datastore/model_provider/huggingface_provider.py +225 -41
  40. mlrun/datastore/model_provider/mock_model_provider.py +87 -0
  41. mlrun/datastore/model_provider/model_provider.py +206 -74
  42. mlrun/datastore/model_provider/openai_provider.py +226 -66
  43. mlrun/datastore/s3.py +39 -18
  44. mlrun/datastore/sources.py +1 -1
  45. mlrun/datastore/store_resources.py +4 -4
  46. mlrun/datastore/storeytargets.py +17 -12
  47. mlrun/datastore/targets.py +1 -1
  48. mlrun/datastore/utils.py +25 -6
  49. mlrun/datastore/v3io.py +1 -1
  50. mlrun/db/base.py +63 -32
  51. mlrun/db/httpdb.py +373 -153
  52. mlrun/db/nopdb.py +54 -21
  53. mlrun/errors.py +4 -2
  54. mlrun/execution.py +66 -25
  55. mlrun/feature_store/api.py +1 -1
  56. mlrun/feature_store/common.py +1 -1
  57. mlrun/feature_store/feature_vector_utils.py +1 -1
  58. mlrun/feature_store/steps.py +8 -6
  59. mlrun/frameworks/_common/utils.py +3 -3
  60. mlrun/frameworks/_dl_common/loggers/logger.py +1 -1
  61. mlrun/frameworks/_dl_common/loggers/tensorboard_logger.py +2 -1
  62. mlrun/frameworks/_ml_common/loggers/mlrun_logger.py +1 -1
  63. mlrun/frameworks/_ml_common/utils.py +2 -1
  64. mlrun/frameworks/auto_mlrun/auto_mlrun.py +4 -3
  65. mlrun/frameworks/lgbm/mlrun_interfaces/mlrun_interface.py +2 -1
  66. mlrun/frameworks/onnx/dataset.py +2 -1
  67. mlrun/frameworks/onnx/mlrun_interface.py +2 -1
  68. mlrun/frameworks/pytorch/callbacks/logging_callback.py +5 -4
  69. mlrun/frameworks/pytorch/callbacks/mlrun_logging_callback.py +2 -1
  70. mlrun/frameworks/pytorch/callbacks/tensorboard_logging_callback.py +2 -1
  71. mlrun/frameworks/pytorch/utils.py +2 -1
  72. mlrun/frameworks/sklearn/metric.py +2 -1
  73. mlrun/frameworks/tf_keras/callbacks/logging_callback.py +5 -4
  74. mlrun/frameworks/tf_keras/callbacks/mlrun_logging_callback.py +2 -1
  75. mlrun/frameworks/tf_keras/callbacks/tensorboard_logging_callback.py +2 -1
  76. mlrun/hub/__init__.py +52 -0
  77. mlrun/hub/base.py +142 -0
  78. mlrun/hub/module.py +172 -0
  79. mlrun/hub/step.py +113 -0
  80. mlrun/k8s_utils.py +105 -16
  81. mlrun/launcher/base.py +15 -7
  82. mlrun/launcher/local.py +4 -1
  83. mlrun/model.py +14 -4
  84. mlrun/model_monitoring/__init__.py +0 -1
  85. mlrun/model_monitoring/api.py +65 -28
  86. mlrun/model_monitoring/applications/__init__.py +1 -1
  87. mlrun/model_monitoring/applications/base.py +299 -128
  88. mlrun/model_monitoring/applications/context.py +2 -4
  89. mlrun/model_monitoring/controller.py +132 -58
  90. mlrun/model_monitoring/db/_schedules.py +38 -29
  91. mlrun/model_monitoring/db/_stats.py +6 -16
  92. mlrun/model_monitoring/db/tsdb/__init__.py +9 -7
  93. mlrun/model_monitoring/db/tsdb/base.py +29 -9
  94. mlrun/model_monitoring/db/tsdb/preaggregate.py +234 -0
  95. mlrun/model_monitoring/db/tsdb/stream_graph_steps.py +63 -0
  96. mlrun/model_monitoring/db/tsdb/timescaledb/queries/timescaledb_metrics_queries.py +414 -0
  97. mlrun/model_monitoring/db/tsdb/timescaledb/queries/timescaledb_predictions_queries.py +376 -0
  98. mlrun/model_monitoring/db/tsdb/timescaledb/queries/timescaledb_results_queries.py +590 -0
  99. mlrun/model_monitoring/db/tsdb/timescaledb/timescaledb_connection.py +434 -0
  100. mlrun/model_monitoring/db/tsdb/timescaledb/timescaledb_connector.py +541 -0
  101. mlrun/model_monitoring/db/tsdb/timescaledb/timescaledb_operations.py +808 -0
  102. mlrun/model_monitoring/db/tsdb/timescaledb/timescaledb_schema.py +502 -0
  103. mlrun/model_monitoring/db/tsdb/timescaledb/timescaledb_stream.py +163 -0
  104. mlrun/model_monitoring/db/tsdb/timescaledb/timescaledb_stream_graph_steps.py +60 -0
  105. mlrun/model_monitoring/db/tsdb/timescaledb/utils/timescaledb_dataframe_processor.py +141 -0
  106. mlrun/model_monitoring/db/tsdb/timescaledb/utils/timescaledb_query_builder.py +585 -0
  107. mlrun/model_monitoring/db/tsdb/timescaledb/writer_graph_steps.py +73 -0
  108. mlrun/model_monitoring/db/tsdb/v3io/stream_graph_steps.py +20 -9
  109. mlrun/model_monitoring/db/tsdb/v3io/v3io_connector.py +235 -51
  110. mlrun/model_monitoring/features_drift_table.py +2 -1
  111. mlrun/model_monitoring/helpers.py +30 -6
  112. mlrun/model_monitoring/stream_processing.py +34 -28
  113. mlrun/model_monitoring/writer.py +224 -4
  114. mlrun/package/__init__.py +2 -1
  115. mlrun/platforms/__init__.py +0 -43
  116. mlrun/platforms/iguazio.py +8 -4
  117. mlrun/projects/operations.py +17 -11
  118. mlrun/projects/pipelines.py +2 -2
  119. mlrun/projects/project.py +187 -123
  120. mlrun/run.py +95 -21
  121. mlrun/runtimes/__init__.py +2 -186
  122. mlrun/runtimes/base.py +103 -25
  123. mlrun/runtimes/constants.py +225 -0
  124. mlrun/runtimes/daskjob.py +5 -2
  125. mlrun/runtimes/databricks_job/databricks_runtime.py +2 -1
  126. mlrun/runtimes/local.py +5 -2
  127. mlrun/runtimes/mounts.py +20 -2
  128. mlrun/runtimes/nuclio/__init__.py +12 -7
  129. mlrun/runtimes/nuclio/api_gateway.py +36 -6
  130. mlrun/runtimes/nuclio/application/application.py +339 -40
  131. mlrun/runtimes/nuclio/function.py +222 -72
  132. mlrun/runtimes/nuclio/serving.py +132 -42
  133. mlrun/runtimes/pod.py +213 -21
  134. mlrun/runtimes/utils.py +49 -9
  135. mlrun/secrets.py +99 -14
  136. mlrun/serving/__init__.py +2 -0
  137. mlrun/serving/remote.py +84 -11
  138. mlrun/serving/routers.py +26 -44
  139. mlrun/serving/server.py +138 -51
  140. mlrun/serving/serving_wrapper.py +6 -2
  141. mlrun/serving/states.py +997 -283
  142. mlrun/serving/steps.py +62 -0
  143. mlrun/serving/system_steps.py +149 -95
  144. mlrun/serving/v2_serving.py +9 -10
  145. mlrun/track/trackers/mlflow_tracker.py +29 -31
  146. mlrun/utils/helpers.py +292 -94
  147. mlrun/utils/http.py +9 -2
  148. mlrun/utils/notifications/notification/base.py +18 -0
  149. mlrun/utils/notifications/notification/git.py +3 -5
  150. mlrun/utils/notifications/notification/mail.py +39 -16
  151. mlrun/utils/notifications/notification/slack.py +2 -4
  152. mlrun/utils/notifications/notification/webhook.py +2 -5
  153. mlrun/utils/notifications/notification_pusher.py +3 -3
  154. mlrun/utils/version/version.json +2 -2
  155. mlrun/utils/version/version.py +3 -4
  156. {mlrun-1.10.0rc18.dist-info → mlrun-1.11.0rc16.dist-info}/METADATA +63 -74
  157. {mlrun-1.10.0rc18.dist-info → mlrun-1.11.0rc16.dist-info}/RECORD +161 -143
  158. mlrun/api/schemas/__init__.py +0 -259
  159. mlrun/db/auth_utils.py +0 -152
  160. mlrun/model_monitoring/db/tsdb/tdengine/schemas.py +0 -344
  161. mlrun/model_monitoring/db/tsdb/tdengine/stream_graph_steps.py +0 -75
  162. mlrun/model_monitoring/db/tsdb/tdengine/tdengine_connection.py +0 -281
  163. mlrun/model_monitoring/db/tsdb/tdengine/tdengine_connector.py +0 -1266
  164. {mlrun-1.10.0rc18.dist-info → mlrun-1.11.0rc16.dist-info}/WHEEL +0 -0
  165. {mlrun-1.10.0rc18.dist-info → mlrun-1.11.0rc16.dist-info}/entry_points.txt +0 -0
  166. {mlrun-1.10.0rc18.dist-info → mlrun-1.11.0rc16.dist-info}/licenses/LICENSE +0 -0
  167. {mlrun-1.10.0rc18.dist-info → mlrun-1.11.0rc16.dist-info}/top_level.txt +0 -0
@@ -11,6 +11,7 @@
11
11
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
+ import copy
14
15
  import pathlib
15
16
  import typing
16
17
 
@@ -20,21 +21,20 @@ import nuclio.auth
20
21
  import mlrun.common.schemas as schemas
21
22
  import mlrun.errors
22
23
  import mlrun.run
23
- from mlrun.common.runtimes.constants import NuclioIngressAddTemplatedIngressModes
24
- from mlrun.runtimes import RemoteRuntime
25
- from mlrun.runtimes.nuclio import min_nuclio_versions
26
- from mlrun.runtimes.nuclio.api_gateway import (
27
- APIGateway,
28
- APIGatewayMetadata,
29
- APIGatewaySpec,
24
+ import mlrun.runtimes.nuclio.api_gateway as nuclio_api_gateway
25
+ import mlrun.runtimes.nuclio.function as nuclio_function
26
+ from mlrun.common.runtimes.constants import (
27
+ NuclioIngressAddTemplatedIngressModes,
28
+ ProbeTimeConfig,
29
+ ProbeType,
30
30
  )
31
- from mlrun.runtimes.nuclio.function import NuclioSpec, NuclioStatus
32
- from mlrun.utils import logger, update_in
31
+ from mlrun.utils import is_valid_port, logger, update_in
33
32
 
34
33
 
35
- class ApplicationSpec(NuclioSpec):
36
- _dict_fields = NuclioSpec._dict_fields + [
34
+ class ApplicationSpec(nuclio_function.NuclioSpec):
35
+ _dict_fields = nuclio_function.NuclioSpec._dict_fields + [
37
36
  "internal_application_port",
37
+ "application_ports",
38
38
  ]
39
39
 
40
40
  def __init__(
@@ -78,7 +78,13 @@ class ApplicationSpec(NuclioSpec):
78
78
  add_templated_ingress_host_mode=None,
79
79
  state_thresholds=None,
80
80
  disable_default_http_trigger=None,
81
+ serving_spec=None,
82
+ graph=None,
83
+ parameters=None,
84
+ track_models=None,
81
85
  internal_application_port=None,
86
+ application_ports=None,
87
+ auth=None,
82
88
  ):
83
89
  super().__init__(
84
90
  command=command,
@@ -118,19 +124,73 @@ class ApplicationSpec(NuclioSpec):
118
124
  security_context=security_context,
119
125
  service_type=service_type,
120
126
  add_templated_ingress_host_mode=add_templated_ingress_host_mode,
127
+ serving_spec=serving_spec,
128
+ graph=graph,
129
+ parameters=parameters,
130
+ track_models=track_models,
121
131
  state_thresholds=state_thresholds,
122
132
  disable_default_http_trigger=disable_default_http_trigger,
133
+ auth=auth,
123
134
  )
124
135
 
125
136
  # Override default min/max replicas (don't assume application is stateless)
126
137
  self.min_replicas = min_replicas or 1
127
138
  self.max_replicas = max_replicas or 1
128
139
 
140
+ # initializing internal application port and application ports
141
+ self._internal_application_port = None
142
+ self._application_ports = []
143
+
144
+ application_ports = application_ports or []
145
+
146
+ # if internal_application_port is not provided, use the first application port
147
+ if not internal_application_port and len(application_ports) > 0:
148
+ internal_application_port = application_ports[0]
149
+
150
+ # the port of application sidecar to which traffic will be routed from a nuclio function
129
151
  self.internal_application_port = (
130
152
  internal_application_port
131
153
  or mlrun.mlconf.function.application.default_sidecar_internal_port
132
154
  )
133
155
 
156
+ # all exposed ports by the application sidecar
157
+ self.application_ports = application_ports
158
+
159
+ @property
160
+ def application_ports(self):
161
+ return self._application_ports
162
+
163
+ @application_ports.setter
164
+ def application_ports(self, ports):
165
+ """
166
+ Set the application ports for the application sidecar.
167
+ The internal application port is always included and always first.
168
+ """
169
+ # Handle None / single int
170
+ if ports is None:
171
+ ports = []
172
+ elif isinstance(ports, int):
173
+ ports = [ports]
174
+ elif not isinstance(ports, list):
175
+ raise mlrun.errors.MLRunInvalidArgumentError(
176
+ "Application ports must be a list of integers"
177
+ )
178
+
179
+ # Validate and normalize
180
+ cleaned_ports = []
181
+ for port in ports:
182
+ is_valid_port(port, raise_on_error=True)
183
+ if port != self.internal_application_port:
184
+ cleaned_ports.append(port)
185
+
186
+ application_ports = [self.internal_application_port] + cleaned_ports
187
+
188
+ # ensure multiple ports are supported in Nuclio
189
+ if len(application_ports) > 1:
190
+ nuclio_function.multiple_port_sidecar_is_supported()
191
+
192
+ self._application_ports = application_ports
193
+
134
194
  @property
135
195
  def internal_application_port(self):
136
196
  return self._internal_application_port
@@ -138,12 +198,22 @@ class ApplicationSpec(NuclioSpec):
138
198
  @internal_application_port.setter
139
199
  def internal_application_port(self, port):
140
200
  port = int(port)
141
- if port < 0 or port > 65535:
142
- raise ValueError("Port must be in the range 0-65535")
201
+ is_valid_port(port, raise_on_error=True)
143
202
  self._internal_application_port = port
144
203
 
204
+ # If when internal application port is being set, length of self._application_ports is 1,
205
+ # it means that it consist of [old_port] only
206
+ # so in this case, we rewrite the list completely, by setting value to [new_value]
207
+ if len(self.application_ports) == 1:
208
+ self._application_ports = [port]
209
+ return
210
+
211
+ # when setting new internal application port, ensure that it is included in the application ports
212
+ # it just triggers setter logic, so setting to the same value is a no-op
213
+ self.application_ports = self._application_ports
145
214
 
146
- class ApplicationStatus(NuclioStatus):
215
+
216
+ class ApplicationStatus(nuclio_function.NuclioStatus):
147
217
  def __init__(
148
218
  self,
149
219
  state=None,
@@ -173,15 +243,17 @@ class ApplicationStatus(NuclioStatus):
173
243
  self.application_source = application_source or None
174
244
  self.sidecar_name = sidecar_name or None
175
245
  self.api_gateway_name = api_gateway_name or None
176
- self.api_gateway: typing.Optional[APIGateway] = api_gateway or None
246
+ self.api_gateway: typing.Optional[nuclio_api_gateway.APIGateway] = (
247
+ api_gateway or None
248
+ )
177
249
  self.url = url or None
178
250
 
179
251
 
180
- class ApplicationRuntime(RemoteRuntime):
252
+ class ApplicationRuntime(nuclio_function.RemoteRuntime):
181
253
  kind = "application"
182
254
  reverse_proxy_image = None
183
255
 
184
- @min_nuclio_versions("1.13.1")
256
+ @nuclio_function.min_nuclio_versions("1.13.1")
185
257
  def __init__(self, spec=None, metadata=None):
186
258
  super().__init__(spec=spec, metadata=metadata)
187
259
 
@@ -206,7 +278,7 @@ class ApplicationRuntime(RemoteRuntime):
206
278
  return self.status.api_gateway
207
279
 
208
280
  @api_gateway.setter
209
- def api_gateway(self, api_gateway: APIGateway):
281
+ def api_gateway(self, api_gateway: nuclio_api_gateway.APIGateway):
210
282
  self.status.api_gateway = api_gateway
211
283
 
212
284
  @property
@@ -222,6 +294,122 @@ class ApplicationRuntime(RemoteRuntime):
222
294
  def set_internal_application_port(self, port: int):
223
295
  self.spec.internal_application_port = port
224
296
 
297
+ def set_probe(
298
+ self,
299
+ type: str,
300
+ initial_delay_seconds: int | None = None,
301
+ period_seconds: int | None = None,
302
+ failure_threshold: int | None = None,
303
+ timeout_seconds: int | None = None,
304
+ http_path: str | None = None,
305
+ http_port: int | None = None,
306
+ http_scheme: str | None = None,
307
+ config: dict | None = None,
308
+ ):
309
+ """Set a Kubernetes probe configuration for the sidecar container
310
+
311
+ The config parameter serves as the base configuration, and individual parameters
312
+ override values in config. If http_path is provided without http_port and config
313
+ is not provided, the port will be enriched from the internal application port
314
+ just before deployment.
315
+
316
+ :param type: Probe type - one of "readiness", "liveness", "startup"
317
+ :param initial_delay_seconds: Number of seconds after the container has started before probes are initiated
318
+ :param period_seconds: How often (in seconds) to perform the probe
319
+ :param failure_threshold: Minimum consecutive failures for the probe to be considered failed
320
+ :param timeout_seconds: Number of seconds after which the probe times out
321
+ :param http_path: If provided, use an HTTP probe with this path
322
+ :param http_port: If HTTP probe is used and no port provided,
323
+ the internal application port will be used
324
+ :param http_scheme: "http" or "https" for HTTP probe. Defaults to "http"
325
+ :param config: A full dict with the probe configuration
326
+ (used as base, overridden by individual parameters)
327
+
328
+ :return: function object (self)
329
+ """
330
+ self._validate_set_probes_input(locals())
331
+ type = ProbeType(type)
332
+
333
+ # Start with config as base
334
+ probe_config = copy.deepcopy(config) if config else {}
335
+
336
+ # Build HTTP probe configuration if http_path is provided
337
+ # Note: If http_path is None, all HTTP-related parameters (http_port, http_scheme) are ignored
338
+ if http_path:
339
+ http_probe = probe_config.get("httpGet", {})
340
+ http_probe["path"] = http_path
341
+ if http_port is not None:
342
+ http_probe["port"] = http_port
343
+ http_probe["scheme"] = http_scheme or http_probe.get("scheme", "HTTP")
344
+ probe_config["httpGet"] = http_probe
345
+
346
+ # Override timing parameters from explicit arguments
347
+ probe_config.update(
348
+ {
349
+ config.value: value
350
+ for config, value in {
351
+ ProbeTimeConfig.INITIAL_DELAY_SECONDS: initial_delay_seconds,
352
+ ProbeTimeConfig.PERIOD_SECONDS: period_seconds,
353
+ ProbeTimeConfig.FAILURE_THRESHOLD: failure_threshold,
354
+ ProbeTimeConfig.TIMEOUT_SECONDS: timeout_seconds,
355
+ }.items()
356
+ if value is not None
357
+ }
358
+ )
359
+
360
+ # Store probe configuration in the sidecar
361
+ sidecar = self._set_sidecar(self._get_sidecar_name())
362
+ sidecar[type.key] = probe_config
363
+
364
+ return self
365
+
366
+ def delete_probe(
367
+ self,
368
+ type: str,
369
+ ):
370
+ """Delete a Kubernetes probe configuration from the sidecar container
371
+
372
+ :param type: Probe type - one of "readiness", "liveness", "startup"
373
+
374
+ :return: function object (self)
375
+ """
376
+ # Validate probe type
377
+ ProbeType.is_valid(type, raise_on_error=True)
378
+ type = ProbeType(type)
379
+
380
+ sidecar = self._get_sidecar()
381
+ if sidecar:
382
+ if type.key in sidecar:
383
+ del sidecar[type.key]
384
+
385
+ return self
386
+
387
+ def with_sidecar(
388
+ self,
389
+ name: typing.Optional[str] = None,
390
+ image: typing.Optional[str] = None,
391
+ ports: typing.Optional[typing.Union[int, list[int]]] = None,
392
+ command: typing.Optional[str] = None,
393
+ args: typing.Optional[list[str]] = None,
394
+ ):
395
+ # wraps with_sidecar just to set the application ports
396
+ super().with_sidecar(
397
+ name=name,
398
+ image=image,
399
+ ports=ports,
400
+ command=command,
401
+ args=args,
402
+ )
403
+
404
+ if ports:
405
+ if self.spec.internal_application_port != ports[0]:
406
+ logger.info(
407
+ f"Setting internal application port to the first port from the sidecar: {ports[0]}. "
408
+ f"If this is not intended, please set the internal_application_port explicitly."
409
+ )
410
+ self.spec.internal_application_port = ports[0]
411
+ self.spec.application_ports = ports
412
+
225
413
  def pre_deploy_validation(self):
226
414
  super().pre_deploy_validation()
227
415
  if not self.spec.config.get("spec.sidecars"):
@@ -302,6 +490,7 @@ class ApplicationRuntime(RemoteRuntime):
302
490
 
303
491
  :return: The default API gateway URL if created or True if the function is ready (deployed)
304
492
  """
493
+
305
494
  if (self.requires_build() and not self.spec.image) or force_build:
306
495
  self._fill_credentials()
307
496
  self._build_application_image(
@@ -315,14 +504,14 @@ class ApplicationRuntime(RemoteRuntime):
315
504
  show_on_failure=show_on_failure,
316
505
  )
317
506
 
318
- # This is a class method that accepts a function instance, so we pass self as the function instance
319
- self._ensure_reverse_proxy_configurations(self)
507
+ self._ensure_reverse_proxy_configurations()
320
508
  self._configure_application_sidecar()
321
509
 
322
510
  # We only allow accessing the application via the API Gateway
323
511
  self.spec.add_templated_ingress_host_mode = (
324
512
  NuclioIngressAddTemplatedIngressModes.never
325
513
  )
514
+ self._enrich_sidecar_probe_ports()
326
515
 
327
516
  super().deploy(
328
517
  project=project,
@@ -431,6 +620,7 @@ class ApplicationRuntime(RemoteRuntime):
431
620
  ssl_redirect: typing.Optional[bool] = None,
432
621
  set_as_default: bool = False,
433
622
  gateway_timeout: typing.Optional[int] = None,
623
+ port: typing.Optional[int] = None,
434
624
  ):
435
625
  """
436
626
  Create the application API gateway. Once the application is deployed, the API gateway can be created.
@@ -447,6 +637,8 @@ class ApplicationRuntime(RemoteRuntime):
447
637
  :param set_as_default: Set the API gateway as the default for the application (`status.api_gateway`)
448
638
  :param gateway_timeout: nginx ingress timeout in sec (request timeout, when will the gateway return an
449
639
  error)
640
+ :param port: The API gateway port, used only when direct_port_access=True
641
+
450
642
  :return: The API gateway URL
451
643
  """
452
644
  if not name:
@@ -467,15 +659,23 @@ class ApplicationRuntime(RemoteRuntime):
467
659
  "Authentication credentials not provided"
468
660
  )
469
661
 
470
- ports = self.spec.internal_application_port if direct_port_access else []
662
+ if not direct_port_access and port:
663
+ logger.warning(
664
+ "Ignoring 'port' because 'direct_port_access' is not enabled. "
665
+ "The 'port' setting is only applicable when 'direct_port_access' is enabled."
666
+ )
471
667
 
472
- api_gateway = APIGateway(
473
- APIGatewayMetadata(
668
+ ports = (
669
+ port or self.spec.internal_application_port if direct_port_access else []
670
+ )
671
+
672
+ api_gateway = nuclio_api_gateway.APIGateway(
673
+ nuclio_api_gateway.APIGatewayMetadata(
474
674
  name=name,
475
675
  namespace=self.metadata.namespace,
476
676
  labels=self.metadata.labels.copy(),
477
677
  ),
478
- APIGatewaySpec(
678
+ nuclio_api_gateway.APIGatewaySpec(
479
679
  functions=[self],
480
680
  project=self.metadata.project,
481
681
  path=path,
@@ -499,6 +699,8 @@ class ApplicationRuntime(RemoteRuntime):
499
699
  api_gateway.with_access_key_auth()
500
700
  elif authentication_mode == schemas.APIGatewayAuthenticationMode.basic:
501
701
  api_gateway.with_basic_auth(*authentication_creds)
702
+ elif authentication_mode == schemas.APIGatewayAuthenticationMode.iguazio:
703
+ api_gateway.with_iguazio_auth()
502
704
 
503
705
  db = self._get_db()
504
706
  api_gateway_scheme = db.store_api_gateway(
@@ -507,12 +709,14 @@ class ApplicationRuntime(RemoteRuntime):
507
709
 
508
710
  if set_as_default:
509
711
  self.status.api_gateway_name = api_gateway_scheme.metadata.name
510
- self.status.api_gateway = APIGateway.from_scheme(api_gateway_scheme)
712
+ self.status.api_gateway = nuclio_api_gateway.APIGateway.from_scheme(
713
+ api_gateway_scheme
714
+ )
511
715
  self.status.api_gateway.wait_for_readiness()
512
716
  self.url = self.status.api_gateway.invoke_url
513
717
  url = self.url
514
718
  else:
515
- api_gateway = APIGateway.from_scheme(api_gateway_scheme)
719
+ api_gateway = nuclio_api_gateway.APIGateway.from_scheme(api_gateway_scheme)
516
720
  api_gateway.wait_for_readiness()
517
721
  url = api_gateway.invoke_url
518
722
  # Update application status (enriches invocation url)
@@ -536,7 +740,7 @@ class ApplicationRuntime(RemoteRuntime):
536
740
  def invoke(
537
741
  self,
538
742
  path: str = "",
539
- body: typing.Optional[typing.Union[str, bytes, dict]] = None,
743
+ body: typing.Optional[typing.Union[str, bytes, dict, list]] = None,
540
744
  method: typing.Optional[str] = None,
541
745
  headers: typing.Optional[dict] = None,
542
746
  force_external_address: bool = False,
@@ -595,6 +799,12 @@ class ApplicationRuntime(RemoteRuntime):
595
799
  """
596
800
  # create a function that includes only the reverse proxy, without the application
597
801
 
802
+ if not mlrun.get_current_project(silent=True):
803
+ raise mlrun.errors.MLRunMissingProjectError(
804
+ "An active project is required to run deploy_reverse_proxy_image(). "
805
+ "Use `mlrun.get_or_create_project()` or set an active project first."
806
+ )
807
+
598
808
  reverse_proxy_func = mlrun.run.new_function(
599
809
  name="reverse-proxy-temp", kind="remote"
600
810
  )
@@ -624,7 +834,7 @@ class ApplicationRuntime(RemoteRuntime):
624
834
  else self.metadata.name
625
835
  )
626
836
 
627
- @min_nuclio_versions("1.13.1")
837
+ @nuclio_function.min_nuclio_versions("1.13.1")
628
838
  def disable_default_http_trigger(
629
839
  self,
630
840
  ):
@@ -632,7 +842,7 @@ class ApplicationRuntime(RemoteRuntime):
632
842
  "Application runtime does not support disabling the default HTTP trigger"
633
843
  )
634
844
 
635
- @min_nuclio_versions("1.13.1")
845
+ @nuclio_function.min_nuclio_versions("1.13.1")
636
846
  def enable_default_http_trigger(
637
847
  self,
638
848
  ):
@@ -684,27 +894,42 @@ class ApplicationRuntime(RemoteRuntime):
684
894
  with_mlrun=with_mlrun,
685
895
  )
686
896
 
687
- @staticmethod
688
- def _ensure_reverse_proxy_configurations(function: RemoteRuntime):
689
- if function.spec.build.functionSourceCode or function.status.container_image:
897
+ def _ensure_reverse_proxy_configurations(self):
898
+ # If an HTTP trigger already exists in the spec,
899
+ # it means the user explicitly defined a custom configuration,
900
+ # so, skip automatic creation.
901
+ skip_http_trigger_creation = False
902
+ for key, value in self.spec.config.items():
903
+ if key.startswith("spec.triggers"):
904
+ if isinstance(value, dict):
905
+ if value.get("kind") == "http":
906
+ skip_http_trigger_creation = True
907
+ break
908
+ if not skip_http_trigger_creation:
909
+ self.with_http(
910
+ workers=mlrun.mlconf.function.application.default_worker_number,
911
+ trigger_name="application-http",
912
+ )
913
+
914
+ if self.spec.build.functionSourceCode or self.status.container_image:
690
915
  return
691
916
 
692
917
  filename, handler = ApplicationRuntime.get_filename_and_handler()
693
918
  name, spec, code = nuclio.build_file(
694
919
  filename,
695
- name=function.metadata.name,
920
+ name=self.metadata.name,
696
921
  handler=handler,
697
922
  )
698
- function.spec.function_handler = mlrun.utils.get_in(spec, "spec.handler")
699
- function.spec.build.functionSourceCode = mlrun.utils.get_in(
923
+ self.spec.function_handler = mlrun.utils.get_in(spec, "spec.handler")
924
+ self.spec.build.functionSourceCode = mlrun.utils.get_in(
700
925
  spec, "spec.build.functionSourceCode"
701
926
  )
702
- function.spec.nuclio_runtime = mlrun.utils.get_in(spec, "spec.runtime")
927
+ self.spec.nuclio_runtime = mlrun.utils.get_in(spec, "spec.runtime")
703
928
 
704
929
  # default the reverse proxy logger level to info
705
930
  logger_sinks_key = "spec.loggerSinks"
706
- if not function.spec.config.get(logger_sinks_key):
707
- function.set_config(
931
+ if not self.spec.config.get(logger_sinks_key):
932
+ self.set_config(
708
933
  logger_sinks_key, [{"level": "info", "sink": "myStdoutLoggerSink"}]
709
934
  )
710
935
 
@@ -728,7 +953,7 @@ class ApplicationRuntime(RemoteRuntime):
728
953
  self.with_sidecar(
729
954
  name=self.status.sidecar_name,
730
955
  image=self.status.application_image,
731
- ports=self.spec.internal_application_port,
956
+ ports=self.spec.application_ports,
732
957
  command=self.spec.command,
733
958
  args=self.spec.args,
734
959
  )
@@ -748,6 +973,80 @@ class ApplicationRuntime(RemoteRuntime):
748
973
  api_gateway_scheme = db.get_api_gateway(
749
974
  name=self.status.api_gateway_name, project=self.metadata.project
750
975
  )
751
- self.status.api_gateway = APIGateway.from_scheme(api_gateway_scheme)
976
+ self.status.api_gateway = nuclio_api_gateway.APIGateway.from_scheme(
977
+ api_gateway_scheme
978
+ )
752
979
  self.status.api_gateway.wait_for_readiness()
753
980
  self.url = self.status.api_gateway.invoke_url
981
+
982
+ def _enrich_sidecar_probe_ports(self):
983
+ """Enrich sidecar probe configurations with internal application port if needed
984
+
985
+ This method is called just before deployment to automatically enrich HTTP probes
986
+ in the sidecar container that were configured without an explicit port.
987
+
988
+ Enrichment logic:
989
+ - Only enriches HTTP probes (httpGet) that don't already have a port specified
990
+ - If the user explicitly provided http_port in set_probe(), enrichment is skipped
991
+ - If the user provided a port in the config dict, enrichment is skipped
992
+ - Enrichment happens just before deployment to ensure the latest internal_application_port
993
+ value is used, even if it was modified after set_probe() was called
994
+ """
995
+ # Check each probe type and enrich missing HTTP ports
996
+ sidecar = self._get_sidecar()
997
+ if not sidecar:
998
+ return
999
+
1000
+ for probe_type in ProbeType:
1001
+ probe_config = sidecar.get(probe_type.key)
1002
+
1003
+ if probe_config and isinstance(probe_config, dict):
1004
+ http_get = probe_config.get("httpGet")
1005
+ if http_get and isinstance(http_get, dict) and "port" not in http_get:
1006
+ if self.spec.internal_application_port is None:
1007
+ raise ValueError(
1008
+ f"Cannot enrich {probe_type.value} probe: HTTP probe requires a port, "
1009
+ "but internal_application_port is not set. "
1010
+ "Please set the internal_application_port or provide http_port in set_probe()."
1011
+ )
1012
+ http_get["port"] = self.spec.internal_application_port
1013
+ logger.debug(
1014
+ "Enriched sidecar probe port",
1015
+ probe_type=probe_type.value,
1016
+ port=http_get["port"],
1017
+ application_name=self.metadata.name,
1018
+ )
1019
+ sidecar[probe_type.key] = probe_config
1020
+
1021
+ def _get_sidecar(self) -> dict | None:
1022
+ """Get the sidecar container for ApplicationRuntime
1023
+
1024
+ Returns the sidecar dict if found, None otherwise.
1025
+ """
1026
+ sidecar_name = self._get_sidecar_name()
1027
+ if not hasattr(self.spec, "config") or "spec.sidecars" not in self.spec.config:
1028
+ return None
1029
+
1030
+ sidecars = self.spec.config["spec.sidecars"]
1031
+ for sidecar in sidecars:
1032
+ if sidecar.get("name") == sidecar_name:
1033
+ return sidecar
1034
+
1035
+ return None
1036
+
1037
+ def _get_sidecar_name(self) -> str:
1038
+ return f"{self.metadata.name}-sidecar"
1039
+
1040
+ @staticmethod
1041
+ def _validate_set_probes_input(params: dict):
1042
+ # Validate probe type
1043
+ ProbeType.is_valid(params.get("type"), raise_on_error=True)
1044
+
1045
+ # At least one optional parameter must be provided
1046
+ optional_params = {
1047
+ k: v for k, v in params.items() if (k != "type" and k != "self")
1048
+ }
1049
+ if all(v is None for v in optional_params.values()):
1050
+ raise ValueError(
1051
+ "Empty probe configuration: at least one parameter must be set"
1052
+ )