dagflow-cli-plugin 0.1.0__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.
- dagflow_cli/__init__.py +1 -0
- dagflow_cli/commands.py +423 -0
- dagflow_cli/launch.py +118 -0
- dagflow_cli/manager.py +283 -0
- dagflow_cli/pex.py +523 -0
- dagflow_cli/plugin_spec.py +19 -0
- dagflow_cli/runtime-platforms.json +1221 -0
- dagflow_cli/workspace.py +215 -0
- dagflow_cli_plugin-0.1.0.dist-info/METADATA +30 -0
- dagflow_cli_plugin-0.1.0.dist-info/RECORD +13 -0
- dagflow_cli_plugin-0.1.0.dist-info/WHEEL +5 -0
- dagflow_cli_plugin-0.1.0.dist-info/entry_points.txt +2 -0
- dagflow_cli_plugin-0.1.0.dist-info/top_level.txt +1 -0
dagflow_cli/workspace.py
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import time
|
|
4
|
+
from typing import Any, Callable
|
|
5
|
+
|
|
6
|
+
from dagflow_cli.launch import _post_json
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
RELOAD_REPOSITORY_LOCATION_MUTATION = """
|
|
10
|
+
mutation ReloadRepositoryLocation($repositoryLocationName: String!) {
|
|
11
|
+
reloadRepositoryLocation(repositoryLocationName: $repositoryLocationName) {
|
|
12
|
+
__typename
|
|
13
|
+
... on WorkspaceLocationEntry { name loadStatus }
|
|
14
|
+
... on RepositoryLocationNotFound { message }
|
|
15
|
+
... on ReloadNotSupported { message }
|
|
16
|
+
... on PythonError { message stack }
|
|
17
|
+
... on UnauthorizedError { message }
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
RELOAD_WORKSPACE_MUTATION = """
|
|
23
|
+
mutation ReloadWorkspace {
|
|
24
|
+
reloadWorkspace {
|
|
25
|
+
__typename
|
|
26
|
+
... on Workspace {
|
|
27
|
+
locationEntries {
|
|
28
|
+
name
|
|
29
|
+
loadStatus
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
... on PythonError { message stack }
|
|
33
|
+
... on UnauthorizedError { message }
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
WORKSPACE_LOCATION_STATUS_QUERY = """
|
|
39
|
+
query WorkspaceLocationStatus {
|
|
40
|
+
workspaceOrError {
|
|
41
|
+
__typename
|
|
42
|
+
... on Workspace {
|
|
43
|
+
locationEntries {
|
|
44
|
+
name
|
|
45
|
+
loadStatus
|
|
46
|
+
locationOrLoadError {
|
|
47
|
+
__typename
|
|
48
|
+
... on PythonError { message stack }
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
... on PythonError { message stack }
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class ReloadRepositoryLocationNotFound(RuntimeError):
|
|
59
|
+
pass
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def reload_repository_location(
|
|
63
|
+
endpoint_url: str,
|
|
64
|
+
token: str,
|
|
65
|
+
location_name: str,
|
|
66
|
+
post_json: Callable[[str, dict[str, Any], str], dict[str, Any]] | None = None,
|
|
67
|
+
) -> dict[str, Any]:
|
|
68
|
+
dagster_location_name = _dagster_location_name(location_name)
|
|
69
|
+
response = (post_json or _post_json)(
|
|
70
|
+
f"{endpoint_url.rstrip('/')}/graphql",
|
|
71
|
+
{
|
|
72
|
+
"query": RELOAD_REPOSITORY_LOCATION_MUTATION,
|
|
73
|
+
"variables": {"repositoryLocationName": dagster_location_name},
|
|
74
|
+
},
|
|
75
|
+
token,
|
|
76
|
+
)
|
|
77
|
+
return _result_or_raise(response, "reloadRepositoryLocation")
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def reload_workspace(
|
|
81
|
+
endpoint_url: str,
|
|
82
|
+
token: str,
|
|
83
|
+
post_json: Callable[[str, dict[str, Any], str], dict[str, Any]] | None = None,
|
|
84
|
+
) -> dict[str, Any]:
|
|
85
|
+
response = (post_json or _post_json)(
|
|
86
|
+
f"{endpoint_url.rstrip('/')}/graphql",
|
|
87
|
+
{"query": RELOAD_WORKSPACE_MUTATION, "variables": {}},
|
|
88
|
+
token,
|
|
89
|
+
)
|
|
90
|
+
return _result_or_raise(response, "reloadWorkspace")
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def reload_after_code_location_deploy(
|
|
94
|
+
endpoint_url: str,
|
|
95
|
+
token: str,
|
|
96
|
+
location_name: str,
|
|
97
|
+
service_existed: bool,
|
|
98
|
+
post_json: Callable[[str, dict[str, Any], str], dict[str, Any]] | None = None,
|
|
99
|
+
) -> dict[str, Any]:
|
|
100
|
+
dagster_location_name = _dagster_location_name(location_name)
|
|
101
|
+
if service_existed:
|
|
102
|
+
try:
|
|
103
|
+
reload_result = reload_repository_location(
|
|
104
|
+
endpoint_url, token, dagster_location_name, post_json=post_json
|
|
105
|
+
)
|
|
106
|
+
except ReloadRepositoryLocationNotFound:
|
|
107
|
+
reload_result = reload_workspace(endpoint_url, token, post_json=post_json)
|
|
108
|
+
else:
|
|
109
|
+
reload_result = reload_workspace(endpoint_url, token, post_json=post_json)
|
|
110
|
+
wait_for_code_location_loaded(endpoint_url, token, dagster_location_name, post_json=post_json)
|
|
111
|
+
return reload_result
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def reload_after_code_location_delete(
|
|
115
|
+
endpoint_url: str,
|
|
116
|
+
token: str,
|
|
117
|
+
location_name: str,
|
|
118
|
+
post_json: Callable[[str, dict[str, Any], str], dict[str, Any]] | None = None,
|
|
119
|
+
) -> dict[str, Any]:
|
|
120
|
+
dagster_location_name = _dagster_location_name(location_name)
|
|
121
|
+
reload_result = reload_workspace(endpoint_url, token, post_json=post_json)
|
|
122
|
+
wait_for_code_location_removed(endpoint_url, token, dagster_location_name, post_json=post_json)
|
|
123
|
+
return reload_result
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def wait_for_code_location_loaded(
|
|
127
|
+
endpoint_url: str,
|
|
128
|
+
token: str,
|
|
129
|
+
location_name: str,
|
|
130
|
+
*,
|
|
131
|
+
timeout_s: float = 300,
|
|
132
|
+
interval_s: float = 5,
|
|
133
|
+
post_json: Callable[[str, dict[str, Any], str], dict[str, Any]] | None = None,
|
|
134
|
+
) -> dict[str, Any]:
|
|
135
|
+
deadline = time.monotonic() + timeout_s
|
|
136
|
+
last_entry: dict[str, Any] | None = None
|
|
137
|
+
while time.monotonic() < deadline:
|
|
138
|
+
entry = code_location_entry(endpoint_url, token, location_name, post_json=post_json)
|
|
139
|
+
if entry is not None:
|
|
140
|
+
last_entry = entry
|
|
141
|
+
error = entry.get("locationOrLoadError")
|
|
142
|
+
if isinstance(error, dict) and error.get("__typename") == "PythonError":
|
|
143
|
+
raise RuntimeError(f"Dagster code location {location_name} failed to load: {error}")
|
|
144
|
+
if entry.get("loadStatus") == "LOADED":
|
|
145
|
+
return entry
|
|
146
|
+
time.sleep(interval_s)
|
|
147
|
+
raise TimeoutError(
|
|
148
|
+
f"Timed out waiting for Dagster code location {location_name} to load; "
|
|
149
|
+
f"last entry was {last_entry}"
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def wait_for_code_location_removed(
|
|
154
|
+
endpoint_url: str,
|
|
155
|
+
token: str,
|
|
156
|
+
location_name: str,
|
|
157
|
+
*,
|
|
158
|
+
timeout_s: float = 300,
|
|
159
|
+
interval_s: float = 5,
|
|
160
|
+
post_json: Callable[[str, dict[str, Any], str], dict[str, Any]] | None = None,
|
|
161
|
+
) -> None:
|
|
162
|
+
deadline = time.monotonic() + timeout_s
|
|
163
|
+
last_entry: dict[str, Any] | None = None
|
|
164
|
+
while time.monotonic() < deadline:
|
|
165
|
+
entry = code_location_entry(endpoint_url, token, location_name, post_json=post_json)
|
|
166
|
+
if entry is None:
|
|
167
|
+
return
|
|
168
|
+
last_entry = entry
|
|
169
|
+
time.sleep(interval_s)
|
|
170
|
+
raise TimeoutError(
|
|
171
|
+
f"Timed out waiting for Dagster code location {location_name} to be removed; "
|
|
172
|
+
f"last entry was {last_entry}"
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def code_location_entry(
|
|
177
|
+
endpoint_url: str,
|
|
178
|
+
token: str,
|
|
179
|
+
location_name: str,
|
|
180
|
+
post_json: Callable[[str, dict[str, Any], str], dict[str, Any]] | None = None,
|
|
181
|
+
) -> dict[str, Any] | None:
|
|
182
|
+
dagster_location_name = _dagster_location_name(location_name)
|
|
183
|
+
response = (post_json or _post_json)(
|
|
184
|
+
f"{endpoint_url.rstrip('/')}/graphql",
|
|
185
|
+
{"query": WORKSPACE_LOCATION_STATUS_QUERY, "variables": {}},
|
|
186
|
+
token,
|
|
187
|
+
)
|
|
188
|
+
if response.get("errors"):
|
|
189
|
+
raise RuntimeError(f"Dagster GraphQL workspace query failed: {response['errors']}")
|
|
190
|
+
workspace = response.get("data", {}).get("workspaceOrError")
|
|
191
|
+
if not isinstance(workspace, dict):
|
|
192
|
+
raise RuntimeError(f"Dagster GraphQL workspace query returned an unexpected response: {response}")
|
|
193
|
+
if workspace.get("__typename") != "Workspace":
|
|
194
|
+
raise RuntimeError(f"Dagster GraphQL workspace query failed: {workspace}")
|
|
195
|
+
for entry in workspace.get("locationEntries", []):
|
|
196
|
+
if entry.get("name") == dagster_location_name:
|
|
197
|
+
return entry
|
|
198
|
+
return None
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def _result_or_raise(response: dict[str, Any], field_name: str) -> dict[str, Any]:
|
|
202
|
+
if response.get("errors"):
|
|
203
|
+
raise RuntimeError(f"Dagster GraphQL reload failed: {response['errors']}")
|
|
204
|
+
result = response.get("data", {}).get(field_name)
|
|
205
|
+
if not isinstance(result, dict):
|
|
206
|
+
raise RuntimeError(f"Dagster GraphQL reload returned an unexpected response: {response}")
|
|
207
|
+
if result.get("__typename") in {"Workspace", "WorkspaceLocationEntry"}:
|
|
208
|
+
return result
|
|
209
|
+
if result.get("__typename") == "RepositoryLocationNotFound":
|
|
210
|
+
raise ReloadRepositoryLocationNotFound(f"Dagster GraphQL reload failed: {result}")
|
|
211
|
+
raise RuntimeError(f"Dagster GraphQL reload failed: {result}")
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def _dagster_location_name(location_name: str) -> str:
|
|
215
|
+
return location_name.lower()
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: dagflow-cli-plugin
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Snowflake CLI plugin for managing Dagflow deployments.
|
|
5
|
+
Requires-Python: <3.13,>=3.11
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
Requires-Dist: dagster-dg-core>=1.12.1
|
|
8
|
+
Requires-Dist: packaging<27,>=24
|
|
9
|
+
Requires-Dist: pex<3,>=2.33
|
|
10
|
+
Requires-Dist: pyyaml<7,>=6
|
|
11
|
+
Requires-Dist: snowflake-cli<4,>=3
|
|
12
|
+
|
|
13
|
+
# Dagflow CLI
|
|
14
|
+
|
|
15
|
+
Snowflake CLI plugin for building, deploying, and managing Dagflow code locations.
|
|
16
|
+
|
|
17
|
+
Install it in the same Python environment as Snowflake CLI:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
pip install dagflow-cli-plugin
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Enable the external plugin in the Snowflake CLI configuration:
|
|
24
|
+
|
|
25
|
+
```toml
|
|
26
|
+
[cli.plugins.dg]
|
|
27
|
+
enabled = true
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Dagflow commands are then available under `snow dg`.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
dagflow_cli/__init__.py,sha256=wsTNGr7bQbNQDQeLV7mDb2lCtW550s52LSR7RkZojr4,48
|
|
2
|
+
dagflow_cli/commands.py,sha256=19XXfmw8CNaq91A-BI_ypzDB5emRk4VGjloDTRtlmLA,16526
|
|
3
|
+
dagflow_cli/launch.py,sha256=5aNcMwi0jBA8Encz47jxhbI8xV7onXyYjtsOvRwmBWc,3998
|
|
4
|
+
dagflow_cli/manager.py,sha256=N1SyuF7-gjApjeMADA84f3iU5ccnEXEdWtnn79XSz2w,11499
|
|
5
|
+
dagflow_cli/pex.py,sha256=_3Q8I3p_dFTiq0sglJKX30kxfZxPwySMoZqGJ090gPM,16867
|
|
6
|
+
dagflow_cli/plugin_spec.py,sha256=WcNplrJZ5fqMfL_drydVw3r_hjjkr0jUMGKI46HKf7g,450
|
|
7
|
+
dagflow_cli/runtime-platforms.json,sha256=fCQznKQsGwcQndqgdplky-qmla3Xy_cl3aGDE3D2IxI,52331
|
|
8
|
+
dagflow_cli/workspace.py,sha256=Hh29w-g9EV5omdKVVdU1Qy7kUz6DI5Hg9lO5sFvREPk,7238
|
|
9
|
+
dagflow_cli_plugin-0.1.0.dist-info/METADATA,sha256=KO0W3idaxOpTiyvHdG0IT0yfJlOF8cwusf_4AtXZ7Ss,726
|
|
10
|
+
dagflow_cli_plugin-0.1.0.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
|
|
11
|
+
dagflow_cli_plugin-0.1.0.dist-info/entry_points.txt,sha256=oDpBkyH-Bhp8JuQv74ZrTWMm8WO1jymR8Xem-0aw8KE,60
|
|
12
|
+
dagflow_cli_plugin-0.1.0.dist-info/top_level.txt,sha256=iCuyZvhHP_1btyzkW7izMxij84empD5tyk2LlkAfxuU,12
|
|
13
|
+
dagflow_cli_plugin-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
dagflow_cli
|