snowflake-cli 3.10.1__py3-none-any.whl → 3.12.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.
- snowflake/cli/__about__.py +1 -1
- snowflake/cli/_app/auth/__init__.py +13 -0
- snowflake/cli/_app/auth/errors.py +28 -0
- snowflake/cli/_app/auth/oidc_providers.py +393 -0
- snowflake/cli/_app/cli_app.py +0 -1
- snowflake/cli/_app/constants.py +10 -0
- snowflake/cli/_app/printing.py +153 -19
- snowflake/cli/_app/snow_connector.py +35 -0
- snowflake/cli/_plugins/auth/__init__.py +4 -2
- snowflake/cli/_plugins/auth/keypair/commands.py +2 -0
- snowflake/cli/_plugins/auth/oidc/__init__.py +13 -0
- snowflake/cli/_plugins/auth/oidc/commands.py +47 -0
- snowflake/cli/_plugins/auth/oidc/manager.py +66 -0
- snowflake/cli/_plugins/auth/oidc/plugin_spec.py +30 -0
- snowflake/cli/_plugins/connection/commands.py +37 -3
- snowflake/cli/_plugins/dbt/commands.py +37 -8
- snowflake/cli/_plugins/dbt/manager.py +144 -12
- snowflake/cli/_plugins/dcm/commands.py +102 -136
- snowflake/cli/_plugins/dcm/manager.py +136 -89
- snowflake/cli/_plugins/logs/commands.py +7 -0
- snowflake/cli/_plugins/logs/manager.py +21 -1
- snowflake/cli/_plugins/nativeapp/sf_sql_facade.py +3 -1
- snowflake/cli/_plugins/notebook/notebook_entity.py +2 -0
- snowflake/cli/_plugins/notebook/notebook_entity_model.py +8 -1
- snowflake/cli/_plugins/object/command_aliases.py +16 -1
- snowflake/cli/_plugins/object/commands.py +27 -1
- snowflake/cli/_plugins/object/manager.py +12 -1
- snowflake/cli/_plugins/snowpark/commands.py +8 -1
- snowflake/cli/_plugins/snowpark/common.py +1 -0
- snowflake/cli/_plugins/snowpark/package/anaconda_packages.py +29 -5
- snowflake/cli/_plugins/snowpark/package_utils.py +44 -3
- snowflake/cli/_plugins/spcs/services/manager.py +5 -4
- snowflake/cli/_plugins/sql/lexer/types.py +1 -0
- snowflake/cli/_plugins/sql/repl.py +100 -26
- snowflake/cli/_plugins/sql/repl_commands.py +607 -0
- snowflake/cli/_plugins/sql/statement_reader.py +44 -20
- snowflake/cli/api/artifacts/bundle_map.py +32 -2
- snowflake/cli/api/artifacts/regex_resolver.py +54 -0
- snowflake/cli/api/artifacts/upload.py +5 -1
- snowflake/cli/api/artifacts/utils.py +12 -1
- snowflake/cli/api/cli_global_context.py +7 -0
- snowflake/cli/api/commands/decorators.py +7 -0
- snowflake/cli/api/commands/flags.py +26 -0
- snowflake/cli/api/config.py +24 -0
- snowflake/cli/api/connections.py +1 -0
- snowflake/cli/api/console/abc.py +13 -2
- snowflake/cli/api/console/console.py +20 -0
- snowflake/cli/api/constants.py +9 -0
- snowflake/cli/api/entities/utils.py +10 -6
- snowflake/cli/api/feature_flags.py +1 -0
- snowflake/cli/api/identifiers.py +18 -1
- snowflake/cli/api/project/schemas/entities/entities.py +0 -6
- snowflake/cli/api/rendering/sql_templates.py +2 -0
- snowflake/cli/api/utils/dict_utils.py +42 -1
- {snowflake_cli-3.10.1.dist-info → snowflake_cli-3.12.0.dist-info}/METADATA +15 -41
- {snowflake_cli-3.10.1.dist-info → snowflake_cli-3.12.0.dist-info}/RECORD +59 -52
- snowflake/cli/_plugins/dcm/dcm_project_entity_model.py +0 -59
- snowflake/cli/_plugins/sql/snowsql_commands.py +0 -331
- {snowflake_cli-3.10.1.dist-info → snowflake_cli-3.12.0.dist-info}/WHEEL +0 -0
- {snowflake_cli-3.10.1.dist-info → snowflake_cli-3.12.0.dist-info}/entry_points.txt +0 -0
- {snowflake_cli-3.10.1.dist-info → snowflake_cli-3.12.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -11,33 +11,26 @@
|
|
|
11
11
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
|
-
|
|
15
14
|
from typing import List, Optional
|
|
16
15
|
|
|
17
16
|
import typer
|
|
18
|
-
from snowflake.cli._plugins.dcm.dcm_project_entity_model import (
|
|
19
|
-
DCMProjectEntityModel,
|
|
20
|
-
)
|
|
21
17
|
from snowflake.cli._plugins.dcm.manager import DCMProjectManager
|
|
22
18
|
from snowflake.cli._plugins.object.command_aliases import add_object_command_aliases
|
|
23
19
|
from snowflake.cli._plugins.object.commands import scope_option
|
|
24
20
|
from snowflake.cli._plugins.object.manager import ObjectManager
|
|
25
|
-
from snowflake.cli.api.cli_global_context import get_cli_context
|
|
26
|
-
from snowflake.cli.api.commands.decorators import with_project_definition
|
|
27
21
|
from snowflake.cli.api.commands.flags import (
|
|
28
22
|
IfExistsOption,
|
|
29
23
|
IfNotExistsOption,
|
|
30
24
|
OverrideableOption,
|
|
31
|
-
PruneOption,
|
|
32
|
-
entity_argument,
|
|
33
25
|
identifier_argument,
|
|
34
26
|
like_option,
|
|
35
27
|
variables_option,
|
|
36
28
|
)
|
|
37
29
|
from snowflake.cli.api.commands.snow_typer import SnowTyperFactory
|
|
38
|
-
from snowflake.cli.api.commands.utils import get_entity_for_operation
|
|
39
30
|
from snowflake.cli.api.console.console import cli_console
|
|
40
|
-
from snowflake.cli.api.constants import
|
|
31
|
+
from snowflake.cli.api.constants import (
|
|
32
|
+
ObjectType,
|
|
33
|
+
)
|
|
41
34
|
from snowflake.cli.api.exceptions import CliError
|
|
42
35
|
from snowflake.cli.api.feature_flags import FeatureFlag
|
|
43
36
|
from snowflake.cli.api.identifiers import FQN
|
|
@@ -46,6 +39,7 @@ from snowflake.cli.api.output.types import (
|
|
|
46
39
|
QueryJsonValueResult,
|
|
47
40
|
QueryResult,
|
|
48
41
|
)
|
|
42
|
+
from snowflake.cli.api.utils.path_utils import is_stage_path
|
|
49
43
|
|
|
50
44
|
app = SnowTyperFactory(
|
|
51
45
|
name="dcm",
|
|
@@ -54,12 +48,6 @@ app = SnowTyperFactory(
|
|
|
54
48
|
)
|
|
55
49
|
|
|
56
50
|
dcm_identifier = identifier_argument(sf_object="DCM Project", example="MY_PROJECT")
|
|
57
|
-
version_flag = typer.Option(
|
|
58
|
-
None,
|
|
59
|
-
"--version",
|
|
60
|
-
help="Version of the DCM Project to use. If not specified default version is used. For names containing '$', use single quotes to prevent shell expansion (e.g., 'VERSION$1').",
|
|
61
|
-
show_default=False,
|
|
62
|
-
)
|
|
63
51
|
variables_flag = variables_option(
|
|
64
52
|
'Variables for the execution context; for example: `-D "<key>=<value>"`.'
|
|
65
53
|
)
|
|
@@ -69,9 +57,36 @@ configuration_flag = typer.Option(
|
|
|
69
57
|
help="Configuration of the DCM Project to use. If not specified default configuration is used.",
|
|
70
58
|
show_default=False,
|
|
71
59
|
)
|
|
72
|
-
from_option =
|
|
60
|
+
from_option = typer.Option(
|
|
73
61
|
None,
|
|
74
62
|
"--from",
|
|
63
|
+
help="Source location: stage path (starting with '@') or local directory path. Omit to use current directory.",
|
|
64
|
+
show_default=False,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
alias_option = typer.Option(
|
|
68
|
+
None,
|
|
69
|
+
"--alias",
|
|
70
|
+
help="Alias for the deployment.",
|
|
71
|
+
show_default=False,
|
|
72
|
+
)
|
|
73
|
+
output_path_option = OverrideableOption(
|
|
74
|
+
None,
|
|
75
|
+
"--output-path",
|
|
76
|
+
show_default=False,
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
terse_option = typer.Option(
|
|
80
|
+
False,
|
|
81
|
+
"--terse",
|
|
82
|
+
help="Returns only a subset of output columns.",
|
|
83
|
+
show_default=False,
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
limit_option = typer.Option(
|
|
87
|
+
None,
|
|
88
|
+
"--limit",
|
|
89
|
+
help="Limits the maximum number of rows returned.",
|
|
75
90
|
show_default=False,
|
|
76
91
|
)
|
|
77
92
|
|
|
@@ -85,73 +100,73 @@ add_object_command_aliases(
|
|
|
85
100
|
),
|
|
86
101
|
scope_option=scope_option(help_example="`list --in database my_db`"),
|
|
87
102
|
ommit_commands=["create"],
|
|
103
|
+
terse_option=terse_option,
|
|
104
|
+
limit_option=limit_option,
|
|
88
105
|
)
|
|
89
106
|
|
|
90
107
|
|
|
91
108
|
@app.command(requires_connection=True)
|
|
92
109
|
def deploy(
|
|
93
110
|
identifier: FQN = dcm_identifier,
|
|
94
|
-
|
|
95
|
-
from_stage: Optional[str] = from_option(
|
|
96
|
-
help="Apply changes defined in given stage instead of using a specific project version."
|
|
97
|
-
),
|
|
111
|
+
from_location: Optional[str] = from_option,
|
|
98
112
|
variables: Optional[List[str]] = variables_flag,
|
|
99
113
|
configuration: Optional[str] = configuration_flag,
|
|
114
|
+
alias: Optional[str] = alias_option,
|
|
100
115
|
**options,
|
|
101
116
|
):
|
|
102
117
|
"""
|
|
103
118
|
Applies changes defined in DCM Project to Snowflake.
|
|
104
119
|
"""
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
120
|
+
manager = DCMProjectManager()
|
|
121
|
+
effective_stage = _get_effective_stage(identifier, from_location)
|
|
122
|
+
|
|
123
|
+
with cli_console.spinner() as spinner:
|
|
124
|
+
spinner.add_task(description=f"Deploying dcm project {identifier}", total=None)
|
|
125
|
+
result = manager.execute(
|
|
126
|
+
project_identifier=identifier,
|
|
127
|
+
configuration=configuration,
|
|
128
|
+
from_stage=effective_stage,
|
|
129
|
+
variables=variables,
|
|
130
|
+
alias=alias,
|
|
131
|
+
output_path=None,
|
|
132
|
+
)
|
|
115
133
|
return QueryJsonValueResult(result)
|
|
116
134
|
|
|
117
135
|
|
|
118
136
|
@app.command(requires_connection=True)
|
|
119
137
|
def plan(
|
|
120
138
|
identifier: FQN = dcm_identifier,
|
|
121
|
-
|
|
122
|
-
from_stage: Optional[str] = from_option(
|
|
123
|
-
help="Plan DCM Project deployment from given stage instead of using a specific version."
|
|
124
|
-
),
|
|
139
|
+
from_location: Optional[str] = from_option,
|
|
125
140
|
variables: Optional[List[str]] = variables_flag,
|
|
126
141
|
configuration: Optional[str] = configuration_flag,
|
|
142
|
+
output_path: Optional[str] = output_path_option(
|
|
143
|
+
help="Path where the deployment plan output will be stored. Can be a stage path (starting with '@') or a local directory path."
|
|
144
|
+
),
|
|
127
145
|
**options,
|
|
128
146
|
):
|
|
129
147
|
"""
|
|
130
148
|
Plans a DCM Project deployment (validates without executing).
|
|
131
149
|
"""
|
|
132
|
-
|
|
133
|
-
|
|
150
|
+
manager = DCMProjectManager()
|
|
151
|
+
effective_stage = _get_effective_stage(identifier, from_location)
|
|
152
|
+
|
|
153
|
+
with cli_console.spinner() as spinner:
|
|
154
|
+
spinner.add_task(description=f"Planning dcm project {identifier}", total=None)
|
|
155
|
+
result = manager.execute(
|
|
156
|
+
project_identifier=identifier,
|
|
157
|
+
configuration=configuration,
|
|
158
|
+
from_stage=effective_stage,
|
|
159
|
+
dry_run=True,
|
|
160
|
+
variables=variables,
|
|
161
|
+
output_path=output_path,
|
|
162
|
+
)
|
|
134
163
|
|
|
135
|
-
result = DCMProjectManager().execute(
|
|
136
|
-
project_name=identifier,
|
|
137
|
-
configuration=configuration,
|
|
138
|
-
version=version,
|
|
139
|
-
from_stage=from_stage,
|
|
140
|
-
dry_run=True,
|
|
141
|
-
variables=variables,
|
|
142
|
-
)
|
|
143
164
|
return QueryJsonValueResult(result)
|
|
144
165
|
|
|
145
166
|
|
|
146
167
|
@app.command(requires_connection=True)
|
|
147
|
-
@with_project_definition()
|
|
148
168
|
def create(
|
|
149
|
-
|
|
150
|
-
no_version: bool = typer.Option(
|
|
151
|
-
False,
|
|
152
|
-
"--no-version",
|
|
153
|
-
help="Do not initialize DCM Project with a new version, only create the snowflake object.",
|
|
154
|
-
),
|
|
169
|
+
identifier: FQN = dcm_identifier,
|
|
155
170
|
if_not_exists: bool = IfNotExistsOption(
|
|
156
171
|
help="Do nothing if the project already exists."
|
|
157
172
|
),
|
|
@@ -159,124 +174,75 @@ def create(
|
|
|
159
174
|
):
|
|
160
175
|
"""
|
|
161
176
|
Creates a DCM Project in Snowflake.
|
|
162
|
-
By default, the DCM Project is initialized with a new version created from local files.
|
|
163
177
|
"""
|
|
164
|
-
cli_context = get_cli_context()
|
|
165
|
-
project: DCMProjectEntityModel = get_entity_for_operation(
|
|
166
|
-
cli_context=cli_context,
|
|
167
|
-
entity_id=entity_id,
|
|
168
|
-
project_definition=cli_context.project_definition,
|
|
169
|
-
entity_type="dcm",
|
|
170
|
-
)
|
|
171
178
|
om = ObjectManager()
|
|
172
|
-
if om.object_exists(object_type="dcm", fqn=
|
|
173
|
-
message = f"DCM Project '{
|
|
179
|
+
if om.object_exists(object_type="dcm", fqn=identifier):
|
|
180
|
+
message = f"DCM Project '{identifier}' already exists."
|
|
174
181
|
if if_not_exists:
|
|
175
182
|
return MessageResult(message)
|
|
176
183
|
raise CliError(message)
|
|
177
184
|
|
|
178
|
-
if not no_version and om.object_exists(
|
|
179
|
-
object_type="stage", fqn=FQN.from_stage(project.stage)
|
|
180
|
-
):
|
|
181
|
-
raise CliError(f"Stage '{project.stage}' already exists.")
|
|
182
|
-
|
|
183
185
|
dpm = DCMProjectManager()
|
|
184
|
-
with cli_console.phase(f"Creating DCM Project '{
|
|
185
|
-
dpm.create(
|
|
186
|
+
with cli_console.phase(f"Creating DCM Project '{identifier}'"):
|
|
187
|
+
dpm.create(project_identifier=identifier)
|
|
186
188
|
|
|
187
|
-
|
|
188
|
-
return MessageResult(f"DCM Project '{project.fqn}' successfully created.")
|
|
189
|
-
return MessageResult(
|
|
190
|
-
f"DCM Project '{project.fqn}' successfully created and initial version is added."
|
|
191
|
-
)
|
|
189
|
+
return MessageResult(f"DCM Project '{identifier}' successfully created.")
|
|
192
190
|
|
|
193
191
|
|
|
194
192
|
@app.command(requires_connection=True)
|
|
195
|
-
|
|
196
|
-
def add_version(
|
|
197
|
-
entity_id: str = entity_argument("dcm"),
|
|
198
|
-
_from: Optional[str] = from_option(
|
|
199
|
-
help="Create a new version using given stage instead of uploading local files."
|
|
200
|
-
),
|
|
201
|
-
_alias: Optional[str] = typer.Option(
|
|
202
|
-
None, "--alias", help="Alias for the version.", show_default=False
|
|
203
|
-
),
|
|
204
|
-
comment: Optional[str] = typer.Option(
|
|
205
|
-
None, "--comment", help="Version comment.", show_default=False
|
|
206
|
-
),
|
|
207
|
-
prune: bool = PruneOption(default=True),
|
|
208
|
-
**options,
|
|
209
|
-
):
|
|
210
|
-
"""Uploads local files to Snowflake and cerates a new DCM Project version."""
|
|
211
|
-
if _from is not None and prune:
|
|
212
|
-
cli_console.warning(
|
|
213
|
-
"When `--from` option is used, `--prune` option will be ignored and files from stage will be used as they are."
|
|
214
|
-
)
|
|
215
|
-
prune = False
|
|
216
|
-
cli_context = get_cli_context()
|
|
217
|
-
project: DCMProjectEntityModel = get_entity_for_operation(
|
|
218
|
-
cli_context=cli_context,
|
|
219
|
-
entity_id=entity_id,
|
|
220
|
-
project_definition=cli_context.project_definition,
|
|
221
|
-
entity_type="dcm",
|
|
222
|
-
)
|
|
223
|
-
om = ObjectManager()
|
|
224
|
-
if not om.object_exists(object_type="dcm", fqn=project.fqn):
|
|
225
|
-
raise CliError(
|
|
226
|
-
f"DCM Project '{project.fqn}' does not exist. Use `dcm create` command first."
|
|
227
|
-
)
|
|
228
|
-
DCMProjectManager().add_version(
|
|
229
|
-
project=project,
|
|
230
|
-
prune=prune,
|
|
231
|
-
from_stage=_from,
|
|
232
|
-
alias=_alias,
|
|
233
|
-
comment=comment,
|
|
234
|
-
)
|
|
235
|
-
alias_str = "" if _alias is None else f"'{_alias}' "
|
|
236
|
-
return MessageResult(
|
|
237
|
-
f"New version {alias_str}added to DCM Project '{project.fqn}'."
|
|
238
|
-
)
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
@app.command(requires_connection=True)
|
|
242
|
-
def list_versions(
|
|
193
|
+
def list_deployments(
|
|
243
194
|
identifier: FQN = dcm_identifier,
|
|
244
195
|
**options,
|
|
245
196
|
):
|
|
246
197
|
"""
|
|
247
|
-
Lists
|
|
198
|
+
Lists deployments of given DCM Project.
|
|
248
199
|
"""
|
|
249
200
|
pm = DCMProjectManager()
|
|
250
|
-
results = pm.
|
|
201
|
+
results = pm.list_deployments(project_identifier=identifier)
|
|
251
202
|
return QueryResult(results)
|
|
252
203
|
|
|
253
204
|
|
|
254
205
|
@app.command(requires_connection=True)
|
|
255
|
-
def
|
|
206
|
+
def drop_deployment(
|
|
256
207
|
identifier: FQN = dcm_identifier,
|
|
257
|
-
|
|
258
|
-
help="Name or alias of the
|
|
208
|
+
deployment_name: str = typer.Argument(
|
|
209
|
+
help="Name or alias of the deployment to drop. For names containing '$', use single quotes to prevent shell expansion (e.g., 'DEPLOYMENT$1').",
|
|
259
210
|
show_default=False,
|
|
260
211
|
),
|
|
261
|
-
if_exists: bool = IfExistsOption(
|
|
212
|
+
if_exists: bool = IfExistsOption(
|
|
213
|
+
help="Do nothing if the deployment does not exist."
|
|
214
|
+
),
|
|
262
215
|
**options,
|
|
263
216
|
):
|
|
264
217
|
"""
|
|
265
|
-
Drops a
|
|
218
|
+
Drops a deployment from the DCM Project.
|
|
266
219
|
"""
|
|
267
220
|
# Detect potential shell expansion issues
|
|
268
|
-
if
|
|
221
|
+
if deployment_name and deployment_name.upper() == "DEPLOYMENT":
|
|
269
222
|
cli_console.warning(
|
|
270
|
-
f"
|
|
271
|
-
f"If you meant to use a
|
|
223
|
+
f"Deployment name '{deployment_name}' might be truncated due to shell expansion. "
|
|
224
|
+
f"If you meant to use a deployment like 'DEPLOYMENT$1', try using single quotes: 'DEPLOYMENT$1'."
|
|
272
225
|
)
|
|
273
226
|
|
|
274
227
|
dpm = DCMProjectManager()
|
|
275
|
-
dpm.
|
|
276
|
-
|
|
277
|
-
|
|
228
|
+
dpm.drop_deployment(
|
|
229
|
+
project_identifier=identifier,
|
|
230
|
+
deployment_name=deployment_name,
|
|
278
231
|
if_exists=if_exists,
|
|
279
232
|
)
|
|
280
233
|
return MessageResult(
|
|
281
|
-
f"
|
|
234
|
+
f"Deployment '{deployment_name}' dropped from DCM Project '{identifier}'."
|
|
282
235
|
)
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def _get_effective_stage(identifier: FQN, from_location: Optional[str]):
|
|
239
|
+
manager = DCMProjectManager()
|
|
240
|
+
if not from_location:
|
|
241
|
+
from_stage = manager.sync_local_files(project_identifier=identifier)
|
|
242
|
+
elif is_stage_path(from_location):
|
|
243
|
+
from_stage = from_location
|
|
244
|
+
else:
|
|
245
|
+
from_stage = manager.sync_local_files(
|
|
246
|
+
project_identifier=identifier, source_directory=from_location
|
|
247
|
+
)
|
|
248
|
+
return from_stage
|
|
@@ -11,126 +11,173 @@
|
|
|
11
11
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
|
-
from
|
|
15
|
-
from
|
|
14
|
+
from contextlib import contextmanager, nullcontext
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from typing import Generator, List
|
|
16
17
|
|
|
17
|
-
|
|
18
|
+
import yaml
|
|
18
19
|
from snowflake.cli._plugins.stage.manager import StageManager
|
|
19
20
|
from snowflake.cli.api.artifacts.upload import sync_artifacts_with_stage
|
|
20
|
-
from snowflake.cli.api.cli_global_context import get_cli_context
|
|
21
21
|
from snowflake.cli.api.commands.utils import parse_key_value_variables
|
|
22
22
|
from snowflake.cli.api.console.console import cli_console
|
|
23
|
+
from snowflake.cli.api.constants import (
|
|
24
|
+
DEFAULT_SIZE_LIMIT_MB,
|
|
25
|
+
ObjectType,
|
|
26
|
+
PatternMatchingType,
|
|
27
|
+
)
|
|
28
|
+
from snowflake.cli.api.exceptions import CliError
|
|
23
29
|
from snowflake.cli.api.identifiers import FQN
|
|
24
30
|
from snowflake.cli.api.project.project_paths import ProjectPaths
|
|
31
|
+
from snowflake.cli.api.project.schemas.entities.common import PathMapping
|
|
32
|
+
from snowflake.cli.api.secure_path import SecurePath
|
|
25
33
|
from snowflake.cli.api.sql_execution import SqlExecutionMixin
|
|
26
34
|
from snowflake.cli.api.stage_path import StagePath
|
|
27
|
-
from snowflake.
|
|
35
|
+
from snowflake.cli.api.utils.path_utils import is_stage_path
|
|
36
|
+
|
|
37
|
+
MANIFEST_FILE_NAME = "manifest.yml"
|
|
38
|
+
DCM_PROJECT_TYPE = "dcm_project"
|
|
28
39
|
|
|
29
40
|
|
|
30
41
|
class DCMProjectManager(SqlExecutionMixin):
|
|
42
|
+
@contextmanager
|
|
43
|
+
def _collect_output(
|
|
44
|
+
self, project_identifier: FQN, output_path: str
|
|
45
|
+
) -> Generator[str, None, None]:
|
|
46
|
+
"""
|
|
47
|
+
Context manager for handling output path - creates temporary stage for local paths,
|
|
48
|
+
downloads files after execution, and ensures proper cleanup.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
project_identifier: The DCM project identifier
|
|
52
|
+
output_path: Either a stage path (@stage/path) or local directory path
|
|
53
|
+
|
|
54
|
+
Yields:
|
|
55
|
+
str: The effective output path to use in the DCM command
|
|
56
|
+
"""
|
|
57
|
+
temp_stage_for_local_output = None
|
|
58
|
+
stage_manager = StageManager()
|
|
59
|
+
|
|
60
|
+
if should_download_files := not is_stage_path(output_path):
|
|
61
|
+
temp_stage_fqn = FQN.from_resource(
|
|
62
|
+
ObjectType.DCM_PROJECT, project_identifier, "OUTPUT_TMP_STAGE"
|
|
63
|
+
)
|
|
64
|
+
stage_manager.create(temp_stage_fqn, temporary=True)
|
|
65
|
+
effective_output_path = StagePath.from_stage_str(temp_stage_fqn.identifier)
|
|
66
|
+
temp_stage_for_local_output = (temp_stage_fqn.identifier, Path(output_path))
|
|
67
|
+
else:
|
|
68
|
+
effective_output_path = StagePath.from_stage_str(output_path)
|
|
69
|
+
|
|
70
|
+
yield effective_output_path.absolute_path()
|
|
71
|
+
|
|
72
|
+
if should_download_files:
|
|
73
|
+
assert temp_stage_for_local_output is not None
|
|
74
|
+
stage_path, local_path = temp_stage_for_local_output
|
|
75
|
+
stage_manager.get_recursive(stage_path=stage_path, dest_path=local_path)
|
|
76
|
+
cli_console.step(f"Plan output saved to: {local_path.resolve()}")
|
|
77
|
+
else:
|
|
78
|
+
cli_console.step(f"Plan output saved to: {output_path}")
|
|
79
|
+
|
|
31
80
|
def execute(
|
|
32
81
|
self,
|
|
33
|
-
|
|
82
|
+
project_identifier: FQN,
|
|
83
|
+
from_stage: str,
|
|
34
84
|
configuration: str | None = None,
|
|
35
|
-
version: str | None = None,
|
|
36
|
-
from_stage: str | None = None,
|
|
37
85
|
variables: List[str] | None = None,
|
|
38
86
|
dry_run: bool = False,
|
|
87
|
+
alias: str | None = None,
|
|
88
|
+
output_path: str | None = None,
|
|
39
89
|
):
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
90
|
+
with self._collect_output(project_identifier, output_path) if (
|
|
91
|
+
output_path and dry_run
|
|
92
|
+
) else nullcontext() as output_stage:
|
|
93
|
+
query = f"EXECUTE DCM PROJECT {project_identifier.sql_identifier}"
|
|
94
|
+
if dry_run:
|
|
95
|
+
query += " PLAN"
|
|
96
|
+
else:
|
|
97
|
+
query += " DEPLOY"
|
|
98
|
+
if alias:
|
|
99
|
+
query += f' AS "{alias}"'
|
|
100
|
+
if configuration or variables:
|
|
101
|
+
query += f" USING"
|
|
102
|
+
if configuration:
|
|
103
|
+
query += f" CONFIGURATION {configuration}"
|
|
104
|
+
if variables:
|
|
105
|
+
query += StageManager.parse_execute_variables(
|
|
106
|
+
parse_key_value_variables(variables)
|
|
107
|
+
).removeprefix(" using")
|
|
56
108
|
stage_path = StagePath.from_stage_str(from_stage)
|
|
57
109
|
query += f" FROM {stage_path.absolute_path()}"
|
|
58
|
-
|
|
110
|
+
if output_stage is not None:
|
|
111
|
+
query += f" OUTPUT_PATH {output_stage}"
|
|
112
|
+
result = self.execute_query(query=query)
|
|
59
113
|
|
|
60
|
-
|
|
61
|
-
query = dedent(f"CREATE DCM PROJECT {project_name.sql_identifier}")
|
|
62
|
-
return self.execute_query(query)
|
|
114
|
+
return result
|
|
63
115
|
|
|
64
|
-
def create(
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
self._create_object(project.fqn)
|
|
68
|
-
if initialize_version_from_local_files:
|
|
69
|
-
self.add_version(project=project)
|
|
116
|
+
def create(self, project_identifier: FQN) -> None:
|
|
117
|
+
query = f"CREATE DCM PROJECT {project_identifier.sql_identifier}"
|
|
118
|
+
self.execute_query(query)
|
|
70
119
|
|
|
71
|
-
def
|
|
72
|
-
|
|
73
|
-
project_name: FQN,
|
|
74
|
-
from_stage: str,
|
|
75
|
-
alias: str | None = None,
|
|
76
|
-
comment: str | None = None,
|
|
77
|
-
):
|
|
78
|
-
stage_path = StagePath.from_stage_str(from_stage)
|
|
79
|
-
query = f"ALTER DCM PROJECT {project_name.identifier} ADD VERSION"
|
|
80
|
-
if alias:
|
|
81
|
-
query += f" IF NOT EXISTS {alias}"
|
|
82
|
-
query += f" FROM {stage_path.absolute_path(at_prefix=True)}"
|
|
83
|
-
if comment:
|
|
84
|
-
query += f" COMMENT = '{comment}'"
|
|
120
|
+
def list_deployments(self, project_identifier: FQN):
|
|
121
|
+
query = f"SHOW DEPLOYMENTS IN DCM PROJECT {project_identifier.identifier}"
|
|
85
122
|
return self.execute_query(query=query)
|
|
86
123
|
|
|
87
|
-
def
|
|
124
|
+
def drop_deployment(
|
|
88
125
|
self,
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
alias: Optional[str] = None,
|
|
93
|
-
comment: Optional[str] = None,
|
|
126
|
+
project_identifier: FQN,
|
|
127
|
+
deployment_name: str,
|
|
128
|
+
if_exists: bool = False,
|
|
94
129
|
):
|
|
95
130
|
"""
|
|
96
|
-
|
|
97
|
-
uploads local files to the stage defined in DCM Project definition.
|
|
131
|
+
Drops a deployment from the DCM Project.
|
|
98
132
|
"""
|
|
133
|
+
query = f"ALTER DCM PROJECT {project_identifier.identifier} DROP DEPLOYMENT"
|
|
134
|
+
if if_exists:
|
|
135
|
+
query += " IF EXISTS"
|
|
136
|
+
query += f' "{deployment_name}"'
|
|
137
|
+
return self.execute_query(query=query)
|
|
99
138
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
)
|
|
139
|
+
@staticmethod
|
|
140
|
+
def sync_local_files(
|
|
141
|
+
project_identifier: FQN, source_directory: str | None = None
|
|
142
|
+
) -> str:
|
|
143
|
+
source_path = (
|
|
144
|
+
SecurePath(source_directory).resolve()
|
|
145
|
+
if source_directory
|
|
146
|
+
else SecurePath.cwd()
|
|
147
|
+
)
|
|
110
148
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
alias=alias,
|
|
116
|
-
comment=comment,
|
|
149
|
+
dcm_manifest_file = source_path / MANIFEST_FILE_NAME
|
|
150
|
+
if not dcm_manifest_file.exists():
|
|
151
|
+
raise CliError(
|
|
152
|
+
f"{MANIFEST_FILE_NAME} was not found in directory {source_path.path}"
|
|
117
153
|
)
|
|
118
154
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
155
|
+
with dcm_manifest_file.open(read_file_limit_mb=DEFAULT_SIZE_LIMIT_MB) as fd:
|
|
156
|
+
dcm_manifest = yaml.safe_load(fd)
|
|
157
|
+
object_type = dcm_manifest.get("type") if dcm_manifest else None
|
|
158
|
+
if object_type is None:
|
|
159
|
+
raise CliError(
|
|
160
|
+
f"Manifest file type is undefined. Expected {DCM_PROJECT_TYPE}"
|
|
161
|
+
)
|
|
162
|
+
if object_type.lower() != DCM_PROJECT_TYPE:
|
|
163
|
+
raise CliError(
|
|
164
|
+
f"Manifest file is defined for type {object_type}. Expected {DCM_PROJECT_TYPE}"
|
|
165
|
+
)
|
|
122
166
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
167
|
+
definitions = list(dcm_manifest.get("include_definitions", list()))
|
|
168
|
+
if MANIFEST_FILE_NAME not in definitions:
|
|
169
|
+
definitions.append(MANIFEST_FILE_NAME)
|
|
170
|
+
|
|
171
|
+
with cli_console.phase(f"Uploading definition files"):
|
|
172
|
+
stage_fqn = FQN.from_resource(
|
|
173
|
+
ObjectType.DCM_PROJECT, project_identifier, "TMP_STAGE"
|
|
174
|
+
)
|
|
175
|
+
sync_artifacts_with_stage(
|
|
176
|
+
project_paths=ProjectPaths(project_root=source_path.path),
|
|
177
|
+
stage_root=stage_fqn.identifier,
|
|
178
|
+
use_temporary_stage=True,
|
|
179
|
+
artifacts=[PathMapping(src=definition) for definition in definitions],
|
|
180
|
+
pattern_type=PatternMatchingType.REGEX,
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
return stage_fqn.identifier
|
|
@@ -48,6 +48,11 @@ def get_logs(
|
|
|
48
48
|
"--log-level",
|
|
49
49
|
help="The log level to filter by. If not provided, INFO will be used",
|
|
50
50
|
),
|
|
51
|
+
partial_match: bool = typer.Option(
|
|
52
|
+
False,
|
|
53
|
+
"--partial",
|
|
54
|
+
help="Enable partial, case-insensitive matching for object names",
|
|
55
|
+
),
|
|
51
56
|
**options,
|
|
52
57
|
):
|
|
53
58
|
"""
|
|
@@ -75,6 +80,7 @@ def get_logs(
|
|
|
75
80
|
refresh_time=refresh_time,
|
|
76
81
|
event_table=event_table,
|
|
77
82
|
log_level=log_level,
|
|
83
|
+
partial_match=partial_match,
|
|
78
84
|
)
|
|
79
85
|
logs = itertools.chain(
|
|
80
86
|
(MessageResult(log.log_message) for logs in logs_stream for log in logs)
|
|
@@ -87,6 +93,7 @@ def get_logs(
|
|
|
87
93
|
to_time=to_time,
|
|
88
94
|
event_table=event_table,
|
|
89
95
|
log_level=log_level,
|
|
96
|
+
partial_match=partial_match,
|
|
90
97
|
)
|
|
91
98
|
logs = (MessageResult(log.log_message) for log in logs_iterable) # type: ignore
|
|
92
99
|
|