Skip to content

Commit

Permalink
platformdirs: introduce user_pictures_dir()
Browse files Browse the repository at this point in the history
Introduces the means to acquire the user's pictures directory path.

Feature suggested from tox-dev#141.
  • Loading branch information
kemzeb committed Apr 25, 2023
1 parent b866d40 commit 40a75fd
Show file tree
Hide file tree
Showing 11 changed files with 115 additions and 0 deletions.
14 changes: 14 additions & 0 deletions src/platformdirs/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,12 @@ def user_documents_dir() -> str:
"""
return PlatformDirs().user_documents_dir

def user_pictures_dir() -> str:
"""
:returns: pictures directory tied to the user
"""
return PlatformDirs().user_pictures_dir


def user_runtime_dir(
appname: str | None = None,
Expand Down Expand Up @@ -465,6 +471,12 @@ def user_documents_path() -> Path:
"""
return PlatformDirs().user_documents_path

def user_pictures_path() -> Path:
"""
:returns: pictures path tied to the user
"""
return PlatformDirs().user_pictures_path


def user_runtime_path(
appname: str | None = None,
Expand Down Expand Up @@ -502,6 +514,7 @@ def user_runtime_path(
"user_state_dir",
"user_log_dir",
"user_documents_dir",
"user_pictures_dir",
"user_runtime_dir",
"site_data_dir",
"site_config_dir",
Expand All @@ -512,6 +525,7 @@ def user_runtime_path(
"user_state_path",
"user_log_path",
"user_documents_path",
"user_pictures_path",
"user_runtime_path",
"site_data_path",
"site_config_path",
Expand Down
1 change: 1 addition & 0 deletions src/platformdirs/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"user_state_dir",
"user_log_dir",
"user_documents_dir",
"user_pictures_dir",
"user_runtime_dir",
"site_data_dir",
"site_config_dir",
Expand Down
23 changes: 23 additions & 0 deletions src/platformdirs/android.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,13 @@ def user_documents_dir(self) -> str:
"""
return _android_documents_folder()

@property
def user_pictures_dir(self) -> str:
"""
:return: pictures directory tied to the user e.g. ``/storage/emulated/0/Pictures``
"""
return _android_pictures_folder()

@property
def user_runtime_dir(self) -> str:
"""
Expand Down Expand Up @@ -121,6 +128,22 @@ def _android_documents_folder() -> str:
return documents_dir


@lru_cache(maxsize=1)
def _android_pictures_folder() -> str:
""":return: pictures folder for the Android OS"""
# Get directories with pyjnius
try:
from jnius import autoclass

Context = autoclass("android.content.Context") # noqa: N806
Environment = autoclass("android.os.Environment") # noqa: N806
pictures_dir: str = Context.getExternalFilesDir(Environment.DIRECTORY_PICTURES).getAbsolutePath()
except Exception:
pictures_dir = "/storage/emulated/0/Pictures"

return pictures_dir


__all__ = [
"Android",
]
10 changes: 10 additions & 0 deletions src/platformdirs/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,11 @@ def user_log_dir(self) -> str:
def user_documents_dir(self) -> str:
""":return: documents directory tied to the user"""

@property
@abstractmethod
def user_pictures_dir(self) -> str:
""":return: pictures directory tied to the user"""

@property
@abstractmethod
def user_runtime_dir(self) -> str:
Expand Down Expand Up @@ -173,6 +178,11 @@ def user_documents_path(self) -> Path:
""":return: documents path tied to the user"""
return Path(self.user_documents_dir)

@property
def user_pictures_path(self) -> Path:
""":return: pictures path tied to the user"""
return Path(self.user_pictures_dir)

@property
def user_runtime_path(self) -> Path:
""":return: runtime 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 @@ -59,6 +59,11 @@ def user_documents_dir(self) -> str:
""":return: documents directory tied to the user, e.g. ``~/Documents``"""
return os.path.expanduser("~/Documents")

@property
def user_pictures_dir(self) -> str:
""":return: pictures directory tied to the user, e.g. ``~/Pictures``"""
return os.path.expanduser("~/Pictures")

@property
def user_runtime_dir(self) -> str:
""":return: runtime directory tied to the user, e.g. ``~/Library/Caches/TemporaryItems/$appname/$version``"""
Expand Down
13 changes: 13 additions & 0 deletions src/platformdirs/unix.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,19 @@ def user_documents_dir(self) -> str:

return documents_dir

@property
def user_pictures_dir(self) -> str:
"""
:return: pictures directory tied to the user, e.g. ``~/Pictures``
"""
pictures_dir = _get_user_dirs_folder("XDG_PICTURES_DIR")
if pictures_dir is None:
pictures_dir = os.environ.get("XDG_PICTURES_DIR", "").strip()
if not pictures_dir:
pictures_dir = os.path.expanduser("~/Pictures")

return pictures_dir

@property
def user_runtime_dir(self) -> str:
"""
Expand Down
12 changes: 12 additions & 0 deletions src/platformdirs/windows.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,13 @@ def user_documents_dir(self) -> str:
"""
return os.path.normpath(get_win_folder("CSIDL_PERSONAL"))

@property
def user_pictures_dir(self) -> str:
"""
:return: pictures directory tied to the user e.g. ``%USERPROFILE%\\Pictures``
"""
return os.path.normpath(get_win_folder("CSIDL_MYPICTURES"))

@property
def user_runtime_dir(self) -> str:
"""
Expand All @@ -116,6 +123,9 @@ def get_win_folder_from_env_vars(csidl_name: str) -> str:
if csidl_name == "CSIDL_PERSONAL": # does not have an environment name
return os.path.join(os.path.normpath(os.environ["USERPROFILE"]), "Documents")

if csidl_name == "CSIDL_MYPICTURES": # does not have an environment name
return os.path.join(os.path.normpath(os.environ["USERPROFILE"]), "Pictures")

env_var_name = {
"CSIDL_APPDATA": "APPDATA",
"CSIDL_COMMON_APPDATA": "ALLUSERSPROFILE",
Expand All @@ -141,6 +151,7 @@ def get_win_folder_from_registry(csidl_name: str) -> str:
"CSIDL_COMMON_APPDATA": "Common AppData",
"CSIDL_LOCAL_APPDATA": "Local AppData",
"CSIDL_PERSONAL": "Personal",
"CSIDL_MYPICTURES": "My Pictures",
}.get(csidl_name)
if shell_folder_name is None:
raise ValueError(f"Unknown CSIDL name: {csidl_name}")
Expand All @@ -160,6 +171,7 @@ def get_win_folder_via_ctypes(csidl_name: str) -> str:
"CSIDL_COMMON_APPDATA": 35,
"CSIDL_LOCAL_APPDATA": 28,
"CSIDL_PERSONAL": 5,
"CSIDL_MYPICTURES": 39,
}.get(csidl_name)
if csidl_const is None:
raise ValueError(f"Unknown CSIDL name: {csidl_name}")
Expand Down
1 change: 1 addition & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"user_state_dir",
"user_log_dir",
"user_documents_dir",
"user_pictures_dir",
"user_runtime_dir",
"site_data_dir",
"site_config_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 @@ -52,6 +52,7 @@ def test_android(mocker: MockerFixture, params: dict[str, Any], func: str) -> No
"user_state_dir": f"/data/data/com.example/files{suffix}",
"user_log_dir": f"/data/data/com.example/cache{suffix}{'' if params.get('opinion', True) is False else '/log'}",
"user_documents_dir": "/storage/emulated/0/Documents",
"user_pictures_dir": "/storage/emulated/0/Pictures",
"user_runtime_dir": f"/data/data/com.example/cache{suffix}{'' if not params.get('opinion', True) else '/tmp'}",
}
expected = expected_map[func]
Expand Down
1 change: 1 addition & 0 deletions tests/test_macos.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ def test_macos(params: dict[str, Any], func: str) -> None:
"user_state_dir": f"{home}/Library/Application Support{suffix}",
"user_log_dir": f"{home}/Library/Logs{suffix}",
"user_documents_dir": f"{home}/Documents",
"user_pictures_dir": f"{home}/Pictures",
"user_runtime_dir": f"{home}/Library/Caches/TemporaryItems{suffix}",
}
expected = expected_map[func]
Expand Down
34 changes: 34 additions & 0 deletions tests/test_unix.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,40 @@ def test_user_documents_dir_default(mocker: MockerFixture) -> None:
assert Unix().user_documents_dir == "/home/example/Documents"


