snowflake-cli-labs 3.0.0rc2__py3-none-any.whl → 3.0.0rc4__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 (88) hide show
  1. snowflake/cli/__about__.py +1 -1
  2. snowflake/cli/_app/commands_registration/builtin_plugins.py +2 -0
  3. snowflake/cli/_app/secret.py +9 -0
  4. snowflake/cli/_app/snow_connector.py +39 -27
  5. snowflake/cli/_app/telemetry.py +28 -0
  6. snowflake/cli/_plugins/connection/commands.py +9 -4
  7. snowflake/cli/_plugins/git/manager.py +53 -7
  8. snowflake/cli/_plugins/helpers/commands.py +61 -0
  9. snowflake/cli/{api/project/schemas/snowpark/__init__.py → _plugins/helpers/plugin_spec.py} +17 -0
  10. snowflake/cli/_plugins/nativeapp/artifacts.py +10 -9
  11. snowflake/cli/_plugins/nativeapp/bundle_context.py +1 -1
  12. snowflake/cli/_plugins/nativeapp/codegen/artifact_processor.py +1 -1
  13. snowflake/cli/_plugins/nativeapp/codegen/compiler.py +6 -1
  14. snowflake/cli/_plugins/nativeapp/codegen/setup/native_app_setup_processor.py +1 -1
  15. snowflake/cli/_plugins/nativeapp/codegen/snowpark/extension_function_utils.py +1 -1
  16. snowflake/cli/_plugins/nativeapp/codegen/snowpark/models.py +1 -1
  17. snowflake/cli/_plugins/nativeapp/codegen/snowpark/python_processor.py +5 -1
  18. snowflake/cli/_plugins/nativeapp/codegen/templates/templates_processor.py +4 -1
  19. snowflake/cli/_plugins/nativeapp/commands.py +87 -96
  20. snowflake/cli/_plugins/nativeapp/entities/__init__.py +0 -0
  21. snowflake/cli/{api/entities/application_entity.py → _plugins/nativeapp/entities/application.py} +264 -83
  22. snowflake/cli/_plugins/nativeapp/entities/application_package.py +1392 -0
  23. snowflake/cli/_plugins/nativeapp/exceptions.py +0 -9
  24. snowflake/cli/_plugins/nativeapp/manager.py +69 -185
  25. snowflake/cli/_plugins/nativeapp/policy.py +3 -0
  26. snowflake/cli/_plugins/nativeapp/project_model.py +2 -2
  27. snowflake/cli/_plugins/nativeapp/run_processor.py +17 -20
  28. snowflake/cli/_plugins/nativeapp/same_account_install_method.py +0 -4
  29. snowflake/cli/_plugins/nativeapp/teardown_processor.py +4 -6
  30. snowflake/cli/_plugins/nativeapp/v2_conversions/v2_to_v1_decorator.py +122 -88
  31. snowflake/cli/_plugins/nativeapp/version/commands.py +7 -39
  32. snowflake/cli/_plugins/nativeapp/version/version_processor.py +46 -312
  33. snowflake/cli/_plugins/object/manager.py +36 -15
  34. snowflake/cli/_plugins/snowpark/commands.py +4 -4
  35. snowflake/cli/_plugins/snowpark/common.py +4 -4
  36. snowflake/cli/{api/entities → _plugins/snowpark}/snowpark_entity.py +2 -2
  37. snowflake/cli/{api/project/schemas/entities/snowpark_entity.py → _plugins/snowpark/snowpark_entity_model.py} +3 -6
  38. snowflake/cli/_plugins/snowpark/snowpark_project_paths.py +1 -1
  39. snowflake/cli/_plugins/stage/manager.py +9 -4
  40. snowflake/cli/_plugins/streamlit/commands.py +15 -3
  41. snowflake/cli/_plugins/streamlit/manager.py +12 -4
  42. snowflake/cli/{api/entities → _plugins/streamlit}/streamlit_entity.py +2 -2
  43. snowflake/cli/{api/project/schemas/entities → _plugins/streamlit}/streamlit_entity_model.py +5 -12
  44. snowflake/cli/_plugins/workspace/commands.py +116 -36
  45. snowflake/cli/_plugins/workspace/plugin_spec.py +1 -1
  46. snowflake/cli/api/cli_global_context.py +7 -0
  47. snowflake/cli/api/commands/decorators.py +14 -0
  48. snowflake/cli/api/commands/flags.py +18 -0
  49. snowflake/cli/api/commands/snow_typer.py +1 -1
  50. snowflake/cli/api/config.py +25 -6
  51. snowflake/cli/api/connections.py +3 -1
  52. snowflake/cli/api/entities/common.py +4 -0
  53. snowflake/cli/api/entities/utils.py +3 -14
  54. snowflake/cli/api/errno.py +1 -0
  55. snowflake/cli/api/identifiers.py +4 -3
  56. snowflake/cli/api/metrics.py +92 -0
  57. snowflake/cli/api/project/definition_conversion.py +61 -18
  58. snowflake/cli/api/project/schemas/entities/common.py +17 -4
  59. snowflake/cli/api/project/schemas/entities/entities.py +11 -10
  60. snowflake/cli/api/project/schemas/project_definition.py +5 -7
  61. snowflake/cli/api/project/schemas/v1/__init__.py +0 -0
  62. snowflake/cli/api/project/schemas/{identifier_model.py → v1/identifier_model.py} +0 -7
  63. snowflake/cli/api/project/schemas/v1/native_app/__init__.py +0 -0
  64. snowflake/cli/api/project/schemas/{native_app → v1/native_app}/native_app.py +4 -4
  65. snowflake/cli/api/project/schemas/v1/snowpark/__init__.py +0 -0
  66. snowflake/cli/api/project/schemas/{snowpark → v1/snowpark}/callable.py +2 -2
  67. snowflake/cli/api/project/schemas/{snowpark → v1/snowpark}/snowpark.py +2 -2
  68. snowflake/cli/api/project/schemas/v1/streamlit/__init__.py +0 -0
  69. snowflake/cli/api/project/schemas/{streamlit → v1/streamlit}/streamlit.py +2 -1
  70. snowflake/cli/api/rendering/sql_templates.py +6 -0
  71. snowflake/cli/api/rest_api.py +11 -5
  72. snowflake/cli/api/sql_execution.py +6 -15
  73. snowflake/cli/api/utils/definition_rendering.py +24 -4
  74. {snowflake_cli_labs-3.0.0rc2.dist-info → snowflake_cli_labs-3.0.0rc4.dist-info}/METADATA +9 -7
  75. {snowflake_cli_labs-3.0.0rc2.dist-info → snowflake_cli_labs-3.0.0rc4.dist-info}/RECORD +83 -79
  76. snowflake/cli/_plugins/nativeapp/init.py +0 -345
  77. snowflake/cli/api/entities/application_package_entity.py +0 -658
  78. snowflake/cli/api/project/schemas/entities/application_entity_model.py +0 -56
  79. snowflake/cli/api/project/schemas/entities/application_package_entity_model.py +0 -94
  80. snowflake/cli/api/project/schemas/streamlit/__init__.py +0 -13
  81. /snowflake/cli/{api/project/schemas/native_app → _plugins/helpers}/__init__.py +0 -0
  82. /snowflake/cli/api/project/schemas/{native_app → v1/native_app}/application.py +0 -0
  83. /snowflake/cli/api/project/schemas/{native_app → v1/native_app}/package.py +0 -0
  84. /snowflake/cli/api/project/schemas/{native_app → v1/native_app}/path_mapping.py +0 -0
  85. /snowflake/cli/api/project/schemas/{snowpark → v1/snowpark}/argument.py +0 -0
  86. {snowflake_cli_labs-3.0.0rc2.dist-info → snowflake_cli_labs-3.0.0rc4.dist-info}/WHEEL +0 -0
  87. {snowflake_cli_labs-3.0.0rc2.dist-info → snowflake_cli_labs-3.0.0rc4.dist-info}/entry_points.txt +0 -0
  88. {snowflake_cli_labs-3.0.0rc2.dist-info → snowflake_cli_labs-3.0.0rc4.dist-info}/licenses/LICENSE +0 -0
