snowflake-cli-labs 2.8.0rc1__py3-none-any.whl → 3.0.0rc1__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 → _app}/__main__.py +1 -1
- snowflake/cli/{app → _app}/cli_app.py +12 -12
- snowflake/cli/{app → _app}/commands_registration/builtin_plugins.py +13 -19
- snowflake/cli/{app → _app}/commands_registration/command_plugins_loader.py +9 -9
- snowflake/cli/{app → _app}/commands_registration/commands_registration_with_callbacks.py +4 -4
- snowflake/cli/{app → _app}/commands_registration/exception_logging.py +2 -2
- snowflake/cli/{app → _app}/commands_registration/typer_registration.py +2 -2
- snowflake/cli/{app → _app}/dev/docs/commands_docs_generator.py +30 -12
- snowflake/cli/{app → _app}/dev/docs/generator.py +3 -3
- snowflake/cli/{app → _app}/dev/docs/project_definition_docs_generator.py +4 -4
- snowflake/cli/{app → _app}/dev/docs/templates/usage.rst.jinja2 +14 -4
- snowflake/cli/{app → _app}/main_typer.py +2 -2
- snowflake/cli/{app → _app}/printing.py +2 -2
- snowflake/cli/{app → _app}/snow_connector.py +24 -17
- snowflake/cli/{app → _app}/telemetry.py +4 -5
- snowflake/cli/{plugins → _plugins}/connection/commands.py +25 -7
- snowflake/cli/_plugins/connection/plugin_spec.py +30 -0
- snowflake/cli/{plugins → _plugins}/connection/util.py +16 -0
- snowflake/cli/{plugins → _plugins}/cortex/commands.py +54 -49
- snowflake/cli/{plugins → _plugins}/cortex/constants.py +1 -1
- snowflake/cli/{plugins → _plugins}/cortex/manager.py +5 -5
- snowflake/cli/{plugins → _plugins}/cortex/plugin_spec.py +1 -1
- snowflake/cli/{plugins → _plugins}/git/commands.py +32 -20
- snowflake/cli/{plugins → _plugins}/git/manager.py +20 -11
- snowflake/cli/{plugins → _plugins}/git/plugin_spec.py +1 -1
- snowflake/cli/{plugins → _plugins}/init/commands.py +10 -6
- snowflake/cli/{plugins → _plugins}/init/plugin_spec.py +1 -1
- snowflake/cli/{plugins → _plugins}/nativeapp/artifacts.py +14 -0
- snowflake/cli/_plugins/nativeapp/bundle_context.py +31 -0
- snowflake/cli/{plugins → _plugins}/nativeapp/codegen/artifact_processor.py +3 -3
- snowflake/cli/{plugins → _plugins}/nativeapp/codegen/compiler.py +32 -18
- snowflake/cli/_plugins/nativeapp/codegen/setup/native_app_setup_processor.py +249 -0
- snowflake/cli/{plugins → _plugins}/nativeapp/codegen/setup/setup_driver.py.source +5 -2
- snowflake/cli/{plugins → _plugins}/nativeapp/codegen/snowpark/extension_function_utils.py +4 -4
- snowflake/cli/{plugins → _plugins}/nativeapp/codegen/snowpark/python_processor.py +23 -29
- snowflake/cli/_plugins/nativeapp/codegen/templates/templates_processor.py +93 -0
- snowflake/cli/{plugins → _plugins}/nativeapp/commands.py +171 -42
- snowflake/cli/{plugins → _plugins}/nativeapp/common_flags.py +1 -1
- snowflake/cli/{plugins → _plugins}/nativeapp/exceptions.py +3 -3
- snowflake/cli/{plugins → _plugins}/nativeapp/init.py +1 -1
- snowflake/cli/_plugins/nativeapp/manager.py +572 -0
- snowflake/cli/{plugins/connection → _plugins/nativeapp}/plugin_spec.py +1 -1
- snowflake/cli/{plugins → _plugins}/nativeapp/project_model.py +35 -19
- snowflake/cli/{plugins → _plugins}/nativeapp/run_processor.py +25 -23
- snowflake/cli/{plugins → _plugins}/nativeapp/teardown_processor.py +24 -110
- snowflake/cli/{plugins → _plugins}/nativeapp/v2_conversions/v2_to_v1_decorator.py +47 -28
- snowflake/cli/{plugins → _plugins}/nativeapp/version/commands.py +15 -12
- snowflake/cli/{plugins → _plugins}/nativeapp/version/version_processor.py +22 -20
- snowflake/cli/{plugins → _plugins}/notebook/commands.py +8 -6
- snowflake/cli/{plugins → _plugins}/notebook/manager.py +14 -14
- snowflake/cli/{plugins → _plugins}/notebook/plugin_spec.py +1 -1
- snowflake/cli/{plugins → _plugins}/notebook/types.py +0 -1
- snowflake/cli/{plugins → _plugins}/object/command_aliases.py +6 -5
- snowflake/cli/{plugins → _plugins}/object/commands.py +16 -10
- snowflake/cli/{plugins → _plugins}/object/manager.py +7 -6
- snowflake/cli/{plugins → _plugins}/object/plugin_spec.py +1 -1
- snowflake/cli/_plugins/snowpark/commands.py +450 -0
- snowflake/cli/_plugins/snowpark/common.py +268 -0
- snowflake/cli/{plugins → _plugins}/snowpark/models.py +0 -7
- snowflake/cli/{plugins → _plugins}/snowpark/package/anaconda_packages.py +2 -36
- snowflake/cli/{plugins → _plugins}/snowpark/package/commands.py +13 -74
- snowflake/cli/{plugins → _plugins}/snowpark/package/manager.py +4 -3
- snowflake/cli/{plugins → _plugins}/snowpark/package_utils.py +5 -5
- snowflake/cli/{plugins/nativeapp → _plugins/snowpark}/plugin_spec.py +1 -1
- snowflake/cli/_plugins/snowpark/snowpark_project_paths.py +109 -0
- snowflake/cli/{plugins → _plugins}/snowpark/snowpark_shared.py +0 -36
- snowflake/cli/{plugins → _plugins}/snowpark/zipper.py +16 -8
- snowflake/cli/{plugins → _plugins}/spcs/__init__.py +5 -7
- snowflake/cli/{plugins → _plugins}/spcs/compute_pool/commands.py +29 -28
- snowflake/cli/{plugins → _plugins}/spcs/compute_pool/manager.py +3 -3
- snowflake/cli/{plugins → _plugins}/spcs/image_registry/commands.py +3 -3
- snowflake/cli/{plugins → _plugins}/spcs/image_repository/commands.py +25 -19
- snowflake/cli/{plugins → _plugins}/spcs/image_repository/manager.py +1 -1
- snowflake/cli/{plugins → _plugins}/spcs/plugin_spec.py +1 -1
- snowflake/cli/{plugins → _plugins}/spcs/services/commands.py +66 -32
- snowflake/cli/{plugins → _plugins}/spcs/services/manager.py +43 -5
- snowflake/cli/{plugins → _plugins}/sql/commands.py +20 -17
- snowflake/cli/{plugins → _plugins}/sql/manager.py +1 -1
- snowflake/cli/{plugins → _plugins}/sql/plugin_spec.py +1 -1
- snowflake/cli/{plugins → _plugins}/stage/commands.py +20 -17
- snowflake/cli/{plugins → _plugins}/stage/diff.py +1 -47
- snowflake/cli/{plugins → _plugins}/stage/manager.py +54 -21
- snowflake/cli/{plugins → _plugins}/stage/plugin_spec.py +1 -1
- snowflake/cli/_plugins/stage/utils.py +54 -0
- snowflake/cli/{plugins → _plugins}/streamlit/commands.py +59 -62
- snowflake/cli/{plugins → _plugins}/streamlit/manager.py +51 -70
- snowflake/cli/_plugins/streamlit/plugin_spec.py +30 -0
- snowflake/cli/_plugins/workspace/action_context.py +17 -0
- snowflake/cli/_plugins/workspace/commands.py +194 -0
- snowflake/cli/_plugins/workspace/manager.py +73 -0
- snowflake/cli/{plugins → _plugins}/workspace/plugin_spec.py +1 -1
- snowflake/cli/api/cli_global_context.py +40 -13
- snowflake/cli/api/commands/common.py +25 -0
- snowflake/cli/api/commands/decorators.py +5 -4
- snowflake/cli/api/commands/experimental_behaviour.py +2 -3
- snowflake/cli/api/commands/flags.py +97 -179
- snowflake/cli/api/commands/overrideable_parameter.py +143 -0
- snowflake/cli/api/commands/snow_typer.py +14 -6
- snowflake/cli/api/commands/typer_pre_execute.py +3 -3
- snowflake/cli/api/commands/utils.py +18 -0
- snowflake/cli/api/config.py +18 -5
- snowflake/cli/api/console/abc.py +5 -2
- snowflake/cli/api/constants.py +11 -0
- snowflake/cli/api/entities/application_entity.py +12 -0
- snowflake/cli/api/entities/application_package_entity.py +553 -0
- snowflake/cli/api/entities/common.py +51 -0
- snowflake/cli/api/entities/snowpark_entity.py +29 -0
- snowflake/cli/api/entities/streamlit_entity.py +12 -0
- snowflake/cli/api/entities/utils.py +357 -0
- snowflake/cli/api/exceptions.py +31 -5
- snowflake/cli/api/feature_flags.py +0 -1
- snowflake/cli/api/identifiers.py +41 -9
- snowflake/cli/api/project/definition.py +37 -6
- snowflake/cli/api/project/definition_conversion.py +194 -0
- snowflake/cli/api/project/definition_manager.py +12 -1
- snowflake/cli/api/project/project_verification.py +3 -3
- snowflake/cli/api/project/schemas/entities/{application_entity.py → application_entity_model.py} +21 -9
- snowflake/cli/api/project/schemas/entities/{application_package_entity.py → application_package_entity_model.py} +43 -15
- snowflake/cli/api/project/schemas/entities/common.py +80 -6
- snowflake/cli/api/project/schemas/entities/entities.py +38 -8
- snowflake/cli/api/project/schemas/entities/snowpark_entity.py +176 -0
- snowflake/cli/api/project/schemas/entities/streamlit_entity_model.py +73 -0
- snowflake/cli/api/project/schemas/identifier_model.py +10 -1
- snowflake/cli/api/project/schemas/native_app/application.py +8 -9
- snowflake/cli/api/project/schemas/native_app/package.py +7 -1
- snowflake/cli/api/project/schemas/project_definition.py +98 -27
- snowflake/cli/api/project/schemas/updatable_model.py +11 -3
- snowflake/cli/api/project/util.py +23 -6
- snowflake/cli/api/rendering/jinja.py +14 -8
- snowflake/cli/api/rendering/project_definition_templates.py +1 -1
- snowflake/cli/api/rendering/sql_templates.py +43 -11
- snowflake/cli/api/secure_path.py +16 -18
- snowflake/cli/api/secure_utils.py +90 -1
- snowflake/cli/api/sql_execution.py +48 -19
- snowflake/cli/api/utils/definition_rendering.py +18 -8
- {snowflake_cli_labs-2.8.0rc1.dist-info → snowflake_cli_labs-3.0.0rc1.dist-info}/METADATA +13 -13
- snowflake_cli_labs-3.0.0rc1.dist-info/RECORD +236 -0
- snowflake_cli_labs-3.0.0rc1.dist-info/entry_points.txt +2 -0
- snowflake/cli/api/commands/project_initialisation.py +0 -65
- snowflake/cli/app/build_and_push.sh +0 -8
- snowflake/cli/plugins/nativeapp/codegen/setup/native_app_setup_processor.py +0 -172
- snowflake/cli/plugins/nativeapp/manager.py +0 -823
- snowflake/cli/plugins/object_stage_deprecated/__init__.py +0 -15
- snowflake/cli/plugins/object_stage_deprecated/commands.py +0 -122
- snowflake/cli/plugins/object_stage_deprecated/plugin_spec.py +0 -32
- snowflake/cli/plugins/snowpark/commands.py +0 -548
- snowflake/cli/plugins/snowpark/common.py +0 -307
- snowflake/cli/plugins/snowpark/manager.py +0 -109
- snowflake/cli/plugins/snowpark/plugin_spec.py +0 -30
- snowflake/cli/plugins/snowpark/snowpark_package_paths.py +0 -65
- snowflake/cli/plugins/spcs/jobs/commands.py +0 -78
- snowflake/cli/plugins/spcs/jobs/manager.py +0 -53
- snowflake/cli/plugins/streamlit/plugin_spec.py +0 -30
- snowflake/cli/plugins/workspace/commands.py +0 -35
- snowflake/cli/templates/default_snowpark/.gitignore +0 -4
- snowflake/cli/templates/default_snowpark/app/__init__.py +0 -0
- snowflake/cli/templates/default_snowpark/app/common.py +0 -2
- snowflake/cli/templates/default_snowpark/app/functions.py +0 -15
- snowflake/cli/templates/default_snowpark/app/procedures.py +0 -22
- snowflake/cli/templates/default_snowpark/requirements.txt +0 -1
- snowflake/cli/templates/default_snowpark/snowflake.yml +0 -23
- snowflake/cli/templates/default_streamlit/.gitignore +0 -4
- snowflake/cli/templates/default_streamlit/common/hello.py +0 -2
- snowflake/cli/templates/default_streamlit/environment.yml +0 -6
- snowflake/cli/templates/default_streamlit/pages/my_page.py +0 -3
- snowflake/cli/templates/default_streamlit/snowflake.yml +0 -10
- snowflake/cli/templates/default_streamlit/streamlit_app.py +0 -4
- snowflake_cli_labs-2.8.0rc1.dist-info/RECORD +0 -240
- snowflake_cli_labs-2.8.0rc1.dist-info/entry_points.txt +0 -2
- /snowflake/cli/{app → _app}/__init__.py +0 -0
- /snowflake/cli/{app → _app}/api_impl/__init__.py +0 -0
- /snowflake/cli/{app → _app}/api_impl/plugin/__init__.py +0 -0
- /snowflake/cli/{app → _app}/api_impl/plugin/plugin_config_provider_impl.py +0 -0
- /snowflake/cli/{app → _app}/commands_registration/__init__.py +0 -0
- /snowflake/cli/{app → _app}/commands_registration/threadsafe.py +0 -0
- /snowflake/cli/{app → _app}/constants.py +0 -0
- /snowflake/cli/{app → _app}/dev/__init__.py +0 -0
- /snowflake/cli/{app → _app}/dev/commands_structure.py +0 -0
- /snowflake/cli/{app → _app}/dev/docs/__init__.py +0 -0
- /snowflake/cli/{app → _app}/dev/docs/project_definition_generate_json_schema.py +0 -0
- /snowflake/cli/{app → _app}/dev/docs/template_utils.py +0 -0
- /snowflake/cli/{app → _app}/dev/docs/templates/definition_description.rst.jinja2 +0 -0
- /snowflake/cli/{app → _app}/dev/docs/templates/overview.rst.jinja2 +0 -0
- /snowflake/cli/{app → _app}/dev/pycharm_remote_debug.py +0 -0
- /snowflake/cli/{app → _app}/loggers.py +0 -0
- /snowflake/cli/{plugins → _plugins}/__init__.py +0 -0
- /snowflake/cli/{plugins → _plugins}/connection/__init__.py +0 -0
- /snowflake/cli/{plugins → _plugins}/cortex/__init__.py +0 -0
- /snowflake/cli/{plugins → _plugins}/cortex/types.py +0 -0
- /snowflake/cli/{plugins → _plugins}/git/__init__.py +0 -0
- /snowflake/cli/{plugins → _plugins}/init/__init__.py +0 -0
- /snowflake/cli/{plugins → _plugins}/nativeapp/__init__.py +0 -0
- /snowflake/cli/{plugins → _plugins}/nativeapp/codegen/__init__.py +0 -0
- /snowflake/cli/{plugins → _plugins}/nativeapp/codegen/sandbox.py +0 -0
- /snowflake/cli/{plugins → _plugins}/nativeapp/codegen/snowpark/callback_source.py.jinja +0 -0
- /snowflake/cli/{plugins → _plugins}/nativeapp/codegen/snowpark/models.py +0 -0
- /snowflake/cli/{plugins → _plugins}/nativeapp/constants.py +0 -0
- /snowflake/cli/{plugins → _plugins}/nativeapp/feature_flags.py +0 -0
- /snowflake/cli/{plugins → _plugins}/nativeapp/policy.py +0 -0
- /snowflake/cli/{plugins → _plugins}/nativeapp/utils.py +0 -0
- /snowflake/cli/{plugins → _plugins}/nativeapp/version/__init__.py +0 -0
- /snowflake/cli/{plugins → _plugins}/notebook/__init__.py +0 -0
- /snowflake/cli/{plugins → _plugins}/notebook/exceptions.py +0 -0
- /snowflake/cli/{plugins → _plugins}/object/__init__.py +0 -0
- /snowflake/cli/{plugins → _plugins}/object/common.py +0 -0
- /snowflake/cli/{plugins → _plugins}/snowpark/__init__.py +0 -0
- /snowflake/cli/{plugins → _plugins}/snowpark/package/__init__.py +0 -0
- /snowflake/cli/{plugins → _plugins}/snowpark/package/utils.py +0 -0
- /snowflake/cli/{plugins → _plugins}/spcs/common.py +0 -0
- /snowflake/cli/{plugins → _plugins}/spcs/compute_pool/__init__.py +0 -0
- /snowflake/cli/{plugins → _plugins}/spcs/image_registry/__init__.py +0 -0
- /snowflake/cli/{plugins → _plugins}/spcs/image_registry/manager.py +0 -0
- /snowflake/cli/{plugins → _plugins}/spcs/image_repository/__init__.py +0 -0
- /snowflake/cli/{plugins/spcs/jobs → _plugins/spcs/services}/__init__.py +0 -0
- /snowflake/cli/{plugins/spcs/services → _plugins/sql}/__init__.py +0 -0
- /snowflake/cli/{plugins → _plugins}/sql/snowsql_templating.py +0 -0
- /snowflake/cli/{plugins/sql → _plugins/stage}/__init__.py +0 -0
- /snowflake/cli/{plugins → _plugins}/stage/md5.py +0 -0
- /snowflake/cli/{plugins/stage → _plugins/streamlit}/__init__.py +0 -0
- /snowflake/cli/{plugins/streamlit → _plugins/workspace}/__init__.py +0 -0
- /snowflake/cli/{plugins/workspace → api/project/schemas/entities}/__init__.py +0 -0
- {snowflake_cli_labs-2.8.0rc1.dist-info → snowflake_cli_labs-3.0.0rc1.dist-info}/WHEEL +0 -0
- {snowflake_cli_labs-2.8.0rc1.dist-info → snowflake_cli_labs-3.0.0rc1.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,176 @@
|
|
|
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
|
+
from pathlib import Path
|
|
18
|
+
from typing import List, Literal, Optional, Union
|
|
19
|
+
|
|
20
|
+
from pydantic import Field, field_validator
|
|
21
|
+
from snowflake.cli.api.identifiers import FQN
|
|
22
|
+
from snowflake.cli.api.project.schemas.entities.common import (
|
|
23
|
+
EntityModelBase,
|
|
24
|
+
ExternalAccessBaseModel,
|
|
25
|
+
)
|
|
26
|
+
from snowflake.cli.api.project.schemas.snowpark.argument import Argument
|
|
27
|
+
from snowflake.cli.api.project.schemas.updatable_model import (
|
|
28
|
+
DiscriminatorField,
|
|
29
|
+
UpdatableModel,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class PathMapping(UpdatableModel):
|
|
34
|
+
class Config:
|
|
35
|
+
frozen = True
|
|
36
|
+
|
|
37
|
+
src: Path = Field(title="Source path (relative to project root)", default=None)
|
|
38
|
+
|
|
39
|
+
dest: Optional[str] = Field(
|
|
40
|
+
title="Destination path on stage",
|
|
41
|
+
description="Paths are relative to stage root; paths ending with a slash indicate that the destination is a directory which source files should be copied into.",
|
|
42
|
+
default=None,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class SnowparkEntityModel(EntityModelBase, ExternalAccessBaseModel):
|
|
47
|
+
handler: str = Field(
|
|
48
|
+
title="Function’s or procedure’s implementation of the object inside source module",
|
|
49
|
+
examples=["functions.hello_function"],
|
|
50
|
+
)
|
|
51
|
+
returns: str = Field(
|
|
52
|
+
title="Type of the result"
|
|
53
|
+
) # TODO: again, consider Literal/Enum
|
|
54
|
+
signature: Union[str, List[Argument]] = Field(
|
|
55
|
+
title="The signature parameter describes consecutive arguments passed to the object"
|
|
56
|
+
)
|
|
57
|
+
runtime: Optional[Union[str, float]] = Field(
|
|
58
|
+
title="Python version to use when executing ", default=None
|
|
59
|
+
)
|
|
60
|
+
imports: Optional[List[str]] = Field(
|
|
61
|
+
title="Stage and path to previously uploaded files you want to import",
|
|
62
|
+
default=[],
|
|
63
|
+
)
|
|
64
|
+
stage: str = Field(title="Stage in which artifacts will be stored")
|
|
65
|
+
artifacts: List[Union[PathMapping, str]] = Field(title="List of required sources")
|
|
66
|
+
|
|
67
|
+
@field_validator("artifacts")
|
|
68
|
+
@classmethod
|
|
69
|
+
def _convert_artifacts(cls, artifacts: Union[dict, str]):
|
|
70
|
+
_artifacts = []
|
|
71
|
+
for artefact in artifacts:
|
|
72
|
+
if isinstance(artefact, PathMapping):
|
|
73
|
+
_artifacts.append(artefact)
|
|
74
|
+
else:
|
|
75
|
+
_artifacts.append(PathMapping(src=artefact))
|
|
76
|
+
return _artifacts
|
|
77
|
+
|
|
78
|
+
@field_validator("runtime")
|
|
79
|
+
@classmethod
|
|
80
|
+
def convert_runtime(cls, runtime_input: Union[str, float]) -> str:
|
|
81
|
+
if isinstance(runtime_input, float):
|
|
82
|
+
return str(runtime_input)
|
|
83
|
+
return runtime_input
|
|
84
|
+
|
|
85
|
+
@field_validator("artifacts")
|
|
86
|
+
@classmethod
|
|
87
|
+
def validate_artifacts(cls, artifacts: List[Path]) -> List[Path]:
|
|
88
|
+
for artefact in artifacts:
|
|
89
|
+
if "*" in str(artefact):
|
|
90
|
+
raise ValueError("Glob patterns not supported for Snowpark artifacts.")
|
|
91
|
+
return artifacts
|
|
92
|
+
|
|
93
|
+
@property
|
|
94
|
+
def udf_sproc_identifier(self) -> UdfSprocIdentifier:
|
|
95
|
+
return UdfSprocIdentifier.from_definition(self)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class ProcedureEntityModel(SnowparkEntityModel):
|
|
99
|
+
type: Literal["procedure"] = DiscriminatorField() # noqa: A003
|
|
100
|
+
execute_as_caller: Optional[bool] = Field(
|
|
101
|
+
title="Determine whether the procedure is executed with the privileges of "
|
|
102
|
+
"the owner (you) or with the privileges of the caller",
|
|
103
|
+
default=False,
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class FunctionEntityModel(SnowparkEntityModel):
|
|
108
|
+
type: Literal["function"] = DiscriminatorField() # noqa: A003
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class UdfSprocIdentifier:
|
|
112
|
+
def __init__(self, identifier: FQN, arg_names, arg_types, arg_defaults):
|
|
113
|
+
self._identifier = identifier
|
|
114
|
+
self._arg_names = arg_names
|
|
115
|
+
self._arg_types = arg_types
|
|
116
|
+
self._arg_defaults = arg_defaults
|
|
117
|
+
|
|
118
|
+
def _identifier_from_signature(self, sig: List[str], for_sql: bool = False):
|
|
119
|
+
signature = self._comma_join(sig)
|
|
120
|
+
id_ = self._identifier.sql_identifier if for_sql else self._identifier
|
|
121
|
+
return f"{id_}({signature})"
|
|
122
|
+
|
|
123
|
+
@staticmethod
|
|
124
|
+
def _comma_join(*args):
|
|
125
|
+
return ", ".join(*args)
|
|
126
|
+
|
|
127
|
+
@property
|
|
128
|
+
def identifier_with_arg_names(self):
|
|
129
|
+
return self._identifier_from_signature(self._arg_names)
|
|
130
|
+
|
|
131
|
+
@property
|
|
132
|
+
def identifier_with_arg_types(self):
|
|
133
|
+
return self._identifier_from_signature(self._arg_types)
|
|
134
|
+
|
|
135
|
+
@property
|
|
136
|
+
def identifier_with_arg_names_types(self):
|
|
137
|
+
sig = [f"{n} {t}" for n, t in zip(self._arg_names, self._arg_types)]
|
|
138
|
+
return self._identifier_from_signature(sig)
|
|
139
|
+
|
|
140
|
+
@property
|
|
141
|
+
def identifier_with_arg_names_types_defaults(self):
|
|
142
|
+
return self._identifier_from_signature(self._full_signature())
|
|
143
|
+
|
|
144
|
+
def _is_signature_type_a_string(self, sig_type: str) -> bool:
|
|
145
|
+
return sig_type.lower() in ["string", "varchar"]
|
|
146
|
+
|
|
147
|
+
def _full_signature(self):
|
|
148
|
+
sig = []
|
|
149
|
+
for name, _type, _default in zip(
|
|
150
|
+
self._arg_names, self._arg_types, self._arg_defaults
|
|
151
|
+
):
|
|
152
|
+
s = f"{name} {_type}"
|
|
153
|
+
if _default:
|
|
154
|
+
if self._is_signature_type_a_string(_type):
|
|
155
|
+
_default = f"'{_default}'"
|
|
156
|
+
s += f" default {_default}"
|
|
157
|
+
sig.append(s)
|
|
158
|
+
return sig
|
|
159
|
+
|
|
160
|
+
@property
|
|
161
|
+
def identifier_for_sql(self):
|
|
162
|
+
return self._identifier_from_signature(self._full_signature(), for_sql=True)
|
|
163
|
+
|
|
164
|
+
@classmethod
|
|
165
|
+
def from_definition(cls, udf_sproc: SnowparkEntityModel):
|
|
166
|
+
names = []
|
|
167
|
+
types = []
|
|
168
|
+
defaults = []
|
|
169
|
+
if udf_sproc.signature and udf_sproc.signature != "null":
|
|
170
|
+
for arg in udf_sproc.signature:
|
|
171
|
+
names.append(arg.name) # type:ignore
|
|
172
|
+
types.append(arg.arg_type) # type:ignore
|
|
173
|
+
defaults.append(arg.default) # type:ignore
|
|
174
|
+
|
|
175
|
+
identifier = udf_sproc.fqn.using_context()
|
|
176
|
+
return cls(identifier, names, types, defaults)
|
|
@@ -0,0 +1,73 @@
|
|
|
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
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from typing import List, Literal, Optional
|
|
18
|
+
|
|
19
|
+
from pydantic import Field, model_validator
|
|
20
|
+
from snowflake.cli.api.project.schemas.entities.common import (
|
|
21
|
+
EntityModelBase,
|
|
22
|
+
ExternalAccessBaseModel,
|
|
23
|
+
)
|
|
24
|
+
from snowflake.cli.api.project.schemas.updatable_model import (
|
|
25
|
+
DiscriminatorField,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class StreamlitEntityModel(EntityModelBase, ExternalAccessBaseModel):
|
|
30
|
+
type: Literal["streamlit"] = DiscriminatorField() # noqa: A003
|
|
31
|
+
title: Optional[str] = Field(
|
|
32
|
+
title="Human-readable title for the Streamlit dashboard", default=None
|
|
33
|
+
)
|
|
34
|
+
query_warehouse: str = Field(
|
|
35
|
+
title="Snowflake warehouse to host the app", default=None
|
|
36
|
+
)
|
|
37
|
+
main_file: Optional[str] = Field(
|
|
38
|
+
title="Entrypoint file of the Streamlit app", default="streamlit_app.py"
|
|
39
|
+
)
|
|
40
|
+
pages_dir: Optional[str] = Field(title="Streamlit pages", default=None)
|
|
41
|
+
stage: Optional[str] = Field(
|
|
42
|
+
title="Stage in which the app’s artifacts will be stored", default="streamlit"
|
|
43
|
+
)
|
|
44
|
+
# Possibly can be PathMapping
|
|
45
|
+
artifacts: Optional[List[Path]] = Field(
|
|
46
|
+
title="List of files which should be deployed. Each file needs to exist locally. "
|
|
47
|
+
"Main file needs to be included in the artifacts.",
|
|
48
|
+
default=None,
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
@model_validator(mode="after")
|
|
52
|
+
def main_file_must_be_in_artifacts(self):
|
|
53
|
+
if not self.artifacts:
|
|
54
|
+
return self
|
|
55
|
+
|
|
56
|
+
if Path(self.main_file) not in self.artifacts:
|
|
57
|
+
raise ValueError(
|
|
58
|
+
f"Specified main file {self.main_file} is not included in artifacts."
|
|
59
|
+
)
|
|
60
|
+
return self
|
|
61
|
+
|
|
62
|
+
@model_validator(mode="after")
|
|
63
|
+
def artifacts_must_exists(self):
|
|
64
|
+
if not self.artifacts:
|
|
65
|
+
return self
|
|
66
|
+
|
|
67
|
+
for artifact in self.artifacts:
|
|
68
|
+
if not artifact.exists():
|
|
69
|
+
raise ValueError(
|
|
70
|
+
f"Specified artifact {artifact} does not exist locally."
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
return self
|
|
@@ -17,7 +17,16 @@ from __future__ import annotations
|
|
|
17
17
|
from typing import Optional, cast
|
|
18
18
|
|
|
19
19
|
from pydantic import Field
|
|
20
|
-
from snowflake.cli.api.project.schemas.updatable_model import
|
|
20
|
+
from snowflake.cli.api.project.schemas.updatable_model import (
|
|
21
|
+
IdentifierField,
|
|
22
|
+
UpdatableModel,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class Identifier(UpdatableModel):
|
|
27
|
+
name: str = Field(title="Entity name")
|
|
28
|
+
schema_: str = Field(title="Entity schema", alias="schema", default=None)
|
|
29
|
+
database: str = Field(title="Entity database", default=None)
|
|
21
30
|
|
|
22
31
|
|
|
23
32
|
class ObjectIdentifierBaseModel:
|
|
@@ -16,19 +16,13 @@ from __future__ import annotations
|
|
|
16
16
|
|
|
17
17
|
from typing import List, Optional
|
|
18
18
|
|
|
19
|
-
from pydantic import Field
|
|
19
|
+
from pydantic import Field, field_validator
|
|
20
|
+
from snowflake.cli.api.project.schemas.entities.common import PostDeployHook
|
|
20
21
|
from snowflake.cli.api.project.schemas.updatable_model import (
|
|
21
22
|
IdentifierField,
|
|
22
23
|
UpdatableModel,
|
|
23
24
|
)
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
class SqlScriptHookType(UpdatableModel):
|
|
27
|
-
sql_script: str = Field(title="SQL file path relative to the project root")
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
# Currently sql_script is the only supported hook type. Change to a Union once other hook types are added
|
|
31
|
-
PostDeployHook = SqlScriptHookType
|
|
25
|
+
from snowflake.cli.api.project.util import append_test_resource_suffix
|
|
32
26
|
|
|
33
27
|
|
|
34
28
|
class Application(UpdatableModel):
|
|
@@ -53,6 +47,11 @@ class Application(UpdatableModel):
|
|
|
53
47
|
default=None,
|
|
54
48
|
)
|
|
55
49
|
|
|
50
|
+
@field_validator("name")
|
|
51
|
+
@classmethod
|
|
52
|
+
def append_test_resource_suffix_to_name(cls, input_value: str) -> str:
|
|
53
|
+
return append_test_resource_suffix(input_value)
|
|
54
|
+
|
|
56
55
|
|
|
57
56
|
class ApplicationV11(Application):
|
|
58
57
|
# Templated defaults only supported in v1.1+
|
|
@@ -17,11 +17,12 @@ from __future__ import annotations
|
|
|
17
17
|
from typing import List, Literal, Optional
|
|
18
18
|
|
|
19
19
|
from pydantic import Field, field_validator, model_validator
|
|
20
|
-
from snowflake.cli.api.project.schemas.
|
|
20
|
+
from snowflake.cli.api.project.schemas.entities.common import PostDeployHook
|
|
21
21
|
from snowflake.cli.api.project.schemas.updatable_model import (
|
|
22
22
|
IdentifierField,
|
|
23
23
|
UpdatableModel,
|
|
24
24
|
)
|
|
25
|
+
from snowflake.cli.api.project.util import append_test_resource_suffix
|
|
25
26
|
|
|
26
27
|
DistributionOptions = Literal["internal", "external", "INTERNAL", "EXTERNAL"]
|
|
27
28
|
|
|
@@ -50,6 +51,11 @@ class Package(UpdatableModel):
|
|
|
50
51
|
default=None,
|
|
51
52
|
)
|
|
52
53
|
|
|
54
|
+
@field_validator("name")
|
|
55
|
+
@classmethod
|
|
56
|
+
def append_test_resource_suffix_to_name(cls, input_value: str) -> str:
|
|
57
|
+
return append_test_resource_suffix(input_value)
|
|
58
|
+
|
|
53
59
|
@field_validator("scripts")
|
|
54
60
|
@classmethod
|
|
55
61
|
def validate_scripts(cls, input_list):
|
|
@@ -15,22 +15,21 @@
|
|
|
15
15
|
from __future__ import annotations
|
|
16
16
|
|
|
17
17
|
from dataclasses import dataclass
|
|
18
|
-
from typing import Dict, Optional, Union
|
|
18
|
+
from typing import Any, Dict, List, Optional, Union
|
|
19
19
|
|
|
20
20
|
from packaging.version import Version
|
|
21
21
|
from pydantic import Field, ValidationError, field_validator, model_validator
|
|
22
|
-
from snowflake.cli.api.feature_flags import FeatureFlag
|
|
23
22
|
from snowflake.cli.api.project.errors import SchemaValidationError
|
|
24
|
-
from snowflake.cli.api.project.schemas.entities.
|
|
25
|
-
|
|
23
|
+
from snowflake.cli.api.project.schemas.entities.application_entity_model import (
|
|
24
|
+
ApplicationEntityModel,
|
|
26
25
|
)
|
|
27
26
|
from snowflake.cli.api.project.schemas.entities.common import (
|
|
28
27
|
DefaultsField,
|
|
29
28
|
TargetField,
|
|
30
29
|
)
|
|
31
30
|
from snowflake.cli.api.project.schemas.entities.entities import (
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
EntityModel,
|
|
32
|
+
v2_entity_model_types_map,
|
|
34
33
|
)
|
|
35
34
|
from snowflake.cli.api.project.schemas.native_app.native_app import (
|
|
36
35
|
NativeApp,
|
|
@@ -42,6 +41,8 @@ from snowflake.cli.api.project.schemas.updatable_model import UpdatableModel
|
|
|
42
41
|
from snowflake.cli.api.utils.types import Context
|
|
43
42
|
from typing_extensions import Annotated
|
|
44
43
|
|
|
44
|
+
AnnotatedEntity = Annotated[EntityModel, Field(discriminator="type")]
|
|
45
|
+
|
|
45
46
|
|
|
46
47
|
@dataclass
|
|
47
48
|
class ProjectProperties:
|
|
@@ -111,9 +112,7 @@ class DefinitionV11(DefinitionV10):
|
|
|
111
112
|
|
|
112
113
|
|
|
113
114
|
class DefinitionV20(_ProjectDefinitionBase):
|
|
114
|
-
entities: Dict[str,
|
|
115
|
-
title="Entity definitions."
|
|
116
|
-
)
|
|
115
|
+
entities: Dict[str, AnnotatedEntity] = Field(title="Entity definitions.")
|
|
117
116
|
|
|
118
117
|
@model_validator(mode="before")
|
|
119
118
|
@classmethod
|
|
@@ -123,34 +122,52 @@ class DefinitionV20(_ProjectDefinitionBase):
|
|
|
123
122
|
"""
|
|
124
123
|
if "defaults" in data and "entities" in data:
|
|
125
124
|
for key, entity in data["entities"].items():
|
|
126
|
-
|
|
127
|
-
if
|
|
125
|
+
entity_fields = get_allowed_fields_for_entity(entity)
|
|
126
|
+
if not entity_fields:
|
|
128
127
|
continue
|
|
129
|
-
entity_model = v2_entity_types_map[entity_type]
|
|
130
128
|
for default_key, default_value in data["defaults"].items():
|
|
131
|
-
if
|
|
132
|
-
default_key in entity_model.model_fields
|
|
133
|
-
and default_key not in entity
|
|
134
|
-
):
|
|
129
|
+
if default_key in entity_fields and default_key not in entity:
|
|
135
130
|
entity[default_key] = default_value
|
|
136
131
|
return data
|
|
137
132
|
|
|
138
133
|
@field_validator("entities", mode="after")
|
|
139
134
|
@classmethod
|
|
140
|
-
def
|
|
135
|
+
def validate_entities_identifiers(
|
|
136
|
+
cls, entities: Dict[str, EntityModel]
|
|
137
|
+
) -> Dict[str, EntityModel]:
|
|
138
|
+
for key, entity in entities.items():
|
|
139
|
+
entity.set_entity_id(key)
|
|
140
|
+
entity.validate_identifier()
|
|
141
|
+
return entities
|
|
142
|
+
|
|
143
|
+
@field_validator("entities", mode="after")
|
|
144
|
+
@classmethod
|
|
145
|
+
def validate_entities(
|
|
146
|
+
cls, entities: Dict[str, AnnotatedEntity]
|
|
147
|
+
) -> Dict[str, AnnotatedEntity]:
|
|
141
148
|
for key, entity in entities.items():
|
|
142
149
|
# TODO Automatically detect TargetFields to validate
|
|
143
|
-
if entity
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
cls._validate_target_field(target_key, target_type, entities)
|
|
150
|
+
if isinstance(entity, list):
|
|
151
|
+
for e in entity:
|
|
152
|
+
cls._validate_single_entity(e, entities)
|
|
153
|
+
else:
|
|
154
|
+
cls._validate_single_entity(entity, entities)
|
|
149
155
|
return entities
|
|
150
156
|
|
|
157
|
+
@classmethod
|
|
158
|
+
def _validate_single_entity(
|
|
159
|
+
cls, entity: EntityModel, entities: Dict[str, AnnotatedEntity]
|
|
160
|
+
):
|
|
161
|
+
if entity.type == ApplicationEntityModel.get_type():
|
|
162
|
+
if isinstance(entity.from_, TargetField):
|
|
163
|
+
target_key = entity.from_.target
|
|
164
|
+
target_object = entity.from_
|
|
165
|
+
target_type = target_object.get_type()
|
|
166
|
+
cls._validate_target_field(target_key, target_type, entities)
|
|
167
|
+
|
|
151
168
|
@classmethod
|
|
152
169
|
def _validate_target_field(
|
|
153
|
-
cls, target_key: str, target_type:
|
|
170
|
+
cls, target_key: str, target_type: EntityModel, entities: Dict[str, EntityModel]
|
|
154
171
|
):
|
|
155
172
|
if target_key not in entities:
|
|
156
173
|
raise ValueError(f"No such target: {target_key}")
|
|
@@ -172,6 +189,39 @@ class DefinitionV20(_ProjectDefinitionBase):
|
|
|
172
189
|
default=None,
|
|
173
190
|
)
|
|
174
191
|
|
|
192
|
+
mixins: Optional[Dict[str, Dict]] = Field(
|
|
193
|
+
title="Mixins to apply to entities",
|
|
194
|
+
default=None,
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
@model_validator(mode="before")
|
|
198
|
+
@classmethod
|
|
199
|
+
def apply_mixins(cls, data: Dict) -> Dict:
|
|
200
|
+
"""
|
|
201
|
+
Applies mixins to those entities, whose meta field contains the mixin name.
|
|
202
|
+
"""
|
|
203
|
+
if "mixins" not in data or "entities" not in data:
|
|
204
|
+
return data
|
|
205
|
+
|
|
206
|
+
for entity in data["entities"].values():
|
|
207
|
+
entity_mixins = entity_mixins_to_list(
|
|
208
|
+
entity.get("meta", {}).get("use_mixins")
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
entity_fields = get_allowed_fields_for_entity(entity)
|
|
212
|
+
if entity_fields and entity_mixins:
|
|
213
|
+
for mixin_name in entity_mixins:
|
|
214
|
+
if mixin_name in data["mixins"]:
|
|
215
|
+
for key, value in data["mixins"][mixin_name].items():
|
|
216
|
+
if key in entity_fields:
|
|
217
|
+
entity[key] = value
|
|
218
|
+
else:
|
|
219
|
+
raise ValueError(f"Mixin {mixin_name} not found in mixins")
|
|
220
|
+
return data
|
|
221
|
+
|
|
222
|
+
def get_entities_by_type(self, entity_type: str):
|
|
223
|
+
return {i: e for i, e in self.entities.items() if e.get_type() == entity_type}
|
|
224
|
+
|
|
175
225
|
|
|
176
226
|
def build_project_definition(**data) -> ProjectDefinition:
|
|
177
227
|
"""
|
|
@@ -193,7 +243,28 @@ ProjectDefinition = Union[ProjectDefinitionV1, ProjectDefinitionV2]
|
|
|
193
243
|
|
|
194
244
|
|
|
195
245
|
def get_version_map():
|
|
196
|
-
version_map = {"1": DefinitionV10, "1.1": DefinitionV11}
|
|
197
|
-
if FeatureFlag.ENABLE_PROJECT_DEFINITION_V2.is_enabled():
|
|
198
|
-
version_map["2"] = DefinitionV20
|
|
246
|
+
version_map = {"1": DefinitionV10, "1.1": DefinitionV11, "2": DefinitionV20}
|
|
199
247
|
return version_map
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def entity_mixins_to_list(entity_mixins: Optional[str | List[str]]) -> List[str]:
|
|
251
|
+
"""
|
|
252
|
+
Convert an optional string or a list of strings to a list of strings.
|
|
253
|
+
"""
|
|
254
|
+
if entity_mixins is None:
|
|
255
|
+
return []
|
|
256
|
+
if isinstance(entity_mixins, str):
|
|
257
|
+
return [entity_mixins]
|
|
258
|
+
return entity_mixins
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def get_allowed_fields_for_entity(entity: Dict[str, Any]) -> List[str]:
|
|
262
|
+
"""
|
|
263
|
+
Get the allowed fields for the given entity.
|
|
264
|
+
"""
|
|
265
|
+
entity_type = entity.get("type")
|
|
266
|
+
if entity_type not in v2_entity_model_types_map:
|
|
267
|
+
return []
|
|
268
|
+
|
|
269
|
+
entity_model = v2_entity_model_types_map[entity_type]
|
|
270
|
+
return entity_model.model_fields
|
|
@@ -117,6 +117,7 @@ class UpdatableModel(BaseModel):
|
|
|
117
117
|
# all the values of the class attributes. We go in reverse order so that
|
|
118
118
|
# values in subclasses overrides values from parent classes in case of field overrides.
|
|
119
119
|
|
|
120
|
+
private_attrs = set()
|
|
120
121
|
for class_ in reversed(cls.__mro__):
|
|
121
122
|
class_dict = class_.__dict__
|
|
122
123
|
field_annotations.update(class_dict.get("__annotations__", {}))
|
|
@@ -128,10 +129,15 @@ class UpdatableModel(BaseModel):
|
|
|
128
129
|
else:
|
|
129
130
|
# If Pydantic did not process this class yet, get the values from class_dict directly
|
|
130
131
|
field_values.update(class_dict)
|
|
132
|
+
for pa in class_dict.get("__private_attributes__", []):
|
|
133
|
+
private_attrs.add(pa)
|
|
131
134
|
|
|
132
135
|
# Add Pydantic validation wrapper around all fields except `DiscriminatorField`s
|
|
133
136
|
for field_name in field_annotations:
|
|
134
|
-
if
|
|
137
|
+
if field_name in private_attrs:
|
|
138
|
+
continue
|
|
139
|
+
field = field_values.get(field_name)
|
|
140
|
+
if not cls._is_entity_type_field(field):
|
|
135
141
|
cls._add_validator(field_name)
|
|
136
142
|
|
|
137
143
|
@classmethod
|
|
@@ -154,7 +160,8 @@ class UpdatableModel(BaseModel):
|
|
|
154
160
|
|
|
155
161
|
setattr(
|
|
156
162
|
cls,
|
|
157
|
-
|
|
163
|
+
# Unique name so that subclasses get a unique instance of this validator
|
|
164
|
+
f"_{cls.__module__}.{cls.__name__}_validate_{field_name}",
|
|
158
165
|
field_validator(field_name, mode="wrap")(validator_skipping_templated_str),
|
|
159
166
|
)
|
|
160
167
|
|
|
@@ -187,7 +194,8 @@ def DiscriminatorField(*args, **kwargs): # noqa N802
|
|
|
187
194
|
When this `DiscriminatorField` is used on a pydantic attribute,
|
|
188
195
|
we will not allow templating on it.
|
|
189
196
|
"""
|
|
190
|
-
|
|
197
|
+
extra = dict(is_discriminator_field=True)
|
|
198
|
+
return Field(json_schema_extra=extra, *args, **kwargs)
|
|
191
199
|
|
|
192
200
|
|
|
193
201
|
def IdentifierField(*args, **kwargs): # noqa N802
|
|
@@ -34,6 +34,9 @@ UNQUOTED_IDENTIFIER_REGEX = r"([a-zA-Z_])([a-zA-Z0-9_$]{0,254})"
|
|
|
34
34
|
QUOTED_IDENTIFIER_REGEX = r'"((""|[^"]){0,255})"'
|
|
35
35
|
VALID_IDENTIFIER_REGEX = f"(?:{UNQUOTED_IDENTIFIER_REGEX}|{QUOTED_IDENTIFIER_REGEX})"
|
|
36
36
|
|
|
37
|
+
# An env var that is used to suffix the names of some account-level resources
|
|
38
|
+
TEST_RESOURCE_SUFFIX_VAR = "SNOWFLAKE_CLI_TEST_RESOURCE_SUFFIX"
|
|
39
|
+
|
|
37
40
|
|
|
38
41
|
def encode_uri_component(s: str) -> str:
|
|
39
42
|
"""
|
|
@@ -191,12 +194,6 @@ def extract_schema(qualified_name: str):
|
|
|
191
194
|
return None
|
|
192
195
|
|
|
193
196
|
|
|
194
|
-
def generate_user_env(username: str) -> dict:
|
|
195
|
-
return {
|
|
196
|
-
"USER": username,
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
|
|
200
197
|
def first_set_env(*keys: str):
|
|
201
198
|
for k in keys:
|
|
202
199
|
v = os.getenv(k)
|
|
@@ -259,3 +256,23 @@ def identifier_to_show_like_pattern(identifier: str) -> str:
|
|
|
259
256
|
matching this identifier
|
|
260
257
|
"""
|
|
261
258
|
return f"'{escape_like_pattern(unquote_identifier(identifier))}'"
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def append_test_resource_suffix(identifier: str) -> str:
|
|
262
|
+
"""
|
|
263
|
+
Append a suffix that should be added to specified account-level resources.
|
|
264
|
+
|
|
265
|
+
This is an internal concern that is currently only used in tests
|
|
266
|
+
to isolate concurrent runs and to add the test name to resources.
|
|
267
|
+
"""
|
|
268
|
+
suffix = os.environ.get(TEST_RESOURCE_SUFFIX_VAR, "")
|
|
269
|
+
if identifier_to_str(identifier).endswith(identifier_to_str(suffix)):
|
|
270
|
+
# If the suffix has already been added, don't add it again
|
|
271
|
+
return identifier
|
|
272
|
+
if is_valid_quoted_identifier(identifier) or is_valid_quoted_identifier(suffix):
|
|
273
|
+
# If either identifier is already quoted, use concat_identifier
|
|
274
|
+
# to add the suffix inside the quotes
|
|
275
|
+
return concat_identifiers([identifier, suffix])
|
|
276
|
+
# Otherwise just append the string, don't add quotes
|
|
277
|
+
# in case the user doesn't want them
|
|
278
|
+
return f"{identifier}{suffix}"
|
|
@@ -17,7 +17,7 @@ from __future__ import annotations
|
|
|
17
17
|
|
|
18
18
|
from pathlib import Path
|
|
19
19
|
from textwrap import dedent
|
|
20
|
-
from typing import Dict, Optional
|
|
20
|
+
from typing import Any, Dict, Optional
|
|
21
21
|
|
|
22
22
|
import jinja2
|
|
23
23
|
from jinja2 import Environment, StrictUndefined, loaders
|
|
@@ -82,8 +82,18 @@ class IgnoreAttrEnvironment(Environment):
|
|
|
82
82
|
return self.undefined(obj=obj, name=argument)
|
|
83
83
|
|
|
84
84
|
|
|
85
|
+
def get_basic_jinja_env(loader: Optional[loaders.BaseLoader] = None) -> Environment:
|
|
86
|
+
return env_bootstrap(
|
|
87
|
+
IgnoreAttrEnvironment(
|
|
88
|
+
loader=loader or loaders.BaseLoader(),
|
|
89
|
+
keep_trailing_newline=True,
|
|
90
|
+
undefined=StrictUndefined,
|
|
91
|
+
)
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
|
|
85
95
|
def jinja_render_from_file(
|
|
86
|
-
template_path: Path, data: Dict, output_file_path: Optional[Path] = None
|
|
96
|
+
template_path: Path, data: Dict[str, Any], output_file_path: Optional[Path] = None
|
|
87
97
|
) -> Optional[str]:
|
|
88
98
|
"""
|
|
89
99
|
Renders a jinja template and outputs either the rendered contents as string or writes to a file.
|
|
@@ -96,12 +106,8 @@ def jinja_render_from_file(
|
|
|
96
106
|
Returns:
|
|
97
107
|
None if file path is provided, else returns the rendered string.
|
|
98
108
|
"""
|
|
99
|
-
env =
|
|
100
|
-
|
|
101
|
-
loader=loaders.FileSystemLoader(template_path.parent),
|
|
102
|
-
keep_trailing_newline=True,
|
|
103
|
-
undefined=StrictUndefined,
|
|
104
|
-
)
|
|
109
|
+
env = get_basic_jinja_env(
|
|
110
|
+
loader=loaders.FileSystemLoader(template_path.parent.as_posix())
|
|
105
111
|
)
|
|
106
112
|
loaded_template = env.get_template(template_path.name)
|
|
107
113
|
rendered_result = loaded_template.render(**data)
|
|
@@ -24,7 +24,7 @@ _YML_TEMPLATE_START = "<%"
|
|
|
24
24
|
_YML_TEMPLATE_END = "%>"
|
|
25
25
|
|
|
26
26
|
|
|
27
|
-
def
|
|
27
|
+
def get_client_side_jinja_env() -> Environment:
|
|
28
28
|
_random_block = "___very___unique___block___to___disable___logic___blocks___"
|
|
29
29
|
return env_bootstrap(
|
|
30
30
|
IgnoreAttrEnvironment(
|