snowflake-cli 3.2.2__py3-none-any.whl → 3.4.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 (97) hide show
  1. snowflake/cli/__about__.py +1 -1
  2. snowflake/cli/_app/__main__.py +2 -2
  3. snowflake/cli/_app/cli_app.py +224 -192
  4. snowflake/cli/_app/commands_registration/commands_registration_with_callbacks.py +1 -27
  5. snowflake/cli/_app/constants.py +4 -0
  6. snowflake/cli/_app/snow_connector.py +12 -0
  7. snowflake/cli/_app/telemetry.py +10 -3
  8. snowflake/cli/_plugins/connection/util.py +12 -19
  9. snowflake/cli/_plugins/cortex/commands.py +2 -4
  10. snowflake/cli/_plugins/git/manager.py +1 -1
  11. snowflake/cli/_plugins/helpers/commands.py +207 -1
  12. snowflake/cli/_plugins/nativeapp/artifacts.py +16 -628
  13. snowflake/cli/_plugins/nativeapp/bundle_context.py +1 -1
  14. snowflake/cli/_plugins/nativeapp/codegen/artifact_processor.py +1 -1
  15. snowflake/cli/_plugins/nativeapp/codegen/compiler.py +42 -20
  16. snowflake/cli/_plugins/nativeapp/codegen/setup/native_app_setup_processor.py +9 -2
  17. snowflake/cli/_plugins/nativeapp/codegen/snowpark/python_processor.py +6 -3
  18. snowflake/cli/_plugins/nativeapp/codegen/templates/templates_processor.py +44 -34
  19. snowflake/cli/_plugins/nativeapp/commands.py +113 -21
  20. snowflake/cli/_plugins/nativeapp/constants.py +5 -0
  21. snowflake/cli/_plugins/nativeapp/entities/application.py +226 -296
  22. snowflake/cli/_plugins/nativeapp/entities/application_package.py +911 -141
  23. snowflake/cli/_plugins/nativeapp/entities/application_package_child_interface.py +43 -0
  24. snowflake/cli/_plugins/nativeapp/feature_flags.py +5 -1
  25. snowflake/cli/_plugins/nativeapp/release_channel/__init__.py +13 -0
  26. snowflake/cli/_plugins/nativeapp/release_channel/commands.py +246 -0
  27. snowflake/cli/_plugins/nativeapp/release_directive/__init__.py +13 -0
  28. snowflake/cli/_plugins/nativeapp/release_directive/commands.py +243 -0
  29. snowflake/cli/_plugins/nativeapp/same_account_install_method.py +9 -17
  30. snowflake/cli/_plugins/nativeapp/sf_facade_exceptions.py +80 -0
  31. snowflake/cli/_plugins/nativeapp/sf_sql_facade.py +1184 -80
  32. snowflake/cli/_plugins/nativeapp/utils.py +11 -0
  33. snowflake/cli/_plugins/nativeapp/v2_conversions/compat.py +7 -3
  34. snowflake/cli/_plugins/nativeapp/version/commands.py +32 -5
  35. snowflake/cli/_plugins/notebook/commands.py +55 -2
  36. snowflake/cli/_plugins/notebook/exceptions.py +1 -1
  37. snowflake/cli/_plugins/notebook/manager.py +7 -5
  38. snowflake/cli/_plugins/notebook/notebook_entity.py +120 -0
  39. snowflake/cli/_plugins/notebook/notebook_entity_model.py +42 -0
  40. snowflake/cli/_plugins/notebook/notebook_project_paths.py +15 -0
  41. snowflake/cli/_plugins/notebook/types.py +3 -0
  42. snowflake/cli/_plugins/snowpark/commands.py +48 -30
  43. snowflake/cli/_plugins/snowpark/common.py +47 -2
  44. snowflake/cli/_plugins/snowpark/snowpark_entity.py +247 -4
  45. snowflake/cli/_plugins/snowpark/snowpark_entity_model.py +18 -30
  46. snowflake/cli/_plugins/snowpark/snowpark_project_paths.py +156 -23
  47. snowflake/cli/_plugins/snowpark/zipper.py +33 -1
  48. snowflake/cli/_plugins/spcs/common.py +129 -0
  49. snowflake/cli/_plugins/spcs/services/commands.py +131 -14
  50. snowflake/cli/_plugins/spcs/services/manager.py +169 -1
  51. snowflake/cli/_plugins/stage/commands.py +2 -1
  52. snowflake/cli/_plugins/stage/diff.py +60 -39
  53. snowflake/cli/_plugins/stage/manager.py +34 -13
  54. snowflake/cli/_plugins/stage/utils.py +1 -1
  55. snowflake/cli/_plugins/streamlit/commands.py +10 -1
  56. snowflake/cli/_plugins/streamlit/manager.py +70 -22
  57. snowflake/cli/_plugins/streamlit/streamlit_entity.py +131 -1
  58. snowflake/cli/_plugins/streamlit/streamlit_entity_model.py +14 -24
  59. snowflake/cli/_plugins/streamlit/streamlit_project_paths.py +30 -0
  60. snowflake/cli/_plugins/workspace/commands.py +6 -5
  61. snowflake/cli/_plugins/workspace/manager.py +9 -5
  62. snowflake/cli/api/artifacts/__init__.py +13 -0
  63. snowflake/cli/api/artifacts/bundle_map.py +500 -0
  64. snowflake/cli/api/artifacts/common.py +78 -0
  65. snowflake/cli/api/artifacts/utils.py +82 -0
  66. snowflake/cli/api/cli_global_context.py +36 -2
  67. snowflake/cli/api/commands/flags.py +10 -4
  68. snowflake/cli/api/commands/utils.py +28 -2
  69. snowflake/cli/api/config.py +6 -2
  70. snowflake/cli/api/connections.py +12 -1
  71. snowflake/cli/api/constants.py +10 -1
  72. snowflake/cli/api/entities/common.py +81 -14
  73. snowflake/cli/api/entities/resolver.py +160 -0
  74. snowflake/cli/api/entities/utils.py +65 -23
  75. snowflake/cli/api/errno.py +63 -3
  76. snowflake/cli/api/feature_flags.py +19 -4
  77. snowflake/cli/api/metrics.py +21 -27
  78. snowflake/cli/api/project/definition_conversion.py +4 -4
  79. snowflake/cli/api/project/project_paths.py +28 -0
  80. snowflake/cli/api/project/schemas/entities/common.py +130 -1
  81. snowflake/cli/api/project/schemas/entities/entities.py +4 -0
  82. snowflake/cli/api/project/schemas/project_definition.py +54 -6
  83. snowflake/cli/api/project/schemas/updatable_model.py +2 -2
  84. snowflake/cli/api/project/schemas/v1/native_app/native_app.py +5 -7
  85. snowflake/cli/api/project/schemas/v1/streamlit/streamlit.py +1 -1
  86. snowflake/cli/api/project/util.py +45 -0
  87. snowflake/cli/api/secure_path.py +6 -0
  88. snowflake/cli/api/sql_execution.py +5 -1
  89. snowflake/cli/api/stage_path.py +7 -2
  90. snowflake/cli/api/utils/graph.py +3 -0
  91. snowflake/cli/api/utils/path_utils.py +24 -0
  92. {snowflake_cli-3.2.2.dist-info → snowflake_cli-3.4.1.dist-info}/METADATA +14 -15
  93. {snowflake_cli-3.2.2.dist-info → snowflake_cli-3.4.1.dist-info}/RECORD +96 -82
  94. {snowflake_cli-3.2.2.dist-info → snowflake_cli-3.4.1.dist-info}/WHEEL +1 -1
  95. snowflake/cli/api/project/schemas/v1/native_app/path_mapping.py +0 -65
  96. {snowflake_cli-3.2.2.dist-info → snowflake_cli-3.4.1.dist-info}/entry_points.txt +0 -0
  97. {snowflake_cli-3.2.2.dist-info → snowflake_cli-3.4.1.dist-info}/licenses/LICENSE +0 -0
