Skip to content

Commit 5f72387

Browse files
sfc-gh-jsikorskisfc-gh-turbaszek
authored andcommitted
Fix for mixins applied twice (#1779)
* Solution * notes * fix * fix * fix * fix * fix
1 parent cb61b02 commit 5f72387

File tree

4 files changed

+53
-24
lines changed

4 files changed

+53
-24
lines changed

RELEASE-NOTES.md

+1
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
* `snow --info` callback returns information about `SNOWFLAKE_HOME` variable.
3434
* Removed requirement of existence of any `requirements.txt` file for Python code execution via `snow git execute` command.
3535
Before the fix the file (even empty) was required to make the execution working.
36+
* Fix for list fields in mixins applied twice
3637

3738
# v3.0.2
3839

src/snowflake/cli/api/project/schemas/project_definition.py

+19-14
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
from packaging.version import Version
2121
from pydantic import Field, ValidationError, field_validator, model_validator
22+
from pydantic_core.core_schema import ValidationInfo
2223
from snowflake.cli._plugins.nativeapp.entities.application import ApplicationEntityModel
2324
from snowflake.cli.api.project.errors import SchemaValidationError
2425
from snowflake.cli.api.project.schemas.entities.common import (
@@ -175,26 +176,30 @@ def _validate_target_field(
175176

176177
@model_validator(mode="before")
177178
@classmethod
178-
def apply_mixins(cls, data: Dict) -> Dict:
179+
def apply_mixins(cls, data: Dict, info: ValidationInfo) -> Dict:
179180
"""
180181
Applies mixins to those entities, whose meta field contains the mixin name.
181182
"""
182183
if "mixins" not in data or "entities" not in data:
183184
return data
184185

185-
entities = data["entities"]
186-
for entity_name, entity in entities.items():
187-
entity_mixins = entity_mixins_to_list(
188-
entity.get("meta", {}).get("use_mixins")
189-
)
186+
duplicated_run = (
187+
info.context.get("is_duplicated_run", False) if info.context else False
188+
)
189+
if not duplicated_run:
190+
entities = data["entities"]
191+
for entity_name, entity in entities.items():
192+
entity_mixins = entity_mixins_to_list(
193+
entity.get("meta", {}).get("use_mixins")
194+
)
190195

191-
merged_values = cls._merge_mixins_with_entity(
192-
entity_id=entity_name,
193-
entity=entity,
194-
entity_mixins_names=entity_mixins,
195-
mixin_defs=data["mixins"],
196-
)
197-
entities[entity_name] = merged_values
196+
merged_values = cls._merge_mixins_with_entity(
197+
entity_id=entity_name,
198+
entity=entity,
199+
entity_mixins_names=entity_mixins,
200+
mixin_defs=data["mixins"],
201+
)
202+
entities[entity_name] = merged_values
198203
return data
199204

200205
@classmethod
@@ -325,6 +330,6 @@ def get_allowed_fields_for_entity(entity: Dict[str, Any]) -> List[str]:
325330
def _unique_extend(list_a: List, list_b: List) -> List:
326331
new_list = list(list_a)
327332
for item in list_b:
328-
if item not in list_a:
333+
if all(item != x for x in list_a):
329334
new_list.append(item)
330335
return new_list

src/snowflake/cli/api/utils/definition_rendering.py

+3-5
Original file line numberDiff line numberDiff line change
@@ -285,9 +285,7 @@ def _add_defaults_to_definition(original_definition: Definition) -> Definition:
285285
with context({"skip_validation_on_templates": True}):
286286
# pass a flag to Pydantic to skip validation for templated scalars
287287
# populate the defaults
288-
project_definition = build_project_definition(
289-
**copy.deepcopy(original_definition)
290-
)
288+
project_definition = build_project_definition(**original_definition)
291289

292290
definition_with_defaults = project_definition.model_dump(
293291
exclude_none=True, warnings=False, by_alias=True
@@ -392,8 +390,8 @@ def on_cycle_action(node: Node[TemplateVar]):
392390
definition,
393391
update_action=lambda val: template_env.render(val, final_context),
394392
)
395-
396-
project_definition = build_project_definition(**definition)
393+
with context({"is_duplicated_run": True}):
394+
project_definition = build_project_definition(**definition)
397395

398396
# Use the values originally provided by the user as the template context
399397
# This intentionally doesn't reflect any field changes made by

tests/project/test_project_definition_v2.py

+30-5
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,7 @@
256256
def test_project_definition_v2_schema(definition_input, expected_error):
257257
definition_input["definition_version"] = "2"
258258
try:
259-
DefinitionV20(**definition_input)
259+
_ = render_definition_template(definition_input, {})
260260
except SchemaValidationError as err:
261261
if expected_error:
262262
if type(expected_error) == str:
@@ -469,7 +469,7 @@ def test_mixin_with_unknown_entity_key_raises_error():
469469
"mixins": {"schema_mixin": {"unknown_key": "NA"}},
470470
}
471471
with pytest.raises(SchemaValidationError) as err:
472-
DefinitionV20(**definition_input)
472+
_ = render_definition_template(definition_input, {})
473473

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

@@ -565,7 +565,7 @@ def test_mixin_order_scalar():
565565
},
566566
},
567567
}
568-
pd = DefinitionV20(**definition_input)
568+
pd = render_definition_template(definition_input, {}).project_definition
569569
entities = pd.entities
570570
assert entities["no_mixin"].stage == stage_from_entity
571571
assert entities["mix1_only"].stage == "mix1"
@@ -620,7 +620,7 @@ def test_mixin_order_sequence_merge_order():
620620
},
621621
},
622622
}
623-
pd = DefinitionV20(**definition_input)
623+
pd = render_definition_template(definition_input, {}).project_definition
624624
entities = pd.entities
625625
assert entities["no_mixin"].external_access_integrations == ["entity_int"]
626626
assert entities["mix1_only"].external_access_integrations == ["mix1_int"]
@@ -688,7 +688,7 @@ def test_mixin_order_mapping_merge_order():
688688
},
689689
},
690690
}
691-
pd = DefinitionV20(**definition_input)
691+
pd = render_definition_template(definition_input, {}).project_definition
692692
entities = pd.entities
693693
assert entities["no_mixin"].secrets == {
694694
"entity_key": "entity_value",
@@ -742,3 +742,28 @@ def test_mixins_values_have_to_be_type_compatible_with_entities():
742742
"Value from mixins for property identifier is of type 'dict' while entity func expects value of type 'str'"
743743
in str(err)
744744
)
745+
746+
747+
def test_if_list_in_mixin_is_applied_correctly():
748+
definition_input = {
749+
"definition_version": "2",
750+
"entities": {
751+
"func": {
752+
"identifier": "my_func",
753+
"type": "function",
754+
"handler": "foo",
755+
"returns": "string",
756+
"signature": "",
757+
"stage": "bar",
758+
"meta": {"use_mixins": ["artifact_mixin"]},
759+
},
760+
},
761+
"mixins": {
762+
"artifact_mixin": {
763+
"external_access_integrations": ["integration_1", "integration_2"],
764+
"artifacts": [{"src": "src", "dest": "my_project"}],
765+
},
766+
},
767+
}
768+
project = render_definition_template(definition_input, {}).project_definition
769+
assert len(project.entities["func"].artifacts) == 1

0 commit comments

Comments
 (0)