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,28 +1,17 @@
1
1
  import json
2
2
  import os
3
3
  from collections import defaultdict
4
+ from collections.abc import Iterable, Mapping, Sequence
5
+ from contextlib import contextmanager
4
6
  from datetime import timezone
5
- from typing import (
6
- TYPE_CHECKING,
7
- Any,
8
- Callable,
9
- Dict,
10
- Iterable,
11
- List,
12
- Mapping,
13
- Optional,
14
- Sequence,
15
- Set,
16
- Tuple,
17
- Union,
18
- cast,
19
- )
7
+ from typing import TYPE_CHECKING, AbstractSet, Any, Callable, Optional, Union, cast # noqa: UP035
20
8
  from uuid import uuid4
21
9
 
22
10
  import dagster._check as check
23
- from dagster import AssetCheckKey, DagsterInvalidInvocationError
11
+ from dagster import AssetCheckKey, DagsterInvalidInvocationError, PartitionsDefinition
24
12
  from dagster._core.assets import AssetDetails
25
13
  from dagster._core.definitions.events import AssetKey, ExpectationResult
14
+ from dagster._core.definitions.freshness import FreshnessStateRecord
26
15
  from dagster._core.event_api import (
27
16
  AssetRecordsFilter,
28
17
  EventLogRecord,
@@ -34,6 +23,7 @@ from dagster._core.event_api import (
34
23
  from dagster._core.events import DagsterEvent, DagsterEventType
35
24
  from dagster._core.events.log import EventLogEntry
36
25
  from dagster._core.execution.stats import RunStepKeyStatsSnapshot, RunStepMarker, StepEventStatus
26
+ from dagster._core.instance.config import PoolConfig, PoolGranularity
37
27
  from dagster._core.storage.asset_check_execution_record import (
38
28
  AssetCheckExecutionRecord,
39
29
  AssetCheckExecutionRecordStatus,
@@ -46,10 +36,11 @@ from dagster._core.storage.event_log.base import (
46
36
  EventLogConnection,
47
37
  EventLogStorage,
48
38
  PlannedMaterializationInfo,
39
+ PoolLimit,
49
40
  )
50
41
  from dagster._core.storage.partition_status_cache import AssetStatusCacheValue
42
+ from dagster._core.types.pagination import PaginatedResults
51
43
  from dagster._serdes import ConfigurableClass, ConfigurableClassData, serialize_value
52
- from dagster._serdes.serdes import deserialize_value
53
44
  from dagster._time import datetime_from_timestamp
54
45
  from dagster._utils.concurrency import (
55
46
  ClaimedSlotInfo,
@@ -61,9 +52,11 @@ from dagster._utils.concurrency import (
61
52
  from dagster._utils.error import SerializableErrorInfo
62
53
  from dagster._utils.merger import merge_dicts
63
54
  from dagster_cloud_cli.core.errors import DagsterCloudAgentServerError
55
+ from dagster_shared.serdes.serdes import deserialize_value
64
56
  from typing_extensions import Self
65
57
 
66
58
  from dagster_cloud.api.dagster_cloud_api import StoreEventBatchRequest
59
+ from dagster_cloud.metrics.tracer import Tracer
67
60
  from dagster_cloud.storage.event_logs.utils import truncate_event
68
61
  from dagster_cloud.util import compressed_namedtuple_upload_file
69
62
 
@@ -71,7 +64,11 @@ if TYPE_CHECKING:
71
64
  from dagster_cloud.instance import DagsterCloudAgentInstance
72
65
 
73
66
 
74
- from .queries import (
67
+ # Guard against out of order event insertion issues by default
68
+ DEFAULT_RUN_SCOPED_EVENT_TAILER_OFFSET = 20000
69
+
70
+
71
+ from dagster_cloud.storage.event_logs.queries import (
75
72
  ADD_DYNAMIC_PARTITIONS_MUTATION,
76
73
  CHECK_CONCURRENCY_CLAIM_QUERY,
77
74
  CLAIM_CONCURRENCY_SLOT_MUTATION,
@@ -79,6 +76,7 @@ from .queries import (
79
76
  DELETE_DYNAMIC_PARTITION_MUTATION,
80
77
  DELETE_EVENTS_MUTATION,
81
78
  ENABLE_SECONDARY_INDEX_MUTATION,
79
+ FETCH_FAILED_MATERIALIZATIONS_QUERY,
82
80
  FETCH_MATERIALIZATIONS_QUERY,
83
81
  FETCH_OBSERVATIONS_QUERY,
84
82
  FETCH_PLANNED_MATERIALIZATIONS,
@@ -87,6 +85,7 @@ from .queries import (
87
85
  GET_ALL_ASSET_KEYS_QUERY,
88
86
  GET_ASSET_CHECK_STATE_QUERY,
89
87
  GET_ASSET_RECORDS_QUERY,
88
+ GET_ASSET_STATUS_CACHE_VALUES,
90
89
  GET_CONCURRENCY_INFO_QUERY,
91
90
  GET_CONCURRENCY_KEYS_QUERY,
92
91
  GET_DYNAMIC_PARTITIONS_QUERY,
@@ -99,6 +98,10 @@ from .queries import (
99
98
  GET_LATEST_TAGS_BY_PARTITION,
100
99
  GET_MATERIALIZATION_COUNT_BY_PARTITION,
101
100
  GET_MATERIALIZED_PARTITIONS,
101
+ GET_MAXIMUM_RECORD_ID,
102
+ GET_PAGINATED_DYNAMIC_PARTITIONS_QUERY,
103
+ GET_POOL_CONFIG_QUERY,
104
+ GET_POOL_LIMITS_QUERY,
102
105
  GET_RECORDS_FOR_RUN_QUERY,
103
106
  GET_RUN_STATUS_CHANGE_EVENTS_QUERY,
104
107
  GET_STATS_FOR_RUN_QUERY,
@@ -116,12 +119,12 @@ from .queries import (
116
119
  UPGRADE_EVENT_LOG_STORAGE_MUTATION,
117
120
  WIPE_ASSET_CACHED_STATUS_DATA_MUTATION,
118
121
  WIPE_ASSET_MUTATION,
122
+ WIPE_ASSET_PARTITIONS_MUTATION,
119
123
  WIPE_EVENT_LOG_STORAGE_MUTATION,
120
124
  )
121
125
 
122
126
 
123
127
  def _input_for_event(event: EventLogEntry):
124
- event = truncate_event(event)
125
128
  return {
126
129
  "errorInfo": _input_for_serializable_error_info(event.error_info),
127
130
  "level": event.level,
@@ -172,7 +175,7 @@ def _input_for_dagster_event(dagster_event: Optional[DagsterEvent]):
172
175
  }
173
176
 
174
177
 
175
- def _event_log_entry_from_graphql(graphene_event_log_entry: Dict) -> EventLogEntry:
178
+ def _event_log_entry_from_graphql(graphene_event_log_entry: dict) -> EventLogEntry:
176
179
  check.dict_param(graphene_event_log_entry, "graphene_event_log_entry")
177
180
 
178
181
  return EventLogEntry(
@@ -202,7 +205,7 @@ def _event_log_entry_from_graphql(graphene_event_log_entry: Dict) -> EventLogEnt
202
205
 
203
206
  def _get_event_records_filter_input(
204
207
  event_records_filter,
205
- ) -> Optional[Dict[str, Any]]:
208
+ ) -> Optional[dict[str, Any]]:
206
209
  check.opt_inst_param(event_records_filter, "event_records_filter", EventRecordsFilter)
207
210
 
208
211
  if event_records_filter is None:
@@ -254,7 +257,7 @@ def _get_event_records_filter_input(
254
257
 
255
258
  def _get_asset_records_filter_input(
256
259
  records_filter: Union[AssetKey, AssetRecordsFilter],
257
- ) -> Dict[str, Any]:
260
+ ) -> dict[str, Any]:
258
261
  check.opt_inst_param(records_filter, "records_filter", (AssetKey, AssetRecordsFilter))
259
262
 
260
263
  if isinstance(records_filter, AssetKey):
@@ -282,7 +285,7 @@ def _get_asset_records_filter_input(
282
285
  }
283
286
 
284
287
 
285
- def _fetch_run_status_changes_filter_input(records_filter) -> Dict[str, Any]:
288
+ def _fetch_run_status_changes_filter_input(records_filter) -> dict[str, Any]:
286
289
  check.inst_param(
287
290
  records_filter, "records_filter", (DagsterEventType, RunStatusChangeRecordsFilter)
288
291
  )
@@ -297,10 +300,11 @@ def _fetch_run_status_changes_filter_input(records_filter) -> Dict[str, Any]:
297
300
  "afterStorageId": records_filter.after_storage_id,
298
301
  "beforeStorageId": records_filter.before_storage_id,
299
302
  "storageIds": records_filter.storage_ids,
303
+ "jobNames": records_filter.job_names,
300
304
  }
301
305
 
302
306
 
303
- def _event_record_from_graphql(graphene_event_record: Dict) -> EventLogRecord:
307
+ def _event_record_from_graphql(graphene_event_record: dict) -> EventLogRecord:
304
308
  check.dict_param(graphene_event_record, "graphene_event_record")
305
309
 
306
310
  return EventLogRecord(
@@ -309,7 +313,7 @@ def _event_record_from_graphql(graphene_event_record: Dict) -> EventLogRecord:
309
313
  )
310
314
 
311
315
 
312
- def _asset_entry_from_graphql(graphene_asset_entry: Dict) -> AssetEntry:
316
+ def _asset_entry_from_graphql(graphene_asset_entry: dict) -> AssetEntry:
313
317
  check.dict_param(graphene_asset_entry, "graphene_asset_entry")
314
318
  return AssetEntry(
315
319
  asset_key=AssetKey(graphene_asset_entry["assetKey"]["path"]),
@@ -351,10 +355,14 @@ def _asset_entry_from_graphql(graphene_asset_entry: Dict) -> AssetEntry:
351
355
  if graphene_asset_entry["lastObservationRecord"]
352
356
  else None
353
357
  ),
358
+ last_planned_materialization_storage_id=graphene_asset_entry[
359
+ "lastPlannedMaterializationStorageId"
360
+ ],
361
+ last_planned_materialization_run_id=graphene_asset_entry["lastPlannedMaterializationRunId"],
354
362
  )
355
363
 
356
364
 
357
- def _asset_record_from_graphql(graphene_asset_record: Dict) -> AssetRecord:
365
+ def _asset_record_from_graphql(graphene_asset_record: dict) -> AssetRecord:
358
366
  check.dict_param(graphene_asset_record, "graphene_asset_record")
359
367
  return AssetRecord(
360
368
  storage_id=graphene_asset_record["storageId"],
@@ -362,8 +370,9 @@ def _asset_record_from_graphql(graphene_asset_record: Dict) -> AssetRecord:
362
370
  )
363
371
 
364
372
 
365
- def _asset_check_execution_record_from_graphql(data: Dict):
373
+ def _asset_check_execution_record_from_graphql(data: dict, key: AssetCheckKey):
366
374
  return AssetCheckExecutionRecord(
375
+ key=key,
367
376
  id=data["id"],
368
377
  run_id=data["runId"],
369
378
  status=AssetCheckExecutionRecordStatus(data["status"]),
@@ -373,25 +382,36 @@ def _asset_check_execution_record_from_graphql(data: Dict):
373
382
 
374
383
 
375
384
  def _asset_check_summary_record_from_graphql(
376
- graphene_asset_check_summary_record: Dict,
385
+ graphene_asset_check_summary_record: dict,
377
386
  ) -> AssetCheckSummaryRecord:
378
387
  check.dict_param(graphene_asset_check_summary_record, "graphene_asset_check_summary_record")
388
+
389
+ asset_check_key = AssetCheckKey.from_graphql_input(
390
+ graphene_asset_check_summary_record["assetCheckKey"]
391
+ )
379
392
  return AssetCheckSummaryRecord(
380
- asset_check_key=AssetCheckKey.from_graphql_input(
381
- graphene_asset_check_summary_record["assetCheckKey"]
382
- ),
393
+ asset_check_key=asset_check_key,
383
394
  last_check_execution_record=(
384
395
  _asset_check_execution_record_from_graphql(
385
- graphene_asset_check_summary_record["lastCheckExecutionRecord"]
396
+ graphene_asset_check_summary_record["lastCheckExecutionRecord"],
397
+ asset_check_key,
386
398
  )
387
399
  if graphene_asset_check_summary_record["lastCheckExecutionRecord"]
388
400
  else None
389
401
  ),
402
+ last_completed_check_execution_record=(
403
+ _asset_check_execution_record_from_graphql(
404
+ graphene_asset_check_summary_record["lastCompletedCheckExecutionRecord"],
405
+ asset_check_key,
406
+ )
407
+ if graphene_asset_check_summary_record["lastCompletedCheckExecutionRecord"]
408
+ else None
409
+ ),
390
410
  last_run_id=graphene_asset_check_summary_record["lastRunId"],
391
411
  )
392
412
 
393
413
 
394
- def _event_records_result_from_graphql(graphene_event_records_result: Dict) -> EventRecordsResult:
414
+ def _event_records_result_from_graphql(graphene_event_records_result: dict) -> EventRecordsResult:
395
415
  return EventRecordsResult(
396
416
  records=[
397
417
  _event_record_from_graphql(record)
@@ -402,19 +422,19 @@ def _event_records_result_from_graphql(graphene_event_records_result: Dict) -> E
402
422
  )
403
423
 
404
424
 
405
- def _claimed_slot_from_graphql(graphene_claimed_slot_dict: Dict) -> ClaimedSlotInfo:
425
+ def _claimed_slot_from_graphql(graphene_claimed_slot_dict: dict) -> ClaimedSlotInfo:
406
426
  check.dict_param(graphene_claimed_slot_dict, "graphene_claimed_slot_dict")
407
427
  return ClaimedSlotInfo(
408
- graphene_claimed_slot_dict["runId"],
409
- graphene_claimed_slot_dict["stepKey"],
428
+ run_id=graphene_claimed_slot_dict["runId"],
429
+ step_key=graphene_claimed_slot_dict["stepKey"],
410
430
  )
411
431
 
412
432
 
413
- def _pending_step_from_graphql(graphene_pending_step: Dict) -> PendingStepInfo:
433
+ def _pending_step_from_graphql(graphene_pending_step: dict) -> PendingStepInfo:
414
434
  check.dict_param(graphene_pending_step, "graphene_pending_step")
415
435
  return PendingStepInfo(
416
- graphene_pending_step["runId"],
417
- graphene_pending_step["stepKey"],
436
+ run_id=graphene_pending_step["runId"],
437
+ step_key=graphene_pending_step["stepKey"],
418
438
  enqueued_timestamp=datetime_from_timestamp(graphene_pending_step["enqueuedTimestamp"]),
419
439
  assigned_timestamp=(
420
440
  datetime_from_timestamp(graphene_pending_step["assignedTimestamp"])
@@ -432,6 +452,7 @@ class GraphQLEventLogStorage(EventLogStorage, ConfigurableClass):
432
452
  """
433
453
  self._inst_data = check.opt_inst_param(inst_data, "inst_data", ConfigurableClassData)
434
454
  self._override_graphql_client = override_graphql_client
455
+ self._tracer = Tracer()
435
456
 
436
457
  @property
437
458
  def inst_data(self):
@@ -450,14 +471,12 @@ class GraphQLEventLogStorage(EventLogStorage, ConfigurableClass):
450
471
  return (
451
472
  self._override_graphql_client
452
473
  if self._override_graphql_client
453
- else self._instance.graphql_client
474
+ else self._instance.graphql_client # pyright: ignore[reportAttributeAccessIssue]
454
475
  )
455
476
 
456
477
  @property
457
478
  def agent_instance(self) -> "DagsterCloudAgentInstance":
458
- from dagster_cloud.instance import DagsterCloudAgentInstance
459
-
460
- return cast(DagsterCloudAgentInstance, self._instance)
479
+ return cast("DagsterCloudAgentInstance", self._instance)
461
480
 
462
481
  def _execute_query(self, query, variables=None, headers=None, idempotent_mutation=False):
463
482
  return self._graphql_client.execute(
@@ -467,11 +486,15 @@ class GraphQLEventLogStorage(EventLogStorage, ConfigurableClass):
467
486
  idempotent_mutation=idempotent_mutation,
468
487
  )
469
488
 
489
+ def get_maximum_record_id(self) -> Optional[int]:
490
+ res = self._execute_query(GET_MAXIMUM_RECORD_ID)
491
+ return res["data"]["eventLogs"]["getMaximumRecordId"]
492
+
470
493
  def get_records_for_run(
471
494
  self,
472
495
  run_id: str,
473
496
  cursor: Optional[str] = None,
474
- of_type: Optional[Union[DagsterEventType, Set[DagsterEventType]]] = None,
497
+ of_type: Optional[Union[DagsterEventType, set[DagsterEventType]]] = None,
475
498
  limit: Optional[int] = None,
476
499
  ascending: bool = True,
477
500
  ) -> EventLogConnection:
@@ -485,12 +508,12 @@ class GraphQLEventLogStorage(EventLogStorage, ConfigurableClass):
485
508
  "runId": check.str_param(run_id, "run_id"),
486
509
  "cursor": check.opt_str_param(cursor, "cursor"),
487
510
  "ofType": (
488
- cast(DagsterEventType, of_type).value
511
+ cast("DagsterEventType", of_type).value
489
512
  if of_type and not is_of_type_set
490
513
  else None
491
514
  ),
492
515
  "ofTypes": (
493
- [dagster_type.value for dagster_type in cast(set, of_type)]
516
+ [dagster_type.value for dagster_type in cast("set", of_type)]
494
517
  if of_type and is_of_type_set
495
518
  else None
496
519
  ),
@@ -524,8 +547,8 @@ class GraphQLEventLogStorage(EventLogStorage, ConfigurableClass):
524
547
  )
525
548
 
526
549
  def get_step_stats_for_run( # pyright: ignore[reportIncompatibleMethodOverride], fix me!
527
- self, run_id: str, step_keys: Optional[List[str]] = None
528
- ) -> List[RunStepKeyStatsSnapshot]:
550
+ self, run_id: str, step_keys: Optional[list[str]] = None
551
+ ) -> list[RunStepKeyStatsSnapshot]:
529
552
  res = self._execute_query(
530
553
  GET_STEP_STATS_FOR_RUN_QUERY,
531
554
  variables={
@@ -579,24 +602,55 @@ class GraphQLEventLogStorage(EventLogStorage, ConfigurableClass):
579
602
  files={"store_events.tmp": f},
580
603
  )
581
604
 
605
+ def _add_metric_header(self, headers: dict[str, str]):
606
+ if os.getenv("DISABLE_DAGSTER_CLOUD_STORE_EVENT_SEND_METRICS") is not None:
607
+ return
608
+
609
+ try:
610
+ span_metric = self._tracer.pop_serialized_span()
611
+ if span_metric:
612
+ headers["Dagster-Cloud-Metric"] = span_metric
613
+ except:
614
+ pass
615
+
616
+ @contextmanager
617
+ def _event_span(self, event: EventLogEntry):
618
+ if os.getenv("DISABLE_DAGSTER_CLOUD_STORE_EVENT_SEND_METRICS") is not None:
619
+ yield
620
+ else:
621
+ with self._tracer.start_span("store-event") as event_span:
622
+ event_span.set_attribute(
623
+ "event_type",
624
+ event.dagster_event.event_type_value
625
+ if event.dagster_event is not None
626
+ else "user",
627
+ )
628
+ event_span.set_attribute("run_id", event.run_id)
629
+ yield
630
+
582
631
  def store_event(self, event: EventLogEntry):
583
632
  check.inst_param(event, "event", EventLogEntry)
584
- headers = {"Idempotency-Key": str(uuid4())}
633
+ headers = {"Idempotency-Key": str(uuid4()), "X-Event-Write": "true"}
585
634
 
586
- if os.getenv("DAGSTER_CLOUD_STORE_EVENT_OVER_HTTP"):
587
- self._store_events_http(headers, [event])
588
- else:
589
- self._execute_query(
590
- STORE_EVENT_MUTATION,
591
- variables={
592
- "eventRecord": _input_for_event(event),
593
- },
594
- headers=headers,
595
- )
635
+ self._add_metric_header(headers)
636
+
637
+ event = truncate_event(event)
638
+
639
+ with self._event_span(event):
640
+ if os.getenv("DAGSTER_CLOUD_STORE_EVENT_OVER_HTTP"):
641
+ self._store_events_http(headers, [event])
642
+ else:
643
+ self._execute_query(
644
+ STORE_EVENT_MUTATION,
645
+ variables={
646
+ "eventRecord": _input_for_event(event),
647
+ },
648
+ headers=headers,
649
+ )
596
650
 
597
651
  def store_event_batch(self, events: Sequence[EventLogEntry]):
598
652
  check.sequence_param(events, "events", of_type=EventLogEntry)
599
- headers = {"Idempotency-Key": str(uuid4())}
653
+ headers = {"Idempotency-Key": str(uuid4()), "X-Event-Write": "true"}
600
654
 
601
655
  if os.getenv("DAGSTER_CLOUD_STORE_EVENT_OVER_HTTP"):
602
656
  self._store_events_http(headers, events)
@@ -683,6 +737,18 @@ class GraphQLEventLogStorage(EventLogStorage, ConfigurableClass):
683
737
  def asset_records_have_last_observation(self) -> bool:
684
738
  return True
685
739
 
740
+ @property
741
+ def supports_partition_subset_in_asset_materialization_planned_events(self) -> bool:
742
+ return True
743
+
744
+ @property
745
+ def asset_records_have_last_planned_and_failed_materializations(self) -> bool:
746
+ return True
747
+
748
+ @property
749
+ def can_store_asset_failure_events(self) -> bool:
750
+ return True
751
+
686
752
  def get_asset_check_summary_records(
687
753
  self, asset_check_keys: Sequence[AssetCheckKey]
688
754
  ) -> Mapping[AssetCheckKey, AssetCheckSummaryRecord]:
@@ -714,6 +780,11 @@ class GraphQLEventLogStorage(EventLogStorage, ConfigurableClass):
714
780
  for result in res["data"]["eventLogs"]["getAssetRecords"]
715
781
  ]
716
782
 
783
+ def get_freshness_state_records(
784
+ self, keys: Sequence[AssetKey]
785
+ ) -> Mapping[AssetKey, FreshnessStateRecord]:
786
+ raise NotImplementedError("Not callable from user cloud")
787
+
717
788
  def has_asset_key(self, asset_key: AssetKey):
718
789
  check.inst_param(asset_key, "asset_key", AssetKey)
719
790
 
@@ -770,7 +841,7 @@ class GraphQLEventLogStorage(EventLogStorage, ConfigurableClass):
770
841
  asset_key: AssetKey,
771
842
  before_cursor: Optional[int] = None,
772
843
  after_cursor: Optional[int] = None,
773
- ) -> Set[str]:
844
+ ) -> set[str]:
774
845
  check.inst_param(asset_key, "asset_key", AssetKey)
775
846
  check.opt_int_param(before_cursor, "before_cursor")
776
847
  check.opt_int_param(after_cursor, "after_cursor")
@@ -805,7 +876,7 @@ class GraphQLEventLogStorage(EventLogStorage, ConfigurableClass):
805
876
  "getMaterializationCountByPartition"
806
877
  ]
807
878
 
808
- materialization_count_by_partition: Dict[AssetKey, Dict[str, int]] = {
879
+ materialization_count_by_partition: dict[AssetKey, dict[str, int]] = {
809
880
  asset_key: {} for asset_key in asset_keys
810
881
  }
811
882
  for asset_count in materialization_count_result:
@@ -818,17 +889,21 @@ class GraphQLEventLogStorage(EventLogStorage, ConfigurableClass):
818
889
  return materialization_count_by_partition
819
890
 
820
891
  def get_latest_storage_id_by_partition(
821
- self, asset_key: AssetKey, event_type: DagsterEventType
892
+ self,
893
+ asset_key: AssetKey,
894
+ event_type: DagsterEventType,
895
+ partitions: Optional[set[str]] = None,
822
896
  ) -> Mapping[str, int]:
823
897
  res = self._execute_query(
824
898
  GET_LATEST_STORAGE_ID_BY_PARTITION,
825
899
  variables={
826
900
  "assetKey": asset_key.to_string(),
827
901
  "eventType": event_type.value,
902
+ "partitions": list(partitions) if partitions else None,
828
903
  },
829
904
  )
830
905
  latest_storage_id_result = res["data"]["eventLogs"]["getLatestStorageIdByPartition"]
831
- latest_storage_id_by_partition: Dict[str, int] = {}
906
+ latest_storage_id_by_partition: dict[str, int] = {}
832
907
 
833
908
  for graphene_latest_storage_id in latest_storage_id_result:
834
909
  latest_storage_id_by_partition[graphene_latest_storage_id["partition"]] = (
@@ -858,7 +933,7 @@ class GraphQLEventLogStorage(EventLogStorage, ConfigurableClass):
858
933
  },
859
934
  )
860
935
  latest_tags_by_partition_result = res["data"]["eventLogs"]["getLatestTagsByPartition"]
861
- latest_tags_by_partition: Dict[str, Dict[str, str]] = defaultdict(dict)
936
+ latest_tags_by_partition: dict[str, dict[str, str]] = defaultdict(dict)
862
937
  for tag_by_partition in latest_tags_by_partition_result:
863
938
  latest_tags_by_partition[tag_by_partition["partition"]][tag_by_partition["key"]] = (
864
939
  tag_by_partition["value"]
@@ -869,7 +944,7 @@ class GraphQLEventLogStorage(EventLogStorage, ConfigurableClass):
869
944
 
870
945
  def get_latest_asset_partition_materialization_attempts_without_materializations(
871
946
  self, asset_key: AssetKey, after_storage_id: Optional[int] = None
872
- ) -> Mapping[str, Tuple[str, int]]:
947
+ ) -> Mapping[str, tuple[str, int]]:
873
948
  res = self._execute_query(
874
949
  GET_LATEST_ASSET_PARTITION_MATERIALIZATION_ATTEMPTS_WITHOUT_MATERIALIZATIONS,
875
950
  variables={
@@ -882,7 +957,7 @@ class GraphQLEventLogStorage(EventLogStorage, ConfigurableClass):
882
957
  ]
883
958
 
884
959
  # Translate list to tuple
885
- return {key: tuple(val) for key, val in result.items()} # type: ignore
960
+ return {key: tuple(val) for key, val in result.items()}
886
961
 
887
962
  def get_event_tags_for_asset(
888
963
  self,
@@ -917,6 +992,28 @@ class GraphQLEventLogStorage(EventLogStorage, ConfigurableClass):
917
992
  )
918
993
  return res["data"]["eventLogs"]["getDynamicPartitions"]
919
994
 
995
+ def get_paginated_dynamic_partitions(
996
+ self, partitions_def_name: str, limit: int, ascending: bool, cursor: Optional[str] = None
997
+ ) -> PaginatedResults[str]:
998
+ check.str_param(partitions_def_name, "partitions_def_name")
999
+ check.int_param(limit, "limit")
1000
+ check.bool_param(ascending, "ascending")
1001
+ check.opt_str_param(cursor, "cursor")
1002
+ res = self._execute_query(
1003
+ GET_PAGINATED_DYNAMIC_PARTITIONS_QUERY,
1004
+ variables={
1005
+ "partitionsDefName": partitions_def_name,
1006
+ "limit": limit,
1007
+ "ascending": ascending,
1008
+ "cursor": cursor,
1009
+ },
1010
+ )
1011
+ return PaginatedResults(
1012
+ results=res["data"]["eventLogs"]["getPaginatedDynamicPartitions"]["results"],
1013
+ cursor=res["data"]["eventLogs"]["getPaginatedDynamicPartitions"]["cursor"],
1014
+ has_more=res["data"]["eventLogs"]["getPaginatedDynamicPartitions"]["hasMore"],
1015
+ )
1016
+
920
1017
  def has_dynamic_partition(self, partitions_def_name: str, partition_key: str) -> bool:
921
1018
  check.str_param(partitions_def_name, "partitions_def_name")
922
1019
  check.str_param(partition_key, "partition_key")
@@ -959,11 +1056,16 @@ class GraphQLEventLogStorage(EventLogStorage, ConfigurableClass):
959
1056
  )
960
1057
  return res
961
1058
 
962
- def wipe_asset_partitions(self, asset_key: AssetKey, partition_keys: Sequence[str]) -> None:
1059
+ def wipe_asset_partitions(self, asset_key: AssetKey, partition_keys: Sequence[str]):
963
1060
  """Remove asset index history from event log for given asset partitions."""
964
- raise NotImplementedError(
965
- "Partitioned asset wipe is not supported yet for this event log storage."
1061
+ res = self._execute_query(
1062
+ WIPE_ASSET_PARTITIONS_MUTATION,
1063
+ variables={
1064
+ "assetKey": asset_key.to_string(),
1065
+ "partitionKeys": partition_keys,
1066
+ },
966
1067
  )
1068
+ return res
967
1069
 
968
1070
  @property
969
1071
  def supports_global_concurrency_limits(self) -> bool:
@@ -1017,7 +1119,7 @@ class GraphQLEventLogStorage(EventLogStorage, ConfigurableClass):
1017
1119
  else:
1018
1120
  raise DagsterCloudAgentServerError(res)
1019
1121
 
1020
- def get_concurrency_keys(self) -> Set[str]:
1122
+ def get_concurrency_keys(self) -> set[str]:
1021
1123
  res = self._execute_query(GET_CONCURRENCY_KEYS_QUERY)
1022
1124
  return set(res["data"]["eventLogs"]["getConcurrencyKeys"])
1023
1125
 
@@ -1033,6 +1135,8 @@ class GraphQLEventLogStorage(EventLogStorage, ConfigurableClass):
1033
1135
  slot_count=info["slotCount"],
1034
1136
  claimed_slots=[_claimed_slot_from_graphql(slot) for slot in info["claimedSlots"]],
1035
1137
  pending_steps=[_pending_step_from_graphql(step) for step in info["pendingSteps"]],
1138
+ limit=info["limit"],
1139
+ using_default_limit=bool(info["usingDefaultLimit"]),
1036
1140
  )
1037
1141
 
1038
1142
  def claim_concurrency_slot(
@@ -1100,7 +1204,7 @@ class GraphQLEventLogStorage(EventLogStorage, ConfigurableClass):
1100
1204
  ),
1101
1205
  )
1102
1206
 
1103
- def get_concurrency_run_ids(self) -> Set[str]:
1207
+ def get_concurrency_run_ids(self) -> set[str]:
1104
1208
  raise NotImplementedError("Not callable from user cloud")
1105
1209
 
1106
1210
  def free_concurrency_slots_for_run(self, run_id: str) -> None:
@@ -1127,6 +1231,7 @@ class GraphQLEventLogStorage(EventLogStorage, ConfigurableClass):
1127
1231
  check_key: AssetCheckKey,
1128
1232
  limit: int,
1129
1233
  cursor: Optional[int] = None,
1234
+ status: Optional[AbstractSet[AssetCheckExecutionRecordStatus]] = None,
1130
1235
  ) -> Sequence[AssetCheckExecutionRecord]:
1131
1236
  raise NotImplementedError("Not callable from user cloud")
1132
1237
 
@@ -1153,6 +1258,26 @@ class GraphQLEventLogStorage(EventLogStorage, ConfigurableClass):
1153
1258
  )
1154
1259
  return _event_records_result_from_graphql(res["data"]["eventLogs"]["fetchMaterializations"])
1155
1260
 
1261
+ def fetch_failed_materializations(
1262
+ self,
1263
+ records_filter: Union[AssetKey, AssetRecordsFilter],
1264
+ limit: int,
1265
+ cursor: Optional[str] = None,
1266
+ ascending: bool = False,
1267
+ ) -> EventRecordsResult:
1268
+ res = self._execute_query(
1269
+ FETCH_FAILED_MATERIALIZATIONS_QUERY,
1270
+ variables={
1271
+ "recordsFilter": _get_asset_records_filter_input(records_filter),
1272
+ "limit": limit,
1273
+ "cursor": cursor,
1274
+ "ascending": ascending,
1275
+ },
1276
+ )
1277
+ return _event_records_result_from_graphql(
1278
+ res["data"]["eventLogs"]["fetchFailedMaterializations"]
1279
+ )
1280
+
1156
1281
  def fetch_observations(
1157
1282
  self,
1158
1283
  records_filter: Union[AssetKey, AssetRecordsFilter],
@@ -1191,6 +1316,10 @@ class GraphQLEventLogStorage(EventLogStorage, ConfigurableClass):
1191
1316
  res["data"]["eventLogs"]["fetchPlannedMaterializations"]
1192
1317
  )
1193
1318
 
1319
+ @property
1320
+ def supports_run_status_change_job_name_filter(self):
1321
+ return True
1322
+
1194
1323
  def fetch_run_status_changes(
1195
1324
  self,
1196
1325
  records_filter: Union[DagsterEventType, RunStatusChangeRecordsFilter],
@@ -1233,7 +1362,7 @@ class GraphQLEventLogStorage(EventLogStorage, ConfigurableClass):
1233
1362
 
1234
1363
  def get_updated_data_version_partitions(
1235
1364
  self, asset_key: AssetKey, partitions: Iterable[str], since_storage_id: int
1236
- ) -> Set[str]:
1365
+ ) -> set[str]:
1237
1366
  res = self._execute_query(
1238
1367
  GET_UPDATED_DATA_VERSION_PARTITIONS,
1239
1368
  variables={
@@ -1244,3 +1373,67 @@ class GraphQLEventLogStorage(EventLogStorage, ConfigurableClass):
1244
1373
  )
1245
1374
  partitions = res["data"]["eventLogs"]["getUpdatedDataVersionPartitions"]
1246
1375
  return set(partitions)
1376
+
1377
+ @property
1378
+ def handles_run_events_in_store_event(self) -> bool:
1379
+ return True
1380
+
1381
+ def default_run_scoped_event_tailer_offset(self) -> int:
1382
+ return DEFAULT_RUN_SCOPED_EVENT_TAILER_OFFSET
1383
+
1384
+ def get_asset_status_cache_values(
1385
+ self,
1386
+ partitions_defs_by_key: Iterable[tuple[AssetKey, Optional[PartitionsDefinition]]],
1387
+ context,
1388
+ ) -> Sequence[Optional[AssetStatusCacheValue]]:
1389
+ asset_keys = [key for key, _ in partitions_defs_by_key]
1390
+ res = self._execute_query(
1391
+ GET_ASSET_STATUS_CACHE_VALUES,
1392
+ variables={
1393
+ "assetKeys": [asset_key.to_graphql_input() for asset_key in asset_keys],
1394
+ },
1395
+ )
1396
+
1397
+ return [
1398
+ AssetStatusCacheValue(
1399
+ latest_storage_id=value["latestStorageId"],
1400
+ partitions_def_id=value["partitionsDefId"],
1401
+ serialized_materialized_partition_subset=value[
1402
+ "serializedMaterializedPartitionSubset"
1403
+ ],
1404
+ serialized_failed_partition_subset=value["serializedFailedPartitionSubset"],
1405
+ serialized_in_progress_partition_subset=value[
1406
+ "serializedInProgressPartitionSubset"
1407
+ ],
1408
+ earliest_in_progress_materialization_event_id=value[
1409
+ "earliestInProgressMaterializationEventId"
1410
+ ],
1411
+ )
1412
+ if value
1413
+ else None
1414
+ for value in res["data"]["eventLogs"]["getAssetStatusCacheValues"]
1415
+ ]
1416
+
1417
+ def get_pool_config(self):
1418
+ res = self._execute_query(GET_POOL_CONFIG_QUERY)
1419
+ pool_config = res["data"]["eventLogs"]["getPoolConfig"]
1420
+ granularity_str = pool_config.get("poolGranularity")
1421
+
1422
+ return PoolConfig(
1423
+ pool_granularity=PoolGranularity(granularity_str) if granularity_str else None,
1424
+ default_pool_limit=pool_config.get("defaultPoolLimit"),
1425
+ op_granularity_run_buffer=pool_config.get("opGranularityRunBuffer"),
1426
+ )
1427
+
1428
+ def get_pool_limits(self) -> Sequence[PoolLimit]:
1429
+ """Get the set of concurrency limited keys and limits."""
1430
+ res = self._execute_query(GET_POOL_LIMITS_QUERY)
1431
+ limits = res["data"]["eventLogs"]["getPoolLimits"]
1432
+ return [
1433
+ PoolLimit(
1434
+ name=limit.get("name"),
1435
+ limit=limit.get("limit"),
1436
+ from_default=limit.get("fromDefault"),
1437
+ )
1438
+ for limit in limits
1439
+ ]