snowflake-cli-labs 2.3.1__py3-none-any.whl → 2.4.0rc1__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 (96) hide show
  1. snowflake/cli/__about__.py +1 -1
  2. snowflake/cli/api/__init__.py +2 -0
  3. snowflake/cli/api/cli_global_context.py +8 -1
  4. snowflake/cli/api/commands/decorators.py +2 -2
  5. snowflake/cli/api/commands/flags.py +49 -4
  6. snowflake/cli/api/commands/snow_typer.py +2 -0
  7. snowflake/cli/api/console/abc.py +2 -0
  8. snowflake/cli/api/console/console.py +6 -5
  9. snowflake/cli/api/constants.py +5 -0
  10. snowflake/cli/api/exceptions.py +12 -0
  11. snowflake/cli/api/identifiers.py +123 -0
  12. snowflake/cli/api/plugins/command/__init__.py +2 -0
  13. snowflake/cli/api/plugins/plugin_config.py +2 -0
  14. snowflake/cli/api/project/definition.py +2 -0
  15. snowflake/cli/api/project/errors.py +3 -3
  16. snowflake/cli/api/project/schemas/identifier_model.py +35 -0
  17. snowflake/cli/api/project/schemas/native_app/native_app.py +4 -0
  18. snowflake/cli/api/project/schemas/native_app/path_mapping.py +21 -3
  19. snowflake/cli/api/project/schemas/project_definition.py +58 -6
  20. snowflake/cli/api/project/schemas/snowpark/argument.py +2 -0
  21. snowflake/cli/api/project/schemas/snowpark/callable.py +8 -17
  22. snowflake/cli/api/project/schemas/streamlit/streamlit.py +2 -2
  23. snowflake/cli/api/project/schemas/updatable_model.py +2 -0
  24. snowflake/cli/api/project/util.py +2 -0
  25. snowflake/cli/api/secure_path.py +2 -0
  26. snowflake/cli/api/sql_execution.py +14 -54
  27. snowflake/cli/api/utils/cursor.py +2 -0
  28. snowflake/cli/api/utils/models.py +23 -0
  29. snowflake/cli/api/utils/naming_utils.py +0 -27
  30. snowflake/cli/api/utils/rendering.py +178 -23
  31. snowflake/cli/app/api_impl/plugin/plugin_config_provider_impl.py +2 -0
  32. snowflake/cli/app/cli_app.py +4 -1
  33. snowflake/cli/app/commands_registration/builtin_plugins.py +8 -0
  34. snowflake/cli/app/commands_registration/command_plugins_loader.py +2 -0
  35. snowflake/cli/app/commands_registration/commands_registration_with_callbacks.py +2 -0
  36. snowflake/cli/app/commands_registration/typer_registration.py +2 -0
  37. snowflake/cli/app/dev/pycharm_remote_debug.py +2 -0
  38. snowflake/cli/app/loggers.py +2 -0
  39. snowflake/cli/app/main_typer.py +1 -1
  40. snowflake/cli/app/printing.py +3 -1
  41. snowflake/cli/app/snow_connector.py +2 -2
  42. snowflake/cli/plugins/connection/commands.py +5 -14
  43. snowflake/cli/plugins/connection/util.py +1 -1
  44. snowflake/cli/plugins/cortex/__init__.py +0 -0
  45. snowflake/cli/plugins/cortex/commands.py +312 -0
  46. snowflake/cli/plugins/cortex/constants.py +3 -0
  47. snowflake/cli/plugins/cortex/manager.py +175 -0
  48. snowflake/cli/plugins/cortex/plugin_spec.py +16 -0
  49. snowflake/cli/plugins/cortex/types.py +8 -0
  50. snowflake/cli/plugins/git/commands.py +15 -0
  51. snowflake/cli/plugins/nativeapp/artifacts.py +368 -123
  52. snowflake/cli/plugins/nativeapp/codegen/artifact_processor.py +45 -0
  53. snowflake/cli/plugins/nativeapp/codegen/compiler.py +104 -0
  54. snowflake/cli/plugins/nativeapp/codegen/sandbox.py +2 -0
  55. snowflake/cli/plugins/nativeapp/codegen/snowpark/callback_source.py.jinja +181 -0
  56. snowflake/cli/plugins/nativeapp/codegen/snowpark/extension_function_utils.py +196 -0
  57. snowflake/cli/plugins/nativeapp/codegen/snowpark/models.py +47 -0
  58. snowflake/cli/plugins/nativeapp/codegen/snowpark/python_processor.py +489 -0
  59. snowflake/cli/plugins/nativeapp/commands.py +11 -4
  60. snowflake/cli/plugins/nativeapp/common_flags.py +12 -5
  61. snowflake/cli/plugins/nativeapp/manager.py +49 -16
  62. snowflake/cli/plugins/nativeapp/policy.py +2 -0
  63. snowflake/cli/plugins/nativeapp/run_processor.py +2 -0
  64. snowflake/cli/plugins/nativeapp/teardown_processor.py +80 -8
  65. snowflake/cli/plugins/nativeapp/utils.py +7 -6
  66. snowflake/cli/plugins/nativeapp/version/commands.py +6 -5
  67. snowflake/cli/plugins/nativeapp/version/version_processor.py +2 -0
  68. snowflake/cli/plugins/notebook/commands.py +21 -0
  69. snowflake/cli/plugins/notebook/exceptions.py +6 -0
  70. snowflake/cli/plugins/notebook/manager.py +46 -3
  71. snowflake/cli/plugins/notebook/types.py +2 -0
  72. snowflake/cli/plugins/object/command_aliases.py +80 -0
  73. snowflake/cli/plugins/object/commands.py +10 -6
  74. snowflake/cli/plugins/object/common.py +2 -0
  75. snowflake/cli/plugins/object_stage_deprecated/__init__.py +1 -0
  76. snowflake/cli/plugins/object_stage_deprecated/plugin_spec.py +20 -0
  77. snowflake/cli/plugins/snowpark/commands.py +62 -6
  78. snowflake/cli/plugins/snowpark/common.py +17 -6
  79. snowflake/cli/plugins/spcs/compute_pool/commands.py +22 -1
  80. snowflake/cli/plugins/spcs/compute_pool/manager.py +2 -0
  81. snowflake/cli/plugins/spcs/image_repository/commands.py +25 -1
  82. snowflake/cli/plugins/spcs/image_repository/manager.py +3 -1
  83. snowflake/cli/plugins/spcs/services/commands.py +39 -5
  84. snowflake/cli/plugins/spcs/services/manager.py +2 -0
  85. snowflake/cli/plugins/sql/commands.py +13 -5
  86. snowflake/cli/plugins/sql/manager.py +40 -19
  87. snowflake/cli/plugins/stage/commands.py +29 -3
  88. snowflake/cli/plugins/stage/diff.py +2 -0
  89. snowflake/cli/plugins/streamlit/commands.py +26 -10
  90. snowflake/cli/plugins/streamlit/manager.py +9 -10
  91. {snowflake_cli_labs-2.3.1.dist-info → snowflake_cli_labs-2.4.0rc1.dist-info}/METADATA +4 -2
  92. {snowflake_cli_labs-2.3.1.dist-info → snowflake_cli_labs-2.4.0rc1.dist-info}/RECORD +96 -76
  93. /snowflake/cli/plugins/{object/stage_deprecated → object_stage_deprecated}/commands.py +0 -0
  94. {snowflake_cli_labs-2.3.1.dist-info → snowflake_cli_labs-2.4.0rc1.dist-info}/WHEEL +0 -0
  95. {snowflake_cli_labs-2.3.1.dist-info → snowflake_cli_labs-2.4.0rc1.dist-info}/entry_points.txt +0 -0
  96. {snowflake_cli_labs-2.3.1.dist-info → snowflake_cli_labs-2.4.0rc1.dist-info}/licenses/LICENSE +0 -0
