snowflake-cli-labs 3.0.0rc0__py3-none-any.whl → 3.0.0rc2__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 +10 -1
- snowflake/cli/_app/snow_connector.py +91 -37
- snowflake/cli/_app/telemetry.py +8 -4
- snowflake/cli/_app/version_check.py +74 -0
- snowflake/cli/_plugins/connection/commands.py +3 -2
- snowflake/cli/_plugins/git/commands.py +55 -14
- snowflake/cli/_plugins/git/manager.py +14 -6
- snowflake/cli/_plugins/nativeapp/codegen/compiler.py +18 -2
- snowflake/cli/_plugins/nativeapp/codegen/setup/native_app_setup_processor.py +123 -42
- snowflake/cli/_plugins/nativeapp/codegen/setup/setup_driver.py.source +5 -2
- snowflake/cli/_plugins/nativeapp/codegen/snowpark/python_processor.py +6 -11
- snowflake/cli/_plugins/nativeapp/codegen/templates/templates_processor.py +111 -0
- snowflake/cli/_plugins/nativeapp/exceptions.py +3 -3
- snowflake/cli/_plugins/nativeapp/manager.py +74 -144
- snowflake/cli/_plugins/nativeapp/project_model.py +2 -9
- snowflake/cli/_plugins/nativeapp/run_processor.py +56 -260
- snowflake/cli/_plugins/nativeapp/same_account_install_method.py +74 -0
- snowflake/cli/_plugins/nativeapp/teardown_processor.py +17 -246
- snowflake/cli/_plugins/nativeapp/v2_conversions/v2_to_v1_decorator.py +91 -17
- snowflake/cli/_plugins/snowpark/commands.py +5 -65
- snowflake/cli/_plugins/snowpark/common.py +17 -1
- snowflake/cli/_plugins/snowpark/models.py +2 -1
- snowflake/cli/_plugins/snowpark/package/anaconda_packages.py +1 -35
- snowflake/cli/_plugins/sql/commands.py +1 -2
- snowflake/cli/_plugins/stage/commands.py +2 -2
- snowflake/cli/_plugins/stage/manager.py +46 -15
- snowflake/cli/_plugins/streamlit/commands.py +4 -63
- snowflake/cli/_plugins/streamlit/manager.py +13 -0
- snowflake/cli/_plugins/workspace/action_context.py +7 -0
- snowflake/cli/_plugins/workspace/commands.py +145 -32
- snowflake/cli/_plugins/workspace/manager.py +21 -4
- snowflake/cli/api/cli_global_context.py +136 -313
- snowflake/cli/api/commands/decorators.py +1 -1
- snowflake/cli/api/commands/flags.py +106 -102
- snowflake/cli/api/commands/snow_typer.py +15 -6
- snowflake/cli/api/config.py +18 -5
- snowflake/cli/api/connections.py +214 -0
- snowflake/cli/api/console/abc.py +4 -2
- snowflake/cli/api/constants.py +11 -0
- snowflake/cli/api/entities/application_entity.py +687 -2
- snowflake/cli/api/entities/application_package_entity.py +407 -9
- snowflake/cli/api/entities/common.py +7 -2
- snowflake/cli/api/entities/utils.py +80 -20
- snowflake/cli/api/exceptions.py +12 -2
- snowflake/cli/api/feature_flags.py +0 -2
- snowflake/cli/api/identifiers.py +3 -0
- snowflake/cli/api/project/definition.py +35 -1
- snowflake/cli/api/project/definition_conversion.py +352 -0
- snowflake/cli/api/project/schemas/entities/application_package_entity_model.py +17 -0
- snowflake/cli/api/project/schemas/entities/common.py +0 -12
- snowflake/cli/api/project/schemas/identifier_model.py +2 -2
- snowflake/cli/api/project/schemas/project_definition.py +102 -43
- snowflake/cli/api/rendering/jinja.py +2 -16
- snowflake/cli/api/rendering/project_definition_templates.py +5 -1
- snowflake/cli/api/rendering/sql_templates.py +14 -4
- snowflake/cli/api/secure_path.py +13 -18
- snowflake/cli/api/secure_utils.py +90 -1
- snowflake/cli/api/sql_execution.py +13 -0
- snowflake/cli/api/utils/definition_rendering.py +7 -7
- {snowflake_cli_labs-3.0.0rc0.dist-info → snowflake_cli_labs-3.0.0rc2.dist-info}/METADATA +9 -9
- {snowflake_cli_labs-3.0.0rc0.dist-info → snowflake_cli_labs-3.0.0rc2.dist-info}/RECORD +65 -61
- snowflake/cli/api/commands/typer_pre_execute.py +0 -26
- {snowflake_cli_labs-3.0.0rc0.dist-info → snowflake_cli_labs-3.0.0rc2.dist-info}/WHEEL +0 -0
- {snowflake_cli_labs-3.0.0rc0.dist-info → snowflake_cli_labs-3.0.0rc2.dist-info}/entry_points.txt +0 -0
- {snowflake_cli_labs-3.0.0rc0.dist-info → snowflake_cli_labs-3.0.0rc2.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
# Copyright (c) 2024 Snowflake Inc.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import asyncio
|
|
18
|
+
import logging
|
|
19
|
+
import re
|
|
20
|
+
import warnings
|
|
21
|
+
from dataclasses import asdict, dataclass, field, fields, replace
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
from typing import Optional
|
|
24
|
+
|
|
25
|
+
from snowflake.cli.api.config import get_default_connection_name
|
|
26
|
+
from snowflake.cli.api.exceptions import InvalidSchemaError
|
|
27
|
+
from snowflake.connector import SnowflakeConnection
|
|
28
|
+
from snowflake.connector.compat import IS_WINDOWS
|
|
29
|
+
|
|
30
|
+
logger = logging.getLogger(__name__)
|
|
31
|
+
|
|
32
|
+
schema_pattern = re.compile(r".+\..+")
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass
|
|
36
|
+
class ConnectionContext:
|
|
37
|
+
# FIXME: can reduce duplication using config.ConnectionConfig
|
|
38
|
+
connection_name: Optional[str] = None
|
|
39
|
+
account: Optional[str] = None
|
|
40
|
+
database: Optional[str] = None
|
|
41
|
+
role: Optional[str] = None
|
|
42
|
+
schema: Optional[str] = None
|
|
43
|
+
user: Optional[str] = None
|
|
44
|
+
password: Optional[str] = field(default=None, repr=False)
|
|
45
|
+
authenticator: Optional[str] = None
|
|
46
|
+
private_key_file: Optional[str] = None
|
|
47
|
+
warehouse: Optional[str] = None
|
|
48
|
+
mfa_passcode: Optional[str] = None
|
|
49
|
+
enable_diag: Optional[bool] = False
|
|
50
|
+
diag_log_path: Optional[Path] = None
|
|
51
|
+
diag_allowlist_path: Optional[Path] = None
|
|
52
|
+
temporary_connection: bool = False
|
|
53
|
+
session_token: Optional[str] = None
|
|
54
|
+
master_token: Optional[str] = None
|
|
55
|
+
token_file_path: Optional[Path] = None
|
|
56
|
+
|
|
57
|
+
VALIDATED_FIELD_NAMES = ["schema"]
|
|
58
|
+
|
|
59
|
+
def present_values_as_dict(self) -> dict:
|
|
60
|
+
"""Dictionary representation of this ConnectionContext for values that are not None"""
|
|
61
|
+
return {k: v for (k, v) in asdict(self).items() if v is not None}
|
|
62
|
+
|
|
63
|
+
def clone(self) -> ConnectionContext:
|
|
64
|
+
return replace(self)
|
|
65
|
+
|
|
66
|
+
def update(self, **updates):
|
|
67
|
+
"""
|
|
68
|
+
Given a dictionary of property (key, value) mappings, update properties
|
|
69
|
+
of this context object with equivalent names to the keys.
|
|
70
|
+
|
|
71
|
+
Raises KeyError if a non-property is specified as a key.
|
|
72
|
+
"""
|
|
73
|
+
field_map = {field.name: field for field in fields(self)}
|
|
74
|
+
for (key, value) in updates.items():
|
|
75
|
+
# ensure key represents a property
|
|
76
|
+
if key not in field_map:
|
|
77
|
+
raise KeyError(f"{key} is not a field of {self.__class__.__name__}")
|
|
78
|
+
setattr(self, key, value)
|
|
79
|
+
|
|
80
|
+
def __repr__(self) -> str:
|
|
81
|
+
"""Minimal repr where None values have their keys omitted."""
|
|
82
|
+
items = [f"{k}={repr(v)}" for (k, v) in self.present_values_as_dict().items()]
|
|
83
|
+
return f"{self.__class__.__name__}({', '.join(items)})"
|
|
84
|
+
|
|
85
|
+
def __setattr__(self, prop, val):
|
|
86
|
+
"""Runs registered validators before setting fields."""
|
|
87
|
+
if prop in self.VALIDATED_FIELD_NAMES:
|
|
88
|
+
validate = getattr(self, f"validate_{prop}")
|
|
89
|
+
validate(val)
|
|
90
|
+
super().__setattr__(prop, val)
|
|
91
|
+
|
|
92
|
+
def validate_schema(self, value: Optional[str]):
|
|
93
|
+
if (
|
|
94
|
+
value
|
|
95
|
+
and not (value.startswith('"') and value.endswith('"'))
|
|
96
|
+
# if schema is fully qualified name (db.schema)
|
|
97
|
+
and schema_pattern.match(value)
|
|
98
|
+
):
|
|
99
|
+
raise InvalidSchemaError(value)
|
|
100
|
+
|
|
101
|
+
def validate_and_complete(self):
|
|
102
|
+
"""
|
|
103
|
+
Ensure we can create a connection from this context.
|
|
104
|
+
"""
|
|
105
|
+
if not self.temporary_connection and not self.connection_name:
|
|
106
|
+
self.connection_name = get_default_connection_name()
|
|
107
|
+
|
|
108
|
+
def build_connection(self):
|
|
109
|
+
from snowflake.cli._app.snow_connector import connect_to_snowflake
|
|
110
|
+
|
|
111
|
+
# Ignore warnings about bad owner or permissions on Windows
|
|
112
|
+
# Telemetry omit our warning filter from config.py
|
|
113
|
+
if IS_WINDOWS:
|
|
114
|
+
warnings.filterwarnings(
|
|
115
|
+
action="ignore",
|
|
116
|
+
message="Bad owner or permissions.*",
|
|
117
|
+
module="snowflake.connector.config_manager",
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
return connect_to_snowflake(**self.present_values_as_dict())
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
class OpenConnectionCache:
|
|
124
|
+
"""
|
|
125
|
+
A connection cache that transparently manages SnowflakeConnection objects
|
|
126
|
+
and is keyed by ConnectionContext objects, e.g. cache[ctx].execute_string(...).
|
|
127
|
+
Connections are automatically closed after CONNECTION_CLEANUP_SEC, but
|
|
128
|
+
are guaranteed to be open (if config is valid) when returned by the cache.
|
|
129
|
+
"""
|
|
130
|
+
|
|
131
|
+
connections: dict[str, SnowflakeConnection]
|
|
132
|
+
cleanup_futures: dict[str, asyncio.TimerHandle]
|
|
133
|
+
|
|
134
|
+
CONNECTION_CLEANUP_SEC: float = 10.0 * 60
|
|
135
|
+
"""Connections are closed this many seconds after the last time they are accessed."""
|
|
136
|
+
|
|
137
|
+
def __init__(self):
|
|
138
|
+
self.connections = {}
|
|
139
|
+
self.cleanup_futures = {}
|
|
140
|
+
|
|
141
|
+
def __getitem__(self, ctx):
|
|
142
|
+
if not isinstance(ctx, ConnectionContext):
|
|
143
|
+
raise ValueError(
|
|
144
|
+
f"Expected key to be ConnectionContext but got {repr(ctx)}"
|
|
145
|
+
)
|
|
146
|
+
key = repr(ctx)
|
|
147
|
+
if not self._has_open_connection(key):
|
|
148
|
+
self._insert(key, ctx)
|
|
149
|
+
self._touch(key)
|
|
150
|
+
return self.connections[key]
|
|
151
|
+
|
|
152
|
+
def clear(self):
|
|
153
|
+
"""Closes all connections and resets the cache to its initial state."""
|
|
154
|
+
connection_keys = list(self.connections.keys())
|
|
155
|
+
for key in connection_keys:
|
|
156
|
+
self._cleanup(key)
|
|
157
|
+
|
|
158
|
+
# if any orphaned futures still exist, clean them up too
|
|
159
|
+
for future in self.cleanup_futures.values():
|
|
160
|
+
future.cancel()
|
|
161
|
+
self.cleanup_futures.clear()
|
|
162
|
+
|
|
163
|
+
def _has_open_connection(self, key: str):
|
|
164
|
+
return key in self.connections
|
|
165
|
+
|
|
166
|
+
def _insert(self, key: str, ctx: ConnectionContext):
|
|
167
|
+
try:
|
|
168
|
+
# N.B. build_connection ultimately calls connect_to_snowflake, which
|
|
169
|
+
# interpolates in connection dicts (from config) and environment variables.
|
|
170
|
+
# This means that we could return a stale (incorrect) connection for the
|
|
171
|
+
# given ConnectionContext if get_env_value or get_connection_dict would
|
|
172
|
+
# have returned different values (i.e. env / config have changed).
|
|
173
|
+
self.connections[key] = ctx.build_connection()
|
|
174
|
+
except Exception:
|
|
175
|
+
logger.debug(
|
|
176
|
+
"ConnectionCache: failed to connect using %s; not caching.", key
|
|
177
|
+
)
|
|
178
|
+
raise
|
|
179
|
+
|
|
180
|
+
def _cancel_cleanup_future_if_exists(self, key: str):
|
|
181
|
+
if key in self.cleanup_futures:
|
|
182
|
+
self.cleanup_futures.pop(key).cancel()
|
|
183
|
+
|
|
184
|
+
def _touch(self, key: str):
|
|
185
|
+
"""
|
|
186
|
+
Extend the lifetime of the cached connection at the given key.
|
|
187
|
+
"""
|
|
188
|
+
loop = None
|
|
189
|
+
try:
|
|
190
|
+
loop = asyncio.get_event_loop()
|
|
191
|
+
except RuntimeError:
|
|
192
|
+
# Python 3.11+ will throw when no event loop;
|
|
193
|
+
# Python 3.10 will issue a DeprecationWarning and return None
|
|
194
|
+
pass
|
|
195
|
+
|
|
196
|
+
if not loop:
|
|
197
|
+
logger.debug(
|
|
198
|
+
"ConnectionCache: no event loop; connections will close at exit."
|
|
199
|
+
)
|
|
200
|
+
return
|
|
201
|
+
|
|
202
|
+
self._cancel_cleanup_future_if_exists(key)
|
|
203
|
+
self.cleanup_futures[key] = loop.call_later(
|
|
204
|
+
self.CONNECTION_CLEANUP_SEC, lambda: self._cleanup(key)
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
def _cleanup(self, key: str):
|
|
208
|
+
"""Closes the cached connection at the given key."""
|
|
209
|
+
if key not in self.connections:
|
|
210
|
+
logger.debug("Cleaning up connection %s, but not found in cache!", key)
|
|
211
|
+
|
|
212
|
+
# doesn't cancel in-flight async queries
|
|
213
|
+
self._cancel_cleanup_future_if_exists(key)
|
|
214
|
+
self.connections.pop(key).close()
|
snowflake/cli/api/console/abc.py
CHANGED
|
@@ -37,14 +37,16 @@ class AbstractConsole(ABC):
|
|
|
37
37
|
"""
|
|
38
38
|
|
|
39
39
|
_print_fn: Callable[[str], None]
|
|
40
|
-
_cli_context: _CliGlobalContextAccess
|
|
41
40
|
_in_phase: bool
|
|
42
41
|
|
|
43
42
|
def __init__(self):
|
|
44
43
|
super().__init__()
|
|
45
|
-
self._cli_context = get_cli_context()
|
|
46
44
|
self._in_phase = False
|
|
47
45
|
|
|
46
|
+
@property
|
|
47
|
+
def _cli_context(self) -> _CliGlobalContextAccess:
|
|
48
|
+
return get_cli_context()
|
|
49
|
+
|
|
48
50
|
@property
|
|
49
51
|
def is_silent(self) -> bool:
|
|
50
52
|
"""Returns information whether intermediate output is muted."""
|
snowflake/cli/api/constants.py
CHANGED
|
@@ -77,3 +77,14 @@ VALID_SCOPES = ["database", "schema", "compute-pool"]
|
|
|
77
77
|
DEFAULT_SIZE_LIMIT_MB = 128
|
|
78
78
|
|
|
79
79
|
SF_REST_API_URL_PREFIX = "/api/v2"
|
|
80
|
+
|
|
81
|
+
PROJECT_TEMPLATE_VARIABLE_OPENING = "<%"
|
|
82
|
+
PROJECT_TEMPLATE_VARIABLE_CLOSING = "%>"
|
|
83
|
+
|
|
84
|
+
INIT_TEMPLATE_VARIABLE_OPENING = "<!"
|
|
85
|
+
INIT_TEMPLATE_VARIABLE_CLOSING = "!>"
|
|
86
|
+
|
|
87
|
+
SNOWPARK_SHARED_MIXIN = "snowpark_shared"
|
|
88
|
+
|
|
89
|
+
DEFAULT_ENV_FILE = "environment.yml"
|
|
90
|
+
DEFAULT_PAGES_DIR = "pages"
|