snowflake-cli 3.7.1__py3-none-any.whl → 3.8.0__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 (56) hide show
  1. snowflake/cli/__about__.py +1 -1
  2. snowflake/cli/_app/snow_connector.py +14 -0
  3. snowflake/cli/_app/telemetry.py +11 -0
  4. snowflake/cli/_plugins/connection/commands.py +4 -2
  5. snowflake/cli/_plugins/nativeapp/codegen/setup/native_app_setup_processor.py +1 -1
  6. snowflake/cli/_plugins/nativeapp/entities/application_package.py +20 -7
  7. snowflake/cli/_plugins/nativeapp/sf_sql_facade.py +5 -3
  8. snowflake/cli/_plugins/project/commands.py +16 -6
  9. snowflake/cli/_plugins/snowpark/common.py +31 -0
  10. snowflake/cli/_plugins/snowpark/package/anaconda_packages.py +3 -0
  11. snowflake/cli/_plugins/snowpark/snowpark_entity.py +21 -1
  12. snowflake/cli/_plugins/snowpark/snowpark_entity_model.py +23 -1
  13. snowflake/cli/_plugins/spcs/common.py +7 -0
  14. snowflake/cli/_plugins/spcs/image_repository/commands.py +7 -2
  15. snowflake/cli/_plugins/spcs/image_repository/manager.py +6 -2
  16. snowflake/cli/_plugins/spcs/services/commands.py +2 -2
  17. snowflake/cli/_plugins/spcs/services/manager.py +36 -1
  18. snowflake/cli/_plugins/sql/commands.py +57 -6
  19. snowflake/cli/_plugins/sql/lexer/__init__.py +7 -0
  20. snowflake/cli/_plugins/sql/lexer/completer.py +12 -0
  21. snowflake/cli/_plugins/sql/lexer/functions.py +421 -0
  22. snowflake/cli/_plugins/sql/lexer/keywords.py +529 -0
  23. snowflake/cli/_plugins/sql/lexer/lexer.py +56 -0
  24. snowflake/cli/_plugins/sql/lexer/types.py +37 -0
  25. snowflake/cli/_plugins/sql/manager.py +43 -9
  26. snowflake/cli/_plugins/sql/repl.py +221 -0
  27. snowflake/cli/_plugins/sql/snowsql_commands.py +331 -0
  28. snowflake/cli/_plugins/sql/statement_reader.py +296 -0
  29. snowflake/cli/_plugins/streamlit/commands.py +30 -15
  30. snowflake/cli/_plugins/streamlit/manager.py +0 -183
  31. snowflake/cli/_plugins/streamlit/streamlit_entity.py +163 -23
  32. snowflake/cli/api/artifacts/upload.py +5 -0
  33. snowflake/cli/api/artifacts/utils.py +0 -2
  34. snowflake/cli/api/cli_global_context.py +7 -3
  35. snowflake/cli/api/commands/decorators.py +70 -0
  36. snowflake/cli/api/commands/flags.py +95 -3
  37. snowflake/cli/api/config.py +10 -0
  38. snowflake/cli/api/connections.py +10 -0
  39. snowflake/cli/api/console/abc.py +8 -2
  40. snowflake/cli/api/console/console.py +16 -0
  41. snowflake/cli/api/console/enum.py +1 -1
  42. snowflake/cli/api/entities/common.py +99 -10
  43. snowflake/cli/api/entities/utils.py +1 -0
  44. snowflake/cli/api/feature_flags.py +6 -0
  45. snowflake/cli/api/project/project_paths.py +5 -0
  46. snowflake/cli/api/rendering/sql_templates.py +2 -1
  47. snowflake/cli/api/sql_execution.py +16 -4
  48. snowflake/cli/api/utils/path_utils.py +15 -0
  49. snowflake/cli/api/utils/python_api_utils.py +12 -0
  50. {snowflake_cli-3.7.1.dist-info → snowflake_cli-3.8.0.dist-info}/METADATA +12 -8
  51. {snowflake_cli-3.7.1.dist-info → snowflake_cli-3.8.0.dist-info}/RECORD +54 -46
  52. snowflake/cli/_plugins/nativeapp/feature_flags.py +0 -28
  53. snowflake/cli/_plugins/sql/source_reader.py +0 -230
  54. {snowflake_cli-3.7.1.dist-info → snowflake_cli-3.8.0.dist-info}/WHEEL +0 -0
  55. {snowflake_cli-3.7.1.dist-info → snowflake_cli-3.8.0.dist-info}/entry_points.txt +0 -0
  56. {snowflake_cli-3.7.1.dist-info → snowflake_cli-3.8.0.dist-info}/licenses/LICENSE +0 -0
