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,66 +1,51 @@
1
- from dagster._serdes import serialize_value
2
- from dagster._utils.error import SerializableErrorInfo
3
-
4
- ERROR_CLASS_NAME_SIZE_LIMIT = 1000
5
-
6
-
7
- def truncate_serialized_error(
8
- error_info: SerializableErrorInfo, field_size_limit: int, max_depth: int
9
- ):
10
- if error_info.cause:
11
- if max_depth == 0:
12
- new_cause = (
13
- error_info.cause
14
- if len(serialize_value(error_info.cause)) <= field_size_limit
15
- else SerializableErrorInfo(
16
- message="(Cause truncated due to size limitations)",
17
- stack=[],
18
- cls_name=None,
19
- )
20
- )
21
- else:
22
- new_cause = truncate_serialized_error(
23
- error_info.cause, field_size_limit, max_depth=max_depth - 1
24
- )
25
- error_info = error_info._replace(cause=new_cause)
26
-
27
- if error_info.context:
28
- if max_depth == 0:
29
- new_context = (
30
- error_info.context
31
- if len(serialize_value(error_info.context)) <= field_size_limit
32
- else SerializableErrorInfo(
33
- message="(Context truncated due to size limitations)",
34
- stack=[],
35
- cls_name=None,
36
- )
37
- )
38
- else:
39
- new_context = truncate_serialized_error(
40
- error_info.context, field_size_limit, max_depth=max_depth - 1
41
- )
42
- error_info = error_info._replace(context=new_context)
1
+ from collections.abc import Sequence
43
2
 
44
- stack_size_so_far = 0
45
- truncated_stack = []
46
- for stack_elem in error_info.stack:
47
- stack_size_so_far += len(stack_elem)
48
- if stack_size_so_far > field_size_limit:
49
- truncated_stack.append("(TRUNCATED)")
50
- break
51
-
52
- truncated_stack.append(stack_elem)
53
-
54
- error_info = error_info._replace(stack=truncated_stack)
55
-
56
- if len(error_info.message) > field_size_limit:
57
- error_info = error_info._replace(
58
- message=error_info.message[:field_size_limit] + " (TRUNCATED)"
59
- )
60
-
61
- if error_info.cls_name and len(error_info.cls_name) > ERROR_CLASS_NAME_SIZE_LIMIT:
62
- error_info = error_info._replace(
63
- cls_name=error_info.cls_name[:ERROR_CLASS_NAME_SIZE_LIMIT] + " (TRUNCATED)"
64
- )
3
+ from dagster._utils.error import SerializableErrorInfo
65
4
 
66
- return error_info
5
+ DAGSTER_FRAMEWORK_SUBSTRINGS = [
6
+ "/site-packages/dagster/",
7
+ "/python_modules/dagster/dagster",
8
+ ]
9
+
10
+
11
+ def remove_dagster_framework_lines_from_serializable_exc_info(error_info: SerializableErrorInfo):
12
+ return error_info._replace(
13
+ stack=remove_dagster_framework_lines_from_stack_trace(error_info.stack),
14
+ cause=(
15
+ remove_dagster_framework_lines_from_serializable_exc_info(error_info.cause)
16
+ if error_info.cause
17
+ else None
18
+ ),
19
+ context=(
20
+ remove_dagster_framework_lines_from_serializable_exc_info(error_info.context)
21
+ if error_info.context
22
+ else None
23
+ ),
24
+ )
25
+
26
+
27
+ def remove_dagster_framework_lines_from_stack_trace(
28
+ stack: Sequence[str],
29
+ ) -> Sequence[str]:
30
+ # Remove lines until you find the first non-dagster framework line
31
+
32
+ for i in range(len(stack)):
33
+ if not _line_contains_dagster_framework_file(stack[i]):
34
+ return stack[i:]
35
+
36
+ # Return the full stack trace if its all Dagster framework lines,
37
+ # to not be left with an empty stack trace
38
+ return stack
39
+
40
+
41
+ def _line_contains_dagster_framework_file(line: str):
42
+ # stack trace line starts with something like
43
+ # File "/usr/local/lib/python3.11/site-packages/dagster/_core/execution/plan/utils.py",
44
+ split_by_comma = line.split(",")
45
+ if not split_by_comma:
46
+ return False
47
+
48
+ file_portion = split_by_comma[0]
49
+ return any(
50
+ framework_substring in file_portion for framework_substring in DAGSTER_FRAMEWORK_SUBSTRINGS
51
+ )
dagster_cloud/version.py CHANGED
@@ -1 +1 @@
1
- __version__ = "1.8.2"
1
+ __version__ = "1.12.6"
@@ -1,4 +1,4 @@
1
- from typing import Any, Dict, List, Optional, cast
1
+ from typing import Any, Optional, cast
2
2
 
