Skip to content

Commit 4c7915a

Browse files
feat: add data config checker for property value types (#2328)
* feat: add data config checker for property value types * feat: update unit tests --------- Co-authored-by: Joao Andre <joaoandre.ja@gmail.com>
1 parent f3f9e4e commit 4c7915a

File tree

3 files changed

+138
-59
lines changed

3 files changed

+138
-59
lines changed

taipy/core/config/checkers/_data_node_config_checker.py

Lines changed: 22 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
# specific language governing permissions and limitations under the License.
1111

1212
from datetime import timedelta
13-
from typing import Any, Callable, Dict, List, Tuple, cast
13+
from typing import Callable, Dict, List, cast
1414

1515
from taipy.common.config._config import _Config
1616
from taipy.common.config.checker._checkers._config_checker import _ConfigChecker
@@ -23,27 +23,6 @@
2323

2424

2525
class _DataNodeConfigChecker(_ConfigChecker):
26-
_PROPERTIES_TYPES: Dict[str, List[Tuple[Any, List[str]]]] = {
27-
DataNodeConfig._STORAGE_TYPE_VALUE_GENERIC: [
28-
(
29-
Callable,
30-
[
31-
DataNodeConfig._OPTIONAL_READ_FUNCTION_GENERIC_PROPERTY,
32-
DataNodeConfig._OPTIONAL_WRITE_FUNCTION_GENERIC_PROPERTY,
33-
],
34-
)
35-
],
36-
DataNodeConfig._STORAGE_TYPE_VALUE_SQL: [
37-
(
38-
Callable,
39-
[
40-
DataNodeConfig._REQUIRED_WRITE_QUERY_BUILDER_SQL_PROPERTY,
41-
DataNodeConfig._OPTIONAL_APPEND_QUERY_BUILDER_SQL_PROPERTY,
42-
],
43-
),
44-
],
45-
}
46-
4726
def __init__(self, config: _Config, collector: IssueCollector):
4827
super().__init__(config, collector)
4928

@@ -67,7 +46,7 @@ def _check(self) -> IssueCollector:
6746
self._check_scope(data_node_config_id, data_node_config)
6847
self._check_validity_period(data_node_config_id, data_node_config)
6948
self._check_required_properties(data_node_config_id, data_node_config)
70-
self._check_class_type(data_node_config_id, data_node_config)
49+
self._check_property_types(data_node_config_id, data_node_config)
7150
self._check_generic_read_write_fct_and_args(data_node_config_id, data_node_config)
7251
self._check_exposed_type(data_node_config_id, data_node_config)
7352
return self._collector
@@ -217,25 +196,26 @@ def _check_generic_read_write_fct_and_args(self, data_node_config_id: str, data_
217196
f"DataNodeConfig `{data_node_config_id}` must be populated with a Callable function.",
218197
)
219198

220-
def _check_class_type(self, data_node_config_id: str, data_node_config: DataNodeConfig):
221-
if data_node_config.storage_type in self._PROPERTIES_TYPES.keys():
222-
for class_type, prop_keys in self._PROPERTIES_TYPES[data_node_config.storage_type]:
223-
for prop_key in prop_keys:
224-
prop_value = data_node_config.properties.get(prop_key) if data_node_config.properties else None
225-
if prop_value and not isinstance(prop_value, class_type):
226-
self._error(
227-
prop_key,
228-
prop_value,
229-
f"`{prop_key}` of DataNodeConfig `{data_node_config_id}` must be"
230-
f" populated with a {'Callable' if class_type == Callable else class_type.__name__}.",
231-
)
232-
if class_type == Callable and callable(prop_value) and prop_value.__name__ == "<lambda>":
233-
self._error(
234-
prop_key,
235-
prop_value,
236-
f"`{prop_key}` of DataNodeConfig `{data_node_config_id}` must be"
237-
f" populated with a serializable Callable function but not a lambda.",
238-
)
199+
def _check_property_types(self, data_node_config_id: str, data_node_config: DataNodeConfig):
200+
if property_types := data_node_config._PROPERTIES_TYPES.get(data_node_config.storage_type):
201+
for prop_key, prop_type in property_types.items():
202+
prop_value = data_node_config.properties.get(prop_key) if data_node_config.properties else None
203+
204+
if prop_value and not isinstance(prop_value, prop_type):
205+
self._error(
206+
prop_key,
207+
prop_value,
208+
f"`{prop_key}` of DataNodeConfig `{data_node_config_id}` must be"
209+
f" populated with a {prop_type}.",
210+
)
211+
212+
if prop_type == Callable and callable(prop_value) and prop_value.__name__ == "<lambda>":
213+
self._error(
214+
prop_key,
215+
prop_value,
216+
f"`{prop_key}` of DataNodeConfig `{data_node_config_id}` must be"
217+
f" populated with a serializable typing.Callable function but not a lambda.",
218+
)
239219

240220
def _check_exposed_type(self, data_node_config_id: str, data_node_config: DataNodeConfig):
241221
if not isinstance(data_node_config.exposed_type, str):

taipy/core/config/data_node_config.py

Lines changed: 94 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ class DataNodeConfig(Section):
4141
"""
4242

4343
name = "DATA_NODE"
44-
44+
_ALL_TYPES = (str, int, float, bool, list, dict, tuple, set, type(None), callable)
4545
_STORAGE_TYPE_KEY = "storage_type"
4646
_STORAGE_TYPE_VALUE_PICKLE = "pickle"
4747
_STORAGE_TYPE_VALUE_SQL_TABLE = "sql_table"
@@ -157,6 +157,99 @@ class DataNodeConfig(Section):
157157
_OPTIONAL_AWS_S3_GET_OBJECT_PARAMETERS_PROPERTY = "aws_s3_get_object_parameters"
158158
_OPTIONAL_AWS_S3_PUT_OBJECT_PARAMETERS_PROPERTY = "aws_s3_put_object_parameters"
159159

160+
_PROPERTIES_TYPES: Dict[str, Dict[str, Any]] = {
161+
_STORAGE_TYPE_VALUE_GENERIC: {
162+
_OPTIONAL_READ_FUNCTION_GENERIC_PROPERTY: Callable,
163+
_OPTIONAL_WRITE_FUNCTION_GENERIC_PROPERTY: Callable,
164+
_OPTIONAL_READ_FUNCTION_ARGS_GENERIC_PROPERTY: list,
165+
_OPTIONAL_WRITE_FUNCTION_ARGS_GENERIC_PROPERTY: list,
166+
},
167+
_STORAGE_TYPE_VALUE_SQL: {
168+
_REQUIRED_DB_NAME_SQL_PROPERTY: str,
169+
_REQUIRED_DB_ENGINE_SQL_PROPERTY: str,
170+
_REQUIRED_READ_QUERY_SQL_PROPERTY: str,
171+
_REQUIRED_WRITE_QUERY_BUILDER_SQL_PROPERTY: Callable,
172+
_OPTIONAL_APPEND_QUERY_BUILDER_SQL_PROPERTY: Callable,
173+
_OPTIONAL_DB_USERNAME_SQL_PROPERTY: str,
174+
_OPTIONAL_DB_PASSWORD_SQL_PROPERTY: str,
175+
_OPTIONAL_HOST_SQL_PROPERTY: str,
176+
_OPTIONAL_PORT_SQL_PROPERTY: int,
177+
_OPTIONAL_DRIVER_SQL_PROPERTY: str,
178+
_OPTIONAL_FOLDER_PATH_SQLITE_PROPERTY: str,
179+
_OPTIONAL_FILE_EXTENSION_SQLITE_PROPERTY: str,
180+
_OPTIONAL_DB_EXTRA_ARGS_SQL_PROPERTY: dict,
181+
_OPTIONAL_EXPOSED_TYPE_SQL_PROPERTY: (str, Callable),
182+
},
183+
_STORAGE_TYPE_VALUE_SQL_TABLE: {
184+
_REQUIRED_DB_NAME_SQL_PROPERTY: str,
185+
_REQUIRED_DB_ENGINE_SQL_PROPERTY: str,
186+
_REQUIRED_TABLE_NAME_SQL_TABLE_PROPERTY: str,
187+
_OPTIONAL_DB_USERNAME_SQL_PROPERTY: str,
188+
_OPTIONAL_DB_PASSWORD_SQL_PROPERTY: str,
189+
_OPTIONAL_HOST_SQL_PROPERTY: str,
190+
_OPTIONAL_PORT_SQL_PROPERTY: int,
191+
_OPTIONAL_DRIVER_SQL_PROPERTY: str,
192+
_OPTIONAL_FOLDER_PATH_SQLITE_PROPERTY: str,
193+
_OPTIONAL_FILE_EXTENSION_SQLITE_PROPERTY: str,
194+
_OPTIONAL_DB_EXTRA_ARGS_SQL_PROPERTY: dict,
195+
_OPTIONAL_EXPOSED_TYPE_SQL_PROPERTY: (str, Callable),
196+
},
197+
_STORAGE_TYPE_VALUE_CSV: {
198+
_OPTIONAL_DEFAULT_PATH_CSV_PROPERTY: str,
199+
_OPTIONAL_ENCODING_PROPERTY: str,
200+
_OPTIONAL_HAS_HEADER_CSV_PROPERTY: bool,
201+
_OPTIONAL_EXPOSED_TYPE_CSV_PROPERTY: (str, Callable),
202+
},
203+
_STORAGE_TYPE_VALUE_EXCEL: {
204+
_OPTIONAL_DEFAULT_PATH_EXCEL_PROPERTY: str,
205+
_OPTIONAL_HAS_HEADER_EXCEL_PROPERTY: bool,
206+
_OPTIONAL_SHEET_NAME_EXCEL_PROPERTY: (str, list),
207+
_OPTIONAL_EXPOSED_TYPE_EXCEL_PROPERTY: (str, Callable),
208+
},
209+
_STORAGE_TYPE_VALUE_IN_MEMORY: {
210+
_OPTIONAL_DEFAULT_DATA_IN_MEMORY_PROPERTY: _ALL_TYPES,
211+
},
212+
_STORAGE_TYPE_VALUE_PICKLE: {
213+
_OPTIONAL_DEFAULT_PATH_PICKLE_PROPERTY: str,
214+
_OPTIONAL_DEFAULT_DATA_PICKLE_PROPERTY: _ALL_TYPES,
215+
},
216+
_STORAGE_TYPE_VALUE_JSON: {
217+
_OPTIONAL_DEFAULT_PATH_JSON_PROPERTY: str,
218+
_OPTIONAL_ENCODING_PROPERTY: str,
219+
_OPTIONAL_ENCODER_JSON_PROPERTY: json.JSONEncoder,
220+
_OPTIONAL_DECODER_JSON_PROPERTY: json.JSONDecoder,
221+
},
222+
_STORAGE_TYPE_VALUE_PARQUET: {
223+
_OPTIONAL_DEFAULT_PATH_PARQUET_PROPERTY: str,
224+
_OPTIONAL_ENGINE_PARQUET_PROPERTY: str,
225+
_OPTIONAL_COMPRESSION_PARQUET_PROPERTY: str,
226+
_OPTIONAL_READ_KWARGS_PARQUET_PROPERTY: dict,
227+
_OPTIONAL_WRITE_KWARGS_PARQUET_PROPERTY: dict,
228+
_OPTIONAL_EXPOSED_TYPE_PARQUET_PROPERTY: (str, Callable),
229+
},
230+
_STORAGE_TYPE_VALUE_MONGO_COLLECTION: {
231+
_REQUIRED_DB_NAME_MONGO_PROPERTY: str,
232+
_REQUIRED_COLLECTION_NAME_MONGO_PROPERTY: str,
233+
_OPTIONAL_CUSTOM_DOCUMENT_MONGO_PROPERTY: str,
234+
_OPTIONAL_USERNAME_MONGO_PROPERTY: str,
235+
_OPTIONAL_PASSWORD_MONGO_PROPERTY: str,
236+
_OPTIONAL_HOST_MONGO_PROPERTY: str,
237+
_OPTIONAL_PORT_MONGO_PROPERTY: int,
238+
_OPTIONAL_DRIVER_MONGO_PROPERTY: str,
239+
_OPTIONAL_DB_EXTRA_ARGS_MONGO_PROPERTY: dict,
240+
},
241+
_STORAGE_TYPE_VALUE_S3_OBJECT: {
242+
_REQUIRED_AWS_ACCESS_KEY_ID_PROPERTY: str,
243+
_REQUIRED_AWS_SECRET_ACCESS_KEY_PROPERTY: str,
244+
_REQUIRED_AWS_STORAGE_BUCKET_NAME_PROPERTY: str,
245+
_REQUIRED_AWS_S3_OBJECT_KEY_PROPERTY: str,
246+
_OPTIONAL_AWS_REGION_PROPERTY: str,
247+
_OPTIONAL_AWS_S3_CLIENT_PARAMETERS_PROPERTY: dict,
248+
_OPTIONAL_AWS_S3_GET_OBJECT_PARAMETERS_PROPERTY: dict,
249+
_OPTIONAL_AWS_S3_PUT_OBJECT_PARAMETERS_PROPERTY: dict,
250+
},
251+
}
252+
160253
_REQUIRED_PROPERTIES: Dict[str, List] = {
161254
_STORAGE_TYPE_VALUE_PICKLE: [],
162255
_STORAGE_TYPE_VALUE_SQL_TABLE: [

tests/core/config/checkers/test_data_node_config_checker.py

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -513,12 +513,12 @@ def test_check_callable_properties(self, caplog):
513513
Config.check()
514514
assert len(Config._collector.errors) == 2
515515
expected_error_message_1 = (
516-
"`write_query_builder` of DataNodeConfig `new` must be populated with a Callable."
516+
"`write_query_builder` of DataNodeConfig `new` must be populated with a typing.Callable."
517517
" Current value of property `write_query_builder` is 1."
518518
)
519519
assert expected_error_message_1 in caplog.text
520520
expected_error_message_2 = (
521-
"`append_query_builder` of DataNodeConfig `new` must be populated with a Callable."
521+
"`append_query_builder` of DataNodeConfig `new` must be populated with a typing.Callable."
522522
" Current value of property `append_query_builder` is 2."
523523
)
524524
assert expected_error_message_2 in caplog.text
@@ -530,7 +530,7 @@ def test_check_callable_properties(self, caplog):
530530
Config.check()
531531
assert len(Config._collector.errors) == 1
532532
expected_error_messages = [
533-
"`write_fct` of DataNodeConfig `new` must be populated with a Callable. Current value"
533+
"`write_fct` of DataNodeConfig `new` must be populated with a typing.Callable. Current value"
534534
" of property `write_fct` is 12.",
535535
]
536536
assert all(message in caplog.text for message in expected_error_messages)
@@ -542,7 +542,7 @@ def test_check_callable_properties(self, caplog):
542542
Config.check()
543543
assert len(Config._collector.errors) == 1
544544
expected_error_messages = [
545-
"`read_fct` of DataNodeConfig `new` must be populated with a Callable. Current value"
545+
"`read_fct` of DataNodeConfig `new` must be populated with a typing.Callable. Current value"
546546
" of property `read_fct` is 5.",
547547
]
548548
assert all(message in caplog.text for message in expected_error_messages)
@@ -554,9 +554,9 @@ def test_check_callable_properties(self, caplog):
554554
Config.check()
555555
assert len(Config._collector.errors) == 2
556556
expected_error_messages = [
557-
"`write_fct` of DataNodeConfig `new` must be populated with a Callable. Current value"
557+
"`write_fct` of DataNodeConfig `new` must be populated with a typing.Callable. Current value"
558558
" of property `write_fct` is 9.",
559-
"`read_fct` of DataNodeConfig `new` must be populated with a Callable. Current value"
559+
"`read_fct` of DataNodeConfig `new` must be populated with a typing.Callable. Current value"
560560
" of property `read_fct` is 5.",
561561
]
562562
assert all(message in caplog.text for message in expected_error_messages)
@@ -588,10 +588,10 @@ def test_check_callable_properties(self, caplog):
588588
Config.check()
589589
assert len(Config._collector.errors) == 2
590590
expected_error_messages = [
591-
"`write_fct` of DataNodeConfig `new` must be populated with a serializable Callable function but"
591+
"`write_fct` of DataNodeConfig `new` must be populated with a serializable typing.Callable function but"
592592
" not a lambda. Current value of property `write_fct` is <function TestDataNodeConfigChecker."
593593
"test_check_callable_properties.<locals>.<lambda>",
594-
"`read_fct` of DataNodeConfig `new` must be populated with a serializable Callable function but"
594+
"`read_fct` of DataNodeConfig `new` must be populated with a serializable typing.Callable function but"
595595
" not a lambda. Current value of property `read_fct` is <function TestDataNodeConfigChecker."
596596
"test_check_callable_properties.<locals>.<lambda>",
597597
]
@@ -616,12 +616,15 @@ def test_check_read_write_fct_args(self, caplog):
616616
with pytest.raises(SystemExit):
617617
Config._collector = IssueCollector()
618618
Config.check()
619-
assert len(Config._collector.errors) == 1
620-
expected_error_message = (
619+
assert len(Config._collector.errors) == 2
620+
621+
expected_error_messages = (
622+
"`write_fct_args` of DataNodeConfig `default` must be populated with a <class 'list'>."
623+
' Current value of property `write_fct_args` is "foo".',
621624
"`write_fct_args` field of DataNodeConfig `default` must be populated with a List value."
622-
' Current value of property `write_fct_args` is "foo".'
625+
' Current value of property `write_fct_args` is "foo".',
623626
)
624-
assert expected_error_message in caplog.text
627+
assert all(message in caplog.text for message in expected_error_messages)
625628
config._sections[DataNodeConfig.name]["default"].storage_type = "generic"
626629
config._sections[DataNodeConfig.name]["default"].properties = {
627630
"write_fct": print,
@@ -641,12 +644,15 @@ def test_check_read_write_fct_args(self, caplog):
641644
with pytest.raises(SystemExit):
642645
Config._collector = IssueCollector()
643646
Config.check()
644-
assert len(Config._collector.errors) == 1
645-
expected_error_message = (
647+
assert len(Config._collector.errors) == 2
648+
649+
expected_error_messages = (
650+
"`read_fct_args` of DataNodeConfig `default` must be populated with a <class 'list'>."
651+
" Current value of property `read_fct_args` is 1.",
646652
"`read_fct_args` field of DataNodeConfig `default` must be populated with a List value."
647-
" Current value of property `read_fct_args` is 1."
653+
" Current value of property `read_fct_args` is 1.",
648654
)
649-
assert expected_error_message in caplog.text
655+
assert all(message in caplog.text for message in expected_error_messages)
650656

651657
config._sections[DataNodeConfig.name]["default"].storage_type = "generic"
652658
config._sections[DataNodeConfig.name]["default"].properties = {

0 commit comments

Comments
 (0)