dagster-cloud-cli 1.12.16__py2.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_cli/__init__.py +5 -0
- dagster_cloud_cli/commands/__init__.py +0 -0
- dagster_cloud_cli/commands/branch_deployment/__init__.py +154 -0
- dagster_cloud_cli/commands/ci/__init__.py +1207 -0
- dagster_cloud_cli/commands/ci/checks.py +135 -0
- dagster_cloud_cli/commands/ci/report.py +49 -0
- dagster_cloud_cli/commands/ci/state.py +129 -0
- dagster_cloud_cli/commands/ci/utils.py +22 -0
- dagster_cloud_cli/commands/config.py +250 -0
- dagster_cloud_cli/commands/deployment/__init__.py +108 -0
- dagster_cloud_cli/commands/deployment/alert_policies/__init__.py +0 -0
- dagster_cloud_cli/commands/deployment/alert_policies/commands.py +52 -0
- dagster_cloud_cli/commands/integration/__init__.py +6 -0
- dagster_cloud_cli/commands/integration/atlan/__init__.py +94 -0
- dagster_cloud_cli/commands/job/__init__.py +117 -0
- dagster_cloud_cli/commands/metrics.py +114 -0
- dagster_cloud_cli/commands/organization/__init__.py +134 -0
- dagster_cloud_cli/commands/organization/saml/__init__.py +0 -0
- dagster_cloud_cli/commands/organization/saml/commands.py +85 -0
- dagster_cloud_cli/commands/run/__init__.py +27 -0
- dagster_cloud_cli/commands/serverless/Dockerfile +38 -0
- dagster_cloud_cli/commands/serverless/Dockerfile.dagster-cloud-cli +20 -0
- dagster_cloud_cli/commands/serverless/__init__.py +560 -0
- dagster_cloud_cli/commands/workspace/__init__.py +358 -0
- dagster_cloud_cli/config/__init__.py +6 -0
- dagster_cloud_cli/config/jinja_template_loader.py +45 -0
- dagster_cloud_cli/config/list_merge_strategies.py +77 -0
- dagster_cloud_cli/config/loader.py +23 -0
- dagster_cloud_cli/config/merger.py +92 -0
- dagster_cloud_cli/config/models.py +76 -0
- dagster_cloud_cli/config_utils.py +506 -0
- dagster_cloud_cli/core/__init__.py +0 -0
- dagster_cloud_cli/core/agent_queue.py +3 -0
- dagster_cloud_cli/core/alert_types.py +26 -0
- dagster_cloud_cli/core/artifacts.py +192 -0
- dagster_cloud_cli/core/docker_runner.py +57 -0
- dagster_cloud_cli/core/errors.py +32 -0
- dagster_cloud_cli/core/graphql_client.py +420 -0
- dagster_cloud_cli/core/headers/__init__.py +0 -0
- dagster_cloud_cli/core/headers/auth.py +16 -0
- dagster_cloud_cli/core/headers/impl.py +31 -0
- dagster_cloud_cli/core/headers/versioning/__init__.py +0 -0
- dagster_cloud_cli/core/headers/versioning/constants.py +2 -0
- dagster_cloud_cli/core/pex_builder/__init__.py +8 -0
- dagster_cloud_cli/core/pex_builder/code_location.py +126 -0
- dagster_cloud_cli/core/pex_builder/deploy.py +255 -0
- dagster_cloud_cli/core/pex_builder/deps.py +495 -0
- dagster_cloud_cli/core/pex_builder/git_context.py +29 -0
- dagster_cloud_cli/core/pex_builder/github_context.py +178 -0
- dagster_cloud_cli/core/pex_builder/gitlab_context.py +30 -0
- dagster_cloud_cli/core/pex_builder/parse_workspace.py +39 -0
- dagster_cloud_cli/core/pex_builder/pex_registry.py +148 -0
- dagster_cloud_cli/core/pex_builder/platforms.py +6105 -0
- dagster_cloud_cli/core/pex_builder/selftest.py +14 -0
- dagster_cloud_cli/core/pex_builder/source.py +166 -0
- dagster_cloud_cli/core/pex_builder/util.py +165 -0
- dagster_cloud_cli/core/workspace.py +145 -0
- dagster_cloud_cli/docker_utils.py +98 -0
- dagster_cloud_cli/entrypoint.py +209 -0
- dagster_cloud_cli/gql.py +1025 -0
- dagster_cloud_cli/pex_utils.py +180 -0
- dagster_cloud_cli/py.typed +0 -0
- dagster_cloud_cli/types.py +78 -0
- dagster_cloud_cli/ui.py +101 -0
- dagster_cloud_cli/utils.py +107 -0
- dagster_cloud_cli/version.py +1 -0
- dagster_cloud_cli-1.12.16.dist-info/METADATA +31 -0
- dagster_cloud_cli-1.12.16.dist-info/RECORD +71 -0
- dagster_cloud_cli-1.12.16.dist-info/WHEEL +5 -0
- dagster_cloud_cli-1.12.16.dist-info/entry_points.txt +3 -0
- dagster_cloud_cli-1.12.16.dist-info/licenses/LICENSE +201 -0
|
@@ -0,0 +1,506 @@
|
|
|
1
|
+
import collections
|
|
2
|
+
import functools
|
|
3
|
+
import inspect
|
|
4
|
+
import os
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any, cast
|
|
7
|
+
|
|
8
|
+
import yaml
|
|
9
|
+
from click import Context
|
|
10
|
+
from dagster_shared.merger import deep_merge_dicts
|
|
11
|
+
from dagster_shared.plus.config import (
|
|
12
|
+
DagsterPlusCliConfig,
|
|
13
|
+
get_dagster_cloud_cli_config_path,
|
|
14
|
+
get_dg_config_path,
|
|
15
|
+
)
|
|
16
|
+
from dagster_shared.utils import remove_none_recursively
|
|
17
|
+
from typer import Option
|
|
18
|
+
|
|
19
|
+
from dagster_cloud_cli import gql, ui
|
|
20
|
+
from dagster_cloud_cli.core.errors import DagsterCloudHTTPError
|
|
21
|
+
from dagster_cloud_cli.utils import add_options
|
|
22
|
+
|
|
23
|
+
DEPLOYMENT_CLI_ARGUMENT = "deployment"
|
|
24
|
+
DEPLOYMENT_ENV_VAR_NAME = "DAGSTER_CLOUD_DEPLOYMENT"
|
|
25
|
+
|
|
26
|
+
ORGANIZATION_CLI_ARGUMENT = "organization"
|
|
27
|
+
ORGANIZATION_ENV_VAR_NAME = "DAGSTER_CLOUD_ORGANIZATION"
|
|
28
|
+
|
|
29
|
+
TOKEN_CLI_ARGUMENT = "api-token"
|
|
30
|
+
TOKEN_CLI_ARGUMENT_VAR = TOKEN_CLI_ARGUMENT.replace("-", "_")
|
|
31
|
+
TOKEN_ENV_VAR_NAME = "DAGSTER_CLOUD_API_TOKEN"
|
|
32
|
+
|
|
33
|
+
URL_CLI_ARGUMENT = "url"
|
|
34
|
+
URL_ENV_VAR_NAME = "DAGSTER_CLOUD_URL"
|
|
35
|
+
|
|
36
|
+
LOCATION_LOAD_TIMEOUT_CLI_ARGUMENT = "location-load-timeout"
|
|
37
|
+
LOCATION_LOAD_TIMEOUT_ARGUMENT_VAR = LOCATION_LOAD_TIMEOUT_CLI_ARGUMENT.replace("-", "_")
|
|
38
|
+
LOCATION_LOAD_TIMEOUT_ENV_VAR_NAME = "DAGSTER_CLOUD_LOCATION_LOAD_TIMEOUT"
|
|
39
|
+
DEFAULT_LOCATION_LOAD_TIMEOUT = 3600
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
AGENT_HEARTBEAT_TIMEOUT_CLI_ARGUMENT = "agent-heartbeat-timeout"
|
|
43
|
+
AGENT_HEARTBEAT_TIMEOUT_ARGUMENT_VAR = AGENT_HEARTBEAT_TIMEOUT_CLI_ARGUMENT.replace("-", "_")
|
|
44
|
+
AGENT_HEARTBEAT_TIMEOUT_ENV_VAR_NAME = "DAGSTER_CLOUD_AGENT_HEARTBEAT_TIMEOUT"
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def get_config_path():
|
|
48
|
+
return (
|
|
49
|
+
get_dagster_cloud_cli_config_path()
|
|
50
|
+
if get_dagster_cloud_cli_config_path().exists()
|
|
51
|
+
else get_dg_config_path()
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def write_config(config: DagsterPlusCliConfig):
|
|
56
|
+
"""Writes the given config object to the CLI config file."""
|
|
57
|
+
config.write()
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def read_config() -> DagsterPlusCliConfig:
|
|
61
|
+
"""Reads the CLI config file into a config object."""
|
|
62
|
+
if not DagsterPlusCliConfig.exists():
|
|
63
|
+
return DagsterPlusCliConfig()
|
|
64
|
+
|
|
65
|
+
return DagsterPlusCliConfig.get()
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def get_deployment(ctx: Context | None = None) -> str | None:
|
|
69
|
+
"""Gets the configured deployment to target.
|
|
70
|
+
Highest precedence is a deployment argument, then `DAGSTER_CLOUD_DEPLOYMENT`
|
|
71
|
+
env var, then `~/.dagster_cloud_cli/config` default.
|
|
72
|
+
"""
|
|
73
|
+
if ctx and ctx.params.get(DEPLOYMENT_CLI_ARGUMENT):
|
|
74
|
+
return ctx.params[DEPLOYMENT_CLI_ARGUMENT]
|
|
75
|
+
return os.getenv(DEPLOYMENT_ENV_VAR_NAME, read_config().default_deployment)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def get_organization(ctx: Context | None = None) -> str | None:
|
|
79
|
+
"""Gets the configured organization to target.
|
|
80
|
+
Highest precedence is an organization argument, then `DAGSTER_CLOUD_ORGANIZATION`
|
|
81
|
+
env var, then `~/.dagster_cloud_cli/config` value.
|
|
82
|
+
"""
|
|
83
|
+
if ctx and ctx.params.get(ORGANIZATION_CLI_ARGUMENT):
|
|
84
|
+
return ctx.params[ORGANIZATION_CLI_ARGUMENT]
|
|
85
|
+
return os.getenv(ORGANIZATION_ENV_VAR_NAME, read_config().organization)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def get_location_load_timeout() -> int:
|
|
89
|
+
"""Gets the configured location load timeout to target.
|
|
90
|
+
Highest precedence is an location-load-timeout argument, then `DAGSTER_CLOUD_LOCATION_LOAD_TIMEOUT`
|
|
91
|
+
env var, then `~/.dagster_cloud_cli/config` value.
|
|
92
|
+
"""
|
|
93
|
+
config_timeout = read_config().agent_timeout
|
|
94
|
+
default_timeout = (
|
|
95
|
+
config_timeout if config_timeout is not None else DEFAULT_LOCATION_LOAD_TIMEOUT
|
|
96
|
+
)
|
|
97
|
+
assert default_timeout is not None
|
|
98
|
+
|
|
99
|
+
env_val = os.getenv(LOCATION_LOAD_TIMEOUT_ENV_VAR_NAME)
|
|
100
|
+
|
|
101
|
+
return int(cast("str", env_val)) if env_val is not None else default_timeout
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def get_agent_heartbeat_timeout(default_timeout: int | None) -> int | None:
|
|
105
|
+
"""Gets the configured agent timeout to target.
|
|
106
|
+
Highest precedence is an agent-timeout argument, then `DAGSTER_CLOUD_AGENT_HEARTBEAT_TIMEOUT`
|
|
107
|
+
env var.
|
|
108
|
+
"""
|
|
109
|
+
env_val = os.getenv(AGENT_HEARTBEAT_TIMEOUT_ENV_VAR_NAME)
|
|
110
|
+
|
|
111
|
+
return int(cast("str", env_val)) if env_val is not None else None
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def get_user_token(ctx: Context | None = None) -> str | None:
|
|
115
|
+
"""Gets the configured user token to use.
|
|
116
|
+
Highest precedence is an api-token argument, then `DAGSTER_CLOUD_API_TOKEN`
|
|
117
|
+
env var, then `~/.dagster_cloud_cli/config` value.
|
|
118
|
+
"""
|
|
119
|
+
if ctx and ctx.params.get(TOKEN_CLI_ARGUMENT_VAR):
|
|
120
|
+
return ctx.params[TOKEN_CLI_ARGUMENT_VAR]
|
|
121
|
+
return os.getenv(TOKEN_ENV_VAR_NAME, read_config().user_token)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def available_deployment_names(ctx, incomplete: str = "") -> list[str]:
|
|
125
|
+
"""Gets a list of deployment names given the Typer Context, used for CLI completion."""
|
|
126
|
+
organization = get_organization(ctx=ctx)
|
|
127
|
+
user_token = get_user_token(ctx=ctx)
|
|
128
|
+
|
|
129
|
+
if not organization or not user_token:
|
|
130
|
+
return []
|
|
131
|
+
try:
|
|
132
|
+
with gql.graphql_client_from_url(gql.url_from_config(organization), user_token) as client:
|
|
133
|
+
deployments = gql.fetch_full_deployments(client)
|
|
134
|
+
names = [deployment["deploymentName"] for deployment in deployments]
|
|
135
|
+
return [name for name in names if name.startswith(incomplete)]
|
|
136
|
+
except DagsterCloudHTTPError:
|
|
137
|
+
return []
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def get_url(ctx: Context | None = None) -> str | None:
|
|
141
|
+
"""Gets the url passed in or from the environment."""
|
|
142
|
+
if ctx and ctx.params.get(URL_CLI_ARGUMENT):
|
|
143
|
+
return ctx.params[URL_CLI_ARGUMENT]
|
|
144
|
+
return os.getenv(URL_ENV_VAR_NAME)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def get_org_url(organization: str, dagster_env: str | None):
|
|
148
|
+
if dagster_env:
|
|
149
|
+
return f"https://{organization}.{dagster_env}.dagster.cloud"
|
|
150
|
+
return f"https://{organization}.dagster.cloud"
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
# Typer Option definitions for common CLI config options (organization, deployment, user token)
|
|
154
|
+
ORGANIZATION_OPTION = Option(
|
|
155
|
+
get_organization,
|
|
156
|
+
"--organization",
|
|
157
|
+
"-o",
|
|
158
|
+
help="Organization to target.",
|
|
159
|
+
show_default=get_organization(), # type: ignore
|
|
160
|
+
)
|
|
161
|
+
DEPLOYMENT_OPTION = Option(
|
|
162
|
+
get_deployment,
|
|
163
|
+
"--deployment",
|
|
164
|
+
"-d",
|
|
165
|
+
help="Deployment to target.",
|
|
166
|
+
autocompletion=available_deployment_names,
|
|
167
|
+
show_default=get_deployment(), # type: ignore
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
DAGSTER_ENV_OPTION = Option(
|
|
171
|
+
None,
|
|
172
|
+
"--dagster-env",
|
|
173
|
+
hidden=True,
|
|
174
|
+
envvar="DAGSTER_CLOUD_ENV",
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
USER_TOKEN_OPTION = Option(
|
|
178
|
+
get_user_token,
|
|
179
|
+
"--api-token",
|
|
180
|
+
"--user-token",
|
|
181
|
+
"-u",
|
|
182
|
+
help="Cloud user token.",
|
|
183
|
+
show_default=ui.censor_token(get_user_token()) if get_user_token() else None, # type: ignore
|
|
184
|
+
)
|
|
185
|
+
URL_OPTION = Option(
|
|
186
|
+
get_url,
|
|
187
|
+
"--url",
|
|
188
|
+
help=(
|
|
189
|
+
"[DEPRECATED] Your Dagster Cloud url, in the form of"
|
|
190
|
+
" 'https://{ORGANIZATION_NAME}.dagster.cloud/{DEPLOYMENT_NAME}'."
|
|
191
|
+
),
|
|
192
|
+
hidden=True,
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
LOCATION_LOAD_TIMEOUT_OPTION = Option(
|
|
196
|
+
get_location_load_timeout(),
|
|
197
|
+
f"--{LOCATION_LOAD_TIMEOUT_CLI_ARGUMENT}",
|
|
198
|
+
"--agent-timeout",
|
|
199
|
+
help=(
|
|
200
|
+
"After making changes to the workspace. how long in seconds to wait for the location to"
|
|
201
|
+
" load before timing out with an error"
|
|
202
|
+
),
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def get_agent_heartbeat_timeout_option(default_timeout: int | None):
|
|
207
|
+
return Option(
|
|
208
|
+
get_agent_heartbeat_timeout(default_timeout),
|
|
209
|
+
f"--{AGENT_HEARTBEAT_TIMEOUT_CLI_ARGUMENT}",
|
|
210
|
+
"--agent-timeout",
|
|
211
|
+
help=(
|
|
212
|
+
"After making changes to the workspace. how long in seconds to wait for the agent to"
|
|
213
|
+
" heartbeat before timing out with an error"
|
|
214
|
+
),
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def dagster_cloud_options(
|
|
219
|
+
allow_empty: bool = False,
|
|
220
|
+
allow_empty_deployment: bool = False,
|
|
221
|
+
requires_url: bool = False,
|
|
222
|
+
):
|
|
223
|
+
"""Apply this decorator to Typer commands to make them take the
|
|
224
|
+
`organization`, `deployment`, and `api-token` arguments.
|
|
225
|
+
These values are passed as keyword arguments.
|
|
226
|
+
|
|
227
|
+
Unless `allow_empty` or `allow_empty_deployment` are set, an error
|
|
228
|
+
will be raised if these values are not specified as arguments or via config/env var.
|
|
229
|
+
|
|
230
|
+
Set `requires_url` if the command needs a Dagster webserver url; if so, this will be provided
|
|
231
|
+
via the `url` keyword argument.
|
|
232
|
+
"""
|
|
233
|
+
|
|
234
|
+
def decorator(to_wrap):
|
|
235
|
+
wrapped_sig = inspect.signature(to_wrap)
|
|
236
|
+
params = collections.OrderedDict(wrapped_sig.parameters)
|
|
237
|
+
|
|
238
|
+
options = {
|
|
239
|
+
ORGANIZATION_CLI_ARGUMENT: (str, ORGANIZATION_OPTION),
|
|
240
|
+
TOKEN_CLI_ARGUMENT_VAR: (str, USER_TOKEN_OPTION),
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
# For requests that need a graphql client, a user can supply a URL (though this
|
|
244
|
+
# is a hidden option), or an organization and deployment
|
|
245
|
+
if requires_url:
|
|
246
|
+
options[URL_CLI_ARGUMENT] = (str, URL_OPTION)
|
|
247
|
+
|
|
248
|
+
has_deployment_param = (
|
|
249
|
+
DEPLOYMENT_CLI_ARGUMENT in params
|
|
250
|
+
and params[DEPLOYMENT_CLI_ARGUMENT].default is params[DEPLOYMENT_CLI_ARGUMENT].empty
|
|
251
|
+
) or requires_url
|
|
252
|
+
if has_deployment_param:
|
|
253
|
+
options[DEPLOYMENT_CLI_ARGUMENT] = (str, DEPLOYMENT_OPTION)
|
|
254
|
+
|
|
255
|
+
has_location_load_timeout_param = LOCATION_LOAD_TIMEOUT_ARGUMENT_VAR in params
|
|
256
|
+
if has_location_load_timeout_param:
|
|
257
|
+
options[LOCATION_LOAD_TIMEOUT_ARGUMENT_VAR] = ( # pyright: ignore[reportArgumentType]
|
|
258
|
+
int,
|
|
259
|
+
LOCATION_LOAD_TIMEOUT_OPTION,
|
|
260
|
+
)
|
|
261
|
+
has_agent_heartbeat_timeout_param = AGENT_HEARTBEAT_TIMEOUT_ARGUMENT_VAR in params
|
|
262
|
+
if has_agent_heartbeat_timeout_param:
|
|
263
|
+
options[AGENT_HEARTBEAT_TIMEOUT_ARGUMENT_VAR] = ( # pyright: ignore[reportArgumentType]
|
|
264
|
+
int,
|
|
265
|
+
get_agent_heartbeat_timeout_option(default_timeout=60),
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
with_options = add_options(options)(to_wrap)
|
|
269
|
+
|
|
270
|
+
@functools.wraps(with_options)
|
|
271
|
+
def wrap_function(*args, **kwargs):
|
|
272
|
+
# If underlying command needs a URL and none is explicitly provided,
|
|
273
|
+
# generate one from the organization + deployment
|
|
274
|
+
if (
|
|
275
|
+
requires_url
|
|
276
|
+
and not kwargs.get(URL_CLI_ARGUMENT)
|
|
277
|
+
and kwargs.get(ORGANIZATION_CLI_ARGUMENT)
|
|
278
|
+
and kwargs.get(DEPLOYMENT_CLI_ARGUMENT)
|
|
279
|
+
):
|
|
280
|
+
kwargs[URL_CLI_ARGUMENT] = gql.url_from_config(
|
|
281
|
+
organization=kwargs.get(ORGANIZATION_CLI_ARGUMENT), # pyright: ignore[reportArgumentType]
|
|
282
|
+
deployment=kwargs.get(DEPLOYMENT_CLI_ARGUMENT),
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
lacking_url = requires_url and not kwargs.get(URL_CLI_ARGUMENT)
|
|
286
|
+
|
|
287
|
+
# Raise errors if important options are not provided
|
|
288
|
+
if not kwargs.get(TOKEN_CLI_ARGUMENT_VAR) and (not allow_empty or lacking_url):
|
|
289
|
+
raise ui.error(
|
|
290
|
+
"A Dagster Cloud user token must be specified for this command.\n\nYou may"
|
|
291
|
+
" specify a token by:\n- Providing the"
|
|
292
|
+
f" {ui.as_code('--' + TOKEN_CLI_ARGUMENT)} parameter.\n- Setting the"
|
|
293
|
+
f" {ui.as_code(TOKEN_ENV_VAR_NAME)} environment variable.\n- Specifying"
|
|
294
|
+
f" {ui.as_code('user_token')} in your config file ({get_config_path()}), run"
|
|
295
|
+
f" {ui.as_code('dagster-cloud config setup')}."
|
|
296
|
+
)
|
|
297
|
+
if not kwargs.get(ORGANIZATION_CLI_ARGUMENT) and (not allow_empty or lacking_url):
|
|
298
|
+
raise ui.error(
|
|
299
|
+
"A Dagster Cloud organization must be specified for this command.\n\nYou may"
|
|
300
|
+
" specify your organization by:\n- Providing the"
|
|
301
|
+
f" {ui.as_code('--' + ORGANIZATION_CLI_ARGUMENT)} parameter.\n- Setting the"
|
|
302
|
+
f" {ui.as_code(ORGANIZATION_ENV_VAR_NAME)} environment variable.\n- Specifying"
|
|
303
|
+
f" {ui.as_code('organization')} in your config file ({get_config_path()}), run"
|
|
304
|
+
f" {ui.as_code('dagster-cloud config setup')}."
|
|
305
|
+
)
|
|
306
|
+
if (
|
|
307
|
+
DEPLOYMENT_CLI_ARGUMENT in kwargs
|
|
308
|
+
and not kwargs.get(DEPLOYMENT_CLI_ARGUMENT)
|
|
309
|
+
and (not (allow_empty or allow_empty_deployment) or lacking_url)
|
|
310
|
+
):
|
|
311
|
+
raise ui.error(
|
|
312
|
+
"A Dagster Cloud deployment must be specified for this command.\n\nYou may"
|
|
313
|
+
" specify a deployment by:\n- Providing the"
|
|
314
|
+
f" {ui.as_code('--' + DEPLOYMENT_CLI_ARGUMENT)} parameter.\n- Setting the"
|
|
315
|
+
f" {ui.as_code(DEPLOYMENT_ENV_VAR_NAME)} environment variable.\n- Running"
|
|
316
|
+
f" {ui.as_code('dagster-cloud config set-deployment <deployment_name>')}.\n-"
|
|
317
|
+
f" Specifying {ui.as_code('default_deployment')} in your config file"
|
|
318
|
+
f" ({get_config_path()})."
|
|
319
|
+
)
|
|
320
|
+
|
|
321
|
+
new_kwargs = dict(kwargs)
|
|
322
|
+
with_options(*args, **new_kwargs)
|
|
323
|
+
|
|
324
|
+
return wrap_function
|
|
325
|
+
|
|
326
|
+
return decorator
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
DEPLOYMENT_METADATA_OPTIONS = {
|
|
330
|
+
# Options to specify code location metadata inline
|
|
331
|
+
"image": (str, Option(None, "--image", help="Docker image.")),
|
|
332
|
+
"location_name": (str, Option(None, "--location-name", help="Location name.")),
|
|
333
|
+
"python_file": (
|
|
334
|
+
Path,
|
|
335
|
+
Option(
|
|
336
|
+
None,
|
|
337
|
+
"--python-file",
|
|
338
|
+
"-f",
|
|
339
|
+
exists=False,
|
|
340
|
+
help="Python file where repository lives.",
|
|
341
|
+
),
|
|
342
|
+
),
|
|
343
|
+
"working_directory": (
|
|
344
|
+
str,
|
|
345
|
+
Option(
|
|
346
|
+
None,
|
|
347
|
+
"--working-directory",
|
|
348
|
+
"-d",
|
|
349
|
+
help="Base directory to use for local imports when loading the repositories.",
|
|
350
|
+
),
|
|
351
|
+
),
|
|
352
|
+
"package_name": (
|
|
353
|
+
str,
|
|
354
|
+
Option(
|
|
355
|
+
None,
|
|
356
|
+
"--package-name",
|
|
357
|
+
"-p",
|
|
358
|
+
help="Local or installed Python package where repositories live",
|
|
359
|
+
),
|
|
360
|
+
),
|
|
361
|
+
"module_name": (
|
|
362
|
+
str,
|
|
363
|
+
Option(None, "--module-name", "-m", help="Python module where repositories live"),
|
|
364
|
+
),
|
|
365
|
+
"autoload_defs_module_name": (
|
|
366
|
+
str,
|
|
367
|
+
Option(
|
|
368
|
+
None,
|
|
369
|
+
"--autoload-defs-module-name",
|
|
370
|
+
help="Python module to recurse over and automatically load Definitions from",
|
|
371
|
+
),
|
|
372
|
+
),
|
|
373
|
+
"executable_path": (
|
|
374
|
+
str,
|
|
375
|
+
Option(
|
|
376
|
+
None,
|
|
377
|
+
"--executable-path",
|
|
378
|
+
help=(
|
|
379
|
+
"Path to reach the executable to use for the Python environment to load the"
|
|
380
|
+
" repositories. Defaults to the installed `dagster` command-line entry point."
|
|
381
|
+
),
|
|
382
|
+
),
|
|
383
|
+
),
|
|
384
|
+
"attribute": (
|
|
385
|
+
str,
|
|
386
|
+
Option(
|
|
387
|
+
None,
|
|
388
|
+
"--attribute",
|
|
389
|
+
"-a",
|
|
390
|
+
help=(
|
|
391
|
+
"Optional attribute that is either a repository or a function that returns a"
|
|
392
|
+
" repository."
|
|
393
|
+
),
|
|
394
|
+
),
|
|
395
|
+
),
|
|
396
|
+
"commit_hash": (
|
|
397
|
+
str,
|
|
398
|
+
Option(
|
|
399
|
+
None,
|
|
400
|
+
"--commit-hash",
|
|
401
|
+
help="Optional metadata which indicates the commit sha associated with this location.",
|
|
402
|
+
),
|
|
403
|
+
),
|
|
404
|
+
"git_url": (
|
|
405
|
+
str,
|
|
406
|
+
Option(
|
|
407
|
+
None,
|
|
408
|
+
"--git-url",
|
|
409
|
+
help=(
|
|
410
|
+
"Optional metadata which specifies a source code reference link for this location."
|
|
411
|
+
),
|
|
412
|
+
),
|
|
413
|
+
),
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
LOCATION_FILE_OPTIONS = {
|
|
418
|
+
# Specify code location metadata via file
|
|
419
|
+
"location_file": (
|
|
420
|
+
Path,
|
|
421
|
+
Option(
|
|
422
|
+
None,
|
|
423
|
+
"--location-file",
|
|
424
|
+
"--from",
|
|
425
|
+
exists=True,
|
|
426
|
+
help="YAML file specifying code location metadata.",
|
|
427
|
+
),
|
|
428
|
+
),
|
|
429
|
+
}
|
|
430
|
+
DEPLOYMENT_CLI_OPTIONS = {
|
|
431
|
+
**DEPLOYMENT_METADATA_OPTIONS,
|
|
432
|
+
**LOCATION_FILE_OPTIONS,
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
def get_location_document(name: str | None, kwargs: dict[str, Any]) -> dict[str, Any]:
|
|
437
|
+
name = name or kwargs.get("location_name")
|
|
438
|
+
location_file = kwargs.get("location_file")
|
|
439
|
+
location_doc_from_file = {}
|
|
440
|
+
if location_file:
|
|
441
|
+
with open(location_file, encoding="utf8") as f:
|
|
442
|
+
location_doc_from_file = yaml.safe_load(f.read())
|
|
443
|
+
|
|
444
|
+
if not location_file and not name:
|
|
445
|
+
raise ui.error(
|
|
446
|
+
"No location name provided. You must either provide a location file with the"
|
|
447
|
+
f" {ui.as_code('--location-file/--from')} flag or specify the location name as an"
|
|
448
|
+
" argument."
|
|
449
|
+
)
|
|
450
|
+
elif location_file:
|
|
451
|
+
if "locations" in location_doc_from_file:
|
|
452
|
+
if not name:
|
|
453
|
+
raise ui.error(
|
|
454
|
+
"If specifying a file with multiple locations, must specify a location name."
|
|
455
|
+
)
|
|
456
|
+
locations_by_name = {
|
|
457
|
+
loc["location_name"]: loc for loc in location_doc_from_file["locations"]
|
|
458
|
+
}
|
|
459
|
+
if name not in locations_by_name:
|
|
460
|
+
raise ui.error(f"No location with name {name} defined in location file.")
|
|
461
|
+
location_doc_from_file = locations_by_name[name]
|
|
462
|
+
elif (
|
|
463
|
+
name
|
|
464
|
+
and location_doc_from_file.get("location_name")
|
|
465
|
+
and location_doc_from_file["location_name"] != name
|
|
466
|
+
):
|
|
467
|
+
raise ui.error(
|
|
468
|
+
f"Name provided via argument and in location file does not match, {name} =/="
|
|
469
|
+
f" {location_doc_from_file['location_name']}."
|
|
470
|
+
)
|
|
471
|
+
|
|
472
|
+
python_file = kwargs.get("python_file")
|
|
473
|
+
python_file_str = str(python_file) if python_file else None
|
|
474
|
+
|
|
475
|
+
if "build" in location_doc_from_file:
|
|
476
|
+
del location_doc_from_file["build"]
|
|
477
|
+
|
|
478
|
+
location_doc_from_kwargs = remove_none_recursively(
|
|
479
|
+
{
|
|
480
|
+
"location_name": name,
|
|
481
|
+
"code_source": {
|
|
482
|
+
"python_file": python_file_str,
|
|
483
|
+
"module_name": kwargs.get("module_name"),
|
|
484
|
+
"package_name": kwargs.get("package_name"),
|
|
485
|
+
"autoload_defs_module_name": kwargs.get("autoload_defs_module_name"),
|
|
486
|
+
},
|
|
487
|
+
"working_directory": kwargs.get("working_directory"),
|
|
488
|
+
"image": kwargs.get("image"),
|
|
489
|
+
"executable_path": kwargs.get("executable_path"),
|
|
490
|
+
"attribute": kwargs.get("attribute"),
|
|
491
|
+
"git": {
|
|
492
|
+
"commit_hash": kwargs.get("commit_hash"),
|
|
493
|
+
"url": kwargs.get("git_url"),
|
|
494
|
+
},
|
|
495
|
+
"pex_metadata": (
|
|
496
|
+
{
|
|
497
|
+
"pex_tag": kwargs["pex_tag"],
|
|
498
|
+
"python_version": kwargs.get("python_version"),
|
|
499
|
+
}
|
|
500
|
+
if kwargs.get("pex_tag")
|
|
501
|
+
else None
|
|
502
|
+
),
|
|
503
|
+
"defs_state_info": kwargs.get("defs_state_info"),
|
|
504
|
+
}
|
|
505
|
+
)
|
|
506
|
+
return deep_merge_dicts(location_doc_from_file, location_doc_from_kwargs)
|
|
File without changes
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
|
|
3
|
+
from dagster_shared.serdes import whitelist_for_serdes
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@whitelist_for_serdes
|
|
7
|
+
class InsightsAlertComparisonOperator(Enum):
|
|
8
|
+
"""Possible comparison operators for an insights alert type, used to
|
|
9
|
+
determine when to trigger an alert based on the value of the metric.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
LESS_THAN = "LESS_THAN"
|
|
13
|
+
GREATER_THAN = "GREATER_THAN"
|
|
14
|
+
|
|
15
|
+
def compare(self, computed_value: float, target_value: float) -> bool:
|
|
16
|
+
if self == InsightsAlertComparisonOperator.LESS_THAN:
|
|
17
|
+
return computed_value < target_value
|
|
18
|
+
return computed_value > target_value
|
|
19
|
+
|
|
20
|
+
def as_text(self) -> str:
|
|
21
|
+
"""Used in alert text to describe the comparison operator,
|
|
22
|
+
e.g. usage is less than the limit or usage is greater than the limit.
|
|
23
|
+
"""
|
|
24
|
+
if self == InsightsAlertComparisonOperator.LESS_THAN:
|
|
25
|
+
return "less than"
|
|
26
|
+
return "greater than"
|