snowflake-cli-labs 3.0.0rc0__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 (51) hide show
  1. snowflake/cli/__about__.py +1 -1
  2. snowflake/cli/_app/snow_connector.py +18 -11
  3. snowflake/cli/_plugins/connection/commands.py +3 -2
  4. snowflake/cli/_plugins/git/manager.py +14 -6
  5. snowflake/cli/_plugins/nativeapp/codegen/compiler.py +18 -2
  6. snowflake/cli/_plugins/nativeapp/codegen/setup/native_app_setup_processor.py +123 -42
  7. snowflake/cli/_plugins/nativeapp/codegen/setup/setup_driver.py.source +5 -2
  8. snowflake/cli/_plugins/nativeapp/codegen/snowpark/python_processor.py +4 -6
  9. snowflake/cli/_plugins/nativeapp/codegen/templates/templates_processor.py +93 -0
  10. snowflake/cli/_plugins/nativeapp/exceptions.py +3 -3
  11. snowflake/cli/_plugins/nativeapp/manager.py +29 -58
  12. snowflake/cli/_plugins/nativeapp/project_model.py +2 -9
  13. snowflake/cli/_plugins/nativeapp/teardown_processor.py +19 -105
  14. snowflake/cli/_plugins/snowpark/commands.py +5 -65
  15. snowflake/cli/_plugins/snowpark/common.py +17 -1
  16. snowflake/cli/_plugins/snowpark/package/anaconda_packages.py +1 -35
  17. snowflake/cli/_plugins/sql/commands.py +1 -2
  18. snowflake/cli/_plugins/stage/commands.py +2 -2
  19. snowflake/cli/_plugins/stage/manager.py +46 -15
  20. snowflake/cli/_plugins/streamlit/commands.py +4 -63
  21. snowflake/cli/_plugins/streamlit/manager.py +4 -0
  22. snowflake/cli/_plugins/workspace/action_context.py +6 -0
  23. snowflake/cli/_plugins/workspace/commands.py +103 -22
  24. snowflake/cli/_plugins/workspace/manager.py +20 -4
  25. snowflake/cli/api/cli_global_context.py +6 -6
  26. snowflake/cli/api/commands/decorators.py +1 -1
  27. snowflake/cli/api/commands/flags.py +31 -12
  28. snowflake/cli/api/commands/snow_typer.py +9 -2
  29. snowflake/cli/api/config.py +17 -4
  30. snowflake/cli/api/constants.py +11 -0
  31. snowflake/cli/api/entities/application_package_entity.py +296 -3
  32. snowflake/cli/api/entities/common.py +6 -2
  33. snowflake/cli/api/entities/utils.py +46 -10
  34. snowflake/cli/api/exceptions.py +12 -2
  35. snowflake/cli/api/feature_flags.py +0 -2
  36. snowflake/cli/api/project/definition.py +24 -1
  37. snowflake/cli/api/project/definition_conversion.py +194 -0
  38. snowflake/cli/api/project/schemas/entities/application_package_entity_model.py +17 -0
  39. snowflake/cli/api/project/schemas/project_definition.py +1 -4
  40. snowflake/cli/api/rendering/jinja.py +2 -16
  41. snowflake/cli/api/rendering/project_definition_templates.py +1 -1
  42. snowflake/cli/api/rendering/sql_templates.py +7 -4
  43. snowflake/cli/api/secure_path.py +13 -18
  44. snowflake/cli/api/secure_utils.py +90 -1
  45. snowflake/cli/api/sql_execution.py +13 -0
  46. snowflake/cli/api/utils/definition_rendering.py +4 -6
  47. {snowflake_cli_labs-3.0.0rc0.dist-info → snowflake_cli_labs-3.0.0rc1.dist-info}/METADATA +5 -5
  48. {snowflake_cli_labs-3.0.0rc0.dist-info → snowflake_cli_labs-3.0.0rc1.dist-info}/RECORD +51 -49
  49. {snowflake_cli_labs-3.0.0rc0.dist-info → snowflake_cli_labs-3.0.0rc1.dist-info}/WHEEL +0 -0
  50. {snowflake_cli_labs-3.0.0rc0.dist-info → snowflake_cli_labs-3.0.0rc1.dist-info}/entry_points.txt +0 -0
  51. {snowflake_cli_labs-3.0.0rc0.dist-info → snowflake_cli_labs-3.0.0rc1.dist-info}/licenses/LICENSE +0 -0
