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,4 +1,4 @@
1
- from typing import List, NamedTuple, Optional
1
+ from typing import NamedTuple, Optional
2
2
 
3
3
  import dagster._check as check
4
4
  from dagster._core.instance.ref import InstanceRef
@@ -21,7 +21,7 @@ class PexServerHandle(
21
21
  )
22
22
  ):
23
23
  def __new__(cls, deployment_name: str, location_name: str, metadata_update_timestamp: int):
24
- return super(PexServerHandle, cls).__new__(
24
+ return super().__new__(
25
25
  cls,
26
26
  check.str_param(deployment_name, "deployment_name"),
27
27
  check.str_param(location_name, "location_name"),
@@ -49,7 +49,7 @@ class CreatePexServerArgs(
49
49
  code_location_deploy_data: CodeLocationDeployData,
50
50
  instance_ref: Optional[InstanceRef] = None,
51
51
  ):
52
- return super(CreatePexServerArgs, cls).__new__(
52
+ return super().__new__(
53
53
  cls,
54
54
  check.inst_param(server_handle, "server_handle", PexServerHandle),
55
55
  check.inst_param(
@@ -100,7 +100,7 @@ class GetPexServersResponse(
100
100
  NamedTuple(
101
101
  "_GetPexServersResponse",
102
102
  [
103
- ("server_handles", List[PexServerHandle]),
103
+ ("server_handles", list[PexServerHandle]),
104
104
  ],
105
105
  )
106
106
  ):
@@ -112,7 +112,7 @@ class GetCrashedPexServersResponse(
112
112
  NamedTuple(
113
113
  "_GetCrashedPexServersResponse",
114
114
  [
115
- ("server_handles", List[PexServerHandle]),
115
+ ("server_handles", list[PexServerHandle]),
116
116
  ],
117
117
  )
118
118
  ):
dagster_cloud/py.typed ADDED
File without changes
@@ -1 +1 @@
1
- from .loader import DagsterCloudSecretsLoader as DagsterCloudSecretsLoader
1
+ from dagster_cloud.secrets.loader import DagsterCloudSecretsLoader as DagsterCloudSecretsLoader
@@ -1,4 +1,4 @@
1
- from typing import Any, Dict, Optional
1
+ from typing import Any, Optional
2
2
 
3
3
  from dagster._core.secrets import SecretsLoader
4
4
  from dagster._serdes import ConfigurableClass
@@ -24,9 +24,9 @@ class DagsterCloudSecretsLoader(SecretsLoader, ConfigurableClass):
24
24
  self._inst_data = inst_data
25
25
 
26
26
  def _execute_query(self, query, variables=None):
27
- return self._instance.graphql_client.execute(query, variable_values=variables)
27
+ return self._instance.graphql_client.execute(query, variable_values=variables) # pyright: ignore[reportAttributeAccessIssue]
28
28
 
29
- def get_secrets_for_environment(self, location_name: Optional[str]) -> Dict[str, str]:
29
+ def get_secrets_for_environment(self, location_name: Optional[str]) -> dict[str, str]:
30
30
  res = self._execute_query(
31
31
  SECRETS_QUERY,
32
32
  variables={"locationName": location_name},
@@ -1 +1 @@
1
- from .io_manager import serverless_io_manager as serverless_io_manager
1
+ from dagster_cloud.serverless.io_manager import serverless_io_manager as serverless_io_manager
@@ -2,19 +2,20 @@ import datetime
2
2
  import io
3
3
  import os
4
4
  import pickle
5
- from typing import Sequence, Tuple, Union
5
+ from typing import Any, Union
6
6
 
7
7
  import boto3
8
8
  import dagster._check as check
9
9
  import requests
10
- from dagster import InputContext, IOManager, OutputContext, io_manager
10
+ from dagster import InputContext, OutputContext, UPathIOManager, io_manager
11
11
  from dagster._utils import PICKLE_PROTOCOL
12
12
  from dagster._vendored.dateutil import parser
13
+ from upath import UPath
13
14
 
14
15
  ECS_AGENT_IP = "169.254.170.2"
15
16
 
16
17
 
17
- class PickledObjectServerlessIOManager(IOManager):
18
+ class PickledObjectServerlessIOManager(UPathIOManager):
18
19
  def __init__(
19
20
  self,
20
21
  s3_bucket,
@@ -23,8 +24,10 @@ class PickledObjectServerlessIOManager(IOManager):
23
24
  self._bucket = check.str_param(s3_bucket, "s3_bucket")
24
25
  self._s3_prefix = check.str_param(s3_prefix, "s3_prefix")
25
26
  self._boto_session, self._boto_session_expiration = self._refresh_boto_session()
27
+ base_path = UPath(s3_prefix) if s3_prefix else None
28
+ super().__init__(base_path=base_path)
26
29
 
27
- def _refresh_boto_session(self) -> Tuple[boto3.Session, datetime.datetime]:
30
+ def _refresh_boto_session(self) -> tuple[boto3.Session, datetime.datetime]:
28
31
  # We have to do this whacky way to get credentials to ensure that we get iam role
29
32
  # we assigned to the task. If we used the default boto behavior, it could get overriden
30
33
  # when users set AWS env vars.
@@ -36,7 +39,7 @@ class PickledObjectServerlessIOManager(IOManager):
36
39
  aws_session_token=aws_creds["Token"],
37
40
  )
38
41
  expiration = parser.parse(aws_creds["Expiration"])
39
- return session, expiration
42
+ return session, expiration # pyright: ignore[reportReturnType]
40
43
 
41
44
  @property
42
45
  def _s3(self):
@@ -44,63 +47,43 @@ class PickledObjectServerlessIOManager(IOManager):
44
47
  self._boto_session_expiration.tzinfo
45
48
  ) + datetime.timedelta(minutes=5):
46
49
  self._boto_session, self._boto_session_expiration = self._refresh_boto_session()
47
- return self._boto_session.client("s3", region_name="us-west-2")
48
-
49
- def _get_path(self, context: Union[InputContext, OutputContext]) -> str:
50
- path: Sequence[str]
51
- if context.has_asset_key:
52
- path = context.get_asset_identifier()
53
- else:
54
- path = ["storage", *context.get_identifier()]
55
-
56
- return "/".join([self._s3_prefix, *path])
57
-
58
- def has_output(self, context):
59
- key = self._get_path(context)
60
- return self._has_object(key)
61
-
62
- def _has_object(self, key):
63
- check.str_param(key, "key")
64
- check.param_invariant(len(key) > 0, "key")
65
-
66
- found_object = False
50
+ return self._boto_session.client(
51
+ "s3",
52
+ region_name=os.getenv("DAGSTER_CLOUD_SERVERLESS_REGION", "us-west-2"),
53
+ )
67
54
 
55
+ def load_from_path(self, context: InputContext, path: UPath) -> Any:
68
56
  try:
69
- self._s3.get_object(Bucket=self._bucket, Key=key)
70
- found_object = True
57
+ s3_obj = self._s3.get_object(Bucket=self._bucket, Key=path.as_posix())["Body"].read()
58
+ return pickle.loads(s3_obj)
71
59
  except self._s3.exceptions.NoSuchKey:
72
- found_object = False
73
-
74
- return found_object
60
+ raise FileNotFoundError(
61
+ f"Could not find the input for '{context.name}'. It may have expired."
62
+ )
75
63
 
76
- def load_input(self, context):
77
- if context.dagster_type.typing_type == type(None):
78
- return None
64
+ def dump_to_path(self, context: OutputContext, obj: Any, path: UPath) -> None:
65
+ pickled_obj = pickle.dumps(obj, PICKLE_PROTOCOL)
66
+ pickled_obj_bytes = io.BytesIO(pickled_obj)
67
+ self._s3.upload_fileobj(pickled_obj_bytes, self._bucket, path.as_posix())
79
68
 
80
- key = self._get_path(context)
81
- check.invariant(self._has_object(key), "Input not found. It may have expired.")
82
- obj = pickle.loads(self._s3.get_object(Bucket=self._bucket, Key=key)["Body"].read())
69
+ def path_exists(self, path: UPath) -> bool:
70
+ try:
71
+ self._s3.get_object(Bucket=self._bucket, Key=path.as_posix())
72
+ except self._s3.exceptions.NoSuchKey:
73
+ return False
74
+ return True
83
75
 
84
- return obj
76
+ def unlink(self, path: UPath) -> None:
77
+ self._s3.delete_object(Bucket=self._bucket, Key=path.as_posix())
85
78
 
86
- def handle_output(self, context, obj):
87
- if context.dagster_type.typing_type == type(None):
88
- check.invariant(
89
- obj is None,
90
- "Output had Nothing type or 'None' annotation, but handle_output received"
91
- f" value that was not None and was of type {type(obj)}.",
92
- )
93
- return None
79
+ def make_directory(self, path: UPath) -> None:
80
+ # It is not necessary to create directories in S3
81
+ return None
94
82
 
95
- key = self._get_path(context)
83
+ def get_op_output_relative_path(self, context: Union[InputContext, OutputContext]):
84
+ from upath import UPath
96
85
 
97
- pickled_obj = pickle.dumps(obj, PICKLE_PROTOCOL)
98
- pickled_obj_bytes = io.BytesIO(pickled_obj)
99
- self._s3.upload_fileobj(
100
- pickled_obj_bytes,
101
- self._bucket,
102
- key,
103
- )
86
+ return UPath(*["storage", *context.get_identifier()])
104
87
 
105
88
 
106
89
  @io_manager
@@ -19,25 +19,62 @@ from dagster_cloud_cli.core.graphql_client import (
19
19
 
20
20
  def dagster_cloud_api_config():
21
21
  return {
22
- "url": Field(StringSource, is_required=False),
23
- "agent_token": Field(StringSource, is_required=True),
24
- "headers": Field(Permissive(), default_value={}),
25
- "cookies": Field(Permissive(), default_value={}),
26
- "timeout": Field(Noneable(IntSource), default_value=DEFAULT_TIMEOUT),
27
- "verify": Field(bool, default_value=True),
28
- "retries": Field(IntSource, default_value=DEFAULT_RETRIES),
29
- "method": Field(StringSource, default_value="POST"),
30
- "backoff_factor": Field(float, default_value=DEFAULT_BACKOFF_FACTOR),
31
- "proxies": Field(Map(str, str), is_required=False),
32
- "socket_options": Field(Noneable(Array(Array(Any)))),
33
- "agent_label": Field(StringSource, is_required=False),
34
- # Handle requests for a single non-branch deployment
22
+ "url": Field(
23
+ StringSource,
24
+ is_required=False,
25
+ description="Dagster+ API server URL. This can be omitted and is derived from your agent token.",
26
+ ),
27
+ "agent_token": Field(
28
+ StringSource, is_required=True, description="Dagster+ agent api token."
29
+ ),
30
+ "agent_label": Field(
31
+ StringSource,
32
+ is_required=False,
33
+ description="Custom label visible in the Dagster+ UI.",
34
+ ),
35
35
  "deployment": Field(
36
- ScalarUnion(scalar_type=str, non_scalar_schema=Array(str)), is_required=False
36
+ ScalarUnion(scalar_type=str, non_scalar_schema=Array(str)),
37
+ is_required=False,
38
+ description="Handle requests for a single deployment.",
37
39
  ),
38
40
  # Handle requests for multiple non-branch deployments
39
- "deployments": Field(Array(StringSource), is_required=False),
41
+ "deployments": Field(
42
+ Array(StringSource),
43
+ is_required=False,
44
+ description="Handle requests for multiple deployments",
45
+ ),
40
46
  # Handle requests for all branch deployments (can be combined with `deployment`)`
41
- "branch_deployments": Field(BoolSource, default_value=False, is_required=False),
42
- "all_serverless_deployments": Field(BoolSource, default_value=False, is_required=False),
47
+ "branch_deployments": Field(
48
+ BoolSource,
49
+ default_value=False,
50
+ is_required=False,
51
+ description="Handle requests for all branch deployments (can be combined with `deployment` or `deployments`)",
52
+ ),
53
+ "timeout": Field(
54
+ Noneable(IntSource),
55
+ default_value=DEFAULT_TIMEOUT,
56
+ description="How long before a request times out against the Dagster+ API servers.",
57
+ ),
58
+ "retries": Field(
59
+ IntSource,
60
+ default_value=DEFAULT_RETRIES,
61
+ description="How many times to retry retriable response codes (429, 503, etc.) before failing.",
62
+ ),
63
+ "backoff_factor": Field(
64
+ float,
65
+ default_value=DEFAULT_BACKOFF_FACTOR,
66
+ description="Exponential factor to back off between retries.",
67
+ ),
68
+ "method": Field(StringSource, default_value="POST", description="Default HTTP method."),
69
+ "verify": Field(bool, default_value=True, description="If False, ignore verifying SSL."),
70
+ "headers": Field(Permissive(), default_value={}, description="Custom headers."),
71
+ "cookies": Field(Permissive(), default_value={}, description="Custom cookies."),
72
+ "proxies": Field(Map(str, str), is_required=False, description="Custom proxies."),
73
+ "socket_options": Field(Noneable(Array(Array(Any))), description="Custom socket options."),
74
+ "all_serverless_deployments": Field(
75
+ BoolSource,
76
+ default_value=False,
77
+ is_required=False,
78
+ description="Unused by hybrid agents",
79
+ ),
43
80
  }
@@ -1 +1,3 @@
1
- from .compute_log_manager import CloudComputeLogManager as CloudComputeLogManager
1
+ from dagster_cloud.storage.compute_logs.compute_log_manager import (
2
+ CloudComputeLogManager as CloudComputeLogManager,
3
+ )
@@ -1,6 +1,7 @@
1
- from typing import TYPE_CHECKING, Any, Dict, Optional, Sequence
1
+ import os
2
+ from collections.abc import Sequence
3
+ from typing import IO, TYPE_CHECKING, Any, Optional
2
4
 
3
- import dagster._seven as _seven
4
5
  import requests
5
6
  from dagster import (
6
7
  Field,
@@ -8,16 +9,18 @@ from dagster import (
8
9
  StringSource,
9
10
  _check as check,
10
11
  )
11
- from dagster._core.storage.cloud_storage_compute_log_manager import CloudStorageComputeLogManager
12
+ from dagster._core.storage.cloud_storage_compute_log_manager import (
13
+ TruncatingCloudStorageComputeLogManager,
14
+ )
12
15
  from dagster._core.storage.compute_log_manager import ComputeIOType
13
16
  from dagster._core.storage.local_compute_log_manager import (
14
17
  IO_TYPE_EXTENSION,
15
18
  LocalComputeLogManager,
16
19
  )
17
20
  from dagster._serdes import ConfigurableClass, ConfigurableClassData
18
- from dagster._utils import ensure_file
19
21
  from dagster_cloud_cli.core.errors import raise_http_error
20
22
  from dagster_cloud_cli.core.headers.auth import DagsterCloudInstanceScope
23
+ from dagster_shared import seven
21
24
  from requests.adapters import HTTPAdapter
22
25
  from typing_extensions import Self
23
26
 
@@ -26,7 +29,7 @@ if TYPE_CHECKING:
26
29
 
27
30
 
28
31
  class CloudComputeLogManager(
29
- CloudStorageComputeLogManager["DagsterCloudAgentInstance"], ConfigurableClass
32
+ TruncatingCloudStorageComputeLogManager["DagsterCloudAgentInstance"], ConfigurableClass
30
33
  ):
31
34
  def __init__(
32
35
  self,
@@ -36,7 +39,7 @@ class CloudComputeLogManager(
36
39
  ):
37
40
  # proxy calls to local compute log manager (for subscriptions, etc)
38
41
  if not local_dir:
39
- local_dir = _seven.get_system_temp_directory()
42
+ local_dir = seven.get_system_temp_directory()
40
43
 
41
44
  self._upload_session = requests.Session()
42
45
  adapter = HTTPAdapter(max_retries=3)
@@ -46,6 +49,7 @@ class CloudComputeLogManager(
46
49
  self._local_manager = LocalComputeLogManager(local_dir)
47
50
  self._upload_interval = check.opt_int_param(upload_interval, "upload_interval")
48
51
  self._inst_data = check.opt_inst_param(inst_data, "inst_data", ConfigurableClassData)
52
+ super().__init__()
49
53
 
50
54
  @property
51
55
  def inst_data(self):
@@ -87,21 +91,23 @@ class CloudComputeLogManager(
87
91
  """Returns whether the cloud storage contains logs for a given log key."""
88
92
  return False
89
93
 
90
- def upload_to_cloud_storage(
91
- self, log_key: Sequence[str], io_type: ComputeIOType, partial=False
94
+ def _upload_file_obj(
95
+ self, data: IO[bytes], log_key: Sequence[str], io_type: ComputeIOType, partial=False
92
96
  ):
93
97
  path = self.local_manager.get_captured_local_path(log_key, IO_TYPE_EXTENSION[io_type])
94
- ensure_file(path)
95
- params: Dict[str, Any] = {
98
+ size_bytes = os.stat(path).st_size
99
+ params: dict[str, Any] = {
96
100
  "log_key": log_key,
97
101
  "io_type": io_type.value,
102
+ "size_bytes": size_bytes,
98
103
  # for back-compat
99
104
  "run_id": log_key[0],
100
105
  "key": log_key[-1],
106
+ "method": "PUT",
101
107
  }
102
108
  if partial:
103
109
  params["partial"] = True
104
- resp = self._instance.requests_managed_retries_session.post(
110
+ resp = self._instance.requests_managed_retries_session.get(
105
111
  self._instance.dagster_cloud_gen_logs_url_url,
106
112
  params=params,
107
113
  headers=self._instance.dagster_cloud_api_headers(DagsterCloudInstanceScope.DEPLOYMENT),
@@ -114,12 +120,11 @@ class CloudComputeLogManager(
114
120
  if resp_data.get("skip_upload"):
115
121
  return
116
122
 
117
- with open(path, "rb") as f:
118
- self._upload_session.post(
119
- resp_data["url"],
120
- data=resp_data["fields"],
121
- files={"file": f},
122
- )
123
+ self._upload_session.put(
124
+ resp_data["url"],
125
+ data=data,
126
+ timeout=self._instance.dagster_cloud_api_timeout,
127
+ )
123
128
 
124
129
  def download_from_cloud_storage(
125
130
  self, log_key: Sequence[str], io_type: ComputeIOType, partial=False
@@ -0,0 +1,3 @@
1
+ from dagster_cloud.storage.defs_state.storage import (
2
+ GraphQLDefsStateStorage as GraphQLDefsStateStorage,
3
+ )
@@ -0,0 +1,15 @@
1
+ GET_LATEST_DEFS_STATE_INFO_QUERY = """
2
+ query getLatestDefsStateInfo {
3
+ latestDefsStateInfo
4
+ }
5
+ """
6
+
7
+ SET_LATEST_VERSION_MUTATION = """
8
+ mutation setLatestDefsStateVersion($key: String!, $version: String!) {
9
+ defsState {
10
+ setLatestDefsStateVersion(key: $key, version: $version) {
11
+ ok
12
+ }
13
+ }
14
+ }
15
+ """
@@ -0,0 +1,113 @@
1
+ from pathlib import Path
2
+ from typing import TYPE_CHECKING, Any, Optional
3
+
4
+ import dagster._check as check
5
+ from dagster._core.storage.defs_state.base import DefsStateStorage
6
+ from dagster._serdes import ConfigurableClass, ConfigurableClassData
7
+ from dagster_cloud_cli.core.artifacts import download_artifact, upload_artifact
8
+ from dagster_cloud_cli.core.headers.auth import DagsterCloudInstanceScope
9
+ from dagster_shared.serdes import deserialize_value
10
+ from dagster_shared.serdes.objects.models.defs_state_info import DefsStateInfo
11
+ from typing_extensions import Self
12
+
13
+ if TYPE_CHECKING:
14
+ from dagster_cloud.instance import DagsterCloudAgentInstance # noqa: F401
15
+
16
+ GET_LATEST_DEFS_STATE_INFO_QUERY = """
17
+ query getLatestDefsStateInfo {
18
+ latestDefsStateInfo
19
+ }
20
+ """
21
+
22
+ SET_LATEST_VERSION_MUTATION = """
23
+ mutation setLatestDefsStateVersion($key: String!, $version: String!) {
24
+ defsState {
25
+ setLatestDefsStateVersion(key: $key, version: $version) {
26
+ ok
27
+ }
28
+ }
29
+ }
30
+ """
31
+
32
+
33
+ class GraphQLDefsStateStorage(DefsStateStorage["DagsterCloudAgentInstance"], ConfigurableClass):
34
+ def __init__(
35
+ self, inst_data: Optional[ConfigurableClassData] = None, override_graphql_client=None
36
+ ):
37
+ """Initialize this class directly only for test (using `override_graphql_client`).
38
+ Use the ConfigurableClass machinery to init from instance yaml.
39
+ """
40
+ self._inst_data = check.opt_inst_param(inst_data, "inst_data", ConfigurableClassData)
41
+ self._override_graphql_client = override_graphql_client
42
+
43
+ @property
44
+ def inst_data(self):
45
+ return self._inst_data
46
+
47
+ @classmethod
48
+ def config_type(cls):
49
+ return {}
50
+
51
+ @classmethod
52
+ def from_config_value(cls, inst_data: ConfigurableClassData, config_value: Any) -> Self:
53
+ return cls(inst_data=inst_data)
54
+
55
+ @property
56
+ def url(self) -> str:
57
+ return self._instance.dagster_cloud_url
58
+
59
+ @property
60
+ def api_token(self) -> str:
61
+ return check.not_none(self._instance.dagster_cloud_agent_token)
62
+
63
+ @property
64
+ def deployment(self) -> str:
65
+ return check.not_none(self._instance.deployment_name)
66
+
67
+ @property
68
+ def graphql_client(self):
69
+ return (
70
+ self._override_graphql_client
71
+ if self._override_graphql_client
72
+ else self._instance.graphql_client
73
+ )
74
+
75
+ def _execute_query(self, query, variables=None, idempotent_mutation=False):
76
+ return self.graphql_client.execute(
77
+ query, variable_values=variables, idempotent_mutation=idempotent_mutation
78
+ )
79
+
80
+ def _get_artifact_key(self, key: str, version: str) -> str:
81
+ return f"__state__/{self._sanitize_key(key)}/{version}"
82
+
83
+ def download_state_to_path(self, key: str, version: str, path: Path) -> None:
84
+ download_artifact(
85
+ url=self.url,
86
+ scope=DagsterCloudInstanceScope.DEPLOYMENT,
87
+ api_token=self.api_token,
88
+ key=self._get_artifact_key(key, version),
89
+ path=path,
90
+ deployment=self.deployment,
91
+ )
92
+
93
+ def upload_state_from_path(self, key: str, version: str, path: Path) -> None:
94
+ upload_artifact(
95
+ url=self.url,
96
+ scope=DagsterCloudInstanceScope.DEPLOYMENT,
97
+ api_token=self.api_token,
98
+ key=self._get_artifact_key(key, version),
99
+ path=path,
100
+ deployment=self.deployment,
101
+ )
102
+ self.set_latest_version(key, version)
103
+
104
+ def get_latest_defs_state_info(self) -> Optional[DefsStateInfo]:
105
+ res = self._execute_query(GET_LATEST_DEFS_STATE_INFO_QUERY)
106
+ result = res["data"]["latestDefsStateInfo"]
107
+ if result is not None:
108
+ return deserialize_value(result, DefsStateInfo)
109
+ else:
110
+ return None
111
+
112
+ def set_latest_version(self, key: str, version: str) -> None:
113
+ self._execute_query(SET_LATEST_VERSION_MUTATION, variables={"key": key, "version": version})
@@ -1 +1,3 @@
1
- from .storage import GraphQLEventLogStorage as GraphQLEventLogStorage
1
+ from dagster_cloud.storage.event_logs.storage import (
2
+ GraphQLEventLogStorage as GraphQLEventLogStorage,
3
+ )