-
Notifications
You must be signed in to change notification settings - Fork 54
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Switch from StrictYAML to Pydantic #870
Changes from 9 commits
74b1a38
6f8930e
67dd5f5
1ab9001
f46a54b
04459c8
a2c84c1
fd7a90e
eefe238
33b89ed
9216782
e0db3fd
bea3edc
a228360
8cbd9f4
54f641c
75d7467
876b4fc
dbb9b1d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -25,6 +25,7 @@ dependencies = [ | |
"typer==0.9.0", | ||
"urllib3>=1.21.1,<2.3", | ||
"GitPython==3.1.42", | ||
"pydantic==2.6.3" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we remove need for strictyml? Or should it be done in separate PR? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Un-done. Left pydantic for some uses not directly related to project definitions |
||
] | ||
classifiers = [ | ||
"Development Status :: 3 - Alpha", | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,6 +7,7 @@ | |
|
||
from snowflake.cli.api.exceptions import MissingConfiguration | ||
from snowflake.cli.api.project.definition import load_project_definition | ||
from snowflake.cli.api.project.schemas.project_definition import ProjectDefinition | ||
|
||
|
||
def _compat_is_mount(path: Path): | ||
|
@@ -100,5 +101,5 @@ def _user_definition_file_if_available(project_path: Path) -> Optional[Path]: | |
) | ||
|
||
@functools.cached_property | ||
def project_definition(self) -> dict: | ||
def project_definition(self) -> ProjectDefinition: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Love the type safety Pydantic gives us! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Me too :) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. And we could use |
||
return load_project_definition(self._project_config_paths) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
from textwrap import dedent | ||
|
||
from pydantic import ValidationError | ||
|
||
|
||
class SchemaValidationError(Exception): | ||
generic_message = "For field {loc} you provided '{loc}'. This caused: {msg}" | ||
message_templates = { | ||
"string_type": "{msg} for field '{loc}', you provided '{input}'", | ||
"extra_forbidden": "{msg}. You provided field '{loc}' with value '{input}' that is not present in the schema", | ||
"missing": "Your project definition is missing following fields: {loc}", | ||
} | ||
|
||
def __init__(self, error: ValidationError): | ||
errors = error.errors() | ||
message = f"During evaluation of {error.title} schema following errors were encoutered:\n" | ||
message += "\n".join( | ||
[ | ||
self.message_templates.get(e["type"], self.generic_message).format(**e) | ||
for e in errors | ||
] | ||
) | ||
|
||
super().__init__(dedent(message)) |
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
from __future__ import annotations | ||
|
||
from typing import Literal, Optional | ||
|
||
from pydantic import Field | ||
from snowflake.cli.api.project.schemas.updatable_model import ( | ||
IdentifierField, | ||
UpdatableModel, | ||
) | ||
|
||
|
||
class Application(UpdatableModel): | ||
role: Optional[str] = Field( | ||
title="Role to use when creating the application instance and consumer-side objects", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: "application object" instead of "application instance" There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||
default=None, | ||
) | ||
name: Optional[str] = Field( | ||
title="Name of the application created when you run the snow app run command", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. "... application object created ..." There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||
default=None, | ||
) | ||
warehouse: Optional[str] = IdentifierField( | ||
title="Name of the application created when you run the snow app run command", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ditto, and for line 26 as well. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||
default=None, | ||
) | ||
debug: Optional[bool] = Field( | ||
title="Whether to enable debug mode when using a named stage to create an application", | ||
default=True, | ||
) | ||
|
||
|
||
DistributionOptions = Literal["internal", "external", "INTERNAL", "EXTERNAL"] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
from __future__ import annotations | ||
|
||
import re | ||
from typing import List, Optional, Union | ||
|
||
from pydantic import Field, field_validator | ||
from snowflake.cli.api.project.schemas.native_app.application import Application | ||
from snowflake.cli.api.project.schemas.native_app.package import Package | ||
from snowflake.cli.api.project.schemas.native_app.path_mapping import PathMapping | ||
from snowflake.cli.api.project.schemas.updatable_model import UpdatableModel | ||
from snowflake.cli.api.project.util import ( | ||
SCHEMA_AND_NAME, | ||
) | ||
|
||
|
||
class NativeApp(UpdatableModel): | ||
name: str = Field( | ||
title="Project identifier", | ||
) | ||
artifacts: List[Union[PathMapping, str]] = Field( | ||
title="List of file source and destination pairs to add to the deploy root", | ||
) | ||
deploy_root: Optional[str] = Field( | ||
title="Folder at the root of your project where the build step copies the artifacts.", | ||
default="output/deploy/", | ||
) | ||
source_stage: Optional[str] = Field( | ||
title="Identifier of the stage that stores the application artifacts.", | ||
default="app_src.stage", | ||
) | ||
package: Optional[Package] = Field(title="PackageSchema", default=None) | ||
application: Optional[Application] = Field(title="Application info", default=None) | ||
|
||
@field_validator("source_stage") | ||
@classmethod | ||
def validate_source_stage(cls, input_value: str): | ||
if not re.match(SCHEMA_AND_NAME, input_value): | ||
raise ValueError("Incorrect value for Native Apps source stage value") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe we can rephrase to: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. +1, I think latter one we can add something like There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||
return input_value |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
from __future__ import annotations | ||
|
||
from typing import List, Optional | ||
|
||
from pydantic import Field, field_validator | ||
from snowflake.cli.api.project.schemas.native_app.application import DistributionOptions | ||
from snowflake.cli.api.project.schemas.updatable_model import ( | ||
IdentifierField, | ||
UpdatableModel, | ||
) | ||
|
||
|
||
class Package(UpdatableModel): | ||
scripts: Optional[List[str]] = Field( | ||
title="List of SQL file paths relative to the project root", default=None | ||
) | ||
role: Optional[str] = IdentifierField( | ||
title="Role to use when creating the application package and provider-side objects", | ||
default=None, | ||
) | ||
name: Optional[str] = IdentifierField( | ||
title="Name of the application created when you run the snow app run command", # TODO: this description seems duplicated, is it ok? | ||
sfc-gh-jsikorski marked this conversation as resolved.
Show resolved
Hide resolved
|
||
default=None, | ||
) | ||
warehouse: Optional[str] = IdentifierField( | ||
title="Warehouse used to run the scripts", default=None | ||
) | ||
distribution: Optional[DistributionOptions] = Field( | ||
title="Distribution of the application package created by the Snowflake CLI", | ||
default="internal", | ||
) | ||
|
||
@field_validator("scripts") | ||
@classmethod | ||
def validate_scripts(cls, input_list): | ||
if len(input_list) != len(set(input_list)): | ||
raise ValueError( | ||
"Scripts field should contain unique values. Check the list for duplicates and try again" | ||
sfc-gh-jsikorski marked this conversation as resolved.
Show resolved
Hide resolved
|
||
) | ||
return input_list |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
from __future__ import annotations | ||
|
||
from typing import Optional | ||
|
||
from snowflake.cli.api.project.schemas.updatable_model import UpdatableModel | ||
|
||
|
||
class PathMapping(UpdatableModel): | ||
src: str | ||
dest: Optional[str] = None |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,23 +1,25 @@ | ||
from __future__ import annotations | ||
|
||
from snowflake.cli.api.project.schemas import ( | ||
native_app, | ||
snowpark, | ||
streamlit, | ||
) | ||
from snowflake.cli.api.project.schemas.relaxed_map import RelaxedMap | ||
from strictyaml import ( | ||
Int, | ||
Optional, | ||
) | ||
from typing import Optional | ||
|
||
project_schema = RelaxedMap( | ||
{ | ||
"definition_version": Int(), | ||
Optional("native_app"): native_app.native_app_schema, | ||
Optional("snowpark"): snowpark.snowpark_schema, | ||
Optional("streamlit"): streamlit.streamlit_schema, | ||
} | ||
) | ||
from pydantic import Field | ||
from snowflake.cli.api.project.schemas.native_app.native_app import NativeApp | ||
from snowflake.cli.api.project.schemas.snowpark.snowpark import Snowpark | ||
from snowflake.cli.api.project.schemas.streamlit.streamlit import Streamlit | ||
from snowflake.cli.api.project.schemas.updatable_model import UpdatableModel | ||
|
||
project_override_schema = project_schema.as_fully_optional() | ||
|
||
class ProjectDefinition(UpdatableModel): | ||
definition_version: int = Field( | ||
title="Version of the project definition schema, which is currently 1" | ||
) | ||
native_app: Optional[NativeApp] = Field( | ||
title="Native app definitions for the project", default=None | ||
) | ||
snowpark: Optional[Snowpark] = Field( | ||
title="Snowpark functions and procedures definitions for the project", | ||
default=None, | ||
) | ||
streamlit: Optional[Streamlit] = Field( | ||
title="Streamlit definitions for the project", default=None | ||
) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's add validator for There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. you mean There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Another way to think about this -- when we get to project definition v2 (design currently in progress) it will look completely different. We should have a way to separate out the definitions by There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok, so my proposition is to add There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree, let's have There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Duplicate line
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done