From a3df7b2ba5864c1707db57415f6d2185cbe0ade7 Mon Sep 17 00:00:00 2001 From: Damien Goutte-Gattat Date: Thu, 17 Sep 2026 12:54:26 +0100 Subject: [PATCH 1/2] Remove null values from configuration file. Automatically generated configuration files (obtained by seeding a repository without a provided configuration file) contain explicit null values for all settings that do not have a default value and do not have been explicitly set on the command line. Such values (i) may cause problems downstream of parsing the configuration, and (ii) needlessly pollute the configuration file. We take care of both problems by * adding a `purge_null_values` method to explicitly clean a dictionary of any null values; * using that new method in two occasions: * when saving the configuration (so that an automatically generated or updated file never contains null values in the first place); * after parsing the configuration and before trying to update it (in case a file would still somehow contain null values). closes #31 --- src/incatools/odk/config.py | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/src/incatools/odk/config.py b/src/incatools/odk/config.py index 077e992..24770c9 100644 --- a/src/incatools/odk/config.py +++ b/src/incatools/odk/config.py @@ -210,7 +210,11 @@ def update_config_dict(obj: Dict[str, Any]) -> None: :param obj: The dictionary to update. """ - # First take care of stubs, if needed + # First remove pointless null values (typically the result of an + # auto-generated config file, when seeding without a file) + purge_null_values(obj) + + # Then take care of stubs, if needed update_stubs(obj) # Then all the other simple changes @@ -266,6 +270,18 @@ def update_config_dict(obj: Dict[str, Any]) -> None: spec["compressions"] = ["none", "gz"] +def purge_null_values(obj: Dict[str, Any]) -> None: + """Removes all null values from a dictionary, recursively.""" + nulls = [] + for k, v in obj.items(): + if v is None: + nulls.append(k) + elif isinstance(v, dict): + purge_null_values(v) + for null in nulls: + obj.pop(null) + + def pop_key(obj: Dict[str, Any], path: str) -> Optional[str]: """Gets the value of a key in a nested dictionary structure. @@ -341,4 +357,6 @@ def save_config(project: OntologyProject, output: TextIO) -> None: :param project: The project to save. :param output: The file-like object where to save the project. """ - output.write(yaml.dump(project.to_dict(), default_flow_style=False)) + project_as_dict = project.to_dict() + purge_null_values(project_as_dict) + output.write(yaml.dump(project_as_dict, default_flow_style=False)) From 5037a83c0c0c9278df5d45e4bd70bed8a6089c92 Mon Sep 17 00:00:00 2001 From: Damien Goutte-Gattat Date: Fri, 18 Sep 2026 13:02:17 +0100 Subject: [PATCH 2/2] Recurse through lists when purging null values. Amend the `purge_null_values` method so that it enters lists and purge any null values in any dict that may be contained (directly or indirectly) in a list item. --- src/incatools/odk/config.py | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/incatools/odk/config.py b/src/incatools/odk/config.py index 24770c9..c44c4fc 100644 --- a/src/incatools/odk/config.py +++ b/src/incatools/odk/config.py @@ -270,16 +270,20 @@ def update_config_dict(obj: Dict[str, Any]) -> None: spec["compressions"] = ["none", "gz"] -def purge_null_values(obj: Dict[str, Any]) -> None: +def purge_null_values(obj: Any) -> None: """Removes all null values from a dictionary, recursively.""" - nulls = [] - for k, v in obj.items(): - if v is None: - nulls.append(k) - elif isinstance(v, dict): - purge_null_values(v) - for null in nulls: - obj.pop(null) + if isinstance(obj, dict): + nulls = [] + for k, v in obj.items(): + if v is None: + nulls.append(k) + else: + purge_null_values(v) + for null in nulls: + obj.pop(null) + elif isinstance(obj, list): + for i in obj: + purge_null_values(i) def pop_key(obj: Dict[str, Any], path: str) -> Optional[str]: