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.
Files changed (108) hide show
  1. dagster_cloud/__init__.py +3 -3
  2. dagster_cloud/agent/__init__.py +4 -4
  3. dagster_cloud/agent/cli/__init__.py +56 -17
  4. dagster_cloud/agent/dagster_cloud_agent.py +360 -172
  5. dagster_cloud/agent/instrumentation/__init__.py +0 -0
  6. dagster_cloud/agent/instrumentation/constants.py +2 -0
  7. dagster_cloud/agent/instrumentation/run_launch.py +23 -0
  8. dagster_cloud/agent/instrumentation/schedule.py +34 -0
  9. dagster_cloud/agent/instrumentation/sensor.py +34 -0
  10. dagster_cloud/anomaly_detection/__init__.py +2 -2
  11. dagster_cloud/anomaly_detection/defs.py +17 -12
  12. dagster_cloud/anomaly_detection/types.py +3 -3
  13. dagster_cloud/api/dagster_cloud_api.py +209 -293
  14. dagster_cloud/auth/constants.py +21 -5
  15. dagster_cloud/batching/__init__.py +1 -0
  16. dagster_cloud/batching/batcher.py +210 -0
  17. dagster_cloud/dagster_insights/__init__.py +12 -6
  18. dagster_cloud/dagster_insights/bigquery/bigquery_utils.py +3 -2
  19. dagster_cloud/dagster_insights/bigquery/dbt_wrapper.py +39 -12
  20. dagster_cloud/dagster_insights/bigquery/insights_bigquery_resource.py +8 -6
  21. dagster_cloud/dagster_insights/insights_utils.py +18 -8
  22. dagster_cloud/dagster_insights/metrics_utils.py +12 -12
  23. dagster_cloud/dagster_insights/snowflake/dagster_snowflake_insights.py +5 -12
  24. dagster_cloud/dagster_insights/snowflake/dbt_wrapper.py +34 -8
  25. dagster_cloud/dagster_insights/snowflake/definitions.py +38 -12
  26. dagster_cloud/dagster_insights/snowflake/insights_snowflake_resource.py +11 -23
  27. dagster_cloud/definitions/__init__.py +0 -0
  28. dagster_cloud/definitions/job_selection.py +36 -0
  29. dagster_cloud/execution/cloud_run_launcher/k8s.py +1 -1
  30. dagster_cloud/execution/cloud_run_launcher/process.py +3 -3
  31. dagster_cloud/execution/monitoring/__init__.py +27 -33
  32. dagster_cloud/execution/utils/process.py +3 -3
  33. dagster_cloud/instance/__init__.py +125 -38
  34. dagster_cloud/instrumentation/__init__.py +32 -0
  35. dagster_cloud/metadata/source_code.py +13 -8
  36. dagster_cloud/metrics/__init__.py +0 -0
  37. dagster_cloud/metrics/tracer.py +59 -0
  38. dagster_cloud/opentelemetry/__init__.py +0 -0
  39. dagster_cloud/opentelemetry/config/__init__.py +73 -0
  40. dagster_cloud/opentelemetry/config/exporter.py +81 -0
  41. dagster_cloud/opentelemetry/config/log_record_processor.py +40 -0
  42. dagster_cloud/opentelemetry/config/logging_handler.py +14 -0
  43. dagster_cloud/opentelemetry/config/meter_provider.py +9 -0
  44. dagster_cloud/opentelemetry/config/metric_reader.py +39 -0
  45. dagster_cloud/opentelemetry/controller.py +319 -0
  46. dagster_cloud/opentelemetry/enum.py +58 -0
  47. dagster_cloud/opentelemetry/factories/__init__.py +1 -0
  48. dagster_cloud/opentelemetry/factories/logs.py +113 -0
  49. dagster_cloud/opentelemetry/factories/metrics.py +121 -0
  50. dagster_cloud/opentelemetry/metrics/__init__.py +0 -0
  51. dagster_cloud/opentelemetry/metrics/meter.py +140 -0
  52. dagster_cloud/opentelemetry/observers/__init__.py +0 -0
  53. dagster_cloud/opentelemetry/observers/dagster_exception_handler.py +40 -0
  54. dagster_cloud/opentelemetry/observers/execution_observer.py +178 -0
  55. dagster_cloud/pex/grpc/__generated__/multi_pex_api_pb2.pyi +175 -0
  56. dagster_cloud/pex/grpc/__init__.py +2 -2
  57. dagster_cloud/pex/grpc/client.py +4 -4
  58. dagster_cloud/pex/grpc/compile.py +2 -2
  59. dagster_cloud/pex/grpc/server/__init__.py +2 -2
  60. dagster_cloud/pex/grpc/server/cli/__init__.py +31 -19
  61. dagster_cloud/pex/grpc/server/manager.py +60 -42
  62. dagster_cloud/pex/grpc/server/registry.py +28 -21
  63. dagster_cloud/pex/grpc/server/server.py +23 -14
  64. dagster_cloud/pex/grpc/types.py +5 -5
  65. dagster_cloud/py.typed +0 -0
  66. dagster_cloud/secrets/__init__.py +1 -1
  67. dagster_cloud/secrets/loader.py +3 -3
  68. dagster_cloud/serverless/__init__.py +1 -1
  69. dagster_cloud/serverless/io_manager.py +36 -53
  70. dagster_cloud/storage/client.py +54 -17
  71. dagster_cloud/storage/compute_logs/__init__.py +3 -1
  72. dagster_cloud/storage/compute_logs/compute_log_manager.py +22 -17
  73. dagster_cloud/storage/defs_state/__init__.py +3 -0
  74. dagster_cloud/storage/defs_state/queries.py +15 -0
  75. dagster_cloud/storage/defs_state/storage.py +113 -0
  76. dagster_cloud/storage/event_logs/__init__.py +3 -1
  77. dagster_cloud/storage/event_logs/queries.py +102 -4
  78. dagster_cloud/storage/event_logs/storage.py +266 -73
  79. dagster_cloud/storage/event_logs/utils.py +88 -7
  80. dagster_cloud/storage/runs/__init__.py +1 -1
  81. dagster_cloud/storage/runs/queries.py +17 -2
  82. dagster_cloud/storage/runs/storage.py +88 -42
  83. dagster_cloud/storage/schedules/__init__.py +1 -1
  84. dagster_cloud/storage/schedules/storage.py +6 -8
  85. dagster_cloud/storage/tags.py +66 -1
  86. dagster_cloud/util/__init__.py +10 -12
  87. dagster_cloud/util/errors.py +49 -64
  88. dagster_cloud/version.py +1 -1
  89. dagster_cloud/workspace/config_schema/__init__.py +55 -13
  90. dagster_cloud/workspace/docker/__init__.py +76 -25
  91. dagster_cloud/workspace/docker/utils.py +1 -1
  92. dagster_cloud/workspace/ecs/__init__.py +1 -1
  93. dagster_cloud/workspace/ecs/client.py +51 -33
  94. dagster_cloud/workspace/ecs/launcher.py +76 -22
  95. dagster_cloud/workspace/ecs/run_launcher.py +3 -3
  96. dagster_cloud/workspace/ecs/utils.py +14 -5
  97. dagster_cloud/workspace/kubernetes/__init__.py +1 -1
  98. dagster_cloud/workspace/kubernetes/launcher.py +61 -29
  99. dagster_cloud/workspace/kubernetes/utils.py +34 -22
  100. dagster_cloud/workspace/user_code_launcher/__init__.py +5 -3
  101. dagster_cloud/workspace/user_code_launcher/process.py +16 -14
  102. dagster_cloud/workspace/user_code_launcher/user_code_launcher.py +552 -172
  103. dagster_cloud/workspace/user_code_launcher/utils.py +105 -1
  104. {dagster_cloud-1.8.2.dist-info → dagster_cloud-1.12.6.dist-info}/METADATA +48 -42
  105. dagster_cloud-1.12.6.dist-info/RECORD +134 -0
  106. {dagster_cloud-1.8.2.dist-info → dagster_cloud-1.12.6.dist-info}/WHEEL +1 -1
  107. dagster_cloud-1.8.2.dist-info/RECORD +0 -100
  108. {dagster_cloud-1.8.2.dist-info → dagster_cloud-1.12.6.dist-info}/top_level.txt +0 -0
@@ -1,19 +1,100 @@
1
- from os import environ
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
- MAXIMUM_EVENT_MESSAGE_CHARACTERS = int(
6
- environ.get("DAGSTER_CLOUD_MAXIMUM_EVENT_MESSAGE_CHARACTERS", 50000)
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, maximum_length=MAXIMUM_EVENT_MESSAGE_CHARACTERS
74
+ event: EventLogEntry,
75
+ maximum_length=None,
76
+ truncations: Optional[list[str]] = None,
12
77
  ) -> EventLogEntry:
13
- if len(event.user_message) > maximum_length:
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 {len(event.user_message)} characters to"
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 typing import (
3
- Any,
4
- Callable,
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.remote_representation.origin import RemoteJobOrigin
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[Dict[str, Any]]:
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 _run_record_from_graphql(graphene_run_record: Dict) -> RunRecord:
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[Dict[str, Any]]:
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(self, run_id: str, event: DagsterEvent):
203
- # no-op, handled by store_event
204
- pass
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[Tuple[str, Iterable[DagsterRun]]]: # pyright: ignore[reportIncompatibleMethodOverride], fix me!
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
- ) -> List[RunRecord]:
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[Tuple[str, Set[str]]]:
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: JobSnapshot, snapshot_id: Optional[str] = None
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", JobSnapshot)
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 create_job_snapshot_id(pipeline_snapshot)
405
+ return snapshot_id if snapshot_id else pipeline_snapshot.snapshot_id
368
406
 
369
- def get_job_snapshot(self, pipeline_snapshot_id: str) -> JobSnapshot: # pyright: ignore[reportIncompatibleMethodOverride], fix me!
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"], JobSnapshot)
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) -> List[RunPartitionData]:
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[Dict[str, str]] = None,
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) -> Dict[str, DaemonHeartbeat]:
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
- status: Optional[BulkActionStatus] = None,
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: Set[str]): # pyright: ignore[reportIncompatibleMethodOverride], fix me!
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 typing import TYPE_CHECKING, Any, Iterable, Optional, Sequence, Set
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 AssetKey
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[Set[InstigatorStatus]] = None,
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, asset_key: AssetKey, limit: int, cursor: Optional[int] = None
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")
@@ -1,4 +1,18 @@
1
- from dagster._core.storage.tags import HIDDEN_TAG_PREFIX, SYSTEM_TAG_PREFIX
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
@@ -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, Dict, List, Mapping, NamedTuple
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 dagster._serdes.utils import create_snapshot_id
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(SerializableNamedtupleMapDiff, cls).__new__(
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, force_update_keys=None):
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 create_snapshot_id(desired_map[existing_key])
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: Dict[str, Field], config_dict: Dict[str, Any]
59
- ) -> List[str]:
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: List[str],
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: NamedTuple):
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