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.
- snowflake/cli/__about__.py +1 -1
- snowflake/cli/_app/commands_registration/builtin_plugins.py +2 -0
- snowflake/cli/_app/secret.py +9 -0
- snowflake/cli/_app/snow_connector.py +39 -27
- snowflake/cli/_app/telemetry.py +28 -0
- snowflake/cli/_plugins/connection/commands.py +9 -4
- snowflake/cli/_plugins/git/manager.py +53 -7
- snowflake/cli/_plugins/helpers/commands.py +61 -0
- snowflake/cli/{api/project/schemas/snowpark/__init__.py → _plugins/helpers/plugin_spec.py} +17 -0
- snowflake/cli/_plugins/nativeapp/artifacts.py +10 -9
- snowflake/cli/_plugins/nativeapp/bundle_context.py +1 -1
- snowflake/cli/_plugins/nativeapp/codegen/artifact_processor.py +1 -1
- snowflake/cli/_plugins/nativeapp/codegen/compiler.py +6 -1
- snowflake/cli/_plugins/nativeapp/codegen/setup/native_app_setup_processor.py +1 -1
- snowflake/cli/_plugins/nativeapp/codegen/snowpark/extension_function_utils.py +1 -1
- snowflake/cli/_plugins/nativeapp/codegen/snowpark/models.py +1 -1
- snowflake/cli/_plugins/nativeapp/codegen/snowpark/python_processor.py +5 -1
- snowflake/cli/_plugins/nativeapp/codegen/templates/templates_processor.py +4 -1
- snowflake/cli/_plugins/nativeapp/commands.py +87 -96
- snowflake/cli/_plugins/nativeapp/entities/__init__.py +0 -0
- snowflake/cli/{api/entities/application_entity.py → _plugins/nativeapp/entities/application.py} +264 -83
- snowflake/cli/_plugins/nativeapp/entities/application_package.py +1392 -0
- snowflake/cli/_plugins/nativeapp/exceptions.py +0 -9
- snowflake/cli/_plugins/nativeapp/manager.py +69 -185
- snowflake/cli/_plugins/nativeapp/policy.py +3 -0
- snowflake/cli/_plugins/nativeapp/project_model.py +2 -2
- snowflake/cli/_plugins/nativeapp/run_processor.py +17 -20
- snowflake/cli/_plugins/nativeapp/same_account_install_method.py +0 -4
- snowflake/cli/_plugins/nativeapp/teardown_processor.py +4 -6
- snowflake/cli/_plugins/nativeapp/v2_conversions/v2_to_v1_decorator.py +122 -88
- snowflake/cli/_plugins/nativeapp/version/commands.py +7 -39
- snowflake/cli/_plugins/nativeapp/version/version_processor.py +46 -312
- snowflake/cli/_plugins/object/manager.py +36 -15
- snowflake/cli/_plugins/snowpark/commands.py +4 -4
- snowflake/cli/_plugins/snowpark/common.py +4 -4
- snowflake/cli/{api/entities → _plugins/snowpark}/snowpark_entity.py +2 -2
- snowflake/cli/{api/project/schemas/entities/snowpark_entity.py → _plugins/snowpark/snowpark_entity_model.py} +3 -6
- snowflake/cli/_plugins/snowpark/snowpark_project_paths.py +1 -1
- snowflake/cli/_plugins/stage/manager.py +9 -4
- snowflake/cli/_plugins/streamlit/commands.py +15 -3
- snowflake/cli/_plugins/streamlit/manager.py +12 -4
- snowflake/cli/{api/entities → _plugins/streamlit}/streamlit_entity.py +2 -2
- snowflake/cli/{api/project/schemas/entities → _plugins/streamlit}/streamlit_entity_model.py +5 -12
- snowflake/cli/_plugins/workspace/commands.py +116 -36
- snowflake/cli/_plugins/workspace/plugin_spec.py +1 -1
- 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/commands/snow_typer.py +1 -1
- snowflake/cli/api/config.py +25 -6
- snowflake/cli/api/connections.py +3 -1
- snowflake/cli/api/entities/common.py +4 -0
- snowflake/cli/api/entities/utils.py +3 -14
- snowflake/cli/api/errno.py +1 -0
- snowflake/cli/api/identifiers.py +4 -3
- snowflake/cli/api/metrics.py +92 -0
- snowflake/cli/api/project/definition_conversion.py +61 -18
- snowflake/cli/api/project/schemas/entities/common.py +17 -4
- snowflake/cli/api/project/schemas/entities/entities.py +11 -10
- snowflake/cli/api/project/schemas/project_definition.py +5 -7
- snowflake/cli/api/project/schemas/v1/__init__.py +0 -0
- snowflake/cli/api/project/schemas/{identifier_model.py → v1/identifier_model.py} +0 -7
- snowflake/cli/api/project/schemas/v1/native_app/__init__.py +0 -0
- snowflake/cli/api/project/schemas/{native_app → v1/native_app}/native_app.py +4 -4
- snowflake/cli/api/project/schemas/v1/snowpark/__init__.py +0 -0
- snowflake/cli/api/project/schemas/{snowpark → v1/snowpark}/callable.py +2 -2
- snowflake/cli/api/project/schemas/{snowpark → v1/snowpark}/snowpark.py +2 -2
- snowflake/cli/api/project/schemas/v1/streamlit/__init__.py +0 -0
- snowflake/cli/api/project/schemas/{streamlit → v1/streamlit}/streamlit.py +2 -1
- snowflake/cli/api/rendering/sql_templates.py +6 -0
- snowflake/cli/api/rest_api.py +11 -5
- snowflake/cli/api/sql_execution.py +6 -15
- snowflake/cli/api/utils/definition_rendering.py +24 -4
- {snowflake_cli_labs-3.0.0rc2.dist-info → snowflake_cli_labs-3.0.0rc4.dist-info}/METADATA +9 -7
- {snowflake_cli_labs-3.0.0rc2.dist-info → snowflake_cli_labs-3.0.0rc4.dist-info}/RECORD +83 -79
- snowflake/cli/_plugins/nativeapp/init.py +0 -345
- snowflake/cli/api/entities/application_package_entity.py +0 -658
- snowflake/cli/api/project/schemas/entities/application_entity_model.py +0 -56
- snowflake/cli/api/project/schemas/entities/application_package_entity_model.py +0 -94
- snowflake/cli/api/project/schemas/streamlit/__init__.py +0 -13
- /snowflake/cli/{api/project/schemas/native_app → _plugins/helpers}/__init__.py +0 -0
- /snowflake/cli/api/project/schemas/{native_app → v1/native_app}/application.py +0 -0
- /snowflake/cli/api/project/schemas/{native_app → v1/native_app}/package.py +0 -0
- /snowflake/cli/api/project/schemas/{native_app → v1/native_app}/path_mapping.py +0 -0
- /snowflake/cli/api/project/schemas/{snowpark → v1/snowpark}/argument.py +0 -0
- {snowflake_cli_labs-3.0.0rc2.dist-info → snowflake_cli_labs-3.0.0rc4.dist-info}/WHEEL +0 -0
- {snowflake_cli_labs-3.0.0rc2.dist-info → snowflake_cli_labs-3.0.0rc4.dist-info}/entry_points.txt +0 -0
- {snowflake_cli_labs-3.0.0rc2.dist-info → snowflake_cli_labs-3.0.0rc4.dist-info}/licenses/LICENSE +0 -0
snowflake/cli/{api/entities/application_entity.py → _plugins/nativeapp/entities/application.py}
RENAMED
|
@@ -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
|
-
|
|
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.
|
|
58
|
-
from snowflake.cli.api.project.schemas.entities.
|
|
59
|
-
|
|
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.
|
|
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
|
-
|
|
72
|
+
identifier_for_url,
|
|
68
73
|
unquote_identifier,
|
|
69
74
|
)
|
|
70
|
-
from snowflake.
|
|
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 =
|
|
112
|
-
app_warehouse =
|
|
113
|
-
post_deploy_hooks =
|
|
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
|
|
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
|
-
#
|
|
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
|
-
#
|
|
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 =
|
|
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
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
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
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
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
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
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
|
-
@
|
|
663
|
-
def
|
|
664
|
-
|
|
692
|
+
@classmethod
|
|
693
|
+
def get_events(
|
|
694
|
+
cls,
|
|
695
|
+
app_name: str,
|
|
665
696
|
package_name: str,
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
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
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
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
|
-
|
|
681
|
-
|
|
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
|
-
|
|
684
|
-
|
|
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
|
-
|
|
688
|
-
|
|
837
|
+
yield from _new_events_only(previous_events, events)
|
|
838
|
+
last_event_time = events[-1]["TIMESTAMP"]
|
|
839
|
+
except KeyboardInterrupt:
|
|
840
|
+
return
|
|
689
841
|
|
|
690
|
-
|
|
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
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
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
|