snowflake-cli-labs 2.8.0rc1__py3-none-any.whl → 2.8.1__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 (28) hide show
  1. snowflake/cli/__about__.py +1 -1
  2. snowflake/cli/api/commands/flags.py +26 -4
  3. snowflake/cli/api/identifiers.py +17 -4
  4. snowflake/cli/api/sql_execution.py +4 -4
  5. snowflake/cli/plugins/git/commands.py +68 -19
  6. snowflake/cli/plugins/git/manager.py +19 -10
  7. snowflake/cli/plugins/init/commands.py +8 -4
  8. snowflake/cli/plugins/notebook/commands.py +6 -5
  9. snowflake/cli/plugins/notebook/manager.py +10 -10
  10. snowflake/cli/plugins/notebook/types.py +0 -1
  11. snowflake/cli/plugins/object/command_aliases.py +3 -2
  12. snowflake/cli/plugins/object/commands.py +13 -6
  13. snowflake/cli/plugins/object/manager.py +7 -6
  14. snowflake/cli/plugins/snowpark/commands.py +4 -6
  15. snowflake/cli/plugins/snowpark/models.py +2 -1
  16. snowflake/cli/plugins/snowpark/package/manager.py +2 -1
  17. snowflake/cli/plugins/spcs/compute_pool/commands.py +21 -20
  18. snowflake/cli/plugins/spcs/image_repository/commands.py +19 -13
  19. snowflake/cli/plugins/spcs/services/commands.py +23 -22
  20. snowflake/cli/plugins/stage/commands.py +7 -5
  21. snowflake/cli/plugins/stage/manager.py +51 -18
  22. snowflake/cli/plugins/streamlit/commands.py +7 -14
  23. snowflake/cli/plugins/streamlit/manager.py +1 -1
  24. {snowflake_cli_labs-2.8.0rc1.dist-info → snowflake_cli_labs-2.8.1.dist-info}/METADATA +1 -1
  25. {snowflake_cli_labs-2.8.0rc1.dist-info → snowflake_cli_labs-2.8.1.dist-info}/RECORD +28 -28
  26. {snowflake_cli_labs-2.8.0rc1.dist-info → snowflake_cli_labs-2.8.1.dist-info}/WHEEL +0 -0
  27. {snowflake_cli_labs-2.8.0rc1.dist-info → snowflake_cli_labs-2.8.1.dist-info}/entry_points.txt +0 -0
  28. {snowflake_cli_labs-2.8.0rc1.dist-info → snowflake_cli_labs-2.8.1.dist-info}/licenses/LICENSE +0 -0
