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
42 changes: 40 additions & 2 deletions sphinx/user-guide/configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,52 @@ The configuration can be modified using two functions:
- :py:func:`config.load_config <vortex.config.load_config>`: Set multiple keys from a TOML file.

The value of a specific key can be queried using
:py:func:`config.from_confi <vortex.config.from_config>`. It is also
:py:func:`config.from_config <vortex.config.from_config>`. It is also
possible to print the entire configuration using
:py:func:`config.print_config <vortex.config.print_config>`.
:py:func:`config.print_config <vortex.config.print_config>`. If the
current configuration was read from a file, the corresponding path can
be read from the value of ``config.file``.

.. code:: toml

[storage]
address = "ftp.domain.com"


.. code:: python

>>> from vortex import config
>>> config.load_config("~/.vortex.d/vortex.toml") # The default
>>> config.file
PosixPath('/home/user/.vortex.d/vortex.toml')
>>> config.from_config("storage", "address")
"ftp.domain.com"
>>> from_config("storage", "protocol")
ConfigurationError: Missing configuration key protocol in section storage
>>> config.set_config("storage", "protocol", value="ftp")
>>> config.print_config()
Section: storage
ADDRESS: hendrix.meteo.fr
PROTOCOL: ftp

.. seealso::

:doc:`../reference/configuration`


Default configuration
^^^^^^^^^^^^^^^^^^^^^

At import time, ``vortex`` tries to read configuration from a file
``vortex.toml`` in the current working directory.

If not found, ``vortex`` reads configuration from
``~/.vortex.d/vortex.toml``.

If the default configuration is not found, *vortex* is left
unconfigured.


``data-tree``
^^^^^^^^^^^^^

Expand Down
28 changes: 24 additions & 4 deletions src/vortex/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

"""

import sys
import types
from pathlib import Path
import tomli

Expand All @@ -17,6 +19,7 @@
]

VORTEX_CONFIG = {}
_PATH = None

logger = loggers.getLogger(__name__)

Expand All @@ -42,11 +45,12 @@ def load_config(configpath=Path("vortex.toml")):
# ...
"""
global VORTEX_CONFIG
global _PATH
configpath = Path(configpath)
try:
with configpath.open(mode="rb") as f:
VORTEX_CONFIG = tomli.load(f)
print(f"Successfully read configuration file {configpath.absolute()}")
_PATH = configpath.absolute()
except FileNotFoundError:
print(
f"Could not read configuration file {configpath.absolute()} (not found)."
Expand All @@ -56,9 +60,12 @@ def load_config(configpath=Path("vortex.toml")):

def print_config():
"""Print configuration (key, value) pairs"""
if VORTEX_CONFIG:
for k, v in VORTEX_CONFIG.items():
print(k.upper(), v)
if not VORTEX_CONFIG:
return None
for section_name, section in VORTEX_CONFIG.items():
print(f"Section: {section_name.upper()}")
for k, v in section.items():
print(f" {k.upper()}: {v}")


def from_config(section, key=None):
Expand Down Expand Up @@ -114,3 +121,16 @@ def get_from_config_w_default(section, key, default):
return from_config(section, key)
except ConfigurationError:
return default


class _ConfigModule(types.ModuleType):
@property
def file(self):
return _PATH

@file.setter
def file(self, value):
raise AttributeError("config.file is read-only")


sys.modules[__name__].__class__ = _ConfigModule
72 changes: 64 additions & 8 deletions tests/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,26 @@
},
}


@pytest.fixture
def clean_config():
"""Save and restore VORTEX_CONFIG and _PATH around a test."""
saved_cfg = config.VORTEX_CONFIG.copy()
saved_path = config._PATH
config.VORTEX_CONFIG = {}
config._PATH = None
yield
config.VORTEX_CONFIG = saved_cfg
config._PATH = saved_path


@pytest.fixture
def toml_config_file(tmp_path):
p = tmp_path / "vortex.toml"
p.write_text('[data-tree]\nrootdir = "/tmp/data"\n')
return p


def test_section_from_config():
with pytest.raises(config.ConfigurationError):
config_section = config.from_config("nonexist")
Expand Down Expand Up @@ -45,14 +65,50 @@ def test_is_defined():


def test_get_from_config_w_default():
assert (
config.get_from_config_w_default(
"data-tree",
"nonexist",
"default",
)
== "default"
)

assert config.get_from_config_w_default(
"data-tree", "nonexist", "default",
) == "default"

assert config.get_from_config_w_default(
"data-tree", "op-rootdir", "default",
"data-tree",
"op-rootdir",
"default",
) == config.from_config("data-tree", "op-rootdir")





def test_load_config_populates_config(clean_config, toml_config_file):
config.load_config(toml_config_file)

assert config.VORTEX_CONFIG == {"data-tree": {"rootdir": "/tmp/data"}}


def test_load_config_sets_file_property(clean_config, toml_config_file):
config.load_config(toml_config_file)

assert config.file == toml_config_file.absolute()


def test_load_config_overrides_existing(clean_config, tmp_path):
first = tmp_path / "first.toml"
first.write_text('[section-a]\nkey = "value-a"\n')
second = tmp_path / "second.toml"
second.write_text('[section-b]\nkey = "value-b"\n')

config.load_config(first)
config.load_config(second)

assert "section-a" not in config.VORTEX_CONFIG
assert config.VORTEX_CONFIG == {"section-b": {"key": "value-b"}}


def test_load_config_file_not_found(clean_config, tmp_path, capsys):
config.load_config(tmp_path / "nonexistent.toml")

captured = capsys.readouterr()
assert "not found" in captured.out
assert config.VORTEX_CONFIG == {}
Loading