@@ -1,3 +1,3 @@
1
1
  from __future__ import annotations
2
2
 
3
- VERSION = "2.3.1"
3
+ VERSION = "2.4.0rc1"
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  from typing import Optional
2
4
 
3
5
  from snowflake.cli.api.plugins.plugin_config import PluginConfigProvider
@@ -1,9 +1,12 @@
1
+ from __future__ import annotations
2
+
1
3
  import re
2
4
  from pathlib import Path
3
5
  from typing import Dict, Optional
4
6
 
5
7
  from snowflake.cli.api.exceptions import InvalidSchemaError
6
8
  from snowflake.cli.api.output.formats import OutputFormat
9
+ from snowflake.cli.api.project.schemas.project_definition import ProjectDefinition
7
10
  from snowflake.connector import SnowflakeConnection
8
11
 
9
12
  schema_pattern = re.compile(r".+\..+")
@@ -280,6 +283,10 @@ class _CliGlobalContextAccess:
280
283
  def connection(self) -> SnowflakeConnection:
281
284
  return self._manager.connection
282
285
 
286
+ @property
287
+ def connection_context(self) -> _ConnectionContext:
288
+ return self._manager.connection_context
289
+
283
290
  @property
284
291
  def enable_tracebacks(self) -> bool:
285
292
  return self._manager.enable_tracebacks
@@ -297,7 +304,7 @@ class _CliGlobalContextAccess:
297
304
  return self._manager.experimental
298
305
 
299
306
  @property
300
- def project_definition(self):
307
+ def project_definition(self) -> ProjectDefinition | None:
301
308
  return self._manager.project_definition
302
309
 
303
310
  @property
@@ -29,7 +29,7 @@ from snowflake.cli.api.commands.flags import (
29
29
  VerboseOption,
30
30
  WarehouseOption,
31
31
  experimental_option,
32
- project_definition_option,
32
+ project_type_option,
33
33
  )
34
34
  from snowflake.cli.api.exceptions import CommandReturnTypeError
35
35
  from snowflake.cli.api.output.formats import OutputFormat
@@ -67,7 +67,7 @@ def with_project_definition(project_name: str):
67
67
  "project_definition",
68
68
  inspect.Parameter.KEYWORD_ONLY,
69
69
  annotation=Optional[str],
70
- default=project_definition_option(project_name),
70
+ default=project_type_option(project_name),
71
71
  )
72
72
  ],
73
73
  )
@@ -12,6 +12,7 @@ import typer
12
12
  from click import ClickException
13
13
  from snowflake.cli.api.cli_global_context import cli_context_manager
14
14
  from snowflake.cli.api.console import cli_console
15
+ from snowflake.cli.api.exceptions import MissingConfiguration
15
16
  from snowflake.cli.api.output.formats import OutputFormat
16
17
 
17
18
  DEFAULT_CONTEXT_SETTINGS = {"help_option_names": ["--help", "-h"]}
@@ -416,7 +417,6 @@ VariablesOption = typer.Option(
416
417
  "--variable",
417
418
  "-D",
418
419
  help="Variables for the template. For example: `-D \"<key>=<value>\"`, string values must be in `''`.",
419
- hidden=True,
420
420
  show_default=False,
421
421
  )
422
422
 
@@ -469,17 +469,21 @@ def experimental_option(
469
469
 
470
470
  def identifier_argument(sf_object: str, example: str) -> typer.Argument:
471
471
  return typer.Argument(
472
- ..., help=f"Identifier of the {sf_object}. For example: {example}"
472
+ ...,
473
+ help=f"Identifier of the {sf_object}. For example: {example}",
474
+ show_default=False,
473
475
  )
474
476
 
475
477
 
476
478
  def execution_identifier_argument(sf_object: str, example: str) -> typer.Argument:
477
479
  return typer.Argument(
478
- ..., help=f"Execution identifier of the {sf_object}. For example: {example}"
480
+ ...,
481
+ help=f"Execution identifier of the {sf_object}. For example: {example}",
482
+ show_default=False,
479
483
  )
480
484
 
481
485
 
482
- def project_definition_option(project_name: str):
486
+ def project_type_option(project_name: str):
483
487
  from snowflake.cli.api.exceptions import NoProjectDefinitionError
484
488
  from snowflake.cli.api.project.definition_manager import DefinitionManager
485
489
 
@@ -515,6 +519,47 @@ def project_definition_option(project_name: str):
515
519
  )
516
520
 
517
521
 
