Module emanate.config

Emanate's configuration module.

emanate.config defines Emanate's defaults, along with helpers for working with configuration objects, loading them from JSON files, and dealing with relative paths.

Expand source code
"""Emanate's configuration module.

`emanate.config` defines Emanate's defaults, along with helpers for working
with configuration objects, loading them from JSON files, and dealing with
relative paths.
"""

import functools
import json
from pathlib import Path
from collections.abc import Iterable


def defaults(src=None):
    """Return Emanate's default configuration.

    config.defaults() resolves the default using the values
    of Path.home() and Path.cwd() at the time it was called.
    """
    if src is None:
        src = Path.cwd()
    base_ignores = resolve({
        'ignore': frozenset((
            "*~",
            ".*~",
            ".*.sw?",
            "emanate.json",
            "*/emanate.json",
            ".emanate",
            ".*.emanate",
            ".git/",
            ".gitignore",
            ".gitmodules",
            "__pycache__/",
        )),
    }, cwd=src.absolute())
    return AttrDict({
        **base_ignores,
        'confirm': True,
        'destination': Path.home(),
        'source': Path.cwd(),
    })


class AttrDict(dict):
    """Simple wrapper around dict, allowing accessing values as attributes."""

    def __getattr__(self, name):
        """Provide the contents of self as attributes."""
        if name not in self:
            raise AttributeError("{!r} object has no attribute {!r}".
                                 format(type(self).__name__, name))

        return self[name]

    def copy(self):
        """Return a new AttrDict, with the same contents as self."""
        return AttrDict(self)


def _merge_one(config, dict_like):
    assert isinstance(config, AttrDict)
    assert dict_like is not None

    config = config.copy()
    for key, value in dict_like.items():
        if value is None:
            continue

        if key == 'ignore':
            config[key] = config.get(key, frozenset()).union(value)
        else:
            config[key] = value

    return config


def merge(*configs, strict_resolve=True):
    """Merge a sequence of configuration dict-like objects.

    Later configurations override previous ones,
    and the `ignore` attributes are merged (according to set union).
    """
    configs = [c for c in configs if c is not None]

    if strict_resolve:
        assert all(map(is_resolved, configs))

    return functools.reduce(_merge_one, configs, AttrDict())


CONFIG_PATHS = ('destination', 'source', 'ignore')


def is_resolved(config):
    """Check that all path options in a configuration object are absolute."""
    for key in CONFIG_PATHS:
        if key in config:
            if isinstance(config[key], (Path)):
                value = config[key]
            elif isinstance(config[key], Iterable):
                return all([is_resolved({key: p}) for p in config[key]])
            if not isinstance(value, Path) or not value.is_absolute():
                return False

    return True


def resolve(config, cwd=None):
    """Convert path options to pathlib.Path objects, and resolve relative paths.

    Returns a new configuration dict-like, similar to its input, with all paths
    attributes converted to `pathlib` objects, and relative paths resolved
    relatively to `cwd`.
    """
    if cwd is None:
        cwd = Path.cwd()

    assert isinstance(cwd, Path)
    assert cwd.is_absolute()
    result = AttrDict(config)

    for key in CONFIG_PATHS:
        if key not in result:
            continue

        if isinstance(result[key], str):
            result[key] = Path(result[key])

        elif isinstance(result[key], Iterable):
            result[key] = [resolve({key: p}, cwd)[key] for p in result[key]]

        if isinstance(result[key], Path) and not result[key].is_absolute():
            result[key] = cwd / result[key].expanduser()

    return result


def from_json(path):
    """Load an Emanate configuration from a file.

    Takes a `pathlib.Path` object designating a JSON configuration file,
    loads it, and resolve paths relative to the file.
    """
    assert isinstance(path, Path)

    with path.open() as file:
        return resolve(json.load(file), cwd=path.parent.resolve())

Functions

def defaults(src=None)

Return Emanate's default configuration.

config.defaults() resolves the default using the values of Path.home() and Path.cwd() at the time it was called.

