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.
- snowflake/cli/__about__.py +1 -1
- snowflake/cli/_app/telemetry.py +28 -0
- snowflake/cli/_plugins/connection/commands.py +9 -4
- snowflake/cli/_plugins/helpers/commands.py +34 -1
- snowflake/cli/_plugins/nativeapp/codegen/compiler.py +5 -0
- snowflake/cli/_plugins/nativeapp/codegen/snowpark/python_processor.py +4 -0
- snowflake/cli/_plugins/nativeapp/codegen/templates/templates_processor.py +3 -0
- snowflake/cli/_plugins/nativeapp/commands.py +9 -86
- snowflake/cli/_plugins/nativeapp/entities/__init__.py +0 -0
- snowflake/cli/_plugins/nativeapp/{application_entity.py → entities/application.py} +266 -39
- snowflake/cli/_plugins/nativeapp/{application_package_entity.py → entities/application_package.py} +357 -72
- snowflake/cli/_plugins/nativeapp/manager.py +62 -183
- snowflake/cli/_plugins/nativeapp/run_processor.py +6 -6
- snowflake/cli/_plugins/nativeapp/teardown_processor.py +2 -4
- snowflake/cli/_plugins/nativeapp/v2_conversions/v2_to_v1_decorator.py +2 -4
- snowflake/cli/_plugins/nativeapp/version/commands.py +1 -15
- snowflake/cli/_plugins/nativeapp/version/version_processor.py +16 -82
- snowflake/cli/_plugins/object/manager.py +36 -15
- snowflake/cli/_plugins/streamlit/commands.py +12 -0
- snowflake/cli/_plugins/streamlit/manager.py +4 -0
- snowflake/cli/_plugins/workspace/commands.py +33 -0
- snowflake/cli/api/cli_global_context.py +7 -0
- snowflake/cli/api/commands/decorators.py +14 -0
- snowflake/cli/api/commands/flags.py +18 -0
- snowflake/cli/api/config.py +25 -6
- snowflake/cli/api/connections.py +3 -1
- snowflake/cli/api/entities/common.py +1 -0
- snowflake/cli/api/entities/utils.py +3 -0
- snowflake/cli/api/metrics.py +92 -0
- snowflake/cli/api/project/definition_conversion.py +69 -22
- snowflake/cli/api/project/definition_manager.py +5 -5
- snowflake/cli/api/project/schemas/entities/entities.py +3 -5
- snowflake/cli/api/project/schemas/project_definition.py +1 -3
- snowflake/cli/api/rendering/sql_templates.py +6 -0
- snowflake/cli/api/rest_api.py +11 -5
- snowflake/cli/api/utils/definition_rendering.py +24 -4
- {snowflake_cli_labs-3.0.0rc3.dist-info → snowflake_cli_labs-3.0.0rc5.dist-info}/METADATA +4 -2
- {snowflake_cli_labs-3.0.0rc3.dist-info → snowflake_cli_labs-3.0.0rc5.dist-info}/RECORD +41 -42
- snowflake/cli/_plugins/nativeapp/application_entity_model.py +0 -56
- snowflake/cli/_plugins/nativeapp/application_package_entity_model.py +0 -94
- snowflake/cli/_plugins/nativeapp/init.py +0 -345
- {snowflake_cli_labs-3.0.0rc3.dist-info → snowflake_cli_labs-3.0.0rc5.dist-info}/WHEEL +0 -0
- {snowflake_cli_labs-3.0.0rc3.dist-info → snowflake_cli_labs-3.0.0rc5.dist-info}/entry_points.txt +0 -0
- {snowflake_cli_labs-3.0.0rc3.dist-info → snowflake_cli_labs-3.0.0rc5.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,19 +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
|
|
8
|
-
from
|
|
9
|
-
|
|
10
|
-
)
|
|
11
|
-
from snowflake.cli._plugins.nativeapp.application_package_entity import (
|
|
12
|
-
ApplicationPackageEntity,
|
|
13
|
-
)
|
|
14
|
-
from snowflake.cli._plugins.nativeapp.application_package_entity_model import (
|
|
15
|
-
ApplicationPackageEntityModel,
|
|
16
|
-
)
|
|
12
|
+
from pydantic import Field, field_validator
|
|
13
|
+
from snowflake.cli._plugins.connection.util import make_snowsight_url
|
|
17
14
|
from snowflake.cli._plugins.nativeapp.common_flags import (
|
|
18
15
|
ForceOption,
|
|
19
16
|
InteractiveOption,
|
|
@@ -26,8 +23,13 @@ from snowflake.cli._plugins.nativeapp.constants import (
|
|
|
26
23
|
OWNER_COL,
|
|
27
24
|
SPECIAL_COMMENT,
|
|
28
25
|
)
|
|
26
|
+
from snowflake.cli._plugins.nativeapp.entities.application_package import (
|
|
27
|
+
ApplicationPackageEntity,
|
|
28
|
+
ApplicationPackageEntityModel,
|
|
29
|
+
)
|
|
29
30
|
from snowflake.cli._plugins.nativeapp.exceptions import (
|
|
30
31
|
ApplicationPackageDoesNotExistError,
|
|
32
|
+
NoEventTableForAccount,
|
|
31
33
|
)
|
|
32
34
|
from snowflake.cli._plugins.nativeapp.policy import (
|
|
33
35
|
AllowAlwaysPolicy,
|
|
@@ -38,10 +40,9 @@ from snowflake.cli._plugins.nativeapp.policy import (
|
|
|
38
40
|
from snowflake.cli._plugins.nativeapp.same_account_install_method import (
|
|
39
41
|
SameAccountInstallMethod,
|
|
40
42
|
)
|
|
41
|
-
from snowflake.cli._plugins.nativeapp.utils import
|
|
42
|
-
needs_confirmation,
|
|
43
|
-
)
|
|
43
|
+
from snowflake.cli._plugins.nativeapp.utils import needs_confirmation
|
|
44
44
|
from snowflake.cli._plugins.workspace.action_context import ActionContext
|
|
45
|
+
from snowflake.cli.api.cli_global_context import get_cli_context
|
|
45
46
|
from snowflake.cli.api.console.abc import AbstractConsole
|
|
46
47
|
from snowflake.cli.api.entities.common import EntityBase, get_sql_executor
|
|
47
48
|
from snowflake.cli.api.entities.utils import (
|
|
@@ -57,11 +58,21 @@ from snowflake.cli.api.errno import (
|
|
|
57
58
|
NOT_SUPPORTED_ON_DEV_MODE_APPLICATIONS,
|
|
58
59
|
ONLY_SUPPORTED_ON_DEV_MODE_APPLICATIONS,
|
|
59
60
|
)
|
|
60
|
-
from snowflake.cli.api.
|
|
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,
|
|
67
|
+
)
|
|
68
|
+
from snowflake.cli.api.project.schemas.updatable_model import DiscriminatorField
|
|
61
69
|
from snowflake.cli.api.project.util import (
|
|
70
|
+
append_test_resource_suffix,
|
|
62
71
|
extract_schema,
|
|
72
|
+
identifier_for_url,
|
|
73
|
+
unquote_identifier,
|
|
63
74
|
)
|
|
64
|
-
from snowflake.connector import ProgrammingError
|
|
75
|
+
from snowflake.connector import DictCursor, ProgrammingError
|
|
65
76
|
|
|
66
77
|
# Reasons why an `alter application ... upgrade` might fail
|
|
67
78
|
UPGRADE_RESTRICTION_CODES = {
|
|
@@ -75,6 +86,31 @@ UPGRADE_RESTRICTION_CODES = {
|
|
|
75
86
|
ApplicationOwnedObject = TypedDict("ApplicationOwnedObject", {"name": str, "type": str})
|
|
76
87
|
|
|
77
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
|
+
|
|
78
114
|
class ApplicationEntity(EntityBase[ApplicationEntityModel]):
|
|
79
115
|
"""
|
|
80
116
|
A Native App application object, created from an application package.
|
|
@@ -528,14 +564,13 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
|
|
|
528
564
|
)
|
|
529
565
|
|
|
530
566
|
# hooks always executed after a create or upgrade
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
)
|
|
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
|
+
)
|
|
539
574
|
return
|
|
540
575
|
|
|
541
576
|
except ProgrammingError as err:
|
|
@@ -587,14 +622,13 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
|
|
|
587
622
|
print_messages(console, create_cursor)
|
|
588
623
|
|
|
589
624
|
# hooks always executed after a create or upgrade
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
)
|
|
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
|
+
)
|
|
598
632
|
|
|
599
633
|
except ProgrammingError as err:
|
|
600
634
|
generic_sql_error_handler(err)
|
|
@@ -608,14 +642,19 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
|
|
|
608
642
|
app_name: str,
|
|
609
643
|
app_warehouse: Optional[str],
|
|
610
644
|
):
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
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
|
+
)
|
|
619
658
|
|
|
620
659
|
@staticmethod
|
|
621
660
|
@contextmanager
|
|
@@ -649,3 +688,191 @@ class ApplicationEntity(EntityBase[ApplicationEntityModel]):
|
|
|
649
688
|
return sql_executor.show_specific_object(
|
|
650
689
|
"applications", app_name, name_col=NAME_COL
|
|
651
690
|
)
|
|
691
|
+
|
|
692
|
+
@classmethod
|
|
693
|
+
def get_events(
|
|
694
|
+
cls,
|
|
695
|
+
app_name: str,
|
|
696
|
+
package_name: str,
|
|
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
|
+
)
|
|
786
|
+
sql_executor = get_sql_executor()
|
|
787
|
+
try:
|
|
788
|
+
return sql_executor.execute_query(query, cursor_class=DictCursor).fetchall()
|
|
789
|
+
except ProgrammingError as err:
|
|
790
|
+
generic_sql_error_handler(err)
|
|
791
|
+
|
|
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
|
|
820
|
+
|
|
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,
|
|
833
|
+
)
|
|
834
|
+
if not events:
|
|
835
|
+
continue
|
|
836
|
+
|
|
837
|
+
yield from _new_events_only(previous_events, events)
|
|
838
|
+
last_event_time = events[-1]["TIMESTAMP"]
|
|
839
|
+
except KeyboardInterrupt:
|
|
840
|
+
return
|
|
841
|
+
|
|
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"), "")
|
|
848
|
+
|
|
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
|