snowflake-cli-labs 2.8.0rc0__py3-none-any.whl → 3.0.0rc0__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 (220) 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 +6 -6
  16. snowflake/cli/{app → _app}/telemetry.py +4 -5
  17. snowflake/cli/{plugins → _plugins}/connection/commands.py +22 -5
  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 +6 -5
  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 +16 -18
  33. snowflake/cli/{plugins → _plugins}/nativeapp/codegen/setup/native_app_setup_processor.py +24 -28
  34. snowflake/cli/{plugins → _plugins}/nativeapp/codegen/snowpark/extension_function_utils.py +4 -4
  35. snowflake/cli/{plugins → _plugins}/nativeapp/codegen/snowpark/python_processor.py +20 -24
  36. snowflake/cli/{plugins → _plugins}/nativeapp/commands.py +171 -42
  37. snowflake/cli/{plugins → _plugins}/nativeapp/common_flags.py +1 -1
  38. snowflake/cli/{plugins → _plugins}/nativeapp/init.py +1 -1
  39. snowflake/cli/_plugins/nativeapp/manager.py +601 -0
  40. snowflake/cli/{plugins/connection → _plugins/nativeapp}/plugin_spec.py +1 -1
  41. snowflake/cli/{plugins → _plugins}/nativeapp/project_model.py +34 -11
  42. snowflake/cli/{plugins → _plugins}/nativeapp/run_processor.py +25 -23
  43. snowflake/cli/{plugins → _plugins}/nativeapp/teardown_processor.py +8 -8
  44. snowflake/cli/{plugins → _plugins}/nativeapp/v2_conversions/v2_to_v1_decorator.py +47 -28
  45. snowflake/cli/{plugins → _plugins}/nativeapp/version/commands.py +15 -12
  46. snowflake/cli/{plugins → _plugins}/nativeapp/version/version_processor.py +22 -20
  47. snowflake/cli/{plugins → _plugins}/notebook/commands.py +8 -6
  48. snowflake/cli/{plugins → _plugins}/notebook/manager.py +14 -14
  49. snowflake/cli/{plugins → _plugins}/notebook/plugin_spec.py +1 -1
  50. snowflake/cli/{plugins → _plugins}/notebook/types.py +0 -1
  51. snowflake/cli/{plugins → _plugins}/object/command_aliases.py +6 -5
  52. snowflake/cli/{plugins → _plugins}/object/commands.py +16 -10
  53. snowflake/cli/{plugins → _plugins}/object/manager.py +7 -6
  54. snowflake/cli/{plugins → _plugins}/object/plugin_spec.py +1 -1
  55. snowflake/cli/_plugins/snowpark/commands.py +510 -0
  56. snowflake/cli/_plugins/snowpark/common.py +252 -0
  57. snowflake/cli/{plugins → _plugins}/snowpark/models.py +0 -7
  58. snowflake/cli/{plugins → _plugins}/snowpark/package/anaconda_packages.py +1 -1
  59. snowflake/cli/{plugins → _plugins}/snowpark/package/commands.py +13 -74
  60. snowflake/cli/{plugins → _plugins}/snowpark/package/manager.py +4 -3
  61. snowflake/cli/{plugins → _plugins}/snowpark/package_utils.py +5 -5
  62. snowflake/cli/{plugins/nativeapp → _plugins/snowpark}/plugin_spec.py +1 -1
  63. snowflake/cli/_plugins/snowpark/snowpark_project_paths.py +109 -0
  64. snowflake/cli/{plugins → _plugins}/snowpark/snowpark_shared.py +0 -36
  65. snowflake/cli/{plugins → _plugins}/snowpark/zipper.py +16 -8
  66. snowflake/cli/{plugins → _plugins}/spcs/__init__.py +5 -7
  67. snowflake/cli/{plugins → _plugins}/spcs/compute_pool/commands.py +29 -28
  68. snowflake/cli/{plugins → _plugins}/spcs/compute_pool/manager.py +3 -3
  69. snowflake/cli/{plugins → _plugins}/spcs/image_registry/commands.py +3 -3
  70. snowflake/cli/{plugins → _plugins}/spcs/image_repository/commands.py +25 -19
  71. snowflake/cli/{plugins → _plugins}/spcs/image_repository/manager.py +1 -1
  72. snowflake/cli/{plugins → _plugins}/spcs/plugin_spec.py +1 -1
  73. snowflake/cli/{plugins → _plugins}/spcs/services/commands.py +66 -32
  74. snowflake/cli/{plugins → _plugins}/spcs/services/manager.py +43 -5
  75. snowflake/cli/{plugins → _plugins}/sql/commands.py +19 -15
  76. snowflake/cli/{plugins → _plugins}/sql/manager.py +1 -1
  77. snowflake/cli/{plugins → _plugins}/sql/plugin_spec.py +1 -1
  78. snowflake/cli/{plugins → _plugins}/stage/commands.py +20 -17
  79. snowflake/cli/{plugins → _plugins}/stage/diff.py +1 -47
  80. snowflake/cli/{plugins → _plugins}/stage/manager.py +8 -6
  81. snowflake/cli/{plugins → _plugins}/stage/plugin_spec.py +1 -1
  82. snowflake/cli/_plugins/stage/utils.py +54 -0
  83. snowflake/cli/_plugins/streamlit/commands.py +242 -0
  84. snowflake/cli/{plugins → _plugins}/streamlit/manager.py +47 -70
  85. snowflake/cli/_plugins/streamlit/plugin_spec.py +30 -0
  86. snowflake/cli/_plugins/workspace/action_context.py +11 -0
  87. snowflake/cli/_plugins/workspace/commands.py +113 -0
  88. snowflake/cli/_plugins/workspace/manager.py +57 -0
  89. snowflake/cli/{plugins → _plugins}/workspace/plugin_spec.py +1 -1
  90. snowflake/cli/api/cli_global_context.py +34 -7
  91. snowflake/cli/api/commands/common.py +25 -0
  92. snowflake/cli/api/commands/decorators.py +4 -3
  93. snowflake/cli/api/commands/experimental_behaviour.py +2 -3
  94. snowflake/cli/api/commands/flags.py +73 -174
  95. snowflake/cli/api/commands/overrideable_parameter.py +143 -0
  96. snowflake/cli/api/commands/snow_typer.py +5 -4
  97. snowflake/cli/api/commands/typer_pre_execute.py +3 -3
  98. snowflake/cli/api/commands/utils.py +18 -0
  99. snowflake/cli/api/config.py +1 -1
  100. snowflake/cli/api/console/abc.py +5 -2
  101. snowflake/cli/api/entities/application_entity.py +12 -0
  102. snowflake/cli/api/entities/application_package_entity.py +260 -0
  103. snowflake/cli/api/entities/common.py +47 -0
  104. snowflake/cli/api/entities/snowpark_entity.py +29 -0
  105. snowflake/cli/api/entities/streamlit_entity.py +12 -0
  106. snowflake/cli/api/entities/utils.py +321 -0
  107. snowflake/cli/api/exceptions.py +19 -3
  108. snowflake/cli/api/feature_flags.py +2 -1
  109. snowflake/cli/api/identifiers.py +41 -9
  110. snowflake/cli/api/project/definition.py +13 -5
  111. snowflake/cli/api/project/definition_manager.py +12 -1
  112. snowflake/cli/api/project/errors.py +16 -1
  113. snowflake/cli/api/project/project_verification.py +3 -3
  114. snowflake/cli/api/project/schemas/entities/{application_entity.py → application_entity_model.py} +21 -9
  115. snowflake/cli/api/project/schemas/entities/{application_package_entity.py → application_package_entity_model.py} +26 -15
  116. snowflake/cli/api/project/schemas/entities/common.py +80 -6
  117. snowflake/cli/api/project/schemas/entities/entities.py +38 -8
  118. snowflake/cli/api/project/schemas/entities/snowpark_entity.py +176 -0
  119. snowflake/cli/api/project/schemas/entities/streamlit_entity_model.py +73 -0
  120. snowflake/cli/api/project/schemas/identifier_model.py +10 -1
  121. snowflake/cli/api/project/schemas/native_app/application.py +8 -9
  122. snowflake/cli/api/project/schemas/native_app/package.py +7 -1
  123. snowflake/cli/api/project/schemas/project_definition.py +97 -23
  124. snowflake/cli/api/project/schemas/updatable_model.py +11 -3
  125. snowflake/cli/api/project/util.py +23 -6
  126. snowflake/cli/api/rendering/jinja.py +28 -8
  127. snowflake/cli/api/rendering/sql_templates.py +41 -12
  128. snowflake/cli/api/secure_path.py +3 -0
  129. snowflake/cli/api/sql_execution.py +35 -19
  130. snowflake/cli/api/utils/definition_rendering.py +14 -2
  131. {snowflake_cli_labs-2.8.0rc0.dist-info → snowflake_cli_labs-3.0.0rc0.dist-info}/METADATA +12 -12
  132. snowflake_cli_labs-3.0.0rc0.dist-info/RECORD +234 -0
  133. snowflake_cli_labs-3.0.0rc0.dist-info/entry_points.txt +2 -0
  134. snowflake/cli/api/commands/project_initialisation.py +0 -65
  135. snowflake/cli/app/build_and_push.sh +0 -8
  136. snowflake/cli/plugins/nativeapp/manager.py +0 -819
  137. snowflake/cli/plugins/object_stage_deprecated/__init__.py +0 -15
  138. snowflake/cli/plugins/object_stage_deprecated/commands.py +0 -122
  139. snowflake/cli/plugins/object_stage_deprecated/plugin_spec.py +0 -32
  140. snowflake/cli/plugins/snowpark/commands.py +0 -548
  141. snowflake/cli/plugins/snowpark/common.py +0 -307
  142. snowflake/cli/plugins/snowpark/manager.py +0 -109
  143. snowflake/cli/plugins/snowpark/plugin_spec.py +0 -30
  144. snowflake/cli/plugins/snowpark/snowpark_package_paths.py +0 -65
  145. snowflake/cli/plugins/spcs/jobs/commands.py +0 -78
  146. snowflake/cli/plugins/spcs/jobs/manager.py +0 -53
  147. snowflake/cli/plugins/streamlit/commands.py +0 -186
  148. snowflake/cli/plugins/streamlit/plugin_spec.py +0 -30
  149. snowflake/cli/plugins/workspace/commands.py +0 -35
  150. snowflake/cli/templates/default_snowpark/.gitignore +0 -4
  151. snowflake/cli/templates/default_snowpark/app/__init__.py +0 -0
  152. snowflake/cli/templates/default_snowpark/app/common.py +0 -2
  153. snowflake/cli/templates/default_snowpark/app/functions.py +0 -15
  154. snowflake/cli/templates/default_snowpark/app/procedures.py +0 -22
  155. snowflake/cli/templates/default_snowpark/requirements.txt +0 -1
  156. snowflake/cli/templates/default_snowpark/snowflake.yml +0 -23
  157. snowflake/cli/templates/default_streamlit/.gitignore +0 -4
  158. snowflake/cli/templates/default_streamlit/common/hello.py +0 -2
  159. snowflake/cli/templates/default_streamlit/environment.yml +0 -6
  160. snowflake/cli/templates/default_streamlit/pages/my_page.py +0 -3
  161. snowflake/cli/templates/default_streamlit/snowflake.yml +0 -10
  162. snowflake/cli/templates/default_streamlit/streamlit_app.py +0 -4
  163. snowflake_cli_labs-2.8.0rc0.dist-info/RECORD +0 -240
  164. snowflake_cli_labs-2.8.0rc0.dist-info/entry_points.txt +0 -2
  165. /snowflake/cli/{app → _app}/__init__.py +0 -0
  166. /snowflake/cli/{app → _app}/api_impl/__init__.py +0 -0
  167. /snowflake/cli/{app → _app}/api_impl/plugin/__init__.py +0 -0
  168. /snowflake/cli/{app → _app}/api_impl/plugin/plugin_config_provider_impl.py +0 -0
  169. /snowflake/cli/{app → _app}/commands_registration/__init__.py +0 -0
  170. /snowflake/cli/{app → _app}/commands_registration/threadsafe.py +0 -0
  171. /snowflake/cli/{app → _app}/constants.py +0 -0
  172. /snowflake/cli/{app → _app}/dev/__init__.py +0 -0
  173. /snowflake/cli/{app → _app}/dev/commands_structure.py +0 -0
  174. /snowflake/cli/{app → _app}/dev/docs/__init__.py +0 -0
  175. /snowflake/cli/{app → _app}/dev/docs/project_definition_generate_json_schema.py +0 -0
  176. /snowflake/cli/{app → _app}/dev/docs/template_utils.py +0 -0
  177. /snowflake/cli/{app → _app}/dev/docs/templates/definition_description.rst.jinja2 +0 -0
  178. /snowflake/cli/{app → _app}/dev/docs/templates/overview.rst.jinja2 +0 -0
  179. /snowflake/cli/{app → _app}/dev/pycharm_remote_debug.py +0 -0
  180. /snowflake/cli/{app → _app}/loggers.py +0 -0
  181. /snowflake/cli/{plugins → _plugins}/__init__.py +0 -0
  182. /snowflake/cli/{plugins → _plugins}/connection/__init__.py +0 -0
  183. /snowflake/cli/{plugins → _plugins}/cortex/__init__.py +0 -0
  184. /snowflake/cli/{plugins → _plugins}/cortex/types.py +0 -0
  185. /snowflake/cli/{plugins → _plugins}/git/__init__.py +0 -0
  186. /snowflake/cli/{plugins → _plugins}/init/__init__.py +0 -0
  187. /snowflake/cli/{plugins → _plugins}/nativeapp/__init__.py +0 -0
  188. /snowflake/cli/{plugins → _plugins}/nativeapp/codegen/__init__.py +0 -0
  189. /snowflake/cli/{plugins → _plugins}/nativeapp/codegen/sandbox.py +0 -0
  190. /snowflake/cli/{plugins → _plugins}/nativeapp/codegen/setup/setup_driver.py.source +0 -0
  191. /snowflake/cli/{plugins → _plugins}/nativeapp/codegen/snowpark/callback_source.py.jinja +0 -0
  192. /snowflake/cli/{plugins → _plugins}/nativeapp/codegen/snowpark/models.py +0 -0
  193. /snowflake/cli/{plugins → _plugins}/nativeapp/constants.py +0 -0
  194. /snowflake/cli/{plugins → _plugins}/nativeapp/exceptions.py +0 -0
  195. /snowflake/cli/{plugins → _plugins}/nativeapp/feature_flags.py +0 -0
  196. /snowflake/cli/{plugins → _plugins}/nativeapp/policy.py +0 -0
  197. /snowflake/cli/{plugins → _plugins}/nativeapp/utils.py +0 -0
  198. /snowflake/cli/{plugins → _plugins}/nativeapp/version/__init__.py +0 -0
  199. /snowflake/cli/{plugins → _plugins}/notebook/__init__.py +0 -0
  200. /snowflake/cli/{plugins → _plugins}/notebook/exceptions.py +0 -0
  201. /snowflake/cli/{plugins → _plugins}/object/__init__.py +0 -0
  202. /snowflake/cli/{plugins → _plugins}/object/common.py +0 -0
  203. /snowflake/cli/{plugins → _plugins}/snowpark/__init__.py +0 -0
  204. /snowflake/cli/{plugins → _plugins}/snowpark/package/__init__.py +0 -0
  205. /snowflake/cli/{plugins → _plugins}/snowpark/package/utils.py +0 -0
  206. /snowflake/cli/{plugins → _plugins}/spcs/common.py +0 -0
  207. /snowflake/cli/{plugins → _plugins}/spcs/compute_pool/__init__.py +0 -0
  208. /snowflake/cli/{plugins → _plugins}/spcs/image_registry/__init__.py +0 -0
  209. /snowflake/cli/{plugins → _plugins}/spcs/image_registry/manager.py +0 -0
  210. /snowflake/cli/{plugins → _plugins}/spcs/image_repository/__init__.py +0 -0
  211. /snowflake/cli/{plugins/spcs/jobs → _plugins/spcs/services}/__init__.py +0 -0
  212. /snowflake/cli/{plugins/spcs/services → _plugins/sql}/__init__.py +0 -0
  213. /snowflake/cli/{plugins → _plugins}/sql/snowsql_templating.py +0 -0
  214. /snowflake/cli/{plugins/sql → _plugins/stage}/__init__.py +0 -0
  215. /snowflake/cli/{plugins → _plugins}/stage/md5.py +0 -0
  216. /snowflake/cli/{plugins/stage → _plugins/streamlit}/__init__.py +0 -0
  217. /snowflake/cli/{plugins/streamlit → _plugins/workspace}/__init__.py +0 -0
  218. /snowflake/cli/{plugins/workspace → api/project/schemas/entities}/__init__.py +0 -0
  219. {snowflake_cli_labs-2.8.0rc0.dist-info → snowflake_cli_labs-3.0.0rc0.dist-info}/WHEEL +0 -0
  220. {snowflake_cli_labs-2.8.0rc0.dist-info → snowflake_cli_labs-3.0.0rc0.dist-info}/licenses/LICENSE +0 -0
