Skip to content

Commit 40f11de

Browse files
Add support for Homebrew-installed software
1 parent d63b824 commit 40f11de

File tree

3 files changed

+95
-8
lines changed

3 files changed

+95
-8
lines changed

src/platformdirs/api.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,9 @@ def __init__( # noqa: PLR0913
5858
"""
5959
self.multipath = multipath
6060
"""
61-
An optional parameter only applicable to Unix/Linux which indicates that the entire list of data dirs should be
62-
returned. By default, the first item would only be returned.
61+
An optional parameter which indicates that the entire list of data dirs should be returned.
62+
By default, the first item would only be returned. This is only applicable for Linux/Unix systems,
63+
as well as macOS systems where `Homebrew <https://brew.sh>`_ is installed.
6364
"""
6465
self.opinion = opinion #: A flag to indicating to use opinionated values.
6566
self.ensure_exists = ensure_exists

src/platformdirs/macos.py

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from __future__ import annotations
33

44
import os.path
5+
import sys
56

67
from .api import PlatformDirsABC
78

@@ -22,8 +23,21 @@ def user_data_dir(self) -> str:
2223

2324
@property
2425
def site_data_dir(self) -> str:
25-
""":return: data directory shared by users, e.g. ``/Library/Application Support/$appname/$version``"""
26-
return self._append_app_name_and_version("/Library/Application Support")
26+
"""
27+
:return: data directory shared by users, e.g. ``/Library/Application Support/$appname/$version``.
28+
If we're using a Python binary managed by `Homebrew <https://brew.sh>`_, the directory
29+
will be under the Homebrew prefix, e.g. ``/opt/homebrew/share/$appname/$version``.
30+
If `multipath <platformdirs.api.PlatformDirsABC.multipath>` is enabled and we're in Homebrew,
31+
the response is a multi-path string separated by ":", e.g.
32+
``/opt/homebrew/share/$appname/$version:/Library/Application Support/$appname/$version``
33+
"""
34+
path_list = [self._append_app_name_and_version("/Library/Application Support")]
35+
is_homebrew = sys.prefix.startswith("/opt/homebrew")
36+
if is_homebrew:
37+
path_list.insert(0, self._append_app_name_and_version("/opt/homebrew/share"))
38+
if self.multipath:
39+
return os.pathsep.join(path_list)
40+
return path_list[0]
2741

2842
@property
2943
def user_config_dir(self) -> str:
@@ -42,8 +56,21 @@ def user_cache_dir(self) -> str:
4256

4357
@property
4458
def site_cache_dir(self) -> str:
45-
""":return: cache directory shared by users, e.g. ``/Library/Caches/$appname/$version``"""
46-
return self._append_app_name_and_version("/Library/Caches")
59+
"""
60+
:return: cache directory shared by users, e.g. ``/Library/Caches/$appname/$version``.
61+
If we're using a Python binary managed by `Homebrew <https://brew.sh>`_, the directory
62+
will be under the Homebrew prefix, e.g. ``/opt/homebrew/var/cache/$appname/$version``.
63+
If `multipath <platformdirs.api.PlatformDirsABC.multipath>` is enabled and we're in Homebrew,
64+
the response is a multi-path string separated by ":", e.g.
65+
``/opt/homebrew/var/cache/$appname/$version:/Library/Caches/$appname/$version``
66+
"""
67+
path_list = [self._append_app_name_and_version("/Library/Caches")]
68+
is_homebrew = sys.prefix.startswith("/opt/homebrew")
69+
if is_homebrew:
70+
path_list.insert(0, self._append_app_name_and_version("/opt/homebrew/var/cache"))
71+
if self.multipath:
72+
return os.pathsep.join(path_list)
73+
return path_list[0]
4774

4875
@property
4976
def user_state_dir(self) -> str:

tests/test_macos.py

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
from __future__ import annotations
22

33
import os
4+
import sys
45
from pathlib import Path
5-
from typing import Any
6+
from typing import TYPE_CHECKING, Any
67

78
import pytest
89

910
from platformdirs.macos import MacOS
1011

12+
if TYPE_CHECKING:
13+
from pytest_mock import MockerFixture
14+
1115

1216
@pytest.mark.parametrize(
1317
"params",
@@ -17,7 +21,17 @@
1721
pytest.param({"appname": "foo", "version": "v1.0"}, id="app_name_version"),
1822
],
1923
)
20-
def test_macos(params: dict[str, Any], func: str) -> None:
24+
def test_macos(mocker: MockerFixture, params: dict[str, Any], func: str) -> None:
25+
# Make sure we are not in Homebrew
26+
py_version = sys.version_info
27+
mocker.patch(
28+
"sys.prefix",
29+
(
30+
"/Applications/Xcode.app/Contents/Developer/Library/Frameworks/Python3.framework/Versions"
31+
f"/{py_version.major}.{py_version.minor}"
32+
),
33+
)
34+
2135
result = getattr(MacOS(**params), func)
2236

2337
home = str(Path("~").expanduser())
@@ -45,3 +59,48 @@ def test_macos(params: dict[str, Any], func: str) -> None:
4559
expected = expected_map[func]
4660

4761
assert result == expected
62+
63+
64+
@pytest.mark.parametrize(
65+
"params",
66+
[
67+
pytest.param({}, id="no_args"),
68+
pytest.param({"appname": "foo"}, id="app_name"),
69+
pytest.param({"appname": "foo", "version": "v1.0"}, id="app_name_version"),
70+
],
71+
)
72+
@pytest.mark.parametrize("multipath", [pytest.param(True, id="multipath"), pytest.param(False, id="singlepath")])
73+
def test_macos_homebrew(mocker: MockerFixture, params: dict[str, Any], multipath: bool, func: str) -> None:
74+
mocker.patch("sys.prefix", "/opt/homebrew/opt/python")
75+
76+
result = getattr(MacOS(multipath=multipath, **params), func)
77+
78+
home = str(Path("~").expanduser())
79+
suffix_elements = tuple(params[i] for i in ("appname", "version") if i in params)
80+
suffix = os.sep.join(("", *suffix_elements)) if suffix_elements else "" # noqa: PTH118
81+
82+
expected_map = {
83+
"user_data_dir": f"{home}/Library/Application Support{suffix}",
84+
"site_data_dir": f"/opt/homebrew/share{suffix}",
85+
"user_config_dir": f"{home}/Library/Application Support{suffix}",
86+
"site_config_dir": f"/opt/homebrew/share{suffix}",
87+
"user_cache_dir": f"{home}/Library/Caches{suffix}",
88+
"site_cache_dir": f"/opt/homebrew/var/cache{suffix}",
89+
"user_state_dir": f"{home}/Library/Application Support{suffix}",
90+
"user_log_dir": f"{home}/Library/Logs{suffix}",
91+
"user_documents_dir": f"{home}/Documents",
92+
"user_downloads_dir": f"{home}/Downloads",
93+
"user_pictures_dir": f"{home}/Pictures",
94+
"user_videos_dir": f"{home}/Movies",
95+
"user_music_dir": f"{home}/Music",
96+
"user_desktop_dir": f"{home}/Desktop",
97+
"user_runtime_dir": f"{home}/Library/Caches/TemporaryItems{suffix}",
98+
"site_runtime_dir": f"{home}/Library/Caches/TemporaryItems{suffix}",
99+
}
100+
if multipath:
101+
expected_map["site_data_dir"] += f":/Library/Application Support{suffix}"
102+
expected_map["site_config_dir"] += f":/Library/Application Support{suffix}"
103+
expected_map["site_cache_dir"] += f":/Library/Caches{suffix}"
104+
expected = expected_map[func]
105+
106+
assert result == expected

0 commit comments

Comments
 (0)