snowflake-cli 3.0.1__py3-none-any.whl → 3.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.
- snowflake/cli/__about__.py +1 -1
- snowflake/cli/_app/cli_app.py +3 -0
- snowflake/cli/_app/dev/docs/templates/overview.rst.jinja2 +1 -1
- snowflake/cli/_app/dev/docs/templates/usage.rst.jinja2 +2 -2
- snowflake/cli/_app/telemetry.py +69 -4
- snowflake/cli/_plugins/connection/commands.py +40 -2
- snowflake/cli/_plugins/git/commands.py +6 -3
- snowflake/cli/_plugins/git/manager.py +5 -0
- snowflake/cli/_plugins/nativeapp/artifacts.py +13 -3
- snowflake/cli/_plugins/nativeapp/codegen/artifact_processor.py +1 -1
- snowflake/cli/_plugins/nativeapp/codegen/compiler.py +7 -0
- snowflake/cli/_plugins/nativeapp/codegen/sandbox.py +10 -10
- snowflake/cli/_plugins/nativeapp/codegen/setup/native_app_setup_processor.py +2 -2
- snowflake/cli/_plugins/nativeapp/codegen/snowpark/extension_function_utils.py +1 -1
- snowflake/cli/_plugins/nativeapp/codegen/snowpark/python_processor.py +8 -8
- snowflake/cli/_plugins/nativeapp/commands.py +135 -186
- snowflake/cli/_plugins/nativeapp/entities/application.py +176 -24
- snowflake/cli/_plugins/nativeapp/entities/application_package.py +112 -136
- snowflake/cli/_plugins/nativeapp/exceptions.py +12 -0
- snowflake/cli/_plugins/nativeapp/manager.py +3 -26
- snowflake/cli/_plugins/nativeapp/v2_conversions/{v2_to_v1_decorator.py → compat.py} +131 -72
- snowflake/cli/_plugins/nativeapp/version/commands.py +30 -29
- snowflake/cli/_plugins/nativeapp/version/version_processor.py +1 -43
- snowflake/cli/_plugins/snowpark/commands.py +0 -2
- snowflake/cli/_plugins/snowpark/common.py +60 -18
- snowflake/cli/_plugins/snowpark/package/anaconda_packages.py +2 -2
- snowflake/cli/_plugins/snowpark/package/commands.py +0 -2
- snowflake/cli/_plugins/snowpark/package_utils.py +27 -38
- snowflake/cli/_plugins/snowpark/snowpark_entity_model.py +5 -2
- snowflake/cli/_plugins/spcs/image_repository/commands.py +4 -37
- snowflake/cli/_plugins/spcs/image_repository/manager.py +4 -1
- snowflake/cli/_plugins/spcs/services/commands.py +36 -4
- snowflake/cli/_plugins/spcs/services/manager.py +36 -4
- snowflake/cli/_plugins/stage/commands.py +8 -3
- snowflake/cli/_plugins/stage/diff.py +16 -16
- snowflake/cli/_plugins/stage/manager.py +164 -73
- snowflake/cli/_plugins/stage/md5.py +1 -1
- snowflake/cli/_plugins/workspace/commands.py +21 -1
- snowflake/cli/_plugins/workspace/context.py +38 -0
- snowflake/cli/_plugins/workspace/manager.py +23 -13
- snowflake/cli/api/cli_global_context.py +3 -3
- snowflake/cli/api/commands/flags.py +23 -7
- snowflake/cli/api/config.py +7 -4
- snowflake/cli/api/connections.py +12 -1
- snowflake/cli/api/entities/common.py +4 -2
- snowflake/cli/api/entities/utils.py +17 -37
- snowflake/cli/api/exceptions.py +32 -0
- snowflake/cli/api/identifiers.py +8 -0
- snowflake/cli/api/project/definition_conversion.py +139 -40
- snowflake/cli/api/project/schemas/entities/common.py +11 -0
- snowflake/cli/api/project/schemas/project_definition.py +30 -25
- snowflake/cli/api/sql_execution.py +5 -7
- snowflake/cli/api/stage_path.py +241 -0
- snowflake/cli/api/utils/definition_rendering.py +3 -5
- {snowflake_cli-3.0.1.dist-info → snowflake_cli-3.1.0.dist-info}/METADATA +11 -11
- {snowflake_cli-3.0.1.dist-info → snowflake_cli-3.1.0.dist-info}/RECORD +59 -59
- snowflake/cli/_plugins/nativeapp/teardown_processor.py +0 -70
- snowflake/cli/_plugins/workspace/action_context.py +0 -18
- {snowflake_cli-3.0.1.dist-info → snowflake_cli-3.1.0.dist-info}/WHEEL +0 -0
- {snowflake_cli-3.0.1.dist-info → snowflake_cli-3.1.0.dist-info}/entry_points.txt +0 -0
- {snowflake_cli-3.0.1.dist-info → snowflake_cli-3.1.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -21,7 +21,6 @@ import os
|
|
|
21
21
|
import re
|
|
22
22
|
import subprocess
|
|
23
23
|
from pathlib import Path
|
|
24
|
-
from textwrap import dedent
|
|
25
24
|
from typing import Dict, List, Optional
|
|
26
25
|
|
|
27
26
|
from click import ClickException
|
|
@@ -97,6 +96,7 @@ def get_package_name_from_pip_wheel(package: str, index_url: str | None = None)
|
|
|
97
96
|
download_dir=tmp_dir.path,
|
|
98
97
|
index_url=index_url,
|
|
99
98
|
dependencies=False,
|
|
99
|
+
raise_on_error=False,
|
|
100
100
|
)
|
|
101
101
|
file_list = [
|
|
102
102
|
f.path.name for f in tmp_dir.iterdir() if f.path.name.endswith(".whl")
|
|
@@ -117,7 +117,6 @@ def _write_requirements_file(file_path: SecurePath, requirements: List[Requireme
|
|
|
117
117
|
|
|
118
118
|
@dataclasses.dataclass
|
|
119
119
|
class DownloadUnavailablePackagesResult:
|
|
120
|
-
succeeded: bool
|
|
121
120
|
error_message: str | None = None
|
|
122
121
|
anaconda_packages: List[Requirement] = dataclasses.field(default_factory=list)
|
|
123
122
|
downloaded_packages_details: List[RequirementWithFiles] = dataclasses.field(
|
|
@@ -151,8 +150,7 @@ def download_unavailable_packages(
|
|
|
151
150
|
if not requirements:
|
|
152
151
|
# all packages are available in Snowflake
|
|
153
152
|
return DownloadUnavailablePackagesResult(
|
|
154
|
-
|
|
155
|
-
anaconda_packages=packages_in_snowflake,
|
|
153
|
+
anaconda_packages=packages_in_snowflake
|
|
156
154
|
)
|
|
157
155
|
|
|
158
156
|
# download all packages with their dependencies
|
|
@@ -160,19 +158,14 @@ def download_unavailable_packages(
|
|
|
160
158
|
# This is a Windows workaround where use TemporaryDirectory instead of NamedTemporaryFile
|
|
161
159
|
requirements_file = downloads_dir / "requirements.txt"
|
|
162
160
|
_write_requirements_file(requirements_file, requirements) # type: ignore
|
|
163
|
-
|
|
161
|
+
pip_wheel(
|
|
164
162
|
package_name=None,
|
|
165
163
|
requirements_file=requirements_file.path, # type: ignore
|
|
166
164
|
download_dir=downloads_dir.path,
|
|
167
165
|
index_url=pip_index_url,
|
|
168
166
|
dependencies=True,
|
|
167
|
+
raise_on_error=True,
|
|
169
168
|
)
|
|
170
|
-
if pip_wheel_result != 0:
|
|
171
|
-
log.info(_pip_failed_log_msg(pip_wheel_result))
|
|
172
|
-
return DownloadUnavailablePackagesResult(
|
|
173
|
-
succeeded=False,
|
|
174
|
-
error_message=_pip_failed_log_msg(pip_wheel_result),
|
|
175
|
-
)
|
|
176
169
|
|
|
177
170
|
# scan all downloaded packages and filter out ones available on Anaconda
|
|
178
171
|
dependencies = split_downloaded_dependencies(
|
|
@@ -196,7 +189,6 @@ def download_unavailable_packages(
|
|
|
196
189
|
for package in dependencies.unavailable_dependencies_wheels:
|
|
197
190
|
package.extract_files(target_dir.path)
|
|
198
191
|
return DownloadUnavailablePackagesResult(
|
|
199
|
-
succeeded=True,
|
|
200
192
|
anaconda_packages=packages_in_snowflake,
|
|
201
193
|
downloaded_packages_details=[
|
|
202
194
|
RequirementWithFiles(requirement=dep.requirement, files=dep.namelist())
|
|
@@ -211,7 +203,8 @@ def pip_wheel(
|
|
|
211
203
|
download_dir: Path,
|
|
212
204
|
index_url: Optional[str],
|
|
213
205
|
dependencies: bool = True,
|
|
214
|
-
|
|
206
|
+
raise_on_error: bool = True,
|
|
207
|
+
) -> int:
|
|
215
208
|
command = ["-m", "pip", "wheel", "-w", str(download_dir)]
|
|
216
209
|
if package_name:
|
|
217
210
|
command.append(package_name)
|
|
@@ -222,23 +215,30 @@ def pip_wheel(
|
|
|
222
215
|
if not dependencies:
|
|
223
216
|
command.append("--no-deps")
|
|
224
217
|
|
|
225
|
-
|
|
218
|
+
log.info(
|
|
219
|
+
"Running pip wheel with command: %s",
|
|
220
|
+
" ".join([str(com) for com in command]),
|
|
221
|
+
)
|
|
222
|
+
result = subprocess.run(
|
|
223
|
+
["python", *command],
|
|
224
|
+
capture_output=True,
|
|
225
|
+
text=True,
|
|
226
|
+
encoding=locale.getpreferredencoding(),
|
|
227
|
+
)
|
|
228
|
+
if result.returncode != 0:
|
|
226
229
|
log.info(
|
|
227
|
-
"
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
process = subprocess.run(
|
|
231
|
-
["python", *command],
|
|
232
|
-
capture_output=True,
|
|
233
|
-
text=True,
|
|
234
|
-
encoding=locale.getpreferredencoding(),
|
|
230
|
+
"pip wheel finished with error code %d. Details: %s",
|
|
231
|
+
result.returncode,
|
|
232
|
+
result.stdout + result.stderr,
|
|
235
233
|
)
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
234
|
+
if raise_on_error:
|
|
235
|
+
raise ClickException(
|
|
236
|
+
f"pip wheel finished with error code {result.returncode}. Please re-run with --verbose or --debug for more details."
|
|
237
|
+
)
|
|
238
|
+
else:
|
|
239
|
+
log.info("pip wheel command executed successfully")
|
|
239
240
|
|
|
240
|
-
|
|
241
|
-
return process.returncode
|
|
241
|
+
return result.returncode
|
|
242
242
|
|
|
243
243
|
|
|
244
244
|
@dataclasses.dataclass
|
|
@@ -341,14 +341,3 @@ def _log_dependencies_found_in_conda(available_dependencies: List[Requirement])
|
|
|
341
341
|
)
|
|
342
342
|
else:
|
|
343
343
|
log.info("None of the package dependencies were found on Anaconda")
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
def _pip_failed_log_msg(return_code: int) -> str:
|
|
347
|
-
return dedent(
|
|
348
|
-
f"""
|
|
349
|
-
pip failed with return code {return_code}. Most likely reasons:
|
|
350
|
-
* incorrect package name or version
|
|
351
|
-
* package isn't compatible with host architecture (most probably due to .so libraries)
|
|
352
|
-
* pip is not installed correctly
|
|
353
|
-
"""
|
|
354
|
-
)
|
|
@@ -147,8 +147,11 @@ class UdfSprocIdentifier:
|
|
|
147
147
|
self._arg_names, self._arg_types, self._arg_defaults
|
|
148
148
|
):
|
|
149
149
|
s = f"{name} {_type}"
|
|
150
|
-
if _default:
|
|
151
|
-
if
|
|
150
|
+
if _default is not None:
|
|
151
|
+
if (
|
|
152
|
+
self._is_signature_type_a_string(_type)
|
|
153
|
+
and _default.lower() != "null"
|
|
154
|
+
):
|
|
152
155
|
_default = f"'{_default}'"
|
|
153
156
|
s += f" default {_default}"
|
|
154
157
|
sig.append(s)
|
|
@@ -39,6 +39,7 @@ from snowflake.cli.api.identifiers import FQN
|
|
|
39
39
|
from snowflake.cli.api.output.types import (
|
|
40
40
|
CollectionResult,
|
|
41
41
|
MessageResult,
|
|
42
|
+
QueryResult,
|
|
42
43
|
SingleQueryResult,
|
|
43
44
|
)
|
|
44
45
|
from snowflake.cli.api.project.util import is_valid_object_name
|
|
@@ -99,44 +100,10 @@ def list_images(
|
|
|
99
100
|
**options,
|
|
100
101
|
) -> CollectionResult:
|
|
101
102
|
"""Lists images in the given repository."""
|
|
102
|
-
|
|
103
|
-
database = repository_manager.get_database()
|
|
104
|
-
schema = repository_manager.get_schema()
|
|
105
|
-
url = repository_manager.get_repository_url(name.identifier)
|
|
106
|
-
api_url = repository_manager.get_repository_api_url(url)
|
|
107
|
-
bearer_login = RegistryManager().login_to_registry(api_url)
|
|
108
|
-
repos = []
|
|
109
|
-
query: Optional[str] = f"{api_url}/_catalog?n=10"
|
|
110
|
-
|
|
111
|
-
while query:
|
|
112
|
-
# Make paginated catalog requests
|
|
113
|
-
response = requests.get(
|
|
114
|
-
query, headers={"Authorization": f"Bearer {bearer_login}"}
|
|
115
|
-
)
|
|
116
|
-
|
|
117
|
-
if response.status_code != 200:
|
|
118
|
-
raise ClickException(f"Call to the registry failed {response.text}")
|
|
119
|
-
|
|
120
|
-
data = json.loads(response.text)
|
|
121
|
-
if "repositories" in data:
|
|
122
|
-
repos.extend(data["repositories"])
|
|
123
|
-
|
|
124
|
-
if "Link" in response.headers:
|
|
125
|
-
# There are more results
|
|
126
|
-
query = f"{api_url}/_catalog?n=10&last={repos[-1]}"
|
|
127
|
-
else:
|
|
128
|
-
query = None
|
|
129
|
-
|
|
130
|
-
images = []
|
|
131
|
-
for repo in repos:
|
|
132
|
-
prefix = f"/{database}/{schema}/{name}/"
|
|
133
|
-
repo = repo.replace("baserepo/", prefix)
|
|
134
|
-
images.append({"image": repo})
|
|
135
|
-
|
|
136
|
-
return CollectionResult(images)
|
|
103
|
+
return QueryResult(ImageRepositoryManager().list_images(name.identifier))
|
|
137
104
|
|
|
138
105
|
|
|
139
|
-
@app.command("list-tags", requires_connection=True)
|
|
106
|
+
@app.command("list-tags", requires_connection=True, deprecated=True)
|
|
140
107
|
def list_tags(
|
|
141
108
|
name: FQN = REPO_NAME_ARGUMENT,
|
|
142
109
|
image_name: str = typer.Option(
|
|
@@ -149,7 +116,7 @@ def list_tags(
|
|
|
149
116
|
),
|
|
150
117
|
**options,
|
|
151
118
|
) -> CollectionResult:
|
|
152
|
-
"""Lists tags for the given image in a repository."""
|
|
119
|
+
"""Lists tags for the given image in a repository. This command is deprecated and will be removed in a future release. Use `list-images` instead."""
|
|
153
120
|
|
|
154
121
|
repository_manager = ImageRepositoryManager()
|
|
155
122
|
url = repository_manager.get_repository_url(name.identifier)
|
|
@@ -18,6 +18,7 @@ from snowflake.cli._plugins.spcs.common import handle_object_already_exists
|
|
|
18
18
|
from snowflake.cli.api.constants import ObjectType
|
|
19
19
|
from snowflake.cli.api.identifiers import FQN
|
|
20
20
|
from snowflake.cli.api.sql_execution import SqlExecutionMixin
|
|
21
|
+
from snowflake.connector.cursor import SnowflakeCursor
|
|
21
22
|
from snowflake.connector.errors import ProgrammingError
|
|
22
23
|
|
|
23
24
|
|
|
@@ -32,7 +33,6 @@ class ImageRepositoryManager(SqlExecutionMixin):
|
|
|
32
33
|
return self._conn.role
|
|
33
34
|
|
|
34
35
|
def get_repository_url(self, repo_name: str, with_scheme: bool = True):
|
|
35
|
-
|
|
36
36
|
repo_row = self.show_specific_object(
|
|
37
37
|
"image repositories", repo_name, check_schema=True
|
|
38
38
|
)
|
|
@@ -82,3 +82,6 @@ class ImageRepositoryManager(SqlExecutionMixin):
|
|
|
82
82
|
handle_object_already_exists(
|
|
83
83
|
e, ObjectType.IMAGE_REPOSITORY, name, replace_available=True
|
|
84
84
|
)
|
|
85
|
+
|
|
86
|
+
def list_images(self, repo_name: str) -> SnowflakeCursor:
|
|
87
|
+
return self._execute_query(f"show images in image repository {repo_name}")
|
|
@@ -131,7 +131,7 @@ def create(
|
|
|
131
131
|
external_access_integrations: Optional[List[str]] = typer.Option(
|
|
132
132
|
None,
|
|
133
133
|
"--eai-name",
|
|
134
|
-
help="Identifies
|
|
134
|
+
help="Identifies external access integrations (EAI) that the service can access. This option may be specified multiple times for multiple EAIs.",
|
|
135
135
|
),
|
|
136
136
|
query_warehouse: Optional[str] = QueryWarehouseOption(),
|
|
137
137
|
tags: Optional[List[Tag]] = TagOption(help="Tag for the service."),
|
|
@@ -174,7 +174,7 @@ def execute_job(
|
|
|
174
174
|
external_access_integrations: Optional[List[str]] = typer.Option(
|
|
175
175
|
None,
|
|
176
176
|
"--eai-name",
|
|
177
|
-
help="Identifies
|
|
177
|
+
help="Identifies external access integrations (EAI) that the job service can access. This option may be specified multiple times for multiple EAIs.",
|
|
178
178
|
),
|
|
179
179
|
query_warehouse: Optional[str] = QueryWarehouseOption(),
|
|
180
180
|
comment: Optional[str] = CommentOption(help=_COMMENT_HELP),
|
|
@@ -194,10 +194,10 @@ def execute_job(
|
|
|
194
194
|
return SingleQueryResult(cursor)
|
|
195
195
|
|
|
196
196
|
|
|
197
|
-
@app.command(requires_connection=True)
|
|
197
|
+
@app.command(requires_connection=True, deprecated=True)
|
|
198
198
|
def status(name: FQN = ServiceNameArgument, **options) -> CommandResult:
|
|
199
199
|
"""
|
|
200
|
-
Retrieves the status of a service.
|
|
200
|
+
Retrieves the status of a service. This command is deprecated and will be removed in a future release. Use `describe` instead to get service status and use `list-instances` and `list-containers` to get more detailed information about service instances and containers.
|
|
201
201
|
"""
|
|
202
202
|
cursor = ServiceManager().status(service_name=name.identifier)
|
|
203
203
|
return QueryJsonValueResult(cursor)
|
|
@@ -259,6 +259,32 @@ def list_endpoints(name: FQN = ServiceNameArgument, **options):
|
|
|
259
259
|
return QueryResult(ServiceManager().list_endpoints(service_name=name.identifier))
|
|
260
260
|
|
|
261
261
|
|
|
262
|
+
@app.command("list-instances", requires_connection=True)
|
|
263
|
+
def list_service_instances(name: FQN = ServiceNameArgument, **options) -> CommandResult:
|
|
264
|
+
"""
|
|
265
|
+
Lists all service instances in a service.
|
|
266
|
+
"""
|
|
267
|
+
return QueryResult(ServiceManager().list_instances(service_name=name.identifier))
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
@app.command("list-containers", requires_connection=True)
|
|
271
|
+
def list_service_containers(
|
|
272
|
+
name: FQN = ServiceNameArgument, **options
|
|
273
|
+
) -> CommandResult:
|
|
274
|
+
"""
|
|
275
|
+
Lists all service containers in a service.
|
|
276
|
+
"""
|
|
277
|
+
return QueryResult(ServiceManager().list_containers(service_name=name.identifier))
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
@app.command("list-roles", requires_connection=True)
|
|
281
|
+
def list_service_roles(name: FQN = ServiceNameArgument, **options) -> CommandResult:
|
|
282
|
+
"""
|
|
283
|
+
Lists all service roles in a service.
|
|
284
|
+
"""
|
|
285
|
+
return QueryResult(ServiceManager().list_roles(service_name=name.identifier))
|
|
286
|
+
|
|
287
|
+
|
|
262
288
|
@app.command(requires_connection=True)
|
|
263
289
|
def suspend(name: FQN = ServiceNameArgument, **options) -> CommandResult:
|
|
264
290
|
"""
|
|
@@ -282,6 +308,11 @@ def set_property(
|
|
|
282
308
|
max_instances: Optional[int] = MaxInstancesOption(show_default=False),
|
|
283
309
|
query_warehouse: Optional[str] = QueryWarehouseOption(show_default=False),
|
|
284
310
|
auto_resume: Optional[bool] = AutoResumeOption(default=None, show_default=False),
|
|
311
|
+
external_access_integrations: Optional[List[str]] = typer.Option(
|
|
312
|
+
None,
|
|
313
|
+
"--eai-name",
|
|
314
|
+
help="Identifies external access integrations (EAI) that the service can access. This option may be specified multiple times for multiple EAIs.",
|
|
315
|
+
),
|
|
285
316
|
comment: Optional[str] = CommentOption(help=_COMMENT_HELP, show_default=False),
|
|
286
317
|
**options,
|
|
287
318
|
):
|
|
@@ -294,6 +325,7 @@ def set_property(
|
|
|
294
325
|
max_instances=max_instances,
|
|
295
326
|
query_warehouse=query_warehouse,
|
|
296
327
|
auto_resume=auto_resume,
|
|
328
|
+
external_access_integrations=external_access_integrations,
|
|
297
329
|
comment=comment,
|
|
298
330
|
)
|
|
299
331
|
return SingleQueryResult(cursor)
|
|
@@ -147,6 +147,15 @@ class ServiceManager(SqlExecutionMixin):
|
|
|
147
147
|
def list_endpoints(self, service_name: str) -> SnowflakeCursor:
|
|
148
148
|
return self._execute_query(f"show endpoints in service {service_name}")
|
|
149
149
|
|
|
150
|
+
def list_instances(self, service_name: str) -> SnowflakeCursor:
|
|
151
|
+
return self._execute_query(f"show service instances in service {service_name}")
|
|
152
|
+
|
|
153
|
+
def list_containers(self, service_name: str) -> SnowflakeCursor:
|
|
154
|
+
return self._execute_query(f"show service containers in service {service_name}")
|
|
155
|
+
|
|
156
|
+
def list_roles(self, service_name: str) -> SnowflakeCursor:
|
|
157
|
+
return self._execute_query(f"show roles in service {service_name}")
|
|
158
|
+
|
|
150
159
|
def suspend(self, service_name: str):
|
|
151
160
|
return self._execute_query(f"alter service {service_name} suspend")
|
|
152
161
|
|
|
@@ -160,6 +169,7 @@ class ServiceManager(SqlExecutionMixin):
|
|
|
160
169
|
max_instances: Optional[int],
|
|
161
170
|
query_warehouse: Optional[str],
|
|
162
171
|
auto_resume: Optional[bool],
|
|
172
|
+
external_access_integrations: Optional[List[str]],
|
|
163
173
|
comment: Optional[str],
|
|
164
174
|
):
|
|
165
175
|
property_pairs = [
|
|
@@ -167,6 +177,7 @@ class ServiceManager(SqlExecutionMixin):
|
|
|
167
177
|
("max_instances", max_instances),
|
|
168
178
|
("query_warehouse", query_warehouse),
|
|
169
179
|
("auto_resume", auto_resume),
|
|
180
|
+
("external_access_integrations", external_access_integrations),
|
|
170
181
|
("comment", comment),
|
|
171
182
|
]
|
|
172
183
|
|
|
@@ -175,10 +186,31 @@ class ServiceManager(SqlExecutionMixin):
|
|
|
175
186
|
raise NoPropertiesProvidedError(
|
|
176
187
|
f"No properties specified for service '{service_name}'. Please provide at least one property to set."
|
|
177
188
|
)
|
|
178
|
-
query: List[str] = [f"alter service {service_name} set"]
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
189
|
+
query: List[str] = [f"alter service {service_name} set "]
|
|
190
|
+
|
|
191
|
+
if min_instances is not None:
|
|
192
|
+
query.append(f" min_instances = {min_instances}")
|
|
193
|
+
|
|
194
|
+
if max_instances is not None:
|
|
195
|
+
query.append(f" max_instances = {max_instances}")
|
|
196
|
+
|
|
197
|
+
if query_warehouse is not None:
|
|
198
|
+
query.append(f" query_warehouse = {query_warehouse}")
|
|
199
|
+
|
|
200
|
+
if auto_resume is not None:
|
|
201
|
+
query.append(f" auto_resume = {auto_resume}")
|
|
202
|
+
|
|
203
|
+
if external_access_integrations is not None:
|
|
204
|
+
external_access_integration_list = ",".join(
|
|
205
|
+
f"{e}" for e in external_access_integrations
|
|
206
|
+
)
|
|
207
|
+
query.append(
|
|
208
|
+
f"external_access_integrations = ({external_access_integration_list})"
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
if comment is not None:
|
|
212
|
+
query.append(f" comment = {comment}")
|
|
213
|
+
|
|
182
214
|
return self._execute_query(strip_empty_lines(query))
|
|
183
215
|
|
|
184
216
|
def unset_property(
|
|
@@ -38,6 +38,7 @@ from snowflake.cli.api.commands.flags import (
|
|
|
38
38
|
OnErrorOption,
|
|
39
39
|
PatternOption,
|
|
40
40
|
identifier_stage_argument,
|
|
41
|
+
identifier_stage_path_argument,
|
|
41
42
|
like_option,
|
|
42
43
|
)
|
|
43
44
|
from snowflake.cli.api.commands.snow_typer import SnowTyperFactory
|
|
@@ -60,6 +61,10 @@ app = SnowTyperFactory(
|
|
|
60
61
|
)
|
|
61
62
|
|
|
62
63
|
StageNameArgument = identifier_stage_argument(sf_object="stage", example="@my_stage")
|
|
64
|
+
StagePathArgument = identifier_stage_path_argument(
|
|
65
|
+
sf_object="stage", example="@my_stage/path"
|
|
66
|
+
)
|
|
67
|
+
|
|
63
68
|
|
|
64
69
|
add_object_command_aliases(
|
|
65
70
|
app=app,
|
|
@@ -74,7 +79,7 @@ add_object_command_aliases(
|
|
|
74
79
|
|
|
75
80
|
@app.command("list-files", requires_connection=True)
|
|
76
81
|
def stage_list_files(
|
|
77
|
-
stage_name: str =
|
|
82
|
+
stage_name: str = StagePathArgument, pattern=PatternOption, **options
|
|
78
83
|
) -> CommandResult:
|
|
79
84
|
"""
|
|
80
85
|
Lists the stage contents.
|
|
@@ -208,11 +213,11 @@ def execute(
|
|
|
208
213
|
**options,
|
|
209
214
|
):
|
|
210
215
|
"""
|
|
211
|
-
Execute immediate all files from the stage path. Files can be filtered with glob
|
|
216
|
+
Execute immediate all files from the stage path. Files can be filtered with a glob-like pattern,
|
|
212
217
|
e.g. `@stage/*.sql`, `@stage/dev/*`. Only files with `.sql` extension will be executed.
|
|
213
218
|
"""
|
|
214
219
|
results = StageManager().execute(
|
|
215
|
-
|
|
220
|
+
stage_path_str=stage_path, on_error=on_error, variables=variables
|
|
216
221
|
)
|
|
217
222
|
return CollectionResult(results)
|
|
218
223
|
|
|
@@ -30,7 +30,7 @@ from .md5 import UnknownMD5FormatError, file_matches_md5sum
|
|
|
30
30
|
|
|
31
31
|
log = logging.getLogger(__name__)
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
StagePathType = PurePosixPath # alias PurePosixPath as StagePath for clarity
|
|
34
34
|
|
|
35
35
|
|
|
36
36
|
@dataclass
|
|
@@ -39,16 +39,16 @@ class DiffResult:
|
|
|
39
39
|
Each collection is a list of stage paths ('/'-separated, regardless of the platform), relative to the stage root.
|
|
40
40
|
"""
|
|
41
41
|
|
|
42
|
-
identical: List[
|
|
42
|
+
identical: List[StagePathType] = field(default_factory=list)
|
|
43
43
|
"Files with matching md5sums"
|
|
44
44
|
|
|
45
|
-
different: List[
|
|
45
|
+
different: List[StagePathType] = field(default_factory=list)
|
|
46
46
|
"Files that may be different between the stage and the local directory"
|
|
47
47
|
|
|
48
|
-
only_local: List[
|
|
48
|
+
only_local: List[StagePathType] = field(default_factory=list)
|
|
49
49
|
"Files that only exist in the local directory"
|
|
50
50
|
|
|
51
|
-
only_on_stage: List[
|
|
51
|
+
only_on_stage: List[StagePathType] = field(default_factory=list)
|
|
52
52
|
"Files that only exist on the stage"
|
|
53
53
|
|
|
54
54
|
def has_changes(self) -> bool:
|
|
@@ -83,12 +83,12 @@ def enumerate_files(path: Path) -> List[Path]:
|
|
|
83
83
|
return paths
|
|
84
84
|
|
|
85
85
|
|
|
86
|
-
def strip_stage_name(path: str) ->
|
|
86
|
+
def strip_stage_name(path: str) -> StagePathType:
|
|
87
87
|
"""Returns the given stage path without the stage name as the first part."""
|
|
88
|
-
return
|
|
88
|
+
return StagePathType(*path.split("/")[1:])
|
|
89
89
|
|
|
90
90
|
|
|
91
|
-
def build_md5_map(list_stage_cursor: DictCursor) -> Dict[
|
|
91
|
+
def build_md5_map(list_stage_cursor: DictCursor) -> Dict[StagePathType, Optional[str]]:
|
|
92
92
|
"""
|
|
93
93
|
Returns a mapping of relative stage paths to their md5sums.
|
|
94
94
|
"""
|
|
@@ -99,7 +99,7 @@ def build_md5_map(list_stage_cursor: DictCursor) -> Dict[StagePath, Optional[str
|
|
|
99
99
|
|
|
100
100
|
|
|
101
101
|
def preserve_from_diff(
|
|
102
|
-
diff: DiffResult, stage_paths_to_sync: Collection[
|
|
102
|
+
diff: DiffResult, stage_paths_to_sync: Collection[StagePathType]
|
|
103
103
|
) -> DiffResult:
|
|
104
104
|
"""
|
|
105
105
|
Returns a filtered version of the provided diff, keeping only the provided stage paths.
|
|
@@ -163,7 +163,7 @@ def compute_stage_diff(
|
|
|
163
163
|
return result
|
|
164
164
|
|
|
165
165
|
|
|
166
|
-
def get_stage_subpath(stage_path:
|
|
166
|
+
def get_stage_subpath(stage_path: StagePathType) -> str:
|
|
167
167
|
"""
|
|
168
168
|
Returns the parent portion of a stage path, as a string, for inclusion in the fully qualified stage path. Note that
|
|
169
169
|
'.' treated specially here, and so the return value of this call is not a `StagePath` instance.
|
|
@@ -172,21 +172,21 @@ def get_stage_subpath(stage_path: StagePath) -> str:
|
|
|
172
172
|
return "" if parent == "." else parent
|
|
173
173
|
|
|
174
174
|
|
|
175
|
-
def to_stage_path(filename: Path) ->
|
|
175
|
+
def to_stage_path(filename: Path) -> StagePathType:
|
|
176
176
|
"""
|
|
177
177
|
Returns the stage file name, with the path separator suitably transformed if needed.
|
|
178
178
|
"""
|
|
179
|
-
return
|
|
179
|
+
return StagePathType(*filename.parts)
|
|
180
180
|
|
|
181
181
|
|
|
182
|
-
def to_local_path(stage_path:
|
|
182
|
+
def to_local_path(stage_path: StagePathType) -> Path:
|
|
183
183
|
return Path(*stage_path.parts)
|
|
184
184
|
|
|
185
185
|
|
|
186
186
|
def delete_only_on_stage_files(
|
|
187
187
|
stage_manager: StageManager,
|
|
188
188
|
stage_fqn: str,
|
|
189
|
-
only_on_stage: List[
|
|
189
|
+
only_on_stage: List[StagePathType],
|
|
190
190
|
role: Optional[str] = None,
|
|
191
191
|
):
|
|
192
192
|
"""
|
|
@@ -200,7 +200,7 @@ def put_files_on_stage(
|
|
|
200
200
|
stage_manager: StageManager,
|
|
201
201
|
stage_fqn: str,
|
|
202
202
|
deploy_root_path: Path,
|
|
203
|
-
stage_paths: List[
|
|
203
|
+
stage_paths: List[StagePathType],
|
|
204
204
|
role: Optional[str] = None,
|
|
205
205
|
overwrite: bool = False,
|
|
206
206
|
):
|
|
@@ -254,7 +254,7 @@ def sync_local_diff_with_stage(
|
|
|
254
254
|
|
|
255
255
|
|
|
256
256
|
def _to_src_dest_pair(
|
|
257
|
-
stage_path:
|
|
257
|
+
stage_path: StagePathType, bundle_map: Optional[BundleMap]
|
|
258
258
|
) -> Tuple[Optional[str], str]:
|
|
259
259
|
if not bundle_map:
|
|
260
260
|
return None, str(stage_path)
|