@@ -173,9 +173,7 @@ def deploy(
173
173
  stage_name = snowpark.stage_name
174
174
  stage_manager = StageManager()
175
175
  stage_name = FQN.from_string(stage_name).using_context()
176
- stage_manager.create(
177
- stage_name=stage_name, comment="deployments managed by Snowflake CLI"
178
- )
176
+ stage_manager.create(fqn=stage_name, comment="deployments managed by Snowflake CLI")
179
177
 
180
178
  snowflake_dependencies = _read_snowflake_requrements_file(
181
179
  paths.snowflake_requirements_file
@@ -251,7 +249,7 @@ def _find_existing_objects(
251
249
  try:
252
250
  current_state = om.describe(
253
251
  object_type=object_type.value.sf_name,
254
- name=identifier,
252
+ fqn=FQN.from_string(identifier),
255
253
  )
256
254
  existing_objects[identifier] = current_state
257
255
  except ProgrammingError:
@@ -528,7 +526,7 @@ def list_(
528
526
  @app.command("drop", requires_connection=True)
529
527
  def drop(
530
528
  object_type: _SnowparkObject = ObjectTypeArgument,
531
- identifier: str = IdentifierArgument,
529
+ identifier: FQN = IdentifierArgument,
532
530
  **options,
533
531
  ):
534
532
  """Drop procedure or function."""
@@ -538,7 +536,7 @@ def drop(
538
536
  @app.command("describe", requires_connection=True)
539
537
  def describe(
540
538
  object_type: _SnowparkObject = ObjectTypeArgument,
541
- identifier: str = IdentifierArgument,
539
+ identifier: FQN = IdentifierArgument,
542
540
  **options,
543
541
  ):
544
542
  """Provides description of a procedure or function."""
@@ -128,13 +128,14 @@ class WheelMetadata:
128
128
  if line.startswith(dep_keyword)
129
129
  ]
130
130
  name = cls._get_name_from_wheel_filename(wheel_path.name)
131
+
131
132
  return cls(name=name, wheel_path=wheel_path, dependencies=dependencies)
132
133
 
133
134
  @staticmethod
134
135
  def _get_name_from_wheel_filename(wheel_filename: str) -> str:
135
136
  # wheel filename is in format {name}-{version}[-{extra info}]
136
137
  # https://peps.python.org/pep-0491/#file-name-convention
137
- return wheel_filename.split("-")[0]
138
+ return wheel_filename.split("-")[0].lower()
138
139
 
139
140
  @staticmethod
140
141
  def to_wheel_name_format(package_name: str) -> str:
@@ -17,6 +17,7 @@ from __future__ import annotations
17
17
  import logging
18
18
  from pathlib import Path
19
19
 
20
+ from snowflake.cli.api.identifiers import FQN
20
21
  from snowflake.cli.api.secure_path import SecurePath
21
22
  from snowflake.cli.plugins.snowpark.package.utils import prepare_app_zip
22
23
  from snowflake.cli.plugins.stage.manager import StageManager
@@ -30,7 +31,7 @@ def upload(file: Path, stage: str, overwrite: bool):
30
31
  temp_app_zip_path = prepare_app_zip(SecurePath(file), temp_dir)
31
32
  sm = StageManager()
32
33
 
33
- sm.create(sm.get_stage_from_path(stage))
34
+ sm.create(FQN.from_string(sm.get_stage_from_path(stage)))
34
35
  put_response = sm.put(
35
36
  temp_app_zip_path.path, stage, overwrite=overwrite
36
37
  ).fetchone()
@@ -21,10 +21,12 @@ from click import ClickException
21
21
  from snowflake.cli.api.commands.flags import (
22
22
  IfNotExistsOption,
23
23
  OverrideableOption,
24
+ identifier_argument,
24
25
  like_option,
25
26
  )
26
27
  from snowflake.cli.api.commands.snow_typer import SnowTyperFactory
27
28
  from snowflake.cli.api.constants import ObjectType
29
+ from snowflake.cli.api.identifiers import FQN
28
30
  from snowflake.cli.api.output.types import CommandResult, SingleQueryResult
29
31
  from snowflake.cli.api.project.util import is_valid_object_name
30
32
  from snowflake.cli.plugins.object.command_aliases import (
@@ -43,22 +45,21 @@ app = SnowTyperFactory(
43
45
  )
44
46
 
45
47
 
46
- def _compute_pool_name_callback(name: str) -> str:
48
+ def _compute_pool_name_callback(name: FQN) -> FQN:
47
49
  """
48
50
  Verifies that compute pool name is a single valid identifier.
49
51
  """
50
- if not is_valid_object_name(name, max_depth=0, allow_quoted=False):
52
+ if not is_valid_object_name(name.identifier, max_depth=0, allow_quoted=False):
51
53
  raise ClickException(
52
54
  f"'{name}' is not a valid compute pool name. Note that compute pool names must be unquoted identifiers."
53
55
  )
54
56
  return name
55
57
 
56
58
 
57
- ComputePoolNameArgument = typer.Argument(
58
- ...,
59
- help="Name of the compute pool.",
59
+ ComputePoolNameArgument = identifier_argument(
60
+ sf_object="compute pool",
61
+ example="my_compute_pool",
60
62
  callback=_compute_pool_name_callback,
61
- show_default=False,
62
63
  )
63
64
 
64
65
 
@@ -106,7 +107,7 @@ add_object_command_aliases(
106
107
 
107
108
  @app.command(requires_connection=True)
108
109
  def create(
109
- name: str = ComputePoolNameArgument,
110
+ name: FQN = ComputePoolNameArgument,
110
111
  instance_family: str = typer.Option(
111
112
  ...,
112
113
  "--family",
@@ -131,7 +132,7 @@ def create(
131
132
  """
132
133
  max_nodes = validate_and_set_instances(min_nodes, max_nodes, "nodes")
133
134
  cursor = ComputePoolManager().create(
134
- pool_name=name,
135
+ pool_name=name.identifier,
135
136
  min_nodes=min_nodes,
136
137
  max_nodes=max_nodes,
137
138
  instance_family=instance_family,
@@ -145,33 +146,33 @@ def create(
145
146
 
146
147
 
147
148
  @app.command("stop-all", requires_connection=True)
148
- def stop_all(name: str = ComputePoolNameArgument, **options) -> CommandResult:
149
+ def stop_all(name: FQN = ComputePoolNameArgument, **options) -> CommandResult:
149
150
  """
150
151
  Deletes all services running on the compute pool.
151
152
  """
152
- cursor = ComputePoolManager().stop(pool_name=name)
153
+ cursor = ComputePoolManager().stop(pool_name=name.identifier)
153
154
  return SingleQueryResult(cursor)
154
155
 
155
156
 
156
157
  @app.command(requires_connection=True)
157
- def suspend(name: str = ComputePoolNameArgument, **options) -> CommandResult:
158
+ def suspend(name: FQN = ComputePoolNameArgument, **options) -> CommandResult:
158
159
  """
159
160
  Suspends the compute pool by suspending all currently running services and then releasing compute pool nodes.
160
161
  """
161
- return SingleQueryResult(ComputePoolManager().suspend(name))
162
+ return SingleQueryResult(ComputePoolManager().suspend(name.identifier))
162
163
 
163
164
 
164
165
  @app.command(requires_connection=True)
165
- def resume(name: str = ComputePoolNameArgument, **options) -> CommandResult:
166
+ def resume(name: FQN = ComputePoolNameArgument, **options) -> CommandResult:
166
167
  """
167
168
  Resumes the compute pool from a SUSPENDED state.
168
169
  """
169
- return SingleQueryResult(ComputePoolManager().resume(name))
170
+ return SingleQueryResult(ComputePoolManager().resume(name.identifier))
170
171
 
171
172
 
172
173
  @app.command("set", requires_connection=True)
173
174
  def set_property(
174
- name: str = ComputePoolNameArgument,
175
+ name: FQN = ComputePoolNameArgument,
175
176
  min_nodes: Optional[int] = MinNodesOption(default=None, show_default=False),
176
177
  max_nodes: Optional[int] = MaxNodesOption(show_default=False),
177
178
  auto_resume: Optional[bool] = AutoResumeOption(default=None, show_default=False),
@@ -187,7 +188,7 @@ def set_property(
187
188
  Sets one or more properties for the compute pool.
188
189
  """
189
190
  cursor = ComputePoolManager().set_property(
190
- pool_name=name,
191
+ pool_name=name.identifier,
191
192
  min_nodes=min_nodes,
192
193
  max_nodes=max_nodes,
193
194
  auto_resume=auto_resume,
@@ -199,7 +200,7 @@ def set_property(
199
200
 
200
201
  @app.command("unset", requires_connection=True)
201
202
  def unset_property(
202
- name: str = ComputePoolNameArgument,
203
+ name: FQN = ComputePoolNameArgument,
203
204
  auto_resume: bool = AutoResumeOption(
204
205
  default=False,
205
206
  param_decls=["--auto-resume"],
@@ -223,7 +224,7 @@ def unset_property(
223
224
  Resets one or more properties for the compute pool to their default value(s).
224
225
  """
225
226
  cursor = ComputePoolManager().unset_property(
226
- pool_name=name,
227
+ pool_name=name.identifier,
227
228
  auto_resume=auto_resume,
228
229
  auto_suspend_secs=auto_suspend_secs,
229
230
  comment=comment,
@@ -232,9 +233,9 @@ def unset_property(
232
233
 
233
234
 
234
235
  @app.command(requires_connection=True)
235
- def status(pool_name: str = ComputePoolNameArgument, **options) -> CommandResult:
236
+ def status(pool_name: FQN = ComputePoolNameArgument, **options) -> CommandResult:
236
237
  """
237
238
  Retrieves the status of a compute pool along with a relevant message, if one exists.
238
239
  """
239
- cursor = ComputePoolManager().status(pool_name=pool_name)
240
+ cursor = ComputePoolManager().status(pool_name=pool_name.identifier)
240
241
  return SingleQueryResult(cursor)
@@ -23,11 +23,13 @@ from click import ClickException
23
23
  from snowflake.cli.api.commands.flags import (
24
24
  IfNotExistsOption,
25
25
  ReplaceOption,
26
+ identifier_argument,
26
27
  like_option,
27
28
  )
28
29
  from snowflake.cli.api.commands.snow_typer import SnowTyperFactory
29
30
  from snowflake.cli.api.console import cli_console
30
31
  from snowflake.cli.api.constants import ObjectType
32
+ from snowflake.cli.api.identifiers import FQN
31
33
  from snowflake.cli.api.output.types import (
32
34
  CollectionResult,
33
35
  MessageResult,
@@ -48,18 +50,18 @@ app = SnowTyperFactory(
48
50
  )
49
51
 
50
52
 
51
- def _repo_name_callback(name: str):
52
- if not is_valid_object_name(name, max_depth=2, allow_quoted=False):
53
+ def _repo_name_callback(name: FQN):
54
+ if not is_valid_object_name(name.identifier, max_depth=2, allow_quoted=False):
53
55
  raise ClickException(
54
56
  f"'{name}' is not a valid image repository name. Note that image repository names must be unquoted identifiers. The same constraint also applies to database and schema names where you create an image repository."
55
57
  )
56
58
  return name
57
59
 
58
60
 
59
- REPO_NAME_ARGUMENT = typer.Argument(
60
- help="Name of the image repository.",
61
+ REPO_NAME_ARGUMENT = identifier_argument(
62
+ sf_object="image repository",
63
+ example="my_repository",
61
64
  callback=_repo_name_callback,
62
- show_default=False,
63
65
  )
64
66
 
65
67
  add_object_command_aliases(
@@ -76,7 +78,7 @@ add_object_command_aliases(
76
78
 
77
79
  @app.command(requires_connection=True)
78
80
  def create(
79
- name: str = REPO_NAME_ARGUMENT,
81
+ name: FQN = REPO_NAME_ARGUMENT,
80
82
  replace: bool = ReplaceOption(),
81
83
  if_not_exists: bool = IfNotExistsOption(),
82
84
  **options,
@@ -86,21 +88,21 @@ def create(
86
88
  """
87
89
  return SingleQueryResult(
88
90
  ImageRepositoryManager().create(
89
- name=name, replace=replace, if_not_exists=if_not_exists
91
+ name=name.identifier, replace=replace, if_not_exists=if_not_exists
90
92
  )
91
93
  )
92
94
 
93
95
 
94
96
  @app.command("list-images", requires_connection=True)
95
97
  def list_images(
96
- name: str = REPO_NAME_ARGUMENT,
98
+ name: FQN = REPO_NAME_ARGUMENT,
97
99
  **options,
98
100
  ) -> CollectionResult:
99
101
  """Lists images in the given repository."""
100
102
  repository_manager = ImageRepositoryManager()
101
103
  database = repository_manager.get_database()
102
104
  schema = repository_manager.get_schema()
103
- url = repository_manager.get_repository_url(name)
105
+ url = repository_manager.get_repository_url(name.identifier)
104
106
  api_url = repository_manager.get_repository_api_url(url)
105
107
  bearer_login = RegistryManager().login_to_registry(api_url)
106
108
  repos = []
@@ -136,7 +138,7 @@ def list_images(
136
138
 
137
139
  @app.command("list-tags", requires_connection=True)
138
140
  def list_tags(
139
- name: str = REPO_NAME_ARGUMENT,
141
+ name: FQN = REPO_NAME_ARGUMENT,
140
142
  image_name: str = typer.Option(
141
143
  ...,
142
144
  "--image-name",
@@ -150,7 +152,7 @@ def list_tags(
150
152
  """Lists tags for the given image in a repository."""
151
153
 
152
154
  repository_manager = ImageRepositoryManager()
153
- url = repository_manager.get_repository_url(name)
155
+ url = repository_manager.get_repository_url(name.identifier)
154
156
  api_url = repository_manager.get_repository_api_url(url)
155
157
  bearer_login = RegistryManager().login_to_registry(api_url)
156
158
 
@@ -187,10 +189,14 @@ def list_tags(
187
189
 
188
190
  @app.command("url", requires_connection=True)
189
191
  def repo_url(
190
- name: str = REPO_NAME_ARGUMENT,
192
+ name: FQN = REPO_NAME_ARGUMENT,
191
193
  **options,
192
194
  ):
193
195
  """Returns the URL for the given repository."""
194
196
  return MessageResult(
195
- (ImageRepositoryManager().get_repository_url(repo_name=name, with_scheme=False))
197
+ (
198
+ ImageRepositoryManager().get_repository_url(
199
+ repo_name=name.identifier, with_scheme=False
200
+ )
201
+ )
196
202
  )
@@ -23,10 +23,12 @@ from click import ClickException
23
23
  from snowflake.cli.api.commands.flags import (
24
24
  IfNotExistsOption,
25
25
  OverrideableOption,
26
+ identifier_argument,
26
27
  like_option,
27
28
  )
28
29
  from snowflake.cli.api.commands.snow_typer import SnowTyperFactory
29
30
  from snowflake.cli.api.constants import ObjectType
31
+ from snowflake.cli.api.identifiers import FQN
30
32
  from snowflake.cli.api.output.types import (
31
33
  CommandResult,
32
34
  QueryJsonValueResult,
@@ -52,19 +54,18 @@ app = SnowTyperFactory(
52
54
  )
53
55
 
54
56
 
55
- def _service_name_callback(name: str) -> str:
56
- if not is_valid_object_name(name, max_depth=2, allow_quoted=False):
57
+ def _service_name_callback(name: FQN) -> FQN:
58
+ if not is_valid_object_name(name.identifier, max_depth=2, allow_quoted=False):
57
59
  raise ClickException(
58
60
  f"'{name}' is not a valid service name. Note service names must be unquoted identifiers. The same constraint also applies to database and schema names where you create a service."
59
61
  )
60
62
  return name
61
63
 
62
64
 
63
- ServiceNameArgument = typer.Argument(
64
- ...,
65
- help="Name of the service.",
65
+ ServiceNameArgument = identifier_argument(
66
+ sf_object="service pool",
67
+ example="my_service",
66
68
  callback=_service_name_callback,
67
- show_default=False,
68
69
  )
69
70
 
70
71
  SpecPathOption = typer.Option(
@@ -116,7 +117,7 @@ add_object_command_aliases(
116
117
 
117
118
  @app.command(requires_connection=True)
118
119
  def create(
119
- name: str = ServiceNameArgument,
120
+ name: FQN = ServiceNameArgument,
120
121
  compute_pool: str = typer.Option(
121
122
  ...,
122
123
  "--compute-pool",
@@ -145,7 +146,7 @@ def create(
145
146
  min_instances, max_instances, "instances"
146
147
  )
147
148
  cursor = ServiceManager().create(
148
- service_name=name,
149
+ service_name=name.identifier,
149
150
  min_instances=min_instances,
150
151
  max_instances=max_instances,
151
152
  compute_pool=compute_pool,
@@ -161,17 +162,17 @@ def create(
161
162
 
162
163
 
163
164
  @app.command(requires_connection=True)
164
- def status(name: str = ServiceNameArgument, **options) -> CommandResult:
165
+ def status(name: FQN = ServiceNameArgument, **options) -> CommandResult:
165
166
  """
166
167
  Retrieves the status of a service.
167
168
  """
168
- cursor = ServiceManager().status(service_name=name)
169
+ cursor = ServiceManager().status(service_name=name.identifier)
169
170
  return QueryJsonValueResult(cursor)
170
171
 
171
172
 
172
173
  @app.command(requires_connection=True)
173
174
  def logs(
174
- name: str = ServiceNameArgument,
175
+ name: FQN = ServiceNameArgument,
175
176
  container_name: str = typer.Option(
176
177
  ...,
177
178
  "--container-name",
@@ -193,7 +194,7 @@ def logs(
193
194
  Retrieves local logs from a service container.
194
195
  """
195
196
  results = ServiceManager().logs(
196
- service_name=name,
197
+ service_name=name.identifier,
197
198
  instance_id=instance_id,
198
199
  container_name=container_name,
199
200
  num_lines=num_lines,
@@ -205,7 +206,7 @@ def logs(
205
206
 
206
207
  @app.command(requires_connection=True)
207
208
  def upgrade(
208
- name: str = ServiceNameArgument,
209
+ name: FQN = ServiceNameArgument,
209
210
  spec_path: Path = SpecPathOption,
210
211
  **options,
211
212
  ):
@@ -213,20 +214,20 @@ def upgrade(
213
214
  Updates an existing service with a new specification file.
214
215
  """
215
216
  return SingleQueryResult(
216
- ServiceManager().upgrade_spec(service_name=name, spec_path=spec_path)
217
+ ServiceManager().upgrade_spec(service_name=name.identifier, spec_path=spec_path)
217
218
  )
218
219
 
219
220
 
220
221
  @app.command("list-endpoints", requires_connection=True)
221
- def list_endpoints(name: str = ServiceNameArgument, **options):
222
+ def list_endpoints(name: FQN = ServiceNameArgument, **options):
222
223
  """
223
224
  Lists the endpoints in a service.
224
225
  """
225
- return QueryResult(ServiceManager().list_endpoints(service_name=name))
226
+ return QueryResult(ServiceManager().list_endpoints(service_name=name.identifier))
226
227
 
227
228
 
228
229
  @app.command(requires_connection=True)
229
- def suspend(name: str = ServiceNameArgument, **options) -> CommandResult:
230
+ def suspend(name: FQN = ServiceNameArgument, **options) -> CommandResult:
230
231
  """
231
232
  Suspends the service, shutting down and deleting all its containers.
232
233
  """
@@ -234,7 +235,7 @@ def suspend(name: str = ServiceNameArgument, **options) -> CommandResult:
234
235
 
235
236
 
236
237
  @app.command(requires_connection=True)
237
- def resume(name: str = ServiceNameArgument, **options) -> CommandResult:
238
+ def resume(name: FQN = ServiceNameArgument, **options) -> CommandResult:
238
239
  """
239
240
  Resumes the service from a SUSPENDED state.
240
241
  """
@@ -243,7 +244,7 @@ def resume(name: str = ServiceNameArgument, **options) -> CommandResult:
243
244
 
244
245
  @app.command("set", requires_connection=True)
245
246
  def set_property(
246
- name: str = ServiceNameArgument,
247
+ name: FQN = ServiceNameArgument,
247
248
  min_instances: Optional[int] = MinInstancesOption(default=None, show_default=False),
248
249
  max_instances: Optional[int] = MaxInstancesOption(show_default=False),
249
250
  query_warehouse: Optional[str] = QueryWarehouseOption(show_default=False),
@@ -255,7 +256,7 @@ def set_property(
255
256
  Sets one or more properties for the service.
256
257
  """
257
258
  cursor = ServiceManager().set_property(
258
- service_name=name,
259
+ service_name=name.identifier,
259
260
  min_instances=min_instances,
260
261
  max_instances=max_instances,
261
262
  query_warehouse=query_warehouse,
@@ -267,7 +268,7 @@ def set_property(
267
268
 
268
269
  @app.command("unset", requires_connection=True)
269
270
  def unset_property(
270
- name: str = ServiceNameArgument,
271
+ name: FQN = ServiceNameArgument,
271
272
  min_instances: bool = MinInstancesOption(
272
273
  default=False,
273
274
  help=f"Reset the MIN_INSTANCES property - {_MIN_INSTANCES_HELP}",
@@ -301,7 +302,7 @@ def unset_property(
301
302
  Resets one or more properties for the service to their default value(s).
302
303
  """
303
304
  cursor = ServiceManager().unset_property(
304
- service_name=name,
305
+ service_name=name.identifier,
305
306
  min_instances=min_instances,
306
307
  max_instances=max_instances,
307
308
  query_warehouse=query_warehouse,
@@ -26,11 +26,13 @@ from snowflake.cli.api.commands.flags import (
26
26
  ExecuteVariablesOption,
27
27
  OnErrorOption,
28
28
  PatternOption,
29
+ identifier_argument,
29
30
  like_option,
30
31
  )
31
32
  from snowflake.cli.api.commands.snow_typer import SnowTyperFactory
32
33
  from snowflake.cli.api.console import cli_console
33
34
  from snowflake.cli.api.constants import ObjectType
35
+ from snowflake.cli.api.identifiers import FQN
34
36
  from snowflake.cli.api.output.formats import OutputFormat
35
37
  from snowflake.cli.api.output.types import (
36
38
  CollectionResult,
@@ -56,7 +58,7 @@ app = SnowTyperFactory(
56
58
  help="Manages stages.",
57
59
  )
58
60
 
59
- StageNameArgument = typer.Argument(..., help="Name of the stage.", show_default=False)
61
+ StageNameArgument = identifier_argument(sf_object="stage", example="@my_stage")
60
62
 
61
63
  add_object_command_aliases(
62
64
  app=app,
@@ -142,17 +144,17 @@ def copy(
142
144
 
143
145
 
144
146
  @app.command("create", requires_connection=True)
145
- def stage_create(stage_name: str = StageNameArgument, **options) -> CommandResult:
147
+ def stage_create(stage_name: FQN = StageNameArgument, **options) -> CommandResult:
146
148
  """
147
149
  Creates a named stage if it does not already exist.
148
150
  """
149
- cursor = StageManager().create(stage_name=stage_name)
151
+ cursor = StageManager().create(fqn=stage_name)
150
152
  return SingleQueryResult(cursor)
151
153
 
152
154
 
153
155
  @app.command("remove", requires_connection=True)
154
156
  def stage_remove(
155
- stage_name: str = StageNameArgument,
157
+ stage_name: FQN = StageNameArgument,
156
158
  file_name: str = typer.Argument(
157
159
  ...,
158
160
  help="Name of the file to remove.",
@@ -164,7 +166,7 @@ def stage_remove(
164
166
  Removes a file from a stage.
165
167
  """
166
168
 
167
- cursor = StageManager().remove(stage_name=stage_name, path=file_name)
169
+ cursor = StageManager().remove(stage_name=stage_name.identifier, path=file_name)
168
170
  return SingleQueryResult(cursor)
169
171
 
170
172
 
@@ -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
 
@@ -168,7 +193,9 @@ class StageManager(SqlExecutionMixin):
168
193
  self._python_exe_procedure = None
169
194
 
170
195
  @staticmethod
171
- def get_standard_stage_prefix(name: str) -> str:
196
+ def get_standard_stage_prefix(name: str | FQN) -> str:
197
+ if isinstance(name, FQN):
198
+ name = name.identifier
172
199
  # Handle embedded stages
173
200
  if name.startswith("snow://") or name.startswith("@"):
174
201
  return name
@@ -239,7 +266,7 @@ class StageManager(SqlExecutionMixin):
239
266
  self._assure_is_existing_directory(dest_directory)
240
267
 
241
268
  result = self._execute_query(
242
- 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}"
243
270
  )
244
271
  results.append(result)
245
272
 
@@ -300,8 +327,8 @@ class StageManager(SqlExecutionMixin):
300
327
  quoted_stage_name = self.quote_stage_name(f"{stage_name}{path}")
301
328
  return self._execute_query(f"remove {quoted_stage_name}")
302
329
 
303
- def create(self, stage_name: str, comment: Optional[str] = None) -> SnowflakeCursor:
304
- query = f"create stage if not exists {stage_name}"
330
+ def create(self, fqn: FQN, comment: Optional[str] = None) -> SnowflakeCursor:
331
+ query = f"create stage if not exists {fqn.sql_identifier}"
305
332
  if comment:
306
333
  query += f" comment='{comment}'"
307
334
  return self._execute_query(query)
@@ -319,8 +346,14 @@ class StageManager(SqlExecutionMixin):
319
346
  stage_path_parts = self._stage_path_part_factory(stage_path)
320
347
  all_files_list = self._get_files_list_from_stage(stage_path_parts)
321
348
 
349
+ all_files_with_stage_name_prefix = [
350
+ stage_path_parts.get_directory(file) for file in all_files_list
351
+ ]
352
+
322
353
  # filter files from stage if match stage_path pattern
323
- 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
+ )
324
357
 
325
358
  if not filtered_file_list:
326
359
  raise ClickException(f"No files matched pattern '{stage_path}'")
@@ -376,7 +409,7 @@ class StageManager(SqlExecutionMixin):
376
409
  if not stage_path_parts.directory:
377
410
  return self._filter_supported_files(files_on_stage)
378
411
 
379
- stage_path = stage_path_parts.path.lower()
412
+ stage_path = stage_path_parts.directory
380
413
 
381
414
  # Exact file path was provided if stage_path in file list
382
415
  if stage_path in files_on_stage: