Skip to content
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
24 changes: 24 additions & 0 deletions docs/platforms.rst
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,30 @@ Default paths
This property does not append ``appname`` or ``version``. It returns the directory
where user-installed executables and scripts are placed.

``site_bin_dir``
~~~~~~~~~~~~~~~~

.. list-table::
:widths: 20 80

* - Linux
- ``/usr/local/bin``
* - macOS
- ``/usr/local/bin``
* - Windows
- ``C:\ProgramData\bin``
* - Android
- Same as ``user_bin_dir``

.. note::

This property does not append ``appname`` or ``version``. It returns the directory
where system-wide executables and scripts are placed. On Unix/Linux, this follows
the `FHS 3.0 <https://refspecs.linuxfoundation.org/FHS_3.0/fhs/index.html>`_
standard for locally-installed software. On Windows, it mirrors the ``site_data_dir``
pattern using ``%ProgramData%``, following the precedent set by
`Chocolatey <https://docs.chocolatey.org/en-us/choco/setup>`_.

macOS
-----

Expand Down
12 changes: 12 additions & 0 deletions src/platformdirs/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,11 @@ def user_bin_dir() -> str:
return PlatformDirs().user_bin_dir


def site_bin_dir() -> str:
""":returns: bin directory shared by users"""
return PlatformDirs().site_bin_dir


def user_applications_dir() -> str:
""":returns: applications directory tied to the user"""
return PlatformDirs().user_applications_dir
Expand Down Expand Up @@ -698,6 +703,11 @@ def user_bin_path() -> Path:
return PlatformDirs().user_bin_path


def site_bin_path() -> Path:
""":returns: bin path shared by users"""
return PlatformDirs().site_bin_path


