snowflake-cli-labs 3.0.0rc3__py3-none-any.whl → 3.0.0rc5__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 (44) hide show
  1. snowflake/cli/__about__.py +1 -1
  2. snowflake/cli/_app/telemetry.py +28 -0
  3. snowflake/cli/_plugins/connection/commands.py +9 -4
  4. snowflake/cli/_plugins/helpers/commands.py +34 -1
  5. snowflake/cli/_plugins/nativeapp/codegen/compiler.py +5 -0
  6. snowflake/cli/_plugins/nativeapp/codegen/snowpark/python_processor.py +4 -0
  7. snowflake/cli/_plugins/nativeapp/codegen/templates/templates_processor.py +3 -0
  8. snowflake/cli/_plugins/nativeapp/commands.py +9 -86
  9. snowflake/cli/_plugins/nativeapp/entities/__init__.py +0 -0
  10. snowflake/cli/_plugins/nativeapp/{application_entity.py → entities/application.py} +266 -39
  11. snowflake/cli/_plugins/nativeapp/{application_package_entity.py → entities/application_package.py} +357 -72
  12. snowflake/cli/_plugins/nativeapp/manager.py +62 -183
  13. snowflake/cli/_plugins/nativeapp/run_processor.py +6 -6
  14. snowflake/cli/_plugins/nativeapp/teardown_processor.py +2 -4
  15. snowflake/cli/_plugins/nativeapp/v2_conversions/v2_to_v1_decorator.py +2 -4
  16. snowflake/cli/_plugins/nativeapp/version/commands.py +1 -15
  17. snowflake/cli/_plugins/nativeapp/version/version_processor.py +16 -82
  18. snowflake/cli/_plugins/object/manager.py +36 -15
  19. snowflake/cli/_plugins/streamlit/commands.py +12 -0
  20. snowflake/cli/_plugins/streamlit/manager.py +4 -0
  21. snowflake/cli/_plugins/workspace/commands.py +33 -0
  22. snowflake/cli/api/cli_global_context.py +7 -0
  23. snowflake/cli/api/commands/decorators.py +14 -0
  24. snowflake/cli/api/commands/flags.py +18 -0
  25. snowflake/cli/api/config.py +25 -6
  26. snowflake/cli/api/connections.py +3 -1
  27. snowflake/cli/api/entities/common.py +1 -0
  28. snowflake/cli/api/entities/utils.py +3 -0
  29. snowflake/cli/api/metrics.py +92 -0
  30. snowflake/cli/api/project/definition_conversion.py +69 -22
  31. snowflake/cli/api/project/definition_manager.py +5 -5
  32. snowflake/cli/api/project/schemas/entities/entities.py +3 -5
  33. snowflake/cli/api/project/schemas/project_definition.py +1 -3
  34. snowflake/cli/api/rendering/sql_templates.py +6 -0
  35. snowflake/cli/api/rest_api.py +11 -5
  36. snowflake/cli/api/utils/definition_rendering.py +24 -4
  37. {snowflake_cli_labs-3.0.0rc3.dist-info → snowflake_cli_labs-3.0.0rc5.dist-info}/METADATA +4 -2
  38. {snowflake_cli_labs-3.0.0rc3.dist-info → snowflake_cli_labs-3.0.0rc5.dist-info}/RECORD +41 -42
  39. snowflake/cli/_plugins/nativeapp/application_entity_model.py +0 -56
  40. snowflake/cli/_plugins/nativeapp/application_package_entity_model.py +0 -94
  41. snowflake/cli/_plugins/nativeapp/init.py +0 -345
  42. {snowflake_cli_labs-3.0.0rc3.dist-info → snowflake_cli_labs-3.0.0rc5.dist-info}/WHEEL +0 -0
  43. {snowflake_cli_labs-3.0.0rc3.dist-info → snowflake_cli_labs-3.0.0rc5.dist-info}/entry_points.txt +0 -0
  44. {snowflake_cli_labs-3.0.0rc3.dist-info → snowflake_cli_labs-3.0.0rc5.dist-info}/licenses/LICENSE +0 -0
@@ -14,28 +14,22 @@
14
14
 
15
15
  from __future__ import annotations
16
16
 
17
- import time
18
17
  from abc import ABC, abstractmethod
19
18
  from datetime import datetime
20
19
  from functools import cached_property
21
20
  from pathlib import Path
22
- from textwrap import dedent
23
21
  from typing import Generator, List, Optional
24
22
 
25
- from snowflake.cli._plugins.connection.util import make_snowsight_url
26
- from snowflake.cli._plugins.nativeapp.application_entity import (
23
+ from snowflake.cli._plugins.nativeapp.artifacts import (
24
+ BundleMap,
25
+ )
26
+ from snowflake.cli._plugins.nativeapp.entities.application import (
27
27
  ApplicationEntity,
28
28
  ApplicationOwnedObject,
29
29
  )
30
- from snowflake.cli._plugins.nativeapp.application_package_entity import (
30
+ from snowflake.cli._plugins.nativeapp.entities.application_package import (
31
31
  ApplicationPackageEntity,
32
32
  )
33
- from snowflake.cli._plugins.nativeapp.artifacts import (
34
- BundleMap,
35
- )
36
- from snowflake.cli._plugins.nativeapp.exceptions import (
37
- NoEventTableForAccount,
38
- )
39
33
  from snowflake.cli._plugins.nativeapp.policy import AllowAlwaysPolicy, PolicyBase
40
34
  from snowflake.cli._plugins.nativeapp.project_model import (
41
35
  NativeAppProjectModel,
@@ -46,18 +40,11 @@ from snowflake.cli._plugins.stage.diff import (
46
40
  from snowflake.cli.api.console import cli_console as cc
47
41
  from snowflake.cli.api.entities.utils import (
48
42
  execute_post_deploy_hooks,
49
- generic_sql_error_handler,
50
43
  sync_deploy_root_with_stage,
51
44
  )
52
45
  from snowflake.cli.api.project.schemas.entities.common import PostDeployHook
53
46
  from snowflake.cli.api.project.schemas.v1.native_app.native_app import NativeApp
54
47
  from snowflake.cli.api.project.schemas.v1.native_app.path_mapping import PathMapping
55
- from snowflake.cli.api.project.util import (
56
- identifier_for_url,
57
- unquote_identifier,
58
- )
59
- from snowflake.cli.api.sql_execution import SqlExecutionMixin
60
- from snowflake.connector import DictCursor, ProgrammingError
61
48
 
62
49
 
63
50
  class NativeAppCommandProcessor(ABC):
@@ -66,7 +53,7 @@ class NativeAppCommandProcessor(ABC):
66
53
  pass
67
54
 
68
55
 
69
- class NativeAppManager(SqlExecutionMixin):
56
+ class NativeAppManager:
70
57
  """
71
58
  Base class with frequently used functionality already implemented and ready to be used by related subclasses.
72
59
  """
@@ -135,9 +122,6 @@ class NativeAppManager(SqlExecutionMixin):
135
122
  def application_warehouse(self) -> Optional[str]:
136
123
  return self.na_project.application_warehouse
137
124
 
138
- def use_application_warehouse(self):
139
- return ApplicationEntity.use_application_warehouse(self.application_warehouse)
140
-
141
125
  @property
142
126
  def project_identifier(self) -> str:
143
127
  return self.na_project.project_identifier
@@ -182,9 +166,7 @@ class NativeAppManager(SqlExecutionMixin):
182
166
 
183
167
  @cached_property
184
168
  def account_event_table(self) -> str:
185
- query = "show parameters like 'event_table' in account"
186
- results = self._execute_query(query, cursor_class=DictCursor)
187
- return next((r["value"] for r in results if r["key"] == "EVENT_TABLE"), "")
169
+ return ApplicationEntity.get_account_event_table()
188
170
 
189
171
  def verify_project_distribution(
190
172
  self, expected_distribution: Optional[str] = None
@@ -262,9 +244,9 @@ class NativeAppManager(SqlExecutionMixin):
262
244
 
263
245
  def get_snowsight_url(self) -> str:
264
246
  """Returns the URL that can be used to visit this app via Snowsight."""
265
- name = identifier_for_url(self.app_name)
266
- with self.use_application_warehouse():
267
- return make_snowsight_url(self._conn, f"/#/apps/application/{name}")
247
+ return ApplicationEntity.get_snowsight_url(
248
+ self.app_name, self.application_warehouse
249
+ )
268
250
 
269
251
  def create_app_package(self) -> None:
270
252
  return ApplicationPackageEntity.create_app_package(
@@ -336,41 +318,53 @@ class NativeAppManager(SqlExecutionMixin):
336
318
  policy=policy,
337
319
  )
338
320
 
339
- def deploy_to_scratch_stage_fn(self):
340
- bundle_map = self.build_bundle()
341
- self.deploy(
342
- bundle_map=bundle_map,
343
- prune=True,
344
- recursive=True,
345
- stage_fqn=self.scratch_stage_fqn,
346
- validate=False,
347
- print_diff=False,
348
- policy=AllowAlwaysPolicy(),
349
- )
350
-
351
321
  def validate(self, use_scratch_stage: bool = False):
352
322
  return ApplicationPackageEntity.validate_setup_script(
353
323
  console=cc,
324
+ project_root=self.project_root,
325
+ deploy_root=self.deploy_root,
326
+ bundle_root=self.bundle_root,
327
+ generated_root=self.generated_root,
328
+ artifacts=self.artifacts,
354
329
  package_name=self.package_name,
355
330
  package_role=self.package_role,
331
+ package_distribution=self.package_distribution,
332
+ prune=True,
333
+ recursive=True,
334
+ paths=[],
356
335
  stage_fqn=self.stage_fqn,
336
+ package_warehouse=self.package_warehouse,
337
+ post_deploy_hooks=self.package_post_deploy_hooks,
338
+ package_scripts=self.package_scripts,
339
+ policy=AllowAlwaysPolicy(),
357
340
  use_scratch_stage=use_scratch_stage,
358
341
  scratch_stage_fqn=self.scratch_stage_fqn,
359
- deploy_to_scratch_stage_fn=self.deploy_to_scratch_stage_fn,
360
342
  )
361
343
 
362
- def get_validation_result(self, use_scratch_stage: bool):
344
+ def get_validation_result(self, use_scratch_stage: bool = False):
363
345
  return ApplicationPackageEntity.get_validation_result(
364
346
  console=cc,
347
+ project_root=self.project_root,
348
+ deploy_root=self.deploy_root,
349
+ bundle_root=self.bundle_root,
350
+ generated_root=self.generated_root,
351
+ artifacts=self.artifacts,
365
352
  package_name=self.package_name,
366
353
  package_role=self.package_role,
354
+ package_distribution=self.package_distribution,
355
+ prune=True,
356
+ recursive=True,
357
+ paths=[],
367
358
  stage_fqn=self.stage_fqn,
359
+ package_warehouse=self.package_warehouse,
360
+ post_deploy_hooks=self.package_post_deploy_hooks,
361
+ package_scripts=self.package_scripts,
362
+ policy=AllowAlwaysPolicy(),
368
363
  use_scratch_stage=use_scratch_stage,
369
364
  scratch_stage_fqn=self.scratch_stage_fqn,
370
- deploy_to_scratch_stage_fn=self.deploy_to_scratch_stage_fn,
371
365
  )
372
366
 
373
- def get_events( # type: ignore [return]
367
+ def get_events( # type: ignore
374
368
  self,
375
369
  since: str | datetime | None = None,
376
370
  until: str | datetime | None = None,
@@ -382,87 +376,19 @@ class NativeAppManager(SqlExecutionMixin):
382
376
  first: int = -1,
383
377
  last: int = -1,
384
378
  ) -> list[dict]:
385
- record_types = record_types or []
386
- scopes = scopes or []
387
-
388
- if first >= 0 and last >= 0:
389
- raise ValueError("first and last cannot be used together")
390
-
391
- if not self.account_event_table:
392
- raise NoEventTableForAccount()
393
-
394
- # resource_attributes uses the unquoted/uppercase app and package name
395
- app_name = unquote_identifier(self.app_name)
396
- package_name = unquote_identifier(self.package_name)
397
- org_name = unquote_identifier(consumer_org)
398
- account_name = unquote_identifier(consumer_account)
399
-
400
- # Filter on record attributes
401
- if consumer_org and consumer_account:
402
- # Look for events shared from a consumer account
403
- app_clause = (
404
- f"resource_attributes:\"snow.application.package.name\" = '{package_name}' "
405
- f"and resource_attributes:\"snow.application.consumer.organization\" = '{org_name}' "
406
- f"and resource_attributes:\"snow.application.consumer.name\" = '{account_name}'"
407
- )
408
- if consumer_app_hash:
409
- # If the user has specified a hash of a specific app installation
410
- # in the consumer account, filter events to that installation only
411
- app_clause += f" and resource_attributes:\"snow.database.hash\" = '{consumer_app_hash.lower()}'"
412
- else:
413
- # Otherwise look for events from an app installed in the same account as the package
414
- app_clause = f"resource_attributes:\"snow.database.name\" = '{app_name}'"
415
-
416
- # Filter on event time
417
- if isinstance(since, datetime):
418
- since_clause = f"and timestamp >= '{since}'"
419
- elif isinstance(since, str) and since:
420
- since_clause = f"and timestamp >= sysdate() - interval '{since}'"
421
- else:
422
- since_clause = ""
423
- if isinstance(until, datetime):
424
- until_clause = f"and timestamp <= '{until}'"
425
- elif isinstance(until, str) and until:
426
- until_clause = f"and timestamp <= sysdate() - interval '{until}'"
427
- else:
428
- until_clause = ""
429
-
430
- # Filter on event type (log, span, span_event)
431
- type_in_values = ",".join(f"'{v}'" for v in record_types)
432
- types_clause = (
433
- f"and record_type in ({type_in_values})" if type_in_values else ""
434
- )
435
-
436
- # Filter on event scope (e.g. the logger name)
437
- scope_in_values = ",".join(f"'{v}'" for v in scopes)
438
- scopes_clause = (
439
- f"and scope:name in ({scope_in_values})" if scope_in_values else ""
440
- )
441
-
442
- # Limit event count
443
- first_clause = f"limit {first}" if first >= 0 else ""
444
- last_clause = f"limit {last}" if last >= 0 else ""
445
-
446
- query = dedent(
447
- f"""\
448
- select * from (
449
- select timestamp, value::varchar value
450
- from {self.account_event_table}
451
- where ({app_clause})
452
- {since_clause}
453
- {until_clause}
454
- {types_clause}
455
- {scopes_clause}
456
- order by timestamp desc
457
- {last_clause}
458
- ) order by timestamp asc
459
- {first_clause}
460
- """
379
+ return ApplicationEntity.get_events(
380
+ app_name=self.app_name,
381
+ package_name=self.package_name,
382
+ since=since,
383
+ until=until,
384
+ record_types=record_types,
385
+ scopes=scopes,
386
+ consumer_org=consumer_org,
387
+ consumer_account=consumer_account,
388
+ consumer_app_hash=consumer_app_hash,
389
+ first=first,
390
+ last=last,
461
391
  )
462
- try:
463
- return self._execute_query(query, cursor_class=DictCursor).fetchall()
464
- except ProgrammingError as err:
465
- generic_sql_error_handler(err)
466
392
 
467
393
  def stream_events(
468
394
  self,
@@ -475,62 +401,15 @@ class NativeAppManager(SqlExecutionMixin):
475
401
  consumer_app_hash: str = "",
476
402
  last: int = -1,
477
403
  ) -> Generator[dict, None, None]:
478
- try:
479
- events = self.get_events(
480
- since=since,
481
- record_types=record_types,
482
- scopes=scopes,
483
- consumer_org=consumer_org,
484
- consumer_account=consumer_account,
485
- consumer_app_hash=consumer_app_hash,
486
- last=last,
487
- )
488
- yield from events # Yield the initial batch of events
489
- last_event_time = events[-1]["TIMESTAMP"] if events else None
490
-
491
- while True: # Then infinite poll for new events
492
- time.sleep(interval_seconds)
493
- previous_events = events
494
- events = self.get_events(
495
- since=last_event_time,
496
- record_types=record_types,
497
- scopes=scopes,
498
- consumer_org=consumer_org,
499
- consumer_account=consumer_account,
500
- consumer_app_hash=consumer_app_hash,
501
- )
502
- if not events:
503
- continue
504
-
505
- yield from _new_events_only(previous_events, events)
506
- last_event_time = events[-1]["TIMESTAMP"]
507
- except KeyboardInterrupt:
508
- return
509
-
510
-
511
- def _new_events_only(previous_events: list[dict], new_events: list[dict]) -> list[dict]:
512
- # The timestamp that overlaps between both sets of events
513
- overlap_time = new_events[0]["TIMESTAMP"]
514
-
515
- # Remove all the events from the new result set
516
- # if they were already printed. We iterate and remove
517
- # instead of filtering in order to handle duplicates
518
- # (i.e. if an event is present 3 times in new_events
519
- # but only once in previous_events, it should still
520
- # appear twice in new_events at the end
521
- new_events = new_events.copy()
522
- for event in reversed(previous_events):
523
- if event["TIMESTAMP"] < overlap_time:
524
- break
525
- # No need to handle ValueError here since we know
526
- # that events that pass the above if check will
527
- # either be in both lists or in new_events only
528
- new_events.remove(event)
529
- return new_events
530
-
531
-
532
- def _validation_item_to_str(item: dict[str, str | int]):
533
- s = item["message"]
534
- if item["errorCode"]:
535
- s = f"{s} (error code {item['errorCode']})"
536
- return s
404
+ return ApplicationEntity.stream_events(
405
+ app_name=self.app_name,
406
+ package_name=self.package_name,
407
+ interval_seconds=interval_seconds,
408
+ since=since,
409
+ record_types=record_types,
410
+ scopes=scopes,
411
+ consumer_org=consumer_org,
412
+ consumer_account=consumer_account,
413
+ consumer_app_hash=consumer_app_hash,
414
+ last=last,
415
+ )
@@ -18,13 +18,11 @@ from pathlib import Path
18
18
  from typing import Optional
19
19
 
20
20
  import typer
21
- from snowflake.cli._plugins.nativeapp.application_entity import (
22
- ApplicationEntity,
23
- )
24
- from snowflake.cli._plugins.nativeapp.application_package_entity import (
21
+ from snowflake.cli._plugins.nativeapp.artifacts import BundleMap
22
+ from snowflake.cli._plugins.nativeapp.entities.application import ApplicationEntity
23
+ from snowflake.cli._plugins.nativeapp.entities.application_package import (
25
24
  ApplicationPackageEntity,
26
25
  )
27
- from snowflake.cli._plugins.nativeapp.artifacts import BundleMap
28
26
  from snowflake.cli._plugins.nativeapp.manager import (
29
27
  NativeAppCommandProcessor,
30
28
  NativeAppManager,
@@ -34,6 +32,7 @@ from snowflake.cli._plugins.nativeapp.same_account_install_method import (
34
32
  SameAccountInstallMethod,
35
33
  )
36
34
  from snowflake.cli.api.console import cli_console as cc
35
+ from snowflake.cli.api.entities.common import get_sql_executor
37
36
  from snowflake.cli.api.entities.utils import (
38
37
  generic_sql_error_handler,
39
38
  )
@@ -100,7 +99,8 @@ class NativeAppRunProcessor(NativeAppManager, NativeAppCommandProcessor):
100
99
  cascade_msg = " (cascade)" if cascade else ""
101
100
  cc.step(f"Dropping application object {self.app_name}{cascade_msg}.")
102
101
  cascade_sql = " cascade" if cascade else ""
103
- self._execute_query(f"drop application {self.app_name}{cascade_sql}")
102
+ sql_executor = get_sql_executor()
103
+ sql_executor.execute_query(f"drop application {self.app_name}{cascade_sql}")
104
104
  except ProgrammingError as err:
105
105
  if err.errno == APPLICATION_OWNS_EXTERNAL_OBJECTS and not cascade:
106
106
  # We need to cascade the deletion, let's try again (only if we didn't try with cascade already)
@@ -17,10 +17,8 @@ from __future__ import annotations
17
17
  from pathlib import Path
18
18
  from typing import Dict, Optional
19
19
 
20
- from snowflake.cli._plugins.nativeapp.application_entity import (
21
- ApplicationEntity,
22
- )
23
- from snowflake.cli._plugins.nativeapp.application_package_entity import (
20
+ from snowflake.cli._plugins.nativeapp.entities.application import ApplicationEntity
21
+ from snowflake.cli._plugins.nativeapp.entities.application_package import (
24
22
  ApplicationPackageEntity,
25
23
  )
26
24
  from snowflake.cli._plugins.nativeapp.manager import (
@@ -20,10 +20,8 @@ from typing import Any, Dict, Optional, Type, TypeVar, Union
20
20
 
21
21
  import typer
22
22
  from click import ClickException
23
- from snowflake.cli._plugins.nativeapp.application_entity_model import (
24
- ApplicationEntityModel,
25
- )
26
- from snowflake.cli._plugins.nativeapp.application_package_entity_model import (
23
+ from snowflake.cli._plugins.nativeapp.entities.application import ApplicationEntityModel
24
+ from snowflake.cli._plugins.nativeapp.entities.application_package import (
27
25
  ApplicationPackageEntityModel,
28
26
  )
29
27
  from snowflake.cli.api.cli_global_context import (
@@ -20,11 +20,6 @@ from typing import Optional
20
20
  import typer
21
21
  from click import MissingParameter
22
22
  from snowflake.cli._plugins.nativeapp.common_flags import ForceOption, InteractiveOption
23
- from snowflake.cli._plugins.nativeapp.policy import (
24
- AllowAlwaysPolicy,
25
- AskAlwaysPolicy,
26
- DenyAlwaysPolicy,
27
- )
28
23
  from snowflake.cli._plugins.nativeapp.run_processor import NativeAppRunProcessor
29
24
  from snowflake.cli._plugins.nativeapp.v2_conversions.v2_to_v1_decorator import (
30
25
  nativeapp_definition_v2_to_v1,
@@ -137,19 +132,10 @@ def drop(
137
132
 
138
133
  assert_project_type("native_app")
139
134
 
140
- is_interactive = False
141
- if force:
142
- policy = AllowAlwaysPolicy()
143
- elif interactive:
144
- is_interactive = True
145
- policy = AskAlwaysPolicy()
146
- else:
147
- policy = DenyAlwaysPolicy()
148
-
149
135
  cli_context = get_cli_context()
150
136
  processor = NativeAppVersionDropProcessor(
151
137
  project_definition=cli_context.project_definition.native_app,
152
138
  project_root=cli_context.project_root,
153
139
  )
154
- processor.process(version, policy, is_interactive)
140
+ processor.process(version, force, interactive)
155
141
  return MessageResult(f"Version drop is now complete.")
@@ -15,30 +15,18 @@
15
15
  from __future__ import annotations
16
16
 
17
17
  from pathlib import Path
18
- from textwrap import dedent
19
18
  from typing import Dict, Optional
20
19
 
21
- import typer
22
- from click import ClickException
23
- from snowflake.cli._plugins.nativeapp.application_package_entity import (
20
+ from snowflake.cli._plugins.nativeapp.entities.application_package import (
24
21
  ApplicationPackageEntity,
25
22
  )
26
- from snowflake.cli._plugins.nativeapp.artifacts import (
27
- find_version_info_in_manifest_file,
28
- )
29
- from snowflake.cli._plugins.nativeapp.exceptions import (
30
- ApplicationPackageDoesNotExistError,
31
- )
32
23
  from snowflake.cli._plugins.nativeapp.manager import (
33
24
  NativeAppCommandProcessor,
34
25
  NativeAppManager,
35
26
  )
36
- from snowflake.cli._plugins.nativeapp.policy import PolicyBase
37
27
  from snowflake.cli._plugins.nativeapp.run_processor import NativeAppRunProcessor
38
28
  from snowflake.cli.api.console import cli_console as cc
39
29
  from snowflake.cli.api.project.schemas.v1.native_app.native_app import NativeApp
40
- from snowflake.cli.api.project.util import to_identifier
41
- from snowflake.connector import ProgrammingError
42
30
 
43
31
 
44
32
  class NativeAppVersionCreateProcessor(NativeAppRunProcessor):
@@ -89,76 +77,22 @@ class NativeAppVersionDropProcessor(NativeAppManager, NativeAppCommandProcessor)
89
77
  def process(
90
78
  self,
91
79
  version: Optional[str],
92
- policy: PolicyBase,
93
- is_interactive: bool,
80
+ force: bool,
81
+ interactive: bool,
94
82
  *args,
95
83
  **kwargs,
96
84
  ):
97
- """
98
- Drops a version defined in an application package. If --force is provided, then no user prompts will be executed.
99
- """
100
-
101
- # 1. Check for existing an existing application package
102
- show_obj_row = self.get_existing_app_pkg_info()
103
- if not show_obj_row:
104
- raise ApplicationPackageDoesNotExistError(self.package_name)
105
-
106
- # 2. Check distribution of the existing application package
107
- actual_distribution = self.get_app_pkg_distribution_in_snowflake
108
- if not self.verify_project_distribution(actual_distribution):
109
- cc.warning(
110
- f"Continuing to execute `snow app version drop` on application package {self.package_name} with distribution '{actual_distribution}'."
111
- )
112
-
113
- # 3. If the user did not pass in a version string, determine from manifest.yml
114
- if not version:
115
- cc.message(
116
- dedent(
117
- f"""\
118
- Version was not provided through the Snowflake CLI. Checking version in the manifest.yml instead.
119
- This step will bundle your app artifacts to determine the location of the manifest.yml file.
120
- """
121
- )
122
- )
123
- self.build_bundle()
124
- version, _ = find_version_info_in_manifest_file(self.deploy_root)
125
- if not version:
126
- raise ClickException(
127
- "Manifest.yml file does not contain a value for the version field."
128
- )
129
-
130
- # Make the version a valid identifier, adding quotes if necessary
131
- version = to_identifier(version)
132
-
133
- cc.step(
134
- dedent(
135
- f"""\
136
- About to drop version {version} in application package {self.package_name}.
137
- """
138
- )
139
- )
140
-
141
- # If user did not provide --force, ask for confirmation
142
- user_prompt = (
143
- f"Are you sure you want to drop version {version} in application package {self.package_name}? Once dropped, this operation cannot be undone.",
144
- )
145
- if not policy.should_proceed(user_prompt):
146
- if is_interactive:
147
- cc.message("Not dropping version.")
148
- raise typer.Exit(0)
149
- else:
150
- cc.message("Cannot drop version non-interactively without --force.")
151
- raise typer.Exit(1)
152
-
153
- # Drop the version
154
- with self.use_role(self.package_role):
155
- try:
156
- self._execute_query(
157
- f"alter application package {self.package_name} drop version {version}"
158
- )
159
- except ProgrammingError as err:
160
- raise err # e.g. version is referenced in a release directive(s)
161
-
162
- cc.message(
163
- f"Version {version} in application package {self.package_name} dropped successfully."
85
+ return ApplicationPackageEntity.version_drop(
86
+ console=cc,
87
+ project_root=self.project_root,
88
+ deploy_root=self.deploy_root,
89
+ bundle_root=self.bundle_root,
90
+ generated_root=self.generated_root,
91
+ artifacts=self.artifacts,
92
+ package_name=self.package_name,
93
+ package_role=self.package_role,
94
+ package_distribution=self.package_distribution,
95
+ version=version,
96
+ force=force,
97
+ interactive=interactive,
164
98
  )
@@ -14,7 +14,6 @@
14
14
 
15
15
  from __future__ import annotations
16
16
 
17
- from textwrap import dedent
18
17
  from typing import Any, Dict, Optional, Tuple, Union
19
18
 
20
19
  from click import ClickException
@@ -28,6 +27,7 @@ from snowflake.cli.api.sql_execution import SqlExecutionMixin
28
27
  from snowflake.connector import ProgrammingError
29
28
  from snowflake.connector.cursor import SnowflakeCursor
30
29
  from snowflake.connector.errors import BadRequest
30
+ from snowflake.connector.vendored.requests.exceptions import HTTPError
31
31
 
32
32
 
33
33
  def _get_object_names(object_type: str) -> ObjectNames:
@@ -77,21 +77,42 @@ class ObjectManager(SqlExecutionMixin):
77
77
  def create(self, object_type: str, object_data: Dict[str, Any]) -> str:
78
78
  rest = RestApi(self._conn)
79
79
  url = rest.determine_url_for_create_query(object_type=object_type)
80
-
81
80
  try:
82
81
  response = rest.send_rest_request(url=url, method="post", data=object_data)
83
- # workaround as SnowflakeRestful class ignores some errors, dropping their info and returns {} instead.
84
- if not response:
82
+ except Exception as err:
83
+ _handle_create_error_codes(err)
84
+ return response["status"]
85
+
86
+
87
+ def _handle_create_error_codes(err: Exception) -> None:
88
+ # according to https://docs.snowflake.com/developer-guide/snowflake-rest-api/reference/
89
+ if isinstance(err, BadRequest):
90
+ raise ClickException(
91
+ "400 bad request: Incorrect object definition (arguments misspelled or malformatted)."
92
+ )
93
+ if isinstance(err, HTTPError):
94
+ match err_code := err.response.status_code:
95
+ case 401:
85
96
  raise ClickException(
86
- dedent(
87
- """ An unexpected error occurred while creating the object. Try again with --debug for more info.
88
- Most probable reasons:
89
- * object you are trying to create already exists
90
- * role you are using do not have permissions to create this object"""
91
- )
97
+ "401 unauthorized: role you are using does not have permissions to create this object."
92
98
  )
93
- return response["status"]
94
- except BadRequest:
95
- raise ClickException(
96
- "Incorrect object definition (arguments misspelled or malformatted)."
97
- )
99
+ # error 403 should be handled by connector
100
+ # error 404 is handled by determine-url logic
101
+ # error 405 should not happen under assumption that "all listable objects can be created"
102
+ case 408:
103
+ raise ClickException(
104
+ "408 timeout: the request timed out and was not completed by the server."
105
+ )
106
+ case 409:
107
+ raise ClickException(
108
+ "409 conflict: object you're trying to create already exists."
109
+ )
110
+ # error 410 is a network maintenance debugging - should not happen to the user
111
+ case 429:
112
+ raise ClickException(
113
+ "429 too many requests. The number of requests hit the rate limit."
114
+ )
115
+ case 500 | 503 | 504:
116
+ raise ClickException(f"{err_code} internal server error.")
117
+ case _:
118
+ raise err
@@ -81,6 +81,18 @@ add_object_command_aliases(
81
81
  )
82
82
 
83
83
 
84
+ @app.command(requires_connection=True)
85
+ def execute(
86
+ name: FQN = StreamlitNameArgument,
87
+ **options,
88
+ ):
89
+ """
90
+ Executes a streamlit in a headless mode.
91
+ """
92
+ _ = StreamlitManager().execute(app_name=name)
93
+ return MessageResult(f"Streamlit {name} executed.")
94
+
95
+
84
96
  @app.command("share", requires_connection=True)
85
97
  def streamlit_share(
86
98
  name: FQN = StreamlitNameArgument,
@@ -43,6 +43,10 @@ log = logging.getLogger(__name__)
43
43
 
44
44
 
45
45
  class StreamlitManager(SqlExecutionMixin):
46
+ def execute(self, app_name: FQN):
47
+ query = f"EXECUTE STREAMLIT {app_name.sql_identifier}()"
48
+ return self._execute_query(query=query)
49
+
46
50
  def share(self, streamlit_name: FQN, to_role: str) -> SnowflakeCursor:
47
51
  return self._execute_query(
48
52
  f"grant usage on streamlit {streamlit_name.sql_identifier} to role {to_role}"