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.
Files changed (224) hide show
  1. snowflake/cli/__about__.py +1 -1
  2. snowflake/cli/{app → _app}/__main__.py +1 -1
  3. snowflake/cli/{app → _app}/cli_app.py +12 -12
  4. snowflake/cli/{app → _app}/commands_registration/builtin_plugins.py +13 -19
  5. snowflake/cli/{app → _app}/commands_registration/command_plugins_loader.py +9 -9
  6. snowflake/cli/{app → _app}/commands_registration/commands_registration_with_callbacks.py +4 -4
  7. snowflake/cli/{app → _app}/commands_registration/exception_logging.py +2 -2
  8. snowflake/cli/{app → _app}/commands_registration/typer_registration.py +2 -2
  9. snowflake/cli/{app → _app}/dev/docs/commands_docs_generator.py +30 -12
  10. snowflake/cli/{app → _app}/dev/docs/generator.py +3 -3
  11. snowflake/cli/{app → _app}/dev/docs/project_definition_docs_generator.py +4 -4
  12. snowflake/cli/{app → _app}/dev/docs/templates/usage.rst.jinja2 +14 -4
  13. snowflake/cli/{app → _app}/main_typer.py +2 -2
  14. snowflake/cli/{app → _app}/printing.py +2 -2
  15. snowflake/cli/{app → _app}/snow_connector.py +24 -17
  16. snowflake/cli/{app → _app}/telemetry.py +4 -5
  17. snowflake/cli/{plugins → _plugins}/connection/commands.py +25 -7
  18. snowflake/cli/_plugins/connection/plugin_spec.py +30 -0
  19. snowflake/cli/{plugins → _plugins}/connection/util.py +16 -0
  20. snowflake/cli/{plugins → _plugins}/cortex/commands.py +54 -49
  21. snowflake/cli/{plugins → _plugins}/cortex/constants.py +1 -1
  22. snowflake/cli/{plugins → _plugins}/cortex/manager.py +5 -5
  23. snowflake/cli/{plugins → _plugins}/cortex/plugin_spec.py +1 -1
  24. snowflake/cli/{plugins → _plugins}/git/commands.py +32 -20
  25. snowflake/cli/{plugins → _plugins}/git/manager.py +20 -11
  26. snowflake/cli/{plugins → _plugins}/git/plugin_spec.py +1 -1
  27. snowflake/cli/{plugins → _plugins}/init/commands.py +10 -6
  28. snowflake/cli/{plugins → _plugins}/init/plugin_spec.py +1 -1
  29. snowflake/cli/{plugins → _plugins}/nativeapp/artifacts.py +14 -0
  30. snowflake/cli/_plugins/nativeapp/bundle_context.py +31 -0
  31. snowflake/cli/{plugins → _plugins}/nativeapp/codegen/artifact_processor.py +3 -3
  32. snowflake/cli/{plugins → _plugins}/nativeapp/codegen/compiler.py +32 -18
  33. snowflake/cli/_plugins/nativeapp/codegen/setup/native_app_setup_processor.py +249 -0
  34. snowflake/cli/{plugins → _plugins}/nativeapp/codegen/setup/setup_driver.py.source +5 -2
  35. snowflake/cli/{plugins → _plugins}/nativeapp/codegen/snowpark/extension_function_utils.py +4 -4
  36. snowflake/cli/{plugins → _plugins}/nativeapp/codegen/snowpark/python_processor.py +23 -29
  37. snowflake/cli/_plugins/nativeapp/codegen/templates/templates_processor.py +93 -0
  38. snowflake/cli/{plugins → _plugins}/nativeapp/commands.py +171 -42
  39. snowflake/cli/{plugins → _plugins}/nativeapp/common_flags.py +1 -1
  40. snowflake/cli/{plugins → _plugins}/nativeapp/exceptions.py +3 -3
  41. snowflake/cli/{plugins → _plugins}/nativeapp/init.py +1 -1
  42. snowflake/cli/_plugins/nativeapp/manager.py +572 -0
  43. snowflake/cli/{plugins/connection → _plugins/nativeapp}/plugin_spec.py +1 -1
  44. snowflake/cli/{plugins → _plugins}/nativeapp/project_model.py +35 -19
  45. snowflake/cli/{plugins → _plugins}/nativeapp/run_processor.py +25 -23
  46. snowflake/cli/{plugins → _plugins}/nativeapp/teardown_processor.py +24 -110
  47. snowflake/cli/{plugins → _plugins}/nativeapp/v2_conversions/v2_to_v1_decorator.py +47 -28
  48. snowflake/cli/{plugins → _plugins}/nativeapp/version/commands.py +15 -12
  49. snowflake/cli/{plugins → _plugins}/nativeapp/version/version_processor.py +22 -20
  50. snowflake/cli/{plugins → _plugins}/notebook/commands.py +8 -6
  51. snowflake/cli/{plugins → _plugins}/notebook/manager.py +14 -14
  52. snowflake/cli/{plugins → _plugins}/notebook/plugin_spec.py +1 -1
  53. snowflake/cli/{plugins → _plugins}/notebook/types.py +0 -1
  54. snowflake/cli/{plugins → _plugins}/object/command_aliases.py +6 -5
  55. snowflake/cli/{plugins → _plugins}/object/commands.py +16 -10
  56. snowflake/cli/{plugins → _plugins}/object/manager.py +7 -6
  57. snowflake/cli/{plugins → _plugins}/object/plugin_spec.py +1 -1
  58. snowflake/cli/_plugins/snowpark/commands.py +450 -0
  59. snowflake/cli/_plugins/snowpark/common.py +268 -0
  60. snowflake/cli/{plugins → _plugins}/snowpark/models.py +0 -7
  61. snowflake/cli/{plugins → _plugins}/snowpark/package/anaconda_packages.py +2 -36
  62. snowflake/cli/{plugins → _plugins}/snowpark/package/commands.py +13 -74
  63. snowflake/cli/{plugins → _plugins}/snowpark/package/manager.py +4 -3
  64. snowflake/cli/{plugins → _plugins}/snowpark/package_utils.py +5 -5
  65. snowflake/cli/{plugins/nativeapp → _plugins/snowpark}/plugin_spec.py +1 -1
  66. snowflake/cli/_plugins/snowpark/snowpark_project_paths.py +109 -0
  67. snowflake/cli/{plugins → _plugins}/snowpark/snowpark_shared.py +0 -36
  68. snowflake/cli/{plugins → _plugins}/snowpark/zipper.py +16 -8
  69. snowflake/cli/{plugins → _plugins}/spcs/__init__.py +5 -7
  70. snowflake/cli/{plugins → _plugins}/spcs/compute_pool/commands.py +29 -28
  71. snowflake/cli/{plugins → _plugins}/spcs/compute_pool/manager.py +3 -3
  72. snowflake/cli/{plugins → _plugins}/spcs/image_registry/commands.py +3 -3
  73. snowflake/cli/{plugins → _plugins}/spcs/image_repository/commands.py +25 -19
  74. snowflake/cli/{plugins → _plugins}/spcs/image_repository/manager.py +1 -1
  75. snowflake/cli/{plugins → _plugins}/spcs/plugin_spec.py +1 -1
  76. snowflake/cli/{plugins → _plugins}/spcs/services/commands.py +66 -32
  77. snowflake/cli/{plugins → _plugins}/spcs/services/manager.py +43 -5
  78. snowflake/cli/{plugins → _plugins}/sql/commands.py +20 -17
  79. snowflake/cli/{plugins → _plugins}/sql/manager.py +1 -1
  80. snowflake/cli/{plugins → _plugins}/sql/plugin_spec.py +1 -1
  81. snowflake/cli/{plugins → _plugins}/stage/commands.py +20 -17
  82. snowflake/cli/{plugins → _plugins}/stage/diff.py +1 -47
  83. snowflake/cli/{plugins → _plugins}/stage/manager.py +54 -21
  84. snowflake/cli/{plugins → _plugins}/stage/plugin_spec.py +1 -1
  85. snowflake/cli/_plugins/stage/utils.py +54 -0
  86. snowflake/cli/{plugins → _plugins}/streamlit/commands.py +59 -62
  87. snowflake/cli/{plugins → _plugins}/streamlit/manager.py +51 -70
  88. snowflake/cli/_plugins/streamlit/plugin_spec.py +30 -0
  89. snowflake/cli/_plugins/workspace/action_context.py +17 -0
  90. snowflake/cli/_plugins/workspace/commands.py +194 -0
  91. snowflake/cli/_plugins/workspace/manager.py +73 -0
  92. snowflake/cli/{plugins → _plugins}/workspace/plugin_spec.py +1 -1
  93. snowflake/cli/api/cli_global_context.py +40 -13
  94. snowflake/cli/api/commands/common.py +25 -0
  95. snowflake/cli/api/commands/decorators.py +5 -4
  96. snowflake/cli/api/commands/experimental_behaviour.py +2 -3
  97. snowflake/cli/api/commands/flags.py +97 -179
  98. snowflake/cli/api/commands/overrideable_parameter.py +143 -0
  99. snowflake/cli/api/commands/snow_typer.py +14 -6
  100. snowflake/cli/api/commands/typer_pre_execute.py +3 -3
  101. snowflake/cli/api/commands/utils.py +18 -0
  102. snowflake/cli/api/config.py +18 -5
  103. snowflake/cli/api/console/abc.py +5 -2
  104. snowflake/cli/api/constants.py +11 -0
  105. snowflake/cli/api/entities/application_entity.py +12 -0
  106. snowflake/cli/api/entities/application_package_entity.py +553 -0
  107. snowflake/cli/api/entities/common.py +51 -0
  108. snowflake/cli/api/entities/snowpark_entity.py +29 -0
  109. snowflake/cli/api/entities/streamlit_entity.py +12 -0
  110. snowflake/cli/api/entities/utils.py +357 -0
  111. snowflake/cli/api/exceptions.py +31 -5
  112. snowflake/cli/api/feature_flags.py +0 -1
  113. snowflake/cli/api/identifiers.py +41 -9
  114. snowflake/cli/api/project/definition.py +37 -6
  115. snowflake/cli/api/project/definition_conversion.py +194 -0
  116. snowflake/cli/api/project/definition_manager.py +12 -1
  117. snowflake/cli/api/project/project_verification.py +3 -3
  118. snowflake/cli/api/project/schemas/entities/{application_entity.py → application_entity_model.py} +21 -9
  119. snowflake/cli/api/project/schemas/entities/{application_package_entity.py → application_package_entity_model.py} +43 -15
  120. snowflake/cli/api/project/schemas/entities/common.py +80 -6
  121. snowflake/cli/api/project/schemas/entities/entities.py +38 -8
  122. snowflake/cli/api/project/schemas/entities/snowpark_entity.py +176 -0
  123. snowflake/cli/api/project/schemas/entities/streamlit_entity_model.py +73 -0
  124. snowflake/cli/api/project/schemas/identifier_model.py +10 -1
  125. snowflake/cli/api/project/schemas/native_app/application.py +8 -9
  126. snowflake/cli/api/project/schemas/native_app/package.py +7 -1
  127. snowflake/cli/api/project/schemas/project_definition.py +98 -27
  128. snowflake/cli/api/project/schemas/updatable_model.py +11 -3
  129. snowflake/cli/api/project/util.py +23 -6
  130. snowflake/cli/api/rendering/jinja.py +14 -8
  131. snowflake/cli/api/rendering/project_definition_templates.py +1 -1
  132. snowflake/cli/api/rendering/sql_templates.py +43 -11
  133. snowflake/cli/api/secure_path.py +16 -18
  134. snowflake/cli/api/secure_utils.py +90 -1
  135. snowflake/cli/api/sql_execution.py +48 -19
  136. snowflake/cli/api/utils/definition_rendering.py +18 -8
  137. {snowflake_cli_labs-2.8.0rc1.dist-info → snowflake_cli_labs-3.0.0rc1.dist-info}/METADATA +13 -13
  138. snowflake_cli_labs-3.0.0rc1.dist-info/RECORD +236 -0
  139. snowflake_cli_labs-3.0.0rc1.dist-info/entry_points.txt +2 -0
  140. snowflake/cli/api/commands/project_initialisation.py +0 -65
  141. snowflake/cli/app/build_and_push.sh +0 -8
  142. snowflake/cli/plugins/nativeapp/codegen/setup/native_app_setup_processor.py +0 -172
  143. snowflake/cli/plugins/nativeapp/manager.py +0 -823
  144. snowflake/cli/plugins/object_stage_deprecated/__init__.py +0 -15
  145. snowflake/cli/plugins/object_stage_deprecated/commands.py +0 -122
  146. snowflake/cli/plugins/object_stage_deprecated/plugin_spec.py +0 -32
  147. snowflake/cli/plugins/snowpark/commands.py +0 -548
  148. snowflake/cli/plugins/snowpark/common.py +0 -307
  149. snowflake/cli/plugins/snowpark/manager.py +0 -109
  150. snowflake/cli/plugins/snowpark/plugin_spec.py +0 -30
  151. snowflake/cli/plugins/snowpark/snowpark_package_paths.py +0 -65
  152. snowflake/cli/plugins/spcs/jobs/commands.py +0 -78
  153. snowflake/cli/plugins/spcs/jobs/manager.py +0 -53
  154. snowflake/cli/plugins/streamlit/plugin_spec.py +0 -30
  155. snowflake/cli/plugins/workspace/commands.py +0 -35
  156. snowflake/cli/templates/default_snowpark/.gitignore +0 -4
  157. snowflake/cli/templates/default_snowpark/app/__init__.py +0 -0
  158. snowflake/cli/templates/default_snowpark/app/common.py +0 -2
  159. snowflake/cli/templates/default_snowpark/app/functions.py +0 -15
  160. snowflake/cli/templates/default_snowpark/app/procedures.py +0 -22
  161. snowflake/cli/templates/default_snowpark/requirements.txt +0 -1
  162. snowflake/cli/templates/default_snowpark/snowflake.yml +0 -23
  163. snowflake/cli/templates/default_streamlit/.gitignore +0 -4
  164. snowflake/cli/templates/default_streamlit/common/hello.py +0 -2
  165. snowflake/cli/templates/default_streamlit/environment.yml +0 -6
  166. snowflake/cli/templates/default_streamlit/pages/my_page.py +0 -3
  167. snowflake/cli/templates/default_streamlit/snowflake.yml +0 -10
  168. snowflake/cli/templates/default_streamlit/streamlit_app.py +0 -4
  169. snowflake_cli_labs-2.8.0rc1.dist-info/RECORD +0 -240
  170. snowflake_cli_labs-2.8.0rc1.dist-info/entry_points.txt +0 -2
  171. /snowflake/cli/{app → _app}/__init__.py +0 -0
  172. /snowflake/cli/{app → _app}/api_impl/__init__.py +0 -0
  173. /snowflake/cli/{app → _app}/api_impl/plugin/__init__.py +0 -0
  174. /snowflake/cli/{app → _app}/api_impl/plugin/plugin_config_provider_impl.py +0 -0
  175. /snowflake/cli/{app → _app}/commands_registration/__init__.py +0 -0
  176. /snowflake/cli/{app → _app}/commands_registration/threadsafe.py +0 -0
  177. /snowflake/cli/{app → _app}/constants.py +0 -0
  178. /snowflake/cli/{app → _app}/dev/__init__.py +0 -0
  179. /snowflake/cli/{app → _app}/dev/commands_structure.py +0 -0
  180. /snowflake/cli/{app → _app}/dev/docs/__init__.py +0 -0
  181. /snowflake/cli/{app → _app}/dev/docs/project_definition_generate_json_schema.py +0 -0
  182. /snowflake/cli/{app → _app}/dev/docs/template_utils.py +0 -0
  183. /snowflake/cli/{app → _app}/dev/docs/templates/definition_description.rst.jinja2 +0 -0
  184. /snowflake/cli/{app → _app}/dev/docs/templates/overview.rst.jinja2 +0 -0
  185. /snowflake/cli/{app → _app}/dev/pycharm_remote_debug.py +0 -0
  186. /snowflake/cli/{app → _app}/loggers.py +0 -0
  187. /snowflake/cli/{plugins → _plugins}/__init__.py +0 -0
  188. /snowflake/cli/{plugins → _plugins}/connection/__init__.py +0 -0
  189. /snowflake/cli/{plugins → _plugins}/cortex/__init__.py +0 -0
  190. /snowflake/cli/{plugins → _plugins}/cortex/types.py +0 -0
  191. /snowflake/cli/{plugins → _plugins}/git/__init__.py +0 -0
  192. /snowflake/cli/{plugins → _plugins}/init/__init__.py +0 -0
  193. /snowflake/cli/{plugins → _plugins}/nativeapp/__init__.py +0 -0
  194. /snowflake/cli/{plugins → _plugins}/nativeapp/codegen/__init__.py +0 -0
  195. /snowflake/cli/{plugins → _plugins}/nativeapp/codegen/sandbox.py +0 -0
  196. /snowflake/cli/{plugins → _plugins}/nativeapp/codegen/snowpark/callback_source.py.jinja +0 -0
  197. /snowflake/cli/{plugins → _plugins}/nativeapp/codegen/snowpark/models.py +0 -0
  198. /snowflake/cli/{plugins → _plugins}/nativeapp/constants.py +0 -0
  199. /snowflake/cli/{plugins → _plugins}/nativeapp/feature_flags.py +0 -0
  200. /snowflake/cli/{plugins → _plugins}/nativeapp/policy.py +0 -0
  201. /snowflake/cli/{plugins → _plugins}/nativeapp/utils.py +0 -0
  202. /snowflake/cli/{plugins → _plugins}/nativeapp/version/__init__.py +0 -0
  203. /snowflake/cli/{plugins → _plugins}/notebook/__init__.py +0 -0
  204. /snowflake/cli/{plugins → _plugins}/notebook/exceptions.py +0 -0
  205. /snowflake/cli/{plugins → _plugins}/object/__init__.py +0 -0
  206. /snowflake/cli/{plugins → _plugins}/object/common.py +0 -0
  207. /snowflake/cli/{plugins → _plugins}/snowpark/__init__.py +0 -0
  208. /snowflake/cli/{plugins → _plugins}/snowpark/package/__init__.py +0 -0
  209. /snowflake/cli/{plugins → _plugins}/snowpark/package/utils.py +0 -0
  210. /snowflake/cli/{plugins → _plugins}/spcs/common.py +0 -0
  211. /snowflake/cli/{plugins → _plugins}/spcs/compute_pool/__init__.py +0 -0
  212. /snowflake/cli/{plugins → _plugins}/spcs/image_registry/__init__.py +0 -0
  213. /snowflake/cli/{plugins → _plugins}/spcs/image_registry/manager.py +0 -0
  214. /snowflake/cli/{plugins → _plugins}/spcs/image_repository/__init__.py +0 -0
  215. /snowflake/cli/{plugins/spcs/jobs → _plugins/spcs/services}/__init__.py +0 -0
  216. /snowflake/cli/{plugins/spcs/services → _plugins/sql}/__init__.py +0 -0
  217. /snowflake/cli/{plugins → _plugins}/sql/snowsql_templating.py +0 -0
  218. /snowflake/cli/{plugins/sql → _plugins/stage}/__init__.py +0 -0
  219. /snowflake/cli/{plugins → _plugins}/stage/md5.py +0 -0
  220. /snowflake/cli/{plugins/stage → _plugins/streamlit}/__init__.py +0 -0
  221. /snowflake/cli/{plugins/streamlit → _plugins/workspace}/__init__.py +0 -0
  222. /snowflake/cli/{plugins/workspace → api/project/schemas/entities}/__init__.py +0 -0
  223. {snowflake_cli_labs-2.8.0rc1.dist-info → snowflake_cli_labs-3.0.0rc1.dist-info}/WHEEL +0 -0
  224. {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 IdentifierField
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.native_app.application import PostDeployHook
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.application_entity import (
25
- ApplicationEntity,
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
- Entity,
33
- v2_entity_types_map,
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, Annotated[Entity, Field(discriminator="type")]] = Field(
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
- entity_type = entity["type"]
127
- if entity_type not in v2_entity_types_map:
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 validate_entities(cls, entities: Dict[str, Entity]) -> Dict[str, Entity]:
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.type == ApplicationEntity.get_type():
144
- if isinstance(entity.from_, TargetField):
145
- target_key = entity.from_.target
146
- target_object = entity.from_
147
- target_type = target_object.get_type()
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: Entity, entities: Dict[str, Entity]
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 not cls._is_entity_type_field(field_values.get(field_name)):
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
- f"_field_validator_with_verbose_name_to_avoid_name_conflict_{field_name}",
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
- return Field(is_discriminator_field=True, *args, **kwargs)
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 = env_bootstrap(
100
- IgnoreAttrEnvironment(
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 get_project_definition_cli_jinja_env() -> Environment:
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(