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.
@@ -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,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (83.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [snowflake.cli.plugin.command]
2
+ dg = dagflow_cli.plugin_spec
@@ -0,0 +1 @@
1
+ dagflow_cli