zenml-nightly 0.83.1.dev20250706__py3-none-any.whl → 0.83.1.dev20250708__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 +1 -1
- zenml/integrations/kubernetes/orchestrators/kubernetes_orchestrator_entrypoint.py +296 -276
- zenml/integrations/kubernetes/pod_settings.py +37 -0
- zenml/integrations/kubernetes/serialization_utils.py +2 -1
- zenml/logging/step_logging.py +81 -0
- zenml/models/v2/core/logs.py +14 -1
- zenml/models/v2/core/pipeline_run.py +16 -0
- zenml/orchestrators/step_launcher.py +1 -0
- zenml/pipelines/pipeline_definition.py +1 -0
- zenml/zen_server/routers/runs_endpoints.py +26 -17
- zenml/zen_stores/migrations/versions/85289fea86ff_adding_source_to_logs.py +68 -0
- zenml/zen_stores/schemas/logs_schemas.py +11 -2
- zenml/zen_stores/schemas/pipeline_run_schemas.py +12 -3
- zenml/zen_stores/sql_zen_store.py +81 -21
- {zenml_nightly-0.83.1.dev20250706.dist-info → zenml_nightly-0.83.1.dev20250708.dist-info}/METADATA +1 -1
- {zenml_nightly-0.83.1.dev20250706.dist-info → zenml_nightly-0.83.1.dev20250708.dist-info}/RECORD +19 -18
- {zenml_nightly-0.83.1.dev20250706.dist-info → zenml_nightly-0.83.1.dev20250708.dist-info}/LICENSE +0 -0
- {zenml_nightly-0.83.1.dev20250706.dist-info → zenml_nightly-0.83.1.dev20250708.dist-info}/WHEEL +0 -0
- {zenml_nightly-0.83.1.dev20250706.dist-info → zenml_nightly-0.83.1.dev20250708.dist-info}/entry_points.txt +0 -0
@@ -19,6 +19,36 @@ from pydantic import field_validator
|
|
19
19
|
|
20
20
|
from zenml.config.base_settings import BaseSettings
|
21
21
|
from zenml.integrations.kubernetes import serialization_utils
|
22
|
+
from zenml.logger import get_logger
|
23
|
+
|
24
|
+
logger = get_logger(__name__)
|
25
|
+
|
26
|
+
|
27
|
+
_pod_settings_logged_warnings = []
|
28
|
+
|
29
|
+
|
30
|
+
def warn_if_invalid_model_data(data: Any, class_name: str) -> None:
|
31
|
+
"""Validates the data of a Kubernetes model.
|
32
|
+
|
33
|
+
Args:
|
34
|
+
data: The data to validate.
|
35
|
+
class_name: Name of the class of the model.
|
36
|
+
"""
|
37
|
+
if not isinstance(data, dict):
|
38
|
+
return
|
39
|
+
|
40
|
+
try:
|
41
|
+
serialization_utils.deserialize_kubernetes_model(data, class_name)
|
42
|
+
except KeyError as e:
|
43
|
+
if str(e) not in _pod_settings_logged_warnings:
|
44
|
+
_pod_settings_logged_warnings.append(str(e))
|
45
|
+
logger.warning(
|
46
|
+
"Invalid data for Kubernetes model class `%s`: %s. "
|
47
|
+
"Hint: Kubernetes expects attribute names in CamelCase, not "
|
48
|
+
"snake_case.",
|
49
|
+
class_name,
|
50
|
+
e,
|
51
|
+
)
|
22
52
|
|
23
53
|
|
24
54
|
class KubernetesPodSettings(BaseSettings):
|
@@ -77,6 +107,7 @@ class KubernetesPodSettings(BaseSettings):
|
|
77
107
|
serialization_utils.serialize_kubernetes_model(element)
|
78
108
|
)
|
79
109
|
else:
|
110
|
+
warn_if_invalid_model_data(element, "V1Volume")
|
80
111
|
result.append(element)
|
81
112
|
|
82
113
|
return result
|
@@ -101,6 +132,7 @@ class KubernetesPodSettings(BaseSettings):
|
|
101
132
|
serialization_utils.serialize_kubernetes_model(element)
|
102
133
|
)
|
103
134
|
else:
|
135
|
+
warn_if_invalid_model_data(element, "V1VolumeMount")
|
104
136
|
result.append(element)
|
105
137
|
|
106
138
|
return result
|
@@ -121,6 +153,7 @@ class KubernetesPodSettings(BaseSettings):
|
|
121
153
|
if isinstance(value, V1Affinity):
|
122
154
|
return serialization_utils.serialize_kubernetes_model(value)
|
123
155
|
else:
|
156
|
+
warn_if_invalid_model_data(value, "V1Affinity")
|
124
157
|
return value
|
125
158
|
|
126
159
|
@field_validator("tolerations", mode="before")
|
@@ -143,6 +176,7 @@ class KubernetesPodSettings(BaseSettings):
|
|
143
176
|
serialization_utils.serialize_kubernetes_model(element)
|
144
177
|
)
|
145
178
|
else:
|
179
|
+
warn_if_invalid_model_data(element, "V1Toleration")
|
146
180
|
result.append(element)
|
147
181
|
|
148
182
|
return result
|
@@ -163,6 +197,7 @@ class KubernetesPodSettings(BaseSettings):
|
|
163
197
|
if isinstance(value, V1ResourceRequirements):
|
164
198
|
return serialization_utils.serialize_kubernetes_model(value)
|
165
199
|
else:
|
200
|
+
warn_if_invalid_model_data(value, "V1ResourceRequirements")
|
166
201
|
return value
|
167
202
|
|
168
203
|
@field_validator("env", mode="before")
|
@@ -185,6 +220,7 @@ class KubernetesPodSettings(BaseSettings):
|
|
185
220
|
serialization_utils.serialize_kubernetes_model(element)
|
186
221
|
)
|
187
222
|
else:
|
223
|
+
warn_if_invalid_model_data(element, "V1EnvVar")
|
188
224
|
result.append(element)
|
189
225
|
|
190
226
|
return result
|
@@ -209,6 +245,7 @@ class KubernetesPodSettings(BaseSettings):
|
|
209
245
|
serialization_utils.serialize_kubernetes_model(element)
|
210
246
|
)
|
211
247
|
else:
|
248
|
+
warn_if_invalid_model_data(element, "V1EnvFromSource")
|
212
249
|
result.append(element)
|
213
250
|
|
214
251
|
return result
|
@@ -117,7 +117,8 @@ def deserialize_kubernetes_model(data: Dict[str, Any], class_name: str) -> Any:
|
|
117
117
|
if key not in attribute_mapping:
|
118
118
|
raise KeyError(
|
119
119
|
f"Got value for attribute {key} which is not one of the "
|
120
|
-
f"available attributes {
|
120
|
+
f"available attributes for class {class_name}: "
|
121
|
+
f"{set(attribute_mapping)}."
|
121
122
|
)
|
122
123
|
|
123
124
|
attribute_name = attribute_mapping[key]
|
zenml/logging/step_logging.py
CHANGED
@@ -18,6 +18,7 @@ import os
|
|
18
18
|
import re
|
19
19
|
import sys
|
20
20
|
import time
|
21
|
+
from contextlib import nullcontext
|
21
22
|
from contextvars import ContextVar
|
22
23
|
from types import TracebackType
|
23
24
|
from typing import Any, Callable, List, Optional, Type, Union
|
@@ -30,7 +31,9 @@ from zenml.artifacts.utils import (
|
|
30
31
|
_load_file_from_artifact_store,
|
31
32
|
_strip_timestamp_from_multiline_string,
|
32
33
|
)
|
34
|
+
from zenml.client import Client
|
33
35
|
from zenml.constants import (
|
36
|
+
ENV_ZENML_DISABLE_PIPELINE_LOGS_STORAGE,
|
34
37
|
ENV_ZENML_DISABLE_STEP_NAMES_IN_LOGS,
|
35
38
|
handle_bool_env_var,
|
36
39
|
)
|
@@ -41,6 +44,11 @@ from zenml.logging import (
|
|
41
44
|
STEP_LOGS_STORAGE_MAX_MESSAGES,
|
42
45
|
STEP_LOGS_STORAGE_MERGE_INTERVAL_SECONDS,
|
43
46
|
)
|
47
|
+
from zenml.models import (
|
48
|
+
LogsRequest,
|
49
|
+
PipelineDeploymentResponse,
|
50
|
+
PipelineRunUpdate,
|
51
|
+
)
|
44
52
|
from zenml.utils.time_utils import utc_now
|
45
53
|
from zenml.zen_stores.base_zen_store import BaseZenStore
|
46
54
|
|
@@ -584,3 +592,76 @@ class PipelineLogsStorageContext:
|
|
584
592
|
return output
|
585
593
|
|
586
594
|
return wrapped_flush
|
595
|
+
|
596
|
+
|
597
|
+
def setup_orchestrator_logging(
|
598
|
+
run_id: str, deployment: "PipelineDeploymentResponse"
|
599
|
+
) -> Any:
|
600
|
+
"""Set up logging for an orchestrator environment.
|
601
|
+
|
602
|
+
This function can be reused by different orchestrators to set up
|
603
|
+
consistent logging behavior.
|
604
|
+
|
605
|
+
Args:
|
606
|
+
run_id: The pipeline run ID.
|
607
|
+
deployment: The deployment of the pipeline run.
|
608
|
+
|
609
|
+
Returns:
|
610
|
+
The logs context (PipelineLogsStorageContext)
|
611
|
+
"""
|
612
|
+
try:
|
613
|
+
step_logging_enabled = True
|
614
|
+
|
615
|
+
# Check whether logging is enabled
|
616
|
+
if handle_bool_env_var(ENV_ZENML_DISABLE_PIPELINE_LOGS_STORAGE, False):
|
617
|
+
step_logging_enabled = False
|
618
|
+
else:
|
619
|
+
if (
|
620
|
+
deployment.pipeline_configuration.enable_pipeline_logs
|
621
|
+
is not None
|
622
|
+
):
|
623
|
+
step_logging_enabled = (
|
624
|
+
deployment.pipeline_configuration.enable_pipeline_logs
|
625
|
+
)
|
626
|
+
|
627
|
+
if not step_logging_enabled:
|
628
|
+
return nullcontext()
|
629
|
+
|
630
|
+
# Fetch the active stack
|
631
|
+
client = Client()
|
632
|
+
active_stack = client.active_stack
|
633
|
+
|
634
|
+
# Configure the logs
|
635
|
+
logs_uri = prepare_logs_uri(
|
636
|
+
artifact_store=active_stack.artifact_store,
|
637
|
+
)
|
638
|
+
|
639
|
+
logs_context = PipelineLogsStorageContext(
|
640
|
+
logs_uri=logs_uri,
|
641
|
+
artifact_store=active_stack.artifact_store,
|
642
|
+
prepend_step_name=False,
|
643
|
+
)
|
644
|
+
|
645
|
+
logs_model = LogsRequest(
|
646
|
+
uri=logs_uri,
|
647
|
+
source="orchestrator",
|
648
|
+
artifact_store_id=active_stack.artifact_store.id,
|
649
|
+
)
|
650
|
+
|
651
|
+
# Add orchestrator logs to the pipeline run
|
652
|
+
try:
|
653
|
+
run_update = PipelineRunUpdate(add_logs=[logs_model])
|
654
|
+
client.zen_store.update_run(
|
655
|
+
run_id=UUID(run_id), run_update=run_update
|
656
|
+
)
|
657
|
+
except Exception as e:
|
658
|
+
logger.error(
|
659
|
+
f"Failed to add orchestrator logs to the run {run_id}: {e}"
|
660
|
+
)
|
661
|
+
raise e
|
662
|
+
return logs_context
|
663
|
+
except Exception as e:
|
664
|
+
logger.error(
|
665
|
+
f"Failed to setup orchestrator logging for run {run_id}: {e}"
|
666
|
+
)
|
667
|
+
return nullcontext()
|
zenml/models/v2/core/logs.py
CHANGED
@@ -34,7 +34,7 @@ class LogsRequest(BaseRequest):
|
|
34
34
|
"""Request model for logs."""
|
35
35
|
|
36
36
|
uri: str = Field(title="The uri of the logs file")
|
37
|
-
|
37
|
+
source: str = Field(title="The source of the logs file")
|
38
38
|
artifact_store_id: UUID = Field(
|
39
39
|
title="The artifact store ID to associate the logs with.",
|
40
40
|
)
|
@@ -75,6 +75,10 @@ class LogsResponseBody(BaseDatedResponseBody):
|
|
75
75
|
title="The uri of the logs file",
|
76
76
|
max_length=TEXT_FIELD_MAX_LENGTH,
|
77
77
|
)
|
78
|
+
source: str = Field(
|
79
|
+
title="The source of the logs file",
|
80
|
+
max_length=TEXT_FIELD_MAX_LENGTH,
|
81
|
+
)
|
78
82
|
|
79
83
|
|
80
84
|
class LogsResponseMetadata(BaseResponseMetadata):
|
@@ -126,6 +130,15 @@ class LogsResponse(
|
|
126
130
|
"""
|
127
131
|
return self.get_body().uri
|
128
132
|
|
133
|
+
@property
|
134
|
+
def source(self) -> str:
|
135
|
+
"""The `source` property.
|
136
|
+
|
137
|
+
Returns:
|
138
|
+
the value of the property.
|
139
|
+
"""
|
140
|
+
return self.get_body().source
|
141
|
+
|
129
142
|
@property
|
130
143
|
def step_run_id(self) -> Optional[UUID]:
|
131
144
|
"""The `step_run_id` property.
|
@@ -153,6 +153,9 @@ class PipelineRunUpdate(BaseUpdate):
|
|
153
153
|
remove_tags: Optional[List[str]] = Field(
|
154
154
|
default=None, title="Tags to remove from the pipeline run."
|
155
155
|
)
|
156
|
+
add_logs: Optional[List[LogsRequest]] = Field(
|
157
|
+
default=None, title="New logs to add to the pipeline run."
|
158
|
+
)
|
156
159
|
|
157
160
|
model_config = ConfigDict(protected_namespaces=())
|
158
161
|
|
@@ -265,6 +268,10 @@ class PipelineRunResponseResources(ProjectScopedResponseResources):
|
|
265
268
|
title="Logs associated with this pipeline run.",
|
266
269
|
default=None,
|
267
270
|
)
|
271
|
+
log_collection: Optional[List["LogsResponse"]] = Field(
|
272
|
+
title="Logs associated with this pipeline run.",
|
273
|
+
default=None,
|
274
|
+
)
|
268
275
|
|
269
276
|
# TODO: In Pydantic v2, the `model_` is a protected namespaces for all
|
270
277
|
# fields defined under base models. If not handled, this raises a warning.
|
@@ -601,6 +608,15 @@ class PipelineRunResponse(
|
|
601
608
|
"""
|
602
609
|
return self.get_resources().logs
|
603
610
|
|
611
|
+
@property
|
612
|
+
def log_collection(self) -> Optional[List["LogsResponse"]]:
|
613
|
+
"""The `log_collection` property.
|
614
|
+
|
615
|
+
Returns:
|
616
|
+
the value of the property.
|
617
|
+
"""
|
618
|
+
return self.get_resources().log_collection
|
619
|
+
|
604
620
|
|
605
621
|
# ------------------ Filter Model ------------------
|
606
622
|
|
@@ -436,22 +436,24 @@ def stop_run(
|
|
436
436
|
@async_fastapi_endpoint_wrapper
|
437
437
|
def run_logs(
|
438
438
|
run_id: UUID,
|
439
|
+
source: str,
|
439
440
|
offset: int = 0,
|
440
441
|
length: int = 1024 * 1024 * 16, # Default to 16MiB of data
|
441
442
|
_: AuthContext = Security(authorize),
|
442
443
|
) -> str:
|
443
|
-
"""Get pipeline run logs.
|
444
|
+
"""Get pipeline run logs for a specific source.
|
444
445
|
|
445
446
|
Args:
|
446
447
|
run_id: ID of the pipeline run.
|
448
|
+
source: Required source to get logs for.
|
447
449
|
offset: The offset from which to start reading.
|
448
450
|
length: The amount of bytes that should be read.
|
449
451
|
|
450
452
|
Returns:
|
451
|
-
|
453
|
+
Logs for the specified source.
|
452
454
|
|
453
455
|
Raises:
|
454
|
-
KeyError: If no logs are
|
456
|
+
KeyError: If no logs are found for the specified source.
|
455
457
|
"""
|
456
458
|
store = zen_store()
|
457
459
|
|
@@ -461,19 +463,26 @@ def run_logs(
|
|
461
463
|
hydrate=True,
|
462
464
|
)
|
463
465
|
|
464
|
-
|
466
|
+
# Handle runner logs from workload manager
|
467
|
+
if run.deployment_id and source == "runner":
|
465
468
|
deployment = store.get_deployment(run.deployment_id)
|
466
469
|
if deployment.template_id and server_config().workload_manager_enabled:
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
470
|
+
workload_logs = workload_manager().get_logs(
|
471
|
+
workload_id=deployment.id
|
472
|
+
)
|
473
|
+
return workload_logs
|
474
|
+
|
475
|
+
# Handle logs from log collection
|
476
|
+
if run.log_collection:
|
477
|
+
for log_entry in run.log_collection:
|
478
|
+
if log_entry.source == source:
|
479
|
+
return fetch_logs(
|
480
|
+
zen_store=store,
|
481
|
+
artifact_store_id=log_entry.artifact_store_id,
|
482
|
+
logs_uri=log_entry.uri,
|
483
|
+
offset=offset,
|
484
|
+
length=length,
|
485
|
+
)
|
486
|
+
|
487
|
+
# If no logs found for the specified source, raise an error
|
488
|
+
raise KeyError(f"No logs found for source '{source}' in run {run_id}")
|
@@ -0,0 +1,68 @@
|
|
1
|
+
"""adding-source-to-logs [85289fea86ff].
|
2
|
+
|
3
|
+
Revision ID: 85289fea86ff
|
4
|
+
Revises: 5bb25e95849c
|
5
|
+
Create Date: 2025-06-30 18:18:24.539265
|
6
|
+
|
7
|
+
"""
|
8
|
+
|
9
|
+
import sqlalchemy as sa
|
10
|
+
from alembic import op
|
11
|
+
|
12
|
+
# revision identifiers, used by Alembic.
|
13
|
+
revision = "85289fea86ff"
|
14
|
+
down_revision = "5bb25e95849c"
|
15
|
+
branch_labels = None
|
16
|
+
depends_on = None
|
17
|
+
|
18
|
+
|
19
|
+
def upgrade() -> None:
|
20
|
+
"""Upgrade database schema and/or data, creating a new revision."""
|
21
|
+
# Add the source column as nullable first
|
22
|
+
with op.batch_alter_table("logs", schema=None) as batch_op:
|
23
|
+
batch_op.add_column(
|
24
|
+
sa.Column("source", sa.VARCHAR(255), nullable=True)
|
25
|
+
)
|
26
|
+
|
27
|
+
# Populate the source field based on existing data
|
28
|
+
connection = op.get_bind()
|
29
|
+
|
30
|
+
# Set source to "step" where step_run_id is present
|
31
|
+
connection.execute(
|
32
|
+
sa.text("""
|
33
|
+
UPDATE logs
|
34
|
+
SET source = 'step'
|
35
|
+
WHERE step_run_id IS NOT NULL
|
36
|
+
""")
|
37
|
+
)
|
38
|
+
|
39
|
+
# Set source to "client" for all other cases (where step_run_id is null)
|
40
|
+
connection.execute(
|
41
|
+
sa.text("""
|
42
|
+
UPDATE logs
|
43
|
+
SET source = 'client'
|
44
|
+
WHERE step_run_id IS NULL
|
45
|
+
""")
|
46
|
+
)
|
47
|
+
|
48
|
+
# Make the source column not nullable
|
49
|
+
with op.batch_alter_table("logs", schema=None) as batch_op:
|
50
|
+
batch_op.alter_column(
|
51
|
+
"source",
|
52
|
+
existing_type=sa.VARCHAR(255),
|
53
|
+
nullable=False,
|
54
|
+
)
|
55
|
+
# Add unique constraint: source is unique for each combination of pipeline_run_id and step_run_id
|
56
|
+
batch_op.create_unique_constraint(
|
57
|
+
"unique_source_per_run_and_step",
|
58
|
+
["source", "pipeline_run_id", "step_run_id"],
|
59
|
+
)
|
60
|
+
|
61
|
+
|
62
|
+
def downgrade() -> None:
|
63
|
+
"""Downgrade database schema and/or data back to the previous revision."""
|
64
|
+
with op.batch_alter_table("logs", schema=None) as batch_op:
|
65
|
+
batch_op.drop_constraint(
|
66
|
+
"unique_source_per_run_and_step", type_="unique"
|
67
|
+
)
|
68
|
+
batch_op.drop_column("source")
|
@@ -16,7 +16,7 @@
|
|
16
16
|
from typing import Any, Optional
|
17
17
|
from uuid import UUID
|
18
18
|
|
19
|
-
from sqlalchemy import TEXT, Column
|
19
|
+
from sqlalchemy import TEXT, VARCHAR, Column, UniqueConstraint
|
20
20
|
from sqlmodel import Field, Relationship
|
21
21
|
|
22
22
|
from zenml.models import (
|
@@ -35,9 +35,18 @@ class LogsSchema(BaseSchema, table=True):
|
|
35
35
|
"""SQL Model for logs."""
|
36
36
|
|
37
37
|
__tablename__ = "logs"
|
38
|
+
__table_args__ = (
|
39
|
+
UniqueConstraint(
|
40
|
+
"source",
|
41
|
+
"pipeline_run_id",
|
42
|
+
"step_run_id",
|
43
|
+
name="unique_source_per_run_and_step",
|
44
|
+
),
|
45
|
+
)
|
38
46
|
|
39
47
|
# Fields
|
40
48
|
uri: str = Field(sa_column=Column(TEXT, nullable=False))
|
49
|
+
source: str = Field(sa_column=Column(VARCHAR(255), nullable=False))
|
41
50
|
|
42
51
|
# Foreign Keys
|
43
52
|
pipeline_run_id: Optional[UUID] = build_foreign_key_field(
|
@@ -87,12 +96,12 @@ class LogsSchema(BaseSchema, table=True):
|
|
87
96
|
include_resources: Whether the resources will be filled.
|
88
97
|
**kwargs: Keyword arguments to allow schema specific logic
|
89
98
|
|
90
|
-
|
91
99
|
Returns:
|
92
100
|
The created `LogsResponse`.
|
93
101
|
"""
|
94
102
|
body = LogsResponseBody(
|
95
103
|
uri=self.uri,
|
104
|
+
source=self.source,
|
96
105
|
created=self.created,
|
97
106
|
updated=self.updated,
|
98
107
|
)
|
@@ -158,9 +158,9 @@ class PipelineRunSchema(NamedSchema, RunMetadataInterface, table=True):
|
|
158
158
|
overlaps="run_metadata",
|
159
159
|
),
|
160
160
|
)
|
161
|
-
logs:
|
161
|
+
logs: List["LogsSchema"] = Relationship(
|
162
162
|
back_populates="pipeline_run",
|
163
|
-
sa_relationship_kwargs={"cascade": "delete"
|
163
|
+
sa_relationship_kwargs={"cascade": "delete"},
|
164
164
|
)
|
165
165
|
step_runs: List["StepRunSchema"] = Relationship(
|
166
166
|
sa_relationship_kwargs={"cascade": "delete"},
|
@@ -531,13 +531,22 @@ class PipelineRunSchema(NamedSchema, RunMetadataInterface, table=True):
|
|
531
531
|
|
532
532
|
resources = None
|
533
533
|
if include_resources:
|
534
|
+
# Add the client logs as "logs" if they exist, for backwards compatibility
|
535
|
+
# TODO: This will be safe to remove in future releases (>0.84.0).
|
536
|
+
client_logs = [
|
537
|
+
log_entry
|
538
|
+
for log_entry in self.logs
|
539
|
+
if log_entry.source == "client"
|
540
|
+
]
|
541
|
+
|
534
542
|
resources = PipelineRunResponseResources(
|
535
543
|
user=self.user.to_model() if self.user else None,
|
536
544
|
model_version=self.model_version.to_model()
|
537
545
|
if self.model_version
|
538
546
|
else None,
|
539
547
|
tags=[tag.to_model() for tag in self.tags],
|
540
|
-
logs=
|
548
|
+
logs=client_logs[0].to_model() if client_logs else None,
|
549
|
+
log_collection=[log.to_model() for log in self.logs],
|
541
550
|
)
|
542
551
|
|
543
552
|
return PipelineRunResponse(
|
@@ -5677,7 +5677,9 @@ class SqlZenStore(BaseZenStore):
|
|
5677
5677
|
The created pipeline run.
|
5678
5678
|
|
5679
5679
|
Raises:
|
5680
|
-
EntityExistsError: If a run with the same name already exists
|
5680
|
+
EntityExistsError: If a run with the same name already exists or
|
5681
|
+
a log entry with the same source already exists within the
|
5682
|
+
scope of the same pipeline run.
|
5681
5683
|
"""
|
5682
5684
|
self._set_request_user_id(request_model=pipeline_run, session=session)
|
5683
5685
|
self._get_reference_schema_by_id(
|
@@ -5698,23 +5700,6 @@ class SqlZenStore(BaseZenStore):
|
|
5698
5700
|
|
5699
5701
|
session.add(new_run)
|
5700
5702
|
|
5701
|
-
# Add logs entry for the run if exists
|
5702
|
-
if pipeline_run.logs is not None:
|
5703
|
-
self._get_reference_schema_by_id(
|
5704
|
-
resource=pipeline_run,
|
5705
|
-
reference_schema=StackComponentSchema,
|
5706
|
-
reference_id=pipeline_run.logs.artifact_store_id,
|
5707
|
-
session=session,
|
5708
|
-
reference_type="logs artifact store",
|
5709
|
-
)
|
5710
|
-
|
5711
|
-
log_entry = LogsSchema(
|
5712
|
-
uri=pipeline_run.logs.uri,
|
5713
|
-
pipeline_run_id=new_run.id,
|
5714
|
-
artifact_store_id=pipeline_run.logs.artifact_store_id,
|
5715
|
-
)
|
5716
|
-
session.add(log_entry)
|
5717
|
-
|
5718
5703
|
try:
|
5719
5704
|
session.commit()
|
5720
5705
|
except IntegrityError:
|
@@ -5736,6 +5721,33 @@ class SqlZenStore(BaseZenStore):
|
|
5736
5721
|
"already exists."
|
5737
5722
|
)
|
5738
5723
|
|
5724
|
+
# Add logs entry for the run if exists
|
5725
|
+
if pipeline_run.logs is not None:
|
5726
|
+
self._get_reference_schema_by_id(
|
5727
|
+
resource=pipeline_run,
|
5728
|
+
reference_schema=StackComponentSchema,
|
5729
|
+
reference_id=pipeline_run.logs.artifact_store_id,
|
5730
|
+
session=session,
|
5731
|
+
reference_type="logs artifact store",
|
5732
|
+
)
|
5733
|
+
|
5734
|
+
log_entry = LogsSchema(
|
5735
|
+
uri=pipeline_run.logs.uri,
|
5736
|
+
source=pipeline_run.logs.source,
|
5737
|
+
pipeline_run_id=new_run.id,
|
5738
|
+
artifact_store_id=pipeline_run.logs.artifact_store_id,
|
5739
|
+
)
|
5740
|
+
try:
|
5741
|
+
session.add(log_entry)
|
5742
|
+
session.commit()
|
5743
|
+
except IntegrityError:
|
5744
|
+
session.rollback()
|
5745
|
+
raise EntityExistsError(
|
5746
|
+
"Unable to create log entry: A log entry with this "
|
5747
|
+
f"source '{pipeline_run.logs.source}' already exists "
|
5748
|
+
f"within the scope of the same pipeline run '{new_run.id}'."
|
5749
|
+
)
|
5750
|
+
|
5739
5751
|
if model_version_id := self._get_or_create_model_version_for_run(
|
5740
5752
|
new_run
|
5741
5753
|
):
|
@@ -6095,6 +6107,10 @@ class SqlZenStore(BaseZenStore):
|
|
6095
6107
|
|
6096
6108
|
Returns:
|
6097
6109
|
The updated pipeline run.
|
6110
|
+
|
6111
|
+
Raises:
|
6112
|
+
EntityExistsError: If a log entry with the same source already
|
6113
|
+
exists within the scope of the same pipeline run.
|
6098
6114
|
"""
|
6099
6115
|
with Session(self.engine) as session:
|
6100
6116
|
# Check if pipeline run with the given ID exists
|
@@ -6109,6 +6125,39 @@ class SqlZenStore(BaseZenStore):
|
|
6109
6125
|
session.commit()
|
6110
6126
|
session.refresh(existing_run)
|
6111
6127
|
|
6128
|
+
# Add logs if specified
|
6129
|
+
if run_update.add_logs:
|
6130
|
+
try:
|
6131
|
+
for log_request in run_update.add_logs:
|
6132
|
+
# Validate the artifact store exists
|
6133
|
+
self._get_reference_schema_by_id(
|
6134
|
+
resource=log_request,
|
6135
|
+
reference_schema=StackComponentSchema,
|
6136
|
+
reference_id=log_request.artifact_store_id,
|
6137
|
+
session=session,
|
6138
|
+
reference_type="logs artifact store",
|
6139
|
+
)
|
6140
|
+
|
6141
|
+
# Create the log entry
|
6142
|
+
log_entry = LogsSchema(
|
6143
|
+
uri=log_request.uri,
|
6144
|
+
source=log_request.source,
|
6145
|
+
pipeline_run_id=existing_run.id,
|
6146
|
+
artifact_store_id=log_request.artifact_store_id,
|
6147
|
+
)
|
6148
|
+
session.add(log_entry)
|
6149
|
+
|
6150
|
+
session.commit()
|
6151
|
+
except IntegrityError:
|
6152
|
+
session.rollback()
|
6153
|
+
raise EntityExistsError(
|
6154
|
+
"Unable to create log entry: One of the provided sources "
|
6155
|
+
f"({', '.join(log.source for log in run_update.add_logs)}) "
|
6156
|
+
"already exists within the scope of the same pipeline run "
|
6157
|
+
f"'{existing_run.id}'. Existing entry sources: "
|
6158
|
+
f"{', '.join(log.source for log in existing_run.logs)}"
|
6159
|
+
)
|
6160
|
+
|
6112
6161
|
self._attach_tags_to_resources(
|
6113
6162
|
tags=run_update.add_tags,
|
6114
6163
|
resources=existing_run,
|
@@ -8830,7 +8879,9 @@ class SqlZenStore(BaseZenStore):
|
|
8830
8879
|
The created step run.
|
8831
8880
|
|
8832
8881
|
Raises:
|
8833
|
-
EntityExistsError: if the step run already exists
|
8882
|
+
EntityExistsError: if the step run already exists or a log entry
|
8883
|
+
with the same source already exists within the scope of the
|
8884
|
+
same step.
|
8834
8885
|
IllegalOperationError: if the pipeline run is stopped or stopping.
|
8835
8886
|
"""
|
8836
8887
|
with Session(self.engine) as session:
|
@@ -8889,11 +8940,20 @@ class SqlZenStore(BaseZenStore):
|
|
8889
8940
|
|
8890
8941
|
log_entry = LogsSchema(
|
8891
8942
|
uri=step_run.logs.uri,
|
8943
|
+
source=step_run.logs.source,
|
8892
8944
|
step_run_id=step_schema.id,
|
8893
8945
|
artifact_store_id=step_run.logs.artifact_store_id,
|
8894
8946
|
)
|
8895
|
-
|
8896
|
-
|
8947
|
+
try:
|
8948
|
+
session.add(log_entry)
|
8949
|
+
session.commit()
|
8950
|
+
except IntegrityError:
|
8951
|
+
session.rollback()
|
8952
|
+
raise EntityExistsError(
|
8953
|
+
"Unable to create log entry: A log entry with this "
|
8954
|
+
f"source '{step_run.logs.source}' already exists "
|
8955
|
+
f"within the scope of the same step '{step_schema.id}'."
|
8956
|
+
)
|
8897
8957
|
# If cached, attach metadata of the original step
|
8898
8958
|
if (
|
8899
8959
|
step_run.status == ExecutionStatus.CACHED
|