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

Merge branch 'support-xdg-config-home' into 'main'

Implements automatic support $XDG_CONFIG_HOME, closes #3

Closes #3

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