dagster-cloud 1.8.2__py3-none-any.whl → 1.12.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- dagster_cloud/__init__.py +3 -3
- dagster_cloud/agent/__init__.py +4 -4
- dagster_cloud/agent/cli/__init__.py +56 -17
- dagster_cloud/agent/dagster_cloud_agent.py +360 -172
- dagster_cloud/agent/instrumentation/__init__.py +0 -0
- dagster_cloud/agent/instrumentation/constants.py +2 -0
- dagster_cloud/agent/instrumentation/run_launch.py +23 -0
- dagster_cloud/agent/instrumentation/schedule.py +34 -0
- dagster_cloud/agent/instrumentation/sensor.py +34 -0
- dagster_cloud/anomaly_detection/__init__.py +2 -2
- dagster_cloud/anomaly_detection/defs.py +17 -12
- dagster_cloud/anomaly_detection/types.py +3 -3
- dagster_cloud/api/dagster_cloud_api.py +209 -293
- dagster_cloud/auth/constants.py +21 -5
- dagster_cloud/batching/__init__.py +1 -0
- dagster_cloud/batching/batcher.py +210 -0
- dagster_cloud/dagster_insights/__init__.py +12 -6
- dagster_cloud/dagster_insights/bigquery/bigquery_utils.py +3 -2
- dagster_cloud/dagster_insights/bigquery/dbt_wrapper.py +39 -12
- dagster_cloud/dagster_insights/bigquery/insights_bigquery_resource.py +8 -6
- dagster_cloud/dagster_insights/insights_utils.py +18 -8
- dagster_cloud/dagster_insights/metrics_utils.py +12 -12
- dagster_cloud/dagster_insights/snowflake/dagster_snowflake_insights.py +5 -12
- dagster_cloud/dagster_insights/snowflake/dbt_wrapper.py +34 -8
- dagster_cloud/dagster_insights/snowflake/definitions.py +38 -12
- dagster_cloud/dagster_insights/snowflake/insights_snowflake_resource.py +11 -23
- dagster_cloud/definitions/__init__.py +0 -0
- dagster_cloud/definitions/job_selection.py +36 -0
- dagster_cloud/execution/cloud_run_launcher/k8s.py +1 -1
- dagster_cloud/execution/cloud_run_launcher/process.py +3 -3
- dagster_cloud/execution/monitoring/__init__.py +27 -33
- dagster_cloud/execution/utils/process.py +3 -3
- dagster_cloud/instance/__init__.py +125 -38
- dagster_cloud/instrumentation/__init__.py +32 -0
- dagster_cloud/metadata/source_code.py +13 -8
- dagster_cloud/metrics/__init__.py +0 -0
- dagster_cloud/metrics/tracer.py +59 -0
- dagster_cloud/opentelemetry/__init__.py +0 -0
- dagster_cloud/opentelemetry/config/__init__.py +73 -0
- dagster_cloud/opentelemetry/config/exporter.py +81 -0
- dagster_cloud/opentelemetry/config/log_record_processor.py +40 -0
- dagster_cloud/opentelemetry/config/logging_handler.py +14 -0
- dagster_cloud/opentelemetry/config/meter_provider.py +9 -0
- dagster_cloud/opentelemetry/config/metric_reader.py +39 -0
- dagster_cloud/opentelemetry/controller.py +319 -0
- dagster_cloud/opentelemetry/enum.py +58 -0
- dagster_cloud/opentelemetry/factories/__init__.py +1 -0
- dagster_cloud/opentelemetry/factories/logs.py +113 -0
- dagster_cloud/opentelemetry/factories/metrics.py +121 -0
- dagster_cloud/opentelemetry/metrics/__init__.py +0 -0
- dagster_cloud/opentelemetry/metrics/meter.py +140 -0
- dagster_cloud/opentelemetry/observers/__init__.py +0 -0
- dagster_cloud/opentelemetry/observers/dagster_exception_handler.py +40 -0
- dagster_cloud/opentelemetry/observers/execution_observer.py +178 -0
- dagster_cloud/pex/grpc/__generated__/multi_pex_api_pb2.pyi +175 -0
- dagster_cloud/pex/grpc/__init__.py +2 -2
- dagster_cloud/pex/grpc/client.py +4 -4
- dagster_cloud/pex/grpc/compile.py +2 -2
- dagster_cloud/pex/grpc/server/__init__.py +2 -2
- dagster_cloud/pex/grpc/server/cli/__init__.py +31 -19
- dagster_cloud/pex/grpc/server/manager.py +60 -42
- dagster_cloud/pex/grpc/server/registry.py +28 -21
- dagster_cloud/pex/grpc/server/server.py +23 -14
- dagster_cloud/pex/grpc/types.py +5 -5
- dagster_cloud/py.typed +0 -0
- dagster_cloud/secrets/__init__.py +1 -1
- dagster_cloud/secrets/loader.py +3 -3
- dagster_cloud/serverless/__init__.py +1 -1
- dagster_cloud/serverless/io_manager.py +36 -53
- dagster_cloud/storage/client.py +54 -17
- dagster_cloud/storage/compute_logs/__init__.py +3 -1
- dagster_cloud/storage/compute_logs/compute_log_manager.py +22 -17
- dagster_cloud/storage/defs_state/__init__.py +3 -0
- dagster_cloud/storage/defs_state/queries.py +15 -0
- dagster_cloud/storage/defs_state/storage.py +113 -0
- dagster_cloud/storage/event_logs/__init__.py +3 -1
- dagster_cloud/storage/event_logs/queries.py +102 -4
- dagster_cloud/storage/event_logs/storage.py +266 -73
- dagster_cloud/storage/event_logs/utils.py +88 -7
- dagster_cloud/storage/runs/__init__.py +1 -1
- dagster_cloud/storage/runs/queries.py +17 -2
- dagster_cloud/storage/runs/storage.py +88 -42
- dagster_cloud/storage/schedules/__init__.py +1 -1
- dagster_cloud/storage/schedules/storage.py +6 -8
- dagster_cloud/storage/tags.py +66 -1
- dagster_cloud/util/__init__.py +10 -12
- dagster_cloud/util/errors.py +49 -64
- dagster_cloud/version.py +1 -1
- dagster_cloud/workspace/config_schema/__init__.py +55 -13
- dagster_cloud/workspace/docker/__init__.py +76 -25
- dagster_cloud/workspace/docker/utils.py +1 -1
- dagster_cloud/workspace/ecs/__init__.py +1 -1
- dagster_cloud/workspace/ecs/client.py +51 -33
- dagster_cloud/workspace/ecs/launcher.py +76 -22
- dagster_cloud/workspace/ecs/run_launcher.py +3 -3
- dagster_cloud/workspace/ecs/utils.py +14 -5
- dagster_cloud/workspace/kubernetes/__init__.py +1 -1
- dagster_cloud/workspace/kubernetes/launcher.py +61 -29
- dagster_cloud/workspace/kubernetes/utils.py +34 -22
- dagster_cloud/workspace/user_code_launcher/__init__.py +5 -3
- dagster_cloud/workspace/user_code_launcher/process.py +16 -14
- dagster_cloud/workspace/user_code_launcher/user_code_launcher.py +552 -172
- dagster_cloud/workspace/user_code_launcher/utils.py +105 -1
- {dagster_cloud-1.8.2.dist-info → dagster_cloud-1.12.6.dist-info}/METADATA +48 -42
- dagster_cloud-1.12.6.dist-info/RECORD +134 -0
- {dagster_cloud-1.8.2.dist-info → dagster_cloud-1.12.6.dist-info}/WHEEL +1 -1
- dagster_cloud-1.8.2.dist-info/RECORD +0 -100
- {dagster_cloud-1.8.2.dist-info → dagster_cloud-1.12.6.dist-info}/top_level.txt +0 -0
dagster_cloud/pex/grpc/types.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from typing import
|
|
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(
|
|
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(
|
|
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",
|
|
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",
|
|
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
|
dagster_cloud/secrets/loader.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from typing import Any,
|
|
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]) ->
|
|
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
|
|
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,
|
|
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(
|
|
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) ->
|
|
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(
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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=
|
|
70
|
-
|
|
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
|
-
|
|
73
|
-
|
|
74
|
-
|
|
60
|
+
raise FileNotFoundError(
|
|
61
|
+
f"Could not find the input for '{context.name}'. It may have expired."
|
|
62
|
+
)
|
|
75
63
|
|
|
76
|
-
def
|
|
77
|
-
|
|
78
|
-
|
|
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
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
-
|
|
76
|
+
def unlink(self, path: UPath) -> None:
|
|
77
|
+
self._s3.delete_object(Bucket=self._bucket, Key=path.as_posix())
|
|
85
78
|
|
|
86
|
-
def
|
|
87
|
-
|
|
88
|
-
|
|
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
|
-
|
|
83
|
+
def get_op_output_relative_path(self, context: Union[InputContext, OutputContext]):
|
|
84
|
+
from upath import UPath
|
|
96
85
|
|
|
97
|
-
|
|
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
|
dagster_cloud/storage/client.py
CHANGED
|
@@ -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(
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
"
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
"
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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)),
|
|
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(
|
|
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(
|
|
42
|
-
|
|
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,6 +1,7 @@
|
|
|
1
|
-
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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
|
|
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
|
-
|
|
95
|
-
params:
|
|
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.
|
|
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
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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,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})
|