@@ -16,7 +16,7 @@ from __future__ import annotations
16
16
 
17
17
  from enum import Enum, unique
18
18
 
19
- VERSION = "3.7.1"
19
+ VERSION = "3.8.0"
20
20
 
21
21
 
22
22
  @unique
@@ -64,6 +64,16 @@ SUPPORTED_ENV_OVERRIDES = [
64
64
  "session_token",
65
65
  "master_token",
66
66
  "token_file_path",
67
+ "oauth_client_id",
68
+ "oauth_client_secret",
69
+ "oauth_authorization_url",
70
+ "oauth_token_request_url",
71
+ "oauth_redirect_uri",
72
+ "oauth_scope",
73
+ "oauth_disable_pkce",
74
+ "oauth_enable_refresh_tokens",
75
+ "oauth_enable_single_use_refresh_tokens",
76
+ "client_store_temporary_credential",
67
77
  ]
68
78
 
69
79
  # mapping of found key -> key to set
@@ -103,14 +113,18 @@ def connect_to_snowflake(
103
113
  temporary_connection, using_session_token, using_master_token
104
114
  )
105
115
 
116
+ connection_parameters = {}
106
117
  if connection_name:
107
118
  connection_parameters = {
108
119
  _resolve_alias(k): v
109
120
  for k, v in get_connection_dict(connection_name).items()
110
121
  }
122
+
111
123
  elif temporary_connection:
112
124
  connection_parameters = {} # we will apply overrides in next step
113
125
 
126
+ connection_parameters["using_session_keep_alive"] = True
127
+
114
128
  # Apply overrides to connection details
115
129
  # (1) Command line override case
116
130
  for key, value in overrides.items():
@@ -75,6 +75,7 @@ class CLITelemetryField(Enum):
75
75
  IS_CLI_EXCEPTION = "is_cli_exception"
76
76
  # Project context
77
77
  PROJECT_DEFINITION_VERSION = "project_definition_version"
78
+ MODE = "mode"
78
79
 
79
80
 
80
81
  class TelemetryEvent(Enum):
@@ -151,9 +152,19 @@ def _find_command_info() -> TelemetryDict:
151
152
  "format", OutputFormat.TABLE
152
153
  ).value,
153
154
  CLITelemetryField.PROJECT_DEFINITION_VERSION: str(_get_definition_version()),
155
+ CLITelemetryField.MODE: _get_cli_running_mode(),
154
156
  }
155
157
 
156
158
 
159
+ def _get_cli_running_mode() -> str:
160
+ try:
161
+ if get_cli_context().is_repl:
162
+ return "repl"
163
+ except Exception:
164
+ pass
165
+ return "cmd"
166
+
167
+
157
168
  def _get_definition_version() -> str | None:
158
169
  try:
159
170
  cli_context = get_cli_context()
@@ -90,9 +90,11 @@ class EmptyInput:
90
90
  return "optional"
91
91
 
92
92
 
93
- def _mask_password(connection_params: dict):
93
+ def _mask_sensitive_parameters(connection_params: dict):
94
94
  if "password" in connection_params:
95
95
  connection_params["password"] = "****"
96
+ if "oauth_client_secret" in connection_params:
97
+ connection_params["oauth_client_secret"] = "****"
96
98
  return connection_params
97
99
 
98
100
 
