snowflake-cli 2.8.2__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 +17 -0
- snowflake/cli/__init__.py +13 -0
- snowflake/cli/api/__init__.py +48 -0
- snowflake/cli/api/cli_global_context.py +390 -0
- snowflake/cli/api/commands/__init__.py +13 -0
- snowflake/cli/api/commands/alias.py +23 -0
- snowflake/cli/api/commands/decorators.py +354 -0
- snowflake/cli/api/commands/execution_metadata.py +40 -0
- snowflake/cli/api/commands/experimental_behaviour.py +19 -0
- snowflake/cli/api/commands/flags.py +662 -0
- snowflake/cli/api/commands/project_initialisation.py +65 -0
- snowflake/cli/api/commands/snow_typer.py +237 -0
- snowflake/cli/api/commands/typer_pre_execute.py +26 -0
- snowflake/cli/api/config.py +348 -0
- snowflake/cli/api/console/__init__.py +17 -0
- snowflake/cli/api/console/abc.py +89 -0
- snowflake/cli/api/console/console.py +134 -0
- snowflake/cli/api/console/enum.py +17 -0
- snowflake/cli/api/constants.py +79 -0
- snowflake/cli/api/errno.py +27 -0
- snowflake/cli/api/exceptions.py +164 -0
- snowflake/cli/api/feature_flags.py +55 -0
- snowflake/cli/api/identifiers.py +167 -0
- snowflake/cli/api/output/__init__.py +13 -0
- snowflake/cli/api/output/formats.py +20 -0
- snowflake/cli/api/output/types.py +118 -0
- snowflake/cli/api/plugins/__init__.py +13 -0
- snowflake/cli/api/plugins/command/__init__.py +72 -0
- snowflake/cli/api/plugins/command/plugin_hook_specs.py +21 -0
- snowflake/cli/api/plugins/plugin_config.py +32 -0
- snowflake/cli/api/project/__init__.py +13 -0
- snowflake/cli/api/project/definition.py +84 -0
- snowflake/cli/api/project/definition_manager.py +134 -0
- snowflake/cli/api/project/errors.py +56 -0
- snowflake/cli/api/project/project_verification.py +23 -0
- snowflake/cli/api/project/schemas/__init__.py +13 -0
- snowflake/cli/api/project/schemas/entities/application_entity.py +44 -0
- snowflake/cli/api/project/schemas/entities/application_package_entity.py +66 -0
- snowflake/cli/api/project/schemas/entities/common.py +78 -0
- snowflake/cli/api/project/schemas/entities/entities.py +30 -0
- snowflake/cli/api/project/schemas/identifier_model.py +49 -0
- snowflake/cli/api/project/schemas/native_app/__init__.py +13 -0
- snowflake/cli/api/project/schemas/native_app/application.py +62 -0
- snowflake/cli/api/project/schemas/native_app/native_app.py +93 -0
- snowflake/cli/api/project/schemas/native_app/package.py +78 -0
- snowflake/cli/api/project/schemas/native_app/path_mapping.py +65 -0
- snowflake/cli/api/project/schemas/project_definition.py +199 -0
- snowflake/cli/api/project/schemas/snowpark/__init__.py +13 -0
- snowflake/cli/api/project/schemas/snowpark/argument.py +28 -0
- snowflake/cli/api/project/schemas/snowpark/callable.py +69 -0
- snowflake/cli/api/project/schemas/snowpark/snowpark.py +36 -0
- snowflake/cli/api/project/schemas/streamlit/__init__.py +13 -0
- snowflake/cli/api/project/schemas/streamlit/streamlit.py +46 -0
- snowflake/cli/api/project/schemas/template.py +77 -0
- snowflake/cli/api/project/schemas/updatable_model.py +194 -0
- snowflake/cli/api/project/util.py +261 -0
- snowflake/cli/api/rendering/__init__.py +13 -0
- snowflake/cli/api/rendering/jinja.py +112 -0
- snowflake/cli/api/rendering/project_definition_templates.py +39 -0
- snowflake/cli/api/rendering/project_templates.py +98 -0
- snowflake/cli/api/rendering/sql_templates.py +60 -0
- snowflake/cli/api/rest_api.py +172 -0
- snowflake/cli/api/sanitizers.py +43 -0
- snowflake/cli/api/secure_path.py +362 -0
- snowflake/cli/api/secure_utils.py +29 -0
- snowflake/cli/api/sql_execution.py +260 -0
- snowflake/cli/api/utils/__init__.py +13 -0
- snowflake/cli/api/utils/cursor.py +34 -0
- snowflake/cli/api/utils/definition_rendering.py +383 -0
- snowflake/cli/api/utils/dict_utils.py +73 -0
- snowflake/cli/api/utils/error_handling.py +23 -0
- snowflake/cli/api/utils/graph.py +97 -0
- snowflake/cli/api/utils/models.py +63 -0
- snowflake/cli/api/utils/naming_utils.py +13 -0
- snowflake/cli/api/utils/path_utils.py +36 -0
- snowflake/cli/api/utils/templating_functions.py +144 -0
- snowflake/cli/api/utils/types.py +35 -0
- snowflake/cli/app/__init__.py +22 -0
- snowflake/cli/app/__main__.py +31 -0
- snowflake/cli/app/api_impl/__init__.py +13 -0
- snowflake/cli/app/api_impl/plugin/__init__.py +13 -0
- snowflake/cli/app/api_impl/plugin/plugin_config_provider_impl.py +66 -0
- snowflake/cli/app/build_and_push.sh +8 -0
- snowflake/cli/app/cli_app.py +243 -0
- snowflake/cli/app/commands_registration/__init__.py +33 -0
- snowflake/cli/app/commands_registration/builtin_plugins.py +54 -0
- snowflake/cli/app/commands_registration/command_plugins_loader.py +169 -0
- snowflake/cli/app/commands_registration/commands_registration_with_callbacks.py +105 -0
- snowflake/cli/app/commands_registration/exception_logging.py +26 -0
- snowflake/cli/app/commands_registration/threadsafe.py +48 -0
- snowflake/cli/app/commands_registration/typer_registration.py +153 -0
- snowflake/cli/app/constants.py +19 -0
- snowflake/cli/app/dev/__init__.py +13 -0
- snowflake/cli/app/dev/commands_structure.py +48 -0
- snowflake/cli/app/dev/docs/__init__.py +13 -0
- snowflake/cli/app/dev/docs/commands_docs_generator.py +100 -0
- snowflake/cli/app/dev/docs/generator.py +35 -0
- snowflake/cli/app/dev/docs/project_definition_docs_generator.py +58 -0
- snowflake/cli/app/dev/docs/project_definition_generate_json_schema.py +227 -0
- snowflake/cli/app/dev/docs/template_utils.py +23 -0
- snowflake/cli/app/dev/docs/templates/definition_description.rst.jinja2 +38 -0
- snowflake/cli/app/dev/docs/templates/overview.rst.jinja2 +9 -0
- snowflake/cli/app/dev/docs/templates/usage.rst.jinja2 +57 -0
- snowflake/cli/app/dev/pycharm_remote_debug.py +46 -0
- snowflake/cli/app/loggers.py +199 -0
- snowflake/cli/app/main_typer.py +62 -0
- snowflake/cli/app/printing.py +181 -0
- snowflake/cli/app/snow_connector.py +243 -0
- snowflake/cli/app/telemetry.py +189 -0
- snowflake/cli/plugins/__init__.py +13 -0
- snowflake/cli/plugins/connection/__init__.py +13 -0
- snowflake/cli/plugins/connection/commands.py +330 -0
- snowflake/cli/plugins/connection/plugin_spec.py +30 -0
- snowflake/cli/plugins/connection/util.py +179 -0
- snowflake/cli/plugins/cortex/__init__.py +13 -0
- snowflake/cli/plugins/cortex/commands.py +327 -0
- snowflake/cli/plugins/cortex/constants.py +17 -0
- snowflake/cli/plugins/cortex/manager.py +189 -0
- snowflake/cli/plugins/cortex/plugin_spec.py +30 -0
- snowflake/cli/plugins/cortex/types.py +22 -0
- snowflake/cli/plugins/git/__init__.py +13 -0
- snowflake/cli/plugins/git/commands.py +354 -0
- snowflake/cli/plugins/git/manager.py +105 -0
- snowflake/cli/plugins/git/plugin_spec.py +30 -0
- snowflake/cli/plugins/init/__init__.py +13 -0
- snowflake/cli/plugins/init/commands.py +248 -0
- snowflake/cli/plugins/init/plugin_spec.py +30 -0
- snowflake/cli/plugins/nativeapp/__init__.py +13 -0
- snowflake/cli/plugins/nativeapp/artifacts.py +742 -0
- snowflake/cli/plugins/nativeapp/codegen/__init__.py +13 -0
- snowflake/cli/plugins/nativeapp/codegen/artifact_processor.py +91 -0
- snowflake/cli/plugins/nativeapp/codegen/compiler.py +130 -0
- snowflake/cli/plugins/nativeapp/codegen/sandbox.py +306 -0
- snowflake/cli/plugins/nativeapp/codegen/setup/native_app_setup_processor.py +172 -0
- snowflake/cli/plugins/nativeapp/codegen/setup/setup_driver.py.source +56 -0
- snowflake/cli/plugins/nativeapp/codegen/snowpark/callback_source.py.jinja +181 -0
- snowflake/cli/plugins/nativeapp/codegen/snowpark/extension_function_utils.py +217 -0
- snowflake/cli/plugins/nativeapp/codegen/snowpark/models.py +61 -0
- snowflake/cli/plugins/nativeapp/codegen/snowpark/python_processor.py +528 -0
- snowflake/cli/plugins/nativeapp/commands.py +439 -0
- snowflake/cli/plugins/nativeapp/common_flags.py +44 -0
- snowflake/cli/plugins/nativeapp/constants.py +27 -0
- snowflake/cli/plugins/nativeapp/exceptions.py +122 -0
- snowflake/cli/plugins/nativeapp/feature_flags.py +24 -0
- snowflake/cli/plugins/nativeapp/init.py +345 -0
- snowflake/cli/plugins/nativeapp/manager.py +823 -0
- snowflake/cli/plugins/nativeapp/plugin_spec.py +30 -0
- snowflake/cli/plugins/nativeapp/policy.py +50 -0
- snowflake/cli/plugins/nativeapp/project_model.py +195 -0
- snowflake/cli/plugins/nativeapp/run_processor.py +389 -0
- snowflake/cli/plugins/nativeapp/teardown_processor.py +301 -0
- snowflake/cli/plugins/nativeapp/utils.py +98 -0
- snowflake/cli/plugins/nativeapp/v2_conversions/v2_to_v1_decorator.py +135 -0
- snowflake/cli/plugins/nativeapp/version/__init__.py +13 -0
- snowflake/cli/plugins/nativeapp/version/commands.py +170 -0
- snowflake/cli/plugins/nativeapp/version/version_processor.py +362 -0
- snowflake/cli/plugins/notebook/__init__.py +13 -0
- snowflake/cli/plugins/notebook/commands.py +85 -0
- snowflake/cli/plugins/notebook/exceptions.py +20 -0
- snowflake/cli/plugins/notebook/manager.py +71 -0
- snowflake/cli/plugins/notebook/plugin_spec.py +30 -0
- snowflake/cli/plugins/notebook/types.py +15 -0
- snowflake/cli/plugins/object/__init__.py +13 -0
- snowflake/cli/plugins/object/command_aliases.py +95 -0
- snowflake/cli/plugins/object/commands.py +181 -0
- snowflake/cli/plugins/object/common.py +85 -0
- snowflake/cli/plugins/object/manager.py +97 -0
- snowflake/cli/plugins/object/plugin_spec.py +30 -0
- snowflake/cli/plugins/object_stage_deprecated/__init__.py +15 -0
- snowflake/cli/plugins/object_stage_deprecated/commands.py +122 -0
- snowflake/cli/plugins/object_stage_deprecated/plugin_spec.py +32 -0
- snowflake/cli/plugins/snowpark/__init__.py +13 -0
- snowflake/cli/plugins/snowpark/commands.py +546 -0
- snowflake/cli/plugins/snowpark/common.py +307 -0
- snowflake/cli/plugins/snowpark/manager.py +109 -0
- snowflake/cli/plugins/snowpark/models.py +157 -0
- snowflake/cli/plugins/snowpark/package/__init__.py +13 -0
- snowflake/cli/plugins/snowpark/package/anaconda_packages.py +233 -0
- snowflake/cli/plugins/snowpark/package/commands.py +256 -0
- snowflake/cli/plugins/snowpark/package/manager.py +44 -0
- snowflake/cli/plugins/snowpark/package/utils.py +26 -0
- snowflake/cli/plugins/snowpark/package_utils.py +354 -0
- snowflake/cli/plugins/snowpark/plugin_spec.py +30 -0
- snowflake/cli/plugins/snowpark/snowpark_package_paths.py +65 -0
- snowflake/cli/plugins/snowpark/snowpark_shared.py +95 -0
- snowflake/cli/plugins/snowpark/zipper.py +81 -0
- snowflake/cli/plugins/spcs/__init__.py +35 -0
- snowflake/cli/plugins/spcs/common.py +99 -0
- snowflake/cli/plugins/spcs/compute_pool/__init__.py +13 -0
- snowflake/cli/plugins/spcs/compute_pool/commands.py +241 -0
- snowflake/cli/plugins/spcs/compute_pool/manager.py +121 -0
- snowflake/cli/plugins/spcs/image_registry/__init__.py +13 -0
- snowflake/cli/plugins/spcs/image_registry/commands.py +65 -0
- snowflake/cli/plugins/spcs/image_registry/manager.py +105 -0
- snowflake/cli/plugins/spcs/image_repository/__init__.py +13 -0
- snowflake/cli/plugins/spcs/image_repository/commands.py +202 -0
- snowflake/cli/plugins/spcs/image_repository/manager.py +84 -0
- snowflake/cli/plugins/spcs/jobs/__init__.py +13 -0
- snowflake/cli/plugins/spcs/jobs/commands.py +78 -0
- snowflake/cli/plugins/spcs/jobs/manager.py +53 -0
- snowflake/cli/plugins/spcs/plugin_spec.py +30 -0
- snowflake/cli/plugins/spcs/services/__init__.py +13 -0
- snowflake/cli/plugins/spcs/services/commands.py +312 -0
- snowflake/cli/plugins/spcs/services/manager.py +170 -0
- snowflake/cli/plugins/sql/__init__.py +13 -0
- snowflake/cli/plugins/sql/commands.py +83 -0
- snowflake/cli/plugins/sql/manager.py +92 -0
- snowflake/cli/plugins/sql/plugin_spec.py +30 -0
- snowflake/cli/plugins/sql/snowsql_templating.py +28 -0
- snowflake/cli/plugins/stage/__init__.py +13 -0
- snowflake/cli/plugins/stage/commands.py +263 -0
- snowflake/cli/plugins/stage/diff.py +326 -0
- snowflake/cli/plugins/stage/manager.py +577 -0
- snowflake/cli/plugins/stage/md5.py +160 -0
- snowflake/cli/plugins/stage/plugin_spec.py +30 -0
- snowflake/cli/plugins/streamlit/__init__.py +13 -0
- snowflake/cli/plugins/streamlit/commands.py +179 -0
- snowflake/cli/plugins/streamlit/manager.py +222 -0
- snowflake/cli/plugins/streamlit/plugin_spec.py +30 -0
- snowflake/cli/plugins/workspace/__init__.py +13 -0
- snowflake/cli/plugins/workspace/commands.py +35 -0
- snowflake/cli/plugins/workspace/plugin_spec.py +30 -0
- snowflake/cli/templates/default_snowpark/.gitignore +4 -0
- snowflake/cli/templates/default_snowpark/app/__init__.py +0 -0
- snowflake/cli/templates/default_snowpark/app/common.py +2 -0
- snowflake/cli/templates/default_snowpark/app/functions.py +15 -0
- snowflake/cli/templates/default_snowpark/app/procedures.py +22 -0
- snowflake/cli/templates/default_snowpark/requirements.txt +1 -0
- snowflake/cli/templates/default_snowpark/snowflake.yml +23 -0
- snowflake/cli/templates/default_streamlit/.gitignore +4 -0
- snowflake/cli/templates/default_streamlit/common/hello.py +2 -0
- snowflake/cli/templates/default_streamlit/environment.yml +6 -0
- snowflake/cli/templates/default_streamlit/pages/my_page.py +3 -0
- snowflake/cli/templates/default_streamlit/snowflake.yml +10 -0
- snowflake/cli/templates/default_streamlit/streamlit_app.py +4 -0
- snowflake_cli-2.8.2.dist-info/METADATA +325 -0
- snowflake_cli-2.8.2.dist-info/RECORD +240 -0
- snowflake_cli-2.8.2.dist-info/WHEEL +4 -0
- snowflake_cli-2.8.2.dist-info/entry_points.txt +2 -0
- snowflake_cli-2.8.2.dist-info/licenses/LICENSE +201 -0
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
# Copyright (c) 2024 Snowflake Inc.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from dataclasses import dataclass
|
|
18
|
+
from typing import Dict, Optional, Union
|
|
19
|
+
|
|
20
|
+
from packaging.version import Version
|
|
21
|
+
from pydantic import Field, ValidationError, field_validator, model_validator
|
|
22
|
+
from snowflake.cli.api.feature_flags import FeatureFlag
|
|
23
|
+
from snowflake.cli.api.project.errors import SchemaValidationError
|
|
24
|
+
from snowflake.cli.api.project.schemas.entities.application_entity import (
|
|
25
|
+
ApplicationEntity,
|
|
26
|
+
)
|
|
27
|
+
from snowflake.cli.api.project.schemas.entities.common import (
|
|
28
|
+
DefaultsField,
|
|
29
|
+
TargetField,
|
|
30
|
+
)
|
|
31
|
+
from snowflake.cli.api.project.schemas.entities.entities import (
|
|
32
|
+
Entity,
|
|
33
|
+
v2_entity_types_map,
|
|
34
|
+
)
|
|
35
|
+
from snowflake.cli.api.project.schemas.native_app.native_app import (
|
|
36
|
+
NativeApp,
|
|
37
|
+
NativeAppV11,
|
|
38
|
+
)
|
|
39
|
+
from snowflake.cli.api.project.schemas.snowpark.snowpark import Snowpark
|
|
40
|
+
from snowflake.cli.api.project.schemas.streamlit.streamlit import Streamlit
|
|
41
|
+
from snowflake.cli.api.project.schemas.updatable_model import UpdatableModel
|
|
42
|
+
from snowflake.cli.api.utils.types import Context
|
|
43
|
+
from typing_extensions import Annotated
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@dataclass
|
|
47
|
+
class ProjectProperties:
|
|
48
|
+
"""
|
|
49
|
+
This class stores 2 objects representing the snowflake project:
|
|
50
|
+
|
|
51
|
+
The project_context object:
|
|
52
|
+
- Used as the context for templating when users reference variables in the project definition file.
|
|
53
|
+
|
|
54
|
+
The project_definition object:
|
|
55
|
+
- This is a transformed object type through Pydantic, which has been normalized.
|
|
56
|
+
- This object could have slightly different structure than what the users see in their yaml project definition files.
|
|
57
|
+
- This should be used for the business logic of snow CLI modules.
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
project_definition: ProjectDefinition
|
|
61
|
+
project_context: Context
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class _ProjectDefinitionBase(UpdatableModel):
|
|
65
|
+
def __init__(self, *args, **kwargs):
|
|
66
|
+
try:
|
|
67
|
+
super().__init__(**kwargs)
|
|
68
|
+
except ValidationError as e:
|
|
69
|
+
raise SchemaValidationError(e) from e
|
|
70
|
+
|
|
71
|
+
definition_version: Union[str, int] = Field(
|
|
72
|
+
title="Version of the project definition schema, which is currently 1",
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
@field_validator("definition_version")
|
|
76
|
+
@classmethod
|
|
77
|
+
def _is_supported_version(cls, version: str) -> str:
|
|
78
|
+
version = str(version)
|
|
79
|
+
version_map = get_version_map()
|
|
80
|
+
if version not in version_map:
|
|
81
|
+
raise ValueError(
|
|
82
|
+
f'Version {version} is not supported. Supported versions: {", ".join(version_map)}'
|
|
83
|
+
)
|
|
84
|
+
return version
|
|
85
|
+
|
|
86
|
+
def meets_version_requirement(self, required_version: str) -> bool:
|
|
87
|
+
return Version(self.definition_version) >= Version(required_version)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class DefinitionV10(_ProjectDefinitionBase):
|
|
91
|
+
native_app: Optional[NativeApp] = Field(
|
|
92
|
+
title="Native app definitions for the project", default=None
|
|
93
|
+
)
|
|
94
|
+
snowpark: Optional[Snowpark] = Field(
|
|
95
|
+
title="Snowpark functions and procedures definitions for the project",
|
|
96
|
+
default=None,
|
|
97
|
+
)
|
|
98
|
+
streamlit: Optional[Streamlit] = Field(
|
|
99
|
+
title="Streamlit definitions for the project", default=None
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class DefinitionV11(DefinitionV10):
|
|
104
|
+
native_app: Optional[NativeAppV11] = Field(
|
|
105
|
+
title="Native app definitions for the project", default=None
|
|
106
|
+
)
|
|
107
|
+
env: Optional[Dict[str, Union[str, int, bool]]] = Field(
|
|
108
|
+
title="Default environment specification for this project.",
|
|
109
|
+
default=None,
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class DefinitionV20(_ProjectDefinitionBase):
|
|
114
|
+
entities: Dict[str, Annotated[Entity, Field(discriminator="type")]] = Field(
|
|
115
|
+
title="Entity definitions."
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
@model_validator(mode="before")
|
|
119
|
+
@classmethod
|
|
120
|
+
def apply_defaults(cls, data: Dict) -> Dict:
|
|
121
|
+
"""
|
|
122
|
+
Applies default values that exist on the model but not specified in yml
|
|
123
|
+
"""
|
|
124
|
+
if "defaults" in data and "entities" in data:
|
|
125
|
+
for key, entity in data["entities"].items():
|
|
126
|
+
entity_type = entity["type"]
|
|
127
|
+
if entity_type not in v2_entity_types_map:
|
|
128
|
+
continue
|
|
129
|
+
entity_model = v2_entity_types_map[entity_type]
|
|
130
|
+
for default_key, default_value in data["defaults"].items():
|
|
131
|
+
if (
|
|
132
|
+
default_key in entity_model.model_fields
|
|
133
|
+
and default_key not in entity
|
|
134
|
+
):
|
|
135
|
+
entity[default_key] = default_value
|
|
136
|
+
return data
|
|
137
|
+
|
|
138
|
+
@field_validator("entities", mode="after")
|
|
139
|
+
@classmethod
|
|
140
|
+
def validate_entities(cls, entities: Dict[str, Entity]) -> Dict[str, Entity]:
|
|
141
|
+
for key, entity in entities.items():
|
|
142
|
+
# TODO Automatically detect TargetFields to validate
|
|
143
|
+
if entity.type == ApplicationEntity.get_type():
|
|
144
|
+
if isinstance(entity.from_, TargetField):
|
|
145
|
+
target_key = entity.from_.target
|
|
146
|
+
target_object = entity.from_
|
|
147
|
+
target_type = target_object.get_type()
|
|
148
|
+
cls._validate_target_field(target_key, target_type, entities)
|
|
149
|
+
return entities
|
|
150
|
+
|
|
151
|
+
@classmethod
|
|
152
|
+
def _validate_target_field(
|
|
153
|
+
cls, target_key: str, target_type: Entity, entities: Dict[str, Entity]
|
|
154
|
+
):
|
|
155
|
+
if target_key not in entities:
|
|
156
|
+
raise ValueError(f"No such target: {target_key}")
|
|
157
|
+
|
|
158
|
+
# Validate the target type
|
|
159
|
+
actual_target_type = entities[target_key].__class__
|
|
160
|
+
if target_type and target_type is not actual_target_type:
|
|
161
|
+
raise ValueError(
|
|
162
|
+
f"Target type mismatch. Expected {target_type.__name__}, got {actual_target_type.__name__}"
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
defaults: Optional[DefaultsField] = Field(
|
|
166
|
+
title="Default key/value entity values that are merged recursively for each entity.",
|
|
167
|
+
default=None,
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
env: Optional[Dict[str, Union[str, int, bool]]] = Field(
|
|
171
|
+
title="Default environment specification for this project.",
|
|
172
|
+
default=None,
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def build_project_definition(**data) -> ProjectDefinition:
|
|
177
|
+
"""
|
|
178
|
+
Returns a ProjectDefinition instance with a version matching the provided definition_version value
|
|
179
|
+
"""
|
|
180
|
+
if not isinstance(data, dict):
|
|
181
|
+
return
|
|
182
|
+
version = data.get("definition_version")
|
|
183
|
+
version_model = get_version_map().get(str(version))
|
|
184
|
+
if not version or not version_model:
|
|
185
|
+
# Raises a SchemaValidationError
|
|
186
|
+
_ProjectDefinitionBase(**data)
|
|
187
|
+
return version_model(**data)
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
ProjectDefinitionV1 = Union[DefinitionV10, DefinitionV11]
|
|
191
|
+
ProjectDefinitionV2 = DefinitionV20
|
|
192
|
+
ProjectDefinition = Union[ProjectDefinitionV1, ProjectDefinitionV2]
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def get_version_map():
|
|
196
|
+
version_map = {"1": DefinitionV10, "1.1": DefinitionV11}
|
|
197
|
+
if FeatureFlag.ENABLE_PROJECT_DEFINITION_V2.is_enabled():
|
|
198
|
+
version_map["2"] = DefinitionV20
|
|
199
|
+
return version_map
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Copyright (c) 2024 Snowflake Inc.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Copyright (c) 2024 Snowflake Inc.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from typing import Optional
|
|
18
|
+
|
|
19
|
+
from pydantic import Field
|
|
20
|
+
from snowflake.cli.api.project.schemas.updatable_model import UpdatableModel
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class Argument(UpdatableModel):
|
|
24
|
+
name: str = Field(title="Name of the argument")
|
|
25
|
+
arg_type: str = Field(
|
|
26
|
+
title="Type of the argument", alias="type"
|
|
27
|
+
) # TODO: consider introducing literal/enum here
|
|
28
|
+
default: Optional[str] = Field(title="Default value for an argument", default=None)
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# Copyright (c) 2024 Snowflake Inc.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from typing import Dict, List, Optional, Union
|
|
18
|
+
|
|
19
|
+
from pydantic import Field, field_validator
|
|
20
|
+
from snowflake.cli.api.project.schemas.identifier_model import ObjectIdentifierModel
|
|
21
|
+
from snowflake.cli.api.project.schemas.snowpark.argument import Argument
|
|
22
|
+
from snowflake.cli.api.project.schemas.updatable_model import UpdatableModel
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class _CallableBase(UpdatableModel):
|
|
26
|
+
handler: str = Field(
|
|
27
|
+
title="Function’s or procedure’s implementation of the object inside source module",
|
|
28
|
+
examples=["functions.hello_function"],
|
|
29
|
+
)
|
|
30
|
+
returns: str = Field(
|
|
31
|
+
title="Type of the result"
|
|
32
|
+
) # TODO: again, consider Literal/Enum
|
|
33
|
+
signature: Union[str, List[Argument]] = Field(
|
|
34
|
+
title="The signature parameter describes consecutive arguments passed to the object"
|
|
35
|
+
)
|
|
36
|
+
runtime: Optional[Union[str, float]] = Field(
|
|
37
|
+
title="Python version to use when executing ", default=None
|
|
38
|
+
)
|
|
39
|
+
external_access_integrations: Optional[List[str]] = Field(
|
|
40
|
+
title="Names of external access integrations needed for this procedure’s handler code to access external networks",
|
|
41
|
+
default=[],
|
|
42
|
+
)
|
|
43
|
+
secrets: Optional[Dict[str, str]] = Field(
|
|
44
|
+
title="Assigns the names of secrets to variables so that you can use the variables to reference the secrets",
|
|
45
|
+
default={},
|
|
46
|
+
)
|
|
47
|
+
imports: Optional[List[str]] = Field(
|
|
48
|
+
title="Stage and path to previously uploaded files you want to import",
|
|
49
|
+
default=[],
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
@field_validator("runtime")
|
|
53
|
+
@classmethod
|
|
54
|
+
def convert_runtime(cls, runtime_input: Union[str, float]) -> str:
|
|
55
|
+
if isinstance(runtime_input, float):
|
|
56
|
+
return str(runtime_input)
|
|
57
|
+
return runtime_input
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class FunctionSchema(_CallableBase, ObjectIdentifierModel(object_name="function")): # type: ignore
|
|
61
|
+
pass
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class ProcedureSchema(_CallableBase, ObjectIdentifierModel(object_name="procedure")): # type: ignore
|
|
65
|
+
execute_as_caller: Optional[bool] = Field(
|
|
66
|
+
title="Determine whether the procedure is executed with the privileges of "
|
|
67
|
+
"the owner (you) or with the privileges of the caller",
|
|
68
|
+
default=False,
|
|
69
|
+
)
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Copyright (c) 2024 Snowflake Inc.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from typing import List, Optional
|
|
18
|
+
|
|
19
|
+
from pydantic import Field
|
|
20
|
+
from snowflake.cli.api.project.schemas.snowpark.callable import (
|
|
21
|
+
FunctionSchema,
|
|
22
|
+
ProcedureSchema,
|
|
23
|
+
)
|
|
24
|
+
from snowflake.cli.api.project.schemas.updatable_model import UpdatableModel
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class Snowpark(UpdatableModel):
|
|
28
|
+
project_name: str = Field(title="Project identifier")
|
|
29
|
+
stage_name: str = Field(title="Stage in which project’s artifacts will be stored")
|
|
30
|
+
src: str = Field(title="Folder where your code should be located")
|
|
31
|
+
functions: Optional[List[FunctionSchema]] = Field(
|
|
32
|
+
title="List of functions defined in the project", default=[]
|
|
33
|
+
)
|
|
34
|
+
procedures: Optional[List[ProcedureSchema]] = Field(
|
|
35
|
+
title="List of procedures defined in the project", default=[]
|
|
36
|
+
)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Copyright (c) 2024 Snowflake Inc.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Copyright (c) 2024 Snowflake Inc.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from typing import List, Optional
|
|
19
|
+
|
|
20
|
+
from pydantic import Field
|
|
21
|
+
from snowflake.cli.api.project.schemas.identifier_model import ObjectIdentifierModel
|
|
22
|
+
from snowflake.cli.api.project.schemas.updatable_model import UpdatableModel
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class Streamlit(UpdatableModel, ObjectIdentifierModel(object_name="Streamlit")): # type: ignore
|
|
26
|
+
stage: Optional[str] = Field(
|
|
27
|
+
title="Stage in which the app’s artifacts will be stored", default="streamlit"
|
|
28
|
+
)
|
|
29
|
+
query_warehouse: str = Field(
|
|
30
|
+
title="Snowflake warehouse to host the app", default="streamlit"
|
|
31
|
+
)
|
|
32
|
+
main_file: Optional[Path] = Field(
|
|
33
|
+
title="Entrypoint file of the Streamlit app", default="streamlit_app.py"
|
|
34
|
+
)
|
|
35
|
+
env_file: Optional[Path] = Field(
|
|
36
|
+
title="File defining additional configurations for the app, such as external dependencies",
|
|
37
|
+
default=None,
|
|
38
|
+
)
|
|
39
|
+
pages_dir: Optional[Path] = Field(title="Streamlit pages", default=None)
|
|
40
|
+
additional_source_files: Optional[List[Path]] = Field(
|
|
41
|
+
title="List of additional files which should be included into deployment artifacts",
|
|
42
|
+
default=None,
|
|
43
|
+
)
|
|
44
|
+
title: Optional[str] = Field(
|
|
45
|
+
title="Human-readable title for the Streamlit dashboard", default=None
|
|
46
|
+
)
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Copyright (c) 2024 Snowflake Inc.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from typing import Any, List, Literal, Optional, Union
|
|
18
|
+
|
|
19
|
+
import typer
|
|
20
|
+
from click import ClickException
|
|
21
|
+
from pydantic import BaseModel, Field
|
|
22
|
+
from snowflake.cli.api.exceptions import InvalidTemplate
|
|
23
|
+
from snowflake.cli.api.secure_path import SecurePath
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class TemplateVariable(BaseModel):
|
|
27
|
+
name: str = Field(..., title="Variable identifier")
|
|
28
|
+
type: Optional[Literal["string", "float", "int"]] = Field( # noqa: A003
|
|
29
|
+
title="Type of the variable", default=None
|
|
30
|
+
)
|
|
31
|
+
prompt: Optional[str] = Field(title="Prompt message for the variable", default=None)
|
|
32
|
+
default: Optional[Any] = Field(title="Default value of the variable", default=None)
|
|
33
|
+
|
|
34
|
+
@property
|
|
35
|
+
def python_type(self):
|
|
36
|
+
# override "unchecked type" (None) with 'str', as Typer deduces type from the value of 'default'
|
|
37
|
+
return {
|
|
38
|
+
"string": str,
|
|
39
|
+
"float": float,
|
|
40
|
+
"int": int,
|
|
41
|
+
None: str,
|
|
42
|
+
}[self.type]
|
|
43
|
+
|
|
44
|
+
def prompt_user_for_value(self, no_interactive: bool) -> Union[str, float, int]:
|
|
45
|
+
if no_interactive:
|
|
46
|
+
if not self.default:
|
|
47
|
+
raise ClickException(f"Cannot determine value of variable {self.name}")
|
|
48
|
+
return self.default
|
|
49
|
+
|
|
50
|
+
prompt = self.prompt if self.prompt else self.name
|
|
51
|
+
return typer.prompt(prompt, default=self.default, type=self.python_type)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class Template(BaseModel):
|
|
55
|
+
minimum_cli_version: Optional[str] = Field(
|
|
56
|
+
None, title="Minimum version of Snowflake CLI supporting this template"
|
|
57
|
+
)
|
|
58
|
+
files_to_render: List[str] = Field(title="List of files to be rendered", default=[])
|
|
59
|
+
variables: List[TemplateVariable] = Field(
|
|
60
|
+
title="List of variables to be rendered", default=[]
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
def __init__(self, template_root: SecurePath, **kwargs):
|
|
64
|
+
super().__init__(**kwargs)
|
|
65
|
+
self._validate_files_exist(template_root)
|
|
66
|
+
|
|
67
|
+
def _validate_files_exist(self, template_root: SecurePath) -> None:
|
|
68
|
+
for path_in_template in self.files_to_render:
|
|
69
|
+
full_path = template_root / path_in_template
|
|
70
|
+
if not full_path.exists():
|
|
71
|
+
raise InvalidTemplate(
|
|
72
|
+
f"[files_to_render] contains not-existing file: {path_in_template}"
|
|
73
|
+
)
|
|
74
|
+
if full_path.is_dir():
|
|
75
|
+
raise InvalidTemplate(
|
|
76
|
+
f"[files_to_render] contains a dictionary: {path_in_template}"
|
|
77
|
+
)
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
# Copyright (c) 2024 Snowflake Inc.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from contextlib import contextmanager
|
|
18
|
+
from contextvars import ContextVar
|
|
19
|
+
from typing import Any, Dict, Iterator, Optional
|
|
20
|
+
|
|
21
|
+
from pydantic import (
|
|
22
|
+
BaseModel,
|
|
23
|
+
ConfigDict,
|
|
24
|
+
Field,
|
|
25
|
+
ValidationInfo,
|
|
26
|
+
field_validator,
|
|
27
|
+
)
|
|
28
|
+
from pydantic.fields import FieldInfo
|
|
29
|
+
from snowflake.cli.api.project.util import IDENTIFIER_NO_LENGTH
|
|
30
|
+
|
|
31
|
+
PROJECT_TEMPLATE_START = "<%"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _is_templated(info: ValidationInfo, value: Any) -> bool:
|
|
35
|
+
return (
|
|
36
|
+
info.context
|
|
37
|
+
and info.context.get("skip_validation_on_templates", False)
|
|
38
|
+
and isinstance(value, str)
|
|
39
|
+
and PROJECT_TEMPLATE_START in value
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
_initial_context: ContextVar[Optional[Dict[str, Any]]] = ContextVar(
|
|
44
|
+
"_init_context_var", default=None
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@contextmanager
|
|
49
|
+
def context(value: Dict[str, Any]) -> Iterator[None]:
|
|
50
|
+
"""
|
|
51
|
+
Thread safe context for Pydantic.
|
|
52
|
+
By using `with context()`, you ensure context changes apply
|
|
53
|
+
to the with block only
|
|
54
|
+
"""
|
|
55
|
+
token = _initial_context.set(value)
|
|
56
|
+
try:
|
|
57
|
+
yield
|
|
58
|
+
finally:
|
|
59
|
+
_initial_context.reset(token)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class UpdatableModel(BaseModel):
|
|
63
|
+
model_config = ConfigDict(validate_assignment=True, extra="forbid")
|
|
64
|
+
|
|
65
|
+
def __init__(self, /, **data: Any) -> None:
|
|
66
|
+
"""
|
|
67
|
+
Pydantic provides 2 options to pass in context:
|
|
68
|
+
1) Through `model_validate()` as a second argument.
|
|
69
|
+
2) Through a custom init method and the use of ContextVar
|
|
70
|
+
|
|
71
|
+
We decided not to use 1) because it silently stops working
|
|
72
|
+
if someone adds a pass through __init__ to any of the Pydantic models.
|
|
73
|
+
|
|
74
|
+
We decided to go with 2) as the safer approach.
|
|
75
|
+
Calling validate_python() in the __init__ is how we can pass context
|
|
76
|
+
on initialization according to Pydantic's documentation:
|
|
77
|
+
https://docs.pydantic.dev/latest/concepts/validators/#using-validation-context-with-basemodel-initialization
|
|
78
|
+
"""
|
|
79
|
+
self.__pydantic_validator__.validate_python(
|
|
80
|
+
data,
|
|
81
|
+
self_instance=self,
|
|
82
|
+
context=_initial_context.get(),
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
@classmethod
|
|
86
|
+
def _is_entity_type_field(cls, field: Any) -> bool:
|
|
87
|
+
"""
|
|
88
|
+
Checks if a field is of type `DiscriminatorField`
|
|
89
|
+
"""
|
|
90
|
+
if not isinstance(field, FieldInfo) or not field.json_schema_extra:
|
|
91
|
+
return False
|
|
92
|
+
|
|
93
|
+
return (
|
|
94
|
+
"is_discriminator_field" in field.json_schema_extra
|
|
95
|
+
and field.json_schema_extra["is_discriminator_field"]
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
@classmethod
|
|
99
|
+
def __init_subclass__(cls, **kwargs):
|
|
100
|
+
"""
|
|
101
|
+
This method will collect all the Pydantic annotations for the class
|
|
102
|
+
currently being initialized (any subclass of `UpdatableModel`).
|
|
103
|
+
|
|
104
|
+
It will add a field validator wrapper for every Pydantic field
|
|
105
|
+
in order to skip validation when templates are found.
|
|
106
|
+
|
|
107
|
+
It will apply this to all Pydantic fields, except for fields
|
|
108
|
+
marked as `DiscriminatorField`. These will be skipped because
|
|
109
|
+
Pydantic does not support validators for discriminator field types.
|
|
110
|
+
"""
|
|
111
|
+
|
|
112
|
+
super().__init_subclass__(**kwargs)
|
|
113
|
+
|
|
114
|
+
field_annotations = {}
|
|
115
|
+
field_values = {}
|
|
116
|
+
# Go through the inheritance classes and collect all the annotations and
|
|
117
|
+
# all the values of the class attributes. We go in reverse order so that
|
|
118
|
+
# values in subclasses overrides values from parent classes in case of field overrides.
|
|
119
|
+
|
|
120
|
+
for class_ in reversed(cls.__mro__):
|
|
121
|
+
class_dict = class_.__dict__
|
|
122
|
+
field_annotations.update(class_dict.get("__annotations__", {}))
|
|
123
|
+
|
|
124
|
+
if "model_fields" in class_dict:
|
|
125
|
+
# This means the class dict has already been processed by Pydantic
|
|
126
|
+
# All fields should properly be populated in model_fields
|
|
127
|
+
field_values.update(class_dict["model_fields"])
|
|
128
|
+
else:
|
|
129
|
+
# If Pydantic did not process this class yet, get the values from class_dict directly
|
|
130
|
+
field_values.update(class_dict)
|
|
131
|
+
|
|
132
|
+
# Add Pydantic validation wrapper around all fields except `DiscriminatorField`s
|
|
133
|
+
for field_name in field_annotations:
|
|
134
|
+
if not cls._is_entity_type_field(field_values.get(field_name)):
|
|
135
|
+
cls._add_validator(field_name)
|
|
136
|
+
|
|
137
|
+
@classmethod
|
|
138
|
+
def _add_validator(cls, field_name: str):
|
|
139
|
+
"""
|
|
140
|
+
Adds a Pydantic validator with mode=wrap for the provided `field_name`.
|
|
141
|
+
During validation, this will check if the field is templated (not expanded yet)
|
|
142
|
+
and in that case, it will skip all the remaining Pydantic validation on that field.
|
|
143
|
+
|
|
144
|
+
Since this validator is added last, it will skip all the other field validators
|
|
145
|
+
defined in the subclasses when templates are found.
|
|
146
|
+
|
|
147
|
+
This logic on templates only applies when context contains `skip_validation_on_templates` flag.
|
|
148
|
+
"""
|
|
149
|
+
|
|
150
|
+
def validator_skipping_templated_str(cls, value, handler, info: ValidationInfo):
|
|
151
|
+
if _is_templated(info, value):
|
|
152
|
+
return value
|
|
153
|
+
return handler(value)
|
|
154
|
+
|
|
155
|
+
setattr(
|
|
156
|
+
cls,
|
|
157
|
+
f"_field_validator_with_verbose_name_to_avoid_name_conflict_{field_name}",
|
|
158
|
+
field_validator(field_name, mode="wrap")(validator_skipping_templated_str),
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
def update_from_dict(self, update_values: Dict[str, Any]):
|
|
162
|
+
"""
|
|
163
|
+
Takes a dictionary with values to override.
|
|
164
|
+
If the field type is subclass of a UpdatableModel, its update_from_dict() method is called with
|
|
165
|
+
the value to be set.
|
|
166
|
+
If not, we use simple setattr to set new value.
|
|
167
|
+
Values provided are validated against original restrictions, so it's impossible to overwrite string field with
|
|
168
|
+
integer value etc.
|
|
169
|
+
"""
|
|
170
|
+
for field, value in update_values.items():
|
|
171
|
+
if field in self.model_fields.keys():
|
|
172
|
+
if (
|
|
173
|
+
hasattr(getattr(self, field), "update_from_dict")
|
|
174
|
+
and field in self.model_fields_set
|
|
175
|
+
):
|
|
176
|
+
getattr(self, field).update_from_dict(value)
|
|
177
|
+
else:
|
|
178
|
+
setattr(self, field, value)
|
|
179
|
+
return self
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def DiscriminatorField(*args, **kwargs): # noqa N802
|
|
183
|
+
"""
|
|
184
|
+
Use this type for discriminator fields used for differentiating
|
|
185
|
+
between different entity types.
|
|
186
|
+
|
|
187
|
+
When this `DiscriminatorField` is used on a pydantic attribute,
|
|
188
|
+
we will not allow templating on it.
|
|
189
|
+
"""
|
|
190
|
+
return Field(is_discriminator_field=True, *args, **kwargs)
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def IdentifierField(*args, **kwargs): # noqa N802
|
|
194
|
+
return Field(max_length=254, pattern=IDENTIFIER_NO_LENGTH, *args, **kwargs)
|