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
|
@@ -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.
|
|
26
|
-
|
|
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.
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
266
|
-
|
|
267
|
-
|
|
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
|
|
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
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
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
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
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.
|
|
22
|
-
|
|
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
|
-
|
|
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.
|
|
21
|
-
|
|
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.
|
|
24
|
-
|
|
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,
|
|
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
|
|
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
|
-
|
|
93
|
-
|
|
80
|
+
force: bool,
|
|
81
|
+
interactive: bool,
|
|
94
82
|
*args,
|
|
95
83
|
**kwargs,
|
|
96
84
|
):
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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
|
-
|
|
84
|
-
|
|
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
|
-
|
|
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
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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}"
|