@@ -1,10 +1,16 @@
1
+ from __future__ import annotations
2
+
3
+ import time
1
4
  from contextlib import contextmanager
5
+ from datetime import datetime
2
6
  from pathlib import Path
3
7
  from textwrap import dedent
4
- from typing import Callable, List, Optional, TypedDict
8
+ from typing import Callable, Generator, List, Literal, Optional, TypedDict
5
9
 
6
10
  import typer
7
11
  from click import ClickException, UsageError
12
+ from pydantic import Field, field_validator
13
+ from snowflake.cli._plugins.connection.util import make_snowsight_url
8
14
  from snowflake.cli._plugins.nativeapp.common_flags import (
9
15
  ForceOption,
10
16
  InteractiveOption,
@@ -15,12 +21,15 @@ from snowflake.cli._plugins.nativeapp.constants import (
15
21
  COMMENT_COL,
16
22
  NAME_COL,
17
23
  OWNER_COL,
18
- PATCH_COL,
19
24
  SPECIAL_COMMENT,
20
- VERSION_COL,
25
+ )
26
+ from snowflake.cli._plugins.nativeapp.entities.application_package import (
27
+ ApplicationPackageEntity,
28
+ ApplicationPackageEntityModel,
21
29
  )
22
30
  from snowflake.cli._plugins.nativeapp.exceptions import (
23
31
  ApplicationPackageDoesNotExistError,
32
+ NoEventTableForAccount,
24
33
  )
25
34
  from snowflake.cli._plugins.nativeapp.policy import (
26
35
  AllowAlwaysPolicy,
@@ -31,18 +40,13 @@ from snowflake.cli._plugins.nativeapp.policy import (
31
40
  from snowflake.cli._plugins.nativeapp.same_account_install_method import (
32
41
  SameAccountInstallMethod,
33
42
  )
34
- from snowflake.cli._plugins.nativeapp.utils import (
35
- needs_confirmation,
36
- )
43
+ from snowflake.cli._plugins.nativeapp.utils import needs_confirmation
37
44
  from snowflake.cli._plugins.workspace.action_context import ActionContext
45
+ from snowflake.cli.api.cli_global_context import get_cli_context
38
46
  from snowflake.cli.api.console.abc import AbstractConsole
39
- from snowflake.cli.api.entities.application_package_entity import (
40
- ApplicationPackageEntity,
41
- )
42
47
  from snowflake.cli.api.entities.common import EntityBase, get_sql_executor
43
48
  from snowflake.cli.api.entities.utils import (
44
49
  drop_generic_object,
45
- ensure_correct_owner,
46
50
  execute_post_deploy_hooks,
47
51
  generic_sql_error_handler,
48
52
  print_messages,
@@ -54,22 +58,21 @@ from snowflake.cli.api.errno import (
54
58
  NOT_SUPPORTED_ON_DEV_MODE_APPLICATIONS,
55
59
  ONLY_SUPPORTED_ON_DEV_MODE_APPLICATIONS,
56
60
  )
57
- from snowflake.cli.api.exceptions import SnowflakeSQLExecutionError
58
- from snowflake.cli.api.project.schemas.entities.application_entity_model import (
59
- ApplicationEntityModel,
61
+ from snowflake.cli.api.metrics import CLICounterField
62
+ from snowflake.cli.api.project.schemas.entities.common import (
63
+ EntityModelBase,
64
+ Identifier,
65
+ PostDeployHook,
66
+ TargetField,
60
67
  )
61
- from snowflake.cli.api.project.schemas.entities.application_package_entity_model import (
62
- ApplicationPackageEntityModel,
63
- )
64
- from snowflake.cli.api.project.schemas.entities.common import PostDeployHook
68
+ from snowflake.cli.api.project.schemas.updatable_model import DiscriminatorField
65
69
  from snowflake.cli.api.project.util import (
70
+ append_test_resource_suffix,
66
71
  extract_schema,
67
- identifier_to_show_like_pattern,
72
+ identifier_for_url,
68
73
  unquote_identifier,
69
74
  )
70
- from snowflake.cli.api.utils.cursor import find_all_rows
71
- from snowflake.connector import ProgrammingError
72
- from snowflake.connector.cursor import DictCursor
75
+ from snowflake.connector import DictCursor, ProgrammingError
73
76
 
74
77
  # Reasons why an `alter application ... upgrade` might fail
75
78
  UPGRADE_RESTRICTION_CODES = {
@@ -83,6 +86,31 @@ UPGRADE_RESTRICTION_CODES = {
83
86
  ApplicationOwnedObject = TypedDict("ApplicationOwnedObject", {"name": str, "type": str})
84
87
 
85
88
 
89
+ class ApplicationEntityModel(EntityModelBase):
90
+ type: Literal["application"] = DiscriminatorField() # noqa A003
91
+ from_: TargetField[ApplicationPackageEntityModel] = Field(
92
+ alias="from",
93
+ title="An application package this entity should be created from",
94
+ )
95
+ debug: Optional[bool] = Field(
96
+ title="Whether to enable debug mode when using a named stage to create an application object",
97
+ default=None,
98
+ )
99
+
100
+ @field_validator("identifier")
101
+ @classmethod
102
+ def append_test_resource_suffix_to_identifier(
103
+ cls, input_value: Identifier | str
104
+ ) -> Identifier | str:
105
+ identifier = (
106
+ input_value.name if isinstance(input_value, Identifier) else input_value
107
+ )
108
+ with_suffix = append_test_resource_suffix(identifier)
109
+ if isinstance(input_value, Identifier):
110
+ return input_value.model_copy(update=dict(name=with_suffix))
111
+ return with_suffix
112
+
113
+
86
114
  class ApplicationEntity(EntityBase[ApplicationEntityModel]):
87
115
  """
88
116
  A Native App application object, created from an application package.
@@ -108,9 +136,9 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
108
136
  app_name = model.fqn.identifier
109
137
  debug_mode = model.debug
110
138
  if model.meta:
111
- app_role = getattr(model.meta, "role", ctx.default_role)
112
- app_warehouse = getattr(model.meta, "warehouse", ctx.default_warehouse)
113
- post_deploy_hooks = getattr(model.meta, "post_deploy", None)
139
+ app_role = model.meta.role or ctx.default_role
140
+ app_warehouse = model.meta.warehouse or ctx.default_warehouse
141
+ post_deploy_hooks = model.meta.post_deploy
114
142
  else:
115
143
  app_role = ctx.default_role
116
144
  app_warehouse = ctx.default_warehouse
@@ -147,6 +175,8 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
147
175
  paths=[],
148
176
  validate=validate,
149
177
  stage_fqn=stage_fqn,
178
+ interactive=interactive,
179
+ force=force,
150
180
  )
151
181
 
152
182
  self.deploy(
@@ -221,10 +251,7 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
221
251
  )
222
252
  return
223
253
 
224
- # 2. Check for the right owner
225
- ensure_correct_owner(row=show_obj_row, role=app_role, obj_name=app_name)
226
-
227
- # 3. Check if created by the Snowflake CLI
254
+ # 2. Check if created by the Snowflake CLI
228
255
  row_comment = show_obj_row[COMMENT_COL]
229
256
  if row_comment not in ALLOWED_SPECIAL_COMMENTS and needs_confirmation(
230
257
  needs_confirm, auto_yes
@@ -251,7 +278,7 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
251
278
  # leave behind an orphan app when we get to dropping the package
252
279
  raise typer.Abort()
253
280
 
254
- # 4. Check for application objects owned by the application
281
+ # 3. Check for application objects owned by the application
255
282
  # This query will fail if the application package has already been dropped, so handle this case gracefully
256
283
  has_objects_to_drop = False
257
284
  message_prefix = ""
@@ -329,7 +356,7 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
329
356
  # If there's nothing to drop, set cascade to an explicit False value
330
357
  cascade = False
331
358
 
332
- # 5. All validations have passed, drop object
359
+ # 4. All validations have passed, drop object
333
360
  drop_generic_object(
334
361
  console=console,
335
362
  object_type="application",
@@ -425,7 +452,7 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
425
452
  # versioned dev
426
453
  if version:
427
454
  try:
428
- version_exists = cls.get_existing_version_info(
455
+ version_exists = ApplicationPackageEntity.get_existing_version_info(
429
456
  version=version,
430
457
  package_name=package_name,
431
458
  package_role=package_role,
@@ -537,14 +564,13 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
537
564
  )
538
565
 
539
566
  # hooks always executed after a create or upgrade
540
- if post_deploy_hooks:
541
- cls.execute_post_deploy_hooks(
542
- console=console,
543
- project_root=project_root,
544
- post_deploy_hooks=post_deploy_hooks,
545
- app_name=app_name,
546
- app_warehouse=app_warehouse,
547
- )
567
+ cls.execute_post_deploy_hooks(
568
+ console=console,
569
+ project_root=project_root,
570
+ post_deploy_hooks=post_deploy_hooks,
571
+ app_name=app_name,
572
+ app_warehouse=app_warehouse,
573
+ )
548
574
  return
549
575
 
550
576
  except ProgrammingError as err:
@@ -596,14 +622,13 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
596
622
  print_messages(console, create_cursor)
597
623
 
598
624
  # hooks always executed after a create or upgrade
599
- if post_deploy_hooks:
600
- cls.execute_post_deploy_hooks(
601
- console=console,
602
- project_root=project_root,
603
- post_deploy_hooks=post_deploy_hooks,
604
- app_name=app_name,
605
- app_warehouse=app_warehouse,
606
- )
625
+ cls.execute_post_deploy_hooks(
626
+ console=console,
627
+ project_root=project_root,
628
+ post_deploy_hooks=post_deploy_hooks,
629
+ app_name=app_name,
630
+ app_warehouse=app_warehouse,
631
+ )
607
632
 
608
633
  except ProgrammingError as err:
609
634
  generic_sql_error_handler(err)
@@ -617,14 +642,19 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
617
642
  app_name: str,
618
643
  app_warehouse: Optional[str],
619
644
  ):
620
- with cls.use_application_warehouse(app_warehouse):
621
- execute_post_deploy_hooks(
622
- console=console,
623
- project_root=project_root,
624
- post_deploy_hooks=post_deploy_hooks,
625
- deployed_object_type="application",
626
- database_name=app_name,
627
- )
645
+ get_cli_context().metrics.set_counter_default(
646
+ CLICounterField.POST_DEPLOY_SCRIPTS, 0
647
+ )
648
+
649
+ if post_deploy_hooks:
650
+ with cls.use_application_warehouse(app_warehouse):
651
+ execute_post_deploy_hooks(
652
+ console=console,
653
+ project_root=project_root,
654
+ post_deploy_hooks=post_deploy_hooks,
655
+ deployed_object_type="application",
656
+ database_name=app_name,
657
+ )
628
658
 
629
659
  @staticmethod
630
660
  @contextmanager
@@ -659,39 +689,190 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
659
689
  "applications", app_name, name_col=NAME_COL
660
690
  )
661
691
 
662
- @staticmethod
663
- def get_existing_version_info(
664
- version: str,
692
+ @classmethod
693
+ def get_events(
694
+ cls,
695
+ app_name: str,
665
696
  package_name: str,
666
- package_role: str,
667
- ) -> Optional[dict]:
668
- """
669
- Get the latest patch on an existing version by name in the application package.
670
- Executes 'show versions like ... in application package' query and returns
671
- the latest patch in the version as a single row, if one exists. Otherwise,
672
- returns None.
673
- """
697
+ since: str | datetime | None = None,
698
+ until: str | datetime | None = None,
699
+ record_types: list[str] | None = None,
700
+ scopes: list[str] | None = None,
701
+ consumer_org: str = "",
702
+ consumer_account: str = "",
703
+ consumer_app_hash: str = "",
704
+ first: int = -1,
705
+ last: int = -1,
706
+ ):
707
+
708
+ record_types = record_types or []
709
+ scopes = scopes or []
710
+
711
+ if first >= 0 and last >= 0:
712
+ raise ValueError("first and last cannot be used together")
713
+
714
+ account_event_table = cls.get_account_event_table()
715
+ if not account_event_table:
716
+ raise NoEventTableForAccount()
717
+
718
+ # resource_attributes uses the unquoted/uppercase app and package name
719
+ app_name = unquote_identifier(app_name)
720
+ package_name = unquote_identifier(package_name)
721
+ org_name = unquote_identifier(consumer_org)
722
+ account_name = unquote_identifier(consumer_account)
723
+
724
+ # Filter on record attributes
725
+ if consumer_org and consumer_account:
726
+ # Look for events shared from a consumer account
727
+ app_clause = (
728
+ f"resource_attributes:\"snow.application.package.name\" = '{package_name}' "
729
+ f"and resource_attributes:\"snow.application.consumer.organization\" = '{org_name}' "
730
+ f"and resource_attributes:\"snow.application.consumer.name\" = '{account_name}'"
731
+ )
732
+ if consumer_app_hash:
733
+ # If the user has specified a hash of a specific app installation
734
+ # in the consumer account, filter events to that installation only
735
+ app_clause += f" and resource_attributes:\"snow.database.hash\" = '{consumer_app_hash.lower()}'"
736
+ else:
737
+ # Otherwise look for events from an app installed in the same account as the package
738
+ app_clause = f"resource_attributes:\"snow.database.name\" = '{app_name}'"
739
+
740
+ # Filter on event time
741
+ if isinstance(since, datetime):
742
+ since_clause = f"and timestamp >= '{since}'"
743
+ elif isinstance(since, str) and since:
744
+ since_clause = f"and timestamp >= sysdate() - interval '{since}'"
745
+ else:
746
+ since_clause = ""
747
+ if isinstance(until, datetime):
748
+ until_clause = f"and timestamp <= '{until}'"
749
+ elif isinstance(until, str) and until:
750
+ until_clause = f"and timestamp <= sysdate() - interval '{until}'"
751
+ else:
752
+ until_clause = ""
753
+
754
+ # Filter on event type (log, span, span_event)
755
+ type_in_values = ",".join(f"'{v}'" for v in record_types)
756
+ types_clause = (
757
+ f"and record_type in ({type_in_values})" if type_in_values else ""
758
+ )
759
+
760
+ # Filter on event scope (e.g. the logger name)
761
+ scope_in_values = ",".join(f"'{v}'" for v in scopes)
762
+ scopes_clause = (
763
+ f"and scope:name in ({scope_in_values})" if scope_in_values else ""
764
+ )
765
+
766
+ # Limit event count
767
+ first_clause = f"limit {first}" if first >= 0 else ""
768
+ last_clause = f"limit {last}" if last >= 0 else ""
769
+
770
+ query = dedent(
771
+ f"""\
772
+ select * from (
773
+ select timestamp, value::varchar value
774
+ from {account_event_table}
775
+ where ({app_clause})
776
+ {since_clause}
777
+ {until_clause}
778
+ {types_clause}
779
+ {scopes_clause}
780
+ order by timestamp desc
781
+ {last_clause}
782
+ ) order by timestamp asc
783
+ {first_clause}
784
+ """
785
+ )
674
786
  sql_executor = get_sql_executor()
675
- with sql_executor.use_role(package_role):
676
- try:
677
- query = f"show versions like {identifier_to_show_like_pattern(version)} in application package {package_name}"
678
- cursor = sql_executor.execute_query(query, cursor_class=DictCursor)
787
+ try:
788
+ return sql_executor.execute_query(query, cursor_class=DictCursor).fetchall()
789
+ except ProgrammingError as err:
790
+ generic_sql_error_handler(err)
679
791
 
680
- if cursor.rowcount is None:
681
- raise SnowflakeSQLExecutionError(query)
792
+ @classmethod
793
+ def stream_events(
794
+ cls,
795
+ app_name: str,
796
+ package_name: str,
797
+ interval_seconds: int,
798
+ since: str | datetime | None = None,
799
+ record_types: list[str] | None = None,
800
+ scopes: list[str] | None = None,
801
+ consumer_org: str = "",
802
+ consumer_account: str = "",
803
+ consumer_app_hash: str = "",
804
+ last: int = -1,
805
+ ) -> Generator[dict, None, None]:
806
+ try:
807
+ events = cls.get_events(
808
+ app_name=app_name,
809
+ package_name=package_name,
810
+ since=since,
811
+ record_types=record_types,
812
+ scopes=scopes,
813
+ consumer_org=consumer_org,
814
+ consumer_account=consumer_account,
815
+ consumer_app_hash=consumer_app_hash,
816
+ last=last,
817
+ )
818
+ yield from events # Yield the initial batch of events
819
+ last_event_time = events[-1]["TIMESTAMP"] if events else None
682
820
 
683
- matching_rows = find_all_rows(
684
- cursor, lambda row: row[VERSION_COL] == unquote_identifier(version)
821
+ while True: # Then infinite poll for new events
822
+ time.sleep(interval_seconds)
823
+ previous_events = events
824
+ events = cls.get_events(
825
+ app_name=app_name,
826
+ package_name=package_name,
827
+ since=last_event_time,
828
+ record_types=record_types,
829
+ scopes=scopes,
830
+ consumer_org=consumer_org,
831
+ consumer_account=consumer_account,
832
+ consumer_app_hash=consumer_app_hash,
685
833
  )
834
+ if not events:
835
+ continue
686
836
 
687
- if not matching_rows:
688
- return None
837
+ yield from _new_events_only(previous_events, events)
838
+ last_event_time = events[-1]["TIMESTAMP"]
839
+ except KeyboardInterrupt:
840
+ return
689
841
 
690
- return max(matching_rows, key=lambda row: row[PATCH_COL])
842
+ @staticmethod
843
+ def get_account_event_table():
844
+ query = "show parameters like 'event_table' in account"
845
+ sql_executor = get_sql_executor()
846
+ results = sql_executor.execute_query(query, cursor_class=DictCursor)
847
+ return next((r["value"] for r in results if r["key"] == "EVENT_TABLE"), "")
691
848
 
692
- except ProgrammingError as err:
693
- if err.msg.__contains__("does not exist or not authorized"):
694
- raise ApplicationPackageDoesNotExistError(package_name)
695
- else:
696
- generic_sql_error_handler(err=err, role=package_role)
697
- return None
849
+ @classmethod
850
+ def get_snowsight_url(cls, app_name: str, app_warehouse: str | None) -> str:
851
+ """Returns the URL that can be used to visit this app via Snowsight."""
852
+ name = identifier_for_url(app_name)
853
+ with cls.use_application_warehouse(app_warehouse):
854
+ sql_executor = get_sql_executor()
855
+ return make_snowsight_url(
856
+ sql_executor._conn, f"/#/apps/application/{name}" # noqa: SLF001
857
+ )
858
+
859
+
860
+ def _new_events_only(previous_events: list[dict], new_events: list[dict]) -> list[dict]:
861
+ # The timestamp that overlaps between both sets of events
862
+ overlap_time = new_events[0]["TIMESTAMP"]
863
+
864
+ # Remove all the events from the new result set
865
+ # if they were already printed. We iterate and remove
866
+ # instead of filtering in order to handle duplicates
867
+ # (i.e. if an event is present 3 times in new_events
868
+ # but only once in previous_events, it should still
869
+ # appear twice in new_events at the end
870
+ new_events = new_events.copy()
871
+ for event in reversed(previous_events):
872
+ if event["TIMESTAMP"] < overlap_time:
873
+ break
874
+ # No need to handle ValueError here since we know
875
+ # that events that pass the above if check will
876
+ # either be in both lists or in new_events only
877
+ new_events.remove(event)
878
+ return new_events