Expand source code
def defaults(src=None):
    """Return Emanate's default configuration.

    config.defaults() resolves the default using the values
    of Path.home() and Path.cwd() at the time it was called.
    """
    if src is None:
        src = Path.cwd()
    base_ignores = resolve({
        'ignore': frozenset((
            "*~",
            ".*~",
            ".*.sw?",
            "emanate.json",
            "*/emanate.json",
            ".emanate",
            ".*.emanate",
            ".git/",
            ".gitignore",
            ".gitmodules",
            "__pycache__/",
        )),
    }, cwd=src.absolute())
    return AttrDict({
        **base_ignores,
        'confirm': True,
        'destination': Path.home(),
        'source': Path.cwd(),
    })
def from_json(path)

Load an Emanate configuration from a file.

Takes a pathlib.Path object designating a JSON configuration file, loads it, and resolve paths relative to the file.

Expand source code
def from_json(path):
    """Load an Emanate configuration from a file.

    Takes a `pathlib.Path` object designating a JSON configuration file,
    loads it, and resolve paths relative to the file.
    """
    assert isinstance(path, Path)

    with path.open() as file:
        return resolve(json.load(file), cwd=path.parent.resolve())
def is_resolved(config)

Check that all path options in a configuration object are absolute.

Expand source code
def is_resolved(config):
    """Check that all path options in a configuration object are absolute."""
    for key in CONFIG_PATHS:
        if key in config:
            if isinstance(config[key], (Path)):
                value = config[key]
            elif isinstance(config[key], Iterable):
                return all([is_resolved({key: p}) for p in config[key]])
            if not isinstance(value, Path) or not value.is_absolute():
                return False

    return True
def merge(*configs, strict_resolve=True)

Merge a sequence of configuration dict-like objects.

Later configurations override previous ones, and the ignore attributes are merged (according to set union).

Expand source code
def merge(*configs, strict_resolve=True):
    """Merge a sequence of configuration dict-like objects.

    Later configurations override previous ones,
    and the `ignore` attributes are merged (according to set union).
    """
    configs = [c for c in configs if c is not None]

    if strict_resolve:
        assert all(map(is_resolved, configs))

    return functools.reduce(_merge_one, configs, AttrDict())
def resolve(config, cwd=None)

Convert path options to pathlib.Path objects, and resolve relative paths.

Returns a new configuration dict-like, similar to its input, with all paths attributes converted to pathlib objects, and relative paths resolved relatively to cwd.

Expand source code
def resolve(config, cwd=None):
    """Convert path options to pathlib.Path objects, and resolve relative paths.

    Returns a new configuration dict-like, similar to its input, with all paths
    attributes converted to `pathlib` objects, and relative paths resolved
    relatively to `cwd`.
    """
    if cwd is None:
        cwd = Path.cwd()

    assert isinstance(cwd, Path)
    assert cwd.is_absolute()
    result = AttrDict(config)

    for key in CONFIG_PATHS:
        if key not in result:
            continue

        if isinstance(result[key], str):
            result[key] = Path(result[key])

        elif isinstance(result[key], Iterable):
            result[key] = [resolve({key: p}, cwd)[key] for p in result[key]]

        if isinstance(result[key], Path) and not result[key].is_absolute():
            result[key] = cwd / result[key].expanduser()

    return result

Classes

class AttrDict (*args, **kwargs)

Simple wrapper around dict, allowing accessing values as attributes.

Expand source code
class AttrDict(dict):
    """Simple wrapper around dict, allowing accessing values as attributes."""

    def __getattr__(self, name):
        """Provide the contents of self as attributes."""
        if name not in self:
            raise AttributeError("{!r} object has no attribute {!r}".
                                 format(type(self).__name__, name))

        return self[name]

    def copy(self):
        """Return a new AttrDict, with the same contents as self."""
        return AttrDict(self)

Ancestors

  • builtins.dict

Methods

def copy(self)

Return a new AttrDict, with the same contents as self.

Expand source code
def copy(self):
    """Return a new AttrDict, with the same contents as self."""
    return AttrDict(self)