dagster-cloud 1.8.2__py3-none-any.whl → 1.12.6__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.
- dagster_cloud/__init__.py +3 -3
- dagster_cloud/agent/__init__.py +4 -4
- dagster_cloud/agent/cli/__init__.py +56 -17
- dagster_cloud/agent/dagster_cloud_agent.py +360 -172
- dagster_cloud/agent/instrumentation/__init__.py +0 -0
- dagster_cloud/agent/instrumentation/constants.py +2 -0
- dagster_cloud/agent/instrumentation/run_launch.py +23 -0
- dagster_cloud/agent/instrumentation/schedule.py +34 -0
- dagster_cloud/agent/instrumentation/sensor.py +34 -0
- dagster_cloud/anomaly_detection/__init__.py +2 -2
- dagster_cloud/anomaly_detection/defs.py +17 -12
- dagster_cloud/anomaly_detection/types.py +3 -3
- dagster_cloud/api/dagster_cloud_api.py +209 -293
- dagster_cloud/auth/constants.py +21 -5
- dagster_cloud/batching/__init__.py +1 -0
- dagster_cloud/batching/batcher.py +210 -0
- dagster_cloud/dagster_insights/__init__.py +12 -6
- dagster_cloud/dagster_insights/bigquery/bigquery_utils.py +3 -2
- dagster_cloud/dagster_insights/bigquery/dbt_wrapper.py +39 -12
- dagster_cloud/dagster_insights/bigquery/insights_bigquery_resource.py +8 -6
- dagster_cloud/dagster_insights/insights_utils.py +18 -8
- dagster_cloud/dagster_insights/metrics_utils.py +12 -12
- dagster_cloud/dagster_insights/snowflake/dagster_snowflake_insights.py +5 -12
- dagster_cloud/dagster_insights/snowflake/dbt_wrapper.py +34 -8
- dagster_cloud/dagster_insights/snowflake/definitions.py +38 -12
- dagster_cloud/dagster_insights/snowflake/insights_snowflake_resource.py +11 -23
- dagster_cloud/definitions/__init__.py +0 -0
- dagster_cloud/definitions/job_selection.py +36 -0
- dagster_cloud/execution/cloud_run_launcher/k8s.py +1 -1
- dagster_cloud/execution/cloud_run_launcher/process.py +3 -3
- dagster_cloud/execution/monitoring/__init__.py +27 -33
- dagster_cloud/execution/utils/process.py +3 -3
- dagster_cloud/instance/__init__.py +125 -38
- dagster_cloud/instrumentation/__init__.py +32 -0
- dagster_cloud/metadata/source_code.py +13 -8
- dagster_cloud/metrics/__init__.py +0 -0
- dagster_cloud/metrics/tracer.py +59 -0
- dagster_cloud/opentelemetry/__init__.py +0 -0
- dagster_cloud/opentelemetry/config/__init__.py +73 -0
- dagster_cloud/opentelemetry/config/exporter.py +81 -0
- dagster_cloud/opentelemetry/config/log_record_processor.py +40 -0
- dagster_cloud/opentelemetry/config/logging_handler.py +14 -0
- dagster_cloud/opentelemetry/config/meter_provider.py +9 -0
- dagster_cloud/opentelemetry/config/metric_reader.py +39 -0
- dagster_cloud/opentelemetry/controller.py +319 -0
- dagster_cloud/opentelemetry/enum.py +58 -0
- dagster_cloud/opentelemetry/factories/__init__.py +1 -0
- dagster_cloud/opentelemetry/factories/logs.py +113 -0
- dagster_cloud/opentelemetry/factories/metrics.py +121 -0
- dagster_cloud/opentelemetry/metrics/__init__.py +0 -0
- dagster_cloud/opentelemetry/metrics/meter.py +140 -0
- dagster_cloud/opentelemetry/observers/__init__.py +0 -0
- dagster_cloud/opentelemetry/observers/dagster_exception_handler.py +40 -0
- dagster_cloud/opentelemetry/observers/execution_observer.py +178 -0
- dagster_cloud/pex/grpc/__generated__/multi_pex_api_pb2.pyi +175 -0
- dagster_cloud/pex/grpc/__init__.py +2 -2
- dagster_cloud/pex/grpc/client.py +4 -4
- dagster_cloud/pex/grpc/compile.py +2 -2
- dagster_cloud/pex/grpc/server/__init__.py +2 -2
- dagster_cloud/pex/grpc/server/cli/__init__.py +31 -19
- dagster_cloud/pex/grpc/server/manager.py +60 -42
- dagster_cloud/pex/grpc/server/registry.py +28 -21
- dagster_cloud/pex/grpc/server/server.py +23 -14
- dagster_cloud/pex/grpc/types.py +5 -5
- dagster_cloud/py.typed +0 -0
- dagster_cloud/secrets/__init__.py +1 -1
- dagster_cloud/secrets/loader.py +3 -3
- dagster_cloud/serverless/__init__.py +1 -1
- dagster_cloud/serverless/io_manager.py +36 -53
- dagster_cloud/storage/client.py +54 -17
- dagster_cloud/storage/compute_logs/__init__.py +3 -1
- dagster_cloud/storage/compute_logs/compute_log_manager.py +22 -17
- dagster_cloud/storage/defs_state/__init__.py +3 -0
- dagster_cloud/storage/defs_state/queries.py +15 -0
- dagster_cloud/storage/defs_state/storage.py +113 -0
- dagster_cloud/storage/event_logs/__init__.py +3 -1
- dagster_cloud/storage/event_logs/queries.py +102 -4
- dagster_cloud/storage/event_logs/storage.py +266 -73
- dagster_cloud/storage/event_logs/utils.py +88 -7
- dagster_cloud/storage/runs/__init__.py +1 -1
- dagster_cloud/storage/runs/queries.py +17 -2
- dagster_cloud/storage/runs/storage.py +88 -42
- dagster_cloud/storage/schedules/__init__.py +1 -1
- dagster_cloud/storage/schedules/storage.py +6 -8
- dagster_cloud/storage/tags.py +66 -1
- dagster_cloud/util/__init__.py +10 -12
- dagster_cloud/util/errors.py +49 -64
- dagster_cloud/version.py +1 -1
- dagster_cloud/workspace/config_schema/__init__.py +55 -13
- dagster_cloud/workspace/docker/__init__.py +76 -25
- dagster_cloud/workspace/docker/utils.py +1 -1
- dagster_cloud/workspace/ecs/__init__.py +1 -1
- dagster_cloud/workspace/ecs/client.py +51 -33
- dagster_cloud/workspace/ecs/launcher.py +76 -22
- dagster_cloud/workspace/ecs/run_launcher.py +3 -3
- dagster_cloud/workspace/ecs/utils.py +14 -5
- dagster_cloud/workspace/kubernetes/__init__.py +1 -1
- dagster_cloud/workspace/kubernetes/launcher.py +61 -29
- dagster_cloud/workspace/kubernetes/utils.py +34 -22
- dagster_cloud/workspace/user_code_launcher/__init__.py +5 -3
- dagster_cloud/workspace/user_code_launcher/process.py +16 -14
- dagster_cloud/workspace/user_code_launcher/user_code_launcher.py +552 -172
- dagster_cloud/workspace/user_code_launcher/utils.py +105 -1
- {dagster_cloud-1.8.2.dist-info → dagster_cloud-1.12.6.dist-info}/METADATA +48 -42
- dagster_cloud-1.12.6.dist-info/RECORD +134 -0
- {dagster_cloud-1.8.2.dist-info → dagster_cloud-1.12.6.dist-info}/WHEEL +1 -1
- dagster_cloud-1.8.2.dist-info/RECORD +0 -100
- {dagster_cloud-1.8.2.dist-info → dagster_cloud-1.12.6.dist-info}/top_level.txt +0 -0
|
@@ -1,19 +1,100 @@
|
|
|
1
|
-
|
|
1
|
+
import os
|
|
2
|
+
from typing import Optional
|
|
2
3
|
|
|
4
|
+
from dagster import DagsterEvent
|
|
5
|
+
from dagster._core.events import (
|
|
6
|
+
EngineEventData,
|
|
7
|
+
HookErroredData,
|
|
8
|
+
JobCanceledData,
|
|
9
|
+
JobFailureData,
|
|
10
|
+
StepFailureData,
|
|
11
|
+
StepRetryData,
|
|
12
|
+
)
|
|
3
13
|
from dagster._core.events.log import EventLogEntry
|
|
14
|
+
from dagster._utils.error import SerializableErrorInfo, truncate_serialized_error
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _get_error_character_size_limit() -> int:
|
|
18
|
+
return int(os.getenv("DAGSTER_CLOUD_MAXIMUM_EVENT_ERROR_SIZE", "500000"))
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _get_maximum_event_message_characters() -> int:
|
|
22
|
+
return int(os.getenv("DAGSTER_CLOUD_MAXIMUM_EVENT_MESSAGE_CHARACTERS", "50000"))
|
|
4
23
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
24
|
+
|
|
25
|
+
def _truncate_dagster_event_error(
|
|
26
|
+
error_info: Optional[SerializableErrorInfo],
|
|
27
|
+
truncations: list[str],
|
|
28
|
+
) -> Optional[SerializableErrorInfo]:
|
|
29
|
+
if not error_info:
|
|
30
|
+
return error_info
|
|
31
|
+
|
|
32
|
+
return truncate_serialized_error(
|
|
33
|
+
error_info,
|
|
34
|
+
_get_error_character_size_limit(),
|
|
35
|
+
max_depth=5,
|
|
36
|
+
truncations=truncations,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _truncate_dagster_event(
|
|
41
|
+
dagster_event: Optional[DagsterEvent],
|
|
42
|
+
truncations: list[str],
|
|
43
|
+
) -> Optional[DagsterEvent]:
|
|
44
|
+
if not dagster_event:
|
|
45
|
+
return dagster_event
|
|
46
|
+
event_specific_data = dagster_event.event_specific_data
|
|
47
|
+
|
|
48
|
+
if isinstance(event_specific_data, JobFailureData):
|
|
49
|
+
event_specific_data = event_specific_data._replace(
|
|
50
|
+
error=_truncate_dagster_event_error(
|
|
51
|
+
event_specific_data.error,
|
|
52
|
+
truncations,
|
|
53
|
+
),
|
|
54
|
+
first_step_failure_event=_truncate_dagster_event(
|
|
55
|
+
event_specific_data.first_step_failure_event,
|
|
56
|
+
truncations,
|
|
57
|
+
),
|
|
58
|
+
)
|
|
59
|
+
elif isinstance(
|
|
60
|
+
event_specific_data,
|
|
61
|
+
(JobCanceledData, EngineEventData, HookErroredData, StepFailureData, StepRetryData),
|
|
62
|
+
):
|
|
63
|
+
event_specific_data = event_specific_data._replace(
|
|
64
|
+
error=_truncate_dagster_event_error(
|
|
65
|
+
event_specific_data.error,
|
|
66
|
+
truncations,
|
|
67
|
+
),
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
return dagster_event._replace(event_specific_data=event_specific_data)
|
|
8
71
|
|
|
9
72
|
|
|
10
73
|
def truncate_event(
|
|
11
|
-
event: EventLogEntry,
|
|
74
|
+
event: EventLogEntry,
|
|
75
|
+
maximum_length=None,
|
|
76
|
+
truncations: Optional[list[str]] = None,
|
|
12
77
|
) -> EventLogEntry:
|
|
13
|
-
if
|
|
78
|
+
truncations = [] if truncations is None else truncations
|
|
79
|
+
|
|
80
|
+
if event.dagster_event:
|
|
81
|
+
event = event._replace(
|
|
82
|
+
dagster_event=_truncate_dagster_event(
|
|
83
|
+
event.dagster_event,
|
|
84
|
+
truncations,
|
|
85
|
+
)
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
maximum_length = (
|
|
89
|
+
maximum_length if maximum_length is not None else _get_maximum_event_message_characters()
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
len_usr_msg = len(event.user_message)
|
|
93
|
+
if len_usr_msg > maximum_length:
|
|
94
|
+
truncations.append(f"user_message {len_usr_msg} to {maximum_length}")
|
|
14
95
|
return event._replace(
|
|
15
96
|
user_message=(
|
|
16
|
-
f"[TRUNCATED from {
|
|
97
|
+
f"[TRUNCATED from {len_usr_msg} characters to"
|
|
17
98
|
f" {maximum_length}]"
|
|
18
99
|
f" {event.user_message[:maximum_length]} [TRUNCATED]"
|
|
19
100
|
),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
from .storage import GraphQLRunStorage as GraphQLRunStorage
|
|
1
|
+
from dagster_cloud.storage.runs.storage import GraphQLRunStorage as GraphQLRunStorage
|
|
@@ -43,6 +43,21 @@ ADD_RUN_MUTATION = (
|
|
|
43
43
|
"""
|
|
44
44
|
)
|
|
45
45
|
|
|
46
|
+
ADD_HISTORICAL_RUN_MUTATION = (
|
|
47
|
+
ERROR_FRAGMENT
|
|
48
|
+
+ """
|
|
49
|
+
mutation addHistoricalRunMutation($serializedPipelineRun: String!, $runCreationTime: Float!) {
|
|
50
|
+
runs {
|
|
51
|
+
addHistoricalRun(serializedPipelineRun: $serializedPipelineRun, runCreationTime: $runCreationTime) {
|
|
52
|
+
ok
|
|
53
|
+
error {
|
|
54
|
+
...errorFragment
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
"""
|
|
60
|
+
)
|
|
46
61
|
|
|
47
62
|
GET_RUNS_QUERY = """
|
|
48
63
|
query getRunsQuery($filters: RunsFilter, $cursor: String, $limit: Int, $bucketBy: RunBucket, $ascending: Boolean) {
|
|
@@ -226,9 +241,9 @@ ADD_RUN_TELEMETRY_MUTATION = """
|
|
|
226
241
|
"""
|
|
227
242
|
|
|
228
243
|
GET_BACKFILLS_QUERY = """
|
|
229
|
-
query getBackfillsQuery($status: String, $cursor: String, $limit: Int) {
|
|
244
|
+
query getBackfillsQuery($status: String, $cursor: String, $limit: Int, $filters: BulkActionsFilter) {
|
|
230
245
|
runs {
|
|
231
|
-
getBackfills(status: $status, cursor: $cursor, limit: $limit)
|
|
246
|
+
getBackfills(status: $status, cursor: $cursor, limit: $limit, filters: $filters)
|
|
232
247
|
}
|
|
233
248
|
}
|
|
234
249
|
"""
|
|
@@ -1,17 +1,7 @@
|
|
|
1
1
|
import json
|
|
2
|
-
from
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
Dict,
|
|
6
|
-
Iterable,
|
|
7
|
-
List,
|
|
8
|
-
Mapping,
|
|
9
|
-
Optional,
|
|
10
|
-
Sequence,
|
|
11
|
-
Set,
|
|
12
|
-
Tuple,
|
|
13
|
-
Union,
|
|
14
|
-
)
|
|
2
|
+
from collections.abc import Iterable, Mapping, Sequence
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from typing import Any, Callable, Optional, Union
|
|
15
5
|
|
|
16
6
|
import dagster._check as check
|
|
17
7
|
from dagster._core.errors import (
|
|
@@ -21,15 +11,10 @@ from dagster._core.errors import (
|
|
|
21
11
|
DagsterSnapshotDoesNotExist,
|
|
22
12
|
)
|
|
23
13
|
from dagster._core.events import DagsterEvent
|
|
24
|
-
from dagster._core.execution.backfill import BulkActionStatus, PartitionBackfill
|
|
14
|
+
from dagster._core.execution.backfill import BulkActionsFilter, BulkActionStatus, PartitionBackfill
|
|
25
15
|
from dagster._core.execution.telemetry import RunTelemetryData
|
|
26
|
-
from dagster._core.
|
|
27
|
-
from dagster._core.snap import
|
|
28
|
-
ExecutionPlanSnapshot,
|
|
29
|
-
JobSnapshot,
|
|
30
|
-
create_execution_plan_snapshot_id,
|
|
31
|
-
create_job_snapshot_id,
|
|
32
|
-
)
|
|
16
|
+
from dagster._core.remote_origin import RemoteJobOrigin
|
|
17
|
+
from dagster._core.snap import ExecutionPlanSnapshot, JobSnap, create_execution_plan_snapshot_id
|
|
33
18
|
from dagster._core.storage.dagster_run import (
|
|
34
19
|
DagsterRun,
|
|
35
20
|
JobBucket,
|
|
@@ -51,10 +36,11 @@ from dagster._utils.merger import merge_dicts
|
|
|
51
36
|
from dagster_cloud_cli.core.errors import DagsterCloudAgentServerError
|
|
52
37
|
from typing_extensions import Self
|
|
53
38
|
|
|
54
|
-
from .queries import (
|
|
39
|
+
from dagster_cloud.storage.runs.queries import (
|
|
55
40
|
ADD_BACKFILL_MUTATION,
|
|
56
41
|
ADD_DAEMON_HEARTBEAT_MUTATION,
|
|
57
42
|
ADD_EXECUTION_PLAN_SNAPSHOT_MUTATION,
|
|
43
|
+
ADD_HISTORICAL_RUN_MUTATION,
|
|
58
44
|
ADD_PIPELINE_SNAPSHOT_MUTATION,
|
|
59
45
|
ADD_RUN_MUTATION,
|
|
60
46
|
ADD_RUN_TAGS_MUTATION,
|
|
@@ -81,11 +67,13 @@ from .queries import (
|
|
|
81
67
|
)
|
|
82
68
|
|
|
83
69
|
|
|
84
|
-
def _get_filters_input(filters: Optional[RunsFilter]) -> Optional[
|
|
70
|
+
def _get_filters_input(filters: Optional[RunsFilter]) -> Optional[dict[str, Any]]:
|
|
85
71
|
filters = check.opt_inst_param(filters, "filters", RunsFilter)
|
|
86
72
|
|
|
87
73
|
if filters is None:
|
|
88
74
|
return None
|
|
75
|
+
|
|
76
|
+
check.invariant(filters.exclude_subruns is None, "RunsFilter.exclude_subruns is not supported")
|
|
89
77
|
return {
|
|
90
78
|
"runIds": filters.run_ids,
|
|
91
79
|
"pipelineName": filters.job_name,
|
|
@@ -109,7 +97,33 @@ def _get_filters_input(filters: Optional[RunsFilter]) -> Optional[Dict[str, Any]
|
|
|
109
97
|
}
|
|
110
98
|
|
|
111
99
|
|
|
112
|
-
def
|
|
100
|
+
def _get_bulk_actions_filters_input(
|
|
101
|
+
filters: Optional[BulkActionsFilter],
|
|
102
|
+
) -> Optional[dict[str, Any]]:
|
|
103
|
+
filters = check.opt_inst_param(filters, "filters", BulkActionsFilter)
|
|
104
|
+
unsupported_filters = []
|
|
105
|
+
if filters and filters.job_name:
|
|
106
|
+
unsupported_filters.append("job_name")
|
|
107
|
+
if filters and filters.backfill_ids:
|
|
108
|
+
unsupported_filters.append("backfill_ids")
|
|
109
|
+
if filters and filters.tags:
|
|
110
|
+
unsupported_filters.append("tags")
|
|
111
|
+
|
|
112
|
+
check.invariant(
|
|
113
|
+
len(unsupported_filters) == 0,
|
|
114
|
+
f"Used the following unsupported filters: {', '.join(unsupported_filters)}.",
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
if filters is None:
|
|
118
|
+
return None
|
|
119
|
+
return {
|
|
120
|
+
"statuses": [status.value for status in filters.statuses] if filters.statuses else None,
|
|
121
|
+
"createdAfter": filters.created_after.timestamp() if filters.created_after else None,
|
|
122
|
+
"createdBefore": filters.created_before.timestamp() if filters.created_before else None,
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def _run_record_from_graphql(graphene_run_record: dict) -> RunRecord:
|
|
113
127
|
check.dict_param(graphene_run_record, "graphene_run_record")
|
|
114
128
|
return RunRecord(
|
|
115
129
|
storage_id=check.int_elem(graphene_run_record, "storageId"),
|
|
@@ -128,7 +142,7 @@ def _run_record_from_graphql(graphene_run_record: Dict) -> RunRecord:
|
|
|
128
142
|
)
|
|
129
143
|
|
|
130
144
|
|
|
131
|
-
def _get_bucket_input(bucket_by: Optional[Union[JobBucket, TagBucket]]) -> Optional[
|
|
145
|
+
def _get_bucket_input(bucket_by: Optional[Union[JobBucket, TagBucket]]) -> Optional[dict[str, Any]]:
|
|
132
146
|
if not bucket_by:
|
|
133
147
|
return None
|
|
134
148
|
|
|
@@ -170,7 +184,7 @@ class GraphQLRunStorage(RunStorage, ConfigurableClass):
|
|
|
170
184
|
return (
|
|
171
185
|
self._override_graphql_client
|
|
172
186
|
if self._override_graphql_client
|
|
173
|
-
else self._instance.graphql_client
|
|
187
|
+
else self._instance.graphql_client # pyright: ignore[reportAttributeAccessIssue]
|
|
174
188
|
)
|
|
175
189
|
|
|
176
190
|
def _execute_query(self, query, variables=None, idempotent_mutation=False):
|
|
@@ -178,6 +192,29 @@ class GraphQLRunStorage(RunStorage, ConfigurableClass):
|
|
|
178
192
|
query, variable_values=variables, idempotent_mutation=idempotent_mutation
|
|
179
193
|
)
|
|
180
194
|
|
|
195
|
+
def add_historical_run(
|
|
196
|
+
self, dagster_run: DagsterRun, run_creation_time: datetime
|
|
197
|
+
) -> DagsterRun:
|
|
198
|
+
check.inst_param(dagster_run, "dagster_run", DagsterRun)
|
|
199
|
+
res = self._execute_query(
|
|
200
|
+
ADD_HISTORICAL_RUN_MUTATION,
|
|
201
|
+
variables={
|
|
202
|
+
"serializedPipelineRun": serialize_value(dagster_run),
|
|
203
|
+
"runCreationTime": run_creation_time.timestamp(),
|
|
204
|
+
},
|
|
205
|
+
)
|
|
206
|
+
result = res["data"]["runs"]["addHistoricalRun"]
|
|
207
|
+
error = result.get("error")
|
|
208
|
+
# Special-case some errors to match the RunStorage API
|
|
209
|
+
if error:
|
|
210
|
+
if error["className"] == "DagsterRunAlreadyExists":
|
|
211
|
+
raise DagsterRunAlreadyExists(error["message"])
|
|
212
|
+
if error["className"] == "DagsterSnapshotDoesNotExist":
|
|
213
|
+
raise DagsterSnapshotDoesNotExist(error["message"])
|
|
214
|
+
else:
|
|
215
|
+
raise DagsterCloudAgentServerError(res)
|
|
216
|
+
return dagster_run
|
|
217
|
+
|
|
181
218
|
def add_run(self, dagster_run: DagsterRun):
|
|
182
219
|
check.inst_param(dagster_run, "dagster_run", DagsterRun)
|
|
183
220
|
res = self._execute_query(
|
|
@@ -199,9 +236,10 @@ class GraphQLRunStorage(RunStorage, ConfigurableClass):
|
|
|
199
236
|
|
|
200
237
|
return dagster_run
|
|
201
238
|
|
|
202
|
-
def handle_run_event(
|
|
203
|
-
|
|
204
|
-
|
|
239
|
+
def handle_run_event(
|
|
240
|
+
self, run_id: str, event: DagsterEvent, update_timestamp: Optional[datetime] = None
|
|
241
|
+
):
|
|
242
|
+
raise NotImplementedError("Should never be called by an agent client")
|
|
205
243
|
|
|
206
244
|
@property
|
|
207
245
|
def supports_bucket_queries(self) -> bool:
|
|
@@ -252,7 +290,7 @@ class GraphQLRunStorage(RunStorage, ConfigurableClass):
|
|
|
252
290
|
)
|
|
253
291
|
return res["data"]["runs"]["getRunsCount"]
|
|
254
292
|
|
|
255
|
-
def get_run_group(self, run_id: str) -> Optional[
|
|
293
|
+
def get_run_group(self, run_id: str) -> Optional[tuple[str, Iterable[DagsterRun]]]: # pyright: ignore[reportIncompatibleMethodOverride], fix me!
|
|
256
294
|
res = self._execute_query(
|
|
257
295
|
GET_RUN_GROUP_QUERY, variables={"runId": check.str_param(run_id, "run_id")}
|
|
258
296
|
)
|
|
@@ -288,7 +326,7 @@ class GraphQLRunStorage(RunStorage, ConfigurableClass):
|
|
|
288
326
|
ascending: bool = False,
|
|
289
327
|
cursor: Optional[str] = None,
|
|
290
328
|
bucket_by: Optional[Union[JobBucket, TagBucket]] = None,
|
|
291
|
-
) ->
|
|
329
|
+
) -> list[RunRecord]:
|
|
292
330
|
res = self._execute_query(
|
|
293
331
|
GET_RUN_RECORDS_QUERY,
|
|
294
332
|
variables={
|
|
@@ -307,7 +345,7 @@ class GraphQLRunStorage(RunStorage, ConfigurableClass):
|
|
|
307
345
|
tag_keys: Sequence[str],
|
|
308
346
|
value_prefix: Optional[str] = None,
|
|
309
347
|
limit: Optional[int] = None,
|
|
310
|
-
) -> Sequence[
|
|
348
|
+
) -> Sequence[tuple[str, set[str]]]:
|
|
311
349
|
res = self._execute_query(
|
|
312
350
|
GET_RUN_TAGS_QUERY,
|
|
313
351
|
variables={
|
|
@@ -353,27 +391,27 @@ class GraphQLRunStorage(RunStorage, ConfigurableClass):
|
|
|
353
391
|
return res["data"]["runs"]["hasPipelineSnapshot"]
|
|
354
392
|
|
|
355
393
|
def add_job_snapshot( # pyright: ignore[reportIncompatibleMethodOverride], fix me!
|
|
356
|
-
self, pipeline_snapshot:
|
|
394
|
+
self, pipeline_snapshot: JobSnap, snapshot_id: Optional[str] = None
|
|
357
395
|
) -> str:
|
|
358
396
|
self._execute_query(
|
|
359
397
|
ADD_PIPELINE_SNAPSHOT_MUTATION,
|
|
360
398
|
variables={
|
|
361
399
|
"serializedPipelineSnapshot": serialize_value(
|
|
362
|
-
check.inst_param(pipeline_snapshot, "pipeline_snapshot",
|
|
400
|
+
check.inst_param(pipeline_snapshot, "pipeline_snapshot", JobSnap)
|
|
363
401
|
),
|
|
364
402
|
"snapshotId": snapshot_id,
|
|
365
403
|
},
|
|
366
404
|
)
|
|
367
|
-
return snapshot_id if snapshot_id else
|
|
405
|
+
return snapshot_id if snapshot_id else pipeline_snapshot.snapshot_id
|
|
368
406
|
|
|
369
|
-
def get_job_snapshot(self, pipeline_snapshot_id: str) ->
|
|
407
|
+
def get_job_snapshot(self, pipeline_snapshot_id: str) -> JobSnap: # pyright: ignore[reportIncompatibleMethodOverride], fix me!
|
|
370
408
|
res = self._execute_query(
|
|
371
409
|
GET_PIPELINE_SNAPSHOT_QUERY,
|
|
372
410
|
variables={
|
|
373
411
|
"pipelineSnapshotId": check.str_param(pipeline_snapshot_id, "pipeline_snapshot_id")
|
|
374
412
|
},
|
|
375
413
|
)
|
|
376
|
-
return deserialize_value(res["data"]["runs"]["getPipelineSnapshot"],
|
|
414
|
+
return deserialize_value(res["data"]["runs"]["getPipelineSnapshot"], JobSnap)
|
|
377
415
|
|
|
378
416
|
def has_execution_plan_snapshot(self, execution_plan_snapshot_id: str) -> bool:
|
|
379
417
|
res = self._execute_query(
|
|
@@ -419,7 +457,7 @@ class GraphQLRunStorage(RunStorage, ConfigurableClass):
|
|
|
419
457
|
res["data"]["runs"]["getExecutionPlanSnapshot"], ExecutionPlanSnapshot
|
|
420
458
|
)
|
|
421
459
|
|
|
422
|
-
def get_run_partition_data(self, runs_filter: RunsFilter) ->
|
|
460
|
+
def get_run_partition_data(self, runs_filter: RunsFilter) -> list[RunPartitionData]:
|
|
423
461
|
res = self._execute_query(
|
|
424
462
|
GET_RUN_PARTITION_DATA_QUERY,
|
|
425
463
|
variables={
|
|
@@ -441,10 +479,13 @@ class GraphQLRunStorage(RunStorage, ConfigurableClass):
|
|
|
441
479
|
},
|
|
442
480
|
)
|
|
443
481
|
|
|
482
|
+
def supports_run_telemetry(self) -> bool:
|
|
483
|
+
return True
|
|
484
|
+
|
|
444
485
|
def add_run_telemetry(
|
|
445
486
|
self,
|
|
446
487
|
run_telemetry: RunTelemetryData,
|
|
447
|
-
tags: Optional[
|
|
488
|
+
tags: Optional[dict[str, str]] = None,
|
|
448
489
|
) -> None:
|
|
449
490
|
if tags is None:
|
|
450
491
|
tags = {}
|
|
@@ -466,7 +507,7 @@ class GraphQLRunStorage(RunStorage, ConfigurableClass):
|
|
|
466
507
|
):
|
|
467
508
|
raise Exception("Not allowed to build indexes from user cloud")
|
|
468
509
|
|
|
469
|
-
def get_daemon_heartbeats(self) ->
|
|
510
|
+
def get_daemon_heartbeats(self) -> dict[str, DaemonHeartbeat]:
|
|
470
511
|
res = self._execute_query(GET_DAEMON_HEARTBEATS_QUERY)
|
|
471
512
|
return {
|
|
472
513
|
key: deserialize_value(heartbeat, DaemonHeartbeat)
|
|
@@ -487,9 +528,10 @@ class GraphQLRunStorage(RunStorage, ConfigurableClass):
|
|
|
487
528
|
|
|
488
529
|
def get_backfills(
|
|
489
530
|
self,
|
|
490
|
-
|
|
531
|
+
filters: Optional[BulkActionsFilter] = None,
|
|
491
532
|
cursor: Optional[str] = None,
|
|
492
533
|
limit: Optional[int] = None,
|
|
534
|
+
status: Optional[BulkActionStatus] = None,
|
|
493
535
|
):
|
|
494
536
|
"""Get a list of partition backfills."""
|
|
495
537
|
res = self._execute_query(
|
|
@@ -498,6 +540,7 @@ class GraphQLRunStorage(RunStorage, ConfigurableClass):
|
|
|
498
540
|
"status": status.value if status else None,
|
|
499
541
|
"cursor": check.opt_str_param(cursor, "cursor"),
|
|
500
542
|
"limit": check.opt_int_param(limit, "limit"),
|
|
543
|
+
"filters": _get_bulk_actions_filters_input(filters),
|
|
501
544
|
},
|
|
502
545
|
)
|
|
503
546
|
return [
|
|
@@ -505,6 +548,9 @@ class GraphQLRunStorage(RunStorage, ConfigurableClass):
|
|
|
505
548
|
for backfill in res["data"]["runs"]["getBackfills"]
|
|
506
549
|
]
|
|
507
550
|
|
|
551
|
+
def get_backfills_count(self, filters: Optional[BulkActionsFilter] = None) -> int:
|
|
552
|
+
raise NotImplementedError("get_backfills_count is not callable from user cloud.")
|
|
553
|
+
|
|
508
554
|
def get_backfill(self, backfill_id: str) -> PartitionBackfill:
|
|
509
555
|
"""Get a single partition backfill."""
|
|
510
556
|
res = self._execute_query(GET_BACKFILL_QUERY, variables={"backfillId": backfill_id})
|
|
@@ -525,7 +571,7 @@ class GraphQLRunStorage(RunStorage, ConfigurableClass):
|
|
|
525
571
|
variables={"serializedPartitionBackfill": serialize_value(partition_backfill)},
|
|
526
572
|
)
|
|
527
573
|
|
|
528
|
-
def get_cursor_values(self, keys:
|
|
574
|
+
def get_cursor_values(self, keys: set[str]): # pyright: ignore[reportIncompatibleMethodOverride], fix me!
|
|
529
575
|
return NotImplementedError("KVS is not supported from the user cloud")
|
|
530
576
|
|
|
531
577
|
def set_cursor_values(self, pairs: Mapping[str, str]): # pyright: ignore[reportIncompatibleMethodOverride], fix me!
|
|
@@ -1 +1 @@
|
|
|
1
|
-
from .storage import GraphQLScheduleStorage as GraphQLScheduleStorage
|
|
1
|
+
from dagster_cloud.storage.schedules.storage import GraphQLScheduleStorage as GraphQLScheduleStorage
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
from
|
|
1
|
+
from collections.abc import Iterable, Sequence
|
|
2
|
+
from typing import TYPE_CHECKING, Any, Optional
|
|
2
3
|
|
|
3
4
|
import dagster._check as check
|
|
4
|
-
from dagster import
|
|
5
|
+
from dagster._core.definitions.asset_key import EntityKey
|
|
5
6
|
from dagster._core.definitions.declarative_automation.serialized_objects import (
|
|
6
7
|
AutomationConditionEvaluationWithRunIds,
|
|
7
8
|
)
|
|
@@ -23,7 +24,7 @@ from dagster._serdes import (
|
|
|
23
24
|
)
|
|
24
25
|
from typing_extensions import Self
|
|
25
26
|
|
|
26
|
-
from .queries import (
|
|
27
|
+
from dagster_cloud.storage.schedules.queries import (
|
|
27
28
|
ADD_JOB_STATE_MUTATION,
|
|
28
29
|
ALL_STORED_JOB_STATE_QUERY,
|
|
29
30
|
CREATE_JOB_TICK_MUTATION,
|
|
@@ -76,7 +77,7 @@ class GraphQLScheduleStorage(ScheduleStorage["DagsterCloudAgentInstance"], Confi
|
|
|
76
77
|
repository_origin_id: Optional[str] = None,
|
|
77
78
|
repository_selector_id: Optional[str] = None,
|
|
78
79
|
instigator_type: Optional[InstigatorType] = None,
|
|
79
|
-
instigator_statuses: Optional[
|
|
80
|
+
instigator_statuses: Optional[set[InstigatorStatus]] = None,
|
|
80
81
|
) -> Iterable[InstigatorState]:
|
|
81
82
|
res = self._execute_query(
|
|
82
83
|
ALL_STORED_JOB_STATE_QUERY,
|
|
@@ -206,7 +207,7 @@ class GraphQLScheduleStorage(ScheduleStorage["DagsterCloudAgentInstance"], Confi
|
|
|
206
207
|
raise NotImplementedError("Not callable from user cloud")
|
|
207
208
|
|
|
208
209
|
def get_auto_materialize_asset_evaluations(
|
|
209
|
-
self,
|
|
210
|
+
self, key: EntityKey, limit: int, cursor: Optional[int] = None
|
|
210
211
|
) -> Sequence[AutoMaterializeAssetEvaluationRecord]:
|
|
211
212
|
raise NotImplementedError("Not callable from user cloud")
|
|
212
213
|
|
|
@@ -220,6 +221,3 @@ class GraphQLScheduleStorage(ScheduleStorage["DagsterCloudAgentInstance"], Confi
|
|
|
220
221
|
|
|
221
222
|
def upgrade(self):
|
|
222
223
|
raise NotImplementedError("Not callable from user cloud")
|
|
223
|
-
|
|
224
|
-
def optimize_for_dagit(self, statement_timeout: int, pool_recycle: int):
|
|
225
|
-
raise NotImplementedError("Not callable from user cloud")
|
dagster_cloud/storage/tags.py
CHANGED
|
@@ -1,4 +1,18 @@
|
|
|
1
|
-
from
|
|
1
|
+
from collections.abc import Mapping
|
|
2
|
+
|
|
3
|
+
from dagster._core.storage.tags import (
|
|
4
|
+
AUTO_MATERIALIZE_TAG,
|
|
5
|
+
AUTO_OBSERVE_TAG,
|
|
6
|
+
BACKFILL_ID_TAG,
|
|
7
|
+
HIDDEN_TAG_PREFIX,
|
|
8
|
+
PARTITION_NAME_TAG,
|
|
9
|
+
PARTITION_SET_TAG,
|
|
10
|
+
REPOSITORY_LABEL_TAG,
|
|
11
|
+
ROOT_RUN_ID_TAG,
|
|
12
|
+
SCHEDULE_NAME_TAG,
|
|
13
|
+
SENSOR_NAME_TAG,
|
|
14
|
+
SYSTEM_TAG_PREFIX,
|
|
15
|
+
)
|
|
2
16
|
|
|
3
17
|
CLOUD_SYSTEM_TAG_PREFIX = "dagster-cloud/"
|
|
4
18
|
|
|
@@ -6,7 +20,58 @@ PEX_METADATA_TAG = f"{HIDDEN_TAG_PREFIX}pex_metadata"
|
|
|
6
20
|
PEX_TAG_TAG = f"{SYSTEM_TAG_PREFIX}pex_tag"
|
|
7
21
|
IGNORE_ALERTS_TAG = f"{CLOUD_SYSTEM_TAG_PREFIX}ignore-alerts"
|
|
8
22
|
|
|
23
|
+
RUN_TAG_RUN_COLUMN_TAG_KEYS = {
|
|
24
|
+
AUTO_MATERIALIZE_TAG,
|
|
25
|
+
AUTO_OBSERVE_TAG,
|
|
26
|
+
REPOSITORY_LABEL_TAG,
|
|
27
|
+
ROOT_RUN_ID_TAG,
|
|
28
|
+
PARTITION_NAME_TAG,
|
|
29
|
+
PARTITION_SET_TAG,
|
|
30
|
+
SCHEDULE_NAME_TAG,
|
|
31
|
+
SENSOR_NAME_TAG,
|
|
32
|
+
BACKFILL_ID_TAG,
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
DID_ALERT_TAG = f"{CLOUD_SYSTEM_TAG_PREFIX}triggered_alert"
|
|
36
|
+
TRIGGERED_ALERT_POLICY_TAG_PREFIX = f"{HIDDEN_TAG_PREFIX}triggered_alert_policy/"
|
|
37
|
+
TRIGGERED_ALERT_ID_TAG_PREFIX = f"{HIDDEN_TAG_PREFIX}triggered_alert/"
|
|
38
|
+
|
|
39
|
+
TRIGGERED_NOTIFICATION_TAG_PREFIX = f"{HIDDEN_TAG_PREFIX}triggered_notification/"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def get_triggered_alert_policy_key(alert_policy_id: str) -> str:
|
|
43
|
+
return f"{TRIGGERED_ALERT_POLICY_TAG_PREFIX}{alert_policy_id}"
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def get_triggered_alert_id_key(alert_id: str) -> str:
|
|
47
|
+
return f"{TRIGGERED_ALERT_ID_TAG_PREFIX}{alert_id}"
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def get_triggered_notification_key(notification_id: str) -> str:
|
|
51
|
+
return f"{TRIGGERED_NOTIFICATION_TAG_PREFIX}{notification_id}"
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def get_triggered_notification_key_value(notification_id: str) -> Mapping[str, str]:
|
|
55
|
+
return {f"{TRIGGERED_NOTIFICATION_TAG_PREFIX}{notification_id}": "true"}
|
|
56
|
+
|
|
9
57
|
|
|
10
58
|
def get_policy_names_from_tag_value(policies_str):
|
|
11
59
|
"""From a comma-delineated string (whitespace allowed), retrieve alert policy names."""
|
|
12
60
|
return [policy_name.strip() for policy_name in policies_str.split(",")]
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def should_tag_be_used_for_indexing_filtering(tag_key: str) -> bool:
|
|
64
|
+
"""Determines whether a tag should be added to the run_tags table,
|
|
65
|
+
which is used for indexing and filtering runs.
|
|
66
|
+
Tags not added to the table will still be stored on the run object, but
|
|
67
|
+
will not be directly queryable via the run_tags table.
|
|
68
|
+
"""
|
|
69
|
+
if tag_key in RUN_TAG_RUN_COLUMN_TAG_KEYS:
|
|
70
|
+
return False
|
|
71
|
+
|
|
72
|
+
if tag_key.startswith(TRIGGERED_ALERT_ID_TAG_PREFIX) or tag_key.startswith(
|
|
73
|
+
TRIGGERED_ALERT_POLICY_TAG_PREFIX
|
|
74
|
+
):
|
|
75
|
+
return False
|
|
76
|
+
|
|
77
|
+
return True
|
dagster_cloud/util/__init__.py
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import zlib
|
|
2
2
|
from collections import namedtuple
|
|
3
|
+
from collections.abc import Mapping
|
|
3
4
|
from contextlib import contextmanager
|
|
4
5
|
from io import BytesIO
|
|
5
|
-
from typing import Any,
|
|
6
|
+
from typing import Any, Callable
|
|
6
7
|
|
|
7
8
|
from dagster import (
|
|
8
9
|
Field,
|
|
@@ -10,7 +11,7 @@ from dagster import (
|
|
|
10
11
|
)
|
|
11
12
|
from dagster._config import BoolSourceType, IntSourceType, StringSourceType
|
|
12
13
|
from dagster._serdes import serialize_value
|
|
13
|
-
from
|
|
14
|
+
from dagster_shared.serdes.serdes import PackableValue
|
|
14
15
|
|
|
15
16
|
|
|
16
17
|
class SerializableNamedtupleMapDiff(
|
|
@@ -25,7 +26,7 @@ class SerializableNamedtupleMapDiff(
|
|
|
25
26
|
to_update,
|
|
26
27
|
to_remove,
|
|
27
28
|
):
|
|
28
|
-
return super(
|
|
29
|
+
return super().__new__(
|
|
29
30
|
cls,
|
|
30
31
|
check.set_param(to_add, "to_add", tuple),
|
|
31
32
|
check.set_param(to_update, "to_update", tuple),
|
|
@@ -33,10 +34,9 @@ class SerializableNamedtupleMapDiff(
|
|
|
33
34
|
)
|
|
34
35
|
|
|
35
36
|
|
|
36
|
-
def diff_serializable_namedtuple_map(desired_map, actual_map,
|
|
37
|
+
def diff_serializable_namedtuple_map(desired_map, actual_map, update_key_fn: Callable):
|
|
37
38
|
desired_keys = set(desired_map.keys())
|
|
38
39
|
actual_keys = set(actual_map.keys())
|
|
39
|
-
force_update_keys = check.opt_set_param(force_update_keys, "force_update_keys", tuple)
|
|
40
40
|
|
|
41
41
|
to_add = desired_keys.difference(actual_keys)
|
|
42
42
|
to_remove = actual_keys.difference(desired_keys)
|
|
@@ -46,17 +46,15 @@ def diff_serializable_namedtuple_map(desired_map, actual_map, force_update_keys=
|
|
|
46
46
|
to_update = {
|
|
47
47
|
existing_key
|
|
48
48
|
for existing_key in existing
|
|
49
|
-
if
|
|
50
|
-
!= create_snapshot_id(actual_map[existing_key])
|
|
51
|
-
or existing_key in force_update_keys
|
|
49
|
+
if update_key_fn(desired_map[existing_key]) != update_key_fn(actual_map[existing_key])
|
|
52
50
|
}
|
|
53
51
|
|
|
54
52
|
return SerializableNamedtupleMapDiff(to_add, to_update, to_remove)
|
|
55
53
|
|
|
56
54
|
|
|
57
55
|
def get_env_names_from_config(
|
|
58
|
-
config_schema:
|
|
59
|
-
) ->
|
|
56
|
+
config_schema: dict[str, Field], config_dict: dict[str, Any]
|
|
57
|
+
) -> list[str]:
|
|
60
58
|
env_vars = []
|
|
61
59
|
for field_name, field in config_schema.items():
|
|
62
60
|
config_type = field.config_type
|
|
@@ -81,14 +79,14 @@ SERVER_HANDLE_TAG = ".dagster/server_handle"
|
|
|
81
79
|
|
|
82
80
|
|
|
83
81
|
def keys_not_none(
|
|
84
|
-
keys:
|
|
82
|
+
keys: list[str],
|
|
85
83
|
dictionary: Mapping[str, Any],
|
|
86
84
|
) -> bool:
|
|
87
85
|
return all(key in dictionary and dictionary[key] is not None for key in keys)
|
|
88
86
|
|
|
89
87
|
|
|
90
88
|
@contextmanager
|
|
91
|
-
def compressed_namedtuple_upload_file(to_serialize:
|
|
89
|
+
def compressed_namedtuple_upload_file(to_serialize: PackableValue):
|
|
92
90
|
compressed_data = zlib.compress(serialize_value(to_serialize).encode("utf-8"))
|
|
93
91
|
with BytesIO(compressed_data) as f:
|
|
94
92
|
yield f
|