This is an automated email from the ASF dual-hosted git repository. ephraimanierobi pushed a commit to branch v3-1-test in repository https://gitbox.apache.org/repos/asf/airflow.git
commit 61ba974489161b5ad8c5a22191e5b4a6b3aef61f Author: Jarek Potiuk <[email protected]> AuthorDate: Sun Nov 16 23:43:01 2025 +0100 [v3-1-test] Fix config list output for multi-line values (#58115) (#58378) Closes: #57355 When a user runs `airflow config list --include-descriptions --include-examples`, multi-line values (like `dag_bundle_config_list`) would cause a `configparser.ParsingError` due to improper indentation. This fix pretty-prints the JSON value using `json.dumps(indent=4)` and then adds an additional four-space indent to each new line. This ensures the INI parser treats the entire block as a single, valid multi-line value. (cherry picked from commit d009b6467544e657245f14543dcc6dd11046fcb1) Co-authored-by: Aaron Chen <[email protected]> --- airflow-core/src/airflow/configuration.py | 8 ++++ airflow-core/tests/unit/core/test_configuration.py | 46 ++++++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/airflow-core/src/airflow/configuration.py b/airflow-core/src/airflow/configuration.py index 1bfbbcf62b4..361b5765fd4 100644 --- a/airflow-core/src/airflow/configuration.py +++ b/airflow-core/src/airflow/configuration.py @@ -589,6 +589,14 @@ class AirflowConfigParser(ConfigParser): value = "\n# ".join(value_lines) file.write(f"# {option} = {value}\n") else: + if "\n" in value: + try: + value = json.dumps(json.loads(value), indent=4) + value = value.replace( + "\n", "\n " + ) # indent multi-line JSON to satisfy configparser format + except JSONDecodeError: + pass file.write(f"{option} = {value}\n") if needs_separation: file.write("\n") diff --git a/airflow-core/tests/unit/core/test_configuration.py b/airflow-core/tests/unit/core/test_configuration.py index 0735245f509..bcce279d086 100644 --- a/airflow-core/tests/unit/core/test_configuration.py +++ b/airflow-core/tests/unit/core/test_configuration.py @@ -1028,6 +1028,52 @@ key7 = for key, value in expected_backend_kwargs.items(): assert getattr(secrets_backend, key) == value + def test_write_pretty_prints_multiline_json(self): + """ + Tests that the `write` method correctly pretty-prints + a config value that is a valid multi-line JSON string. + """ + json_string = '[\n{\n"name": "dags-folder",\n"classpath": "test.class"\n}\n]' + + test_conf = AirflowConfigParser() + test_conf.add_section("test_json") + test_conf.set("test_json", "my_json_config", json_string) + + with StringIO() as string_file: + test_conf.write(string_file, include_descriptions=False, include_env_vars=False) + content = string_file.getvalue() + + expected_formatted_string = ( + "my_json_config = [\n" + " {\n" + ' "name": "dags-folder",\n' + ' "classpath": "test.class"\n' + " }\n" + " ]\n" + ) + + assert expected_formatted_string in content + assert json_string not in content + + def test_write_handles_multiline_non_json_string(self): + """ + Tests that `write` does not crash when encountering a multi-line string + that is NOT valid JSON. + """ + multiline_string = "This is the first line.\nThis is the second line." + + test_conf = AirflowConfigParser() + test_conf.add_section("test_multiline") + test_conf.set("test_multiline", "my_string_config", multiline_string) + + with StringIO() as string_file: + test_conf.write(string_file, include_descriptions=False, include_env_vars=False) + content = string_file.getvalue() + + expected_raw_output = "my_string_config = This is the first line.\nThis is the second line.\n" + + assert expected_raw_output in content + @mock.patch.dict( "os.environ",
