zenml-nightly 0.82.1.dev20250522__py3-none-any.whl → 0.82.1.dev20250525__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.
zenml/VERSION CHANGED
@@ -1 +1 @@
1
- 0.82.1.dev20250522
1
+ 0.82.1.dev20250525
zenml/client.py CHANGED
@@ -2024,7 +2024,9 @@ class Client(metaclass=ClientMetaClass):
2024
2024
  name=name,
2025
2025
  type=component_type,
2026
2026
  flavor=flavor,
2027
- configuration=configuration,
2027
+ configuration=validated_config.model_dump(
2028
+ mode="json", exclude_unset=True
2029
+ ),
2028
2030
  labels=labels,
2029
2031
  )
2030
2032
 
@@ -2112,7 +2114,9 @@ class Client(metaclass=ClientMetaClass):
2112
2114
  assert validated_config is not None
2113
2115
  warn_if_config_server_mismatch(validated_config)
2114
2116
 
2115
- update_model.configuration = existing_configuration
2117
+ update_model.configuration = validated_config.model_dump(
2118
+ mode="json", exclude_unset=True
2119
+ )
2116
2120
 
2117
2121
  if labels is not None:
2118
2122
  existing_labels = component.labels or {}
@@ -386,6 +386,31 @@ class DockerSettings(BaseSettings):
386
386
 
387
387
  return self
388
388
 
389
+ @model_validator(mode="before")
390
+ @classmethod
391
+ @before_validator_handler
392
+ def _warn_about_future_default_installer(
393
+ cls, data: Dict[str, Any]
394
+ ) -> Dict[str, Any]:
395
+ """Warns about the future change of default package installer from pip to uv.
396
+
397
+ Args:
398
+ data: The model data.
399
+
400
+ Returns:
401
+ The validated settings values.
402
+ """
403
+ if "python_package_installer" not in data:
404
+ logger.warning(
405
+ "In a future release, the default Python package installer "
406
+ "used by ZenML to build container images for your "
407
+ "containerized pipelines will change from 'pip' to 'uv'. "
408
+ "To maintain current behavior, you can explicitly set "
409
+ "`python_package_installer=PythonPackageInstaller.PIP` "
410
+ "in your DockerSettings."
411
+ )
412
+ return data
413
+
389
414
  model_config = ConfigDict(
390
415
  # public attributes are immutable
391
416
  frozen=True,
@@ -45,6 +45,7 @@ from zenml.constants import (
45
45
  DEFAULT_ZENML_SERVER_MAX_REQUEST_BODY_SIZE_IN_BYTES,
46
46
  DEFAULT_ZENML_SERVER_NAME,
47
47
  DEFAULT_ZENML_SERVER_PIPELINE_RUN_AUTH_WINDOW,
48
+ DEFAULT_ZENML_SERVER_REQUEST_TIMEOUT,
48
49
  DEFAULT_ZENML_SERVER_SECURE_HEADERS_CACHE,
49
50
  DEFAULT_ZENML_SERVER_SECURE_HEADERS_CONTENT,
50
51
  DEFAULT_ZENML_SERVER_SECURE_HEADERS_CSP,
@@ -252,6 +253,11 @@ class ServerConfiguration(BaseModel):
252
253
  used.
253
254
  file_download_size_limit: The maximum size of the file download in
254
255
  bytes. If not specified, the default value of 2GB will be used.
256
+ thread_pool_size: The size of the thread pool for handling requests. If
257
+ not specified, the default value of 40 will be used.
258
+ server_request_timeout: The timeout for server requests in seconds. If
259
+ not specified, the default value of 20 seconds will be used. This
260
+ value should be lower than the client's request timeout.
255
261
  """
256
262
 
257
263
  deployment_type: ServerDeploymentType = ServerDeploymentType.OTHER
@@ -348,6 +354,7 @@ class ServerConfiguration(BaseModel):
348
354
  auto_activate: bool = False
349
355
 
350
356
  thread_pool_size: int = DEFAULT_ZENML_SERVER_THREAD_POOL_SIZE
357
+ server_request_timeout: int = DEFAULT_ZENML_SERVER_REQUEST_TIMEOUT
351
358
 
352
359
  max_request_body_size_in_bytes: int = (
353
360
  DEFAULT_ZENML_SERVER_MAX_REQUEST_BODY_SIZE_IN_BYTES
zenml/constants.py CHANGED
@@ -282,6 +282,7 @@ DEFAULT_ZENML_SERVER_MAX_DEVICE_AUTH_ATTEMPTS = 3
282
282
  DEFAULT_ZENML_SERVER_DEVICE_AUTH_TIMEOUT = 60 * 5 # 5 minutes
283
283
  DEFAULT_ZENML_SERVER_DEVICE_AUTH_POLLING = 5 # seconds
284
284
  DEFAULT_HTTP_TIMEOUT = 30
285
+ DEFAULT_ZENML_SERVER_REQUEST_TIMEOUT = 20 # seconds
285
286
  SERVICE_CONNECTOR_VERIFY_REQUEST_TIMEOUT = 120 # seconds
286
287
  ZENML_API_KEY_PREFIX = "ZENKEY_"
287
288
  DEFAULT_ZENML_SERVER_PIPELINE_RUN_AUTH_WINDOW = 60 * 48 # 48 hours
zenml/enums.py CHANGED
@@ -314,6 +314,7 @@ class EnvironmentType(StrEnum):
314
314
  LIGHTNING_AI_STUDIO = "lightning_ai_studio"
315
315
  GITHUB_CODESPACES = "github_codespaces"
316
316
  VSCODE_REMOTE_CONTAINER = "vscode_remote_container"
317
+ ZENML_CODESPACE = "zenml_codespace"
317
318
 
318
319
 
319
320
  class ModelStages(StrEnum):
zenml/environment.py CHANGED
@@ -79,6 +79,8 @@ def get_environment() -> str:
79
79
  return EnvironmentType.GENERIC_CI
80
80
  elif Environment.in_github_codespaces():
81
81
  return EnvironmentType.GITHUB_CODESPACES
82
+ elif Environment.in_zenml_codespace():
83
+ return EnvironmentType.ZENML_CODESPACE
82
84
  elif Environment.in_vscode_remote_container():
83
85
  return EnvironmentType.VSCODE_REMOTE_CONTAINER
84
86
  elif Environment.in_lightning_ai_studio():
@@ -254,6 +256,16 @@ class Environment(metaclass=SingletonMetaClass):
254
256
  or "GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN" in os.environ
255
257
  )
256
258
 
259
+ @staticmethod
260
+ def in_zenml_codespace() -> bool:
261
+ """If the current Python process is running in ZenML Codespaces.
262
+
263
+ Returns:
264
+ `True` if the current Python process is running in ZenML Codespaces,
265
+ `False` otherwise.
266
+ """
267
+ return os.environ.get("ZENML_ENVIRONMENT") == "codespace"
268
+
257
269
  @staticmethod
258
270
  def in_vscode_remote_container() -> bool:
259
271
  """If the current Python process is running in a VS Code Remote Container.
@@ -45,7 +45,7 @@ class GcpIntegration(Integration):
45
45
  NAME = GCP
46
46
  REQUIREMENTS = [
47
47
  "kfp>=2.6.0",
48
- "gcsfs",
48
+ "gcsfs!=2025.5.0,!=2025.5.0.post1",
49
49
  "google-cloud-secret-manager",
50
50
  "google-cloud-container>=2.21.0",
51
51
  "google-cloud-artifact-registry>=1.11.3",
@@ -78,7 +78,7 @@ from zenml.service_connectors.service_connector import (
78
78
  from zenml.utils.enum_utils import StrEnum
79
79
  from zenml.utils.pydantic_utils import before_validator_handler
80
80
  from zenml.utils.secret_utils import PlainSerializedSecretStr
81
- from zenml.utils.time_utils import utc_now
81
+ from zenml.utils.time_utils import to_utc_timezone, utc_now
82
82
 
83
83
  logger = get_logger(__name__)
84
84
 
@@ -1200,14 +1200,13 @@ class GCPServiceConnector(ServiceConnector):
1200
1200
  elif auth_method == GCPAuthenticationMethods.OAUTH2_TOKEN:
1201
1201
  assert isinstance(cfg, GCPOAuth2TokenConfig)
1202
1202
 
1203
- expires_at = self.expires_at
1204
- if expires_at:
1205
- # Remove the UTC timezone
1206
- expires_at = expires_at.replace(tzinfo=None)
1207
-
1208
1203
  credentials = gcp_credentials.Credentials(
1209
1204
  token=cfg.token.get_secret_value(),
1210
- expiry=expires_at,
1205
+ # Currently GCP expects the expiry to be a timezone-naive
1206
+ # UTC datetime.
1207
+ expiry=to_utc_timezone(self.expires_at).replace(tzinfo=None)
1208
+ if self.expires_at
1209
+ else None,
1211
1210
  scopes=scopes,
1212
1211
  )
1213
1212
 
@@ -1303,10 +1302,12 @@ class GCPServiceConnector(ServiceConnector):
1303
1302
  )
1304
1303
 
1305
1304
  if credentials.expiry:
1306
- # Add the UTC timezone to the expiration time
1307
- expires_at = credentials.expiry.replace(
1308
- tzinfo=datetime.timezone.utc
1309
- )
1305
+ expires_at = credentials.expiry
1306
+
1307
+ if expires_at:
1308
+ # Add the UTC timezone to the expiration time, if it's not already
1309
+ # set
1310
+ expires_at = to_utc_timezone(expires_at)
1310
1311
 
1311
1312
  return credentials, expires_at
1312
1313
 
@@ -425,7 +425,7 @@ class HyperAIOrchestrator(ContainerizedOrchestrator):
425
425
  with tempfile.NamedTemporaryFile(mode="w", delete=True) as f:
426
426
  # Define bash line and command line
427
427
  bash_line = "#!/bin/bash\n"
428
- command_line = f'cd {directory_name} && echo {ENV_ZENML_HYPERAI_RUN_ID}="{deployment_id}_$(date +\%s)" > .env && docker compose up -d'
428
+ command_line = rf'cd {directory_name} && echo {ENV_ZENML_HYPERAI_RUN_ID}="{deployment_id}_$(date +\%s)" > .env && docker compose up -d'
429
429
 
430
430
  # Write script to temporary file
431
431
  with f.file as f_:
@@ -270,18 +270,22 @@ def wait_pod(
270
270
  # Stream logs to `zenml.logger.info()`.
271
271
  # TODO: can we do this without parsing all logs every time?
272
272
  if stream_logs and pod_is_not_pending(resp):
273
- response = core_api.read_namespaced_pod_log(
274
- name=pod_name,
275
- namespace=namespace,
276
- _preload_content=False,
277
- )
278
- raw_data = response.data
279
- decoded_log = raw_data.decode("utf-8", errors="replace")
280
- logs = decoded_log.splitlines()
281
- if len(logs) > logged_lines:
282
- for line in logs[logged_lines:]:
283
- logger.info(line)
284
- logged_lines = len(logs)
273
+ try:
274
+ response = core_api.read_namespaced_pod_log(
275
+ name=pod_name,
276
+ namespace=namespace,
277
+ _preload_content=False,
278
+ )
279
+ except ApiException as e:
280
+ logger.error(f"Error reading pod logs: {e}. Retrying...")
281
+ else:
282
+ raw_data = response.data
283
+ decoded_log = raw_data.decode("utf-8", errors="replace")
284
+ logs = decoded_log.splitlines()
285
+ if len(logs) > logged_lines:
286
+ for line in logs[logged_lines:]:
287
+ logger.info(line)
288
+ logged_lines = len(logs)
285
289
 
286
290
  # Raise an error if the pod failed.
287
291
  if pod_failed(resp):
@@ -352,7 +352,9 @@ class BuiltInContainerMaterializer(BaseMaterializer):
352
352
  ):
353
353
  type_ = find_type_by_str(type_str)
354
354
  materializer_class = materializer_registry[type_]
355
- materializer = materializer_class(uri=path_)
355
+ materializer = materializer_class(
356
+ uri=path_, artifact_store=self.artifact_store
357
+ )
356
358
  element = materializer.load(type_)
357
359
  outputs.append(element)
358
360
 
@@ -364,7 +366,9 @@ class BuiltInContainerMaterializer(BaseMaterializer):
364
366
  materializer_class = source_utils.load(
365
367
  entry["materializer"]
366
368
  )
367
- materializer = materializer_class(uri=path_)
369
+ materializer = materializer_class(
370
+ uri=path_, artifact_store=self.artifact_store
371
+ )
368
372
  element = materializer.load(type_)
369
373
  outputs.append(element)
370
374
 
@@ -427,7 +431,9 @@ class BuiltInContainerMaterializer(BaseMaterializer):
427
431
  self.artifact_store.mkdir(element_path)
428
432
  type_ = type(element)
429
433
  materializer_class = materializer_registry[type_]
430
- materializer = materializer_class(uri=element_path)
434
+ materializer = materializer_class(
435
+ uri=element_path, artifact_store=self.artifact_store
436
+ )
431
437
  materializers.append(materializer)
432
438
  metadata.append(
433
439
  {
@@ -1,5 +1,8 @@
1
1
  """Utils concerning anything concerning the cloud control plane backend."""
2
2
 
3
+ import logging
4
+ import threading
5
+ import time
3
6
  from datetime import datetime, timedelta
4
7
  from threading import RLock
5
8
  from typing import Any, Dict, Optional
@@ -12,9 +15,12 @@ from zenml.exceptions import (
12
15
  IllegalOperationError,
13
16
  SubscriptionUpgradeRequiredError,
14
17
  )
18
+ from zenml.logger import get_logger
15
19
  from zenml.utils.time_utils import utc_now
16
20
  from zenml.zen_server.utils import get_zenml_headers, server_config
17
21
 
22
+ logger = get_logger(__name__)
23
+
18
24
  _cloud_connection: Optional["ZenMLCloudConnection"] = None
19
25
 
20
26
 
@@ -56,16 +62,16 @@ class ZenMLCloudConnection:
56
62
  """
57
63
  url = self._config.api_url + endpoint
58
64
 
59
- response = self.session.request(
60
- method=method,
61
- url=url,
62
- params=params,
63
- json=data,
64
- timeout=self._config.http_timeout,
65
- )
66
- if response.status_code == 401:
67
- # Refresh the auth token and try again
68
- self._reset_login()
65
+ if logger.isEnabledFor(logging.DEBUG):
66
+ # Get the request ID from the current thread object
67
+ request_id = threading.current_thread().name
68
+ logger.debug(
69
+ f"[{request_id}] RBAC STATS - {method} {endpoint} started"
70
+ )
71
+ start_time = time.time()
72
+
73
+ status_code: Optional[int] = None
74
+ try:
69
75
  response = self.session.request(
70
76
  method=method,
71
77
  url=url,
@@ -73,18 +79,36 @@ class ZenMLCloudConnection:
73
79
  json=data,
74
80
  timeout=self._config.http_timeout,
75
81
  )
82
+ if response.status_code == 401:
83
+ # Refresh the auth token and try again
84
+ self._reset_login()
85
+ response = self.session.request(
86
+ method=method,
87
+ url=url,
88
+ params=params,
89
+ json=data,
90
+ timeout=self._config.http_timeout,
91
+ )
76
92
 
77
- try:
78
- response.raise_for_status()
79
- except requests.HTTPError as e:
80
- if response.status_code == 402:
81
- raise SubscriptionUpgradeRequiredError(response.json())
82
- elif response.status_code == 403:
83
- raise IllegalOperationError(response.json())
84
- else:
85
- raise RuntimeError(
86
- f"Failed while trying to contact the central zenml pro "
87
- f"service: {e}"
93
+ status_code = response.status_code
94
+ try:
95
+ response.raise_for_status()
96
+ except requests.HTTPError as e:
97
+ if response.status_code == 402:
98
+ raise SubscriptionUpgradeRequiredError(response.json())
99
+ elif response.status_code == 403:
100
+ raise IllegalOperationError(response.json())
101
+ else:
102
+ raise RuntimeError(
103
+ f"Failed while trying to contact the central zenml pro "
104
+ f"service: {e}"
105
+ )
106
+ finally:
107
+ if logger.isEnabledFor(logging.DEBUG):
108
+ duration = (time.time() - start_time) * 1000
109
+ logger.debug(
110
+ f"[{request_id}] RBAC STATS - {status_code} {method} "
111
+ f"{endpoint} completed in {duration:.2f}ms"
88
112
  )
89
113
 
90
114
  return response
@@ -93,7 +93,7 @@ def create_stack_component(
93
93
 
94
94
  from zenml.stack.utils import validate_stack_component_config
95
95
 
96
- validate_stack_component_config(
96
+ validated_config = validate_stack_component_config(
97
97
  configuration_dict=component.configuration,
98
98
  flavor=component.flavor,
99
99
  component_type=component.type,
@@ -102,6 +102,11 @@ def create_stack_component(
102
102
  validate_custom_flavors=False,
103
103
  )
104
104
 
105
+ if validated_config:
106
+ component.configuration = validated_config.model_dump(
107
+ mode="json", exclude_unset=True
108
+ )
109
+
105
110
  return verify_permissions_and_create_entity(
106
111
  request_model=component,
107
112
  create_method=zen_store().create_stack_component,
@@ -199,7 +204,7 @@ def update_stack_component(
199
204
  from zenml.stack.utils import validate_stack_component_config
200
205
 
201
206
  existing_component = zen_store().get_stack_component(component_id)
202
- validate_stack_component_config(
207
+ validated_config = validate_stack_component_config(
203
208
  configuration_dict=component_update.configuration,
204
209
  flavor=existing_component.flavor_name,
205
210
  component_type=existing_component.type,
@@ -207,6 +212,10 @@ def update_stack_component(
207
212
  # We allow custom flavors to fail import on the server side.
208
213
  validate_custom_flavors=False,
209
214
  )
215
+ if validated_config:
216
+ component_update.configuration = validated_config.model_dump(
217
+ mode="json", exclude_unset=True
218
+ )
210
219
 
211
220
  if component_update.connector:
212
221
  service_connector = zen_store().get_service_connector(
zenml/zen_server/utils.py CHANGED
@@ -15,6 +15,7 @@
15
15
 
16
16
  import inspect
17
17
  import os
18
+ import threading
18
19
  from functools import wraps
19
20
  from typing import (
20
21
  TYPE_CHECKING,
@@ -322,40 +323,6 @@ def async_fastapi_endpoint_wrapper(
322
323
  Decorated function.
323
324
  """
324
325
 
325
- @wraps(func)
326
- def decorated(*args: P.args, **kwargs: P.kwargs) -> Any:
327
- # These imports can't happen at module level as this module is also
328
- # used by the CLI when installed without the `server` extra
329
- from fastapi import HTTPException
330
- from fastapi.responses import JSONResponse
331
-
332
- from zenml.zen_server.auth import AuthContext, set_auth_context
333
-
334
- for arg in args:
335
- if isinstance(arg, AuthContext):
336
- set_auth_context(arg)
337
- break
338
- else:
339
- for _, arg in kwargs.items():
340
- if isinstance(arg, AuthContext):
341
- set_auth_context(arg)
342
- break
343
-
344
- try:
345
- return func(*args, **kwargs)
346
- except OAuthError as error:
347
- # The OAuthError is special because it needs to have a JSON response
348
- return JSONResponse(
349
- status_code=error.status_code,
350
- content=error.to_dict(),
351
- )
352
- except HTTPException:
353
- raise
354
- except Exception as error:
355
- logger.exception("API error")
356
- http_exception = http_exception_from_error(error)
357
- raise http_exception
358
-
359
326
  # When having a sync FastAPI endpoint, it runs the endpoint function in
360
327
  # a worker threadpool. If all threads are busy, it will queue the task.
361
328
  # The problem is that after the endpoint code returns, FastAPI will queue
@@ -369,10 +336,52 @@ def async_fastapi_endpoint_wrapper(
369
336
  # a worker thread to become available.
370
337
  # See: `fastapi.routing.serialize_response(...)` and
371
338
  # https://github.com/fastapi/fastapi/pull/888 for more information.
372
- @wraps(decorated)
339
+ @wraps(func)
373
340
  async def async_decorated(*args: P.args, **kwargs: P.kwargs) -> Any:
374
341
  from starlette.concurrency import run_in_threadpool
375
342
 
343
+ from zenml.zen_server.zen_server_api import request_ids
344
+
345
+ request_id = request_ids.get()
346
+
347
+ @wraps(func)
348
+ def decorated(*args: P.args, **kwargs: P.kwargs) -> Any:
349
+ # These imports can't happen at module level as this module is also
350
+ # used by the CLI when installed without the `server` extra
351
+ from fastapi import HTTPException
352
+ from fastapi.responses import JSONResponse
353
+
354
+ from zenml.zen_server.auth import AuthContext, set_auth_context
355
+
356
+ if request_id:
357
+ # Change the name of the current thread to the request ID
358
+ threading.current_thread().name = request_id
359
+
360
+ for arg in args:
361
+ if isinstance(arg, AuthContext):
362
+ set_auth_context(arg)
363
+ break
364
+ else:
365
+ for _, arg in kwargs.items():
366
+ if isinstance(arg, AuthContext):
367
+ set_auth_context(arg)
368
+ break
369
+
370
+ try:
371
+ return func(*args, **kwargs)
372
+ except OAuthError as error:
373
+ # The OAuthError is special because it needs to have a JSON response
374
+ return JSONResponse(
375
+ status_code=error.status_code,
376
+ content=error.to_dict(),
377
+ )
378
+ except HTTPException:
379
+ raise
380
+ except Exception as error:
381
+ logger.exception("API error")
382
+ http_exception = http_exception_from_error(error)
383
+ raise http_exception
384
+
376
385
  return await run_in_threadpool(decorated, *args, **kwargs)
377
386
 
378
387
  return async_decorated
@@ -20,11 +20,17 @@ To run this file locally, execute:
20
20
  ```
21
21
  """
22
22
 
23
+ import logging
23
24
  import os
25
+ import threading
26
+ import time
27
+ from asyncio import Lock, Semaphore, TimeoutError, wait_for
24
28
  from asyncio.log import logger
29
+ from contextvars import ContextVar
25
30
  from datetime import datetime, timedelta
26
31
  from genericpath import isfile
27
- from typing import Any, List, Set
32
+ from typing import Any, List, Optional, Set
33
+ from uuid import uuid4
28
34
 
29
35
  from anyio import to_thread
30
36
  from fastapi import FastAPI, HTTPException, Request
@@ -113,6 +119,10 @@ from zenml.zen_server.utils import (
113
119
 
114
120
  DASHBOARD_DIRECTORY = "dashboard"
115
121
 
122
+ request_ids: ContextVar[Optional[str]] = ContextVar(
123
+ "request_ids", default=None
124
+ )
125
+
116
126
 
117
127
  def relative_path(rel: str) -> str:
118
128
  """Get the absolute path of a path relative to the ZenML server module.
@@ -138,6 +148,7 @@ last_user_activity: datetime = utc_now()
138
148
  last_user_activity_reported: datetime = last_user_activity + timedelta(
139
149
  seconds=-DEFAULT_ZENML_SERVER_REPORT_USER_ACTIVITY_TO_DB_SECONDS
140
150
  )
151
+ last_user_activity_lock = Lock()
141
152
 
142
153
 
143
154
  # Customize the default request validation handler that comes with FastAPI
@@ -247,51 +258,6 @@ class RestrictFileUploadsMiddleware(BaseHTTPMiddleware):
247
258
 
248
259
  ALLOWED_FOR_FILE_UPLOAD: Set[str] = set()
249
260
 
250
- app.add_middleware(
251
- CORSMiddleware,
252
- allow_origins=server_config().cors_allow_origins,
253
- allow_credentials=True,
254
- allow_methods=["*"],
255
- allow_headers=["*"],
256
- )
257
-
258
- app.add_middleware(
259
- RequestBodyLimit, max_bytes=server_config().max_request_body_size_in_bytes
260
- )
261
- app.add_middleware(
262
- RestrictFileUploadsMiddleware, allowed_paths=ALLOWED_FOR_FILE_UPLOAD
263
- )
264
-
265
-
266
- @app.middleware("http")
267
- async def set_secure_headers(request: Request, call_next: Any) -> Any:
268
- """Middleware to set secure headers.
269
-
270
- Args:
271
- request: The incoming request.
272
- call_next: The next function to be called.
273
-
274
- Returns:
275
- The response with secure headers set.
276
- """
277
- try:
278
- response = await call_next(request)
279
- except Exception:
280
- logger.exception("An error occurred while processing the request")
281
- response = JSONResponse(
282
- status_code=500,
283
- content={"detail": "An unexpected error occurred."},
284
- )
285
-
286
- # If the request is for the openAPI docs, don't set secure headers
287
- if request.url.path.startswith("/docs") or request.url.path.startswith(
288
- "/redoc"
289
- ):
290
- return response
291
-
292
- secure_headers().framework.fastapi(response)
293
- return response
294
-
295
261
 
296
262
  @app.middleware("http")
297
263
  async def track_last_user_activity(request: Request, call_next: Any) -> Any:
@@ -310,24 +276,30 @@ async def track_last_user_activity(request: Request, call_next: Any) -> Any:
310
276
  """
311
277
  global last_user_activity
312
278
  global last_user_activity_reported
279
+ global last_user_activity_lock
313
280
 
314
281
  now = utc_now()
315
282
 
316
283
  try:
317
284
  if is_user_request(request):
318
- last_user_activity = now
285
+ report_user_activity = False
286
+ async with last_user_activity_lock:
287
+ last_user_activity = now
288
+ if (
289
+ (now - last_user_activity_reported).total_seconds()
290
+ > DEFAULT_ZENML_SERVER_REPORT_USER_ACTIVITY_TO_DB_SECONDS
291
+ ):
292
+ last_user_activity_reported = now
293
+ report_user_activity = True
294
+
295
+ if report_user_activity:
296
+ zen_store()._update_last_user_activity_timestamp(
297
+ last_user_activity=last_user_activity
298
+ )
319
299
  except Exception as e:
320
300
  logger.debug(
321
301
  f"An unexpected error occurred while checking user activity: {e}"
322
302
  )
323
- if (
324
- (now - last_user_activity_reported).total_seconds()
325
- > DEFAULT_ZENML_SERVER_REPORT_USER_ACTIVITY_TO_DB_SECONDS
326
- ):
327
- last_user_activity_reported = now
328
- zen_store()._update_last_user_activity_timestamp(
329
- last_user_activity=last_user_activity
330
- )
331
303
 
332
304
  try:
333
305
  return await call_next(request)
@@ -378,6 +350,190 @@ async def infer_source_context(request: Request, call_next: Any) -> Any:
378
350
  )
379
351
 
380
352
 
353
+ request_semaphore = Semaphore(server_config().thread_pool_size)
354
+
355
+
356
+ @app.middleware("http")
357
+ async def prevent_read_timeout(request: Request, call_next: Any) -> Any:
358
+ """Prevent read timeout client errors.
359
+
360
+ Args:
361
+ request: The incoming request.
362
+ call_next: The next function to be called.
363
+
364
+ Returns:
365
+ The response to the request.
366
+ """
367
+ # Only process the REST API requests because these are the ones that
368
+ # take the most time to complete.
369
+ if not request.url.path.startswith(API):
370
+ return await call_next(request)
371
+
372
+ server_request_timeout = server_config().server_request_timeout
373
+
374
+ active_threads = threading.active_count()
375
+ request_id = request_ids.get()
376
+
377
+ client_ip = request.client.host if request.client else "unknown"
378
+ method = request.method
379
+ url_path = request.url.path
380
+
381
+ logger.debug(
382
+ f"[{request_id}] API STATS - {method} {url_path} from {client_ip} "
383
+ f"QUEUED [ "
384
+ f"threads: {active_threads} "
385
+ f"]"
386
+ )
387
+
388
+ start_time = time.time()
389
+
390
+ try:
391
+ # Here we wait until a worker thread is available to process the
392
+ # request with a timeout value that is set to be lower than the
393
+ # what the client is willing to wait for (i.e. lower than the
394
+ # client's HTTP request timeout). The rationale is that we want to
395
+ # respond to the client before it times out and decides to retry the
396
+ # request (which would overwhelm the server).
397
+ await wait_for(
398
+ request_semaphore.acquire(),
399
+ timeout=server_request_timeout,
400
+ )
401
+ except TimeoutError:
402
+ end_time = time.time()
403
+ duration = (end_time - start_time) * 1000
404
+ active_threads = threading.active_count()
405
+
406
+ logger.debug(
407
+ f"[{request_id}] API STATS - {method} {url_path} from {client_ip} "
408
+ f"THROTTLED after {duration:.2f}ms [ "
409
+ f"threads: {active_threads} "
410
+ f"]"
411
+ )
412
+
413
+ # We return a 429 error, basically telling the client to slow down.
414
+ # For the client, the 429 error is more meaningful than a ReadTimeout
415
+ # error, because it also tells the client two additional things:
416
+ #
417
+ # 1. The server is alive.
418
+ # 2. The server hasn't processed the request, so even if the request
419
+ # is not idempotent, it's safe to retry it.
420
+ return JSONResponse(
421
+ {"error": "Server too busy. Please try again later."},
422
+ status_code=429,
423
+ )
424
+
425
+ duration = (time.time() - start_time) * 1000
426
+ active_threads = threading.active_count()
427
+
428
+ logger.debug(
429
+ f"[{request_id}] API STATS - {method} {url_path} from {client_ip} "
430
+ f"ACCEPTED after {duration:.2f}ms [ "
431
+ f"threads: {active_threads} "
432
+ f"]"
433
+ )
434
+
435
+ try:
436
+ return await call_next(request)
437
+ finally:
438
+ request_semaphore.release()
439
+
440
+
441
+ @app.middleware("http")
442
+ async def log_requests(request: Request, call_next: Any) -> Any:
443
+ """Log requests to the ZenML server.
444
+
445
+ Args:
446
+ request: The incoming request object.
447
+ call_next: A function that will receive the request as a parameter and
448
+ pass it to the corresponding path operation.
449
+
450
+ Returns:
451
+ The response to the request.
452
+ """
453
+ if not logger.isEnabledFor(logging.DEBUG):
454
+ return await call_next(request)
455
+
456
+ # Get active threads count
457
+ active_threads = threading.active_count()
458
+
459
+ request_id = request.headers.get("X-Request-ID", str(uuid4())[:8])
460
+ # Detect if the request comes from Python, Web UI or something else
461
+ if source := request.headers.get("User-Agent"):
462
+ source = source.split("/")[0]
463
+ request_id = f"{request_id}/{source}"
464
+
465
+ request_ids.set(request_id)
466
+ client_ip = request.client.host if request.client else "unknown"
467
+ method = request.method
468
+ url_path = request.url.path
469
+
470
+ logger.debug(
471
+ f"[{request_id}] API STATS - {method} {url_path} from {client_ip} "
472
+ f"RECEIVED [ "
473
+ f"threads: {active_threads} "
474
+ f"]"
475
+ )
476
+
477
+ start_time = time.time()
478
+ response = await call_next(request)
479
+ duration = (time.time() - start_time) * 1000
480
+ status_code = response.status_code
481
+
482
+ logger.debug(
483
+ f"[{request_id}] API STATS - {status_code} {method} {url_path} from "
484
+ f"{client_ip} took {duration:.2f}ms [ "
485
+ f"threads: {active_threads} "
486
+ f"]"
487
+ )
488
+ return response
489
+
490
+
491
+ app.add_middleware(
492
+ CORSMiddleware,
493
+ allow_origins=server_config().cors_allow_origins,
494
+ allow_credentials=True,
495
+ allow_methods=["*"],
496
+ allow_headers=["*"],
497
+ )
498
+
499
+ app.add_middleware(
500
+ RequestBodyLimit, max_bytes=server_config().max_request_body_size_in_bytes
501
+ )
502
+ app.add_middleware(
503
+ RestrictFileUploadsMiddleware, allowed_paths=ALLOWED_FOR_FILE_UPLOAD
504
+ )
505
+
506
+
507
+ @app.middleware("http")
508
+ async def set_secure_headers(request: Request, call_next: Any) -> Any:
509
+ """Middleware to set secure headers.
510
+
511
+ Args:
512
+ request: The incoming request.
513
+ call_next: The next function to be called.
514
+
515
+ Returns:
516
+ The response with secure headers set.
517
+ """
518
+ try:
519
+ response = await call_next(request)
520
+ except Exception:
521
+ logger.exception("An error occurred while processing the request")
522
+ response = JSONResponse(
523
+ status_code=500,
524
+ content={"detail": "An unexpected error occurred."},
525
+ )
526
+
527
+ # If the request is for the openAPI docs, don't set secure headers
528
+ if request.url.path.startswith("/docs") or request.url.path.startswith(
529
+ "/redoc"
530
+ ):
531
+ return response
532
+
533
+ secure_headers().framework.fastapi(response)
534
+ return response
535
+
536
+
381
537
  @app.on_event("startup")
382
538
  def initialize() -> None:
383
539
  """Initialize the ZenML server."""
@@ -15,6 +15,7 @@
15
15
 
16
16
  import os
17
17
  import re
18
+ import time
18
19
  from datetime import datetime
19
20
  from pathlib import Path
20
21
  from typing import (
@@ -30,7 +31,7 @@ from typing import (
30
31
  Union,
31
32
  )
32
33
  from urllib.parse import urlparse
33
- from uuid import UUID
34
+ from uuid import UUID, uuid4
34
35
 
35
36
  import requests
36
37
  import urllib3
@@ -4179,6 +4180,20 @@ class RestZenStore(BaseZenStore):
4179
4180
  Returns:
4180
4181
  A requests session.
4181
4182
  """
4183
+
4184
+ class AugmentedRetry(Retry):
4185
+ """Augmented retry class that also retries on 429 status codes for POST requests."""
4186
+
4187
+ def is_retry(
4188
+ self,
4189
+ method: str,
4190
+ status_code: int,
4191
+ has_retry_after: bool = False,
4192
+ ) -> bool:
4193
+ if status_code == 429:
4194
+ return True
4195
+ return super().is_retry(method, status_code, has_retry_after)
4196
+
4182
4197
  if self._session is None:
4183
4198
  # We only need to initialize the session once over the lifetime
4184
4199
  # of the client. We can swap the token out when it expires.
@@ -4208,7 +4223,7 @@ class RestZenStore(BaseZenStore):
4208
4223
  # the timeout period.
4209
4224
  # Connection Refused: If the server refuses the connection.
4210
4225
  #
4211
- retries = Retry(
4226
+ retries = AugmentedRetry(
4212
4227
  connect=5,
4213
4228
  read=8,
4214
4229
  redirect=3,
@@ -4222,7 +4237,7 @@ class RestZenStore(BaseZenStore):
4222
4237
  504, # Gateway Timeout
4223
4238
  ],
4224
4239
  other=3,
4225
- backoff_factor=0.5,
4240
+ backoff_factor=1,
4226
4241
  )
4227
4242
  self._session.mount("https://", HTTPAdapter(max_retries=retries))
4228
4243
  self._session.mount("http://", HTTPAdapter(max_retries=retries))
@@ -4360,6 +4375,14 @@ class RestZenStore(BaseZenStore):
4360
4375
  self.session.headers.update(
4361
4376
  {source_context.name: source_context.get().value}
4362
4377
  )
4378
+ # Add a request ID to the request headers
4379
+ request_id = str(uuid4())[:8]
4380
+ self.session.headers.update({"X-Request-ID": request_id})
4381
+ path = url.removeprefix(self.url)
4382
+ start_time = time.time()
4383
+ logger.debug(
4384
+ f"Sending {method} request to {path} with request ID {request_id}..."
4385
+ )
4363
4386
 
4364
4387
  # If the server replies with a credentials validation (401 Unauthorized)
4365
4388
  # error, we (re-)authenticate and retry the request here in the
@@ -4401,7 +4424,8 @@ class RestZenStore(BaseZenStore):
4401
4424
  # request again, this time with a valid API token in the
4402
4425
  # header.
4403
4426
  logger.debug(
4404
- f"The last request was not authenticated: {e}\n"
4427
+ f"The last request with ID {request_id} was not "
4428
+ f"authenticated: {e}\n"
4405
4429
  "Re-authenticating and retrying..."
4406
4430
  )
4407
4431
  self.authenticate()
@@ -4428,8 +4452,9 @@ class RestZenStore(BaseZenStore):
4428
4452
  # that was rejected by the server. We attempt a
4429
4453
  # re-authentication here and then retry the request.
4430
4454
  logger.debug(
4431
- "The last request was authenticated with an API token "
4432
- f"that was rejected by the server: {e}\n"
4455
+ f"The last request with ID {request_id} was authenticated "
4456
+ "with an API token that was rejected by the server: "
4457
+ f"{e}\n"
4433
4458
  "Re-authenticating and retrying..."
4434
4459
  )
4435
4460
  re_authenticated = True
@@ -4441,13 +4466,21 @@ class RestZenStore(BaseZenStore):
4441
4466
  # The last request was made after re-authenticating but
4442
4467
  # still failed. Bailing out.
4443
4468
  logger.debug(
4444
- f"The last request failed after re-authenticating: {e}\n"
4469
+ f"The last request with ID {request_id} failed after "
4470
+ "re-authenticating: {e}\n"
4445
4471
  "Bailing out..."
4446
4472
  )
4447
4473
  raise CredentialsNotValid(
4448
4474
  "The current credentials are no longer valid. Please "
4449
4475
  "log in again using 'zenml login'."
4450
4476
  ) from e
4477
+ finally:
4478
+ end_time = time.time()
4479
+ duration = (end_time - start_time) * 1000
4480
+ logger.debug(
4481
+ f"Request to {path} with request ID {request_id} took "
4482
+ f"{duration:.2f}ms."
4483
+ )
4451
4484
 
4452
4485
  def get(
4453
4486
  self,
@@ -4467,7 +4500,6 @@ class RestZenStore(BaseZenStore):
4467
4500
  Returns:
4468
4501
  The response body.
4469
4502
  """
4470
- logger.debug(f"Sending GET request to {path}...")
4471
4503
  return self._request(
4472
4504
  "GET",
4473
4505
  self.url + API + VERSION_1 + path,
@@ -4496,7 +4528,6 @@ class RestZenStore(BaseZenStore):
4496
4528
  Returns:
4497
4529
  The response body.
4498
4530
  """
4499
- logger.debug(f"Sending DELETE request to {path}...")
4500
4531
  return self._request(
4501
4532
  "DELETE",
4502
4533
  self.url + API + VERSION_1 + path,
@@ -4526,7 +4557,6 @@ class RestZenStore(BaseZenStore):
4526
4557
  Returns:
4527
4558
  The response body.
4528
4559
  """
4529
- logger.debug(f"Sending POST request to {path}...")
4530
4560
  return self._request(
4531
4561
  "POST",
4532
4562
  self.url + API + VERSION_1 + path,
@@ -4556,7 +4586,6 @@ class RestZenStore(BaseZenStore):
4556
4586
  Returns:
4557
4587
  The response body.
4558
4588
  """
4559
- logger.debug(f"Sending PUT request to {path}...")
4560
4589
  json = (
4561
4590
  body.model_dump(mode="json", exclude_unset=True) if body else None
4562
4591
  )
@@ -14,6 +14,7 @@
14
14
  """SQL Zen Store implementation."""
15
15
 
16
16
  import base64
17
+ import inspect
17
18
  import json
18
19
  import logging
19
20
  import math
@@ -21,6 +22,7 @@ import os
21
22
  import random
22
23
  import re
23
24
  import sys
25
+ import threading
24
26
  import time
25
27
  from collections import defaultdict
26
28
  from datetime import datetime
@@ -58,7 +60,7 @@ from pydantic import (
58
60
  field_validator,
59
61
  model_validator,
60
62
  )
61
- from sqlalchemy import func
63
+ from sqlalchemy import QueuePool, func
62
64
  from sqlalchemy.engine import URL, Engine, make_url
63
65
  from sqlalchemy.exc import (
64
66
  ArgumentError,
@@ -66,6 +68,7 @@ from sqlalchemy.exc import (
66
68
  )
67
69
  from sqlalchemy.orm import Mapped, noload
68
70
  from sqlalchemy.util import immutabledict
71
+ from sqlmodel import Session as SqlModelSession
69
72
 
70
73
  # Important to note: The select function of SQLModel works slightly differently
71
74
  # from the select function of sqlalchemy. If you input only one entity on the
@@ -74,7 +77,6 @@ from sqlalchemy.util import immutabledict
74
77
  # the tuple. While this is convenient in most cases, in unique cases like using
75
78
  # the "add_columns" functionality, one might encounter unexpected results.
76
79
  from sqlmodel import (
77
- Session,
78
80
  SQLModel,
79
81
  and_,
80
82
  col,
@@ -412,6 +414,81 @@ def exponential_backoff_with_jitter(
412
414
  return random.uniform(0, exponential_backoff)
413
415
 
414
416
 
417
+ class Session(SqlModelSession):
418
+ """Session subclass that automatically tracks duration and calling context."""
419
+
420
+ def __enter__(self) -> "Session":
421
+ """Enter the context manager.
422
+
423
+ Returns:
424
+ The SqlModel session.
425
+ """
426
+ if logger.isEnabledFor(logging.DEBUG):
427
+ # Get the request ID from the current thread object
428
+ self.request_id = threading.current_thread().name
429
+
430
+ # Get SQLAlchemy connection pool info
431
+ assert isinstance(self.bind, Engine)
432
+ assert isinstance(self.bind.pool, QueuePool)
433
+ checked_out_connections = self.bind.pool.checkedout()
434
+ available_connections = self.bind.pool.checkedin()
435
+ overflow = self.bind.pool.overflow()
436
+
437
+ # Look up the stack to find the SQLZenStore method
438
+ for frame in inspect.stack():
439
+ if "self" in frame.frame.f_locals:
440
+ instance = frame.frame.f_locals["self"]
441
+ if isinstance(instance, SqlZenStore):
442
+ self.caller_method = (
443
+ f"{instance.__class__.__name__}.{frame.function}"
444
+ )
445
+ break
446
+ else:
447
+ self.caller_method = "unknown"
448
+
449
+ logger.debug(
450
+ f"[{self.request_id}] SQL STATS - "
451
+ f"'{self.caller_method}' started [ conn(active): "
452
+ f"{checked_out_connections} conn(idle): "
453
+ f"{available_connections} conn(overflow): {overflow} ]"
454
+ )
455
+
456
+ self.start_time = time.time()
457
+
458
+ return super().__enter__()
459
+
460
+ def __exit__(
461
+ self,
462
+ exc_type: Optional[Any],
463
+ exc_val: Optional[Any],
464
+ exc_tb: Optional[Any],
465
+ ) -> None:
466
+ """Exit the context manager.
467
+
468
+ Args:
469
+ exc_type: The exception type.
470
+ exc_val: The exception value.
471
+ exc_tb: The exception traceback.
472
+ """
473
+ if logger.isEnabledFor(logging.DEBUG):
474
+ duration = (time.time() - self.start_time) * 1000
475
+
476
+ # Get SQLAlchemy connection pool info
477
+ assert isinstance(self.bind, Engine)
478
+ assert isinstance(self.bind.pool, QueuePool)
479
+ checked_out_connections = self.bind.pool.checkedout()
480
+ available_connections = self.bind.pool.checkedin()
481
+ overflow = self.bind.pool.overflow()
482
+ logger.debug(
483
+ f"[{self.request_id}] SQL STATS - "
484
+ f"'{self.caller_method}' completed in "
485
+ f"{duration:.2f}ms [ conn(active): "
486
+ f"{checked_out_connections} conn(idle): "
487
+ f"{available_connections} conn(overflow): {overflow} ]"
488
+ )
489
+ super().__exit__(exc_type, exc_val, exc_tb)
490
+
491
+
415
492
  class SQLDatabaseDriver(StrEnum):
416
493
  """SQL database drivers supported by the SQL ZenML store."""
417
494
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: zenml-nightly
3
- Version: 0.82.1.dev20250522
3
+ Version: 0.82.1.dev20250525
4
4
  Summary: ZenML: Write production-ready ML code.
5
5
  License: Apache-2.0
6
6
  Keywords: machine learning,production,pipeline,mlops,devops
@@ -1,5 +1,5 @@
1
1
  zenml/README.md,sha256=827dekbOWAs1BpW7VF1a4d7EbwPbjwccX-2zdXBENZo,1777
2
- zenml/VERSION,sha256=V62XyuhhLfntir6O3u5XhATyUFx8svXLb1DDa10S8UM,19
2
+ zenml/VERSION,sha256=NvkXzVXVpaIaM--b1e96BJGApJO649xw63IMoAKz3BE,19
3
3
  zenml/__init__.py,sha256=CKEyepFK-7akXYiMrNVh92Nb01Cjs23w4_YyI6sgdc8,2242
4
4
  zenml/actions/__init__.py,sha256=mrt6wPo73iKRxK754_NqsGyJ3buW7RnVeIGXr1xEw8Y,681
5
5
  zenml/actions/base_action.py,sha256=UcaHev6BTuLDwuswnyaPjdA8AgUqB5xPZ-lRtuvf2FU,25553
@@ -55,7 +55,7 @@ zenml/cli/text_utils.py,sha256=bY1GIjoULt1cW2FyrPlMoAXNS2R7cSOjDFEZQqrpVQ8,3553
55
55
  zenml/cli/user_management.py,sha256=sNnhaUxH-cHecbZBR1L0mEU0TnLNZHzI6ZBCUSQa7OY,13078
56
56
  zenml/cli/utils.py,sha256=vMAb9f6GDfNVGmZWOz9UOyPRpKI3KfnYpRl_w9YUBNE,86501
57
57
  zenml/cli/version.py,sha256=nm1iSU_1V6-MUwpMKeXcwFhLYGUMLswvQL67cEuCpxA,3635
58
- zenml/client.py,sha256=bQqMJVZcg5TJxwQLsB2zmKaTQ6PdYdUALIqytqO-j1k,293057
58
+ zenml/client.py,sha256=aCj2HEE6M724js6t4QJQq1S9R6C-_hxQe4UMUN0gjbw,293202
59
59
  zenml/client_lazy_loader.py,sha256=MOBgS1ITYqGvPUnWQ6edn9s8Hr_72YfWbwEIfHKUr9g,7104
60
60
  zenml/code_repositories/__init__.py,sha256=W5bDfzAG8OXIKZSV1L-VHuzMcSCYa9qzTdPb3jqfyYw,920
61
61
  zenml/code_repositories/base_code_repository.py,sha256=Id6VjbUu8N3ZpNvBGhOgbahtoMiCAtYXed3G7YQ_iAc,5225
@@ -67,7 +67,7 @@ zenml/config/base_settings.py,sha256=itoLqc1cOwEYhgSGdZmSKSaBevQkvYH7NQh7PUamazc
67
67
  zenml/config/build_configuration.py,sha256=jGGNwP0Cb7a80JXArNxDgxzxl9ytSZRtv-WW7_psLbM,6870
68
68
  zenml/config/compiler.py,sha256=bK3LCDkrFc9SapJYH-vuQZ_o8scHNs-FdC5DblIUU4U,23024
69
69
  zenml/config/constants.py,sha256=QvSgMwXWxtspcJ45CrFDP1ZY3w6gS3bIhXLOtIDAbZA,713
70
- zenml/config/docker_settings.py,sha256=B3pYlnyQDiwSEHO6y965Jx6BMrVqKARjp7v23uBziLc,17068
70
+ zenml/config/docker_settings.py,sha256=WhoocDGn9cegEd4mKb_oo7pZ87sXZ0NvqkOVnWK8CY4,17985
71
71
  zenml/config/global_config.py,sha256=ZD3WodfcWMBJZOl1FNn3ztzwilGDDv9EKdHClLqSO8s,29562
72
72
  zenml/config/pipeline_configurations.py,sha256=7trCbElpqGGgawii2FrdLW8fKaAWCR8jACkNqdG_vcQ,3983
73
73
  zenml/config/pipeline_run_configuration.py,sha256=Y9C5mVUH-w4gc1w1PCQFpjEmfBBpSMvb8riA_sL78hY,2311
@@ -77,7 +77,7 @@ zenml/config/retry_config.py,sha256=4UH1xqw0G6fSEbXSNKfmiFEkwadxQef9BGMe3JBm6NI,
77
77
  zenml/config/schedule.py,sha256=-83j99U9OyiG7E322XWA7QvuLSwQzF21whwpeiF0b30,5348
78
78
  zenml/config/secret_reference_mixin.py,sha256=YvY68MTd1gE23IVprf0BLkNn62hoxcvb5nqGgc8jMkU,5871
79
79
  zenml/config/secrets_store_config.py,sha256=y05zqyQhr_DGrs3IfBGa_FRoZ043hSYRT5wzrx-zHTU,2818
80
- zenml/config/server_config.py,sha256=or-LKYFl-9yE3MEq_yPa5aCCu3d5_f0tL9MqPtb_3-c,31852
80
+ zenml/config/server_config.py,sha256=DYYQ10HddkvlCWobBpwpg8ggO6imMZCnfFxzqlkQg_U,32336
81
81
  zenml/config/settings_resolver.py,sha256=PR9BRm_x1dy7nVKa9UqpeFdck8IEATSW6aWT8FKd-DI,4278
82
82
  zenml/config/source.py,sha256=RzUw8lin8QztUjz-AdoCzVM5Om_cSSPuroaPx-qAO4w,8226
83
83
  zenml/config/step_configurations.py,sha256=vjGtCoHneUJeaMkTiuzl7TTPBO94MefxNlFIAL1l5rU,13038
@@ -85,7 +85,7 @@ zenml/config/step_run_info.py,sha256=KiVRSTtKmZ1GbvseDTap2imr7XwMHD3jSFVpyLNEK1I
85
85
  zenml/config/store_config.py,sha256=Cla5p5dTB6nNlo8_OZDs9hod5hspi64vxwtZj882XgU,3559
86
86
  zenml/config/strict_base_model.py,sha256=t_ULrtJF2eW7TgyYBRobl1fscwwIZXATYky8ER97ev4,860
87
87
  zenml/console.py,sha256=hj_KerPQKwnyKACj0ehSqUQX0mGVCJBKE1QvCt6ik3A,1160
88
- zenml/constants.py,sha256=nLjJUhBqFHWHlGKbZZw-_tn7x99y6q4vmk5LOe7GhJI,16597
88
+ zenml/constants.py,sha256=O8b67hvsObx42r2REHA_X4XqDwqm_Hcsq7hv6q96HDs,16650
89
89
  zenml/container_registries/__init__.py,sha256=ZSPbBIOnzhg88kQSpYgKe_POLuru14m629665-kAVAA,2200
90
90
  zenml/container_registries/azure_container_registry.py,sha256=t1sfDa94Vzbyqtb1iPFNutJ2EXV5_p9CUNITasoiQ70,2667
91
91
  zenml/container_registries/base_container_registry.py,sha256=-9RIkD6oXNPaU59R3PB_PtyCqsFoLPLSn5xYZmEmzbc,8915
@@ -100,8 +100,8 @@ zenml/entrypoints/base_entrypoint_configuration.py,sha256=t7sU2MEEPVomX9dZCpPhk1
100
100
  zenml/entrypoints/entrypoint.py,sha256=XNgXBCMKoidmP0_AYgMpqo-neG8Y8jG0rj43ofTDZ9E,2033
101
101
  zenml/entrypoints/pipeline_entrypoint_configuration.py,sha256=To-vTP29qAE36ndJDF1fRw9wL2Nk2bsBuO-ayAwvSmo,1646
102
102
  zenml/entrypoints/step_entrypoint_configuration.py,sha256=fJuTvJnGuhKc60CH1VMQL5EHomGXkYZulv6pVgd9M6w,7316
103
- zenml/enums.py,sha256=m18yol20jS9JNhuf7UPBYUsgNRCtbyCaWPNpeEAJeXY,11199
104
- zenml/environment.py,sha256=UtriBwD0ylEK1cmSjEkDTlUUXBifBEukAn6gEdcnv4M,11827
103
+ zenml/enums.py,sha256=xXp6VkaY03NlGKEBs9Bf7oVfUrtESECmhkQYAaKRS5g,11239
104
+ zenml/environment.py,sha256=_iS9o4rTxRCjLocSw_LEe9pZ9jfyMhXBY47LIUF3VCI,12255
105
105
  zenml/event_hub/__init__.py,sha256=-fD9mPOslf4J-_XFBPp5gYlPz7-6ZaAKHa5jxf_nyAo,757
106
106
  zenml/event_hub/base_event_hub.py,sha256=PqKrnvOye0UUS3s09zGgjI5dtj0IwzEBDbavA_PgfZQ,6579
107
107
  zenml/event_hub/event_hub.py,sha256=e1eCRB1qAabOFIx2OJCAoNqN1QQIW0CSTukiWuGMOi8,6502
@@ -253,7 +253,7 @@ zenml/integrations/feast/feature_stores/__init__.py,sha256=Wi3NBBBPJg6CjgtxmBjoU
253
253
  zenml/integrations/feast/feature_stores/feast_feature_store.py,sha256=jV6WznuKT3y1aikI3OEwoI8r_l8bEu5waX0LKePPuU8,5880
254
254
  zenml/integrations/feast/flavors/__init__.py,sha256=gbCZ4tKgLZSI4-gzOCR2xihiPNmpe-lMUxwvMrhYL-w,858
255
255
  zenml/integrations/feast/flavors/feast_feature_store_flavor.py,sha256=E0k2iwgNti8lOVr9W8n2nTVQQf2wSeJWeaQD1vwAIbU,3060
256
- zenml/integrations/gcp/__init__.py,sha256=4bl_mutvdwXw-ZVdWdTHi3spTRH1JRCtBNPFxq5c74k,2957
256
+ zenml/integrations/gcp/__init__.py,sha256=9o7TXcQlNebImis821VXcOVFxKau86mbQ7H8d8e6mIw,2984
257
257
  zenml/integrations/gcp/artifact_stores/__init__.py,sha256=zYQkZBI4-COGX-E0NS7G-hLT88wbQBnYY6Oh1gruSRs,798
258
258
  zenml/integrations/gcp/artifact_stores/gcp_artifact_store.py,sha256=XfSIJ4HtsZvaUrUtzXvUp7QHr3WbgVDNsz7_q1h-DCo,10988
259
259
  zenml/integrations/gcp/constants.py,sha256=ZBQS_ZEjerUrJq-hH3UusgZAvB45FLgxNv11TSt3qhw,1334
@@ -271,7 +271,7 @@ zenml/integrations/gcp/image_builders/gcp_image_builder.py,sha256=5T6BXsHxLhvp1B
271
271
  zenml/integrations/gcp/orchestrators/__init__.py,sha256=6xLFJKZKQk73fHPF-XdpbQO87zjQNGTsNHjJjLfG_Kg,805
272
272
  zenml/integrations/gcp/orchestrators/vertex_orchestrator.py,sha256=qoMCr36buZUz0y4CyTFQde3RDslkaGLAG0FjXc0XEPU,42100
273
273
  zenml/integrations/gcp/service_connectors/__init__.py,sha256=fdydawaor8KAtMYvRZieiTuA1i5QATxXXgI-yV1lsn8,788
274
- zenml/integrations/gcp/service_connectors/gcp_service_connector.py,sha256=JGrFTKkQV4ZDn_yTEqX-AtBMraFasFgzLVws2mvhS64,94915
274
+ zenml/integrations/gcp/service_connectors/gcp_service_connector.py,sha256=9u-vEHbmSyN5IGwYI8v39TcFZg5ObgkxlbwSPz-e5zE,95018
275
275
  zenml/integrations/gcp/step_operators/__init__.py,sha256=iPkob2LtPIQ-OHszhbNz_ojhoovL6SprmTx37It4EJ8,808
276
276
  zenml/integrations/gcp/step_operators/vertex_step_operator.py,sha256=X8CCniyAo7NHiy3Mv_YSKQ4Hw3UYMXob6B3uWKsCJ-0,13567
277
277
  zenml/integrations/gcp/vertex_custom_job_parameters.py,sha256=B5RLkw7KDOi4ZfWHFnC6TGLphXMzToMjROxszCEAS9c,3676
@@ -317,7 +317,7 @@ zenml/integrations/hyperai/__init__.py,sha256=6ed5rgRgiRr2Ksi3DDFUaNQwPkCfE4BRSn
317
317
  zenml/integrations/hyperai/flavors/__init__.py,sha256=PUGBPmQ7y3H7QU2zAj7Ri0rrUBbOWnM_L59AIVWUYwU,800
318
318
  zenml/integrations/hyperai/flavors/hyperai_orchestrator_flavor.py,sha256=erjbYJ5E3qkifyGd_ArtbY1JdfyOEz4vgl9QrZK0R9Y,5578
319
319
  zenml/integrations/hyperai/orchestrators/__init__.py,sha256=kSYpMZPEWwNu2vxoOC6PeyQ9RLzsPAgTHxL35K36MiE,784
320
- zenml/integrations/hyperai/orchestrators/hyperai_orchestrator.py,sha256=2bKFlJE6HUU3oItkj3-siVxTKUpextN25t8aX7du4ZU,20286
320
+ zenml/integrations/hyperai/orchestrators/hyperai_orchestrator.py,sha256=6Z4GAXfkC2Ia4czQrgCy85xuocC9TYXTLmaNP8xxv_s,20287
321
321
  zenml/integrations/hyperai/service_connectors/__init__.py,sha256=oHuCNC09z5C7Wlb3vV1d4zJjftttHg364eoEBVRDOdo,803
322
322
  zenml/integrations/hyperai/service_connectors/hyperai_service_connector.py,sha256=7Ql5cVoSY3SE4j7uzeVi5TWuoKFDvsHFTn72k9_wPUY,13400
323
323
  zenml/integrations/integration.py,sha256=ljUK2GHy-LNqsdloOvf8mIfUQsqoi0yBJYocMyS-DX8,6879
@@ -337,7 +337,7 @@ zenml/integrations/kubernetes/flavors/__init__.py,sha256=a5gU45qCj3FkLwl_uVjlIkL
337
337
  zenml/integrations/kubernetes/flavors/kubernetes_orchestrator_flavor.py,sha256=JH_Kxfh1eoiSlz-OpixJ6itEsDiGPvbkA5_PffMTa54,10215
338
338
  zenml/integrations/kubernetes/flavors/kubernetes_step_operator_flavor.py,sha256=xFO7cSusji-mgbRrt4mU29gdyC9iEjEHKtomdFLp9mM,6265
339
339
  zenml/integrations/kubernetes/orchestrators/__init__.py,sha256=TJID3OTieZBox36WpQpzD0jdVRA_aZVcs_bNtfXS8ik,811
340
- zenml/integrations/kubernetes/orchestrators/kube_utils.py,sha256=8BVOpg40dGeRSkbzG_66xOksry5xpMVVmcXVDqb1bSs,18747
340
+ zenml/integrations/kubernetes/orchestrators/kube_utils.py,sha256=N66GH5ac22Xm_A3nr162kbFBhMeypSFaQjOQRHlGXIQ,18942
341
341
  zenml/integrations/kubernetes/orchestrators/kubernetes_orchestrator.py,sha256=qubV13fSGE_vo8wSzIfcp66McclLw-zDtmB4yCCaS08,25974
342
342
  zenml/integrations/kubernetes/orchestrators/kubernetes_orchestrator_entrypoint.py,sha256=Nd1YZsP6FZ4LTpoeFIM3YlFUugHX7F9axb7U9ZgGNdQ,12692
343
343
  zenml/integrations/kubernetes/orchestrators/kubernetes_orchestrator_entrypoint_configuration.py,sha256=KjHfQK9VQEQkkkM2i9w51AzqolgIU01M5dgb2YGamvY,2754
@@ -596,7 +596,7 @@ zenml/login/server_info.py,sha256=-_sK2L-curHdzUv1JDOwF6GoEeAXT5vFZN0J-5Ug4wU,16
596
596
  zenml/login/web_login.py,sha256=Kq_fA9UQEravB2DtAkMmNvDttk8xppnxV617tCYUl6U,9186
597
597
  zenml/materializers/__init__.py,sha256=C3lZaTmIFxwIPwCKF8oLQUkLaX2o7Dbj9hvYVFrSzt8,1758
598
598
  zenml/materializers/base_materializer.py,sha256=M4hwkw7PB0LskCE92r-S35011l7DlFemit-EuUCW3Nc,14002
599
- zenml/materializers/built_in_materializer.py,sha256=KjW3p8LWORZTjHqoGGC-Hlf-bdNSir3zsbRD2C_oQBQ,16951
599
+ zenml/materializers/built_in_materializer.py,sha256=HgcCHg3GpuQ4t-jecYcdg0kldUfLeUJvIpm8-wcS11I,17189
600
600
  zenml/materializers/cloudpickle_materializer.py,sha256=x8a6jEMTky6N2YVHiwrnGWSfVJUpiy-4kQsD2Aqj_E0,4837
601
601
  zenml/materializers/materializer_registry.py,sha256=ic-aWhJ2Ex9F_rml2dDVAxhRfW3nd71QMxzfTPP6BIM,4002
602
602
  zenml/materializers/numpy_materializer.py,sha256=OLcHF9Z0tAqQ_U8TraA0vGmZjHoT7eT_XevncIutt0M,1715
@@ -805,7 +805,7 @@ zenml/utils/yaml_utils.py,sha256=RvEr-N84wwG1Aq8rfdPAP7lev_SEaSQlqcL9fB_vHk8,584
805
805
  zenml/zen_server/__init__.py,sha256=WyltI9TzFW2mEHZVOs6alLWMCQrrZaFALtrQXs83STA,1355
806
806
  zenml/zen_server/auth.py,sha256=sJ_UAG58Qa0sUx8P1QkWmwp5zfAafFnm8WBHAOnPi4w,40070
807
807
  zenml/zen_server/cache.py,sha256=Tc4TSugmsU1bhThxlYfE8rv0KmltIX1CcVHgzrJ0Eus,6633
808
- zenml/zen_server/cloud_utils.py,sha256=qKMtHjnkVfAk_yxF2K9iC0TkMCjp8s0gdz_cEsbB4DE,10908
808
+ zenml/zen_server/cloud_utils.py,sha256=vKsjWadgPctZDtfeG9qJC-5gKOir_tZsglYMBkC8QqU,11874
809
809
  zenml/zen_server/csrf.py,sha256=Jsbi_IKriWCOuquSYTOEWqSXiGORJATIhR5Wrkm4XzQ,2684
810
810
  zenml/zen_server/dashboard/assets/404-_AtuLtaX.js,sha256=Y2jAo7K2feru-k7QcMxjnfUNkDrO7xv341YyKKpjZiU,1033
811
811
  zenml/zen_server/dashboard/assets/@radix-C7hRs6Kx.js,sha256=cNA9UX8LGrKUQubGrls3E3Wq1fCrlK51W14yh22FE_U,296700
@@ -1052,7 +1052,7 @@ zenml/zen_server/routers/server_endpoints.py,sha256=Aci-7TB5VZylmasEYc2tVpG7Npgt
1052
1052
  zenml/zen_server/routers/service_accounts_endpoints.py,sha256=q-vFzzYYorqaOPUnESQU06h2I-BaZvIyPxCAhQ4gLPw,12086
1053
1053
  zenml/zen_server/routers/service_connectors_endpoints.py,sha256=CkwhnNI49_64lqG7aWHtyE41iqKlY-lumiOrWU7aDU0,17570
1054
1054
  zenml/zen_server/routers/service_endpoints.py,sha256=37CY-22h4hTscEkBByKJqdHcOWsP2lTGAiY2WgBPB0w,5566
1055
- zenml/zen_server/routers/stack_components_endpoints.py,sha256=-3YE_8jZuAsQFwJxrZkDZnCwlSFh5dm_0_8vJ04a2xM,8234
1055
+ zenml/zen_server/routers/stack_components_endpoints.py,sha256=MBQ3iz29WN1n9T_BLpd2BtWxWoLIBP6QT7BfaAkGEMY,8580
1056
1056
  zenml/zen_server/routers/stack_deployment_endpoints.py,sha256=r7N4UaDAwj9ACYfdyUofRHpNPRrzWl-vmGoSErN9oYI,5381
1057
1057
  zenml/zen_server/routers/stacks_endpoints.py,sha256=ci-WZ774Q2T3-XN6cZARJ95wmRUW2-W1mh7gKlbNE54,7527
1058
1058
  zenml/zen_server/routers/steps_endpoints.py,sha256=KZpVsEsycLMmiEGnwT2EkmQvaTIIyL4QOUJBXMu7NSo,8239
@@ -1066,8 +1066,8 @@ zenml/zen_server/template_execution/__init__.py,sha256=79knXLKfegsvVSVSWecpqrepq
1066
1066
  zenml/zen_server/template_execution/runner_entrypoint_configuration.py,sha256=Y8aYJhqqs8Kv8I1q-dM1WemS5VBIfyoaaYH_YkzC7iY,1541
1067
1067
  zenml/zen_server/template_execution/utils.py,sha256=CiN7d8jCUTV5oKuoNF9Z_eDkitZrhL6V_Tz4CjU7em8,19410
1068
1068
  zenml/zen_server/template_execution/workload_manager_interface.py,sha256=CL9c7z8ajuZE01DaHmdCDCZmsroDcTarvN-nE8jv6qQ,2590
1069
- zenml/zen_server/utils.py,sha256=NU1IrwT8GoYNt_s43nzmr9ZeUjkPW7iEB2NmJq5NvP8,20074
1070
- zenml/zen_server/zen_server_api.py,sha256=1_YHOUmBulXuHWCxDLYtDCMxGze159UmU3f05643PSg,18182
1069
+ zenml/zen_server/utils.py,sha256=roJuND2KMVPue7fztVOKn4Tvoj1bvQ54TFpXgIa78Y0,20474
1070
+ zenml/zen_server/zen_server_api.py,sha256=Bhdn2AwGINEpKJiTL3QpC0EK8pvgGNK1IZNJr6E6ijE,23229
1071
1071
  zenml/zen_stores/__init__.py,sha256=6LTgH6XwDeDxKqVJ1JTfGhmS8II1NLopPloINGmdyI0,691
1072
1072
  zenml/zen_stores/base_zen_store.py,sha256=AplsW2NR-G9_CU54XvNTQJo4W0KJ5TJV22cjKW4n2BY,16124
1073
1073
  zenml/zen_stores/migrations/README.md,sha256=x04jsb6EOP6PBEGMQlDELiqKEham2O-iztAs9AylMFc,4898
@@ -1273,7 +1273,7 @@ zenml/zen_stores/migrations/versions/f49904a80aa7_increase_length_of_artifact_ta
1273
1273
  zenml/zen_stores/migrations/versions/f76a368a25a5_add_stack_description.py,sha256=u8fRomaasFeGhxvM2zU-Ab-AEpVsWm5zRcixxKFXdRw,904
1274
1274
  zenml/zen_stores/migrations/versions/fbd7f18ced1e_increase_step_run_field_lengths.py,sha256=kn-ng5EHe_mmLfffIFbz7T59z-to3oMx8III_4wOsz4,1956
1275
1275
  zenml/zen_stores/migrations/versions/ff538a321a92_migrate_onboarding_state.py,sha256=gsUFLJQ32_o9U35JCVqkqJVVk-zfq3yel25hXhzVFm4,3829
1276
- zenml/zen_stores/rest_zen_store.py,sha256=d5JHoGeO_FSg55MmVNIFYmwN1JdAnJ2QbE6R590fKZU,158599
1276
+ zenml/zen_stores/rest_zen_store.py,sha256=ljz1iIc8szDh1dlX1129Q817gcDBuzFzQnw6hkSMYCg,159628
1277
1277
  zenml/zen_stores/schemas/__init__.py,sha256=4EXqExiVyxdnGxhQ_Hz79mOdRuMD0LsGlw0PaP2Ef6o,4333
1278
1278
  zenml/zen_stores/schemas/action_schemas.py,sha256=sv2J2TP12MeyGPQR2JsOPIivbPQ5OImg64exYS7CZBM,6496
1279
1279
  zenml/zen_stores/schemas/api_key_schemas.py,sha256=0pK7b9HlJuQL3DuKT4eGjFb87tyd4x-E2VyxJLpRv3o,7459
@@ -1316,11 +1316,11 @@ zenml/zen_stores/secrets_stores/hashicorp_secrets_store.py,sha256=5err1a-TrV3SR5
1316
1316
  zenml/zen_stores/secrets_stores/secrets_store_interface.py,sha256=Q2Jbnt2Pp7NGlR-u1YBfRZV2g8su2Fd0ArBMdksAE-Q,2819
1317
1317
  zenml/zen_stores/secrets_stores/service_connector_secrets_store.py,sha256=S87ne23D08PAwtfRVlVnBn8R0ilTpEh6r8blauNV5WQ,6941
1318
1318
  zenml/zen_stores/secrets_stores/sql_secrets_store.py,sha256=LPFW757WCJLP1S8vrvjsrl2Tf1yo281xUTjSBsos4qk,8788
1319
- zenml/zen_stores/sql_zen_store.py,sha256=oUB9u3tMVmRrSxslC1oEN5kHQa8i7g4bNEpJKgsYmqY,441911
1319
+ zenml/zen_stores/sql_zen_store.py,sha256=jsx_1_MJZE4dnNXxj8bAMX8pT5JfoJliADSBF9n6kJc,444809
1320
1320
  zenml/zen_stores/template_utils.py,sha256=GbJ7LgGVYHSCKPEA8RNTxPoVTWqpC77F_lGzjJ4O1Fw,9220
1321
1321
  zenml/zen_stores/zen_store_interface.py,sha256=fF_uL_FplnvGvM5o3jOQ8i1zHXhuhKLL2n4nvIKSR7E,92090
1322
- zenml_nightly-0.82.1.dev20250522.dist-info/LICENSE,sha256=wbnfEnXnafPbqwANHkV6LUsPKOtdpsd-SNw37rogLtc,11359
1323
- zenml_nightly-0.82.1.dev20250522.dist-info/METADATA,sha256=ie1RvBnJCcu9pLlEoRM3C4vp9wyEFwJzpONvj5P70YQ,24317
1324
- zenml_nightly-0.82.1.dev20250522.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
1325
- zenml_nightly-0.82.1.dev20250522.dist-info/entry_points.txt,sha256=QK3ETQE0YswAM2mWypNMOv8TLtr7EjnqAFq1br_jEFE,43
1326
- zenml_nightly-0.82.1.dev20250522.dist-info/RECORD,,
1322
+ zenml_nightly-0.82.1.dev20250525.dist-info/LICENSE,sha256=wbnfEnXnafPbqwANHkV6LUsPKOtdpsd-SNw37rogLtc,11359
1323
+ zenml_nightly-0.82.1.dev20250525.dist-info/METADATA,sha256=pRZo-Bs06muo6ri5f4qKK8lmwYavxdTFZbsS0W3FRDw,24317
1324
+ zenml_nightly-0.82.1.dev20250525.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
1325
+ zenml_nightly-0.82.1.dev20250525.dist-info/entry_points.txt,sha256=QK3ETQE0YswAM2mWypNMOv8TLtr7EjnqAFq1br_jEFE,43
1326
+ zenml_nightly-0.82.1.dev20250525.dist-info/RECORD,,