Skip to content

Commit 86b83e8

Browse files
IndexSeekFokkokevinjqliu
authored
feat: search current working directory for config file (#1464)
Resolves #1333 Adds the current working directory to the search path for the `.pyiceberg.yaml` file. As it is now, the file is searched in the following order: 1. the `PYICEBERG_HOME` environment variable 2. ~/ 3. ./ I'm unsure if people would like to have 2 and 3 swapped. In either case, users can still override this with the environment variable. --------- Co-authored-by: Fokko Driesprong <[email protected]> Co-authored-by: Kevin Liu <[email protected]>
1 parent d6dce6d commit 86b83e8

File tree

4 files changed

+93
-9
lines changed

4 files changed

+93
-9
lines changed

mkdocs/docs/api.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ catalog:
4949

5050
and loaded in python by calling `load_catalog(name="hive")` and `load_catalog(name="rest")`.
5151

52-
This information must be placed inside a file called `.pyiceberg.yaml` located either in the `$HOME` or `%USERPROFILE%` directory (depending on whether the operating system is Unix-based or Windows-based, respectively) or in the `$PYICEBERG_HOME` directory (if the corresponding environment variable is set).
52+
This information must be placed inside a file called `.pyiceberg.yaml` located either in the `$HOME` or `%USERPROFILE%` directory (depending on whether the operating system is Unix-based or Windows-based, respectively), in the current working directory, or in the `$PYICEBERG_HOME` directory (if the corresponding environment variable is set).
5353

5454
For more details on possible configurations refer to the [specific page](https://py.iceberg.apache.org/configuration/).
5555

mkdocs/docs/configuration.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,11 @@ hide:
2828

2929
There are three ways to pass in configuration:
3030

31-
- Using the `~/.pyiceberg.yaml` configuration file
31+
- Using the `.pyiceberg.yaml` configuration file (Recommended)
3232
- Through environment variables
3333
- By passing in credentials through the CLI or the Python API
3434

35-
The configuration file is recommended since that's the easiest way to manage the credentials.
35+
The configuration file can be stored in either the directory specified by the `PYICEBERG_HOME` environment variable, the home directory, or current working directory (in this order).
3636

3737
To change the path searched for the `.pyiceberg.yaml`, you can overwrite the `PYICEBERG_HOME` environment variable.
3838

pyiceberg/utils/config.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -84,12 +84,14 @@ def _load_yaml(directory: Optional[str]) -> Optional[RecursiveDict]:
8484
return file_config_lowercase
8585
return None
8686

87-
# Give priority to the PYICEBERG_HOME directory
88-
if pyiceberg_home_config := _load_yaml(os.environ.get(PYICEBERG_HOME)):
89-
return pyiceberg_home_config
90-
# Look into the home directory
91-
if pyiceberg_home_config := _load_yaml(os.path.expanduser("~")):
92-
return pyiceberg_home_config
87+
# Directories to search for the configuration file
88+
# The current search order is: PYICEBERG_HOME, home directory, then current directory
89+
search_dirs = [os.environ.get(PYICEBERG_HOME), os.path.expanduser("~"), os.getcwd()]
90+
91+
for directory in search_dirs:
92+
if config := _load_yaml(directory):
93+
return config
94+
9395
# Didn't find a config
9496
return None
9597

tests/utils/test_config.py

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
# specific language governing permissions and limitations
1616
# under the License.
1717
import os
18+
from typing import Any, Dict, Optional
1819
from unittest import mock
1920

2021
import pytest
@@ -93,3 +94,84 @@ def test_from_configuration_files_get_typed_value(tmp_path_factory: pytest.TempP
9394

9495
assert Config().get_bool("legacy-current-snapshot-id")
9596
assert Config().get_int("max-workers") == 4
97+
98+
99+
@pytest.mark.parametrize(
100+
"config_setup, expected_result",
101+
[
102+
# PYICEBERG_HOME takes precedence
103+
(
104+
{
105+
"pyiceberg_home_content": "https://service.io/pyiceberg_home",
106+
"home_content": "https://service.io/user-home",
107+
"cwd_content": "https://service.io/cwd",
108+
},
109+
"https://service.io/pyiceberg_home",
110+
),
111+
# Home directory (~) is checked after PYICEBERG_HOME
112+
(
113+
{
114+
"pyiceberg_home_content": None,
115+
"home_content": "https://service.io/user-home",
116+
"cwd_content": "https://service.io/cwd",
117+
},
118+
"https://service.io/user-home",
119+
),
120+
# Current working directory (.) is the last fallback
121+
(
122+
{
123+
"pyiceberg_home_content": None,
124+
"home_content": None,
125+
"cwd_content": "https://service.io/cwd",
126+
},
127+
"https://service.io/cwd",
128+
),
129+
# No configuration files found
130+
(
131+
{
132+
"pyiceberg_home_content": None,
133+
"home_content": None,
134+
"cwd_content": None,
135+
},
136+
None,
137+
),
138+
],
139+
)
140+
def test_config_lookup_order(
141+
monkeypatch: pytest.MonkeyPatch,
142+
tmp_path_factory: pytest.TempPathFactory,
143+
config_setup: Dict[str, Any],
144+
expected_result: Optional[str],
145+
) -> None:
146+
"""
147+
Test that the configuration lookup prioritizes PYICEBERG_HOME, then home (~), then cwd.
148+
"""
149+
150+
def create_config_file(path: str, uri: Optional[str]) -> None:
151+
if uri:
152+
config_file_path = os.path.join(path, ".pyiceberg.yaml")
153+
content = {"catalog": {"default": {"uri": uri}}}
154+
with open(config_file_path, "w", encoding="utf-8") as file:
155+
yaml_str = as_document(content).as_yaml()
156+
file.write(yaml_str)
157+
158+
# Create temporary directories for PYICEBERG_HOME, home (~), and cwd
159+
pyiceberg_home = str(tmp_path_factory.mktemp("pyiceberg_home"))
160+
home_dir = str(tmp_path_factory.mktemp("home"))
161+
cwd_dir = str(tmp_path_factory.mktemp("cwd"))
162+
163+
# Create configuration files in the respective directories
164+
create_config_file(pyiceberg_home, config_setup.get("pyiceberg_home_content"))
165+
create_config_file(home_dir, config_setup.get("home_content"))
166+
create_config_file(cwd_dir, config_setup.get("cwd_content"))
167+
168+
# Mock environment and paths
169+
monkeypatch.setenv("PYICEBERG_HOME", pyiceberg_home)
170+
monkeypatch.setattr(os.path, "expanduser", lambda _: home_dir)
171+
monkeypatch.chdir(cwd_dir)
172+
173+
# Perform the lookup and validate the result
174+
result = Config()._from_configuration_files()
175+
assert (
176+
result["catalog"]["default"]["uri"] if result else None # type: ignore
177+
) == expected_result, f"Unexpected configuration result. Expected: {expected_result}, Actual: {result}"

0 commit comments

Comments
 (0)