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
@@ -14,4 +14,4 @@
14
14
 
15
15
  from __future__ import annotations
16
16
 
17
- VERSION = "2.8.0rc1"
17
+ VERSION = "2.8.1"
@@ -28,6 +28,7 @@ from snowflake.cli.api.cli_global_context import cli_context_manager
28
28
  from snowflake.cli.api.commands.typer_pre_execute import register_pre_execute_command
29
29
  from snowflake.cli.api.console import cli_console
30
30
  from snowflake.cli.api.exceptions import MissingConfiguration
31
+ from snowflake.cli.api.identifiers import FQN
31
32
  from snowflake.cli.api.output.formats import OutputFormat
32
33
  from snowflake.cli.api.project.definition_manager import DefinitionManager
33
34
  from snowflake.cli.api.rendering.jinja import CONTEXT_KEY
@@ -350,13 +351,23 @@ EnableDiagOption = typer.Option(
350
351
  rich_help_panel=_CONNECTION_SECTION,
351
352
  )
352
353
 
354
+ # Set default via callback to avoid including tempdir path in generated docs (snow --docs).
355
+ # Use constant instead of None, as None is removed from telemetry data.
356
+ _DIAG_LOG_DEFAULT_VALUE = "<temporary_directory>"
357
+
358
+
359
+ def _diag_log_path_callback(path: str):
360
+ if path == _DIAG_LOG_DEFAULT_VALUE:
361
+ path = tempfile.gettempdir()
362
+ cli_context_manager.connection_context.set_diag_log_path(Path(path))
363
+ return path
364
+
365
+
353
366
  DiagLogPathOption: Path = typer.Option(
354
367
  tempfile.gettempdir(),
355
368
  "--diag-log-path",
356
369
  help="Diagnostic report path",
357
- callback=_callback(
358
- lambda: cli_context_manager.connection_context.set_diag_log_path
359
- ),
370
+ callback=_diag_log_path_callback,
360
371
  show_default=False,
361
372
  rich_help_panel=_CONNECTION_SECTION,
362
373
  exists=True,
@@ -514,11 +525,15 @@ def experimental_option(
514
525
  )
515
526
 
516
527
 
517
- def identifier_argument(sf_object: str, example: str) -> typer.Argument:
528
+ def identifier_argument(
529
+ sf_object: str, example: str, callback: Callable | None = None
530
+ ) -> typer.Argument:
518
531
  return typer.Argument(
519
532
  ...,
520
533
  help=f"Identifier of the {sf_object}. For example: {example}",
521
534
  show_default=False,
535
+ click_type=IdentifierType(),
536
+ callback=callback,
522
537
  )
523
538
 
524
539
 
@@ -638,3 +653,10 @@ def parse_key_value_variables(variables: Optional[List[str]]) -> List[Variable]:
638
653
  key, value = p.split("=", 1)
639
654
  result.append(Variable(key.strip(), value.strip()))
640
655
  return result
656
+
657
+
658
+ class IdentifierType(click.ParamType):
659
+ name = "TEXT"
660
+
661
+ def convert(self, value, param, ctx):
662
+ return FQN.from_string(value)
@@ -35,10 +35,17 @@ class FQN:
35
35
  fqn = FQN.from_string("my_name").set_database("db").set_schema("foo")
36
36
  """
37
37
 
38
- def __init__(self, database: str | None, schema: str | None, name: str):
38
+ def __init__(
39
+ self,
40
+ database: str | None,
41
+ schema: str | None,
42
+ name: str,
43
+ signature: str | None = None,
44
+ ):
39
45
  self._database = database
40
46
  self._schema = schema
41
47
  self._name = name
48
+ self.signature = signature
42
49
 
43
50
  @property
44
51
  def database(self) -> str | None:
@@ -72,6 +79,8 @@ class FQN:
72
79
 
73
80
  @property
74
81
  def sql_identifier(self) -> str:
82
+ if self.signature:
83
+ return f"IDENTIFIER('{self.identifier}'){self.signature}"
75
84
  return f"IDENTIFIER('{self.identifier}')"
76
85
 
77
86
  def __str__(self):
@@ -98,9 +107,13 @@ class FQN:
98
107
  else:
99
108
  database = None
100
109
  schema = result.group("first_qualifier")
101
- if signature := result.group("signature"):
102
- unqualified_name = unqualified_name + signature
103
- return cls(name=unqualified_name, schema=schema, database=database)
110
+
111
+ signature = None
112
+ if result.group("signature"):
113
+ signature = result.group("signature")
114
+ return cls(
115
+ name=unqualified_name, schema=schema, database=database, signature=signature
116
+ )
104
117
 
105
118
  @classmethod
106
119
  def from_stage(cls, stage: str) -> "FQN":
@@ -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 ''})
@@ -18,7 +18,7 @@ import itertools
18
18
  import logging
19
19
  from os import path
20
20
  from pathlib import Path
21
- from typing import List, Optional
21
+ from typing import Dict, List, Optional
22
22
 
23
23
  import typer
24
24
  from click import ClickException
@@ -41,6 +41,7 @@ from snowflake.cli.plugins.object.command_aliases import (
41
41
  )
42
42
  from snowflake.cli.plugins.object.manager import ObjectManager
43
43
  from snowflake.cli.plugins.stage.manager import OnErrorType
44
+ from snowflake.connector import DictCursor
44
45
 
45
46
  app = SnowTyperFactory(
46
47
  name="git",
@@ -82,10 +83,12 @@ add_object_command_aliases(
82
83
  scope_option=scope_option(help_example="`list --in database my_db`"),
83
84
  )
84
85
 
86
+ from snowflake.cli.api.identifiers import FQN
85
87
 
86
- def _assure_repository_does_not_exist(om: ObjectManager, repository_name: str) -> None:
88
+
89
+ def _assure_repository_does_not_exist(om: ObjectManager, repository_name: FQN) -> None:
87
90
  if om.object_exists(
88
- object_type=ObjectType.GIT_REPOSITORY.value.cli_name, name=repository_name
91
+ object_type=ObjectType.GIT_REPOSITORY.value.cli_name, fqn=repository_name
89
92
  ):
90
93
  raise ClickException(f"Repository '{repository_name}' already exists")
91
94
 
@@ -95,9 +98,27 @@ def _validate_origin_url(url: str) -> None:
95
98
  raise ClickException("Url address should start with 'https'")
96
99
 
97
100
 
101
+ def _unique_new_object_name(
102
+ om: ObjectManager, object_type: ObjectType, proposed_fqn: FQN
103
+ ) -> str:
104
+ existing_objects: List[Dict] = om.show(
105
+ object_type=object_type.value.cli_name,
106
+ like=f"{proposed_fqn.name}%",
107
+ cursor_class=DictCursor,
108
+ ).fetchall()
109
+ existing_names = set(o["name"].upper() for o in existing_objects)
110
+
111
+ result = proposed_fqn.name
112
+ i = 1
113
+ while result.upper() in existing_names:
114
+ result = proposed_fqn.name + str(i)
115
+ i += 1
116
+ return result
117
+
118
+
98
119
  @app.command("setup", requires_connection=True)
99
120
  def setup(
100
- repository_name: str = RepoNameArgument,
121
+ repository_name: FQN = RepoNameArgument,
101
122
  **options,
102
123
  ) -> CommandResult:
103
124
  """
@@ -123,12 +144,29 @@ def setup(
123
144
  should_create_secret = False
124
145
  secret_name = None
125
146
  if secret_needed:
126
- secret_name = f"{repository_name}_secret"
127
- secret_name = typer.prompt(
128
- "Secret identifier (will be created if not exists)", default=secret_name
147
+ default_secret_name = (
148
+ FQN.from_string(f"{repository_name.name}_secret")
149
+ .set_schema(repository_name.schema)
150
+ .set_database(repository_name.database)
151
+ )
152
+ default_secret_name.set_name(
153
+ _unique_new_object_name(
154
+ om, object_type=ObjectType.SECRET, proposed_fqn=default_secret_name
155
+ ),
156
+ )
157
+ secret_name = FQN.from_string(
158
+ typer.prompt(
159
+ "Secret identifier (will be created if not exists)",
160
+ default=default_secret_name.name,
161
+ )
129
162
  )
163
+ if not secret_name.database:
164
+ secret_name.set_database(repository_name.database)
165
+ if not secret_name.schema:
166
+ secret_name.set_schema(repository_name.schema)
167
+
130
168
  if om.object_exists(
131
- object_type=ObjectType.SECRET.value.cli_name, name=secret_name
169
+ object_type=ObjectType.SECRET.value.cli_name, fqn=secret_name
132
170
  ):
133
171
  cli_console.step(f"Using existing secret '{secret_name}'")
134
172
  else:
@@ -137,10 +175,17 @@ def setup(
137
175
  secret_username = typer.prompt("username")
138
176
  secret_password = typer.prompt("password/token", hide_input=True)
139
177
 
140
- api_integration = f"{repository_name}_api_integration"
141
- api_integration = typer.prompt(
142
- "API integration identifier (will be created if not exists)",
143
- default=api_integration,
178
+ # API integration is an account-level object
179
+ api_integration = FQN.from_string(f"{repository_name.name}_api_integration")
180
+ api_integration.set_name(
181
+ typer.prompt(
182
+ "API integration identifier (will be created if not exists)",
183
+ default=_unique_new_object_name(
184
+ om,
185
+ object_type=ObjectType.INTEGRATION,
186
+ proposed_fqn=api_integration,
187
+ ),
188
+ )
144
189
  )
145
190
 
146
191
  if should_create_secret:
@@ -150,7 +195,7 @@ def setup(
150
195
  cli_console.step(f"Secret '{secret_name}' successfully created.")
151
196
 
152
197
  if not om.object_exists(
153
- object_type=ObjectType.INTEGRATION.value.cli_name, name=api_integration
198
+ object_type=ObjectType.INTEGRATION.value.cli_name, fqn=api_integration
154
199
  ):
155
200
  manager.create_api_integration(
156
201
  name=api_integration,
@@ -177,7 +222,7 @@ def setup(
177
222
  requires_connection=True,
178
223
  )
179
224
  def list_branches(
180
- repository_name: str = RepoNameArgument,
225
+ repository_name: FQN = RepoNameArgument,
181
226
  like=like_option(
182
227
  help_example='`list-branches --like "%_test"` lists all branches that end with "_test"'
183
228
  ),
@@ -186,7 +231,9 @@ def list_branches(
186
231
  """
187
232
  List all branches in the repository.
188
233
  """
189
- return QueryResult(GitManager().show_branches(repo_name=repository_name, like=like))
234
+ return QueryResult(
235
+ GitManager().show_branches(repo_name=repository_name.identifier, like=like)
236
+ )
190
237
 
191
238
 
192
239
  @app.command(
@@ -194,7 +241,7 @@ def list_branches(
194
241
  requires_connection=True,
195
242
  )
196
243
  def list_tags(
197
- repository_name: str = RepoNameArgument,
244
+ repository_name: FQN = RepoNameArgument,
198
245
  like=like_option(
199
246
  help_example='`list-tags --like "v2.0%"` lists all tags that start with "v2.0"'
200
247
  ),
@@ -203,7 +250,9 @@ def list_tags(
203
250
  """
204
251
  List all tags in the repository.
205
252
  """
206
- return QueryResult(GitManager().show_tags(repo_name=repository_name, like=like))
253
+ return QueryResult(
254
+ GitManager().show_tags(repo_name=repository_name.identifier, like=like)
255
+ )
207
256
 
208
257
 
209
258
  @app.command(
@@ -228,13 +277,13 @@ def list_files(
228
277
  requires_connection=True,
229
278
  )
230
279
  def fetch(
231
- repository_name: str = RepoNameArgument,
280
+ repository_name: FQN = RepoNameArgument,
232
281
  **options,
233
282
  ) -> CommandResult:
234
283
  """
235
284
  Fetch changes from origin to Snowflake repository.
236
285
  """
237
- return QueryResult(GitManager().fetch(repo_name=repository_name))
286
+ return QueryResult(GitManager().fetch(fqn=repository_name))
238
287
 
239
288
 
240
289
  @app.command(
@@ -18,6 +18,7 @@ from pathlib import Path
18
18
  from textwrap import dedent
19
19
  from typing import List
20
20
 
21
+ from snowflake.cli.api.identifiers import FQN
21
22
  from snowflake.cli.plugins.stage.manager import (
22
23
  USER_STAGE_PREFIX,
23
24
  StageManager,
@@ -40,17 +41,25 @@ class GitStagePathParts(StagePathParts):
40
41
 
41
42
  @property
42
43
  def path(self) -> str:
43
- return (
44
- f"{self.stage_name}{self.directory}"
45
- if self.stage_name.endswith("/")
46
- else f"{self.stage_name}/{self.directory}"
47
- )
44
+ return f"{self.stage_name.rstrip('/')}/{self.directory}"
48
45
 
49
- def add_stage_prefix(self, file_path: str) -> str:
46
+ @classmethod
47
+ def get_directory(cls, stage_path: str) -> str:
48
+ return "/".join(Path(stage_path).parts[3:])
49
+
50
+ @property
51
+ def full_path(self) -> str:
52
+ return f"{self.stage.rstrip('/')}/{self.directory}"
53
+
54
+ def replace_stage_prefix(self, file_path: str) -> str:
50
55
  stage = Path(self.stage).parts[0]
51
56
  file_path_without_prefix = Path(file_path).parts[1:]
52
57
  return f"{stage}/{'/'.join(file_path_without_prefix)}"
53
58
 
59
+ def add_stage_prefix(self, file_path: str) -> str:
60
+ stage = self.stage.rstrip("/")
61
+ return f"{stage}/{file_path.lstrip('/')}"
62
+
54
63
  def get_directory_from_file_path(self, file_path: str) -> List[str]:
55
64
  stage_path_length = len(Path(self.directory).parts)
56
65
  return list(Path(file_path).parts[3 + stage_path_length : -1])
@@ -63,15 +72,15 @@ class GitManager(StageManager):
63
72
  def show_tags(self, repo_name: str, like: str) -> SnowflakeCursor:
64
73
  return self._execute_query(f"show git tags like '{like}' in {repo_name}")
65
74
 
66
- def fetch(self, repo_name: str) -> SnowflakeCursor:
67
- return self._execute_query(f"alter git repository {repo_name} fetch")
75
+ def fetch(self, fqn: FQN) -> SnowflakeCursor:
76
+ return self._execute_query(f"alter git repository {fqn} fetch")
68
77
 
69
78
  def create(
70
- self, repo_name: str, api_integration: str, url: str, secret: str
79
+ self, repo_name: FQN, api_integration: str, url: str, secret: str
71
80
  ) -> SnowflakeCursor:
72
81
  query = dedent(
73
82
  f"""
74
- create git repository {repo_name}
83
+ create git repository {repo_name.sql_identifier}
75
84
  api_integration = {api_integration}
76
85
  origin = '{url}'
77
86
  """
@@ -68,7 +68,8 @@ TemplateOption = typer.Option(
68
68
  show_default=False,
69
69
  )
70
70
  SourceOption = typer.Option(
71
- default=DEFAULT_SOURCE,
71
+ DEFAULT_SOURCE,
72
+ "--template-source",
72
73
  help=f"local path to template directory or URL to git repository with templates.",
73
74
  )
74
75
  VariablesOption = variables_option(
@@ -132,13 +133,13 @@ def _fetch_remote_template(
132
133
  return template_root
133
134
 
134
135
 
135
- def _read_template_metadata(template_root: SecurePath) -> Template:
136
+ def _read_template_metadata(template_root: SecurePath, args_error_msg: str) -> Template:
136
137
  """Parse template.yml file."""
137
138
  template_metadata_path = template_root / TEMPLATE_METADATA_FILE_NAME
138
139
  log.debug("Reading template metadata from %s", template_metadata_path.path)
139
140
  if not template_metadata_path.exists():
140
141
  raise InvalidTemplate(
141
- f"Template does not have {TEMPLATE_METADATA_FILE_NAME} file."
142
+ f"File {TEMPLATE_METADATA_FILE_NAME} not found. {args_error_msg}"
142
143
  )
143
144
  with template_metadata_path.open(read_file_limit_mb=DEFAULT_SIZE_LIMIT_MB) as fd:
144
145
  yaml_contents = yaml.safe_load(fd) or {}
@@ -203,6 +204,7 @@ def init(
203
204
  is_remote = any(
204
205
  template_source.startswith(prefix) for prefix in ["git@", "http://", "https://"] # type: ignore
205
206
  )
207
+ args_error_msg = f"Check whether {TemplateOption.param_decls[0]} and {SourceOption.param_decls[0]} arguments are correct."
206
208
 
207
209
  # copy/download template into tmpdir, so it is going to be removed in case command ends with an error
208
210
  with SecurePath.temporary_directory() as tmpdir:
@@ -217,7 +219,9 @@ def init(
217
219
  destination=tmpdir,
218
220
  )
219
221
 
220
- template_metadata = _read_template_metadata(template_root)
222
+ template_metadata = _read_template_metadata(
223
+ template_root, args_error_msg=args_error_msg
224
+ )
221
225
  if template_metadata.minimum_cli_version:
222
226
  _validate_cli_version(template_metadata.minimum_cli_version)
223
227
 
@@ -17,9 +17,10 @@ import logging
17
17
  import typer
18
18
  from snowflake.cli.api.commands.flags import identifier_argument
19
19
  from snowflake.cli.api.commands.snow_typer import SnowTyperFactory
20
+ from snowflake.cli.api.identifiers import FQN
20
21
  from snowflake.cli.api.output.types import MessageResult
21
22
  from snowflake.cli.plugins.notebook.manager import NotebookManager
22
- from snowflake.cli.plugins.notebook.types import NotebookName, NotebookStagePath
23
+ from snowflake.cli.plugins.notebook.types import NotebookStagePath
23
24
  from typing_extensions import Annotated
24
25
 
25
26
  app = SnowTyperFactory(
@@ -38,7 +39,7 @@ NotebookFile: NotebookStagePath = typer.Option(
38
39
 
39
40
  @app.command(requires_connection=True)
40
41
  def execute(
41
- identifier: str = NOTEBOOK_IDENTIFIER,
42
+ identifier: FQN = NOTEBOOK_IDENTIFIER,
42
43
  **options,
43
44
  ):
44
45
  """
@@ -51,7 +52,7 @@ def execute(
51
52
 
52
53
  @app.command(requires_connection=True)
53
54
  def get_url(
54
- identifier: str = NOTEBOOK_IDENTIFIER,
55
+ identifier: FQN = NOTEBOOK_IDENTIFIER,
55
56
  **options,
56
57
  ):
57
58
  """Return a url to a notebook."""
@@ -61,7 +62,7 @@ def get_url(
61
62
 
62
63
  @app.command(name="open", requires_connection=True)
63
64
  def open_cmd(
64
- identifier: str = NOTEBOOK_IDENTIFIER,
65
+ identifier: FQN = NOTEBOOK_IDENTIFIER,
65
66
  **options,
66
67
  ):
67
68
  """Opens a notebook in default browser"""
@@ -72,7 +73,7 @@ def open_cmd(
72
73
 
73
74
  @app.command(requires_connection=True)
74
75
  def create(
75
- identifier: Annotated[NotebookName, NOTEBOOK_IDENTIFIER],
76
+ identifier: Annotated[FQN, NOTEBOOK_IDENTIFIER],
76
77
  notebook_file: Annotated[NotebookStagePath, NotebookFile],
77
78
  **options,
78
79
  ):
@@ -20,23 +20,23 @@ from snowflake.cli.api.identifiers import FQN
20
20
  from snowflake.cli.api.sql_execution import SqlExecutionMixin
21
21
  from snowflake.cli.plugins.connection.util import make_snowsight_url
22
22
  from snowflake.cli.plugins.notebook.exceptions import NotebookStagePathError
23
- from snowflake.cli.plugins.notebook.types import NotebookName, NotebookStagePath
23
+ from snowflake.cli.plugins.notebook.types import NotebookStagePath
24
24
 
25
25
 
26
26
  class NotebookManager(SqlExecutionMixin):
27
- def execute(self, notebook_name: NotebookName):
28
- query = f"EXECUTE NOTEBOOK {notebook_name}()"
27
+ def execute(self, notebook_name: FQN):
28
+ query = f"EXECUTE NOTEBOOK {notebook_name.sql_identifier}()"
29
29
  return self._execute_query(query=query)
30
30
 
31
- def get_url(self, notebook_name: NotebookName):
32
- fqn = FQN.from_string(notebook_name).using_connection(self._conn)
31
+ def get_url(self, notebook_name: FQN):
32
+ fqn = notebook_name.using_connection(self._conn)
33
33
  return make_snowsight_url(
34
34
  self._conn,
35
35
  f"/#/notebooks/{fqn.url_identifier}",
36
36
  )
37
37
 
38
38
  @staticmethod
39
- def parse_stage_as_path(notebook_file: NotebookName) -> Path:
39
+ def parse_stage_as_path(notebook_file: str) -> Path:
40
40
  """Parses notebook file path to pathlib.Path."""
41
41
  if not notebook_file.endswith(".ipynb"):
42
42
  raise NotebookStagePathError(notebook_file)
@@ -48,19 +48,19 @@ class NotebookManager(SqlExecutionMixin):
48
48
 
49
49
  def create(
50
50
  self,
51
- notebook_name: NotebookName,
51
+ notebook_name: FQN,
52
52
  notebook_file: NotebookStagePath,
53
53
  ) -> str:
54
- notebook_fqn = FQN.from_string(notebook_name).using_connection(self._conn)
54
+ notebook_fqn = notebook_name.using_connection(self._conn)
55
55
  stage_path = self.parse_stage_as_path(notebook_file)
56
56
 
57
57
  queries = dedent(
58
58
  f"""
59
- CREATE OR REPLACE NOTEBOOK {notebook_fqn.identifier}
59
+ CREATE OR REPLACE NOTEBOOK {notebook_fqn.sql_identifier}
60
60
  FROM '{stage_path.parent}'
61
61
  QUERY_WAREHOUSE = '{cli_context.connection.warehouse}'
62
62
  MAIN_FILE = '{stage_path.name}';
63
-
63
+ // Cannot use IDENTIFIER(...)
64
64
  ALTER NOTEBOOK {notebook_fqn.identifier} ADD LIVE VERSION FROM LAST;
65
65
  """
66
66
  )
@@ -12,5 +12,4 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
- NotebookName = str
16
15
  NotebookStagePath = str
@@ -20,6 +20,7 @@ import typer
20
20
  from click import ClickException
21
21
  from snowflake.cli.api.commands.snow_typer import SnowTyperFactory
22
22
  from snowflake.cli.api.constants import ObjectType
23
+ from snowflake.cli.api.identifiers import FQN
23
24
  from snowflake.cli.plugins.object.commands import (
24
25
  ScopeOption,
25
26
  describe,
@@ -72,7 +73,7 @@ def add_object_command_aliases(
72
73
  if "drop" not in ommit_commands:
73
74
 
74
75
  @app.command("drop", requires_connection=True)
75
- def drop_cmd(name: str = name_argument, **options):
76
+ def drop_cmd(name: FQN = name_argument, **options):
76
77
  return drop(
77
78
  object_type=object_type.value.cli_name,
78
79
  object_name=name,
@@ -84,7 +85,7 @@ def add_object_command_aliases(
84
85
  if "describe" not in ommit_commands:
85
86
 
86
87
  @app.command("describe", requires_connection=True)
87
- def describe_cmd(name: str = name_argument, **options):
88
+ def describe_cmd(name: FQN = name_argument, **options):
88
89
  return describe(
89
90
  object_type=object_type.value.cli_name,
90
91
  object_name=name,
@@ -18,9 +18,14 @@ from typing import List, Optional, Tuple
18
18
 
19
19
  import typer
20
20
  from click import ClickException
21
- from snowflake.cli.api.commands.flags import like_option, parse_key_value_variables
21
+ from snowflake.cli.api.commands.flags import (
22
+ IdentifierType,
23
+ like_option,
24
+ parse_key_value_variables,
25
+ )
22
26
  from snowflake.cli.api.commands.snow_typer import SnowTyperFactory
23
27
  from snowflake.cli.api.constants import SUPPORTED_OBJECTS, VALID_SCOPES
28
+ from snowflake.cli.api.identifiers import FQN
24
29
  from snowflake.cli.api.output.types import MessageResult, QueryResult
25
30
  from snowflake.cli.api.project.util import is_valid_identifier
26
31
  from snowflake.cli.plugins.object.manager import ObjectManager
@@ -31,7 +36,9 @@ app = SnowTyperFactory(
31
36
  )
32
37
 
33
38
 
34
- NameArgument = typer.Argument(help="Name of the object")
39
+ NameArgument = typer.Argument(
40
+ help="Name of the object.", show_default=False, click_type=IdentifierType()
41
+ )
35
42
  ObjectArgument = typer.Argument(
36
43
  help="Type of object. For example table, database, compute-pool.",
37
44
  case_sensitive=False,
@@ -112,8 +119,8 @@ def list_(
112
119
  help=f"Drops Snowflake object of given name and type. {SUPPORTED_TYPES_MSG}",
113
120
  requires_connection=True,
114
121
  )
115
- def drop(object_type: str = ObjectArgument, object_name: str = NameArgument, **options):
116
- return QueryResult(ObjectManager().drop(object_type=object_type, name=object_name))
122
+ def drop(object_type: str = ObjectArgument, object_name: FQN = NameArgument, **options):
123
+ return QueryResult(ObjectManager().drop(object_type=object_type, fqn=object_name))
117
124
 
118
125
 
119
126
  # Image repository is the only supported object that does not have a DESCRIBE command.
@@ -125,10 +132,10 @@ DESCRIBE_SUPPORTED_TYPES_MSG = f"\n\nSupported types: {', '.join(obj for obj in
125
132
  requires_connection=True,
126
133
  )
127
134
  def describe(
128
- object_type: str = ObjectArgument, object_name: str = NameArgument, **options
135
+ object_type: str = ObjectArgument, object_name: FQN = NameArgument, **options
129
136
  ):
130
137
  return QueryResult(
131
- ObjectManager().describe(object_type=object_type, name=object_name)
138
+ ObjectManager().describe(object_type=object_type, fqn=object_name)
132
139
  )
133
140
 
134
141
 
@@ -22,6 +22,7 @@ from snowflake.cli.api.constants import (
22
22
  OBJECT_TO_NAMES,
23
23
  ObjectNames,
24
24
  )
25
+ from snowflake.cli.api.identifiers import FQN
25
26
  from snowflake.cli.api.rest_api import RestApi
26
27
  from snowflake.cli.api.sql_execution import SqlExecutionMixin
27
28
  from snowflake.connector import ProgrammingError
@@ -53,22 +54,22 @@ class ObjectManager(SqlExecutionMixin):
53
54
  query += f" in {scope[0].replace('-', ' ')} {scope[1]}"
54
55
  return self._execute_query(query, **kwargs)
55
56
 
56
- def drop(self, *, object_type: str, name: str) -> SnowflakeCursor:
57
+ def drop(self, *, object_type: str, fqn: FQN) -> SnowflakeCursor:
57
58
  object_name = _get_object_names(object_type).sf_name
58
- return self._execute_query(f"drop {object_name} {name}")
59
+ return self._execute_query(f"drop {object_name} {fqn.sql_identifier}")
59
60
 
60
- def describe(self, *, object_type: str, name: str):
61
+ def describe(self, *, object_type: str, fqn: FQN):
61
62
  # Image repository is the only supported object that does not have a DESCRIBE command.
62
63
  if object_type == "image-repository":
63
64
  raise ClickException(
64
65
  f"Describe is currently not supported for object of type image-repository"
65
66
  )
66
67
  object_name = _get_object_names(object_type).sf_name
67
- return self._execute_query(f"describe {object_name} {name}")
68
+ return self._execute_query(f"describe {object_name} {fqn.sql_identifier}")
68
69
 
69
- def object_exists(self, *, object_type: str, name: str):
70
+ def object_exists(self, *, object_type: str, fqn: FQN):
70
71
  try:
71
- self.describe(object_type=object_type, name=name)
72
+ self.describe(object_type=object_type, fqn=fqn)
72
73
  return True
73
74
  except ProgrammingError:
74
75
  return False