Skip to content

Commit 3f29817

Browse files
authored
🐛 fix(macos): yield individual site dirs in iter_*_dirs (#429)
On macOS, `iter_data_dirs()` and `iter_config_dirs()` were falling back to the base class implementation which yields `site_data_dir` as a single string. When `XDG_DATA_DIRS` is set with multiple paths (common on NixOS and similar setups) or under Homebrew where multiple site directories exist, this produced a colon-joined string like `/path1:/path2` instead of yielding each directory individually. 🐛 The Unix platform already overrides both methods to `yield from self._site_data_dirs` / `self._site_config_dirs`, correctly producing individual paths. macOS has the same `_site_data_dirs` and `_site_config_dirs` properties (Homebrew-aware + XDG mixin) but was missing the matching overrides. This adds the identical overrides to `_MacOSDefaults`, consistent with the existing Unix approach. Windows is unaffected since it only has single-value site directories with no `_site_data_dirs` property. Fixes #377
1 parent 170391e commit 3f29817

2 files changed

Lines changed: 56 additions & 0 deletions

File tree

src/platformdirs/macos.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66
import sys
77
from typing import TYPE_CHECKING
88

9+
if TYPE_CHECKING:
10+
from collections.abc import Iterator
11+
912
from ._xdg import XDGMixin
1013
from .api import PlatformDirsABC
1114

@@ -137,6 +140,16 @@ def site_runtime_dir(self) -> str:
137140
""":return: runtime directory shared by users, same as `user_runtime_dir`"""
138141
return self.user_runtime_dir
139142

143+
def iter_config_dirs(self) -> Iterator[str]:
144+
""":yield: all user and site configuration directories."""
145+
yield self.user_config_dir
146+
yield from self._site_config_dirs
147+
148+
def iter_data_dirs(self) -> Iterator[str]:
149+
""":yield: all user and site data directories."""
150+
yield self.user_data_dir
151+
yield from self._site_data_dirs
152+
140153

141154
class MacOS(XDGMixin, _MacOSDefaults):
142155
"""

tests/test_macos.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,3 +268,46 @@ def test_macos_xdg_empty_falls_back(
268268
"user_desktop_dir": f"{home}/Desktop",
269269
}
270270
assert getattr(MacOS(), prop) == expected_map[prop]
271+
272+
273+
def test_iter_data_dirs_xdg(monkeypatch: pytest.MonkeyPatch) -> None:
274+
monkeypatch.setenv("XDG_DATA_HOME", "/xdg/data")
275+
monkeypatch.setenv("XDG_DATA_DIRS", "/xdg/share1:/xdg/share2")
276+
dirs = list(MacOS().iter_data_dirs())
277+
assert dirs == ["/xdg/data", "/xdg/share1", "/xdg/share2"]
278+
279+
280+
def test_iter_config_dirs_xdg(monkeypatch: pytest.MonkeyPatch) -> None:
281+
monkeypatch.setenv("XDG_CONFIG_HOME", "/xdg/config")
282+
monkeypatch.setenv("XDG_CONFIG_DIRS", "/xdg/etc1:/xdg/etc2")
283+
dirs = list(MacOS().iter_config_dirs())
284+
assert dirs == ["/xdg/config", "/xdg/etc1", "/xdg/etc2"]
285+
286+
287+
@pytest.mark.usefixtures("_clear_xdg_env")
288+
def test_iter_data_dirs_homebrew(mocker: MockerFixture) -> None:
289+
mocker.patch("sys.prefix", "/opt/homebrew/opt/python@3.13/Frameworks/Python.framework/Versions/3.13")
290+
dirs = list(MacOS().iter_data_dirs())
291+
home = str(Path("~").expanduser())
292+
assert dirs == [f"{home}/Library/Application Support", "/opt/homebrew/share", "/Library/Application Support"]
293+
294+
295+
@pytest.mark.usefixtures("_clear_xdg_env")
296+
def test_iter_config_dirs_homebrew(mocker: MockerFixture) -> None:
297+
mocker.patch("sys.prefix", "/opt/homebrew/opt/python@3.13/Frameworks/Python.framework/Versions/3.13")
298+
dirs = list(MacOS().iter_config_dirs())
299+
home = str(Path("~").expanduser())
300+
assert dirs == [f"{home}/Library/Application Support", "/opt/homebrew/share", "/Library/Application Support"]
301+
302+
303+
@pytest.mark.usefixtures("_clear_xdg_env")
304+
def test_iter_data_dirs_no_homebrew(mocker: MockerFixture) -> None:
305+
py_version = sys.version_info
306+
builtin_py_prefix = (
307+
"/Applications/Xcode.app/Contents/Developer/Library/Frameworks/Python3.framework"
308+
f"/Versions/{py_version.major}.{py_version.minor}"
309+
)
310+
mocker.patch("sys.prefix", builtin_py_prefix)
311+
dirs = list(MacOS().iter_data_dirs())
312+
home = str(Path("~").expanduser())
313+
assert dirs == [f"{home}/Library/Application Support", "/Library/Application Support"]

0 commit comments

Comments
 (0)