config_ninja.contrib.local

Use a local file as the backend.

Example

The following config-ninja settings file configures the LocalBackend to render two files (/tmp/config-ninja/local/settings.json and /tmp/config-ninja/local/subset.toml) from a single local source file (config-ninja-settings.yaml):

# the following top-level key is required
CONFIG_NINJA_OBJECTS:
  # each second-level key identifies a config-ninja object
  example-0:
    # set the location that the object is written to
    dest:
      format: json
      path: /tmp/config-ninja/local/settings.json

    # specify where the object is stored / retrieved from
    source:
      backend: local
      format: yaml

      init:
        kwargs:
          path: config-ninja-settings.yaml

  example-1:
    dest:
      # you can specify the path to a Jinja2 template:
      format: templates/settings-subset.toml.j2
      path: /tmp/config-ninja/local/subset.toml

    source:
      backend: local
      format: yaml

      new:
        kwargs:
          path: config-ninja-settings.yaml
 1"""Use a local file as the backend.
 2
 3## Example
 4
 5The following `config-ninja`_ settings file configures the `LocalBackend` to render two files
 6(`/tmp/config-ninja/local/settings.json` and `/tmp/config-ninja/local/subset.toml`) from a single
 7local source file (`config-ninja-settings.yaml`):
 8
 9```yaml
10.. include:: ../../../examples/local-backend.yaml
11```
12
13.. _config-ninja: https://bryant-finney.github.io/config-ninja/config_ninja.html
14"""
15
16from __future__ import annotations
17
18import logging
19import warnings
20from pathlib import Path
21from typing import AsyncIterator
22
23from watchfiles import awatch  # pyright: ignore[reportUnknownVariableType]
24
25from config_ninja.backend import Backend
26
27__all__ = ['LocalBackend']
28
29logger = logging.getLogger(__name__)
30
31
32class LocalBackend(Backend):
33    """Read the configuration from a local file.
34
35    ## Usage
36
37    >>> backend = LocalBackend(example_file)
38    >>> print(backend.get())
39    key_0: value_0
40    key_1: 1
41    key_2: true
42    key_3:
43        - 1
44        - 2
45        - 3
46    """
47
48    path: Path
49    """Read the configuration from this file"""
50
51    def __init__(self, path: str) -> None:
52        """Set attributes to initialize the backend.
53
54        If the given `path` doesn't exist, emit a warning and continue.
55
56        >>> with pytest.warns(RuntimeWarning):
57        ...     backend = LocalBackend('does_not_exist')
58        """
59        self.path = Path(path)
60        if not self.path.is_file():
61            warnings.warn(f'could not read file: {path}', category=RuntimeWarning, stacklevel=2)
62
63    def get(self) -> str:
64        """Read the contents of the configuration file as a string."""
65        return self.path.read_text(encoding='utf-8')
66
67    async def poll(self, interval: int = 0) -> AsyncIterator[str]:
68        """Poll the file's parent directory for changes, and yield the file contents on change.
69
70        .. note::
71            The `interval` parameter is ignored
72        """
73        yield self.get()
74        async for _ in awatch(self.path):
75            logger.info("detected change to '%s'", self.path)
76            yield self.get()
77
78
79logger.debug('successfully imported %s', __name__)
class LocalBackend(config_ninja.backend.Backend):
33class LocalBackend(Backend):
34    """Read the configuration from a local file.
35
36    ## Usage
37
38    >>> backend = LocalBackend(example_file)
39    >>> print(backend.get())
40    key_0: value_0
41    key_1: 1
42    key_2: true
43    key_3:
44        - 1
45        - 2
46        - 3
47    """
48
49    path: Path
50    """Read the configuration from this file"""
51
52    def __init__(self, path: str) -> None:
53        """Set attributes to initialize the backend.
54
55        If the given `path` doesn't exist, emit a warning and continue.
56
57        >>> with pytest.warns(RuntimeWarning):
58        ...     backend = LocalBackend('does_not_exist')
59        """
60        self.path = Path(path)
61        if not self.path.is_file():
62            warnings.warn(f'could not read file: {path}', category=RuntimeWarning, stacklevel=2)
63
64    def get(self) -> str:
65        """Read the contents of the configuration file as a string."""
66        return self.path.read_text(encoding='utf-8')
67
68    async def poll(self, interval: int = 0) -> AsyncIterator[str]:
69        """Poll the file's parent directory for changes, and yield the file contents on change.
70
71        .. note::
72            The `interval` parameter is ignored
73        """
74        yield self.get()
75        async for _ in awatch(self.path):
76            logger.info("detected change to '%s'", self.path)
77            yield self.get()

Read the configuration from a local file.

Usage

>>> backend = LocalBackend(example_file)
>>> print(backend.get())
key_0: value_0
key_1: 1
key_2: true
key_3:
    - 1
    - 2
    - 3
LocalBackend(path: str)
52    def __init__(self, path: str) -> None:
53        """Set attributes to initialize the backend.
54
55        If the given `path` doesn't exist, emit a warning and continue.
56
57        >>> with pytest.warns(RuntimeWarning):
58        ...     backend = LocalBackend('does_not_exist')
59        """
60        self.path = Path(path)
61        if not self.path.is_file():
62            warnings.warn(f'could not read file: {path}', category=RuntimeWarning, stacklevel=2)

Set attributes to initialize the backend.

If the given path doesn't exist, emit a warning and continue.

>>> with pytest.warns(RuntimeWarning):
...     backend = LocalBackend('does_not_exist')
path: pathlib.Path

Read the configuration from this file

def get(self) -> str:
64    def get(self) -> str:
65        """Read the contents of the configuration file as a string."""
66        return self.path.read_text(encoding='utf-8')

Read the contents of the configuration file as a string.

async def poll(self, interval: int = 0) -> AsyncIterator[str]:
68    async def poll(self, interval: int = 0) -> AsyncIterator[str]:
69        """Poll the file's parent directory for changes, and yield the file contents on change.
70
71        .. note::
72            The `interval` parameter is ignored
73        """
74        yield self.get()
75        async for _ in awatch(self.path):
76            logger.info("detected change to '%s'", self.path)
77            yield self.get()

Poll the file's parent directory for changes, and yield the file contents on change.

The interval parameter is ignored

Inherited Members
config_ninja.backend.Backend
new