From f7ab87cb156dc9bd255646e7c5de0ed3e47d2c52 Mon Sep 17 00:00:00 2001 From: Scott Stevenson Date: Wed, 28 Oct 2020 19:36:08 +0000 Subject: [PATCH] Provide functions rather than variables Refactoring the API to provide functions rather than variables means that xdg will now respect changes to environment variables made after it is imported. This additionally makes testing easier. The previous variable based API is maintained for backward compatibility, but is no longer documented. --- README.md | 42 +++++--- src/xdg/__init__.py | 92 +++++++++++----- test/test_xdg.py | 255 +++++++++++++++----------------------------- 3 files changed, 178 insertions(+), 211 deletions(-) diff --git a/README.md b/README.md index 7235520..bcfb4bd 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,10 @@ # xdg -`xdg` is a tiny Python module which provides the variables defined by the [XDG -Base Directory Specification][spec], to save you from duplicating the same -snippet of logic in every Python utility you write that deals with user cache, -configuration, or data files. It has no external dependencies. +`xdg` is a Python module which provides functions to return paths to the +directories defined by the [XDG Base Directory Specification][spec], to save you +from duplicating the same snippet of logic in every Python utility you write +that deals with user cache, configuration, or data files. It has no external +dependencies. ## Installation @@ -21,23 +22,30 @@ Alternatively, since `xdg` is only a single file you may prefer to just copy ## Usage ```python -from xdg import (XDG_CACHE_HOME, XDG_CONFIG_DIRS, XDG_CONFIG_HOME, - XDG_DATA_DIRS, XDG_DATA_HOME, XDG_RUNTIME_DIR) +from xdg import ( + xdg_cache_home, + xdg_config_dirs, + xdg_config_home, + xdg_data_dirs, + xdg_data_home, + xdg_runtime_dir, +) ``` -`XDG_CACHE_HOME`, `XDG_CONFIG_HOME`, and `XDG_DATA_HOME` are [`pathlib.Path` -objects][path] containing the value of the environment variable of the same -name, or the default defined in the specification if the environment variable is -unset or empty. +`xdg_cache_home()`, `xdg_config_home()`, and `xdg_data_home()` return +[`pathlib.Path` objects][path] containing the value of the environment variable +named `XDG_CACHE_HOME`, `XDG_CONFIG_HOME`, and `XDG_DATA_HOME` respectively, or +the default defined in the specification if the environment variable is unset or +empty. -`XDG_CONFIG_DIRS` and `XDG_DATA_DIRS` are lists of `pathlib.Path` objects -containing the value of the environment variable of the same name split on -colons, or the default defined in the specification if the environment variable -is unset or empty. +`xdg_config_dirs()` and `xdg_data_dirs()` return a list of `pathlib.Path` +objects containing the value, split on colons, of the environment variable named +`XDG_CONFIG_DIRS` and `XDG_DATA_DIRS` respectively, or the default defined in +the specification if the environment variable is unset or empty. -`XDG_RUNTIME_DIR` is a `pathlib.Path` object containing the value of the -environment variable of the same name, or `None` if the environment variable is -unset. +`xdg_runtime_dir()` returns a `pathlib.Path` object containing the value of the +`XDG_RUNTIME_DIR` environment variable, or `None` if the environment variable is +not set. ## Copyright diff --git a/src/xdg/__init__.py b/src/xdg/__init__.py index 4a912b4..3e46422 100644 --- a/src/xdg/__init__.py +++ b/src/xdg/__init__.py @@ -16,18 +16,20 @@ """XDG Base Directory Specification variables. -XDG_CACHE_HOME, XDG_CONFIG_HOME, and XDG_DATA_HOME are pathlib.Path -objects containing the value of the environment variable of the same -name, or the default defined in the specification if the environment -variable is unset or empty. - -XDG_CONFIG_DIRS and XDG_DATA_DIRS are lists of pathlib.Path objects -containing the value of the environment variable of the same name split -on colons, or the default defined in the specification if the -environment variable is unset or empty. - -XDG_RUNTIME_DIR is a pathlib.Path object containing the value of the -environment variable of the same name, or None if the environment +xdg_cache_home(), xdg_config_home(), and xdg_data_home() return +pathlib.Path objects containing the value of the environment variable +named XDG_CACHE_HOME, XDG_CONFIG_HOME, and XDG_DATA_HOME respectively, +or the default defined in the specification if the environment variable +is unset or empty. + +xdg_config_dirs() and xdg_data_dirs() return a list of pathlib.Path +objects containing the value, split on colons, of the environment +variable named XDG_CONFIG_DIRS and XDG_DATA_DIRS respectively, or the +default defined in the specification if the environment variable is +unset or empty. + +xdg_runtime_dir() returns a pathlib.Path object containing the value of +the XDG_RUNTIME_DIR environment variable, or None if the environment variable is not set. """ @@ -39,6 +41,12 @@ from typing import List, Optional __all__ = [ + "xdg_cache_home", + "xdg_config_dirs", + "xdg_config_home", + "xdg_data_dirs", + "xdg_data_home", + "xdg_runtime_dir", "XDG_CACHE_HOME", "XDG_CONFIG_DIRS", "XDG_CONFIG_HOME", @@ -47,7 +55,10 @@ "XDG_RUNTIME_DIR", ] -HOME = Path(os.path.expandvars("$HOME")) + +def _home_dir() -> Path: + """Return a Path corresponding to the user's home directory.""" + return Path(os.path.expandvars("$HOME")) def _path_from_env(variable: str, default: Path) -> Path: @@ -105,20 +116,51 @@ def _paths_from_env(variable: str, default: List[Path]) -> List[Path]: return default -XDG_CACHE_HOME = _path_from_env("XDG_CACHE_HOME", HOME / ".cache") +def xdg_cache_home() -> Path: + """Return a Path corresponding to XDG_CACHE_HOME.""" + return _path_from_env("XDG_CACHE_HOME", _home_dir() / ".cache") -XDG_CONFIG_DIRS = _paths_from_env("XDG_CONFIG_DIRS", [Path("/etc/xdg")]) -XDG_CONFIG_HOME = _path_from_env("XDG_CONFIG_HOME", HOME / ".config") +def xdg_config_dirs() -> List[Path]: + """Return a list of Paths corresponding to XDG_CONFIG_DIRS.""" + return _paths_from_env("XDG_CONFIG_DIRS", [Path("/etc/xdg")]) -XDG_DATA_DIRS = _paths_from_env( - "XDG_DATA_DIRS", - [Path(path) for path in "/usr/local/share/:/usr/share/".split(":")], -) -XDG_DATA_HOME = _path_from_env("XDG_DATA_HOME", HOME / ".local" / "share") +def xdg_config_home() -> Path: + """Return a Path corresponding to XDG_CONFIG_HOME.""" + return _path_from_env("XDG_CONFIG_HOME", _home_dir() / ".config") + + +def xdg_data_dirs() -> List[Path]: + """Return a list of Paths corresponding to XDG_DATA_DIRS.""" + return _paths_from_env( + "XDG_DATA_DIRS", + [Path(path) for path in "/usr/local/share/:/usr/share/".split(":")], + ) + -try: - XDG_RUNTIME_DIR: Optional[Path] = Path(os.environ["XDG_RUNTIME_DIR"]) -except KeyError: - XDG_RUNTIME_DIR = None +def xdg_data_home() -> Path: + """Return a Path corresponding to XDG_DATA_HOME.""" + return _path_from_env("XDG_DATA_HOME", _home_dir() / ".local" / "share") + + +def xdg_runtime_dir() -> Optional[Path]: + """Return a Path corresponding to XDG_RUNTIME_DIR. + + If the XDG_RUNTIME_DIR environment variable is not set, None will be + returned as per the specification. + + """ + try: + return Path(os.environ["XDG_RUNTIME_DIR"]) + except KeyError: + return None + + +# The following variables are deprecated, but remain for backward compatibility. +XDG_CACHE_HOME = xdg_cache_home() +XDG_CONFIG_DIRS = xdg_config_dirs() +XDG_CONFIG_HOME = xdg_config_home() +XDG_DATA_DIRS = xdg_data_dirs() +XDG_DATA_HOME = xdg_data_home() +XDG_RUNTIME_DIR = xdg_runtime_dir() diff --git a/test/test_xdg.py b/test/test_xdg.py index 8d6bbee..2e9ad9d 100644 --- a/test/test_xdg.py +++ b/test/test_xdg.py @@ -1,213 +1,130 @@ """Test suite for xdg.""" -# pylint: disable=import-outside-toplevel -# pylint: disable=no-self-use -# pylint: disable=redefined-outer-name -# pylint: disable=unused-argument - import os -import sys from pathlib import Path -from typing import TYPE_CHECKING, Callable -import pytest +from _pytest.monkeypatch import MonkeyPatch -if TYPE_CHECKING: - from _pytest.monkeypatch import MonkeyPatch +import xdg HOME_DIR = Path("/homedir") -@pytest.fixture # type: ignore -def unimport() -> None: - """Ensure xdg is absent from sys.modules.""" - try: - del sys.modules["xdg"] - except KeyError: - pass - - -class TestXdgCacheHome: - """Tests for XDG_CACHE_HOME.""" - - def test_unset( - self, monkeypatch: "MonkeyPatch", unimport: Callable - ) -> None: - """Test when XDG_CACHE_HOME is unset.""" - monkeypatch.delenv("XDG_CACHE_HOME", raising=False) - monkeypatch.setenv("HOME", os.fspath(HOME_DIR)) - from xdg import XDG_CACHE_HOME - - assert XDG_CACHE_HOME == HOME_DIR / ".cache" - - def test_empty( - self, monkeypatch: "MonkeyPatch", unimport: Callable - ) -> None: - """Test when XDG_CACHE_HOME is empty.""" - monkeypatch.setenv("HOME", os.fspath(HOME_DIR)) - monkeypatch.setenv("XDG_CACHE_HOME", "") - from xdg import XDG_CACHE_HOME - - assert XDG_CACHE_HOME == HOME_DIR / ".cache" - - def test_set(self, monkeypatch: "MonkeyPatch", unimport: Callable) -> None: - """Test when XDG_CACHE_HOME is set.""" - monkeypatch.setenv("XDG_CACHE_HOME", "/xdg_cache_home") - from xdg import XDG_CACHE_HOME - - assert XDG_CACHE_HOME == Path("/xdg_cache_home") - - -class TestXdgConfigDirs: - """Tests for XDG_CONFIG_DIRS.""" - - def test_unset( - self, monkeypatch: "MonkeyPatch", unimport: Callable - ) -> None: - """Test when XDG_CONFIG_DIRS is unset.""" - monkeypatch.delenv("XDG_CONFIG_DIRS", raising=False) - from xdg import XDG_CONFIG_DIRS - - assert XDG_CONFIG_DIRS == [Path("/etc/xdg")] - - def test_empty( - self, monkeypatch: "MonkeyPatch", unimport: Callable - ) -> None: - """Test when XDG_CONFIG_DIRS is empty.""" - monkeypatch.setenv("XDG_CONFIG_DIRS", "") - from xdg import XDG_CONFIG_DIRS - - assert XDG_CONFIG_DIRS == [Path("/etc/xdg")] +def test_xdg_cache_home_unset(monkeypatch: MonkeyPatch) -> None: + """Test xdg_cache_home when XDG_CACHE_HOME is unset.""" + monkeypatch.delenv("XDG_CACHE_HOME", raising=False) + monkeypatch.setenv("HOME", os.fspath(HOME_DIR)) + assert xdg.xdg_cache_home() == HOME_DIR / ".cache" - def test_set(self, monkeypatch: "MonkeyPatch", unimport: Callable) -> None: - """Test when XDG_CONFIG_DIRS is set.""" - monkeypatch.setenv("XDG_CONFIG_DIRS", "/first:/sec/ond") - from xdg import XDG_CONFIG_DIRS - assert XDG_CONFIG_DIRS == [Path("/first"), Path("/sec/ond")] +def test_xdg_cache_home_empty(monkeypatch: MonkeyPatch) -> None: + """Test xdg_cache_home when XDG_CACHE_HOME is empty.""" + monkeypatch.setenv("HOME", os.fspath(HOME_DIR)) + monkeypatch.setenv("XDG_CACHE_HOME", "") + assert xdg.xdg_cache_home() == HOME_DIR / ".cache" -class TestXdgConfigHome: - """Tests for XDG_CONFIG_HOME.""" +def test_xdg_cache_home_set(monkeypatch: MonkeyPatch) -> None: + """Test xdg_cache_home when XDG_CACHE_HOME is set.""" + monkeypatch.setenv("XDG_CACHE_HOME", "/xdg_cache_home") + assert xdg.xdg_cache_home() == Path("/xdg_cache_home") - def test_unset( - self, monkeypatch: "MonkeyPatch", unimport: Callable - ) -> None: - """Test when XDG_CONFIG_HOME is unset.""" - monkeypatch.delenv("XDG_CONFIG_HOME", raising=False) - monkeypatch.setenv("HOME", os.fspath(HOME_DIR)) - from xdg import XDG_CONFIG_HOME - assert XDG_CONFIG_HOME == HOME_DIR / ".config" +def test_xdg_config_dirs_unset(monkeypatch: MonkeyPatch) -> None: + """Test xdg_config_dirs when XDG_CONFIG_DIRS is unset.""" + monkeypatch.delenv("XDG_CONFIG_DIRS", raising=False) + assert xdg.xdg_config_dirs() == [Path("/etc/xdg")] - def test_empty( - self, monkeypatch: "MonkeyPatch", unimport: Callable - ) -> None: - """Test when XDG_CONFIG_HOME is empty.""" - monkeypatch.setenv("HOME", os.fspath(HOME_DIR)) - monkeypatch.setenv("XDG_CONFIG_HOME", "") - from xdg import XDG_CONFIG_HOME - assert XDG_CONFIG_HOME == HOME_DIR / ".config" +def test_xdg_config_dirs_empty(monkeypatch: MonkeyPatch) -> None: + """Test xdg_config_dirs when XDG_CONFIG_DIRS is empty.""" + monkeypatch.setenv("XDG_CONFIG_DIRS", "") + assert xdg.xdg_config_dirs() == [Path("/etc/xdg")] - def test_set(self, monkeypatch: "MonkeyPatch", unimport: Callable) -> None: - """Test when XDG_CONFIG_HOME is set.""" - monkeypatch.setenv("XDG_CONFIG_HOME", "/xdg_config_home") - from xdg import XDG_CONFIG_HOME - assert XDG_CONFIG_HOME == Path("/xdg_config_home") +def test_xdg_config_dirs_set(monkeypatch: MonkeyPatch) -> None: + """Test xdg_config_dirs when XDG_CONFIG_DIRS is set.""" + monkeypatch.setenv("XDG_CONFIG_DIRS", "/first:/sec/ond") + assert xdg.xdg_config_dirs() == [Path("/first"), Path("/sec/ond")] -class TestXdgDataDirs: - """Tests for XDG_DATA_DIRS.""" +def test_xdg_config_home_unset(monkeypatch: MonkeyPatch) -> None: + """Test xdg_config_home when XDG_CONFIG_HOME is unset.""" + monkeypatch.delenv("XDG_CONFIG_HOME", raising=False) + monkeypatch.setenv("HOME", os.fspath(HOME_DIR)) + assert xdg.xdg_config_home() == HOME_DIR / ".config" - def test_unset( - self, monkeypatch: "MonkeyPatch", unimport: Callable - ) -> None: - """Test when XDG_DATA_DIRS is unset.""" - monkeypatch.delenv("XDG_DATA_DIRS", raising=False) - from xdg import XDG_DATA_DIRS - assert XDG_DATA_DIRS == [ - Path("/usr/local/share/"), - Path("/usr/share/"), - ] +def test_xdg_config_home_empty(monkeypatch: MonkeyPatch) -> None: + """Test xdg_config_home when XDG_CONFIG_HOME is empty.""" + monkeypatch.setenv("HOME", os.fspath(HOME_DIR)) + monkeypatch.setenv("XDG_CONFIG_HOME", "") + assert xdg.xdg_config_home() == HOME_DIR / ".config" - def test_empty( - self, monkeypatch: "MonkeyPatch", unimport: Callable - ) -> None: - """Test when XDG_DATA_DIRS is empty.""" - monkeypatch.setenv("XDG_DATA_DIRS", "") - from xdg import XDG_DATA_DIRS - assert XDG_DATA_DIRS == [ - Path("/usr/local/share/"), - Path("/usr/share/"), - ] +def test_xdg_config_home_set(monkeypatch: MonkeyPatch) -> None: + """Test xdg_config_home when XDG_CONFIG_HOME is set.""" + monkeypatch.setenv("XDG_CONFIG_HOME", "/xdg_config_home") + assert xdg.xdg_config_home() == Path("/xdg_config_home") - def test_set(self, monkeypatch: "MonkeyPatch", unimport: Callable) -> None: - """Test when XDG_DATA_DIRS is set.""" - monkeypatch.setenv("XDG_DATA_DIRS", "/first/:/sec/ond/") - from xdg import XDG_DATA_DIRS - assert XDG_DATA_DIRS == [Path("/first/"), Path("/sec/ond/")] +def test_xdg_data_dirs_unset(monkeypatch: MonkeyPatch) -> None: + """Test xdg_data_dirs when XDG_DATA_DIRS is unset.""" + monkeypatch.delenv("XDG_DATA_DIRS", raising=False) + assert xdg.xdg_data_dirs() == [ + Path("/usr/local/share/"), + Path("/usr/share/"), + ] -class TestXdgDataHome: - """Tests for XDG_DATA_HOME.""" +def test_xdg_data_dirs_empty(monkeypatch: MonkeyPatch) -> None: + """Test xdg_data_dirs when XDG_DATA_DIRS is empty.""" + monkeypatch.setenv("XDG_DATA_DIRS", "") + assert xdg.xdg_data_dirs() == [ + Path("/usr/local/share/"), + Path("/usr/share/"), + ] - def test_unset( - self, monkeypatch: "MonkeyPatch", unimport: Callable - ) -> None: - """Test when XDG_DATA_HOME is unset.""" - monkeypatch.delenv("XDG_DATA_HOME", raising=False) - monkeypatch.setenv("HOME", os.fspath(HOME_DIR)) - from xdg import XDG_DATA_HOME - assert XDG_DATA_HOME == HOME_DIR / ".local" / "share" +def test_xdg_data_dirs_set(monkeypatch: MonkeyPatch) -> None: + """Test xdg_data_dirs when XDG_DATA_DIRS is set.""" + monkeypatch.setenv("XDG_DATA_DIRS", "/first/:/sec/ond/") + assert xdg.xdg_data_dirs() == [Path("/first/"), Path("/sec/ond/")] - def test_empty( - self, monkeypatch: "MonkeyPatch", unimport: Callable - ) -> None: - """Test when XDG_DATA_HOME is empty.""" - monkeypatch.setenv("HOME", os.fspath(HOME_DIR)) - monkeypatch.setenv("XDG_DATA_HOME", "") - from xdg import XDG_DATA_HOME - assert XDG_DATA_HOME == HOME_DIR / ".local" / "share" +def test_xdg_data_home_unset(monkeypatch: MonkeyPatch) -> None: + """Test xdg_data_home when XDG_DATA_HOME is unset.""" + monkeypatch.delenv("XDG_DATA_HOME", raising=False) + monkeypatch.setenv("HOME", os.fspath(HOME_DIR)) + assert xdg.xdg_data_home() == HOME_DIR / ".local" / "share" - def test_set(self, monkeypatch: "MonkeyPatch", unimport: Callable) -> None: - """Test when XDG_DATA_HOME is set.""" - monkeypatch.setenv("XDG_DATA_HOME", "/xdg_data_home") - from xdg import XDG_DATA_HOME - assert XDG_DATA_HOME == Path("/xdg_data_home") +def test_xdg_data_home_empty(monkeypatch: MonkeyPatch) -> None: + """Test xdg_data_home when XDG_DATA_HOME is empty.""" + monkeypatch.setenv("HOME", os.fspath(HOME_DIR)) + monkeypatch.setenv("XDG_DATA_HOME", "") + assert xdg.xdg_data_home() == HOME_DIR / ".local" / "share" -class TestXdgRuntimeDir: - """Tests for XDG_RUNTIME_DIR.""" +def test_xdg_data_home_set(monkeypatch: MonkeyPatch) -> None: + """Test xdg_data_home when XDG_DATA_HOME is set.""" + monkeypatch.setenv("XDG_DATA_HOME", "/xdg_data_home") + assert xdg.xdg_data_home() == Path("/xdg_data_home") - def test_unset( - self, monkeypatch: "MonkeyPatch", unimport: Callable - ) -> None: - """Test when XDG_RUNTIME_DIR is unset.""" - monkeypatch.delenv("XDG_RUNTIME_DIR", raising=False) - from xdg import XDG_RUNTIME_DIR - assert XDG_RUNTIME_DIR is None +def test_xdg_runtime_dir_unset(monkeypatch: MonkeyPatch) -> None: + """Test xdg_runtime_dir when XDG_RUNTIME_DIR is unset.""" + monkeypatch.delenv("XDG_RUNTIME_DIR", raising=False) + assert xdg.xdg_runtime_dir() is None - def test_empty( - self, monkeypatch: "MonkeyPatch", unimport: Callable - ) -> None: - """Test when XDG_RUNTIME_DIR is empty.""" - monkeypatch.setenv("XDG_RUNTIME_DIR", "") - from xdg import XDG_RUNTIME_DIR - assert XDG_RUNTIME_DIR == Path("") +def test_xdg_runtime_dir_empty(monkeypatch: MonkeyPatch) -> None: + """Test xdg_runtime_dir when XDG_RUNTIME_DIR is empty.""" + monkeypatch.setenv("XDG_RUNTIME_DIR", "") + assert xdg.xdg_runtime_dir() == Path("") - def test_set(self, monkeypatch: "MonkeyPatch", unimport: Callable) -> None: - """Test when XDG_RUNTIME_DIR is set.""" - monkeypatch.setenv("XDG_RUNTIME_DIR", "/xdg_runtime_dir") - from xdg import XDG_RUNTIME_DIR - assert XDG_RUNTIME_DIR == Path("/xdg_runtime_dir") +def test_xdg_runtime_dir_set(monkeypatch: MonkeyPatch) -> None: + """Test xdg_runtime_dir when XDG_RUNTIME_DIR is set.""" + monkeypatch.setenv("XDG_RUNTIME_DIR", "/xdg_runtime_dir") + assert xdg.xdg_runtime_dir() == Path("/xdg_runtime_dir")