@@ -14,7 +14,9 @@
14
14
 
15
15
  from __future__ import annotations
16
16
 
17
+ import json
17
18
  import sys
19
+ from datetime import datetime
18
20
  from typing import TextIO
19
21
 
20
22
  from click import ClickException
@@ -23,6 +25,22 @@ from snowflake.cli.api.exceptions import ObjectAlreadyExistsError
23
25
  from snowflake.cli.api.project.util import unquote_identifier
24
26
  from snowflake.connector.errors import ProgrammingError
25
27
 
28
+ EVENT_COLUMN_NAMES = [
29
+ "TIMESTAMP",
30
+ "START_TIMESTAMP",
31
+ "OBSERVED_TIMESTAMP",
32
+ "TRACE",
33
+ "RESOURCE",
34
+ "RESOURCE_ATTRIBUTES",
35
+ "SCOPE",
36
+ "SCOPE_ATTRIBUTES",
37
+ "RECORD_TYPE",
38
+ "RECORD",
39
+ "RECORD_ATTRIBUTES",
40
+ "VALUE",
41
+ "EXEMPLARS",
42
+ ]
43
+
26
44
  if not sys.stdout.closed and sys.stdout.isatty():
27
45
  GREEN = "\033[32m"
28
46
  BLUE = "\033[34m"