522
+ def project_definition_option(optional: bool = False):
523
+ from snowflake.cli.api.project.definition_manager import DefinitionManager
524
+
525
+ def _callback(project_path: Optional[str]):
526
+ try:
527
+ dm = DefinitionManager(project_path)
528
+ project_definition = dm.project_definition
529
+ project_root = dm.project_root
530
+ except MissingConfiguration:
531
+ if optional:
532
+ project_definition = None
533
+ project_root = None
534
+ else:
535
+ raise
536
+ cli_context_manager.set_project_definition(project_definition)
537
+ cli_context_manager.set_project_root(project_root)
538
+ return project_definition
539
+
540
+ return typer.Option(
541
+ None,
542
+ "-p",
543
+ "--project",
544
+ help=f"Path where Snowflake project resides. Defaults to current working directory.",
545
+ callback=_callback,
546
+ show_default=False,
547
+ )
548
+
549
+
550
+ def readable_file_option(param_name: str, help_str: str) -> typer.Option:
551
+ return typer.Option(
552
+ None,
553
+ param_name,
554
+ exists=True,
555
+ file_okay=True,
556
+ dir_okay=False,
557
+ readable=True,
558
+ help=help_str,
559
+ show_default=False,
560
+ )
561
+
562
+
518
563
  def deprecated_flag_callback(msg: str):
519
564
  def _warning_callback(ctx: click.Context, param: click.Parameter, value: Any):
520
565
  if ctx.get_parameter_source(param.name) != click.core.ParameterSource.DEFAULT: # type: ignore[attr-defined]
@@ -22,6 +22,8 @@ class SnowTyper(typer.Typer):
22
22
  **kwargs,
23
23
  context_settings=DEFAULT_CONTEXT_SETTINGS,
24
24
  pretty_exceptions_show_locals=False,
25
+ no_args_is_help=True,
26
+ add_completion=True,
25
27
  )
26
28
 
27
29
  @wraps(typer.Typer.command)
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  from abc import ABC, abstractmethod
2
4
  from contextlib import contextmanager
3
5
  from typing import Callable, Iterator, Optional
@@ -57,11 +57,12 @@ class CliConsole(AbstractConsole):
57
57
  self._print(self._format_message(enter_message, Output.PHASE))
58
58
  self._in_phase = True
59
59
 
60
- yield self.step
61
-
62
- self._in_phase = False
63
- if exit_message:
64
- self._print(self._format_message(exit_message, Output.PHASE))
60
+ try:
61
+ yield self.step
62
+ finally:
63
+ self._in_phase = False
64
+ if exit_message:
65
+ self._print(self._format_message(exit_message, Output.PHASE))
65
66
 
66
67
  def step(self, message: str):