@@ -106,7 +108,7 @@ def list_connections(**options) -> CommandResult:
106
108
  result = (
107
109
  {
108
110
  "connection_name": connection_name,
109
- "parameters": _mask_password(
111
+ "parameters": _mask_sensitive_parameters(
110
112
  connection_config.to_dict_of_known_non_empty_values()
111
113
  ),
112
114
  "is_default": connection_name == default_connection,
@@ -35,10 +35,10 @@ from snowflake.cli._plugins.nativeapp.codegen.sandbox import (
35
35
  SandboxEnvBuilder,
36
36
  execute_script_in_sandbox,
37
37
  )
38
- from snowflake.cli._plugins.nativeapp.feature_flags import FeatureFlag
39
38
  from snowflake.cli._plugins.stage.diff import to_stage_path
40
39
  from snowflake.cli.api.artifacts.bundle_map import BundleMap
41
40
  from snowflake.cli.api.console import cli_console as cc
41
+ from snowflake.cli.api.feature_flags import FeatureFlag
42
42
  from snowflake.cli.api.project.schemas.entities.common import (
43
43
  PathMapping,
44
44
  ProcessorMapping,
@@ -44,7 +44,6 @@ from snowflake.cli._plugins.nativeapp.exceptions import (
44
44
  ObjectPropertyNotFoundError,
45
45
  SetupScriptFailedValidation,
46
46
  )
47
- from snowflake.cli._plugins.nativeapp.feature_flags import FeatureFlag
48
47
  from snowflake.cli._plugins.nativeapp.policy import (
49
48
  AllowAlwaysPolicy,
50
49
  AskAlwaysPolicy,
@@ -88,6 +87,7 @@ from snowflake.cli.api.entities.utils import (
88
87
  )
89
88
  from snowflake.cli.api.errno import DOES_NOT_EXIST_OR_NOT_AUTHORIZED
90
89
  from snowflake.cli.api.exceptions import SnowflakeSQLExecutionError
90
+ from snowflake.cli.api.feature_flags import FeatureFlag
91
91
  from snowflake.cli.api.project.schemas.entities.common import (
92
92
  EntityModelBaseWithArtifacts,
93
93
  Identifier,
@@ -190,6 +190,10 @@ class ApplicationPackageEntityModel(EntityModelBaseWithArtifacts):
190
190
  title="Entities that will be bundled and deployed as part of this application package",
191
191
  default=[],
192
192
  )
193
+ enable_release_channels: bool = Field(
194
+ title="Enable release channels for this application package",
195
+ default=False,
196
+ )
193
197
 
194
198
  @field_validator("children")
195
199
  @classmethod
@@ -1550,24 +1554,33 @@ class ApplicationPackageEntity(EntityBase[ApplicationPackageEntityModel]):
1550
1554
  def _get_enable_release_channels_flag(self) -> Optional[bool]:
1551
1555
  """
1552
1556
  Returns the requested value of enable_release_channels flag for the application package.
1553
- It retrieves the value from the configuration file and checks that the feature is enabled in the account.
1557
+ It retrieves the value from snowflake.yml (and from the configuration file),
1558
+ and checks that the feature is enabled in the account.
1554
1559
  If return value is None, it means do not explicitly set the flag.
1555
1560
  """
1556
- feature_flag_from_config = FeatureFlag.ENABLE_RELEASE_CHANNELS.get_value()
1561
+ value_from_snowflake_yml = self.model.enable_release_channels
1562
+ feature_flag_from_config = FeatureFlag.ENABLE_RELEASE_CHANNELS.is_enabled()
1563
+ if feature_flag_from_config and not value_from_snowflake_yml:
1564
+ self._workspace_ctx.console.warning(
1565
+ f"{FeatureFlag.ENABLE_RELEASE_CHANNELS.name} value in config.toml is deprecated."
1566
+ f" Set [enable_release_channels] for the application package in snowflake.yml instead."
1567
+ )
1568
+ enable_release_channels = value_from_snowflake_yml or feature_flag_from_config
1569
+
1557
1570
  feature_enabled_in_account = (
1558
1571
  get_snowflake_facade().get_ui_parameter(
1559
1572
  UIParameter.NA_FEATURE_RELEASE_CHANNELS, "ENABLED"
1560
1573
  )
1561
1574
  == "ENABLED"
1562
1575
  )
1563
-
1564
- if feature_flag_from_config is not None and not feature_enabled_in_account:
1576
+ if enable_release_channels and not feature_enabled_in_account:
1565
1577
  self._workspace_ctx.console.warning(
1566
- f"Ignoring feature flag {FeatureFlag.ENABLE_RELEASE_CHANNELS.name} because release channels are not enabled in the current account."
1578
+ f"Ignoring [enable_release_channels] value because "
1579
+ "release channels are not enabled in the current account."
1567
1580
  )
1568
1581
  return None
1569
1582
 
1570
- return feature_flag_from_config
1583
+ return enable_release_channels
1571
1584
 
1572
1585
  def create_app_package(self) -> None:
1573
1586
  """
@@ -148,6 +148,7 @@ class SnowflakeSQLFacade:
148
148
  yield
149
149
  return
150
150
 
151
+ name = to_identifier(name)
151
152
  try:
152
153
  current_obj_result_row = self._sql_executor.execute_query(
153
154
  f"select current_{object_type}()"
@@ -158,7 +159,7 @@ class SnowflakeSQLFacade:
158
159
  )
159
160
 
160
161
  try:
161
- prev_obj = current_obj_result_row[0]
162
+ prev_obj = to_identifier(current_obj_result_row[0])
162
163
  except IndexError:
163
164
  prev_obj = None
164
165
 
@@ -230,12 +231,13 @@ class SnowflakeSQLFacade:
230
231
  with self._use_role_optional(role_to_use):
231
232
  try:
232
233
  self._sql_executor.execute_query(
233
- f"grant {comma_separated_privileges} on {object_type_and_name} to role {role_to_grant}"
234
+ f"grant {comma_separated_privileges} on {object_type_and_name} to role {to_identifier(role_to_grant)}"
234
235
  )
235
236
  except Exception as err:
236
237
  handle_unclassified_error(
237
238
  err,
238
- f"Failed to grant {comma_separated_privileges} on {object_type_and_name} to role {role_to_grant}.",
239
+ f"Failed to grant {comma_separated_privileges} on {object_type_and_name}"
240
+ f" to role {to_identifier(role_to_grant)}.",
239
241
  )
240
242
 
241
243
  def execute_user_script(
@@ -15,7 +15,6 @@
15
15
  from typing import List, Optional
16
16
 
17
17
  import typer
18
- from click import ClickException
19
18
  from snowflake.cli._plugins.object.command_aliases import add_object_command_aliases
20
19
  from snowflake.cli._plugins.object.commands import scope_option
21
20
  from snowflake.cli._plugins.object.manager import ObjectManager
@@ -38,6 +37,7 @@ from snowflake.cli.api.commands.snow_typer import SnowTyperFactory
38
37
  from snowflake.cli.api.commands.utils import get_entity_for_operation
39
38
  from snowflake.cli.api.console.console import cli_console
40
39
  from snowflake.cli.api.constants import ObjectType
40
+ from snowflake.cli.api.exceptions import CliError
41
41
  from snowflake.cli.api.identifiers import FQN
42
42
  from snowflake.cli.api.output.types import MessageResult, QueryResult, SingleQueryResult
43
43
 
@@ -73,7 +73,7 @@ add_object_command_aliases(
73
73
  help_example='`list --like "my%"` lists all projects that begin with “my”'
74
74
  ),
75
75
  scope_option=scope_option(help_example="`list --in database my_db`"),
76
- ommit_commands=["drop", "create", "describe"],
76
+ ommit_commands=["create", "describe"],
77
77
  )
78
78
 
79
79
 
@@ -133,11 +133,11 @@ def create(
133
133
  )
134
134
  om = ObjectManager()
135
135
  if om.object_exists(object_type="project", fqn=project.fqn):
136
- raise ClickException(f"Project '{project.fqn}' already exists.")
136
+ raise CliError(f"Project '{project.fqn}' already exists.")
137
137
  if not no_version and om.object_exists(
138
138
  object_type="stage", fqn=FQN.from_stage(project.stage)
139
139
  ):
140
- raise ClickException(f"Stage '{project.stage}' already exists.")
140
+ raise CliError(f"Stage '{project.stage}' already exists.")
141
141
 
142
142
  pm = ProjectManager()
143
143
  with cli_console.phase(f"Creating project '{project.fqn}'"):
@@ -154,17 +154,22 @@ def create(
154
154
  @with_project_definition()
155
155
  def add_version(
156
156
  entity_id: str = entity_argument("project"),
157
- _from: Optional[str] = from_option(mutually_exclusive=["prune"]),
157
+ _from: Optional[str] = from_option(),
158
158
  _alias: Optional[str] = typer.Option(
159
159
  None, "--alias", help="Alias for the version.", show_default=False
160
160
  ),
161
161
  comment: Optional[str] = typer.Option(
162
162
  None, "--comment", help="Version comment.", show_default=False
163
163
  ),
164
- prune: bool = PruneOption(mutually_exclusive=["_from"]),
164
+ prune: bool = PruneOption(default=True),
165
165
  **options,
166
166
  ):
167
167
  """Uploads local files to Snowflake and cerates a new project version."""
168
+ if _from is not None and prune:
169
+ cli_console.warning(
170
+ "When `--from` option is used, `--prune` option will be ignored and files from stage will be used as they are."
171
+ )
172
+ prune = False
168
173
  cli_context = get_cli_context()
169
174
  project: ProjectEntityModel = get_entity_for_operation(
170
175
  cli_context=cli_context,
@@ -172,6 +177,11 @@ def add_version(
172
177
  project_definition=cli_context.project_definition,
173
178
  entity_type="project",
174
179
  )
180
+ om = ObjectManager()
181
+ if not om.object_exists(object_type="project", fqn=project.fqn):
182
+ raise CliError(
183
+ f"Project '{project.fqn}' does not exist. Use `project create` command first"
184
+ )
175
185
  ProjectManager().add_version(
176
186
  project=project,
177
187
  prune=prune,
@@ -103,6 +103,21 @@ class SnowparkObjectManager(SqlExecutionMixin):
103
103
  if isinstance(entity, ProcedureEntityModel) and entity.execute_as_caller:
104
104
  query.append("execute as caller")
105
105
 
106
+ if entity.artifact_repository and entity.artifact_repository_packages:
107
+ packages = [f"'{item}'" for item in entity.artifact_repository_packages]
108
+ query.extend(
109
+ [
110
+ f"ARTIFACT_REPOSITORY= {entity.artifact_repository}",
111
+ f"ARTIFACT_REPOSITORY_PACKAGES=({','.join(packages)})",
112
+ ]
113
+ )
114
+
115
+ if entity.resource_constraint:
116
+ constraints = ",".join(
117
+ f"{key}='{value}'" for key, value in entity.resource_constraint.items()
118
+ )
119
+ query.append(f"RESOURCE_CONSTRAINT=({constraints})")
120
+
106
121
  return self.execute_query("\n".join(query))
107
122
 
108
123
  def deploy_entity(
@@ -184,6 +199,22 @@ def _check_if_replace_is_required(
184
199
  log.info("Runtime versions do not match. Replacing the %s", object_type)
185
200
  return True
186
201
 
202
+ if entity.resource_constraint != resource_json.get("resource_constraint", None):
203
+ log.info("Resource constraints do not match. Replacing the %s", object_type)
204
+ return True
205
+
206
+ if entity.artifact_repository != resource_json.get("artifact_repository", None):
207
+ log.info("Artifact repository does not match. Replacing the %s", object_type)
208
+ return True
209
+
210
+ if entity.artifact_repository_packages != resource_json.get(
211
+ "artifact_repository_packages", None
212
+ ):
213
+ log.info(
214
+ "Artifact repository packages do not match. Replacing the %s", object_type
215
+ )
216
+ return True
217
+
187
218
  if isinstance(entity, ProcedureEntityModel):
188
219
  if resource_json.get("execute as", "OWNER") != (
189
220
  "CALLER" if entity.execute_as_caller else "OWNER"
@@ -88,6 +88,9 @@ class AnacondaPackages:
88
88
  return False
89
89
  if skip_version_check or not package.specs:
90
90
  return True
91
+ if any(spec[0] == "!=" for spec in package.specs):
92
+ # Snowflake doesn't support '!=' so we need to resolve this requirement externally
93
+ return False
91
94
  return self._packages[package.name].is_required_version_available(package)
92
95
 
93
96
  def package_latest_version(self, package: Requirement) -> str | None:
@@ -3,7 +3,6 @@ from pathlib import Path
3
3
  from typing import Generic, List, Optional, TypeVar
4
4
 
5
5
  from click import ClickException
6
- from snowflake.cli._plugins.nativeapp.feature_flags import FeatureFlag
7
6
  from snowflake.cli._plugins.snowpark import package_utils
8
7
  from snowflake.cli._plugins.snowpark.common import (
9
8
  DEFAULT_RUNTIME,
@@ -25,6 +24,7 @@ from snowflake.cli._plugins.snowpark.snowpark_project_paths import SnowparkProje
25
24
  from snowflake.cli._plugins.snowpark.zipper import zip_dir
26
25
  from snowflake.cli._plugins.workspace.context import ActionContext
27
26
  from snowflake.cli.api.entities.common import EntityBase
27
+ from snowflake.cli.api.feature_flags import FeatureFlag
28
28
  from snowflake.cli.api.secure_path import SecurePath
29
29
  from snowflake.connector import ProgrammingError
30
30
 
@@ -176,6 +176,17 @@ class SnowparkEntity(EntityBase[Generic[T]]):
176
176
  if self.model.type == "procedure" and self.model.execute_as_caller:
177
177
  query.append("EXECUTE AS CALLER")
178
178
 
179
+ if self.model.artifact_repository and self.model.artifact_repository_packages:
180
+ packages = [f"'{item}'" for item in self.model.artifact_repository_packages]
181
+ query.extend(
182
+ [
183
+ f"ARTIFACT_REPOSITORY= {self.model.artifact_repository} ",
184
+ f"ARTIFACT_REPOSITORY_PACKAGES=({','.join(packages)})",
185
+ ]
186
+ )
187
+ if self.model.resource_constraint:
188
+ query.append(self._get_resource_constraints_sql())
189
+
179
190
  return "\n".join(query)
180
191
 
181
192
  def get_execute_sql(self, execution_arguments: List[str] | None = None):
@@ -237,6 +248,15 @@ class SnowparkEntity(EntityBase[Generic[T]]):
237
248
 
238
249
  return download_result
239
250
 
251
+ def _get_resource_constraints_sql(self) -> str:
252
+ if self.model.resource_constraint:
253
+ constraints = ",".join(
254
+ f"{key}='{value}'"
255
+ for key, value in self.model.resource_constraint.items()
256
+ )
257
+ return f"RESOURCE_CONSTRAINT=({constraints})"
258
+ return ""
259
+
240
260
 
241
261
  class FunctionEntity(SnowparkEntity[FunctionEntityModel]):
242
262
  """
@@ -17,7 +17,7 @@ from __future__ import annotations
17
17
  import glob
18
18
  from typing import List, Literal, Optional, Union
19
19
 
20
- from pydantic import Field, field_validator
20
+ from pydantic import Field, field_validator, model_validator
21
21
  from snowflake.cli.api.feature_flags import FeatureFlag
22
22
  from snowflake.cli.api.identifiers import FQN
23
23
  from snowflake.cli.api.project.schemas.entities.common import (
@@ -50,6 +50,17 @@ class SnowparkEntityModel(
50
50
  )
51
51
  stage: str = Field(title="Stage in which artifacts will be stored")
52
52
 
53
+ artifact_repository: Optional[str] = Field(
54
+ default=None, title="Artifact repository to be used"
55
+ )
56
+ artifact_repository_packages: Optional[List[str]] = Field(
57
+ default=None, title="Packages to be installed from artifact repository"
58
+ )
59
+
60
+ resource_constraint: Optional[dict] = Field(
61
+ default=None, title="Resource constraints for the function/procedure"
62
+ )
63
+
53
64
  @field_validator("artifacts")
54
65
  @classmethod
55
66
  def _convert_artifacts(cls, artifacts: Union[dict, str]):
@@ -75,6 +86,17 @@ class SnowparkEntityModel(
75
86
  return str(runtime_input)
76
87
  return runtime_input
77
88
 
89
+ @model_validator(mode="before")
90
+ @classmethod
91
+ def check_artifact_repository(cls, values: dict) -> dict:
92
+ artifact_repository = values.get("artifact_repository")
93
+ artifact_repository_packages = values.get("artifact_repository_packages")
94
+ if artifact_repository_packages and not artifact_repository:
95
+ raise ValueError(
96
+ "You specified Artifact_repository_packages without setting Artifact_repository.",
97
+ )
98
+ return values
99
+
78
100
  @property
79
101
  def udf_sproc_identifier(self) -> UdfSprocIdentifier:
80
102
  return UdfSprocIdentifier.from_definition(self)
@@ -181,6 +181,13 @@ def build_time_clauses(
181
181
  return since_clause, until_clause
182
182
 
183
183
 
184
+ def build_db_and_schema_clause(database_name: str, schema_name: str | None) -> str:
185
+ return f"""
186
+ and resource_attributes:"snow.database.name" = '{database_name}'
187
+ and resource_attributes:"snow.schema.name" = '{schema_name or 'PUBLIC'}'
188
+ """
189
+
190
+
184
191
  def format_event_row(event_dict: dict) -> dict:
185
192
  try:
186
193
  resource_attributes = json.loads(event_dict.get("RESOURCE_ATTRIBUTES", "{}"))
@@ -75,7 +75,7 @@ add_object_command_aliases(
75
75
  object_type=ObjectType.IMAGE_REPOSITORY,
76
76
  name_argument=REPO_NAME_ARGUMENT,
77
77
  like_option=like_option(
78
- help_example='`list --like "my%"` lists all image repositories that begin with “my”.'
78
+ help_example='`--like "my%"` lists all image repositories that begin with “my”.'
79
79
  ),
80
80
  scope_option=scope_option(help_example="`list --in database my_db`"),
81
81
  ommit_commands=["describe"],
@@ -126,10 +126,15 @@ def deploy(
126
126
  @app.command("list-images", requires_connection=True)
127
127
  def list_images(
128
128
  name: FQN = REPO_NAME_ARGUMENT,
129
+ like_option: Optional[str] = like_option(
130
+ help_example='`--like "my%"` lists all image repositories that begin with “my”.'
131
+ ),
129
132
  **options,
130
133
  ) -> CollectionResult:
131
134
  """Lists images in the given repository."""
132
- return QueryResult(ImageRepositoryManager().list_images(name.identifier))
135
+ return QueryResult(
136
+ ImageRepositoryManager().list_images(name.identifier, like_option)
137
+ )
133
138
 
134
139
 
135
140
  @app.command("list-tags", requires_connection=True, deprecated=True)
@@ -83,5 +83,9 @@ class ImageRepositoryManager(SqlExecutionMixin):
83
83
  e, ObjectType.IMAGE_REPOSITORY, name, replace_available=True
84
84
  )
85
85
 
86
- def list_images(self, repo_name: str) -> SnowflakeCursor:
87
- return self.execute_query(f"show images in image repository {repo_name}")
86
+ def list_images(self, repo_name: str, like_option: str) -> SnowflakeCursor:
87
+ if like_option:
88
+ query = f"show images like '{like_option}' in image repository {repo_name}"
89
+ else:
90
+ query = f"show images in image repository {repo_name}"
91
+ return self.execute_query(query)
@@ -437,7 +437,7 @@ def metrics(
437
437
  manager = ServiceManager()
438
438
  if since or until:
439
439
  metrics = manager.get_all_metrics(
440
- service_name=name.identifier,
440
+ service_name=name,
441
441
  container_name=container_name,
442
442
  instance_id=instance_id,
443
443
  since=since,
@@ -446,7 +446,7 @@ def metrics(
446
446
  )
447
447
  else:
448
448
  metrics = manager.get_latest_metrics(
449
- service_name=name.identifier,
449
+ service_name=name,
450
450
  container_name=container_name,
451
451
  instance_id=instance_id,
452
452
  show_all_columns=show_all_columns,
@@ -26,6 +26,7 @@ from snowflake.cli._plugins.spcs.common import (
26
26
  EVENT_COLUMN_NAMES,
27
27
  NoPropertiesProvidedError,
28
28
  SPCSEventTableError,
29
+ build_db_and_schema_clause,
29
30
  build_resource_clause,
30
31
  build_time_clauses,
31
32
  filter_log_timestamp,
@@ -197,6 +198,8 @@ class ServiceManager(SqlExecutionMixin):
197
198
  if not artifacts:
198
199
  raise ValueError("Service needs to have artifacts to deploy")
199
200
 
201
+ service_project_paths.remove_up_bundle_root()
202
+ SecurePath(service_project_paths.bundle_root).mkdir(parents=True, exist_ok=True)
200
203
  bundle_map = bundle_artifacts(service_project_paths, artifacts)
201
204
  for absolute_src, absolute_dest in bundle_map.all_mappings(
202
205
  absolute=True, expand_directories=True
@@ -208,6 +211,7 @@ class ServiceManager(SqlExecutionMixin):
208
211
  stage_manager.put(
209
212
  local_path=absolute_dest, stage_path=stage_path, overwrite=True
210
213
  )
214
+ service_project_paths.clean_up_output()
211
215
 
212
216
  def execute_job(
213
217
  self,
@@ -388,13 +392,14 @@ class ServiceManager(SqlExecutionMixin):
388
392
 
389
393
  def get_all_metrics(
390
394
  self,
391
- service_name: str,
395
+ service_name: FQN | str,
392
396
  instance_id: str,
393
397
  container_name: str,
394
398
  since: str | datetime | None = None,
395
399
  until: str | datetime | None = None,
396
400
  show_all_columns: bool = False,
397
401
  ):
402
+ service_name, database, schema = parse_service_details(service_name)
398
403
 
399
404
  account_event_table = self.get_account_event_table()
400
405
  resource_clause = build_resource_clause(
@@ -402,11 +407,18 @@ class ServiceManager(SqlExecutionMixin):
402
407
  )
403
408
  since_clause, until_clause = build_time_clauses(since, until)
404
409
 
410
+ db_and_schema_clause = ""
411
+ if database:
412
+ db_and_schema_clause = build_db_and_schema_clause(
413
+ database_name=database, schema_name=schema
414
+ )
415
+
405
416
  query = f"""\
406
417
  select *
407
418
  from {account_event_table}
408
419
  where (
409
420
  {resource_clause}
421
+ {db_and_schema_clause}
410
422
  {since_clause}
411
423
  {until_clause}
412
424
  )
@@ -438,12 +450,19 @@ class ServiceManager(SqlExecutionMixin):
438
450
  container_name: str,
439
451
  show_all_columns: bool = False,
440
452
  ):
453
+ service_name, database, schema = parse_service_details(service_name)
441
454
 
442
455
  account_event_table = self.get_account_event_table()
443
456
  resource_clause = build_resource_clause(
444
457
  service_name, instance_id, container_name
445
458
  )
446
459
 
460
+ db_and_schema_clause = ""
461
+ if database:
462
+ db_and_schema_clause = build_db_and_schema_clause(
463
+ database_name=database, schema_name=schema
464
+ )
465
+
447
466
  query = f"""
448
467
  with rankedmetrics as (
449
468
  select
@@ -457,6 +476,7 @@ class ServiceManager(SqlExecutionMixin):
457
476
  record_type = 'METRIC'
458
477
  and scope['name'] = 'snow.spcs.platform'
459
478
  and {resource_clause}
479
+ {db_and_schema_clause}
460
480
  and timestamp > dateadd('hour', -1, current_timestamp)
461
481
  )
462
482
  select *
@@ -580,3 +600,18 @@ class ServiceManager(SqlExecutionMixin):
580
600
  unset_list = [property_name for property_name, value in property_pairs if value]
581
601
  query = f"alter service {service_name} unset {','.join(unset_list)}"
582
602
  return self.execute_query(query)
603
+
604
+
605
+ def parse_service_details(
606
+ service_identifier: str | FQN,
607
+ ) -> tuple[str, str | None, str | None]:
608
+ if isinstance(service_identifier, FQN):
609
+ name = service_identifier.name
610
+ database = service_identifier.database
611
+ schema = service_identifier.schema
612
+ else:
613
+ name = service_identifier
614
+ database = None
615
+ schema = None
616
+
617
+ return name, database, schema