@@ -15,22 +15,22 @@
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
22
  from snowflake.cli.api.feature_flags import FeatureFlag
23
23
  from snowflake.cli.api.project.errors import SchemaValidationError
24
- from snowflake.cli.api.project.schemas.entities.application_entity import (
25
- ApplicationEntity,
24
+ from snowflake.cli.api.project.schemas.entities.application_entity_model import (
25
+ ApplicationEntityModel,
26
26
  )
27
27
  from snowflake.cli.api.project.schemas.entities.common import (
28
28
  DefaultsField,
29
29
  TargetField,
30
30
  )
31
31
  from snowflake.cli.api.project.schemas.entities.entities import (
32
- Entity,
33
- v2_entity_types_map,
32
+ EntityModel,
33
+ v2_entity_model_types_map,
34
34
  )
35
35
  from snowflake.cli.api.project.schemas.native_app.native_app import (
36
36
  NativeApp,
@@ -42,6 +42,8 @@ from snowflake.cli.api.project.schemas.updatable_model import UpdatableModel
42
42
  from snowflake.cli.api.utils.types import Context
43
43
  from typing_extensions import Annotated
44
44
 
45
+ AnnotatedEntity = Annotated[EntityModel, Field(discriminator="type")]
46
+
45
47
 
46
48
  @dataclass
47
49
  class ProjectProperties:
@@ -111,9 +113,7 @@ class DefinitionV11(DefinitionV10):
111
113
 
112
114
 
113
115
  class DefinitionV20(_ProjectDefinitionBase):
114
- entities: Dict[str, Annotated[Entity, Field(discriminator="type")]] = Field(
115
- title="Entity definitions."
116
- )
116
+ entities: Dict[str, AnnotatedEntity] = Field(title="Entity definitions.")
117
117
 
118
118
  @model_validator(mode="before")
119
119
  @classmethod
@@ -123,34 +123,52 @@ class DefinitionV20(_ProjectDefinitionBase):
123
123
  """
124
124
  if "defaults" in data and "entities" in data:
125
125
  for key, entity in data["entities"].items():
126
- entity_type = entity["type"]
127
- if entity_type not in v2_entity_types_map:
126
+ entity_fields = get_allowed_fields_for_entity(entity)
127
+ if not entity_fields:
128
128
  continue
129
- entity_model = v2_entity_types_map[entity_type]
130
129
  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
- ):
130
+ if default_key in entity_fields and default_key not in entity:
135
131
  entity[default_key] = default_value
136
132
  return data
137
133
 
138
134
  @field_validator("entities", mode="after")
139
135
  @classmethod
140
- def validate_entities(cls, entities: Dict[str, Entity]) -> Dict[str, Entity]:
136
+ def validate_entities_identifiers(
137
+ cls, entities: Dict[str, EntityModel]
138
+ ) -> Dict[str, EntityModel]:
139
+ for key, entity in entities.items():
140
+ entity.set_entity_id(key)
141
+ entity.validate_identifier()
142
+ return entities
143
+
144
+ @field_validator("entities", mode="after")
145
+ @classmethod
146
+ def validate_entities(
147
+ cls, entities: Dict[str, AnnotatedEntity]
148
+ ) -> Dict[str, AnnotatedEntity]:
141
149
  for key, entity in entities.items():
142
150
  # 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)
151
+ if isinstance(entity, list):
152
+ for e in entity:
153
+ cls._validate_single_entity(e, entities)
154
+ else:
155
+ cls._validate_single_entity(entity, entities)
149
156
  return entities
150
157
 
158
+ @classmethod
159
+ def _validate_single_entity(
160
+ cls, entity: EntityModel, entities: Dict[str, AnnotatedEntity]
161
+ ):
162
+ if entity.type == ApplicationEntityModel.get_type():
163
+ if isinstance(entity.from_, TargetField):
164
+ target_key = entity.from_.target
165
+ target_object = entity.from_
166
+ target_type = target_object.get_type()
167
+ cls._validate_target_field(target_key, target_type, entities)
168
+
151
169
  @classmethod
152
170
  def _validate_target_field(
153
- cls, target_key: str, target_type: Entity, entities: Dict[str, Entity]
171
+ cls, target_key: str, target_type: EntityModel, entities: Dict[str, EntityModel]
154
172
  ):
155
173
  if target_key not in entities:
156
174
  raise ValueError(f"No such target: {target_key}")
@@ -172,6 +190,39 @@ class DefinitionV20(_ProjectDefinitionBase):
172
190
  default=None,
173
191
  )
174
192
 
193
+ mixins: Optional[Dict[str, Dict]] = Field(
194
+ title="Mixins to apply to entities",
195
+ default=None,
196
+ )
197
+
198
+ @model_validator(mode="before")
199
+ @classmethod
200
+ def apply_mixins(cls, data: Dict) -> Dict:
201
+ """
202
+ Applies mixins to those entities, whose meta field contains the mixin name.
203
+ """
204
+ if "mixins" not in data or "entities" not in data:
205
+ return data
206
+
207
+ for entity in data["entities"].values():
208
+ entity_mixins = entity_mixins_to_list(
209
+ entity.get("meta", {}).get("use_mixins")
210
+ )
211
+
212
+ entity_fields = get_allowed_fields_for_entity(entity)
213
+ if entity_fields and entity_mixins:
214
+ for mixin_name in entity_mixins:
215
+ if mixin_name in data["mixins"]:
216
+ for key, value in data["mixins"][mixin_name].items():
217
+ if key in entity_fields:
218
+ entity[key] = value
219
+ else:
220
+ raise ValueError(f"Mixin {mixin_name} not found in mixins")
221
+ return data
222
+
223
+ def get_entities_by_type(self, entity_type: str):
224
+ return {i: e for i, e in self.entities.items() if e.get_type() == entity_type}
225
+
175
226
 
176
227
  def build_project_definition(**data) -> ProjectDefinition:
177
228
  """
@@ -197,3 +248,26 @@ def get_version_map():
197
248
  if FeatureFlag.ENABLE_PROJECT_DEFINITION_V2.is_enabled():
198
249
  version_map["2"] = DefinitionV20
199
250
  return version_map
251
+
252
+
253
+ def entity_mixins_to_list(entity_mixins: Optional[str | List[str]]) -> List[str]:
254
+ """
255
+ Convert an optional string or a list of strings to a list of strings.
256
+ """
257
+ if entity_mixins is None:
258
+ return []
259
+ if isinstance(entity_mixins, str):
260
+ return [entity_mixins]
261
+ return entity_mixins
262
+
263
+
264
+ def get_allowed_fields_for_entity(entity: Dict[str, Any]) -> List[str]:
265
+ """
266
+ Get the allowed fields for the given entity.
267
+ """
268
+ entity_type = entity.get("type")
269
+ if entity_type not in v2_entity_model_types_map:
270
+ return []
271
+
272
+ entity_model = v2_entity_model_types_map[entity_type]
273
+ 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,32 @@ class IgnoreAttrEnvironment(Environment):
82
82
  return self.undefined(obj=obj, name=argument)
83
83
 
84
84
 
85
+ def _get_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
+
95
+ def jinja_render_from_str(template_content: str, data: Dict[str, Any]) -> str:
96
+ """
97
+ Renders a jinja template and outputs either the rendered contents as string or writes to a file.
98
+
99
+ Args:
100
+ template_content (str): template contents
101
+ data (dict): A dictionary of jinja variables and their actual values
102
+
103
+ Returns:
104
+ None if file path is provided, else returns the rendered string.
105
+ """
106
+ return _get_jinja_env().from_string(template_content).render(data)
107
+
108
+
85
109
  def jinja_render_from_file(
86
- template_path: Path, data: Dict, output_file_path: Optional[Path] = None
110
+ template_path: Path, data: Dict[str, Any], output_file_path: Optional[Path] = None
87
111
  ) -> Optional[str]:
88
112
  """
89
113
  Renders a jinja template and outputs either the rendered contents as string or writes to a file.
@@ -96,12 +120,8 @@ def jinja_render_from_file(
96
120
  Returns:
97
121
  None if file path is provided, else returns the rendered string.
98
122
  """
99
- env = env_bootstrap(
100
- IgnoreAttrEnvironment(
101
- loader=loaders.FileSystemLoader(template_path.parent),
102
- keep_trailing_newline=True,
103
- undefined=StrictUndefined,
104
- )
123
+ env = _get_jinja_env(
124
+ loader=loaders.FileSystemLoader(template_path.parent.as_posix())
105
125
  )
106
126
  loaded_template = env.get_template(template_path.name)
107
127
  rendered_result = loaded_template.render(**data)
@@ -14,11 +14,13 @@
14
14
 
15
15
  from __future__ import annotations
16
16
 
17
- from typing import Dict, Optional
17
+ from typing import Dict
18
18
 
19
19
  from click import ClickException
20
- from jinja2 import StrictUndefined, loaders
21
- from snowflake.cli.api.cli_global_context import cli_context
20
+ from jinja2 import Environment, StrictUndefined, loaders, meta
21
+ from snowflake.cli.api.cli_global_context import get_cli_context
22
+ from snowflake.cli.api.console.console import cli_console
23
+ from snowflake.cli.api.exceptions import InvalidTemplate
22
24
  from snowflake.cli.api.rendering.jinja import (
23
25
  CONTEXT_KEY,
24
26
  FUNCTION_KEY,
@@ -26,26 +28,52 @@ from snowflake.cli.api.rendering.jinja import (
26
28
  env_bootstrap,
27
29
  )
28
30
 
29
- _SQL_TEMPLATE_START = "&{"
30
- _SQL_TEMPLATE_END = "}"
31
+ _SQL_TEMPLATE_START = "<%"
32
+ _SQL_TEMPLATE_END = "%>"
33
+ _OLD_SQL_TEMPLATE_START = "&{"
34
+ _OLD_SQL_TEMPLATE_END = "}"
31
35
  RESERVED_KEYS = [CONTEXT_KEY, FUNCTION_KEY]
32
36
 
33
37
 
34
- def get_sql_cli_jinja_env(*, loader: Optional[loaders.BaseLoader] = None):
38
+ def _get_sql_jinja_env(template_start: str, template_end: str) -> Environment:
35
39
  _random_block = "___very___unique___block___to___disable___logic___blocks___"
36
40
  return env_bootstrap(
37
41
  IgnoreAttrEnvironment(
38
- loader=loader or loaders.BaseLoader(),
39
- keep_trailing_newline=True,
40
- variable_start_string=_SQL_TEMPLATE_START,
41
- variable_end_string=_SQL_TEMPLATE_END,
42
+ variable_start_string=template_start,
43
+ variable_end_string=template_end,
44
+ loader=loaders.BaseLoader(),
42
45
  block_start_string=_random_block,
43
46
  block_end_string=_random_block,
47
+ keep_trailing_newline=True,
44
48
  undefined=StrictUndefined,
45
49
  )
46
50
  )
47
51
 
48
52
 
53
+ def _does_template_have_env_syntax(env: Environment, template_content: str) -> bool:
54
+ template = env.parse(template_content)
55
+ return bool(meta.find_undeclared_variables(template))
56
+
57
+
58
+ def choose_sql_jinja_env_based_on_template_syntax(template_content: str) -> Environment:
59
+ old_syntax_env = _get_sql_jinja_env(_OLD_SQL_TEMPLATE_START, _OLD_SQL_TEMPLATE_END)
60
+ new_syntax_env = _get_sql_jinja_env(_SQL_TEMPLATE_START, _SQL_TEMPLATE_END)
61
+ has_old_syntax = _does_template_have_env_syntax(old_syntax_env, template_content)
62
+ has_new_syntax = _does_template_have_env_syntax(new_syntax_env, template_content)
63
+ if has_old_syntax and has_new_syntax:
64
+ raise InvalidTemplate(
65
+ f"The SQL query mixes {_OLD_SQL_TEMPLATE_START} ... {_OLD_SQL_TEMPLATE_END} syntax"
66
+ f" and {_SQL_TEMPLATE_START} ... {_SQL_TEMPLATE_END} syntax."
67
+ )
68
+ if has_old_syntax:
69
+ cli_console.warning(
70
+ f"Warning: {_OLD_SQL_TEMPLATE_START} ... {_OLD_SQL_TEMPLATE_END} syntax is deprecated."
71
+ f" Use {_SQL_TEMPLATE_START} ... {_SQL_TEMPLATE_END} syntax instead."
72
+ )
73
+ return old_syntax_env
74
+ return new_syntax_env
75
+
76
+
49
77
  def snowflake_sql_jinja_render(content: str, data: Dict | None = None) -> str:
50
78
  data = data or {}
51
79
 
@@ -55,6 +83,7 @@ def snowflake_sql_jinja_render(content: str, data: Dict | None = None) -> str:
55
83
  f"{reserved_key} in user defined data. The `{reserved_key}` variable is reserved for CLI usage."
56
84
  )
57
85
 
58
- context_data = cli_context.template_context
86
+ context_data = get_cli_context().template_context
59
87
  context_data.update(data)
60
- return get_sql_cli_jinja_env().from_string(content).render(**context_data)
88
+ env = choose_sql_jinja_env_based_on_template_syntax(content)
89
+ return env.from_string(content).render(context_data)
@@ -349,6 +349,9 @@ class SecurePath:
349
349
  ):
350
350
  raise FileTooLargeError(self._path.resolve(), size_limit_in_mb)
351
351
 
352
+ def rename(self, new_name: Union[str | Path]):
353
+ self._path.rename(new_name)
354
+
352
355
 
353
356
  def _raise_file_exists_error(path: Path):
354
357
  raise FileExistsError(errno.EEXIST, os.strerror(errno.EEXIST), path)
@@ -21,7 +21,7 @@ from io import StringIO
21
21
  from textwrap import dedent
22
22
  from typing import Iterable, Optional, Tuple
23
23
 
24
- from snowflake.cli.api.cli_global_context import cli_context
24
+ from snowflake.cli.api.cli_global_context import get_cli_context
25
25
  from snowflake.cli.api.console import cli_console
26
26
  from snowflake.cli.api.constants import ObjectType
27
27
  from snowflake.cli.api.exceptions import (
@@ -35,27 +35,21 @@ from snowflake.cli.api.project.util import (
35
35
  unquote_identifier,
36
36
  )
37
37
  from snowflake.cli.api.utils.cursor import find_first_row
38
+ from snowflake.connector import SnowflakeConnection
38
39
  from snowflake.connector.cursor import DictCursor, SnowflakeCursor
39
40
  from snowflake.connector.errors import ProgrammingError
40
41
 
41
42
 
42
- class SqlExecutionMixin:
43
- def __init__(self):
43
+ class SqlExecutor:
44
+ def __init__(self, connection: SnowflakeConnection | None = None):
44
45
  self._snowpark_session = None
46
+ self._connection = connection
45
47
 
46
48
  @property
47
- def _conn(self):
48
- return cli_context.connection
49
-
50
- @property
51
- def snowpark_session(self):
52
- if not self._snowpark_session:
53
- from snowflake.snowpark.session import Session
54
-
55
- self._snowpark_session = Session.builder.configs(
56
- {"connection": self._conn}
57
- ).create()
58
- return self._snowpark_session
49
+ def _conn(self) -> SnowflakeConnection:
50
+ if self._connection:
51
+ return self._connection
52
+ return get_cli_context().connection
59
53
 
60
54
  @cached_property
61
55
  def _log(self):
@@ -88,6 +82,12 @@ class SqlExecutionMixin:
88
82
  def _execute_queries(self, queries: str, **kwargs):
89
83
  return list(self._execute_string(dedent(queries), **kwargs))
90
84
 
85
+ def execute_query(self, query: str, **kwargs):
86
+ return self._execute_query(query, **kwargs)
87
+
88
+ def execute_queries(self, queries: str, **kwargs):
89
+ return self._execute_queries(queries, **kwargs)
90
+
91
91
  def use(self, object_type: ObjectType, name: str):
92
92
  try:
93
93
  self._execute_query(f"use {object_type.value.sf_name} {name}")
@@ -147,11 +147,11 @@ class SqlExecutionMixin:
147
147
  self.use(object_type=ObjectType.WAREHOUSE, name=prev_wh)
148
148
 
149
149
  def create_password_secret(
150
- self, name: str, username: str, password: str
150
+ self, name: FQN, username: str, password: str
151
151
  ) -> SnowflakeCursor:
152
152
  return self._execute_query(
153
153
  f"""
154
- create secret {name}
154
+ create secret {name.sql_identifier}
155
155
  type = password
156
156
  username = '{username}'
157
157
  password = '{password}'
@@ -159,11 +159,11 @@ class SqlExecutionMixin:
159
159
  )
160
160
 
161
161
  def create_api_integration(
162
- self, name: str, api_provider: str, allowed_prefix: str, secret: Optional[str]
162
+ self, name: FQN, api_provider: str, allowed_prefix: str, secret: Optional[str]
163
163
  ) -> SnowflakeCursor:
164
164
  return self._execute_query(
165
165
  f"""
166
- create api integration {name}
166
+ create api integration {name.sql_identifier}
167
167
  api_provider = {api_provider}
168
168
  api_allowed_prefixes = ('{allowed_prefix}')
169
169
  allowed_authentication_secrets = ({secret if secret else ''})
@@ -254,6 +254,22 @@ class SqlExecutionMixin:
254
254
  return show_obj_row
255
255
 
256
256
 
257
+ class SqlExecutionMixin(SqlExecutor):
258
+ def __init__(self, *args, **kwargs):
259
+ super().__init__(*args, **kwargs)
260
+ self._snowpark_session = None
261
+
262
+ @property
263
+ def snowpark_session(self):
264
+ if not self._snowpark_session:
265
+ from snowflake.snowpark.session import Session
266
+
267
+ self._snowpark_session = Session.builder.configs(
268
+ {"connection": self._conn}
269
+ ).create()
270
+ return self._snowpark_session
271
+
272
+
257
273
  class VerboseCursor(SnowflakeCursor):
258
274
  def execute(self, command: str, *args, **kwargs):
259
275
  cli_console.message(command)
@@ -26,7 +26,7 @@ from snowflake.cli.api.project.schemas.project_definition import (
26
26
  build_project_definition,
27
27
  )
28
28
  from snowflake.cli.api.project.schemas.updatable_model import context
29
- from snowflake.cli.api.rendering.jinja import CONTEXT_KEY
29
+ from snowflake.cli.api.rendering.jinja import CONTEXT_KEY, FUNCTION_KEY
30
30
  from snowflake.cli.api.rendering.project_definition_templates import (
31
31
  get_project_definition_cli_jinja_env,
32
32
  )
@@ -344,7 +344,7 @@ def render_definition_template(
344
344
  _validate_env_section(definition.get("env", {}))
345
345
 
346
346
  # add available templating functions
347
- project_context["fn"] = get_templating_functions()
347
+ project_context[FUNCTION_KEY] = get_templating_functions()
348
348
 
349
349
  referenced_vars = _get_referenced_vars_in_definition(template_env, definition)
350
350
 
@@ -374,10 +374,22 @@ def render_definition_template(
374
374
  )
375
375
 
376
376
  project_definition = build_project_definition(**definition)
377
+
378
+ # Use the values originally provided by the user as the template context
379
+ # This intentionally doesn't reflect any field changes made by
380
+ # validators, to minimize user surprise when templating values
377
381
  project_context[CONTEXT_KEY] = definition
382
+
378
383
  # Use `ProjectEnvironment` in project context in order to
379
384
  # handle env variables overrides from OS env and from CLI arguments.
380
385
  project_context[CONTEXT_KEY]["env"] = ProjectEnvironment(
381
386
  default_env=project_context[CONTEXT_KEY].get("env"), override_env=override_env
382
387
  )
383
388
  return ProjectProperties(project_definition, project_context)
389
+
390
+
391
+ def raw_project_properties(definition: Definition) -> ProjectProperties:
392
+ """
393
+ Returns the raw project definition data without any templating.
394
+ """
395
+ return ProjectProperties(build_project_definition(**definition), {})
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: snowflake-cli-labs
3
- Version: 2.8.0rc0
3
+ Version: 3.0.0rc0
4
4
  Summary: Snowflake CLI
5
5
  Project-URL: Source code, https://github.com/snowflakedb/snowflake-cli
6
6
  Project-URL: Bug Tracker, https://github.com/snowflakedb/snowflake-cli/issues
@@ -216,7 +216,7 @@ Classifier: License :: OSI Approved :: Apache Software License
216
216
  Classifier: Programming Language :: Python :: 3 :: Only
217
217
  Classifier: Programming Language :: SQL
218
218
  Classifier: Topic :: Database
219
- Requires-Python: >=3.8
219
+ Requires-Python: >=3.10
220
220
  Requires-Dist: gitpython==3.1.43
221
221
  Requires-Dist: jinja2==3.1.4
222
222
  Requires-Dist: packaging
@@ -225,20 +225,20 @@ Requires-Dist: pluggy==1.5.0
225
225
  Requires-Dist: pydantic==2.8.2
226
226
  Requires-Dist: pyyaml==6.0.1
227
227
  Requires-Dist: requests==2.32.3
228
- Requires-Dist: requirements-parser==0.9.0
228
+ Requires-Dist: requirements-parser==0.10.2
229
229
  Requires-Dist: rich==13.7.1
230
230
  Requires-Dist: setuptools==70.3.0
231
- Requires-Dist: snowflake-connector-python[secure-local-storage]==3.11.0
231
+ Requires-Dist: snowflake-connector-python[secure-local-storage]==3.12.0
232
232
  Requires-Dist: snowflake-core==0.8.0; python_version < '3.12'
233
233
  Requires-Dist: snowflake-snowpark-python>=1.15.0; python_version < '3.12'
234
- Requires-Dist: tomlkit==0.13.0
235
- Requires-Dist: typer==0.12.3
234
+ Requires-Dist: tomlkit==0.13.2
235
+ Requires-Dist: typer==0.12.4
236
236
  Requires-Dist: urllib3<2.3,>=1.24.3
237
237
  Provides-Extra: development
238
- Requires-Dist: coverage==7.6.0; extra == 'development'
238
+ Requires-Dist: coverage==7.6.1; extra == 'development'
239
239
  Requires-Dist: pre-commit>=3.5.0; extra == 'development'
240
240
  Requires-Dist: pytest-randomly==3.15.0; extra == 'development'
241
- Requires-Dist: pytest==8.3.1; extra == 'development'
241
+ Requires-Dist: pytest==8.3.2; extra == 'development'
242
242
  Requires-Dist: syrupy==4.6.1; extra == 'development'
243
243
  Description-Content-Type: text/markdown
244
244
 
@@ -282,12 +282,12 @@ Cheatsheet: https://github.com/Snowflake-Labs/sf-cheatsheets/blob/main/snowflake
282
282
 
283
283
  ## Install Snowflake CLI
284
284
 
285
- ### Install with pip (PyPi)
285
+ ### Install with pipx (PyPi)
286
286
 
287
- Requires Python >= 3.8
287
+ We recommend installing Snowflake CLI in isolated environment using [pipx](https://pipx.pypa.io/stable/). Requires Python >= 3.10
288
288
 
289
289
  ```bash
290
- pip install snowflake-cli-labs
290
+ pipx install snowflake-cli-labs
291
291
  snow --help
292
292
  ```
293
293
 
@@ -303,7 +303,7 @@ snow --help
303
303
 
304
304
  ### Install from source
305
305
 
306
- Requires Python >= 3.8 and git
306
+ Requires Python >= 3.10 and git
307
307
 
308
308
  ```bash
309
309
  git clone https://github.com/snowflakedb/snowflake-cli