def test_user_pictures_dir(mocker: MockerFixture) -> None:
example_path = "/home/example/ExamplePicturesFolder"
mock = mocker.patch("platformdirs.unix._get_user_dirs_folder")
mock.return_value = example_path
assert Unix().user_pictures_dir == example_path


def test_user_pictures_dir_env_var(mocker: MockerFixture) -> None:
# Mock pictures dir not being in user-dirs.dirs file
mock = mocker.patch("platformdirs.unix._get_user_dirs_folder")
mock.return_value = None

example_path = "/home/example/ExamplePicturesFolder"
mocker.patch.dict(os.environ, {"XDG_PICTURES_DIR": example_path})

assert Unix().user_pictures_dir == example_path


def test_user_pictures_dir_default(mocker: MockerFixture) -> None:
# Mock pictures dir not being in user-dirs.dirs file
mock = mocker.patch("platformdirs.unix._get_user_dirs_folder")
mock.return_value = None

# Mock no XDG_PICTURES_DIR env variable being set
mocker.patch.dict(os.environ, {"XDG_PICTURES_DIR": ""})

# Mock home directory
mocker.patch.dict(os.environ, {"HOME": "/home/example"})
# Mock home directory for running the test on Windows
mocker.patch.dict(os.environ, {"USERPROFILE": "/home/example"})

assert Unix().user_pictures_dir == "/home/example/Pictures"


class XDGVariable(typing.NamedTuple):
name: str
default_value: str
Expand Down

0 comments on commit 40a75fd

Please sign in to comment.