You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@iceberg.apache.org by gi...@apache.org on 2026/09/07 06:25:52 UTC

(iceberg-python) branch main updated: fix: treat a .pyiceberg.yaml without a mapping as no config (#3915)

This is an automated email from the ASF dual-hosted git repository.

github-merge-queue[bot] pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/iceberg-python.git


The following commit(s) were added to refs/heads/main by this push:
     new 75b74cfca fix: treat a .pyiceberg.yaml without a mapping as no config (#3915)
75b74cfca is described below

commit 75b74cfca0d0491e94c12c9da978eeb0bc05698a
Author: jackylee <qc...@gmail.com>
AuthorDate: Mon Sep 7 06:13:04 2026 +0000

    fix: treat a .pyiceberg.yaml without a mapping as no config (#3915)
    
    `_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 <no...@anthropic.com>
---
 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 ab9b549d2..2b5baafa5 100644
--- a/pyiceberg/utils/config.py
+++ b/pyiceberg/utils/config.py
@@ -79,6 +79,9 @@ class Config:
                     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 5cd6a7203..309821023 100644
--- a/tests/utils/test_config.py
+++ b/tests/utils/test_config.py
@@ -183,3 +183,20 @@ def test_config_lookup_order(
     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