Skip to content

Commit

Permalink
Fix for mixins applied twice (#1779)
Browse files Browse the repository at this point in the history
* Solution

* notes

* fix

* fix

* fix

* fix

* fix
  • Loading branch information
sfc-gh-jsikorski authored and sfc-gh-turbaszek committed Oct 23, 2024
1 parent cb61b02 commit 5f72387
Show file tree
Hide file tree
Showing 4 changed files with 53 additions and 24 deletions.
1 change: 1 addition & 0 deletions RELEASE-NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
* `snow --info` callback returns information about `SNOWFLAKE_HOME` variable.
* Removed requirement of existence of any `requirements.txt` file for Python code execution via `snow git execute` command.
Before the fix the file (even empty) was required to make the execution working.
* Fix for list fields in mixins applied twice

# v3.0.2

Expand Down
33 changes: 19 additions & 14 deletions src/snowflake/cli/api/project/schemas/project_definition.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

from packaging.version import Version
from pydantic import Field, ValidationError, field_validator, model_validator
from pydantic_core.core_schema import ValidationInfo
from snowflake.cli._plugins.nativeapp.entities.application import ApplicationEntityModel
from snowflake.cli.api.project.errors import SchemaValidationError
from snowflake.cli.api.project.schemas.entities.common import (
Expand Down Expand Up @@ -175,26 +176,30 @@ def _validate_target_field(

@model_validator(mode="before")
@classmethod
def apply_mixins(cls, data: Dict) -> Dict:
def apply_mixins(cls, data: Dict, info: ValidationInfo) -> Dict:
"""
Applies mixins to those entities, whose meta field contains the mixin name.
"""
if "mixins" not in data or "entities" not in data:
return data

entities = data["entities"]
for entity_name, entity in entities.items():
entity_mixins = entity_mixins_to_list(
entity.get("meta", {}).get("use_mixins")
)
duplicated_run = (
info.context.get("is_duplicated_run", False) if info.context else False
)
if not duplicated_run:
entities = data["entities"]
for entity_name, entity in entities.items():
entity_mixins = entity_mixins_to_list(
entity.get("meta", {}).get("use_mixins")
)

merged_values = cls._merge_mixins_with_entity(
entity_id=entity_name,
entity=entity,
entity_mixins_names=entity_mixins,
mixin_defs=data["mixins"],
)
entities[entity_name] = merged_values
merged_values = cls._merge_mixins_with_entity(
entity_id=entity_name,
entity=entity,
entity_mixins_names=entity_mixins,
mixin_defs=data["mixins"],
)
entities[entity_name] = merged_values
return data

@classmethod
Expand Down Expand Up @@ -325,6 +330,6 @@ def get_allowed_fields_for_entity(entity: Dict[str, Any]) -> List[str]:
def _unique_extend(list_a: List, list_b: List) -> List:
new_list = list(list_a)
for item in list_b:
if item not in list_a:
if all(item != x for x in list_a):
new_list.append(item)
return new_list
8 changes: 3 additions & 5 deletions src/snowflake/cli/api/utils/definition_rendering.py
Original file line number Diff line number Diff line change
Expand Up @@ -285,9 +285,7 @@ def _add_defaults_to_definition(original_definition: Definition) -> Definition:
with context({"skip_validation_on_templates": True}):
# pass a flag to Pydantic to skip validation for templated scalars
# populate the defaults
project_definition = build_project_definition(
**copy.deepcopy(original_definition)
)
project_definition = build_project_definition(**original_definition)

definition_with_defaults = project_definition.model_dump(
exclude_none=True, warnings=False, by_alias=True
Expand Down Expand Up @@ -392,8 +390,8 @@ def on_cycle_action(node: Node[TemplateVar]):
definition,
update_action=lambda val: template_env.render(val, final_context),
)

project_definition = build_project_definition(**definition)
with context({"is_duplicated_run": True}):
project_definition = build_project_definition(**definition)

# Use the values originally provided by the user as the template context
# This intentionally doesn't reflect any field changes made by
Expand Down
35 changes: 30 additions & 5 deletions tests/project/test_project_definition_v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@
def test_project_definition_v2_schema(definition_input, expected_error):
definition_input["definition_version"] = "2"
try:
DefinitionV20(**definition_input)
_ = render_definition_template(definition_input, {})
except SchemaValidationError as err:
if expected_error:
if type(expected_error) == str:
Expand Down Expand Up @@ -469,7 +469,7 @@ def test_mixin_with_unknown_entity_key_raises_error():
"mixins": {"schema_mixin": {"unknown_key": "NA"}},
}
with pytest.raises(SchemaValidationError) as err:
DefinitionV20(**definition_input)
_ = render_definition_template(definition_input, {})

assert "Unsupported key 'unknown_key' for entity func of type function" in str(err)

Expand Down Expand Up @@ -565,7 +565,7 @@ def test_mixin_order_scalar():
},
},
}
pd = DefinitionV20(**definition_input)
pd = render_definition_template(definition_input, {}).project_definition
entities = pd.entities
assert entities["no_mixin"].stage == stage_from_entity
assert entities["mix1_only"].stage == "mix1"
Expand Down Expand Up @@ -620,7 +620,7 @@ def test_mixin_order_sequence_merge_order():
},
},
}
pd = DefinitionV20(**definition_input)
pd = render_definition_template(definition_input, {}).project_definition
entities = pd.entities
assert entities["no_mixin"].external_access_integrations == ["entity_int"]
assert entities["mix1_only"].external_access_integrations == ["mix1_int"]
Expand Down Expand Up @@ -688,7 +688,7 @@ def test_mixin_order_mapping_merge_order():
},
},
}
pd = DefinitionV20(**definition_input)
pd = render_definition_template(definition_input, {}).project_definition
entities = pd.entities
assert entities["no_mixin"].secrets == {
"entity_key": "entity_value",
Expand Down Expand Up @@ -742,3 +742,28 @@ def test_mixins_values_have_to_be_type_compatible_with_entities():
"Value from mixins for property identifier is of type 'dict' while entity func expects value of type 'str'"
in str(err)
)


def test_if_list_in_mixin_is_applied_correctly():
definition_input = {
"definition_version": "2",
"entities": {
"func": {
"identifier": "my_func",
"type": "function",
"handler": "foo",
"returns": "string",
"signature": "",
"stage": "bar",
"meta": {"use_mixins": ["artifact_mixin"]},
},
},
"mixins": {
"artifact_mixin": {
"external_access_integrations": ["integration_1", "integration_2"],
"artifacts": [{"src": "src", "dest": "my_project"}],
},
},
}
project = render_definition_template(definition_input, {}).project_definition
assert len(project.entities["func"].artifacts) == 1

0 comments on commit 5f72387

Please sign in to comment.