From 440450fab9b73783cf59557d5e05fe137c55ab56 Mon Sep 17 00:00:00 2001 From: jackylee-ch Date: Mon, 7 Sep 2026 11:13:04 +0800 Subject: [PATCH] fix: treat a .pyiceberg.yaml without a mapping as no config `_load_yaml` passed `strictyaml.load(...).data` straight to `_lowercase_dictionary_keys`. For an empty or comment-only document that value is a `str`, not a mapping, so the call raised `AttributeError: 'str' object has no attribute 'items'`. `Config()` runs at import time, so commenting out the file made `import pyiceberg.catalog` fail with an error naming neither YAML nor the file. Return `None` instead, which the annotated return type already allows and which the caller already handles as "keep looking". Co-Authored-By: Claude Code --- pyiceberg/utils/config.py | 3 +++ tests/utils/test_config.py | 17 +++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/pyiceberg/utils/config.py b/pyiceberg/utils/config.py index ab9b549d25..2b5baafa59 100644 --- a/pyiceberg/utils/config.py +++ b/pyiceberg/utils/config.py @@ -79,6 +79,9 @@ def _load_yaml(directory: str | None) -> RecursiveDict | None: with open(path, encoding=UTF8) as f: yml_str = f.read() file_config = strictyaml.load(yml_str).data + if not isinstance(file_config, dict): + # An empty or comment-only document parses as a string + return None file_config_lowercase = _lowercase_dictionary_keys(file_config) return file_config_lowercase return None diff --git a/tests/utils/test_config.py b/tests/utils/test_config.py index 5cd6a7203a..309821023d 100644 --- a/tests/utils/test_config.py +++ b/tests/utils/test_config.py @@ -183,3 +183,20 @@ def create_config_file(path: str, uri: str | None) -> None: assert ( result["catalog"]["default"]["uri"] if result else None # type: ignore ) == expected_result, f"Unexpected configuration result. Expected: {expected_result}, Actual: {result}" + + +@pytest.mark.parametrize("content", ["", "\n", "# only a comment\n"]) +def test_from_configuration_files_without_a_mapping( + monkeypatch: pytest.MonkeyPatch, tmp_path_factory: pytest.TempPathFactory, content: str +) -> None: + """A file that holds no mapping should be treated as absent.""" + pyiceberg_home = str(tmp_path_factory.mktemp("pyiceberg_home")) + empty_dir = str(tmp_path_factory.mktemp("empty")) + with open(os.path.join(pyiceberg_home, ".pyiceberg.yaml"), "w", encoding=UTF8) as file: + file.write(content) + + monkeypatch.setenv("PYICEBERG_HOME", pyiceberg_home) + monkeypatch.setattr(os.path, "expanduser", lambda _: empty_dir) + monkeypatch.chdir(empty_dir) + + assert Config()._from_configuration_files() is None