67
68
  """Displays a message to output.
@@ -23,6 +23,11 @@ class ObjectType(Enum):
23
23
  DATABASE = ObjectNames("database", "database", "databases")
24
24
  FUNCTION = ObjectNames("function", "function", "functions")
25
25
  INTEGRATION = ObjectNames("integration", "integration", "integrations")
26
+ EXTERNAL_ACCESS_INTEGRATION = ObjectNames(
27
+ "external-access-integration",
28
+ "external access integration",
29
+ "external access integrations",
30
+ )
26
31
  # JOB = ObjectNames("job", "job", "jobs")
27
32
  NETWORK_RULE = ObjectNames("network-rule", "network rule", "network rules")
28
33
  PROCEDURE = ObjectNames("procedure", "procedure", "procedures")
@@ -128,3 +128,15 @@ class SchemaNotProvidedError(ClickException):
128
128
  super().__init__(
129
129
  "Schema not specified. Please update connection to add `schema` parameter, or re-run command using `--schema` option. Use `snow connection list` to list existing connections."
130
130
  )
131
+
132
+
133
+ class FQNNameError(ClickException):
134
+ def __init__(self, name: str):
135
+ super().__init__(f"Specified name '{name}' is not valid name.")
136
+
137
+
138
+ class FQNInconsistencyError(ClickException):
139
+ def __init__(self, part: str, name: str):
140
+ super().__init__(
141
+ f"{part.capitalize()} provided but name '{name}' is fully qualified name."
142
+ )
@@ -0,0 +1,123 @@
1
+ from __future__ import annotations
2
+
3
+ import re
4
+
5
+ from click import ClickException
6
+ from snowflake.cli.api.cli_global_context import cli_context
7
+ from snowflake.cli.api.exceptions import FQNInconsistencyError, FQNNameError
8
+ from snowflake.cli.api.project.schemas.identifier_model import ObjectIdentifierBaseModel
9
+ from snowflake.cli.api.project.util import VALID_IDENTIFIER_REGEX, unquote_identifier
10
+
11
+
12
+ class FQN:
13
+ """
14
+ Class representing an identifier and supporting fully qualified names.
15
+
16
+ The instance supports builder pattern that allows updating the identifier with database and
17
+ schema from different sources. For example:
18
+
19
+ fqn = FQN.from_string("my_schema.object").using_connection(conn)
20
+ fqn = FQN.from_identifier_model(cli_context.project_definition.streamlit).using_context()
21
+ fqn = FQN.from_string("my_name").set_database("db").set_schema("foo")
22
+ """
23
+
24
+ def __init__(self, database: str | None, schema: str | None, name: str):
25
+ self._database = database
26
+ self._schema = schema
27
+ self._name = name
28
+
29
+ @property
30
+ def database(self) -> str | None:
31
+ return self._database
32
+
33
+ @property
34
+ def schema(self) -> str | None:
35
+ return self._schema
36
+
37
+ @property
38
+ def name(self) -> str:
39
+ return self._name
40
+
41
+ @property
42
+ def identifier(self) -> str:
43
+ if self.database:
44
+ return f"{self.database}.{self.schema if self.schema else 'PUBLIC'}.{self.name}"
45
+ if self.schema:
46
+ return f"{self.schema}.{self.name}"
47
+ return self.name
48
+
49
+ @property
50
+ def url_identifier(self) -> str:
51
+ return ".".join(unquote_identifier(part) for part in self.identifier.split("."))
52
+
53
+ def __str__(self):
54
+ return self.identifier
55
+
56
+ def __eq__(self, other):
57
+ return self.identifier == other.identifier
58
+
59
+ @classmethod
60
+ def from_string(cls, identifier: str) -> "FQN":
61
+ """
62
+ Takes in an object name in the form [[database.]schema.]name. Returns a FQN instance.
63
+ """
64
+ qualifier_pattern = rf"(?:(?P<first_qualifier>{VALID_IDENTIFIER_REGEX})\.)?(?:(?P<second_qualifier>{VALID_IDENTIFIER_REGEX})\.)?(?P<name>{VALID_IDENTIFIER_REGEX})(?P<signature>\(.*\))?"
65
+ result = re.fullmatch(qualifier_pattern, identifier)
66
+
67
+ if result is None:
68
+ raise FQNNameError(identifier)
69
+
70
+ unqualified_name = result.group("name")
71
+ if result.group("second_qualifier") is not None:
72
+ database = result.group("first_qualifier")
73
+ schema = result.group("second_qualifier")
74
+ else:
75
+ database = None
76
+ schema = result.group("first_qualifier")
77
+ if signature := result.group("signature"):
78
+ unqualified_name = unqualified_name + signature
79
+ return cls(name=unqualified_name, schema=schema, database=database)
80
+
81
+ @classmethod
82
+ def from_identifier_model(cls, model: ObjectIdentifierBaseModel) -> "FQN":
83
+ """Create an instance from object model."""
84
+ if not isinstance(model, ObjectIdentifierBaseModel):
85
+ raise ClickException(
86
+ f"Expected {type(ObjectIdentifierBaseModel)}, got {model}."
87
+ )
88
+
89
+ fqn = cls.from_string(model.name)
90
+
91
+ if fqn.database and model.database:
92
+ raise FQNInconsistencyError("database", model.name)
93
+ if fqn.schema and model.schema_name:
94
+ raise FQNInconsistencyError("schema", model.name)
95
+
96
+ return fqn.set_database(model.database).set_schema(model.schema_name)
97
+
98
+ def set_database(self, database: str | None) -> "FQN":
99
+ if database:
100
+ self._database = database
101
+ return self
102
+
103
+ def set_schema(self, schema: str | None) -> "FQN":
104
+ if schema:
105
+ self._schema = schema
106
+ return self
107
+
108
+ def set_name(self, name: str) -> "FQN":
109
+ self._name = name
110
+ return self
111
+
112
+ def using_connection(self, conn) -> "FQN":
113
+ """Update the instance with database and schema from connection."""
114
+ # Update the identifier only it if wasn't already a qualified name
115
+ if conn.database and not self.database:
116
+ self.set_database(conn.database)
117
+ if conn.schema and not self.schema:
118
+ self.set_schema(conn.schema)
119
+ return self
120
+
121
+ def using_context(self) -> "FQN":
122
+ """Update the instance with database and schema from connection in current cli context."""
123
+ return self.using_connection(cli_context.connection)
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  from dataclasses import dataclass
2
4
  from enum import Enum
3
5
  from functools import cached_property
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  from dataclasses import dataclass
2
4
  from typing import Any, Dict, List
3
5
 
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  from pathlib import Path
2
4
  from typing import List
3
5
 
@@ -6,14 +6,14 @@ from pydantic import ValidationError
6
6
  class SchemaValidationError(Exception):
7
7
  generic_message = "For field {loc} you provided '{loc}'. This caused: {msg}"
8
8
  message_templates = {
9
- "string_type": "{msg} for field '{loc}', you provided '{input}'",
10
- "extra_forbidden": "{msg}. You provided field '{loc}' with value '{input}' that is not present in the schema",
9
+ "string_type": "{msg} for field '{loc}', you provided '{input}'.",
10
+ "extra_forbidden": "{msg}. You provided field '{loc}' with value '{input}' that is not supported in given version.",
11
11
  "missing": "Your project definition is missing following fields: {loc}",
12
12
  }
13
13
 
14
14
  def __init__(self, error: ValidationError):
15
15
  errors = error.errors()
16
- message = f"During evaluation of {error.title} schema following errors were encountered:\n"
16
+ message = f"During evaluation of {error.title} in project definition following errors were encountered:\n"
17
17
  message += "\n".join(
18
18
  [
19
19
  self.message_templates.get(e["type"], self.generic_message).format(**e)
@@ -0,0 +1,35 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Optional, cast
4
+
5
+ from pydantic import Field
6
+ from snowflake.cli.api.project.schemas.updatable_model import IdentifierField
7
+
8
+
9
+ class ObjectIdentifierBaseModel:
10
+ """
11
+ Type representing a base class defining object that can be identified by fully qualified name (db.schema.name).
12
+ This is not a Pydantic model and the purpose of this class is to provide typing support to Pydantic models
13
+ generated using a factory class ObjectIdentifierModel.
14
+ """
15
+
16
+ name: str
17
+ database: Optional[str]
18
+ schema_name: Optional[str]
19
+
20
+
21
+ def ObjectIdentifierModel(object_name: str) -> ObjectIdentifierBaseModel: # noqa: N802
22
+ """Generates ObjectIdentifierBaseModel but with object specific descriptions."""
23
+
24
+ class _ObjectIdentifierModel(ObjectIdentifierBaseModel):
25
+ name: str = Field(title=f"{object_name} name")
26
+ database: Optional[str] = IdentifierField(
27
+ title=f"Name of the database for the {object_name}", default=None
28
+ )
29
+ schema_name: Optional[str] = IdentifierField(
30
+ title=f"Name of the schema for the {object_name}",
31
+ default=None,
32
+ alias="schema",
33
+ )
34
+
35
+ return cast(ObjectIdentifierBaseModel, _ObjectIdentifierModel)
@@ -24,6 +24,10 @@ class NativeApp(UpdatableModel):
24
24
  title="Folder at the root of your project where the build step copies the artifacts.",
25
25
  default="output/deploy/",
26
26
  )
27
+ generated_root: Optional[str] = Field(
28
+ title="Subdirectory of the deploy root where files generated by the Snowflake CLI will be written.",
29
+ default="__generated/",
30
+ )
27
31
  source_stage: Optional[str] = Field(
28
32
  title="Identifier of the stage that stores the application artifacts.",
29
33
  default="app_src.stage",
@@ -2,11 +2,11 @@ from __future__ import annotations
2
2
 
3
3
  from typing import Any, Dict, List, Optional, Union
4
4
 
5
- from pydantic import Field
5
+ from pydantic import Field, field_validator
6
6
  from snowflake.cli.api.project.schemas.updatable_model import UpdatableModel
7
7
 
8
8
 
9
- class AnnotationProcessor(UpdatableModel):
9
+ class ProcessorMapping(UpdatableModel):
10
10
  name: str = Field(
11
11
  title="Name of a processor to invoke on a collection of artifacts."
12
12
  )
@@ -19,4 +19,22 @@ class AnnotationProcessor(UpdatableModel):
19
19
  class PathMapping(UpdatableModel):
20
20
  src: str
21
21
  dest: Optional[str] = None
22
- processors: Optional[List[Union[str, AnnotationProcessor]]] = None
22
+ processors: Optional[List[Union[str, ProcessorMapping]]] = []
23
+
24
+ @field_validator("processors")
25
+ @classmethod
26
+ def transform_processors(
27
+ cls, input_values: Optional[List[Union[str, Dict, ProcessorMapping]]]
28
+ ):
29
+ if input_values is None:
30
+ return []
31
+
32
+ transformed_processors: List[ProcessorMapping] = []
33
+ for input_processor in input_values:
34
+ if isinstance(input_processor, str):
35
+ transformed_processors.append(ProcessorMapping(name=input_processor))
36
+ elif isinstance(input_processor, Dict):
37
+ transformed_processors.append(ProcessorMapping(**input_processor))
38
+ else:
39
+ transformed_processors.append(input_processor)
40
+ return transformed_processors
@@ -1,20 +1,36 @@
1
1
  from __future__ import annotations
2
2
 
3
- from typing import Optional
3
+ from typing import Any, Dict, Optional, Union
4
4
 
5
- from pydantic import Field
5
+ from packaging.version import Version
6
+ from pydantic import Field, field_validator
6
7
  from snowflake.cli.api.project.schemas.native_app.native_app import NativeApp
7
8
  from snowflake.cli.api.project.schemas.snowpark.snowpark import Snowpark
8
9
  from snowflake.cli.api.project.schemas.streamlit.streamlit import Streamlit
9
10
  from snowflake.cli.api.project.schemas.updatable_model import UpdatableModel
11
+ from snowflake.cli.api.utils.models import EnvironWithDefinedDictFallback
10
12
 
11
13
 
12
- class ProjectDefinition(UpdatableModel):
13
- definition_version: int = Field(
14
+ class _BaseDefinition(UpdatableModel):
15
+ definition_version: Union[str, int] = Field(
14
16
  title="Version of the project definition schema, which is currently 1",
15
- ge=1,
16
- le=1,
17
17
  )
18
+
19
+ @field_validator("definition_version")
20
+ @classmethod
21
+ def _is_supported_version(cls, version: str) -> str:
22
+ version = str(version)
23
+ if version not in _version_map:
24
+ raise ValueError(
25
+ f'Version {version} is not supported. Supported versions: {", ".join(_version_map)}'
26
+ )
27
+ return version
28
+
29
+ def meets_version_requirement(self, required_version: str) -> bool:
30
+ return Version(self.definition_version) >= Version(required_version)
31
+
32
+
33
+ class _DefinitionV10(_BaseDefinition):
18
34
  native_app: Optional[NativeApp] = Field(
19
35
  title="Native app definitions for the project", default=None
20
36
  )
@@ -25,3 +41,39 @@ class ProjectDefinition(UpdatableModel):
25
41
  streamlit: Optional[Streamlit] = Field(
26
42
  title="Streamlit definitions for the project", default=None
27
43
  )
44
+
45
+
46
+ class _DefinitionV11(_DefinitionV10):
47
+ env: Optional[Dict] = Field(
48
+ title="Environment specification for this project.",
49
+ default=None,
50
+ validation_alias="env",
51
+ )
52
+
53
+ @field_validator("env")
54
+ @classmethod
55
+ def _convert_env(cls, env: Optional[Dict]) -> EnvironWithDefinedDictFallback:
56
+ variables = EnvironWithDefinedDictFallback(env if env else {})
57
+ return variables
58
+
59
+
60
+ class ProjectDefinition(_DefinitionV11):
61
+ def __init__(self, **kwargs):
62
+ super().__init__(**kwargs)
63
+ self._validate(kwargs)
64
+
65
+ @staticmethod
66
+ def _validate(data: Any):
67
+ if not isinstance(data, dict):
68
+ return
69
+ if version := str(data.get("definition_version")):
70
+ version_model = _version_map.get(version)
71
+ if not version_model:
72
+ raise ValueError(
73
+ f"Unknown schema version: {version}. Supported version: {_supported_version}"
74
+ )
75
+ version_model(**data)
76
+
77
+
78
+ _version_map = {"1": _DefinitionV10, "1.1": _DefinitionV11}
79
+ _supported_version = tuple(_version_map.keys())
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  from typing import Optional
2
4
 
3
5
  from pydantic import Field
@@ -1,26 +1,16 @@
1
+ from __future__ import annotations
2
+
1
3
  from typing import Dict, List, Optional, Union
2
4
 
3
5
  from pydantic import Field, field_validator
6
+ from snowflake.cli.api.project.schemas.identifier_model import ObjectIdentifierModel
4
7
  from snowflake.cli.api.project.schemas.snowpark.argument import Argument
5
8
  from snowflake.cli.api.project.schemas.updatable_model import (
6
- IdentifierField,
7
9
  UpdatableModel,
8
10
  )
9
11
 
10
12
 
11
- class Callable(UpdatableModel):
12
- name: str = Field(
13
- title="Object identifier"
14
- ) # TODO: implement validator. If a name is filly qualified, database and schema cannot be specified
15
- database: Optional[str] = IdentifierField(
16
- title="Name of the database for the function or procedure", default=None
17
- )
18
-
19
- schema_name: Optional[str] = IdentifierField(
20
- title="Name of the schema for the function or procedure",
21
- default=None,
22
- alias="schema",
23
- )
13
+ class _CallableBase(UpdatableModel):
24
14
  handler: str = Field(
25
15
  title="Function’s or procedure’s implementation of the object inside source module",
26
16
  examples=["functions.hello_function"],
@@ -55,12 +45,13 @@ class Callable(UpdatableModel):
55
45
  return runtime_input
56
46
 
57
47
 
58
- class FunctionSchema(Callable):
48
+ class FunctionSchema(_CallableBase, ObjectIdentifierModel(object_name="function")): # type: ignore
59
49
  pass
60
50
 
61
51
 
62
- class ProcedureSchema(Callable):
52
+ class ProcedureSchema(_CallableBase, ObjectIdentifierModel(object_name="procedure")): # type: ignore
63
53
  execute_as_caller: Optional[bool] = Field(
64
- title="Determine whether the procedure is executed with the privileges of the owner (you) or with the privileges of the caller",
54
+ title="Determine whether the procedure is executed with the privileges of "
55
+ "the owner (you) or with the privileges of the caller",
65
56
  default=False,
66
57
  )
@@ -3,11 +3,11 @@ from __future__ import annotations
3
3
  from typing import List, Optional
4
4
 
5
5
  from pydantic import Field
6
+ from snowflake.cli.api.project.schemas.identifier_model import ObjectIdentifierModel
6
7
  from snowflake.cli.api.project.schemas.updatable_model import UpdatableModel
7
8
 
8
9
 
9
- class Streamlit(UpdatableModel):
10
- name: str = Field(title="App identifier")
10
+ class Streamlit(UpdatableModel, ObjectIdentifierModel(object_name="Streamlit")): # type: ignore
11
11
  stage: Optional[str] = Field(
12
12
  title="Stage in which the app’s artifacts will be stored", default="streamlit"
13
13
  )
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  from typing import Any, Dict
2
4
 
3
5
  from pydantic import BaseModel, ConfigDict, Field, ValidationError
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  import codecs
2
4
  import os
3
5
  import re
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  import errno
2
4
  import logging
3
5
  import os