3
3
  from dagster import (
4
4
  Enum as DagsterEnum,
@@ -13,15 +13,15 @@ from dagster import (
13
13
  )
14
14
  from dagster._config import EvaluationError, StringSource, validate_config
15
15
 
16
- from .docker import SHARED_DOCKER_CONFIG
17
- from .ecs import (
16
+ from dagster_cloud.workspace.config_schema.docker import SHARED_DOCKER_CONFIG
17
+ from dagster_cloud.workspace.config_schema.ecs import (
18
18
  ECS_CONTAINER_CONTEXT_CONFIG as ECS_CONTAINER_CONTEXT_CONFIG,
19
19
  SHARED_ECS_CONFIG as SHARED_ECS_CONFIG,
20
20
  )
21
- from .kubernetes import SHARED_K8S_CONFIG
21
+ from dagster_cloud.workspace.config_schema.kubernetes import SHARED_K8S_CONFIG
22
22
 
23
23
 
24
- def validate_workspace_location(workspace_location) -> Optional[List[str]]:
24
+ def validate_workspace_location(workspace_location) -> Optional[list[str]]:
25
25
  """Processes a single workspace location config. Returns a list of error
26
26
  messages if any.
27
27
  """
@@ -29,7 +29,7 @@ def validate_workspace_location(workspace_location) -> Optional[List[str]]:
29
29
  return [error.message for error in validation.errors or []]
30
30
 
31
31
 
32
- def validate_workspace_config(workspace_config) -> Optional[List[str]]:
32
+ def validate_workspace_config(workspace_config) -> Optional[list[str]]:
33
33
  """Processes an entire workspace location config. Returns a list of
34
34
  error messages, if any.
35
35
  """
@@ -37,7 +37,7 @@ def validate_workspace_config(workspace_config) -> Optional[List[str]]:
37
37
  return [error.message for error in validation.errors or []]
38
38
 
39
39
 
40
- def process_workspace_config(workspace_config) -> Dict[str, Any]:
40
+ def process_workspace_config(workspace_config) -> dict[str, Any]:
41
41
  """Checks a workspace config, erroring if any mismatches with config
42
42
  and migrating an input in the legacy workspace config format to the
43
43
  modern format, returning the validated input.
@@ -62,8 +62,21 @@ def process_workspace_config(workspace_config) -> Dict[str, Any]:
62
62
  python_file = config.get("python_file")
63
63
  package_name = config.get("package_name")
64
64
  module_name = config.get("module_name")
65
+ autoload_defs_module_name = config.get("autoload_defs_module_name")
65
66
  check.invariant(
66
- len([val for val in [python_file, package_name, module_name] if val]) == 1,
67
+ len(
68
+ [
69
+ val
70
+ for val in [
71
+ python_file,
72
+ package_name,
73
+ module_name,
74
+ autoload_defs_module_name,
75
+ ]
76
+ if val
77
+ ]
78
+ )
79
+ == 1,
67
80
  "Must supply exactly one of a file name, a package name, or a module name",
68
81
  )
69
82
 
@@ -73,7 +86,8 @@ def process_workspace_config(workspace_config) -> Dict[str, Any]:
73
86
  new_location = {
74
87
  k: v
75
88
  for k, v in location.items()
76
- if k not in ("python_file", "package_name", "module_name")
89
+ if k
90
+ not in ("python_file", "package_name", "module_name", "autoload_defs_module_name")
77
91
  }
78
92
  new_location["code_source"] = {}
79
93
  if "python_file" in location:
@@ -82,6 +96,10 @@ def process_workspace_config(workspace_config) -> Dict[str, Any]:
82
96
  new_location["code_source"]["package_name"] = location["package_name"]
83
97
  elif "module_name" in location:
84
98
  new_location["code_source"]["module_name"] = location["module_name"]
99
+ elif "autoload_defs_module_name" in location:
100
+ new_location["code_source"]["autoload_defs_module_name"] = location[
101
+ "autoload_defs_module_name"
102
+ ]
85
103
 
86
104
  new_location["location_name"] = name
87
105
  updated_locations.append(new_location)
@@ -90,10 +108,12 @@ def process_workspace_config(workspace_config) -> Dict[str, Any]:
90
108
  check.is_list(workspace_config.get("locations"))
91
109
 
92
110
  validation = validate_config(WORKSPACE_CONFIG_SCHEMA, workspace_config)
93
- check.invariant(
94
- validation.success,
95
- ", ".join([error.message for error in cast(List[EvaluationError], validation.errors)]),
96
- )
111
+ if not validation.success:
112
+ check.failed(
113
+ ", ".join(
114
+ [error.message for error in cast("list[EvaluationError]", validation.errors)]
115
+ ),
116
+ )
97
117
  return workspace_config
98
118
 
99
119
 
@@ -139,6 +159,8 @@ K8S_CONFIG_FIELDS = {
139
159
  ),
140
160
  is_required=False,
141
161
  ),
162
+ "deployment_metadata": Permissive(),
163
+ "service_metadata": Permissive(),
142
164
  }
143
165
  ),
144
166
  is_required=False,
@@ -190,6 +212,10 @@ CONFIG_SCHEMA_FIELDS = {
190
212
  config=str,
191
213
  description="Python module containing the target Dagster repository.",
192
214
  ),
215
+ "autoload_defs_module_name": Field(
216
+ config=str,
217
+ description="Python module to automatically load Dagster definitions from.",
218
+ ),
193
219
  },
194
220
  ),
195
221
  description="Python entry point for the code location.",
@@ -250,6 +276,17 @@ CONFIG_SCHEMA_FIELDS = {
250
276
  is_required=False,
251
277
  description="Locations that specify an agent queue will only have their requests handled by agents configured to read from a matching queue. By default, requests are placed on a default queue that's handled by all agents.",
252
278
  ),
279
+ "defs_state_info": Field(
280
+ config=Shape(
281
+ fields={
282
+ "info_mapping": Map(
283
+ str, Noneable(Shape(fields={"version": str, "create_timestamp": float}))
284
+ )
285
+ },
286
+ ),
287
+ is_required=False,
288
+ description="Defs state info for the code location.",
289
+ ),
253
290
  }
254
291
 
255
292
 
@@ -278,6 +315,11 @@ LEGACY_CONFIG_SCHEMA_FIELDS = {
278
315
  is_required=False,
279
316
  description="Python module containing the target Dagster repository.",
280
317
  ),
318
+ "autoload_defs_module_name": Field(
319
+ config=str,
320
+ is_required=False,
321
+ description="Python module to automatically load Dagster definitions from.",
322
+ ),
281
323
  }
282
324
  LEGACY_LOCATION_CONFIG_SCHEMA = Shape(fields=LEGACY_CONFIG_SCHEMA_FIELDS)
283
325
  LEGACY_NAMED_LOCATIONS_CONFIG_SCHEMA = Map(
@@ -1,7 +1,7 @@
1
1
  import logging
2
2
  import sys
3
3
  import time
4
- from typing import Any, Dict, List, NamedTuple, Optional, Tuple
4
+ from typing import Any, NamedTuple, Optional
5
5
 
6
6
  import docker
7
7
  import docker.errors
@@ -10,36 +10,44 @@ from dagster import (
10
10
  IntSource,
11
11
  _check as check,
12
12
  )
13
+ from dagster._core.launcher.base import LaunchRunContext
13
14
  from dagster._core.utils import parse_env_var
15
+ from dagster._grpc.types import ExecuteRunArgs
14
16
  from dagster._serdes import ConfigurableClass
15
17
  from dagster._serdes.config_class import ConfigurableClassData
16
18
  from dagster._utils import find_free_port
17
19
  from dagster._utils.merger import merge_dicts
18
20
  from dagster._vendored.dateutil.parser import parse
21
+ from dagster_cloud_cli.core.workspace import PexMetadata
19
22
  from dagster_docker import DockerRunLauncher
20
23
  from dagster_docker.container_context import DockerContainerContext
24
+ from dagster_shared.serdes.serdes import deserialize_value, serialize_value
21
25
  from docker.models.containers import Container
22
26
  from typing_extensions import Self
23
27
 
24
28
  from dagster_cloud.api.dagster_cloud_api import UserCodeDeploymentType
25
29
  from dagster_cloud.execution.monitoring import CloudContainerResourceLimits
26
- from dagster_cloud.workspace.user_code_launcher.user_code_launcher import UserCodeLauncherEntry
27
-
28
- from ..config_schema.docker import SHARED_DOCKER_CONFIG
29
- from ..user_code_launcher import (
30
+ from dagster_cloud.storage.tags import PEX_METADATA_TAG
31
+ from dagster_cloud.workspace.config_schema.docker import SHARED_DOCKER_CONFIG
32
+ from dagster_cloud.workspace.docker.utils import unique_docker_resource_name
33
+ from dagster_cloud.workspace.user_code_launcher import (
30
34
  DEFAULT_SERVER_PROCESS_STARTUP_TIMEOUT,
31
35
  SHARED_USER_CODE_LAUNCHER_CONFIG,
32
36
  DagsterCloudGrpcServer,
33
37
  DagsterCloudUserCodeLauncher,
34
38
  ServerEndpoint,
35
39
  )
36
- from ..user_code_launcher.utils import deterministic_label_for_location
37
- from .utils import unique_docker_resource_name
40
+ from dagster_cloud.workspace.user_code_launcher.user_code_launcher import UserCodeLauncherEntry
41
+ from dagster_cloud.workspace.user_code_launcher.utils import (
42
+ deterministic_label_for_location,
43
+ get_grpc_server_env,
44
+ )
38
45
 
39
46
  GRPC_SERVER_LABEL = "dagster_grpc_server"
40
47
  MULTIPEX_SERVER_LABEL = "dagster_multipex_server"
41
48
  AGENT_LABEL = "dagster_agent_id"
42
49
  SERVER_TIMESTAMP_LABEL = "dagster_server_timestamp"
50
+ STOP_TIMEOUT_LABEL = "dagster_stop_timeout"
43
51
 
44
52
 
45
53
  IMAGE_PULL_LOG_INTERVAL = 15
@@ -52,7 +60,7 @@ class DagsterDockerContainer(NamedTuple):
52
60
 
53
61
  container: Container
54
62
 
55
- def __str__(self):
63
+ def __str__(self): # pyright: ignore[reportIncompatibleMethodOverride]
56
64
  return self.container.id
57
65
 
58
66
 
@@ -76,7 +84,7 @@ class DockerUserCodeLauncher(
76
84
  container_kwargs, "container_kwargs", key_type=str
77
85
  )
78
86
 
79
- super(DockerUserCodeLauncher, self).__init__(**kwargs)
87
+ super().__init__(**kwargs)
80
88
 
81
89
  @property
82
90
  def requires_images(self):
@@ -90,11 +98,11 @@ class DockerUserCodeLauncher(
90
98
  return self._inst_data
91
99
 
92
100
  @property
93
- def env_vars(self) -> List[str]:
101
+ def env_vars(self) -> list[str]:
94
102
  return self._input_env_vars + self._instance.dagster_cloud_api_env_vars
95
103
 
96
104
  @property
97
- def container_kwargs(self) -> Dict[str, Any]:
105
+ def container_kwargs(self) -> dict[str, Any]:
98
106
  return self._container_kwargs
99
107
 
100
108
  @classmethod
@@ -130,6 +138,10 @@ class DockerUserCodeLauncher(
130
138
  command,
131
139
  labels,
132
140
  ):
141
+ container_kwargs = {**container_context.container_kwargs}
142
+
143
+ container_kwargs.pop("stop_timeout", None)
144
+
133
145
  return client.containers.create(
134
146
  image,
135
147
  detach=True,
@@ -140,12 +152,12 @@ class DockerUserCodeLauncher(
140
152
  labels=labels,
141
153
  command=command,
142
154
  ports=ports,
143
- **container_context.container_kwargs,
155
+ **container_kwargs,
144
156
  )
145
157
 
146
158
  def _get_standalone_dagster_server_handles_for_location(
147
159
  self, deployment_name: str, location_name: str
148
- ) -> List[DagsterDockerContainer]:
160
+ ) -> list[DagsterDockerContainer]:
149
161
  client = docker.client.from_env()
150
162
  return [
151
163
  DagsterDockerContainer(container=container)
@@ -163,7 +175,7 @@ class DockerUserCodeLauncher(
163
175
 
164
176
  def _get_multipex_server_handles_for_location(
165
177
  self, deployment_name: str, location_name: str
166
- ) -> List[DagsterDockerContainer]:
178
+ ) -> list[DagsterDockerContainer]:
167
179
  client = docker.client.from_env()
168
180
  return [
169
181
  DagsterDockerContainer(container=container)
@@ -186,13 +198,13 @@ class DockerUserCodeLauncher(
186
198
  container_name: str,
187
199
  hostname: str,
188
200
  grpc_port: int,
189
- ports: Dict[int, int],
201
+ ports: dict[int, int],
190
202
  image: str,
191
203
  container_context: DockerContainerContext,
192
- command: List[str],
193
- additional_env: Dict[str, str],
194
- labels: Dict[str, str],
195
- ) -> Tuple[Container, ServerEndpoint]:
204
+ command: list[str],
205
+ additional_env: dict[str, str],
206
+ labels: dict[str, str],
207
+ ) -> tuple[Container, ServerEndpoint]:
196
208
  client = docker.client.from_env()
197
209
 
198
210
  self._logger.info(
@@ -304,8 +316,11 @@ class DockerUserCodeLauncher(
304
316
  command = metadata.get_grpc_server_command(
305
317
  metrics_enabled=self._instance.user_code_launcher.code_server_metrics_enabled
306
318
  )
307
- environment = metadata.get_grpc_server_env(
308
- grpc_port, location_name, self._instance.ref_for_deployment(deployment_name)
319
+ environment = get_grpc_server_env(
320
+ metadata,
321
+ grpc_port,
322
+ location_name,
323
+ self._instance.ref_for_deployment(deployment_name),
309
324
  )
310
325
  labels = {
311
326
  GRPC_SERVER_LABEL: "",
@@ -314,6 +329,9 @@ class DockerUserCodeLauncher(
314
329
  SERVER_TIMESTAMP_LABEL: str(desired_entry.update_timestamp),
315
330
  }
316
331
 
332
+ if "stop_timeout" in container_context.container_kwargs:
333
+ labels[STOP_TIMEOUT_LABEL] = str(container_context.container_kwargs["stop_timeout"])
334
+
317
335
  container, server_endpoint = self._launch_container(
318
336
  deployment_name,
319
337
  location_name,
@@ -347,8 +365,12 @@ class DockerUserCodeLauncher(
347
365
 
348
366
  def _remove_server_handle(self, server_handle: DagsterDockerContainer) -> None:
349
367
  container = server_handle.container
368
+
369
+ stop_timeout_str = server_handle.container.labels.get(STOP_TIMEOUT_LABEL)
370
+ stop_timeout = int(stop_timeout_str) if stop_timeout_str else None
371
+
350
372
  try:
351
- container.stop()
373
+ container.stop(timeout=stop_timeout)
352
374
  except Exception:
353
375
  self._logger.error(f"Failure stopping container {container.id}: {sys.exc_info()}")
354
376
  container.remove(force=True)
@@ -359,9 +381,9 @@ class DockerUserCodeLauncher(
359
381
 
360
382
  def get_server_create_timestamp(self, handle: DagsterDockerContainer) -> Optional[float]:
361
383
  created_time_str = handle.container.attrs["Created"]
362
- return parse(created_time_str).timestamp()
384
+ return parse(created_time_str).timestamp() # pyright: ignore[reportAttributeAccessIssue]
363
385
 
364
- def _list_server_handles(self) -> List[DagsterDockerContainer]:
386
+ def _list_server_handles(self) -> list[DagsterDockerContainer]:
365
387
  client = docker.client.from_env()
366
388
  return [
367
389
  DagsterDockerContainer(container=container)
@@ -374,7 +396,7 @@ class DockerUserCodeLauncher(
374
396
  ]
375
397
 
376
398
  def run_launcher(self):
377
- launcher = DockerRunLauncher(
399
+ launcher = CloudDockerRunLauncher(
378
400
  image=None,
379
401
  env_vars=self.env_vars,
380
402
  networks=self._networks,
@@ -383,3 +405,32 @@ class DockerUserCodeLauncher(
383
405
  launcher.register_instance(self._instance)
384
406
 
385
407
  return launcher
408
+
409
+
410
+ class CloudDockerRunLauncher(DockerRunLauncher):
411
+ def launch_run(self, context: LaunchRunContext) -> None:
412
+ serialized_pex_metadata = context.dagster_run.tags.get(PEX_METADATA_TAG)
413
+
414
+ if serialized_pex_metadata:
415
+ run = context.dagster_run
416
+ job_origin = check.not_none(run.job_code_origin)
417
+
418
+ docker_image = self._get_docker_image(job_origin)
419
+
420
+ run_args = ExecuteRunArgs(
421
+ job_origin=job_origin,
422
+ run_id=run.run_id,
423
+ instance_ref=self._instance.get_ref(),
424
+ )
425
+
426
+ deserialize_value(serialized_pex_metadata, PexMetadata)
427
+ command = [
428
+ "dagster-cloud",
429
+ "pex",
430
+ "execute-run",
431
+ serialize_value(run_args),
432
+ serialized_pex_metadata,
433
+ ]
434
+ self._launch_container_with_command(run, docker_image, command)
435
+ else:
436
+ return super().launch_run(context)
@@ -1,6 +1,6 @@
1
1
  import re
2
2
 
3
- from ..user_code_launcher.utils import unique_resource_name
3
+ from dagster_cloud.workspace.user_code_launcher.utils import unique_resource_name
4
4
 
5
5
 
6
6
  def unique_docker_resource_name(deployment_name, location_name):
@@ -1 +1 @@
1
- from .launcher import EcsUserCodeLauncher as EcsUserCodeLauncher
1
+ from dagster_cloud.workspace.ecs.launcher import EcsUserCodeLauncher as EcsUserCodeLauncher