forked from Avaiga/taipy-config
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
* feat: add _ConfigComparator * feat: add deepdiff as required package
- Loading branch information
Showing
5 changed files
with
190 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,6 +5,7 @@ name = "pypi" | |
|
||
[packages] | ||
toml = "==0.10" | ||
deepdiff = "==6.2.2" | ||
|
||
[dev-packages] | ||
black = "*" | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -20,6 +20,7 @@ | |
|
||
requirements = [ | ||
"toml>=0.10,<0.11", | ||
"deepdiff>=6.2,<6.3" | ||
] | ||
|
||
test_requirements = ["pytest>=3.8"] | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
# Copyright 2022 Avaiga Private Limited | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with | ||
# the License. You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on | ||
# an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the | ||
# specific language governing permissions and limitations under the License. | ||
|
||
import json | ||
import re | ||
from typing import Dict, List, Tuple | ||
|
||
from deepdiff import DeepDiff | ||
|
||
from ._base_serializer import _BaseSerializer | ||
from ._config import _Config | ||
from .config import Config | ||
|
||
|
||
class _ConfigComparator: | ||
@classmethod | ||
def _compare(cls, old_conf: _Config, new_conf: _Config) -> Dict[str, List[Tuple[Tuple[str]]]]: | ||
"""Compare between 2 _Config object to check for compatibility. | ||
Return a dictionary with the following format: | ||
{ | ||
"item_added": [ | ||
((entity_name_1, entity_id_1, attribute_1), added_object_1), | ||
((entity_name_2, entity_id_2, attribute_2), added_object_2), | ||
], | ||
"item_changed": [ | ||
((entity_name_1, entity_id_1, attribute_1), (old_value_1, new_value_1)), | ||
((entity_name_2, entity_id_2, attribute_2), (old_value_2, new_value_2)), | ||
], | ||
"item_removed": [ | ||
((entity_name_1, entity_id_1, attribute_1), removed_object_1), | ||
((entity_name_2, entity_id_2, attribute_2), removed_object_2), | ||
], | ||
} | ||
""" | ||
old_conf_json = json.loads(Config._to_json(old_conf)) # type: ignore | ||
new_conf_json = json.loads(Config._to_json(new_conf)) # type: ignore | ||
|
||
deepdiff_result = DeepDiff(old_conf_json, new_conf_json) | ||
|
||
config_diff: Dict[str, List] = { | ||
"added_items": [], | ||
"removed_items": [], | ||
"modified_items": [], | ||
} | ||
|
||
if dictionary_item_added := deepdiff_result.get("dictionary_item_added"): | ||
for item_added in dictionary_item_added: | ||
entity_name, entity_id, attribute = cls.__get_changed_entity_attribute(item_added) | ||
|
||
if attribute: | ||
value_added = new_conf_json[entity_name][entity_id][attribute] | ||
else: | ||
value_added = new_conf_json[entity_name][entity_id] | ||
|
||
entity_name = cls.__rename_global_node_name(entity_name) | ||
|
||
config_diff["added_items"].append(((entity_name, entity_id, attribute), (value_added))) | ||
|
||
if dictionary_item_removed := deepdiff_result.get("dictionary_item_removed"): | ||
for item_removed in dictionary_item_removed: | ||
entity_name, entity_id, attribute = cls.__get_changed_entity_attribute(item_removed) | ||
|
||
if attribute: | ||
value_removed = old_conf_json[entity_name][entity_id][attribute] | ||
else: | ||
value_removed = old_conf_json[entity_name][entity_id] | ||
|
||
entity_name = cls.__rename_global_node_name(entity_name) | ||
|
||
config_diff["removed_items"].append(((entity_name, entity_id, attribute), (value_removed))) | ||
|
||
if values_changed := deepdiff_result.get("values_changed"): | ||
for item_changed, value_changed in values_changed.items(): | ||
entity_name, entity_id, attribute = cls.__get_changed_entity_attribute(item_changed) | ||
entity_name = cls.__rename_global_node_name(entity_name) | ||
|
||
config_diff["modified_items"].append( | ||
((entity_name, entity_id, attribute), (value_changed["old_value"], value_changed["new_value"])) | ||
) | ||
|
||
# Sort by entity name | ||
config_diff["added_items"].sort(key=lambda x: x[0][0]) | ||
config_diff["removed_items"].sort(key=lambda x: x[0][0]) | ||
config_diff["modified_items"].sort(key=lambda x: x[0][0]) | ||
|
||
return config_diff | ||
|
||
@classmethod | ||
def __get_changed_entity_attribute(cls, attribute_bracket_notation): | ||
"""Split the entity name, entity id (if exists), and the attribute name from JSON bracket notation.""" | ||
try: | ||
entity_name, entity_id, attribute = re.findall(r"\[\'(.*?)\'\]", attribute_bracket_notation) | ||
except ValueError: | ||
entity_name, entity_id = re.findall(r"\[\'(.*?)\'\]", attribute_bracket_notation) | ||
attribute = None | ||
|
||
return entity_name, entity_id, attribute | ||
|
||
@classmethod | ||
def __rename_global_node_name(cls, node_name): | ||
if node_name == _BaseSerializer._GLOBAL_NODE_NAME: | ||
return "Global Configuration" | ||
return node_name |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
# Copyright 2022 Avaiga Private Limited | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with | ||
# the License. You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on | ||
# an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the | ||
# specific language governing permissions and limitations under the License. | ||
|
||
from src.taipy.config._config import _Config | ||
from src.taipy.config._config_comparator import _ConfigComparator | ||
from src.taipy.config.global_app.global_app_config import GlobalAppConfig | ||
from tests.config.utils.section_for_tests import SectionForTest | ||
from tests.config.utils.unique_section_for_tests import UniqueSectionForTest | ||
|
||
|
||
def test_config_comparator(): | ||
unique_section_1 = UniqueSectionForTest(attribute="unique_attribute_1", prop="unique_prop_1") | ||
section_1 = SectionForTest("section_1", attribute="attribute_1", prop="prop_1") | ||
section_2 = SectionForTest("section_2", attribute=2, prop="prop_2") | ||
section_3 = SectionForTest("section_3", attribute=3, prop="prop_3") | ||
section_4 = SectionForTest("section_4", attribute=4, prop="prop_4") | ||
|
||
_config_1 = _Config._default_config() | ||
_config_1._sections[SectionForTest.name] = {"section_1": section_1, "section_2": section_2, "section_4": section_4} | ||
_config_1._unique_sections[UniqueSectionForTest.name] = unique_section_1 | ||
|
||
_config_2 = _Config._default_config() | ||
|
||
# Update some global config | ||
_config_2._global_config = GlobalAppConfig( | ||
root_folder="foo", | ||
storage_folder="bar", | ||
repository_properties={"foo": "bar"}, | ||
repository_type="baz", | ||
clean_entities_enabled=True, | ||
) | ||
# Update section_2, add section_3, and remove section 4 | ||
_config_2._sections[SectionForTest.name] = {"section_1": section_1, "section_2": section_3, "section_3": section_2} | ||
_config_2._unique_sections[UniqueSectionForTest.name] = unique_section_1 | ||
|
||
config_diff = _ConfigComparator._compare(_config_1, _config_2) | ||
|
||
# The result was sorted so test by indexing is fine. | ||
assert len(config_diff["added_items"]) == 2 | ||
assert config_diff["added_items"][1] == ( | ||
("section_name", "section_3", None), | ||
{"attribute": "2:int", "prop": "prop_2"}, | ||
) | ||
|
||
# TODO: This should not in ["added_items"] since it is a default value that was changed. This should be fixed soon. | ||
assert config_diff["added_items"][0] == (("Global Configuration", "repository_properties", None), {"foo": "bar"}) | ||
|
||
assert len(config_diff["modified_items"]) == 6 | ||
assert config_diff["modified_items"][0] == (("Global Configuration", "root_folder", None), ("./taipy/", "foo")) | ||
assert config_diff["modified_items"][1] == (("Global Configuration", "storage_folder", None), (".data/", "bar")) | ||
assert config_diff["modified_items"][2] == ( | ||
("Global Configuration", "clean_entities_enabled", None), | ||
("ENV[TAIPY_CLEAN_ENTITIES_ENABLED]", "True:bool"), | ||
) | ||
assert config_diff["modified_items"][3] == ( | ||
("Global Configuration", "repository_type", None), | ||
("filesystem", "baz"), | ||
) | ||
assert config_diff["modified_items"][4] == (("section_name", "section_2", "attribute"), ("2:int", "3:int")) | ||
assert config_diff["modified_items"][5] == (("section_name", "section_2", "prop"), ("prop_2", "prop_3")) | ||
|
||
assert len(config_diff["removed_items"]) == 1 | ||
assert config_diff["removed_items"][0] == ( | ||
("section_name", "section_4", None), | ||
{"attribute": "4:int", "prop": "prop_4"}, | ||
) |