@@ -124,5 +142,116 @@ def new_logs_only(prev_log_records: list[str], new_log_records: list[str]) -> li
124
142
  return new_log_records_sorted
125
143
 
126
144
 
145
+ def build_resource_clause(
146
+ service_name: str, instance_id: str, container_name: str
147
+ ) -> str:
148
+ resource_filters = []
149
+ if service_name:
150
+ resource_filters.append(
151
+ f"resource_attributes:\"snow.service.name\" = '{service_name}'"
152
+ )
153
+ if instance_id:
154
+ resource_filters.append(
155
+ f"(resource_attributes:\"snow.service.instance\" = '{instance_id}' "
156
+ f"OR resource_attributes:\"snow.service.container.instance\" = '{instance_id}')"
157
+ )
158
+ if container_name:
159
+ resource_filters.append(
160
+ f"resource_attributes:\"snow.service.container.name\" = '{container_name}'"
161
+ )
162
+ return " and ".join(resource_filters) if resource_filters else "1=1"
163
+
164
+
165
+ def build_time_clauses(
166
+ since: str | datetime | None, until: str | datetime | None
167
+ ) -> tuple[str, str]:
168
+ since_clause = ""
169
+ until_clause = ""
170
+
171
+ if isinstance(since, datetime):
172
+ since_clause = f"and timestamp >= '{since}'"
173
+ elif isinstance(since, str) and since:
174
+ since_clause = f"and timestamp >= sysdate() - interval '{since}'"
175
+
176
+ if isinstance(until, datetime):
177
+ until_clause = f"and timestamp <= '{until}'"
178
+ elif isinstance(until, str) and until:
179
+ until_clause = f"and timestamp <= sysdate() - interval '{until}'"
180
+
181
+ return since_clause, until_clause
182
+
183
+
184
+ def format_event_row(event_dict: dict) -> dict:
185
+ try:
186
+ resource_attributes = json.loads(event_dict.get("RESOURCE_ATTRIBUTES", "{}"))
187
+ record_attributes = json.loads(event_dict.get("RECORD_ATTRIBUTES", "{}"))
188
+ record = json.loads(event_dict.get("RECORD", "{}"))
189
+
190
+ database_name = resource_attributes.get("snow.database.name", "N/A")
191
+ schema_name = resource_attributes.get("snow.schema.name", "N/A")
192
+ service_name = resource_attributes.get("snow.service.name", "N/A")
193
+ instance_name = resource_attributes.get("snow.service.instance", "N/A")
194
+ container_name = resource_attributes.get("snow.service.container.name", "N/A")
195
+ event_name = record_attributes.get("event.name", "Unknown Event")
196
+ event_value = event_dict.get("VALUE", "Unknown Value")
197
+ severity = record.get("severity_text", "Unknown Severity")
198
+
199
+ return {
200
+ "TIMESTAMP": event_dict.get("TIMESTAMP", "N/A"),
201
+ "DATABASE NAME": database_name,
202
+ "SCHEMA NAME": schema_name,
203
+ "SERVICE NAME": service_name,
204
+ "INSTANCE ID": instance_name,
205
+ "CONTAINER NAME": container_name,
206
+ "SEVERITY": severity,
207
+ "EVENT NAME": event_name,
208
+ "EVENT VALUE": event_value,
209
+ }
210
+ except (json.JSONDecodeError, KeyError) as e:
211
+ raise RecordProcessingError(f"Error processing event row.")
212
+
213
+
214
+ def format_metric_row(metric_dict: dict) -> dict:
215
+ try:
216
+ resource_attributes = json.loads(metric_dict["RESOURCE_ATTRIBUTES"])
217
+ record = json.loads(metric_dict["RECORD"])
218
+
219
+ database_name = resource_attributes.get("snow.database.name", "N/A")
220
+ schema_name = resource_attributes.get("snow.schema.name", "N/A")
221
+ service_name = resource_attributes.get("snow.service.name", "N/A")
222
+ instance_name = resource_attributes.get(
223
+ "snow.service.container.instance", "N/A"
224
+ )
225
+ container_name = resource_attributes.get("snow.service.container.name", "N/A")
226
+
227
+ metric_name = record["metric"].get("name", "Unknown Metric")
228
+ metric_value = metric_dict.get("VALUE", "Unknown Value")
229
+
230
+ return {
231
+ "TIMESTAMP": metric_dict.get("TIMESTAMP", "N/A"),
232
+ "DATABASE NAME": database_name,
233
+ "SCHEMA NAME": schema_name,
234
+ "SERVICE NAME": service_name,
235
+ "INSTANCE ID": instance_name,
236
+ "CONTAINER NAME": container_name,
237
+ "METRIC NAME": metric_name,
238
+ "METRIC VALUE": metric_value,
239
+ }
240
+ except (json.JSONDecodeError, KeyError) as e:
241
+ raise RecordProcessingError(f"Error processing metric row.")
242
+
243
+
244
+ class RecordProcessingError(ClickException):
245
+ """Raised when processing an event or metric record fails due to invalid data."""
246
+
247
+ pass
248
+
249
+
250
+ class SPCSEventTableError(ClickException):
251
+ """Raised when there is an issue related to the SPCS event table."""
252
+
253
+ pass
254
+
255
+
127
256
  class NoPropertiesProvidedError(ClickException):