@@ -14,7 +14,6 @@
14
14
 
15
15
  from __future__ import annotations
16
16
 
17
- import json
18
17
  import time
19
18
  from abc import ABC, abstractmethod
20
19
  from contextlib import contextmanager
@@ -37,9 +36,7 @@ from snowflake.cli._plugins.nativeapp.constants import (
37
36
  NAME_COL,
38
37
  )
39
38
  from snowflake.cli._plugins.nativeapp.exceptions import (
40
- ApplicationPackageDoesNotExistError,
41
39
  NoEventTableForAccount,
42
- SetupScriptFailedValidation,
43
40
  )
44
41
  from snowflake.cli._plugins.nativeapp.project_model import (
45
42
  NativeAppProjectModel,
@@ -47,7 +44,6 @@ from snowflake.cli._plugins.nativeapp.project_model import (
47
44
  from snowflake.cli._plugins.stage.diff import (
48
45
  DiffResult,
49
46
  )
50
- from snowflake.cli._plugins.stage.manager import StageManager
51
47
  from snowflake.cli.api.console import cli_console as cc
52
48
  from snowflake.cli.api.entities.application_package_entity import (
53
49
  ApplicationPackageEntity,
@@ -57,10 +53,6 @@ from snowflake.cli.api.entities.utils import (
57
53
  generic_sql_error_handler,
58
54
  sync_deploy_root_with_stage,
59
55
  )
60
- from snowflake.cli.api.errno import (
61
- DOES_NOT_EXIST_OR_NOT_AUTHORIZED,
62
- )
63
- from snowflake.cli.api.exceptions import SnowflakeSQLExecutionError
64
56
  from snowflake.cli.api.project.schemas.entities.common import PostDeployHook
65
57
  from snowflake.cli.api.project.schemas.native_app.native_app import NativeApp
66
58
  from snowflake.cli.api.project.schemas.native_app.path_mapping import PathMapping
@@ -381,59 +373,38 @@ class NativeAppManager(SqlExecutionMixin):
381
373
 
382
374
  return diff
383
375
 
384
- def validate(self, use_scratch_stage: bool = False):
385
- """Validates Native App setup script SQL."""
386
- with cc.phase(f"Validating Snowflake Native App setup script."):
387
- validation_result = self.get_validation_result(use_scratch_stage)
388
-
389
- # First print warnings, regardless of the outcome of validation
390
- for warning in validation_result.get("warnings", []):
391
- cc.warning(_validation_item_to_str(warning))
392
-
393
- # Then print errors
394
- for error in validation_result.get("errors", []):
395
- # Print them as warnings for now since we're going to be
396
- # revamping CLI output soon
397
- cc.warning(_validation_item_to_str(error))
376
+ def deploy_to_scratch_stage_fn(self):
377
+ bundle_map = self.build_bundle()
378
+ self.deploy(
379
+ bundle_map=bundle_map,
380
+ prune=True,
381
+ recursive=True,
382
+ stage_fqn=self.scratch_stage_fqn,
383
+ validate=False,
384
+ print_diff=False,
385
+ )
398
386
 
399
- # Then raise an exception if validation failed
400
- if validation_result["status"] == "FAIL":
401
- raise SetupScriptFailedValidation()
387
+ def validate(self, use_scratch_stage: bool = False):
388
+ return ApplicationPackageEntity.validate_setup_script(
389
+ console=cc,
390
+ package_name=self.package_name,
391
+ package_role=self.package_role,
392
+ stage_fqn=self.stage_fqn,
393
+ use_scratch_stage=use_scratch_stage,
394
+ scratch_stage_fqn=self.scratch_stage_fqn,
395
+ deploy_to_scratch_stage_fn=self.deploy_to_scratch_stage_fn,
396
+ )
402
397
 
403
398
  def get_validation_result(self, use_scratch_stage: bool):
404
- """Call system$validate_native_app_setup() to validate deployed Native App setup script."""
405
- stage_fqn = self.stage_fqn
406
- if use_scratch_stage:
407
- stage_fqn = self.scratch_stage_fqn
408
- bundle_map = self.build_bundle()
409
- self.deploy(
410
- bundle_map=bundle_map,
411
- prune=True,
412
- recursive=True,
413
- stage_fqn=stage_fqn,
414
- validate=False,
415
- print_diff=False,
416
- )
417
- prefixed_stage_fqn = StageManager.get_standard_stage_prefix(stage_fqn)
418
- try:
419
- cursor = self._execute_query(
420
- f"call system$validate_native_app_setup('{prefixed_stage_fqn}')"
421
- )
422
- except ProgrammingError as err:
423
- if err.errno == DOES_NOT_EXIST_OR_NOT_AUTHORIZED:
424
- raise ApplicationPackageDoesNotExistError(self.package_name)
425
- generic_sql_error_handler(err)
426
- else:
427
- if not cursor.rowcount:
428
- raise SnowflakeSQLExecutionError()
429
- return json.loads(cursor.fetchone()[0])
430
- finally:
431
- if use_scratch_stage:
432
- cc.step(f"Dropping stage {self.scratch_stage_fqn}.")
433
- with self.use_role(self.package_role):
434
- self._execute_query(
435
- f"drop stage if exists {self.scratch_stage_fqn}"
436
- )
399
+ return ApplicationPackageEntity.get_validation_result(
400
+ console=cc,
401
+ package_name=self.package_name,
402
+ package_role=self.package_role,
403
+ stage_fqn=self.stage_fqn,
404
+ use_scratch_stage=use_scratch_stage,
405
+ scratch_stage_fqn=self.scratch_stage_fqn,
406
+ deploy_to_scratch_stage_fn=self.deploy_to_scratch_stage_fn,
407
+ )
437
408
 
438
409
  def get_events( # type: ignore [return]
439
410
  self,
@@ -21,6 +21,7 @@ from typing import List, Optional
21
21
  from snowflake.cli._plugins.nativeapp.artifacts import resolve_without_follow
22
22
  from snowflake.cli._plugins.nativeapp.bundle_context import BundleContext
23
23
  from snowflake.cli.api.cli_global_context import get_cli_context
24
+ from snowflake.cli.api.entities.common import get_sql_executor
24
25
  from snowflake.cli.api.project.definition import (
25
26
  default_app_package,
26
27
  default_application,
@@ -34,14 +35,6 @@ from snowflake.cli.api.project.util import (
34
35
  extract_schema,
35
36
  to_identifier,
36
37
  )
37
- from snowflake.connector import DictCursor
38
-
39
-
40
- def current_role() -> str:
41
- conn = get_cli_context().connection
42
- *_, cursor = conn.execute_string("select current_role()", cursor_class=DictCursor)
43
- role_result = cursor.fetchone()
44
- return role_result["CURRENT_ROLE()"]
45
38
 
46
39
 
47
40
  class NativeAppProjectModel:
@@ -198,7 +191,7 @@ class NativeAppProjectModel:
198
191
  def _default_role(self) -> str:
199
192
  role = default_role()
200
193
  if role is None:
201
- role = current_role()
194
+ role = get_sql_executor().current_role()
202
195
  return role
203
196
 
204
197
  @cached_property
@@ -22,13 +22,8 @@ import typer
22
22
  from snowflake.cli._plugins.nativeapp.constants import (
23
23
  ALLOWED_SPECIAL_COMMENTS,
24
24
  COMMENT_COL,
25
- EXTERNAL_DISTRIBUTION,
26
- INTERNAL_DISTRIBUTION,
27
25
  OWNER_COL,
28
26
  )
29
- from snowflake.cli._plugins.nativeapp.exceptions import (
30
- CouldNotDropApplicationPackageWithVersions,
31
- )
32
27
  from snowflake.cli._plugins.nativeapp.manager import (
33
28
  NativeAppCommandProcessor,
34
29
  NativeAppManager,
@@ -37,11 +32,15 @@ from snowflake.cli._plugins.nativeapp.utils import (
37
32
  needs_confirmation,
38
33
  )
39
34
  from snowflake.cli.api.console import cli_console as cc
40
- from snowflake.cli.api.entities.utils import ensure_correct_owner
35
+ from snowflake.cli.api.entities.application_package_entity import (
36
+ ApplicationPackageEntity,
37
+ )
38
+ from snowflake.cli.api.entities.utils import (
39
+ drop_generic_object,
40
+ ensure_correct_owner,
41
+ )
41
42
  from snowflake.cli.api.errno import APPLICATION_NO_LONGER_AVAILABLE
42
- from snowflake.cli.api.exceptions import SnowflakeSQLExecutionError
43
43
  from snowflake.connector import ProgrammingError
44
- from snowflake.connector.cursor import DictCursor
45
44
 
46
45
 
47
46
  class NativeAppTeardownProcessor(NativeAppManager, NativeAppCommandProcessor):
@@ -51,20 +50,13 @@ class NativeAppTeardownProcessor(NativeAppManager, NativeAppCommandProcessor):
51
50
  def drop_generic_object(
52
51
  self, object_type: str, object_name: str, role: str, cascade: bool = False
53
52
  ):
54
- """
55
- Drop object using the given role.
56
- """
57
- with self.use_role(role):
58
- cc.step(f"Dropping {object_type} {object_name} now.")
59
- drop_query = f"drop {object_type} {object_name}"
60
- if cascade:
61
- drop_query += " cascade"
62
- try:
63
- self._execute_query(drop_query)
64
- except:
65
- raise SnowflakeSQLExecutionError(drop_query)
66
-
67
- cc.message(f"Dropped {object_type} {object_name} successfully.")
53
+ return drop_generic_object(
54
+ console=cc,
55
+ object_type=object_type,
56
+ object_name=object_name,
57
+ role=role,
58
+ cascade=cascade,
59
+ )
68
60
 
69
61
  def drop_application(
70
62
  self, auto_yes: bool, interactive: bool = False, cascade: Optional[bool] = None
@@ -198,90 +190,12 @@ class NativeAppTeardownProcessor(NativeAppManager, NativeAppCommandProcessor):
198
190
  return # The application object was successfully dropped, therefore exit gracefully
199
191
 
200
192
  def drop_package(self, auto_yes: bool):
201
- """
202
- Attempts to drop application package unless user specifies otherwise.
203
- """
204
- needs_confirm = True
205
-
206
- # 1. If existing application package is not found, exit gracefully
207
- show_obj_row = self.get_existing_app_pkg_info()
208
- if show_obj_row is None:
209
- cc.warning(
210
- f"Role {self.package_role} does not own any application package with the name {self.package_name}, or the application package does not exist."
211
- )
212
- return
213
-
214
- # 2. Check for the right owner
215
- ensure_correct_owner(
216
- row=show_obj_row, role=self.package_role, obj_name=self.package_name
217
- )
218
-
219
- with self.use_role(self.package_role):
220
- # 3. Check for versions in the application package
221
- show_versions_query = (
222
- f"show versions in application package {self.package_name}"
223
- )
224
- show_versions_cursor = self._execute_query(
225
- show_versions_query, cursor_class=DictCursor
226
- )
227
- if show_versions_cursor.rowcount is None:
228
- raise SnowflakeSQLExecutionError(show_versions_query)
229
-
230
- if show_versions_cursor.rowcount > 0:
231
- # allow dropping a package with versions when --force is set
232
- if not auto_yes:
233
- raise CouldNotDropApplicationPackageWithVersions(
234
- "Drop versions first, or use --force to override."
235
- )
236
-
237
- # 4. Check distribution of the existing application package
238
- actual_distribution = self.get_app_pkg_distribution_in_snowflake
239
- if not self.verify_project_distribution(actual_distribution):
240
- cc.warning(
241
- f"Continuing to execute `snow app teardown` on application package {self.package_name} with distribution '{actual_distribution}'."
242
- )
243
-
244
- # 5. If distribution is internal, check if created by the Snowflake CLI
245
- row_comment = show_obj_row[COMMENT_COL]
246
- if actual_distribution == INTERNAL_DISTRIBUTION:
247
- if row_comment in ALLOWED_SPECIAL_COMMENTS:
248
- needs_confirm = False
249
- else:
250
- if needs_confirmation(needs_confirm, auto_yes):
251
- cc.warning(
252
- f"Application package {self.package_name} was not created by Snowflake CLI."
253
- )
254
- else:
255
- if needs_confirmation(needs_confirm, auto_yes):
256
- cc.warning(
257
- f"Application package {self.package_name} in your Snowflake account has distribution property '{EXTERNAL_DISTRIBUTION}' and could be associated with one or more of your listings on Snowflake Marketplace."
258
- )
259
-
260
- if needs_confirmation(needs_confirm, auto_yes):
261
- should_drop_object = typer.confirm(
262
- dedent(
263
- f"""\
264
- Application package details:
265
- Name: {self.app_name}
266
- Created on: {show_obj_row["created_on"]}
267
- Distribution: {actual_distribution}
268
- Owner: {show_obj_row[OWNER_COL]}
269
- Comment: {show_obj_row[COMMENT_COL]}
270
- Are you sure you want to drop it?
271
- """
272
- )
273
- )
274
- if not should_drop_object:
275
- cc.message(f"Did not drop application package {self.package_name}.")
276
- return # The user desires to keep the application package, therefore exit gracefully
277
-
278
- # All validations have passed, drop object
279
- self.drop_generic_object(
280
- object_type="application package",
281
- object_name=self.package_name,
282
- role=self.package_role,
193
+ return ApplicationPackageEntity.drop(
194
+ console=cc,
195
+ package_name=self.package_name,
196
+ package_role=self.package_role,
197
+ force_drop=auto_yes,
283
198
  )
284
- return # The application package was successfully dropped, therefore exit gracefully
285
199
 
286
200
  def process(
287
201
  self,
@@ -75,7 +75,6 @@ from snowflake.cli.api.constants import (
75
75
  DEFAULT_SIZE_LIMIT_MB,
76
76
  )
77
77
  from snowflake.cli.api.exceptions import (
78
- NoProjectDefinitionError,
79
78
  SecretsWithoutExternalAccessIntegrationError,
80
79
  )
81
80
  from snowflake.cli.api.identifiers import FQN
@@ -85,6 +84,9 @@ from snowflake.cli.api.output.types import (
85
84
  MessageResult,
86
85
  SingleQueryResult,
87
86
  )
87
+ from snowflake.cli.api.project.definition_conversion import (
88
+ convert_project_definition_to_v2,
89
+ )
88
90
  from snowflake.cli.api.project.schemas.entities.snowpark_entity import (
89
91
  FunctionEntityModel,
90
92
  ProcedureEntityModel,
@@ -93,10 +95,6 @@ from snowflake.cli.api.project.schemas.project_definition import (
93
95
  ProjectDefinition,
94
96
  ProjectDefinitionV2,
95
97
  )
96
- from snowflake.cli.api.project.schemas.snowpark.callable import (
97
- FunctionSchema,
98
- ProcedureSchema,
99
- )
100
98
  from snowflake.cli.api.secure_path import SecurePath
101
99
  from snowflake.connector import DictCursor, ProgrammingError
102
100
  from snowflake.connector.cursor import SnowflakeCursor
@@ -123,7 +121,7 @@ LikeOption = like_option(
123
121
  )
124
122
 
125
123
 
126
- @app.command("deploy", requires_connection=True)
124
+ @app.command("deploy", requires_connection=True, require_warehouse=True)
127
125
  @with_project_definition()
128
126
  def deploy(
129
127
  replace: bool = ReplaceOption(
@@ -445,66 +443,8 @@ def describe(
445
443
  )
446
444
 
447
445
 
448
- def migrate_v1_snowpark_to_v2(pd: ProjectDefinition):
449
- if not pd.snowpark:
450
- raise NoProjectDefinitionError(
451
- project_type="snowpark", project_root=get_cli_context().project_root
452
- )
453
-
454
- artifact_mapping = {"src": pd.snowpark.src}
455
- if pd.snowpark.project_name:
456
- artifact_mapping["dest"] = pd.snowpark.project_name
457
-
458
- snowpark_shared_mixin = "snowpark_shared"
459
- data: dict = {
460
- "definition_version": "2",
461
- "mixins": {
462
- snowpark_shared_mixin: {
463
- "stage": pd.snowpark.stage_name,
464
- "artifacts": [artifact_mapping],
465
- }
466
- },
467
- "entities": {},
468
- }
469
-
470
- for index, entity in enumerate([*pd.snowpark.procedures, *pd.snowpark.functions]):
471
- identifier = {"name": entity.name}
472
- if entity.database is not None:
473
- identifier["database"] = entity.database
474
- if entity.schema_name is not None:
475
- identifier["schema"] = entity.schema_name
476
-
477
- if entity.name.startswith("<%") and entity.name.endswith("%>"):
478
- entity_name = f"snowpark_entity_{index}"
479
- else:
480
- entity_name = entity.name
481
-
482
- v2_entity = {
483
- "type": "function" if isinstance(entity, FunctionSchema) else "procedure",
484
- "stage": pd.snowpark.stage_name,
485
- "handler": entity.handler,
486
- "returns": entity.returns,
487
- "signature": entity.signature,
488
- "runtime": entity.runtime,
489
- "external_access_integrations": entity.external_access_integrations,
490
- "secrets": entity.secrets,
491
- "imports": entity.imports,
492
- "identifier": identifier,
493
- "meta": {"use_mixins": [snowpark_shared_mixin]},
494
- }
495
- if isinstance(entity, ProcedureSchema):
496
- v2_entity["execute_as_caller"] = entity.execute_as_caller
497
-
498
- data["entities"][entity_name] = v2_entity
499
-
500
- if hasattr(pd, "env") and pd.env:
501
- data["env"] = {k: v for k, v in pd.env.items()}
502
-
503
- return ProjectDefinitionV2(**data)
504
-
505
-
506
446
  def _get_v2_project_definition(cli_context) -> ProjectDefinitionV2:
507
447
  pd = cli_context.project_definition
508
448
  if not pd.meets_version_requirement("2"):
509
- pd = migrate_v1_snowpark_to_v2(pd)
449
+ pd = convert_project_definition_to_v2(pd)
510
450
  return pd
@@ -23,7 +23,13 @@ from click import UsageError
23
23
  from snowflake.cli._plugins.snowpark.models import Requirement
24
24
  from snowflake.cli._plugins.snowpark.snowpark_project_paths import Artefact
25
25
  from snowflake.cli.api.console import cli_console
26
- from snowflake.cli.api.constants import ObjectType
26
+ from snowflake.cli.api.constants import (
27
+ INIT_TEMPLATE_VARIABLE_CLOSING,
28
+ INIT_TEMPLATE_VARIABLE_OPENING,
29
+ PROJECT_TEMPLATE_VARIABLE_CLOSING,
30
+ PROJECT_TEMPLATE_VARIABLE_OPENING,
31
+ ObjectType,
32
+ )
27
33
  from snowflake.cli.api.project.schemas.entities.snowpark_entity import (
28
34
  ProcedureEntityModel,
29
35
  SnowparkEntityModel,
@@ -250,3 +256,13 @@ def _compare_imports(
250
256
  }
251
257
 
252
258
  return project_imports != object_imports
259
+
260
+
261
+ def is_name_a_templated_one(name: str) -> bool:
262
+ return (
263
+ PROJECT_TEMPLATE_VARIABLE_OPENING in name
264
+ and PROJECT_TEMPLATE_VARIABLE_CLOSING in name
265
+ ) or (
266
+ INIT_TEMPLATE_VARIABLE_OPENING in name
267
+ and INIT_TEMPLATE_VARIABLE_CLOSING in name
268
+ )
@@ -18,12 +18,9 @@ import logging
18
18
  from dataclasses import dataclass
19
19
  from typing import Dict, List, Set
20
20
 
21
- import requests
22
- from click import ClickException
23
21
  from packaging.requirements import InvalidRequirement
24
22
  from packaging.requirements import Requirement as PkgRequirement
25
23
  from packaging.version import InvalidVersion, parse
26
- from requests import HTTPError
27
24
  from snowflake.cli._plugins.snowpark.models import Requirement
28
25
  from snowflake.cli.api.exceptions import SnowflakeSQLExecutionError
29
26
  from snowflake.cli.api.secure_path import SecurePath
@@ -170,23 +167,13 @@ class AnacondaPackagesManager(SqlExecutionMixin):
170
167
  "https://repo.anaconda.com/pkgs/snowflake/channeldata.json"
171
168
  )
172
169
 
173
- # TODO in v3.0: Keep only SQL query, remove fallback to JSON with channel's metadata
174
170
  def find_packages_available_in_snowflake_anaconda(self) -> AnacondaPackages:
175
171
  """
176
172
  Finds python packages available in Snowflake to use in functions and stored procedures.
177
173
  It tries to get the list of packages using SQL query
178
174
  but if the try fails then the fallback is to parse JSON containing info about Snowflake's Anaconda channel.
179
175
  """
180
- try:
181
- packages = self._query_snowflake_for_available_packages()
182
- except Exception as ex:
183
- log.warning(
184
- "Cannot fetch available packages information from Snowflake. "
185
- "Please check your connection configuration. "
186
- "Fallback to Anaconda channel metadata."
187
- )
188
- log.debug("Available packages query failure: %s", ex.__str__(), exc_info=ex)
189
- packages = self._get_available_packages_from_anaconda_channel_info()
176
+ packages = self._query_snowflake_for_available_packages()
190
177
  return AnacondaPackages(packages)
191
178
 
192
179
  def _query_snowflake_for_available_packages(self) -> dict[str, AvailablePackage]:
@@ -210,24 +197,3 @@ class AnacondaPackagesManager(SqlExecutionMixin):
210
197
  snowflake_name=package_name, versions={version}
211
198
  )
212
199
  return packages
213
-
214
- def _get_available_packages_from_anaconda_channel_info(
215
- self,
216
- ) -> dict[str, AvailablePackage]:
217
- try:
218
- response = requests.get(self._snowflake_channel_url)
219
- response.raise_for_status()
220
- packages = {}
221
- for key, package in response.json()["packages"].items():
222
- if not (version := package.get("version")):
223
- continue
224
- package_name = package.get("name", key)
225
- standardized_name = Requirement.standardize_name(package_name)
226
- packages[standardized_name] = AvailablePackage(
227
- snowflake_name=package_name, versions={version}
228
- )
229
- return packages
230
- except HTTPError as err:
231
- raise ClickException(
232
- f"Accessing Snowflake Anaconda channel failed. Reason {err}"
233
- )
@@ -73,8 +73,7 @@ def execute_sql(
73
73
  Query to execute can be specified using query option, filename option (all queries from file will be executed)
74
74
  or via stdin by piping output from other command. For example `cat my.sql | snow sql -i`.
75
75
 
76
- The command supports variable substitution that happens on client-side. Both &VARIABLE or &{ VARIABLE }
77
- syntax are supported.
76
+ The command supports variable substitution that happens on client-side.
78
77
  """
79
78
 
80
79
  data = {}
@@ -37,7 +37,7 @@ from snowflake.cli.api.commands.flags import (
37
37
  ExecuteVariablesOption,
38
38
  OnErrorOption,
39
39
  PatternOption,
40
- identifier_argument,
40
+ identifier_stage_argument,
41
41
  like_option,
42
42
  )
43
43
  from snowflake.cli.api.commands.snow_typer import SnowTyperFactory
@@ -59,7 +59,7 @@ app = SnowTyperFactory(
59
59
  help="Manages stages.",
60
60
  )
61
61
 
62
- StageNameArgument = identifier_argument(sf_object="stage", example="@my_stage")
62
+ StageNameArgument = identifier_stage_argument(sf_object="stage", example="@my_stage")
63
63
 
64
64
  add_object_command_aliases(
65
65
  app=app,
@@ -65,14 +65,21 @@ class StagePathParts:
65
65
  stage_name: str
66
66
  is_directory: bool
67
67
 
68
- @staticmethod
69
- def get_directory(stage_path: str) -> str:
68
+ @classmethod
69
+ def get_directory(cls, stage_path: str) -> str:
70
70
  return "/".join(Path(stage_path).parts[1:])
71
71
 
72
72
  @property
73
73
  def path(self) -> str:
74
74
  raise NotImplementedError
75
75
 
76
+ @property
77
+ def full_path(self) -> str:
78
+ raise NotImplementedError
79
+
80
+ def replace_stage_prefix(self, file_path: str) -> str:
81
+ raise NotImplementedError
82
+
76
83
  def add_stage_prefix(self, file_path: str) -> str:
77
84
  raise NotImplementedError
78
85
 
@@ -112,24 +119,27 @@ class DefaultStagePathParts(StagePathParts):
112
119
  self.directory = self.get_directory(stage_path)
113
120
  self.stage = StageManager.get_stage_from_path(stage_path)
114
121
  stage_name = self.stage.split(".")[-1]
115
- if stage_name.startswith("@"):
116
- stage_name = stage_name[1:]
122
+ stage_name = stage_name[1:] if stage_name.startswith("@") else stage_name
117
123
  self.stage_name = stage_name
118
124
  self.is_directory = True if stage_path.endswith("/") else False
119
125
 
120
126
  @property
121
127
  def path(self) -> str:
122
- return (
123
- f"{self.stage_name}{self.directory}"
124
- if self.stage_name.endswith("/")
125
- else f"{self.stage_name}/{self.directory}"
126
- )
128
+ return f"{self.stage_name.rstrip('/')}/{self.directory}"
127
129
 
128
- def add_stage_prefix(self, file_path: str) -> str:
130
+ @property
131
+ def full_path(self) -> str:
132
+ return f"{self.stage.rstrip('/')}/{self.directory}"
133
+
134
+ def replace_stage_prefix(self, file_path: str) -> str:
129
135
  stage = Path(self.stage).parts[0]
130
136
  file_path_without_prefix = Path(file_path).parts[1:]
131
137
  return f"{stage}/{'/'.join(file_path_without_prefix)}"
132
138
 
139
+ def add_stage_prefix(self, file_path: str) -> str:
140
+ stage = self.stage.rstrip("/")
141
+ return f"{stage}/{file_path.lstrip('/')}"
142
+
133
143
  def get_directory_from_file_path(self, file_path: str) -> List[str]:
134
144
  stage_path_length = len(Path(self.directory).parts)
135
145
  return list(Path(file_path).parts[1 + stage_path_length : -1])
@@ -146,14 +156,29 @@ class UserStagePathParts(StagePathParts):
146
156
 
147
157
  def __init__(self, stage_path: str):
148
158
  self.directory = self.get_directory(stage_path)
149
- self.stage = "@~"
150
- self.stage_name = "@~"
159
+ self.stage = USER_STAGE_PREFIX
160
+ self.stage_name = USER_STAGE_PREFIX
151
161
  self.is_directory = True if stage_path.endswith("/") else False
152
162
 
163
+ @classmethod
164
+ def get_directory(cls, stage_path: str) -> str:
165
+ if Path(stage_path).parts[0] == USER_STAGE_PREFIX:
166
+ return super().get_directory(stage_path)
167
+ return stage_path
168
+
153
169
  @property
154
170
  def path(self) -> str:
155
171
  return f"{self.directory}"
156
172
 
173
+ @property
174
+ def full_path(self) -> str:
175
+ return f"{self.stage}/{self.directory}"
176
+
177
+ def replace_stage_prefix(self, file_path: str) -> str:
178
+ if Path(file_path).parts[0] == self.stage_name:
179
+ return file_path
180
+ return f"{self.stage}/{file_path}"
181
+
157
182
  def add_stage_prefix(self, file_path: str) -> str:
158
183
  return f"{self.stage}/{file_path}"
159
184
 
@@ -241,7 +266,7 @@ class StageManager(SqlExecutionMixin):
241
266
  self._assure_is_existing_directory(dest_directory)
242
267
 
243
268
  result = self._execute_query(
244
- f"get {self.quote_stage_name(stage_path_parts.add_stage_prefix(file_path))} {self._to_uri(f'{dest_directory}/')} parallel={parallel}"
269
+ f"get {self.quote_stage_name(stage_path_parts.replace_stage_prefix(file_path))} {self._to_uri(f'{dest_directory}/')} parallel={parallel}"
245
270
  )
246
271
  results.append(result)
247
272
 
@@ -321,8 +346,14 @@ class StageManager(SqlExecutionMixin):
321
346
  stage_path_parts = self._stage_path_part_factory(stage_path)
322
347
  all_files_list = self._get_files_list_from_stage(stage_path_parts)
323
348
 
349
+ all_files_with_stage_name_prefix = [
350
+ stage_path_parts.get_directory(file) for file in all_files_list
351
+ ]
352
+
324
353
  # filter files from stage if match stage_path pattern
325
- filtered_file_list = self._filter_files_list(stage_path_parts, all_files_list)
354
+ filtered_file_list = self._filter_files_list(
355
+ stage_path_parts, all_files_with_stage_name_prefix
356
+ )
326
357
 
327
358
  if not filtered_file_list:
328
359
  raise ClickException(f"No files matched pattern '{stage_path}'")
@@ -378,7 +409,7 @@ class StageManager(SqlExecutionMixin):
378
409
  if not stage_path_parts.directory:
379
410
  return self._filter_supported_files(files_on_stage)
380
411
 
381
- stage_path = stage_path_parts.path.lower()
412
+ stage_path = stage_path_parts.directory
382
413
 
383
414
  # Exact file path was provided if stage_path in file list
384
415
  if stage_path in files_on_stage: