snowflake-cli-labs 3.0.0rc5__py3-none-any.whl → 3.0.2__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.
- README.md +21 -0
- {snowflake_cli_labs-3.0.0rc5.dist-info → snowflake_cli_labs-3.0.2.dist-info}/METADATA +6 -96
- snowflake_cli_labs-3.0.2.dist-info/RECORD +5 -0
- snowflake/cli/__about__.py +0 -17
- snowflake/cli/__init__.py +0 -13
- snowflake/cli/_app/__init__.py +0 -22
- snowflake/cli/_app/__main__.py +0 -31
- snowflake/cli/_app/api_impl/__init__.py +0 -13
- snowflake/cli/_app/api_impl/plugin/__init__.py +0 -13
- snowflake/cli/_app/api_impl/plugin/plugin_config_provider_impl.py +0 -66
- snowflake/cli/_app/cli_app.py +0 -252
- snowflake/cli/_app/commands_registration/__init__.py +0 -33
- snowflake/cli/_app/commands_registration/builtin_plugins.py +0 -50
- snowflake/cli/_app/commands_registration/command_plugins_loader.py +0 -169
- snowflake/cli/_app/commands_registration/commands_registration_with_callbacks.py +0 -105
- snowflake/cli/_app/commands_registration/exception_logging.py +0 -26
- snowflake/cli/_app/commands_registration/threadsafe.py +0 -48
- snowflake/cli/_app/commands_registration/typer_registration.py +0 -153
- snowflake/cli/_app/constants.py +0 -19
- snowflake/cli/_app/dev/__init__.py +0 -13
- snowflake/cli/_app/dev/commands_structure.py +0 -48
- snowflake/cli/_app/dev/docs/__init__.py +0 -13
- snowflake/cli/_app/dev/docs/commands_docs_generator.py +0 -118
- snowflake/cli/_app/dev/docs/generator.py +0 -35
- snowflake/cli/_app/dev/docs/project_definition_docs_generator.py +0 -58
- snowflake/cli/_app/dev/docs/project_definition_generate_json_schema.py +0 -227
- snowflake/cli/_app/dev/docs/template_utils.py +0 -23
- snowflake/cli/_app/dev/docs/templates/definition_description.rst.jinja2 +0 -38
- snowflake/cli/_app/dev/docs/templates/overview.rst.jinja2 +0 -9
- snowflake/cli/_app/dev/docs/templates/usage.rst.jinja2 +0 -67
- snowflake/cli/_app/dev/pycharm_remote_debug.py +0 -46
- snowflake/cli/_app/loggers.py +0 -199
- snowflake/cli/_app/main_typer.py +0 -62
- snowflake/cli/_app/printing.py +0 -181
- snowflake/cli/_app/secret.py +0 -9
- snowflake/cli/_app/snow_connector.py +0 -309
- snowflake/cli/_app/telemetry.py +0 -220
- snowflake/cli/_app/version_check.py +0 -74
- snowflake/cli/_plugins/__init__.py +0 -13
- snowflake/cli/_plugins/connection/__init__.py +0 -13
- snowflake/cli/_plugins/connection/commands.py +0 -353
- snowflake/cli/_plugins/connection/plugin_spec.py +0 -30
- snowflake/cli/_plugins/connection/util.py +0 -195
- snowflake/cli/_plugins/cortex/__init__.py +0 -13
- snowflake/cli/_plugins/cortex/commands.py +0 -332
- snowflake/cli/_plugins/cortex/constants.py +0 -17
- snowflake/cli/_plugins/cortex/manager.py +0 -189
- snowflake/cli/_plugins/cortex/plugin_spec.py +0 -30
- snowflake/cli/_plugins/cortex/types.py +0 -22
- snowflake/cli/_plugins/git/__init__.py +0 -13
- snowflake/cli/_plugins/git/commands.py +0 -358
- snowflake/cli/_plugins/git/manager.py +0 -151
- snowflake/cli/_plugins/git/plugin_spec.py +0 -30
- snowflake/cli/_plugins/helpers/__init__.py +0 -13
- snowflake/cli/_plugins/helpers/commands.py +0 -90
- snowflake/cli/_plugins/helpers/plugin_spec.py +0 -30
- snowflake/cli/_plugins/init/__init__.py +0 -13
- snowflake/cli/_plugins/init/commands.py +0 -248
- snowflake/cli/_plugins/init/plugin_spec.py +0 -30
- snowflake/cli/_plugins/nativeapp/__init__.py +0 -13
- snowflake/cli/_plugins/nativeapp/artifacts.py +0 -757
- snowflake/cli/_plugins/nativeapp/bundle_context.py +0 -31
- snowflake/cli/_plugins/nativeapp/codegen/__init__.py +0 -13
- snowflake/cli/_plugins/nativeapp/codegen/artifact_processor.py +0 -91
- snowflake/cli/_plugins/nativeapp/codegen/compiler.py +0 -149
- snowflake/cli/_plugins/nativeapp/codegen/sandbox.py +0 -306
- snowflake/cli/_plugins/nativeapp/codegen/setup/native_app_setup_processor.py +0 -249
- snowflake/cli/_plugins/nativeapp/codegen/setup/setup_driver.py.source +0 -59
- snowflake/cli/_plugins/nativeapp/codegen/snowpark/callback_source.py.jinja +0 -181
- snowflake/cli/_plugins/nativeapp/codegen/snowpark/extension_function_utils.py +0 -217
- snowflake/cli/_plugins/nativeapp/codegen/snowpark/models.py +0 -61
- snowflake/cli/_plugins/nativeapp/codegen/snowpark/python_processor.py +0 -523
- snowflake/cli/_plugins/nativeapp/codegen/templates/templates_processor.py +0 -114
- snowflake/cli/_plugins/nativeapp/commands.py +0 -559
- snowflake/cli/_plugins/nativeapp/common_flags.py +0 -44
- snowflake/cli/_plugins/nativeapp/constants.py +0 -27
- snowflake/cli/_plugins/nativeapp/entities/__init__.py +0 -0
- snowflake/cli/_plugins/nativeapp/entities/application.py +0 -878
- snowflake/cli/_plugins/nativeapp/entities/application_package.py +0 -1392
- snowflake/cli/_plugins/nativeapp/exceptions.py +0 -113
- snowflake/cli/_plugins/nativeapp/feature_flags.py +0 -24
- snowflake/cli/_plugins/nativeapp/manager.py +0 -415
- snowflake/cli/_plugins/nativeapp/plugin_spec.py +0 -30
- snowflake/cli/_plugins/nativeapp/policy.py +0 -53
- snowflake/cli/_plugins/nativeapp/project_model.py +0 -211
- snowflake/cli/_plugins/nativeapp/run_processor.py +0 -184
- snowflake/cli/_plugins/nativeapp/same_account_install_method.py +0 -70
- snowflake/cli/_plugins/nativeapp/teardown_processor.py +0 -70
- snowflake/cli/_plugins/nativeapp/utils.py +0 -98
- snowflake/cli/_plugins/nativeapp/v2_conversions/v2_to_v1_decorator.py +0 -262
- snowflake/cli/_plugins/nativeapp/version/__init__.py +0 -13
- snowflake/cli/_plugins/nativeapp/version/commands.py +0 -141
- snowflake/cli/_plugins/nativeapp/version/version_processor.py +0 -98
- snowflake/cli/_plugins/notebook/__init__.py +0 -13
- snowflake/cli/_plugins/notebook/commands.py +0 -86
- snowflake/cli/_plugins/notebook/exceptions.py +0 -20
- snowflake/cli/_plugins/notebook/manager.py +0 -71
- snowflake/cli/_plugins/notebook/plugin_spec.py +0 -30
- snowflake/cli/_plugins/notebook/types.py +0 -15
- snowflake/cli/_plugins/object/__init__.py +0 -13
- snowflake/cli/_plugins/object/command_aliases.py +0 -95
- snowflake/cli/_plugins/object/commands.py +0 -180
- snowflake/cli/_plugins/object/common.py +0 -85
- snowflake/cli/_plugins/object/manager.py +0 -118
- snowflake/cli/_plugins/object/plugin_spec.py +0 -30
- snowflake/cli/_plugins/snowpark/__init__.py +0 -13
- snowflake/cli/_plugins/snowpark/commands.py +0 -450
- snowflake/cli/_plugins/snowpark/common.py +0 -268
- snowflake/cli/_plugins/snowpark/models.py +0 -150
- snowflake/cli/_plugins/snowpark/package/__init__.py +0 -13
- snowflake/cli/_plugins/snowpark/package/anaconda_packages.py +0 -199
- snowflake/cli/_plugins/snowpark/package/commands.py +0 -195
- snowflake/cli/_plugins/snowpark/package/manager.py +0 -44
- snowflake/cli/_plugins/snowpark/package/utils.py +0 -26
- snowflake/cli/_plugins/snowpark/package_utils.py +0 -354
- snowflake/cli/_plugins/snowpark/plugin_spec.py +0 -30
- snowflake/cli/_plugins/snowpark/snowpark_entity.py +0 -29
- snowflake/cli/_plugins/snowpark/snowpark_entity_model.py +0 -173
- snowflake/cli/_plugins/snowpark/snowpark_project_paths.py +0 -109
- snowflake/cli/_plugins/snowpark/snowpark_shared.py +0 -59
- snowflake/cli/_plugins/snowpark/zipper.py +0 -89
- snowflake/cli/_plugins/spcs/__init__.py +0 -33
- snowflake/cli/_plugins/spcs/common.py +0 -99
- snowflake/cli/_plugins/spcs/compute_pool/__init__.py +0 -13
- snowflake/cli/_plugins/spcs/compute_pool/commands.py +0 -241
- snowflake/cli/_plugins/spcs/compute_pool/manager.py +0 -121
- snowflake/cli/_plugins/spcs/image_registry/__init__.py +0 -13
- snowflake/cli/_plugins/spcs/image_registry/commands.py +0 -65
- snowflake/cli/_plugins/spcs/image_registry/manager.py +0 -105
- snowflake/cli/_plugins/spcs/image_repository/__init__.py +0 -13
- snowflake/cli/_plugins/spcs/image_repository/commands.py +0 -202
- snowflake/cli/_plugins/spcs/image_repository/manager.py +0 -84
- snowflake/cli/_plugins/spcs/plugin_spec.py +0 -30
- snowflake/cli/_plugins/spcs/services/__init__.py +0 -13
- snowflake/cli/_plugins/spcs/services/commands.py +0 -345
- snowflake/cli/_plugins/spcs/services/manager.py +0 -208
- snowflake/cli/_plugins/sql/__init__.py +0 -13
- snowflake/cli/_plugins/sql/commands.py +0 -86
- snowflake/cli/_plugins/sql/manager.py +0 -92
- snowflake/cli/_plugins/sql/plugin_spec.py +0 -30
- snowflake/cli/_plugins/sql/snowsql_templating.py +0 -28
- snowflake/cli/_plugins/stage/__init__.py +0 -13
- snowflake/cli/_plugins/stage/commands.py +0 -264
- snowflake/cli/_plugins/stage/diff.py +0 -280
- snowflake/cli/_plugins/stage/manager.py +0 -582
- snowflake/cli/_plugins/stage/md5.py +0 -160
- snowflake/cli/_plugins/stage/plugin_spec.py +0 -30
- snowflake/cli/_plugins/stage/utils.py +0 -54
- snowflake/cli/_plugins/streamlit/__init__.py +0 -13
- snowflake/cli/_plugins/streamlit/commands.py +0 -195
- snowflake/cli/_plugins/streamlit/manager.py +0 -220
- snowflake/cli/_plugins/streamlit/plugin_spec.py +0 -30
- snowflake/cli/_plugins/streamlit/streamlit_entity.py +0 -12
- snowflake/cli/_plugins/streamlit/streamlit_entity_model.py +0 -66
- snowflake/cli/_plugins/workspace/__init__.py +0 -13
- snowflake/cli/_plugins/workspace/action_context.py +0 -18
- snowflake/cli/_plugins/workspace/commands.py +0 -306
- snowflake/cli/_plugins/workspace/manager.py +0 -74
- snowflake/cli/_plugins/workspace/plugin_spec.py +0 -30
- snowflake/cli/api/__init__.py +0 -48
- snowflake/cli/api/cli_global_context.py +0 -247
- snowflake/cli/api/commands/__init__.py +0 -13
- snowflake/cli/api/commands/alias.py +0 -23
- snowflake/cli/api/commands/common.py +0 -25
- snowflake/cli/api/commands/decorators.py +0 -369
- snowflake/cli/api/commands/execution_metadata.py +0 -40
- snowflake/cli/api/commands/experimental_behaviour.py +0 -18
- snowflake/cli/api/commands/flags.py +0 -561
- snowflake/cli/api/commands/overrideable_parameter.py +0 -143
- snowflake/cli/api/commands/snow_typer.py +0 -247
- snowflake/cli/api/commands/utils.py +0 -18
- snowflake/cli/api/config.py +0 -380
- snowflake/cli/api/connections.py +0 -216
- snowflake/cli/api/console/__init__.py +0 -17
- snowflake/cli/api/console/abc.py +0 -94
- snowflake/cli/api/console/console.py +0 -134
- snowflake/cli/api/console/enum.py +0 -17
- snowflake/cli/api/constants.py +0 -90
- snowflake/cli/api/entities/common.py +0 -56
- snowflake/cli/api/entities/utils.py +0 -370
- snowflake/cli/api/errno.py +0 -28
- snowflake/cli/api/exceptions.py +0 -190
- snowflake/cli/api/feature_flags.py +0 -54
- snowflake/cli/api/identifiers.py +0 -190
- snowflake/cli/api/metrics.py +0 -92
- snowflake/cli/api/output/__init__.py +0 -13
- snowflake/cli/api/output/formats.py +0 -20
- snowflake/cli/api/output/types.py +0 -118
- snowflake/cli/api/plugins/__init__.py +0 -13
- snowflake/cli/api/plugins/command/__init__.py +0 -72
- snowflake/cli/api/plugins/command/plugin_hook_specs.py +0 -21
- snowflake/cli/api/plugins/plugin_config.py +0 -32
- snowflake/cli/api/project/__init__.py +0 -13
- snowflake/cli/api/project/definition.py +0 -126
- snowflake/cli/api/project/definition_conversion.py +0 -400
- snowflake/cli/api/project/definition_manager.py +0 -145
- snowflake/cli/api/project/errors.py +0 -56
- snowflake/cli/api/project/project_verification.py +0 -23
- snowflake/cli/api/project/schemas/__init__.py +0 -13
- snowflake/cli/api/project/schemas/entities/__init__.py +0 -13
- snowflake/cli/api/project/schemas/entities/common.py +0 -153
- snowflake/cli/api/project/schemas/entities/entities.py +0 -61
- snowflake/cli/api/project/schemas/project_definition.py +0 -330
- snowflake/cli/api/project/schemas/template.py +0 -77
- snowflake/cli/api/project/schemas/updatable_model.py +0 -202
- snowflake/cli/api/project/schemas/v1/__init__.py +0 -0
- snowflake/cli/api/project/schemas/v1/identifier_model.py +0 -51
- snowflake/cli/api/project/schemas/v1/native_app/__init__.py +0 -0
- snowflake/cli/api/project/schemas/v1/native_app/application.py +0 -61
- snowflake/cli/api/project/schemas/v1/native_app/native_app.py +0 -93
- snowflake/cli/api/project/schemas/v1/native_app/package.py +0 -84
- snowflake/cli/api/project/schemas/v1/native_app/path_mapping.py +0 -65
- snowflake/cli/api/project/schemas/v1/snowpark/__init__.py +0 -0
- snowflake/cli/api/project/schemas/v1/snowpark/argument.py +0 -28
- snowflake/cli/api/project/schemas/v1/snowpark/callable.py +0 -69
- snowflake/cli/api/project/schemas/v1/snowpark/snowpark.py +0 -36
- snowflake/cli/api/project/schemas/v1/streamlit/__init__.py +0 -0
- snowflake/cli/api/project/schemas/v1/streamlit/streamlit.py +0 -47
- snowflake/cli/api/project/util.py +0 -278
- snowflake/cli/api/rendering/__init__.py +0 -13
- snowflake/cli/api/rendering/jinja.py +0 -118
- snowflake/cli/api/rendering/project_definition_templates.py +0 -43
- snowflake/cli/api/rendering/project_templates.py +0 -98
- snowflake/cli/api/rendering/sql_templates.py +0 -105
- snowflake/cli/api/rest_api.py +0 -178
- snowflake/cli/api/sanitizers.py +0 -43
- snowflake/cli/api/secure_path.py +0 -360
- snowflake/cli/api/secure_utils.py +0 -118
- snowflake/cli/api/sql_execution.py +0 -280
- snowflake/cli/api/utils/__init__.py +0 -13
- snowflake/cli/api/utils/cursor.py +0 -34
- snowflake/cli/api/utils/definition_rendering.py +0 -415
- snowflake/cli/api/utils/dict_utils.py +0 -73
- snowflake/cli/api/utils/error_handling.py +0 -23
- snowflake/cli/api/utils/graph.py +0 -97
- snowflake/cli/api/utils/models.py +0 -63
- snowflake/cli/api/utils/naming_utils.py +0 -13
- snowflake/cli/api/utils/path_utils.py +0 -36
- snowflake/cli/api/utils/templating_functions.py +0 -144
- snowflake/cli/api/utils/types.py +0 -35
- snowflake_cli_labs-3.0.0rc5.dist-info/RECORD +0 -242
- snowflake_cli_labs-3.0.0rc5.dist-info/entry_points.txt +0 -2
- {snowflake_cli_labs-3.0.0rc5.dist-info → snowflake_cli_labs-3.0.2.dist-info}/WHEEL +0 -0
- {snowflake_cli_labs-3.0.0rc5.dist-info → snowflake_cli_labs-3.0.2.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,160 +0,0 @@
|
|
|
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 hashlib
|
|
18
|
-
import logging
|
|
19
|
-
import math
|
|
20
|
-
import os.path
|
|
21
|
-
import re
|
|
22
|
-
from pathlib import Path
|
|
23
|
-
from typing import List, Tuple
|
|
24
|
-
|
|
25
|
-
from click.exceptions import ClickException
|
|
26
|
-
from snowflake.cli.api.secure_path import UNLIMITED, SecurePath
|
|
27
|
-
from snowflake.connector.constants import S3_CHUNK_SIZE, S3_MAX_PARTS, S3_MIN_PART_SIZE
|
|
28
|
-
|
|
29
|
-
ONE_MEGABYTE = 1024**2
|
|
30
|
-
READ_BUFFER_BYTES = 64 * 1024
|
|
31
|
-
MD5SUM_REGEX = r"^[A-Fa-f0-9]{32}$"
|
|
32
|
-
MULTIPART_MD5SUM_REGEX = r"^([A-Fa-f0-9]{32})-(\d+)$"
|
|
33
|
-
|
|
34
|
-
log = logging.getLogger(__name__)
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
class UnknownMD5FormatError(ClickException):
|
|
38
|
-
def __init__(self, md5: str):
|
|
39
|
-
super().__init__(f"Unknown md5 format: {md5}")
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
def is_md5sum(checksum: str) -> bool:
|
|
43
|
-
"""
|
|
44
|
-
Could the provided hexadecimal checksum represent a valid md5sum?
|
|
45
|
-
"""
|
|
46
|
-
return re.match(MD5SUM_REGEX, checksum) is not None
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
def parse_multipart_md5sum(checksum: str) -> Tuple[str, int] | None:
|
|
50
|
-
"""
|
|
51
|
-
Does this represent a multi-part md5sum (i.e. "<md5>-<n>")?
|
|
52
|
-
If so, returns the tuple (md5, n), otherwise None.
|
|
53
|
-
"""
|
|
54
|
-
multipart_md5 = re.match(MULTIPART_MD5SUM_REGEX, checksum)
|
|
55
|
-
if multipart_md5:
|
|
56
|
-
return (multipart_md5.group(1), int(multipart_md5.group(2)))
|
|
57
|
-
return None
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
def compute_md5sum(file: Path, chunk_size: int | None = None) -> str:
|
|
61
|
-
"""
|
|
62
|
-
Returns a hexadecimal checksum for the file located at the given path.
|
|
63
|
-
If chunk_size is given, computes a multi-part md5sum.
|
|
64
|
-
"""
|
|
65
|
-
if not file.is_file():
|
|
66
|
-
raise ValueError(
|
|
67
|
-
"The provided file does not exist or not a (symlink to a) regular file"
|
|
68
|
-
)
|
|
69
|
-
|
|
70
|
-
# If the stage uses SNOWFLAKE_FULL encryption, this will fail to provide
|
|
71
|
-
# a matching md5sum, even when the underlying file is the same, as we do
|
|
72
|
-
# not have access to the encrypted file under checksum.
|
|
73
|
-
|
|
74
|
-
file_size = os.path.getsize(file)
|
|
75
|
-
if file_size == 0:
|
|
76
|
-
# simple md5 with no content
|
|
77
|
-
return hashlib.md5().hexdigest()
|
|
78
|
-
|
|
79
|
-
with SecurePath(file).open("rb", read_file_limit_mb=UNLIMITED) as f:
|
|
80
|
-
md5s: List[hashlib._Hash] = [] # noqa: SLF001
|
|
81
|
-
hasher = hashlib.md5()
|
|
82
|
-
|
|
83
|
-
remains = file_size
|
|
84
|
-
remains_in_chunk: int = min(chunk_size, remains) if chunk_size else remains
|
|
85
|
-
while remains > 0:
|
|
86
|
-
sz = min(READ_BUFFER_BYTES, remains_in_chunk)
|
|
87
|
-
buf = f.read(sz)
|
|
88
|
-
hasher.update(buf)
|
|
89
|
-
remains_in_chunk -= sz
|
|
90
|
-
remains -= sz
|
|
91
|
-
if remains_in_chunk == 0:
|
|
92
|
-
if not chunk_size:
|
|
93
|
-
# simple md5; only one chunk processed
|
|
94
|
-
return hasher.hexdigest()
|
|
95
|
-
else:
|
|
96
|
-
# push the hash of this chunk + reset
|
|
97
|
-
md5s.append(hasher)
|
|
98
|
-
hasher = hashlib.md5()
|
|
99
|
-
remains_in_chunk = min(chunk_size, remains)
|
|
100
|
-
|
|
101
|
-
# multi-part hash (e.g. aws)
|
|
102
|
-
digests = b"".join(m.digest() for m in md5s)
|
|
103
|
-
digests_md5 = hashlib.md5(digests)
|
|
104
|
-
return f"{digests_md5.hexdigest()}-{len(md5s)}"
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
def file_matches_md5sum(local_file: Path, remote_md5: str | None) -> bool:
|
|
108
|
-
"""
|
|
109
|
-
Try a few different md5sums to determine if a local file is identical
|
|
110
|
-
to a file that has a given remote md5sum.
|
|
111
|
-
|
|
112
|
-
Handles the multi-part md5sums generated by e.g. AWS S3, using values
|
|
113
|
-
from the python connector to make educated guesses on chunk size.
|
|
114
|
-
|
|
115
|
-
Assumes that upload time would dominate local hashing time.
|
|
116
|
-
"""
|
|
117
|
-
if not remote_md5:
|
|
118
|
-
# no hash available
|
|
119
|
-
return False
|
|
120
|
-
|
|
121
|
-
if is_md5sum(remote_md5):
|
|
122
|
-
# regular hash
|
|
123
|
-
return compute_md5sum(local_file) == remote_md5
|
|
124
|
-
|
|
125
|
-
if md5_and_chunks := parse_multipart_md5sum(remote_md5):
|
|
126
|
-
# multi-part hash (e.g. aws)
|
|
127
|
-
(_, num_chunks) = md5_and_chunks
|
|
128
|
-
file_size = os.path.getsize(local_file)
|
|
129
|
-
|
|
130
|
-
# If this file uses the maximum number of parts supported by the cloud backend,
|
|
131
|
-
# the chunk size is likely not a clean multiple of a megabyte. Try reverse engineering
|
|
132
|
-
# from the file size first, then fall back to the usual detection method.
|
|
133
|
-
# At time of writing this logic would trigger for files >= 80GiB (python connector)
|
|
134
|
-
if num_chunks == S3_MAX_PARTS:
|
|
135
|
-
chunk_size = max(math.ceil(file_size / S3_MAX_PARTS), S3_MIN_PART_SIZE)
|
|
136
|
-
if compute_md5sum(local_file, chunk_size) == remote_md5:
|
|
137
|
-
return True
|
|
138
|
-
|
|
139
|
-
# Estimates the chunk size the multi-part file must have been uploaded with
|
|
140
|
-
# by trying chunk sizes that give the most evenly-sized chunks.
|
|
141
|
-
#
|
|
142
|
-
# First we'll try the chunk size that's a multiple of S3_CHUNK_SIZE (8mb) from
|
|
143
|
-
# the python connector that results in num_chunks, then we'll do the same with
|
|
144
|
-
# a smaller granularity (1mb) that is used by default in some AWS multi-part
|
|
145
|
-
# upload implementations.
|
|
146
|
-
#
|
|
147
|
-
# We're working backwards from num_chunks here because it's the only value we know.
|
|
148
|
-
for chunk_size_alignment in [S3_CHUNK_SIZE, ONE_MEGABYTE]:
|
|
149
|
-
# +1 because we need at least one chunk when file_size < num_chunks * chunk_size_alignment
|
|
150
|
-
# -1 because we don't want to add an extra chunk when file_size is an exact multiple of num_chunks * chunk_size_alignment
|
|
151
|
-
multiplier = 1 + ((file_size - 1) // (num_chunks * chunk_size_alignment))
|
|
152
|
-
chunk_size = multiplier * chunk_size_alignment
|
|
153
|
-
if compute_md5sum(local_file, chunk_size) == remote_md5:
|
|
154
|
-
return True
|
|
155
|
-
|
|
156
|
-
# we were unable to figure out the chunk size, or the files are different
|
|
157
|
-
log.debug("multi-part md5: %s != %s", remote_md5, local_file)
|
|
158
|
-
return False
|
|
159
|
-
|
|
160
|
-
raise UnknownMD5FormatError(remote_md5)
|
|
@@ -1,30 +0,0 @@
|
|
|
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 snowflake.cli._plugins.stage import commands
|
|
16
|
-
from snowflake.cli.api.plugins.command import (
|
|
17
|
-
SNOWCLI_ROOT_COMMAND_PATH,
|
|
18
|
-
CommandSpec,
|
|
19
|
-
CommandType,
|
|
20
|
-
plugin_hook_impl,
|
|
21
|
-
)
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
@plugin_hook_impl
|
|
25
|
-
def command_spec():
|
|
26
|
-
return CommandSpec(
|
|
27
|
-
parent_command_path=SNOWCLI_ROOT_COMMAND_PATH,
|
|
28
|
-
command_type=CommandType.COMMAND_GROUP,
|
|
29
|
-
typer_instance=commands.app.create_instance(),
|
|
30
|
-
)
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
from typing import Optional
|
|
2
|
-
|
|
3
|
-
from snowflake.cli._plugins.nativeapp.artifacts import BundleMap
|
|
4
|
-
from snowflake.cli._plugins.stage.diff import (
|
|
5
|
-
DiffResult,
|
|
6
|
-
_to_diff_line,
|
|
7
|
-
_to_src_dest_pair,
|
|
8
|
-
)
|
|
9
|
-
from snowflake.cli.api.console import cli_console as cc
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
def print_diff_to_console(
|
|
13
|
-
diff: DiffResult,
|
|
14
|
-
bundle_map: Optional[BundleMap] = None,
|
|
15
|
-
):
|
|
16
|
-
if not diff.has_changes():
|
|
17
|
-
cc.message("Your stage is up-to-date with your local deploy root.")
|
|
18
|
-
return
|
|
19
|
-
|
|
20
|
-
blank_line_needed = False
|
|
21
|
-
if diff.only_local or diff.different:
|
|
22
|
-
cc.message("Local changes to be deployed:")
|
|
23
|
-
messages_to_output = []
|
|
24
|
-
for p in diff.different:
|
|
25
|
-
src_dest_pair = _to_src_dest_pair(p, bundle_map)
|
|
26
|
-
messages_to_output.append(
|
|
27
|
-
(
|
|
28
|
-
src_dest_pair,
|
|
29
|
-
_to_diff_line("modified", src_dest_pair[0], src_dest_pair[1]),
|
|
30
|
-
)
|
|
31
|
-
)
|
|
32
|
-
for p in diff.only_local:
|
|
33
|
-
src_dest_pair = _to_src_dest_pair(p, bundle_map)
|
|
34
|
-
messages_to_output.append(
|
|
35
|
-
(
|
|
36
|
-
src_dest_pair,
|
|
37
|
-
_to_diff_line("added", src_dest_pair[0], src_dest_pair[1]),
|
|
38
|
-
)
|
|
39
|
-
)
|
|
40
|
-
|
|
41
|
-
with cc.indented():
|
|
42
|
-
for key, message in sorted(messages_to_output, key=lambda pair: pair[0]):
|
|
43
|
-
cc.message(message)
|
|
44
|
-
|
|
45
|
-
blank_line_needed = True
|
|
46
|
-
|
|
47
|
-
if diff.only_on_stage:
|
|
48
|
-
if blank_line_needed:
|
|
49
|
-
cc.message("")
|
|
50
|
-
cc.message(f"Deleted paths to be removed from your stage:")
|
|
51
|
-
with cc.indented():
|
|
52
|
-
for p in sorted(diff.only_on_stage):
|
|
53
|
-
diff_line = _to_diff_line("deleted", src=None, dest=str(p))
|
|
54
|
-
cc.message(diff_line)
|
|
@@ -1,13 +0,0 @@
|
|
|
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.
|
|
@@ -1,195 +0,0 @@
|
|
|
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 logging
|
|
18
|
-
from pathlib import Path
|
|
19
|
-
from typing import Dict
|
|
20
|
-
|
|
21
|
-
import click
|
|
22
|
-
import typer
|
|
23
|
-
from click import ClickException, UsageError
|
|
24
|
-
from snowflake.cli._plugins.object.command_aliases import (
|
|
25
|
-
add_object_command_aliases,
|
|
26
|
-
scope_option,
|
|
27
|
-
)
|
|
28
|
-
from snowflake.cli._plugins.streamlit.manager import StreamlitManager
|
|
29
|
-
from snowflake.cli._plugins.streamlit.streamlit_entity_model import (
|
|
30
|
-
StreamlitEntityModel,
|
|
31
|
-
)
|
|
32
|
-
from snowflake.cli.api.cli_global_context import get_cli_context
|
|
33
|
-
from snowflake.cli.api.commands.decorators import (
|
|
34
|
-
with_experimental_behaviour,
|
|
35
|
-
with_project_definition,
|
|
36
|
-
)
|
|
37
|
-
from snowflake.cli.api.commands.flags import (
|
|
38
|
-
ReplaceOption,
|
|
39
|
-
entity_argument,
|
|
40
|
-
identifier_argument,
|
|
41
|
-
like_option,
|
|
42
|
-
)
|
|
43
|
-
from snowflake.cli.api.commands.snow_typer import SnowTyperFactory
|
|
44
|
-
from snowflake.cli.api.constants import ObjectType
|
|
45
|
-
from snowflake.cli.api.exceptions import NoProjectDefinitionError
|
|
46
|
-
from snowflake.cli.api.identifiers import FQN
|
|
47
|
-
from snowflake.cli.api.output.types import (
|
|
48
|
-
CommandResult,
|
|
49
|
-
MessageResult,
|
|
50
|
-
SingleQueryResult,
|
|
51
|
-
)
|
|
52
|
-
from snowflake.cli.api.project.definition_conversion import (
|
|
53
|
-
convert_project_definition_to_v2,
|
|
54
|
-
)
|
|
55
|
-
|
|
56
|
-
app = SnowTyperFactory(
|
|
57
|
-
name="streamlit",
|
|
58
|
-
help="Manages a Streamlit app in Snowflake.",
|
|
59
|
-
)
|
|
60
|
-
log = logging.getLogger(__name__)
|
|
61
|
-
|
|
62
|
-
StreamlitNameArgument = identifier_argument(
|
|
63
|
-
sf_object="Streamlit app", example="my_streamlit"
|
|
64
|
-
)
|
|
65
|
-
OpenOption = typer.Option(
|
|
66
|
-
False,
|
|
67
|
-
"--open",
|
|
68
|
-
help="Whether to open the Streamlit app in a browser.",
|
|
69
|
-
is_flag=True,
|
|
70
|
-
)
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
add_object_command_aliases(
|
|
74
|
-
app=app,
|
|
75
|
-
object_type=ObjectType.STREAMLIT,
|
|
76
|
-
name_argument=StreamlitNameArgument,
|
|
77
|
-
like_option=like_option(
|
|
78
|
-
help_example='`list --like "my%"` lists all streamlit apps that begin with “my”'
|
|
79
|
-
),
|
|
80
|
-
scope_option=scope_option(help_example="`list --in database my_db`"),
|
|
81
|
-
)
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
@app.command(requires_connection=True)
|
|
85
|
-
def execute(
|
|
86
|
-
name: FQN = StreamlitNameArgument,
|
|
87
|
-
**options,
|
|
88
|
-
):
|
|
89
|
-
"""
|
|
90
|
-
Executes a streamlit in a headless mode.
|
|
91
|
-
"""
|
|
92
|
-
_ = StreamlitManager().execute(app_name=name)
|
|
93
|
-
return MessageResult(f"Streamlit {name} executed.")
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
@app.command("share", requires_connection=True)
|
|
97
|
-
def streamlit_share(
|
|
98
|
-
name: FQN = StreamlitNameArgument,
|
|
99
|
-
to_role: str = typer.Argument(
|
|
100
|
-
...,
|
|
101
|
-
help="Role with which to share the Streamlit app.",
|
|
102
|
-
show_default=False,
|
|
103
|
-
),
|
|
104
|
-
**options,
|
|
105
|
-
) -> CommandResult:
|
|
106
|
-
"""
|
|
107
|
-
Shares a Streamlit app with another role.
|
|
108
|
-
"""
|
|
109
|
-
cursor = StreamlitManager().share(streamlit_name=name, to_role=to_role)
|
|
110
|
-
return SingleQueryResult(cursor)
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
def _default_file_callback(param_name: str):
|
|
114
|
-
from click.core import ParameterSource # type: ignore
|
|
115
|
-
|
|
116
|
-
def _check_file_exists_if_not_default(ctx: click.Context, value):
|
|
117
|
-
if (
|
|
118
|
-
ctx.get_parameter_source(param_name) != ParameterSource.DEFAULT # type: ignore
|
|
119
|
-
and value
|
|
120
|
-
and not Path(value).exists()
|
|
121
|
-
):
|
|
122
|
-
raise ClickException(f"Provided file {value} does not exist")
|
|
123
|
-
return Path(value)
|
|
124
|
-
|
|
125
|
-
return _check_file_exists_if_not_default
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
@app.command("deploy", requires_connection=True)
|
|
129
|
-
@with_project_definition()
|
|
130
|
-
@with_experimental_behaviour()
|
|
131
|
-
def streamlit_deploy(
|
|
132
|
-
replace: bool = ReplaceOption(
|
|
133
|
-
help="Replace the Streamlit app if it already exists."
|
|
134
|
-
),
|
|
135
|
-
entity_id: str = entity_argument("streamlit"),
|
|
136
|
-
open_: bool = OpenOption,
|
|
137
|
-
**options,
|
|
138
|
-
) -> CommandResult:
|
|
139
|
-
"""
|
|
140
|
-
Deploys a Streamlit app defined in the project definition file (snowflake.yml). By default, the command uploads
|
|
141
|
-
environment.yml and any other pages or folders, if present. If you don’t specify a stage name, the `streamlit`
|
|
142
|
-
stage is used. If the specified stage does not exist, the command creates it. If multiple Streamlits are defined
|
|
143
|
-
in snowflake.yml and no entity_id is provided then command will raise an error.
|
|
144
|
-
"""
|
|
145
|
-
|
|
146
|
-
cli_context = get_cli_context()
|
|
147
|
-
pd = cli_context.project_definition
|
|
148
|
-
if not pd.meets_version_requirement("2"):
|
|
149
|
-
if not pd.streamlit:
|
|
150
|
-
raise NoProjectDefinitionError(
|
|
151
|
-
project_type="streamlit", project_root=cli_context.project_root
|
|
152
|
-
)
|
|
153
|
-
pd = convert_project_definition_to_v2(cli_context.project_root, pd)
|
|
154
|
-
|
|
155
|
-
streamlits: Dict[str, StreamlitEntityModel] = pd.get_entities_by_type(
|
|
156
|
-
entity_type="streamlit"
|
|
157
|
-
)
|
|
158
|
-
|
|
159
|
-
if not streamlits:
|
|
160
|
-
raise NoProjectDefinitionError(
|
|
161
|
-
project_type="streamlit", project_root=cli_context.project_root
|
|
162
|
-
)
|
|
163
|
-
|
|
164
|
-
if entity_id and entity_id not in streamlits:
|
|
165
|
-
raise UsageError(f"No '{entity_id}' entity in project definition file.")
|
|
166
|
-
|
|
167
|
-
if len(streamlits.keys()) == 1:
|
|
168
|
-
entity_id = list(streamlits.keys())[0]
|
|
169
|
-
|
|
170
|
-
if entity_id is None:
|
|
171
|
-
raise UsageError(
|
|
172
|
-
"Multiple Streamlit apps found. Please provide entity id for the operation."
|
|
173
|
-
)
|
|
174
|
-
|
|
175
|
-
# Get first streamlit
|
|
176
|
-
streamlit: StreamlitEntityModel = streamlits[entity_id]
|
|
177
|
-
url = StreamlitManager().deploy(streamlit=streamlit, replace=replace)
|
|
178
|
-
|
|
179
|
-
if open_:
|
|
180
|
-
typer.launch(url)
|
|
181
|
-
|
|
182
|
-
return MessageResult(f"Streamlit successfully deployed and available under {url}")
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
@app.command("get-url", requires_connection=True)
|
|
186
|
-
def get_url(
|
|
187
|
-
name: FQN = StreamlitNameArgument,
|
|
188
|
-
open_: bool = OpenOption,
|
|
189
|
-
**options,
|
|
190
|
-
):
|
|
191
|
-
"""Returns a URL to the specified Streamlit app"""
|
|
192
|
-
url = StreamlitManager().get_url(streamlit_name=name)
|
|
193
|
-
if open_:
|
|
194
|
-
typer.launch(url)
|
|
195
|
-
return MessageResult(url)
|
|
@@ -1,220 +0,0 @@
|
|
|
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 logging
|
|
18
|
-
from pathlib import Path
|
|
19
|
-
from typing import List, Optional
|
|
20
|
-
|
|
21
|
-
from click import ClickException
|
|
22
|
-
from snowflake.cli._plugins.connection.util import (
|
|
23
|
-
MissingConnectionAccountError,
|
|
24
|
-
MissingConnectionRegionError,
|
|
25
|
-
make_snowsight_url,
|
|
26
|
-
)
|
|
27
|
-
from snowflake.cli._plugins.object.manager import ObjectManager
|
|
28
|
-
from snowflake.cli._plugins.stage.manager import StageManager
|
|
29
|
-
from snowflake.cli._plugins.streamlit.streamlit_entity_model import (
|
|
30
|
-
StreamlitEntityModel,
|
|
31
|
-
)
|
|
32
|
-
from snowflake.cli.api.commands.experimental_behaviour import (
|
|
33
|
-
experimental_behaviour_enabled,
|
|
34
|
-
)
|
|
35
|
-
from snowflake.cli.api.console import cli_console
|
|
36
|
-
from snowflake.cli.api.feature_flags import FeatureFlag
|
|
37
|
-
from snowflake.cli.api.identifiers import FQN
|
|
38
|
-
from snowflake.cli.api.sql_execution import SqlExecutionMixin
|
|
39
|
-
from snowflake.connector.cursor import SnowflakeCursor
|
|
40
|
-
from snowflake.connector.errors import ProgrammingError
|
|
41
|
-
|
|
42
|
-
log = logging.getLogger(__name__)
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
class StreamlitManager(SqlExecutionMixin):
|
|
46
|
-
def execute(self, app_name: FQN):
|
|
47
|
-
query = f"EXECUTE STREAMLIT {app_name.sql_identifier}()"
|
|
48
|
-
return self._execute_query(query=query)
|
|
49
|
-
|
|
50
|
-
def share(self, streamlit_name: FQN, to_role: str) -> SnowflakeCursor:
|
|
51
|
-
return self._execute_query(
|
|
52
|
-
f"grant usage on streamlit {streamlit_name.sql_identifier} to role {to_role}"
|
|
53
|
-
)
|
|
54
|
-
|
|
55
|
-
def _put_streamlit_files(
|
|
56
|
-
self,
|
|
57
|
-
root_location: str,
|
|
58
|
-
artifacts: Optional[List[Path]] = None,
|
|
59
|
-
):
|
|
60
|
-
cli_console.step(f"Deploying files to {root_location}")
|
|
61
|
-
if not artifacts:
|
|
62
|
-
return
|
|
63
|
-
stage_manager = StageManager()
|
|
64
|
-
for file in artifacts:
|
|
65
|
-
if file.is_dir():
|
|
66
|
-
if not any(file.iterdir()):
|
|
67
|
-
cli_console.warning(f"Skipping empty directory: {file}")
|
|
68
|
-
continue
|
|
69
|
-
|
|
70
|
-
stage_manager.put(
|
|
71
|
-
f"{file.joinpath('*')}", f"{root_location}/{file}", 4, True
|
|
72
|
-
)
|
|
73
|
-
elif len(file.parts) > 1:
|
|
74
|
-
stage_manager.put(file, f"{root_location}/{file.parent}", 4, True)
|
|
75
|
-
else:
|
|
76
|
-
stage_manager.put(file, root_location, 4, True)
|
|
77
|
-
|
|
78
|
-
def _create_streamlit(
|
|
79
|
-
self,
|
|
80
|
-
streamlit: StreamlitEntityModel,
|
|
81
|
-
replace: Optional[bool] = None,
|
|
82
|
-
experimental: Optional[bool] = None,
|
|
83
|
-
from_stage_name: Optional[str] = None,
|
|
84
|
-
):
|
|
85
|
-
streamlit_id = streamlit.fqn.using_connection(self._conn)
|
|
86
|
-
cli_console.step(f"Creating {streamlit_id} Streamlit")
|
|
87
|
-
query = []
|
|
88
|
-
if replace:
|
|
89
|
-
query.append(f"CREATE OR REPLACE STREAMLIT {streamlit_id.sql_identifier}")
|
|
90
|
-
elif experimental:
|
|
91
|
-
# For experimental behaviour, we need to use CREATE STREAMLIT IF NOT EXISTS
|
|
92
|
-
# for a streamlit app with an embedded stage
|
|
93
|
-
# because this is analogous to the behavior for non-experimental
|
|
94
|
-
# deploy which does CREATE STAGE IF NOT EXISTS
|
|
95
|
-
query.append(
|
|
96
|
-
f"CREATE STREAMLIT IF NOT EXISTS {streamlit_id.sql_identifier}"
|
|
97
|
-
)
|
|
98
|
-
else:
|
|
99
|
-
query.append(f"CREATE STREAMLIT {streamlit_id.sql_identifier}")
|
|
100
|
-
|
|
101
|
-
if from_stage_name:
|
|
102
|
-
query.append(f"ROOT_LOCATION = '{from_stage_name}'")
|
|
103
|
-
|
|
104
|
-
query.append(f"MAIN_FILE = '{streamlit.main_file}'")
|
|
105
|
-
if streamlit.imports:
|
|
106
|
-
query.append(streamlit.get_imports_sql())
|
|
107
|
-
if streamlit.query_warehouse:
|
|
108
|
-
query.append(f"QUERY_WAREHOUSE = {streamlit.query_warehouse}")
|
|
109
|
-
if streamlit.title:
|
|
110
|
-
query.append(f"TITLE = '{streamlit.title}'")
|
|
111
|
-
|
|
112
|
-
if streamlit.comment:
|
|
113
|
-
query.append(f"COMMENT = '{streamlit.comment}'")
|
|
114
|
-
|
|
115
|
-
if streamlit.external_access_integrations:
|
|
116
|
-
query.append(streamlit.get_external_access_integrations_sql())
|
|
117
|
-
|
|
118
|
-
if streamlit.secrets:
|
|
119
|
-
query.append(streamlit.get_secrets_sql())
|
|
120
|
-
|
|
121
|
-
self._execute_query("\n".join(query))
|
|
122
|
-
|
|
123
|
-
def deploy(self, streamlit: StreamlitEntityModel, replace: bool = False):
|
|
124
|
-
streamlit_id = streamlit.fqn.using_connection(self._conn)
|
|
125
|
-
if (
|
|
126
|
-
ObjectManager().object_exists(object_type="streamlit", fqn=streamlit_id)
|
|
127
|
-
and not replace
|
|
128
|
-
):
|
|
129
|
-
raise ClickException(
|
|
130
|
-
f"Streamlit {streamlit.fqn} already exist. If you want to replace it use --replace flag."
|
|
131
|
-
)
|
|
132
|
-
|
|
133
|
-
# for backwards compatibility - quoted stage path might be case-sensitive
|
|
134
|
-
# https://docs.snowflake.com/en/sql-reference/identifiers-syntax#double-quoted-identifiers
|
|
135
|
-
streamlit_name_for_root_location = streamlit_id.name
|
|
136
|
-
use_versioned_stage = FeatureFlag.ENABLE_STREAMLIT_VERSIONED_STAGE.is_enabled()
|
|
137
|
-
if (
|
|
138
|
-
experimental_behaviour_enabled()
|
|
139
|
-
or FeatureFlag.ENABLE_STREAMLIT_EMBEDDED_STAGE.is_enabled()
|
|
140
|
-
or use_versioned_stage
|
|
141
|
-
):
|
|
142
|
-
"""
|
|
143
|
-
1. Create streamlit object
|
|
144
|
-
2. Upload files to embedded stage
|
|
145
|
-
"""
|
|
146
|
-
# TODO: Support from_stage
|
|
147
|
-
# from_stage_stmt = f"FROM_STAGE = '{stage_name}'" if stage_name else ""
|
|
148
|
-
self._create_streamlit(
|
|
149
|
-
streamlit=streamlit,
|
|
150
|
-
replace=replace,
|
|
151
|
-
experimental=True,
|
|
152
|
-
)
|
|
153
|
-
try:
|
|
154
|
-
if use_versioned_stage:
|
|
155
|
-
self._execute_query(
|
|
156
|
-
f"ALTER STREAMLIT {streamlit_id.identifier} ADD LIVE VERSION FROM LAST"
|
|
157
|
-
)
|
|
158
|
-
elif not FeatureFlag.ENABLE_STREAMLIT_NO_CHECKOUTS.is_enabled():
|
|
159
|
-
self._execute_query(
|
|
160
|
-
f"ALTER streamlit {streamlit_id.identifier} CHECKOUT"
|
|
161
|
-
)
|
|
162
|
-
except ProgrammingError as e:
|
|
163
|
-
# If an error is raised because a CHECKOUT has already occurred or a LIVE VERSION already exists, simply skip it and continue
|
|
164
|
-
if "Checkout already exists" in str(
|
|
165
|
-
e
|
|
166
|
-
) or "There is already a live version" in str(e):
|
|
167
|
-
log.info("Checkout already exists, continuing")
|
|
168
|
-
else:
|
|
169
|
-
raise
|
|
170
|
-
|
|
171
|
-
stage_path = streamlit_id.identifier
|
|
172
|
-
embedded_stage_name = f"snow://streamlit/{stage_path}"
|
|
173
|
-
if use_versioned_stage:
|
|
174
|
-
# "LIVE" is the only supported version for now, but this may change later.
|
|
175
|
-
root_location = f"{embedded_stage_name}/versions/live"
|
|
176
|
-
else:
|
|
177
|
-
root_location = f"{embedded_stage_name}/default_checkout"
|
|
178
|
-
|
|
179
|
-
self._put_streamlit_files(
|
|
180
|
-
root_location,
|
|
181
|
-
streamlit.artifacts,
|
|
182
|
-
)
|
|
183
|
-
else:
|
|
184
|
-
"""
|
|
185
|
-
1. Create stage
|
|
186
|
-
2. Upload files to created stage
|
|
187
|
-
3. Create streamlit from stage
|
|
188
|
-
"""
|
|
189
|
-
stage_manager = StageManager()
|
|
190
|
-
|
|
191
|
-
stage_name = streamlit.stage or "streamlit"
|
|
192
|
-
stage_name = FQN.from_string(stage_name).using_connection(self._conn)
|
|
193
|
-
|
|
194
|
-
cli_console.step(f"Creating {stage_name} stage")
|
|
195
|
-
stage_manager.create(fqn=stage_name)
|
|
196
|
-
|
|
197
|
-
root_location = stage_manager.get_standard_stage_prefix(
|
|
198
|
-
f"{stage_name}/{streamlit_name_for_root_location}"
|
|
199
|
-
)
|
|
200
|
-
|
|
201
|
-
self._put_streamlit_files(root_location, streamlit.artifacts)
|
|
202
|
-
|
|
203
|
-
self._create_streamlit(
|
|
204
|
-
streamlit=streamlit,
|
|
205
|
-
replace=replace,
|
|
206
|
-
from_stage_name=root_location,
|
|
207
|
-
experimental=False,
|
|
208
|
-
)
|
|
209
|
-
|
|
210
|
-
return self.get_url(streamlit_name=streamlit_id)
|
|
211
|
-
|
|
212
|
-
def get_url(self, streamlit_name: FQN) -> str:
|
|
213
|
-
try:
|
|
214
|
-
fqn = streamlit_name.using_connection(self._conn)
|
|
215
|
-
return make_snowsight_url(
|
|
216
|
-
self._conn,
|
|
217
|
-
f"/#/streamlit-apps/{fqn.url_identifier}",
|
|
218
|
-
)
|
|
219
|
-
except (MissingConnectionRegionError, MissingConnectionAccountError) as e:
|
|
220
|
-
return "https://app.snowflake.com"
|