Skip to content
Snippets Groups Projects
Commit a56f8c44 authored by André Anjos's avatar André Anjos :speech_balloon:
Browse files

Implements automatic support $XDG_CONFIG_HOME, closes #3

parent e3235a06
No related branches found
No related tags found
1 merge request!15Implements automatic support $XDG_CONFIG_HOME, closes #3
Pipeline #68652 passed
...@@ -27,11 +27,13 @@ requirements: ...@@ -27,11 +27,13 @@ requirements:
- click {{ click }} - click {{ click }}
- tomli {{ tomli }} - tomli {{ tomli }}
- tomli-w {{ tomli_w }} - tomli-w {{ tomli_w }}
- xdg {{ xdg }}
run: run:
- python >=3.9 - python >=3.9
- {{ pin_compatible('click') }} - {{ pin_compatible('click') }}
- {{ pin_compatible('tomli') }} - {{ pin_compatible('tomli') }}
- {{ pin_compatible('tomli-w') }} - {{ pin_compatible('tomli-w') }}
- {{ pin_compatible('xdg') }}
test: test:
source_files: source_files:
......
...@@ -11,3 +11,4 @@ ...@@ -11,3 +11,4 @@
.. _click: http://click.pocoo.org/ .. _click: http://click.pocoo.org/
.. _click-plugins: https://github.com/click-contrib/click-plugins .. _click-plugins: https://github.com/click-contrib/click-plugins
.. _logging-tree module: https://pypi.org/project/logging_tree/ .. _logging-tree module: https://pypi.org/project/logging_tree/
.. _xdg-defaults: https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
...@@ -28,28 +28,16 @@ load such a file and provide access to values set therein: ...@@ -28,28 +28,16 @@ load such a file and provide access to values set therein:
.. code-block:: python .. code-block:: python
>>> from exposed import rc >>> from exposed import rc
>>> defaults = rc.UserDefaults("~/.myapprc.toml") >>> defaults = rc.UserDefaults("myapprc.toml")
The ``defaults`` object in this example is a subtype of :py:class:`dict`, with .. note::
a few extra methods implemented. Values can optionally be organized in
sections.
You may also pass the name of an environment variable to be used to override If the input filename given upon the construction of
the standard configuration file. If that environment variable is set by the :py:class:`exposed.rc.UserDefaults` is not absolute, it is considered
user, and is non-empty, then the object will load the file path pointed by the relative to the value of the environment variable ``$XDG_CONFIG_HOME``. In
value stored on that variable instead of the default. For example: UNIX-style operating systems, the above example would typically resolve to
``${HOME}/.config/myapprc.toml``. Check the `XDG defaults <xdg-defaults_>`_
.. code-block:: python for specifics.
>>> import os
>>> os.environ["MYAPPRC"] = "~/.myapprc2.toml"
>>> from exposed import rc
>>> defaults = rc.UserDefaults("~/.myapprc.toml", "MYAPPRC")
Because the environment variable is set to a non-empty value, then the
``defaults`` object will consider ``~/.myapprc2.toml`` as the source
configuration file instead of ``~/.myapprc.toml``. All the following
operations will be based on that setting.
Reading and writing values Reading and writing values
......
...@@ -28,6 +28,7 @@ dependencies = [ ...@@ -28,6 +28,7 @@ dependencies = [
"click>=8", "click>=8",
"tomli", "tomli",
"tomli-w", "tomli-w",
"xdg",
] ]
[project.urls] [project.urls]
......
...@@ -10,21 +10,22 @@ import collections.abc ...@@ -10,21 +10,22 @@ import collections.abc
import io import io
import json import json
import logging import logging
import os
import pathlib import pathlib
import typing import typing
import tomli import tomli
import tomli_w import tomli_w
import xdg
class UserDefaults(collections.abc.MutableMapping): class UserDefaults(collections.abc.MutableMapping):
"""Contains user defaults read from the user TOML configuration file. """Contains user defaults read from the user TOML configuration file.
Upon intialisation, an instance of this class will read the user Upon intialisation, an instance of this class will read the user
configuration file defined by the first argument. If ``envname`` is configuration file defined by the first argument. If the input file is
provided, and it is set, it overrides the location of ``filename`` and the specified as a relative path, then it is considered relative to the
configuration from that location is loaded instead. environment variable ``${XDG_CONFIG_HOME}``, or its default setting (which is
operating system dependent, c.f. `XDG defaults`_).
This object may be used (with limited functionality) like a dictionary. In This object may be used (with limited functionality) like a dictionary. In
this mode, objects of this class read and write values to the ``DEFAULT`` this mode, objects of this class read and write values to the ``DEFAULT``
...@@ -34,15 +35,13 @@ class UserDefaults(collections.abc.MutableMapping): ...@@ -34,15 +35,13 @@ class UserDefaults(collections.abc.MutableMapping):
Arguments: Arguments:
path: The path to the file typically containing the user defaults. path: The path, absolute or relative, to the file containing the user
The use of ``~`` (tilde) is supported to refer to one's home defaults to read. If `path` is a relative path, then it is
directory. considered relative to the directory defined by the environment
variable ``${XDG_CONFIG_HOME}`` (read `XDG defaults`_ for details on
the default location of this directory in the various operating
envname: If this value is provided, and it is set by the user, and is systems). The tilde (`~`) character may be used to represent the
non-empty, then loads the file path pointed by the value stored on user home, and is automatically expanded.
that environment variable instead of the default. The use of ``~``
(tilde) is supported to refer to one's home directory.
logger: A logger to use for messaging operations. If not set, use this logger: A logger to use for messaging operations. If not set, use this
module's logger. module's logger.
...@@ -51,29 +50,24 @@ class UserDefaults(collections.abc.MutableMapping): ...@@ -51,29 +50,24 @@ class UserDefaults(collections.abc.MutableMapping):
Attributes: Attributes:
path: The current path to the user defaults base file. path: The current path to the user defaults base file.
.. _XDG defaults: https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
""" """
def __init__( def __init__(
self, self,
path: str | pathlib.Path, path: str | pathlib.Path,
envname: str | None = None,
logger: logging.Logger | None = None, logger: logging.Logger | None = None,
) -> None: ) -> None:
self.logger = logger or logging.getLogger(__name__) self.logger = logger or logging.getLogger(__name__)
if ( self.path = pathlib.Path(path).expanduser()
envname is not None
and envname in os.environ if not self.path.is_absolute():
and os.environ[envname] self.path = xdg.xdg_config_home() / self.path
):
self.path = pathlib.Path(os.environ[envname])
self.logger.debug(
f"User configuration file set by environment variable ${envname}"
)
else:
self.path = pathlib.Path(path)
self.path = self.path.expanduser()
self.logger.info(f"User configuration file set to `{str(self.path)}'") self.logger.info(f"User configuration file set to `{str(self.path)}'")
self.data: dict[str, typing.Any] = {} self.data: dict[str, typing.Any] = {}
self.read() self.read()
......
...@@ -36,13 +36,19 @@ def test_rc_basic_loading(datadir): ...@@ -36,13 +36,19 @@ def test_rc_basic_loading(datadir):
assert rc["float.error"] == 3.14 assert rc["float.error"] == 3.14
def test_rc_env_loading(datadir): def test_rc_loading_from_xdg_config_home(datadir):
# tests if we can simply read an RC file # tests if we can simply read an RC file from the XDG_CONFIG_HOME
envname = "_TEST_RC_ENV_LOADING" _environ = dict(os.environ) # or os.environ.copy()
os.environ[envname] = str(datadir / "userdefaults_ex1.cfg") try:
rc = UserDefaults("does-not-exist", envname) os.environ["XDG_CONFIG_HOME"] = str(datadir)
_check_userdefaults_ex1_contents(rc) rc = UserDefaults("userdefaults_ex1.cfg")
assert not os.path.exists("does-not-exist") _check_userdefaults_ex1_contents(rc)
with pytest.raises(KeyError):
assert rc["float.error"] == 3.14
finally:
os.environ.clear()
os.environ.update(_environ)
def test_rc_init_empty(tmp_path): def test_rc_init_empty(tmp_path):
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment