snowflake-cli 3.5.0__py3-none-any.whl → 3.7.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 +13 -1
- snowflake/cli/_app/commands_registration/builtin_plugins.py +4 -0
- snowflake/cli/_app/loggers.py +2 -2
- snowflake/cli/_app/snow_connector.py +7 -6
- snowflake/cli/_app/telemetry.py +3 -15
- snowflake/cli/_app/version_check.py +4 -4
- snowflake/cli/_plugins/auth/__init__.py +11 -0
- snowflake/cli/_plugins/auth/keypair/__init__.py +0 -0
- snowflake/cli/_plugins/auth/keypair/commands.py +151 -0
- snowflake/cli/_plugins/auth/keypair/manager.py +331 -0
- snowflake/cli/_plugins/auth/keypair/plugin_spec.py +30 -0
- snowflake/cli/_plugins/connection/commands.py +78 -1
- snowflake/cli/_plugins/helpers/commands.py +25 -1
- snowflake/cli/_plugins/helpers/snowsl_vars_reader.py +133 -0
- snowflake/cli/_plugins/init/commands.py +9 -6
- snowflake/cli/_plugins/logs/__init__.py +0 -0
- snowflake/cli/_plugins/logs/commands.py +105 -0
- snowflake/cli/_plugins/logs/manager.py +107 -0
- snowflake/cli/_plugins/logs/plugin_spec.py +16 -0
- snowflake/cli/_plugins/logs/utils.py +60 -0
- snowflake/cli/_plugins/nativeapp/entities/application.py +4 -1
- snowflake/cli/_plugins/nativeapp/sf_sql_facade.py +33 -6
- snowflake/cli/_plugins/notebook/commands.py +3 -0
- snowflake/cli/_plugins/notebook/notebook_entity.py +16 -27
- snowflake/cli/_plugins/object/command_aliases.py +3 -1
- snowflake/cli/_plugins/object/manager.py +4 -2
- snowflake/cli/_plugins/project/commands.py +89 -48
- snowflake/cli/_plugins/project/manager.py +57 -23
- snowflake/cli/_plugins/project/project_entity_model.py +22 -3
- snowflake/cli/_plugins/snowpark/commands.py +15 -2
- snowflake/cli/_plugins/spcs/compute_pool/commands.py +17 -5
- snowflake/cli/_plugins/sql/manager.py +43 -52
- snowflake/cli/_plugins/sql/source_reader.py +230 -0
- snowflake/cli/_plugins/stage/manager.py +25 -12
- snowflake/cli/_plugins/streamlit/commands.py +3 -0
- snowflake/cli/_plugins/streamlit/manager.py +19 -15
- snowflake/cli/api/artifacts/upload.py +30 -34
- snowflake/cli/api/artifacts/utils.py +8 -6
- snowflake/cli/api/cli_global_context.py +7 -2
- snowflake/cli/api/commands/decorators.py +11 -2
- snowflake/cli/api/commands/flags.py +35 -4
- snowflake/cli/api/commands/snow_typer.py +20 -2
- snowflake/cli/api/config.py +5 -3
- snowflake/cli/api/constants.py +2 -0
- snowflake/cli/api/entities/utils.py +29 -16
- snowflake/cli/api/errno.py +1 -0
- snowflake/cli/api/exceptions.py +75 -27
- snowflake/cli/api/feature_flags.py +1 -0
- snowflake/cli/api/identifiers.py +2 -0
- snowflake/cli/api/plugins/plugin_config.py +2 -2
- snowflake/cli/api/project/schemas/template.py +3 -3
- snowflake/cli/api/rendering/project_templates.py +3 -3
- snowflake/cli/api/rendering/sql_templates.py +2 -2
- snowflake/cli/api/rest_api.py +2 -3
- snowflake/cli/{_app → api}/secret.py +4 -1
- snowflake/cli/api/secure_path.py +16 -4
- snowflake/cli/api/sql_execution.py +8 -4
- snowflake/cli/api/utils/definition_rendering.py +14 -8
- snowflake/cli/api/utils/templating_functions.py +4 -4
- {snowflake_cli-3.5.0.dist-info → snowflake_cli-3.7.0.dist-info}/METADATA +11 -11
- {snowflake_cli-3.5.0.dist-info → snowflake_cli-3.7.0.dist-info}/RECORD +64 -52
- {snowflake_cli-3.5.0.dist-info → snowflake_cli-3.7.0.dist-info}/WHEEL +0 -0
- {snowflake_cli-3.5.0.dist-info → snowflake_cli-3.7.0.dist-info}/entry_points.txt +0 -0
- {snowflake_cli-3.5.0.dist-info → snowflake_cli-3.7.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -15,26 +15,31 @@
|
|
|
15
15
|
from typing import List, Optional
|
|
16
16
|
|
|
17
17
|
import typer
|
|
18
|
+
from click import ClickException
|
|
19
|
+
from snowflake.cli._plugins.object.command_aliases import add_object_command_aliases
|
|
20
|
+
from snowflake.cli._plugins.object.commands import scope_option
|
|
21
|
+
from snowflake.cli._plugins.object.manager import ObjectManager
|
|
18
22
|
from snowflake.cli._plugins.project.feature_flags import FeatureFlag
|
|
19
23
|
from snowflake.cli._plugins.project.manager import ProjectManager
|
|
20
24
|
from snowflake.cli._plugins.project.project_entity_model import (
|
|
21
25
|
ProjectEntityModel,
|
|
22
26
|
)
|
|
23
|
-
from snowflake.cli._plugins.stage.manager import StageManager
|
|
24
|
-
from snowflake.cli.api.artifacts.upload import put_files
|
|
25
27
|
from snowflake.cli.api.cli_global_context import get_cli_context
|
|
26
28
|
from snowflake.cli.api.commands.decorators import with_project_definition
|
|
27
29
|
from snowflake.cli.api.commands.flags import (
|
|
30
|
+
OverrideableOption,
|
|
31
|
+
PruneOption,
|
|
28
32
|
entity_argument,
|
|
29
33
|
identifier_argument,
|
|
34
|
+
like_option,
|
|
30
35
|
variables_option,
|
|
31
36
|
)
|
|
32
37
|
from snowflake.cli.api.commands.snow_typer import SnowTyperFactory
|
|
33
38
|
from snowflake.cli.api.commands.utils import get_entity_for_operation
|
|
34
39
|
from snowflake.cli.api.console.console import cli_console
|
|
40
|
+
from snowflake.cli.api.constants import ObjectType
|
|
35
41
|
from snowflake.cli.api.identifiers import FQN
|
|
36
|
-
from snowflake.cli.api.output.types import MessageResult, SingleQueryResult
|
|
37
|
-
from snowflake.cli.api.project.project_paths import ProjectPaths
|
|
42
|
+
from snowflake.cli.api.output.types import MessageResult, QueryResult, SingleQueryResult
|
|
38
43
|
|
|
39
44
|
app = SnowTyperFactory(
|
|
40
45
|
name="project",
|
|
@@ -44,17 +49,38 @@ app = SnowTyperFactory(
|
|
|
44
49
|
|
|
45
50
|
project_identifier = identifier_argument(sf_object="project", example="MY_PROJECT")
|
|
46
51
|
version_flag = typer.Option(
|
|
47
|
-
|
|
52
|
+
None,
|
|
53
|
+
"--version",
|
|
54
|
+
help="Version of the project to use. If not specified default version is used",
|
|
55
|
+
show_default=False,
|
|
48
56
|
)
|
|
49
57
|
variables_flag = variables_option(
|
|
50
58
|
'Variables for the execution context; for example: `-D "<key>=<value>"`.'
|
|
51
59
|
)
|
|
60
|
+
from_option = OverrideableOption(
|
|
61
|
+
None,
|
|
62
|
+
"--from",
|
|
63
|
+
help="Create a new version using given stage instead of uploading local files.",
|
|
64
|
+
show_default=False,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
add_object_command_aliases(
|
|
69
|
+
app=app,
|
|
70
|
+
object_type=ObjectType.PROJECT,
|
|
71
|
+
name_argument=project_identifier,
|
|
72
|
+
like_option=like_option(
|
|
73
|
+
help_example='`list --like "my%"` lists all projects that begin with “my”'
|
|
74
|
+
),
|
|
75
|
+
scope_option=scope_option(help_example="`list --in database my_db`"),
|
|
76
|
+
ommit_commands=["drop", "create", "describe"],
|
|
77
|
+
)
|
|
52
78
|
|
|
53
79
|
|
|
54
80
|
@app.command(requires_connection=True)
|
|
55
81
|
def execute(
|
|
56
82
|
identifier: FQN = project_identifier,
|
|
57
|
-
version: str = version_flag,
|
|
83
|
+
version: Optional[str] = version_flag,
|
|
58
84
|
variables: Optional[List[str]] = variables_flag,
|
|
59
85
|
**options,
|
|
60
86
|
):
|
|
@@ -70,7 +96,7 @@ def execute(
|
|
|
70
96
|
@app.command(requires_connection=True)
|
|
71
97
|
def dry_run(
|
|
72
98
|
identifier: FQN = project_identifier,
|
|
73
|
-
version: str = version_flag,
|
|
99
|
+
version: Optional[str] = version_flag,
|
|
74
100
|
variables: Optional[List[str]] = variables_flag,
|
|
75
101
|
**options,
|
|
76
102
|
):
|
|
@@ -85,12 +111,18 @@ def dry_run(
|
|
|
85
111
|
|
|
86
112
|
@app.command(requires_connection=True)
|
|
87
113
|
@with_project_definition()
|
|
88
|
-
def
|
|
114
|
+
def create(
|
|
89
115
|
entity_id: str = entity_argument("project"),
|
|
116
|
+
no_version: bool = typer.Option(
|
|
117
|
+
False,
|
|
118
|
+
"--no-version",
|
|
119
|
+
help="Do not initialize project with a new version, only create the snowflake object.",
|
|
120
|
+
),
|
|
90
121
|
**options,
|
|
91
122
|
):
|
|
92
123
|
"""
|
|
93
|
-
|
|
124
|
+
Creates a project in Snowflake.
|
|
125
|
+
By default, the project is initialized with a new version created from local files.
|
|
94
126
|
"""
|
|
95
127
|
cli_context = get_cli_context()
|
|
96
128
|
project: ProjectEntityModel = get_entity_for_operation(
|
|
@@ -99,59 +131,68 @@ def create_version(
|
|
|
99
131
|
project_definition=cli_context.project_definition,
|
|
100
132
|
entity_type="project",
|
|
101
133
|
)
|
|
134
|
+
om = ObjectManager()
|
|
135
|
+
if om.object_exists(object_type="project", fqn=project.fqn):
|
|
136
|
+
raise ClickException(f"Project '{project.fqn}' already exists.")
|
|
137
|
+
if not no_version and om.object_exists(
|
|
138
|
+
object_type="stage", fqn=FQN.from_stage(project.stage)
|
|
139
|
+
):
|
|
140
|
+
raise ClickException(f"Stage '{project.stage}' already exists.")
|
|
102
141
|
|
|
103
|
-
|
|
104
|
-
with cli_console.phase("
|
|
105
|
-
|
|
106
|
-
sm = StageManager()
|
|
107
|
-
|
|
108
|
-
cli_console.step(f"Creating stage {stage_name}")
|
|
109
|
-
sm.create(fqn=stage_name)
|
|
110
|
-
|
|
111
|
-
put_files(
|
|
112
|
-
project_paths=ProjectPaths(project_root=cli_context.project_root),
|
|
113
|
-
stage_root=project.stage,
|
|
114
|
-
artifacts=project.artifacts,
|
|
115
|
-
)
|
|
116
|
-
|
|
117
|
-
# Create project and version
|
|
118
|
-
with cli_console.phase("Creating project and version"):
|
|
119
|
-
pm = ProjectManager()
|
|
120
|
-
cli_console.step(f"Creating project {project.fqn}")
|
|
121
|
-
pm.create(project_name=project.fqn)
|
|
142
|
+
pm = ProjectManager()
|
|
143
|
+
with cli_console.phase(f"Creating project '{project.fqn}'"):
|
|
144
|
+
pm.create(project=project, initialize_version_from_local_files=not no_version)
|
|
122
145
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
return MessageResult(
|
|
146
|
+
if no_version:
|
|
147
|
+
return MessageResult(f"Project '{project.fqn}' successfully created.")
|
|
148
|
+
return MessageResult(
|
|
149
|
+
f"Project '{project.fqn}' successfully created and initial version is added."
|
|
150
|
+
)
|
|
126
151
|
|
|
127
152
|
|
|
128
153
|
@app.command(requires_connection=True)
|
|
129
154
|
@with_project_definition()
|
|
130
155
|
def add_version(
|
|
131
156
|
entity_id: str = entity_argument("project"),
|
|
132
|
-
_from: str =
|
|
133
|
-
|
|
134
|
-
"--from",
|
|
135
|
-
help="Source stage to create the version from.",
|
|
136
|
-
show_default=False,
|
|
137
|
-
),
|
|
138
|
-
alias: str
|
|
139
|
-
| None = typer.Option(
|
|
157
|
+
_from: Optional[str] = from_option(mutually_exclusive=["prune"]),
|
|
158
|
+
_alias: Optional[str] = typer.Option(
|
|
140
159
|
None, "--alias", help="Alias for the version.", show_default=False
|
|
141
160
|
),
|
|
142
|
-
comment: str
|
|
143
|
-
| None = typer.Option(
|
|
161
|
+
comment: Optional[str] = typer.Option(
|
|
144
162
|
None, "--comment", help="Version comment.", show_default=False
|
|
145
163
|
),
|
|
164
|
+
prune: bool = PruneOption(mutually_exclusive=["_from"]),
|
|
146
165
|
**options,
|
|
147
166
|
):
|
|
148
|
-
"""
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
167
|
+
"""Uploads local files to Snowflake and cerates a new project version."""
|
|
168
|
+
cli_context = get_cli_context()
|
|
169
|
+
project: ProjectEntityModel = get_entity_for_operation(
|
|
170
|
+
cli_context=cli_context,
|
|
171
|
+
entity_id=entity_id,
|
|
172
|
+
project_definition=cli_context.project_definition,
|
|
173
|
+
entity_type="project",
|
|
174
|
+
)
|
|
175
|
+
ProjectManager().add_version(
|
|
176
|
+
project=project,
|
|
177
|
+
prune=prune,
|
|
153
178
|
from_stage=_from,
|
|
154
|
-
alias=
|
|
179
|
+
alias=_alias,
|
|
155
180
|
comment=comment,
|
|
156
181
|
)
|
|
157
|
-
|
|
182
|
+
alias_str = "" if _alias is None else f"'{_alias}' "
|
|
183
|
+
return MessageResult(
|
|
184
|
+
f"New project version {alias_str}added to project '{project.fqn}'"
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
@app.command(requires_connection=True)
|
|
189
|
+
def list_versions(
|
|
190
|
+
identifier: FQN = project_identifier,
|
|
191
|
+
**options,
|
|
192
|
+
):
|
|
193
|
+
"""
|
|
194
|
+
Lists versions of given project.
|
|
195
|
+
"""
|
|
196
|
+
pm = ProjectManager()
|
|
197
|
+
results = pm.list_versions(project_name=identifier)
|
|
198
|
+
return QueryResult(results)
|
|
@@ -12,13 +12,19 @@
|
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
from textwrap import dedent
|
|
15
|
-
from typing import List
|
|
15
|
+
from typing import List, Optional
|
|
16
16
|
|
|
17
|
+
from snowflake.cli._plugins.project.project_entity_model import ProjectEntityModel
|
|
17
18
|
from snowflake.cli._plugins.stage.manager import StageManager
|
|
19
|
+
from snowflake.cli.api.artifacts.upload import sync_artifacts_with_stage
|
|
20
|
+
from snowflake.cli.api.cli_global_context import get_cli_context
|
|
18
21
|
from snowflake.cli.api.commands.utils import parse_key_value_variables
|
|
22
|
+
from snowflake.cli.api.console.console import cli_console
|
|
19
23
|
from snowflake.cli.api.identifiers import FQN
|
|
24
|
+
from snowflake.cli.api.project.project_paths import ProjectPaths
|
|
20
25
|
from snowflake.cli.api.sql_execution import SqlExecutionMixin
|
|
21
26
|
from snowflake.cli.api.stage_path import StagePath
|
|
27
|
+
from snowflake.connector.cursor import SnowflakeCursor
|
|
22
28
|
|
|
23
29
|
|
|
24
30
|
class ProjectManager(SqlExecutionMixin):
|
|
@@ -40,37 +46,65 @@ class ProjectManager(SqlExecutionMixin):
|
|
|
40
46
|
query += " DRY_RUN=TRUE"
|
|
41
47
|
return self.execute_query(query=query)
|
|
42
48
|
|
|
43
|
-
def
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
) -> str:
|
|
47
|
-
queries = dedent(f"CREATE PROJECT IF NOT EXISTS {project_name.sql_identifier}")
|
|
48
|
-
return self.execute_query(query=queries)
|
|
49
|
+
def _create_object(self, project_name: FQN) -> SnowflakeCursor:
|
|
50
|
+
query = dedent(f"CREATE PROJECT {project_name.sql_identifier}")
|
|
51
|
+
return self.execute_query(query)
|
|
49
52
|
|
|
50
|
-
def
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
)
|
|
56
|
-
)
|
|
53
|
+
def create(
|
|
54
|
+
self, project: ProjectEntityModel, initialize_version_from_local_files: bool
|
|
55
|
+
) -> None:
|
|
56
|
+
self._create_object(project.fqn)
|
|
57
|
+
if initialize_version_from_local_files:
|
|
58
|
+
self.add_version(project=project)
|
|
57
59
|
|
|
58
|
-
def
|
|
60
|
+
def _create_version(
|
|
59
61
|
self,
|
|
60
|
-
project_name:
|
|
62
|
+
project_name: FQN,
|
|
61
63
|
from_stage: str,
|
|
62
64
|
alias: str | None = None,
|
|
63
65
|
comment: str | None = None,
|
|
64
66
|
):
|
|
65
|
-
|
|
66
|
-
project_name
|
|
67
|
-
if isinstance(project_name, FQN)
|
|
68
|
-
else FQN.from_string(project_name)
|
|
69
|
-
)
|
|
67
|
+
stage_path = StagePath.from_stage_str(from_stage)
|
|
70
68
|
query = f"ALTER PROJECT {project_name.identifier} ADD VERSION"
|
|
71
69
|
if alias:
|
|
72
|
-
query += f
|
|
73
|
-
query += f" FROM {
|
|
70
|
+
query += f' IF NOT EXISTS "{alias}"'
|
|
71
|
+
query += f" FROM {stage_path.absolute_path(at_prefix=True)}"
|
|
74
72
|
if comment:
|
|
75
73
|
query += f" COMMENT = '{comment}'"
|
|
76
74
|
return self.execute_query(query=query)
|
|
75
|
+
|
|
76
|
+
def add_version(
|
|
77
|
+
self,
|
|
78
|
+
project: ProjectEntityModel,
|
|
79
|
+
prune: bool = False,
|
|
80
|
+
from_stage: Optional[str] = None,
|
|
81
|
+
alias: Optional[str] = None,
|
|
82
|
+
comment: Optional[str] = None,
|
|
83
|
+
):
|
|
84
|
+
"""
|
|
85
|
+
Adds a version to project. If [from_stage] is not defined,
|
|
86
|
+
uploads local files to the stage defined in project definition.
|
|
87
|
+
"""
|
|
88
|
+
|
|
89
|
+
if not from_stage:
|
|
90
|
+
cli_context = get_cli_context()
|
|
91
|
+
from_stage = project.stage
|
|
92
|
+
with cli_console.phase("Uploading artifacts"):
|
|
93
|
+
sync_artifacts_with_stage(
|
|
94
|
+
project_paths=ProjectPaths(project_root=cli_context.project_root),
|
|
95
|
+
stage_root=from_stage,
|
|
96
|
+
artifacts=project.artifacts,
|
|
97
|
+
prune=prune,
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
with cli_console.phase(f"Creating project version from stage {from_stage}"):
|
|
101
|
+
return self._create_version(
|
|
102
|
+
project_name=project.fqn,
|
|
103
|
+
from_stage=from_stage, # type:ignore
|
|
104
|
+
alias=alias,
|
|
105
|
+
comment=comment,
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
def list_versions(self, project_name: FQN):
|
|
109
|
+
query = f"SHOW VERSIONS IN PROJECT {project_name.identifier}"
|
|
110
|
+
return self.execute_query(query=query)
|
|
@@ -13,26 +13,45 @@
|
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
from __future__ import annotations
|
|
15
15
|
|
|
16
|
-
from typing import Literal, Optional, TypeVar
|
|
16
|
+
from typing import List, Literal, Optional, TypeVar
|
|
17
17
|
|
|
18
|
-
from pydantic import Field
|
|
18
|
+
from pydantic import Field, field_validator
|
|
19
|
+
from snowflake.cli.api.cli_global_context import get_cli_context
|
|
19
20
|
from snowflake.cli.api.entities.common import EntityBase, attach_spans_to_entity_actions
|
|
21
|
+
from snowflake.cli.api.exceptions import CliError
|
|
20
22
|
from snowflake.cli.api.project.schemas.entities.common import (
|
|
23
|
+
Artifacts,
|
|
21
24
|
EntityModelBaseWithArtifacts,
|
|
25
|
+
PathMapping,
|
|
22
26
|
)
|
|
23
27
|
from snowflake.cli.api.project.schemas.updatable_model import (
|
|
24
28
|
DiscriminatorField,
|
|
25
29
|
)
|
|
30
|
+
from snowflake.cli.api.secure_path import SecurePath
|
|
26
31
|
|
|
27
32
|
T = TypeVar("T")
|
|
28
33
|
|
|
29
34
|
|
|
35
|
+
MANIFEST_FILE_NAME = "manifest.yml"
|
|
36
|
+
|
|
37
|
+
|
|
30
38
|
class ProjectEntityModel(EntityModelBaseWithArtifacts):
|
|
31
39
|
type: Literal["project"] = DiscriminatorField() # noqa: A003
|
|
32
40
|
stage: Optional[str] = Field(
|
|
33
41
|
title="Stage in which the project artifacts will be stored", default=None
|
|
34
42
|
)
|
|
35
|
-
|
|
43
|
+
|
|
44
|
+
@field_validator("artifacts")
|
|
45
|
+
@classmethod
|
|
46
|
+
def transform_artifacts(cls, orig_artifacts: Artifacts) -> List[PathMapping]:
|
|
47
|
+
if not (
|
|
48
|
+
SecurePath(get_cli_context().project_root) / MANIFEST_FILE_NAME
|
|
49
|
+
).exists():
|
|
50
|
+
raise CliError(
|
|
51
|
+
f"{MANIFEST_FILE_NAME} was not found in project root directory"
|
|
52
|
+
)
|
|
53
|
+
orig_artifacts.append(PathMapping(src=MANIFEST_FILE_NAME))
|
|
54
|
+
return super().transform_artifacts(orig_artifacts)
|
|
36
55
|
|
|
37
56
|
|
|
38
57
|
@attach_spans_to_entity_actions(entity_name="project")
|
|
@@ -71,6 +71,7 @@ from snowflake.cli.api.commands.decorators import (
|
|
|
71
71
|
)
|
|
72
72
|
from snowflake.cli.api.commands.flags import (
|
|
73
73
|
ForceReplaceOption,
|
|
74
|
+
PruneOption,
|
|
74
75
|
ReplaceOption,
|
|
75
76
|
execution_identifier_argument,
|
|
76
77
|
identifier_argument,
|
|
@@ -133,6 +134,9 @@ def deploy(
|
|
|
133
134
|
"overwrites existing files, but does not remove any files already on the stage."
|
|
134
135
|
),
|
|
135
136
|
force_replace: bool = ForceReplaceOption(),
|
|
137
|
+
prune: bool = PruneOption(
|
|
138
|
+
help="Remove contents of the stage before uploading artifacts."
|
|
139
|
+
),
|
|
136
140
|
**options,
|
|
137
141
|
) -> CommandResult:
|
|
138
142
|
"""
|
|
@@ -174,7 +178,7 @@ def deploy(
|
|
|
174
178
|
snowpark_entities=snowpark_entities,
|
|
175
179
|
)
|
|
176
180
|
|
|
177
|
-
create_stages_and_upload_artifacts(stages_to_artifact_map)
|
|
181
|
+
create_stages_and_upload_artifacts(stages_to_artifact_map, prune=prune)
|
|
178
182
|
|
|
179
183
|
# Create snowpark entities
|
|
180
184
|
with cli_console.phase("Creating Snowpark entities"):
|
|
@@ -242,8 +246,17 @@ def build_artifacts_mappings(
|
|
|
242
246
|
return entities_to_imports_map, stages_to_artifact_map
|
|
243
247
|
|
|
244
248
|
|
|
245
|
-
def create_stages_and_upload_artifacts(
|
|
249
|
+
def create_stages_and_upload_artifacts(
|
|
250
|
+
stages_to_artifact_map: StageToArtifactMapping, prune: bool
|
|
251
|
+
):
|
|
246
252
|
stage_manager = StageManager()
|
|
253
|
+
if prune:
|
|
254
|
+
# snowflake.cli._plugins.snowpark.snowpark_project_paths.Artifact class assumes that "stage"
|
|
255
|
+
# is a stage object, not path on stage - whole stage is managed by snowpark - it can be removed
|
|
256
|
+
for stage in stages_to_artifact_map.keys():
|
|
257
|
+
cli_console.step(f"Removing contents of stage {stage}")
|
|
258
|
+
stage_manager.remove(stage, path="")
|
|
259
|
+
|
|
247
260
|
for stage, artifacts in stages_to_artifact_map.items():
|
|
248
261
|
cli_console.step(f"Creating (if not exists) stage: {stage}")
|
|
249
262
|
stage = FQN.from_stage(stage).using_context()
|
|
@@ -88,9 +88,17 @@ MaxNodesOption = OverrideableOption(
|
|
|
88
88
|
_AUTO_RESUME_HELP = "The compute pool will automatically resume when a service or job is submitted to it."
|
|
89
89
|
|
|
90
90
|
AutoResumeOption = OverrideableOption(
|
|
91
|
-
|
|
92
|
-
"--auto-resume
|
|
91
|
+
False,
|
|
92
|
+
"--auto-resume",
|
|
93
93
|
help=_AUTO_RESUME_HELP,
|
|
94
|
+
mutually_exclusive=["no_auto_resume"],
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
NoAutoResumeOption = OverrideableOption(
|
|
98
|
+
False,
|
|
99
|
+
"--no-auto-resume",
|
|
100
|
+
help=_AUTO_RESUME_HELP,
|
|
101
|
+
mutually_exclusive=["auto_resume"],
|
|
94
102
|
)
|
|
95
103
|
|
|
96
104
|
_AUTO_SUSPEND_SECS_HELP = "Number of seconds of inactivity after which you want Snowflake to automatically suspend the compute pool."
|
|
@@ -126,6 +134,7 @@ def create(
|
|
|
126
134
|
min_nodes: int = MinNodesOption(),
|
|
127
135
|
max_nodes: Optional[int] = MaxNodesOption(),
|
|
128
136
|
auto_resume: bool = AutoResumeOption(),
|
|
137
|
+
no_auto_resume: bool = NoAutoResumeOption(),
|
|
129
138
|
initially_suspended: bool = typer.Option(
|
|
130
139
|
False,
|
|
131
140
|
"--init-suspend/--no-init-suspend",
|
|
@@ -140,13 +149,14 @@ def create(
|
|
|
140
149
|
"""
|
|
141
150
|
Creates a new compute pool.
|
|
142
151
|
"""
|
|
152
|
+
resume_option = True if auto_resume else False if no_auto_resume else True
|
|
143
153
|
max_nodes = validate_and_set_instances(min_nodes, max_nodes, "nodes")
|
|
144
154
|
cursor = ComputePoolManager().create(
|
|
145
155
|
pool_name=name.identifier,
|
|
146
156
|
min_nodes=min_nodes,
|
|
147
157
|
max_nodes=max_nodes,
|
|
148
158
|
instance_family=instance_family,
|
|
149
|
-
auto_resume=
|
|
159
|
+
auto_resume=resume_option,
|
|
150
160
|
initially_suspended=initially_suspended,
|
|
151
161
|
auto_suspend_secs=auto_suspend_secs,
|
|
152
162
|
tags=tags,
|
|
@@ -223,7 +233,8 @@ def set_property(
|
|
|
223
233
|
name: FQN = ComputePoolNameArgument,
|
|
224
234
|
min_nodes: Optional[int] = MinNodesOption(default=None, show_default=False),
|
|
225
235
|
max_nodes: Optional[int] = MaxNodesOption(show_default=False),
|
|
226
|
-
auto_resume:
|
|
236
|
+
auto_resume: bool = AutoResumeOption(default=None, show_default=False),
|
|
237
|
+
no_auto_resume: bool = NoAutoResumeOption(default=None, show_default=False),
|
|
227
238
|
auto_suspend_secs: Optional[int] = AutoSuspendSecsOption(
|
|
228
239
|
default=None, show_default=False
|
|
229
240
|
),
|
|
@@ -235,11 +246,12 @@ def set_property(
|
|
|
235
246
|
"""
|
|
236
247
|
Sets one or more properties for the compute pool.
|
|
237
248
|
"""
|
|
249
|
+
resume_option = True if auto_resume else False if no_auto_resume else None
|
|
238
250
|
cursor = ComputePoolManager().set_property(
|
|
239
251
|
pool_name=name.identifier,
|
|
240
252
|
min_nodes=min_nodes,
|
|
241
253
|
max_nodes=max_nodes,
|
|
242
|
-
auto_resume=
|
|
254
|
+
auto_resume=resume_option,
|
|
243
255
|
auto_suspend_secs=auto_suspend_secs,
|
|
244
256
|
comment=comment,
|
|
245
257
|
)
|
|
@@ -14,23 +14,29 @@
|
|
|
14
14
|
|
|
15
15
|
from __future__ import annotations
|
|
16
16
|
|
|
17
|
+
import logging
|
|
17
18
|
import sys
|
|
18
|
-
from
|
|
19
|
-
from itertools import chain
|
|
19
|
+
from functools import partial
|
|
20
20
|
from pathlib import Path
|
|
21
21
|
from typing import Dict, Iterable, List, Tuple
|
|
22
22
|
|
|
23
|
-
from click import ClickException, UsageError
|
|
24
|
-
from jinja2 import UndefinedError
|
|
25
23
|
from snowflake.cli._plugins.sql.snowsql_templating import transpile_snowsql_templates
|
|
24
|
+
from snowflake.cli._plugins.sql.source_reader import (
|
|
25
|
+
compile_statements,
|
|
26
|
+
files_reader,
|
|
27
|
+
query_reader,
|
|
28
|
+
)
|
|
29
|
+
from snowflake.cli.api.console import cli_console
|
|
30
|
+
from snowflake.cli.api.exceptions import CliArgumentError, CliSqlError
|
|
26
31
|
from snowflake.cli.api.rendering.sql_templates import snowflake_sql_jinja_render
|
|
27
|
-
from snowflake.cli.api.secure_path import
|
|
32
|
+
from snowflake.cli.api.secure_path import SecurePath
|
|
28
33
|
from snowflake.cli.api.sql_execution import SqlExecutionMixin, VerboseCursor
|
|
29
34
|
from snowflake.connector.cursor import SnowflakeCursor
|
|
30
|
-
from snowflake.connector.util_text import split_statements
|
|
31
35
|
|
|
32
36
|
IsSingleStatement = bool
|
|
33
37
|
|
|
38
|
+
logger = logging.getLogger(__name__)
|
|
39
|
+
|
|
34
40
|
|
|
35
41
|
class SqlManager(SqlExecutionMixin):
|
|
36
42
|
def execute(
|
|
@@ -41,57 +47,42 @@ class SqlManager(SqlExecutionMixin):
|
|
|
41
47
|
data: Dict | None = None,
|
|
42
48
|
retain_comments: bool = False,
|
|
43
49
|
) -> Tuple[IsSingleStatement, Iterable[SnowflakeCursor]]:
|
|
44
|
-
|
|
45
|
-
# Check if any two inputs were provided simultaneously
|
|
46
|
-
if len([i for i in inputs if i]) > 1:
|
|
47
|
-
raise UsageError(
|
|
48
|
-
"Multiple input sources specified. Please specify only one."
|
|
49
|
-
)
|
|
50
|
+
"""Reads, transforms and execute statements from input.
|
|
50
51
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
return self._execute_single_query(
|
|
55
|
-
query=query, data=data, retain_comments=retain_comments
|
|
56
|
-
)
|
|
52
|
+
Only one input can be consumed at a time.
|
|
53
|
+
When no compilation errors are detected, the sequence on queries
|
|
54
|
+
in executed and returned as tuple.
|
|
57
55
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
single_statement = False
|
|
62
|
-
for file in files:
|
|
63
|
-
query_from_file = SecurePath(file).read_text(
|
|
64
|
-
file_size_limit_mb=UNLIMITED
|
|
65
|
-
)
|
|
66
|
-
single_statement, result = self._execute_single_query(
|
|
67
|
-
query=query_from_file, data=data, retain_comments=retain_comments
|
|
68
|
-
)
|
|
69
|
-
results.append(result)
|
|
56
|
+
Throws an exception ff multiple inputs are provided.
|
|
57
|
+
"""
|
|
58
|
+
query = sys.stdin.read() if std_in else query
|
|
70
59
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
60
|
+
stmt_operators = (
|
|
61
|
+
transpile_snowsql_templates,
|
|
62
|
+
partial(snowflake_sql_jinja_render, data=data),
|
|
63
|
+
)
|
|
64
|
+
remove_comments = not retain_comments
|
|
74
65
|
|
|
75
|
-
|
|
76
|
-
|
|
66
|
+
if query:
|
|
67
|
+
stmt_reader = query_reader(query, stmt_operators, remove_comments)
|
|
68
|
+
elif files:
|
|
69
|
+
secured_files = [SecurePath(f) for f in files]
|
|
70
|
+
stmt_reader = files_reader(secured_files, stmt_operators, remove_comments)
|
|
71
|
+
else:
|
|
72
|
+
raise CliArgumentError("Use either query, filename or input option.")
|
|
77
73
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
try:
|
|
82
|
-
query = transpile_snowsql_templates(query)
|
|
83
|
-
query = snowflake_sql_jinja_render(content=query, data=data)
|
|
84
|
-
except UndefinedError as err:
|
|
85
|
-
raise ClickException(f"SQL template rendering error: {err}")
|
|
74
|
+
errors, stmt_count, compiled_statements = compile_statements(stmt_reader)
|
|
75
|
+
if not any((errors, stmt_count, compiled_statements)):
|
|
76
|
+
raise CliArgumentError("Use either query, filename or input option.")
|
|
86
77
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
)
|
|
92
|
-
)
|
|
93
|
-
single_statement = len(statements) == 1
|
|
78
|
+
if errors:
|
|
79
|
+
for error in errors:
|
|
80
|
+
logger.info("Statement compilation error: %s", error)
|
|
81
|
+
cli_console.warning(error)
|
|
82
|
+
raise CliSqlError("SQL rendering error")
|
|
94
83
|
|
|
95
|
-
|
|
96
|
-
|
|
84
|
+
is_single_statement = not (stmt_count > 1)
|
|
85
|
+
return is_single_statement, self.execute_string(
|
|
86
|
+
"\n".join(compiled_statements),
|
|
87
|
+
cursor_class=VerboseCursor,
|
|
97
88
|
)
|