Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for Homebrew-installed software #232

Merged
merged 4 commits into from
Oct 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/platformdirs/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,8 @@ def __init__( # noqa: PLR0913
"""
self.multipath = multipath
"""
An optional parameter only applicable to Unix/Linux which indicates that the entire list of data dirs should be
returned. By default, the first item would only be returned.
An optional parameter which indicates that the entire list of data dirs should be returned.
By default, the first item would only be returned.
"""
self.opinion = opinion #: A flag to indicating to use opinionated values.
self.ensure_exists = ensure_exists
Expand Down
33 changes: 29 additions & 4 deletions src/platformdirs/macos.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from __future__ import annotations

import os.path
import sys

from .api import PlatformDirsABC

Expand All @@ -22,8 +23,20 @@ def user_data_dir(self) -> str:

@property
def site_data_dir(self) -> str:
""":return: data directory shared by users, e.g. ``/Library/Application Support/$appname/$version``"""
return self._append_app_name_and_version("/Library/Application Support")
"""
:return: data directory shared by users, e.g. ``/Library/Application Support/$appname/$version``.
If we're using a Python binary managed by `Homebrew <https://brew.sh>`_, the directory
will be under the Homebrew prefix, e.g. ``/opt/homebrew/share/$appname/$version``.
If `multipath <platformdirs.api.PlatformDirsABC.multipath>` is enabled and we're in Homebrew,
the response is a multi-path string separated by ":", e.g.
``/opt/homebrew/share/$appname/$version:/Library/Application Support/$appname/$version``
"""
is_homebrew = sys.prefix.startswith("/opt/homebrew")
path_list = [self._append_app_name_and_version("/opt/homebrew/share")] if is_homebrew else []
path_list.append(self._append_app_name_and_version("/Library/Application Support"))
if self.multipath:
return os.pathsep.join(path_list)
return path_list[0]

@property
def user_config_dir(self) -> str:
Expand All @@ -42,8 +55,20 @@ def user_cache_dir(self) -> str:

@property
def site_cache_dir(self) -> str:
""":return: cache directory shared by users, e.g. ``/Library/Caches/$appname/$version``"""
return self._append_app_name_and_version("/Library/Caches")
"""
:return: cache directory shared by users, e.g. ``/Library/Caches/$appname/$version``.
If we're using a Python binary managed by `Homebrew <https://brew.sh>`_, the directory
will be under the Homebrew prefix, e.g. ``/opt/homebrew/var/cache/$appname/$version``.
If `multipath <platformdirs.api.PlatformDirsABC.multipath>` is enabled and we're in Homebrew,
the response is a multi-path string separated by ":", e.g.
``/opt/homebrew/var/cache/$appname/$version:/Library/Caches/$appname/$version``
"""
is_homebrew = sys.prefix.startswith("/opt/homebrew")
path_list = [self._append_app_name_and_version("/opt/homebrew/var/cache")] if is_homebrew else []
path_list.append(self._append_app_name_and_version("/Library/Caches"))
if self.multipath:
return os.pathsep.join(path_list)
return path_list[0]

@property
def user_state_dir(self) -> str:
Expand Down
68 changes: 66 additions & 2 deletions tests/test_macos.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,27 @@
from __future__ import annotations

import os
import sys
from pathlib import Path
from typing import Any
from typing import TYPE_CHECKING, Any

import pytest

from platformdirs.macos import MacOS

if TYPE_CHECKING:
from pytest_mock import MockerFixture


@pytest.fixture(autouse=True)
def _fix_os_pathsep(mocker: MockerFixture) -> None:
"""
If we're not actually running on macOS, set `os.pathsep` to what it should be on macOS.
"""
if sys.platform != "darwin": # pragma: darwin no cover
mocker.patch("os.pathsep", ":")
singingwolfboy marked this conversation as resolved.
Show resolved Hide resolved
mocker.patch("os.path.pathsep", ":")


@pytest.mark.parametrize(
"params",
Expand All @@ -17,7 +31,15 @@
pytest.param({"appname": "foo", "version": "v1.0"}, id="app_name_version"),
],
)
def test_macos(params: dict[str, Any], func: str) -> None:
def test_macos(mocker: MockerFixture, params: dict[str, Any], func: str) -> None:
# Make sure we are not in Homebrew
py_version = sys.version_info
builtin_py_prefix = (
"/Applications/Xcode.app/Contents/Developer/Library/Frameworks/Python3.framework"
f"/Versions/{py_version.major}.{py_version.minor}"
)
mocker.patch("sys.prefix", builtin_py_prefix)

result = getattr(MacOS(**params), func)

home = str(Path("~").expanduser())
Expand Down Expand Up @@ -45,3 +67,45 @@ def test_macos(params: dict[str, Any], func: str) -> None:
expected = expected_map[func]

assert result == expected


@pytest.mark.parametrize(
"params",
[
pytest.param({}, id="no_args"),
pytest.param({"appname": "foo"}, id="app_name"),
pytest.param({"appname": "foo", "version": "v1.0"}, id="app_name_version"),
],
)
@pytest.mark.parametrize(
"site_func",
[
"site_data_dir",
"site_config_dir",
"site_cache_dir",
"site_runtime_dir",
],
)
@pytest.mark.parametrize("multipath", [pytest.param(True, id="multipath"), pytest.param(False, id="singlepath")])
def test_macos_homebrew(mocker: MockerFixture, params: dict[str, Any], multipath: bool, site_func: str) -> None:
mocker.patch("sys.prefix", "/opt/homebrew/opt/python")
gaborbernat marked this conversation as resolved.
Show resolved Hide resolved

result = getattr(MacOS(multipath=multipath, **params), site_func)

home = str(Path("~").expanduser())
suffix_elements = tuple(params[i] for i in ("appname", "version") if i in params)
suffix = os.sep.join(("", *suffix_elements)) if suffix_elements else "" # noqa: PTH118

expected_map = {
"site_data_dir": f"/opt/homebrew/share{suffix}",
"site_config_dir": f"/opt/homebrew/share{suffix}",
"site_cache_dir": f"/opt/homebrew/var/cache{suffix}",
"site_runtime_dir": f"{home}/Library/Caches/TemporaryItems{suffix}",
gaborbernat marked this conversation as resolved.
Show resolved Hide resolved
}
if multipath:
expected_map["site_data_dir"] += f":/Library/Application Support{suffix}"
expected_map["site_config_dir"] += f":/Library/Application Support{suffix}"
expected_map["site_cache_dir"] += f":/Library/Caches{suffix}"
expected = expected_map[site_func]

assert result == expected
Loading