128
257
  pass
@@ -37,9 +37,12 @@ from snowflake.cli.api.commands.flags import (
37
37
  )
38
38
  from snowflake.cli.api.commands.snow_typer import SnowTyperFactory
39
39
  from snowflake.cli.api.constants import ObjectType
40
- from snowflake.cli.api.exceptions import IncompatibleParametersError
40
+ from snowflake.cli.api.exceptions import (
41
+ IncompatibleParametersError,
42
+ )
41
43
  from snowflake.cli.api.identifiers import FQN
42
44
  from snowflake.cli.api.output.types import (
45
+ CollectionResult,
43
46
  CommandResult,
44
47
  MessageResult,
45
48
  QueryJsonValueResult,
@@ -55,6 +58,38 @@ app = SnowTyperFactory(
55
58
  short_help="Manages services.",
56
59
  )
57
60
 
61
+ # Define common options
62
+ container_name_option = typer.Option(
63
+ ...,
64
+ "--container-name",
65
+ help="Name of the container.",
66
+ show_default=False,
67
+ )
68
+
69
+ instance_id_option = typer.Option(
70
+ ...,
71
+ "--instance-id",
72
+ help="ID of the service instance, starting with 0.",
73
+ show_default=False,
74
+ )
75
+
76
+ since_option = typer.Option(
77
+ default="",
78
+ help="Fetch events that are newer than this time ago, in Snowflake interval syntax.",
79
+ )
80
+
81
+ until_option = typer.Option(
82
+ default="",
83
+ help="Fetch events that are older than this time ago, in Snowflake interval syntax.",
84
+ )
85
+
86
+ show_all_columns_option = typer.Option(
87
+ False,
88
+ "--all",
89
+ is_flag=True,
90
+ help="Fetch all columns.",
91
+ )
92
+
58
93
 
59
94
  def _service_name_callback(name: FQN) -> FQN:
60
95
  if not is_valid_object_name(name.identifier, max_depth=2, allow_quoted=False):
@@ -209,18 +244,8 @@ def status(name: FQN = ServiceNameArgument, **options) -> CommandResult:
209
244
  @app.command(requires_connection=True)
210
245
  def logs(
211
246
  name: FQN = ServiceNameArgument,
212
- container_name: str = typer.Option(
213
- ...,
214
- "--container-name",
215
- help="Name of the container.",
216
- show_default=False,
217
- ),
218
- instance_id: str = typer.Option(
219
- ...,
220
- "--instance-id",
221
- help="ID of the service instance, starting with 0.",
222
- show_default=False,
223
- ),
247
+ container_name: str = container_name_option,
248
+ instance_id: str = instance_id_option,
224
249
  num_lines: int = typer.Option(
225
250
  DEFAULT_NUM_LINES, "--num-lines", help="Number of lines to retrieve."
226
251
  ),
@@ -237,12 +262,17 @@ def logs(
237
262
  False, "--include-timestamps", help="Include timestamps in logs.", is_flag=True
238
263
  ),
239
264
  follow: bool = typer.Option(
240
- False, "--follow", help="Stream logs in real-time.", is_flag=True
265
+ False,
266
+ "--follow",
267
+ help="Stream logs in real-time.",
268
+ is_flag=True,
269
+ hidden=True,
241
270
  ),
242
271
  follow_interval: int = typer.Option(
243
272
  2,
244
273
  "--follow-interval",
245
274
  help="Set custom polling intervals for log streaming (--follow flag) in seconds.",
275
+ hidden=True,
246
276
  ),
247
277
  **options,
248
278
  ):
@@ -288,6 +318,93 @@ def logs(
288
318
  return StreamResult(cast(Generator[CommandResult, None, None], stream))
289
319
 
290
320
 
321
+ @app.command(
322
+ requires_connection=True,
323
+ )
324
+ def events(
325
+ name: FQN = ServiceNameArgument,
326
+ container_name: str = container_name_option,
327
+ instance_id: str = instance_id_option,
328
+ since: str = since_option,
329
+ until: str = until_option,
330
+ first: int = typer.Option(
331
+ default=None,
332
+ show_default=False,
333
+ help="Fetch only the first N events. Cannot be used with --last.",
334
+ ),
335
+ last: int = typer.Option(
336
+ default=None,
337
+ show_default=False,
338
+ help="Fetch only the last N events. Cannot be used with --first.",
339
+ ),
340
+ show_all_columns: bool = show_all_columns_option,
341
+ **options,
342
+ ):
343
+ """
344
+ Retrieve platform events for a service container.
345
+ """
346
+
347
+ if first is not None and last is not None:
348
+ raise IncompatibleParametersError(["--first", "--last"])
349
+
350
+ manager = ServiceManager()
351
+ events = manager.get_events(
352
+ service_name=name.identifier,
353
+ container_name=container_name,
354
+ instance_id=instance_id,
355
+ since=since,
356
+ until=until,
357
+ first=first,
358
+ last=last,
359
+ show_all_columns=show_all_columns,
360
+ )
361
+
362
+ if not events:
363
+ return MessageResult("No events found.")
364
+
365
+ return CollectionResult(events)
366
+
367
+
368
+ @app.command(
369
+ requires_connection=True,
370
+ )
371
+ def metrics(
372
+ name: FQN = ServiceNameArgument,
373
+ container_name: str = container_name_option,
374
+ instance_id: str = instance_id_option,
375
+ since: str = since_option,
376
+ until: str = until_option,
377
+ show_all_columns: bool = show_all_columns_option,
378
+ **options,
379
+ ):
380
+ """
381
+ Retrieve platform metrics for a service container.
382
+ """
383
+
384
+ manager = ServiceManager()
385
+ if since or until:
386
+ metrics = manager.get_all_metrics(
387
+ service_name=name.identifier,
388
+ container_name=container_name,
389
+ instance_id=instance_id,
390
+ since=since,
391
+ until=until,
392
+ show_all_columns=show_all_columns,
393
+ )
394
+ else:
395
+ metrics = manager.get_latest_metrics(
396
+ service_name=name.identifier,
397
+ container_name=container_name,
398
+ instance_id=instance_id,
399
+ show_all_columns=show_all_columns,
400
+ )
401
+
402
+ if not metrics:
403
+ return MessageResult("No metrics found.")
404
+
405
+ return CollectionResult(metrics)
406
+
407
+
291
408
  @app.command(requires_connection=True)
292
409
  def upgrade(
293
410
  name: FQN = ServiceNameArgument,
@@ -16,14 +16,21 @@ from __future__ import annotations
16
16
 
17
17
  import json
18
18
  import time
19
+ from datetime import datetime
19
20
  from pathlib import Path
20
21
  from typing import List, Optional
21
22
 
22
23
  import yaml
23
24
  from snowflake.cli._plugins.object.common import Tag
24
25
  from snowflake.cli._plugins.spcs.common import (
26
+ EVENT_COLUMN_NAMES,
25
27
  NoPropertiesProvidedError,
28
+ SPCSEventTableError,
29
+ build_resource_clause,
30
+ build_time_clauses,
26
31
  filter_log_timestamp,
32
+ format_event_row,
33
+ format_metric_row,
27
34
  handle_object_already_exists,
28
35
  new_logs_only,
29
36
  strip_empty_lines,
@@ -31,7 +38,7 @@ from snowflake.cli._plugins.spcs.common import (
31
38
  from snowflake.cli.api.constants import DEFAULT_SIZE_LIMIT_MB, ObjectType
32
39
  from snowflake.cli.api.secure_path import SecurePath
33
40
  from snowflake.cli.api.sql_execution import SqlExecutionMixin
34
- from snowflake.connector.cursor import SnowflakeCursor
41
+ from snowflake.connector.cursor import DictCursor, SnowflakeCursor
35
42
  from snowflake.connector.errors import ProgrammingError
36
43
 
37
44
 
@@ -199,6 +206,167 @@ class ServiceManager(SqlExecutionMixin):
199
206
  except KeyboardInterrupt:
200
207
  return
201
208
 
209
+ def get_account_event_table(self):
210
+ query = "show parameters like 'event_table' in account"
211
+ results = self.execute_query(query, cursor_class=DictCursor)
212
+ event_table = next(
213
+ (r["value"] for r in results if r["key"] == "EVENT_TABLE"), ""
214
+ )
215
+ if not event_table:
216
+ raise SPCSEventTableError("No SPCS event table configured in the account.")
217
+ return event_table
218
+
219
+ def get_events(
220
+ self,
221
+ service_name: str,
222
+ instance_id: str,
223
+ container_name: str,
224
+ since: str | datetime | None = None,
225
+ until: str | datetime | None = None,
226
+ first: Optional[int] = None,
227
+ last: Optional[int] = None,
228
+ show_all_columns: bool = False,
229
+ ):
230
+
231
+ account_event_table = self.get_account_event_table()
232
+ resource_clause = build_resource_clause(
233
+ service_name, instance_id, container_name
234
+ )
235
+ since_clause, until_clause = build_time_clauses(since, until)
236
+
237
+ first_clause = f"limit {first}" if first is not None else ""
238
+ last_clause = f"limit {last}" if last is not None else ""
239
+
240
+ query = f"""\
241
+ select *
242
+ from (
243
+ select *
244
+ from {account_event_table}
245
+ where (
246
+ {resource_clause}
247
+ {since_clause}
248
+ {until_clause}
249
+ )
250
+ and record_type = 'LOG'
251
+ and scope['name'] = 'snow.spcs.platform'
252
+ order by timestamp desc
253
+ {last_clause}
254
+ )
255
+ order by timestamp asc
256
+ {first_clause}
257
+ """
258
+
259
+ cursor = self.execute_query(query)
260
+ raw_events = cursor.fetchall()
261
+ if not raw_events:
262
+ return []
263
+
264
+ if show_all_columns:
265
+ return [dict(zip(EVENT_COLUMN_NAMES, event)) for event in raw_events]
266
+
267
+ formatted_events = []
268
+ for raw_event in raw_events:
269
+ event_dict = dict(zip(EVENT_COLUMN_NAMES, raw_event))
270
+ formatted = format_event_row(event_dict)
271
+ formatted_events.append(formatted)
272
+
273
+ return formatted_events
274
+
275
+ def get_all_metrics(
276
+ self,
277
+ service_name: str,
278
+ instance_id: str,
279
+ container_name: str,
280
+ since: str | datetime | None = None,
281
+ until: str | datetime | None = None,
282
+ show_all_columns: bool = False,
283
+ ):
284
+
285
+ account_event_table = self.get_account_event_table()
286
+ resource_clause = build_resource_clause(
287
+ service_name, instance_id, container_name
288
+ )
289
+ since_clause, until_clause = build_time_clauses(since, until)
290
+
291
+ query = f"""\
292
+ select *
293
+ from {account_event_table}
294
+ where (
295
+ {resource_clause}
296
+ {since_clause}
297
+ {until_clause}
298
+ )
299
+ and record_type = 'METRIC'
300
+ and scope['name'] = 'snow.spcs.platform'
301
+ order by timestamp desc
302
+ """
303
+
304
+ cursor = self.execute_query(query)
305
+ raw_metrics = cursor.fetchall()
306
+ if not raw_metrics:
307
+ return []
308
+
309
+ if show_all_columns:
310
+ return [dict(zip(EVENT_COLUMN_NAMES, metric)) for metric in raw_metrics]
311
+
312
+ formatted_metrics = []
313
+ for raw_metric in raw_metrics:
314
+ metric_dict = dict(zip(EVENT_COLUMN_NAMES, raw_metric))
315
+ formatted = format_metric_row(metric_dict)
316
+ formatted_metrics.append(formatted)
317
+
318
+ return formatted_metrics
319
+
320
+ def get_latest_metrics(
321
+ self,
322
+ service_name: str,
323
+ instance_id: str,
324
+ container_name: str,
325
+ show_all_columns: bool = False,
326
+ ):
327
+
328
+ account_event_table = self.get_account_event_table()
329
+ resource_clause = build_resource_clause(
330
+ service_name, instance_id, container_name
331
+ )
332
+
333
+ query = f"""
334
+ with rankedmetrics as (
335
+ select
336
+ *,
337
+ row_number() over (
338
+ partition by record['metric']['name']
339
+ order by timestamp desc
340
+ ) as rank
341
+ from {account_event_table}
342
+ where
343
+ record_type = 'METRIC'
344
+ and scope['name'] = 'snow.spcs.platform'
345
+ and {resource_clause}
346
+ and timestamp > dateadd('hour', -1, current_timestamp)
347
+ )
348
+ select *
349
+ from rankedmetrics
350
+ where rank = 1
351
+ order by timestamp desc;
352
+ """
353
+
354
+ cursor = self.execute_query(query)
355
+ raw_metrics = cursor.fetchall()
356
+ if not raw_metrics:
357
+ return []
358
+
359
+ if show_all_columns:
360
+ return [dict(zip(EVENT_COLUMN_NAMES, metric)) for metric in raw_metrics]
361
+
362
+ formatted_metrics = []
363
+ for raw_metric in raw_metrics:
364
+ metric_dict = dict(zip(EVENT_COLUMN_NAMES, raw_metric))
365
+ formatted = format_metric_row(metric_dict)
366
+ formatted_metrics.append(formatted)
367
+
368
+ return formatted_metrics
369
+
202
370
  def upgrade_spec(self, service_name: str, spec_path: Path):
203
371
  spec = self._read_yaml(spec_path)
204
372
  query = f"alter service {service_name} from specification $$ {spec} $$"
@@ -192,7 +192,8 @@ def stage_diff(
192
192
  Diffs a stage with a local folder.
193
193
  """
194
194
  diff: DiffResult = compute_stage_diff(
195
- local_root=Path(folder_name), stage_fqn=stage_name
195
+ local_root=Path(folder_name),
196
+ stage_path=StageManager.stage_path_parts_from_str(stage_name), # noqa: SLF001
196
197
  )
197
198
  if get_cli_context().output_format == OutputFormat.JSON:
198
199
  return ObjectResult(diff.to_dict())