Source code for walter.sources

from configparser import ConfigParser, NoOptionError
from fnmatch import fnmatch
from os import environ


[docs]class Source: """Base class for configuration sources. To implement a simple (non-file-based) configuration source, subclass this class and override ``__getitem__``. ``__getitem__`` should return a string, or raise ``KeyError`` if a key isn't found in the configuration source. If you are implementing an ambient configuration source (e.g. one that reads from environment variables, command-line args, a single file in a well-known location, or something else that doesn't depend on Walter's search path), you can expose your ``Source`` subclass to users directly. If instead you are implementing a file-based source, see also :class:`~walter.sources.FileSource`. """
[docs]class FileSource: """Base class for file-based configuration sources. Because Walter implements searching for configuration files internally, and allows for a mix of different types of configuration files, a file-based configuration source consists of two classes. One is the actual source itself. This is a subclass of :class:`~walter.sources.Source` — not this class — and behaves like a normal source, except it takes a file-like object as its first positional argument, and it is an implementation detail that is not exposed to your users. The other is the "meta-source", which is a subclass of ``FileSource``. It is responsible for two things: determining which filenames match the source, and creating new source objects from files. Users will create an instance of the meta-source and pass that to Walter, which will use it to create source instances. While it is possible to override :meth:`match_filename` and :meth:`create` entirely, most meta-sources should be able to get by with simply setting two properties and adding a docstring: * ``source_class``, your actual source class. * ``pattern``, a default file pattern to match on, which can be either a shell glob or a compiled regular expression. Unless you override ``__init__``, your meta-source will accept a ``filename`` arg that allows users to override ``pattern``; any other keyword arguments given to the meta-source will be passed through to the source itself. .. example:: A file-based source that uses JSON might look like this:: class JsonSource(Source): def __init__(self, f, allow_comments=True): self.data = json.load( f, allow_comments=allow_comments) def __getitem__(self, key): return self.data[key] class JsonFileSource(FileSource): \"\"\"A file source for JSON blobs. :param allow_comments: Whether to allow comments. :type allow_comments: bool \"\"\" source_class = JsonSource pattern = "config.json" """ def __init__(self, filename=None, **kwargs): if filename is not None: self.pattern = filename self.kwargs = kwargs
[docs] def match_filename(self, filename): """Test a filename to see if it matches this source. :return: Whether the filename matches this source. :rtype: bool """ if hasattr(self.pattern, "match"): return bool(self.pattern.match(filename)) return fnmatch(filename, self.pattern)
[docs] def create(self, file_obj): """Return a new source with the given file object. :return: A new source object. """ return self.source_class(file_obj, **self.kwargs)
[docs]class EnvironmentSource(Source): """Source that extracts values from environment variables. :param prefix: Prefix to expect at the beginning of environment variable names. :type prefix: str """ def __init__(self, prefix=""): self.prefix = prefix def __getitem__(self, key): return environ[self.prefix + key]
class IniSource(Source): def __init__(self, file, section="settings"): self.section = section self.parser = ConfigParser() self.parser.read_file(file) def __getitem__(self, key): try: return self.parser.get(self.section, key) except NoOptionError: raise KeyError(key)
[docs]class IniFileSource(FileSource): """Source that extracts values from ``.ini`` files. Files should be in the format expected by :class:`configparser.ConfigParser`. :param section: Section header to look for settings under. Defaults to ``settings``. :type section: str """ pattern = "settings.ini" source_class = IniSource