def user_applications_path() -> Path:
""":returns: applications path tied to the user"""
return PlatformDirs().user_applications_path
Expand Down Expand Up @@ -777,6 +787,8 @@ def site_runtime_path(
"__version_info__",
"site_applications_dir",
"site_applications_path",
"site_bin_dir",
"site_bin_path",
"site_cache_dir",
"site_cache_path",
"site_config_dir",
Expand Down
1 change: 1 addition & 0 deletions src/platformdirs/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"user_videos_dir",
"user_music_dir",
"user_bin_dir",
"site_bin_dir",
"user_applications_dir",
"user_runtime_dir",
"site_data_dir",
Expand Down
5 changes: 5 additions & 0 deletions src/platformdirs/android.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,11 @@ def user_bin_dir(self) -> str:
""":return: bin directory tied to the user, e.g. ``/data/user/<userid>/<packagename>/files/bin``"""
return os.path.join(cast("str", _android_folder()), "files", "bin") # noqa: PTH118

@property
def site_bin_dir(self) -> str:
""":return: bin directory shared by users, same as `user_bin_dir`"""
return self.user_bin_dir

@property
def user_applications_dir(self) -> str:
""":return: applications directory tied to the user, same as `user_data_dir`"""
Expand Down
10 changes: 10 additions & 0 deletions src/platformdirs/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,11 @@ def user_desktop_dir(self) -> str:
def user_bin_dir(self) -> str:
""":return: bin directory tied to the user"""

@property
@abstractmethod
def site_bin_dir(self) -> str:
""":return: bin directory shared by users"""

@property
@abstractmethod
def user_applications_dir(self) -> str:
Expand Down Expand Up @@ -314,6 +319,11 @@ def user_bin_path(self) -> Path:
""":return: bin path tied to the user"""
return Path(self.user_bin_dir)

@property
def site_bin_path(self) -> Path:
""":return: bin path shared by users"""
return Path(self.site_bin_dir)

@property
def user_applications_path(self) -> Path:
""":return: applications path tied to the user"""
Expand Down
5 changes: 5 additions & 0 deletions src/platformdirs/macos.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,11 @@ def user_bin_dir(self) -> str:
""":return: bin directory tied to the user, e.g. ``~/.local/bin``"""
return os.path.expanduser("~/.local/bin") # noqa: PTH111

@property
def site_bin_dir(self) -> str:
""":return: bin directory shared by users, e.g. ``/usr/local/bin``"""
return "/usr/local/bin"

@property
def user_applications_dir(self) -> str:
""":return: applications directory tied to the user, e.g. ``~/Applications``"""
Expand Down
10 changes: 10 additions & 0 deletions src/platformdirs/unix.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,11 @@ def user_bin_dir(self) -> str:
""":return: bin directory tied to the user, e.g. ``~/.local/bin``"""
return os.path.expanduser("~/.local/bin") # noqa: PTH111

@property
def site_bin_dir(self) -> str:
""":return: bin directory shared by users, e.g. ``/usr/local/bin``"""
return "/usr/local/bin"

@property
def user_applications_dir(self) -> str:
""":return: applications directory tied to the user, e.g. ``~/.local/share/applications``"""
Expand Down Expand Up @@ -266,6 +271,11 @@ def user_runtime_dir(self) -> str:
""":return: runtime directory tied to the user, or site equivalent when root with ``use_site_for_root``"""
return self.site_runtime_dir if self._use_site else super().user_runtime_dir

@property
def user_bin_dir(self) -> str:
""":return: bin directory tied to the user, or site equivalent when root with ``use_site_for_root``"""
return self.site_bin_dir if self._use_site else super().user_bin_dir


def _get_user_media_dir(env_var: str, fallback_tilde_path: str) -> str:
if media_dir := _get_user_dirs_folder(env_var):
Expand Down
5 changes: 5 additions & 0 deletions src/platformdirs/windows.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,11 @@ def user_bin_dir(self) -> str:
""":return: bin directory tied to the user, e.g. ``%LOCALAPPDATA%\\Programs``"""
return os.path.normpath(os.path.join(get_win_folder("CSIDL_LOCAL_APPDATA"), "Programs")) # noqa: PTH118

@property
def site_bin_dir(self) -> str:
""":return: bin directory shared by users, e.g. ``C:\\ProgramData\\bin``"""
return os.path.normpath(os.path.join(get_win_folder("CSIDL_COMMON_APPDATA"), "bin")) # noqa: PTH118

@property
def user_applications_dir(self) -> str:
""":return: applications directory tied to the user, e.g. ``Start Menu\\Programs``"""
Expand Down
1 change: 1 addition & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"user_videos_dir",
"user_music_dir",
"user_bin_dir",
"site_bin_dir",
"user_applications_dir",
"user_runtime_dir",
"site_data_dir",
Expand Down
1 change: 1 addition & 0 deletions tests/test_android.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ def test_android(mocker: MockerFixture, params: dict[str, Any], func: str) -> No
"user_music_dir": "/storage/emulated/0/Music",
"user_desktop_dir": "/storage/emulated/0/Desktop",
"user_bin_dir": "/data/data/com.example/files/bin",
"site_bin_dir": "/data/data/com.example/files/bin",
"user_applications_dir": f"/data/data/com.example/files{suffix}",
"site_applications_dir": f"/data/data/com.example/files{suffix}",
"user_runtime_dir": f"/data/data/com.example/cache{suffix}{'' if not params.get('opinion', True) else val}",
Expand Down
2 changes: 2 additions & 0 deletions tests/test_macos.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ def test_macos(mocker: MockerFixture, params: dict[str, Any], func: str) -> None
"user_music_dir": f"{home}/Music",
"user_desktop_dir": f"{home}/Desktop",
"user_bin_dir": f"{home}/.local/bin",
"site_bin_dir": "/usr/local/bin",
"user_applications_dir": f"{home}/Applications",
"site_applications_dir": "/Applications",
"user_runtime_dir": f"{home}/Library/Caches/TemporaryItems{suffix}",
Expand Down Expand Up @@ -270,6 +271,7 @@ def test_macos_xdg_empty_falls_back(
"user_music_dir": f"{home}/Music",
"user_desktop_dir": f"{home}/Desktop",
"user_bin_dir": f"{home}/.local/bin",
"site_bin_dir": "/usr/local/bin",
"user_applications_dir": f"{home}/Applications",
}
assert getattr(MacOS(), prop) == expected_map[prop]
Expand Down
2 changes: 2 additions & 0 deletions tests/test_unix.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ def _func_to_path(func: str) -> XDGVariable | None:
"user_log_dir": XDGVariable("XDG_STATE_HOME", "~/.local/state"),
"user_runtime_dir": XDGVariable("XDG_RUNTIME_DIR", f"{gettempdir()}/runtime-1234"),
"user_bin_dir": None,
"site_bin_dir": None,
"user_applications_dir": None,
"site_applications_dir": None,
"site_log_dir": None,
Expand Down Expand Up @@ -346,6 +347,7 @@ def test_user_media_dir_no_user_dirs_file(
("user_state_dir", os.path.join("/var/lib", "foo")), # noqa: PTH118
("user_log_dir", os.path.join("/var/log", "foo")), # noqa: PTH118
("user_runtime_dir", os.path.join("/run", "foo")), # noqa: PTH118
("user_bin_dir", "/usr/local/bin"),
]


Expand Down
1 change: 1 addition & 0 deletions tests/test_windows.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ def test_windows(params: dict[str, Any], func: str) -> None:
"user_music_dir": os.path.normpath(_WIN_FOLDERS["CSIDL_MYMUSIC"]),
"user_desktop_dir": os.path.normpath(_WIN_FOLDERS["CSIDL_DESKTOPDIRECTORY"]),
"user_bin_dir": os.path.join(_LOCAL, "Programs"), # noqa: PTH118
"site_bin_dir": os.path.join(_COMMON, "bin"), # noqa: PTH118
"user_applications_dir": os.path.normpath(_WIN_FOLDERS["CSIDL_PROGRAMS"]),
"site_applications_dir": os.path.normpath(_WIN_FOLDERS["CSIDL_COMMON_PROGRAMS"]),
"user_runtime_dir": temp,
Expand Down