Source code for tangled.settings

import builtins
import configparser
import json
import os

from tangled.util import (
    abs_path, get_items_with_key_prefix, is_asset_path, is_object_path, load_object)


[docs]def parse_settings_file(path, section='app', interpolation=None, meta_settings=True, **kwargs): """Parse settings from the .ini file at ``path``. ``path`` can be a file system path or an asset path. ``section`` specifies which [section] to get settings from. By default, some extra metadata will be added to the settings parsed from a file. These are the file name the settings were loaded from (__file__), the base file names of any extended files (__base__ and __bases__), and the environment indicated by the file's base name (env). Use ``meta_settings=False`` to disable this. ``kwargs`` are the keyword args for :func:`parse_settings`. """ file_name = abs_path(path) file_dir = os.path.dirname(file_name) defaults = {'__dir__': json.dumps(file_dir)} if interpolation is None: interpolation = configparser.ExtendedInterpolation() parser = configparser.ConfigParser( defaults=defaults, delimiters='=', interpolation=interpolation) with open(file_name) as fp: parser.read_file(fp) try: settings = dict(parser[section]) except KeyError: raise ValueError('Settings file has no [{}] section'.format(section)) try: settings = parse_settings(settings, **kwargs) except ValueError as exc: file_name = os.path.relpath(file_name, os.getcwd()) message = '{exc} in {file_name}'.format_map(locals()) raise ValueError(message) from None required = kwargs.pop('required', None) if meta_settings: settings['__file__'] = file_name settings['__base__'] = None settings['__bases__'] = () if 'env' not in settings: env = os.path.basename(file_name) env = os.path.splitext(env)[0] settings['env'] = env extends = settings.pop('extends', None) if extends: if not is_asset_path(extends): extends = os.path.join(file_dir, extends) base_file_name = abs_path(extends) base_settings = parse_settings_file( base_file_name, section, interpolation, meta_settings, **kwargs) if meta_settings: settings['__base__'] = base_file_name settings['__bases__'] = (base_file_name,) settings['__bases__'] += base_settings['__bases__'] base_settings.update(settings) settings = base_settings if required: check_required(settings, required) return settings
[docs]def parse_settings(settings, defaults={}, required=(), extra={}, prefix=None, strip_prefix=True): """Convert settings values. All settings values should be JSON-encoded strings. For example:: debug = true factory:object = "tangled.web:Application" something:package.module:SomeClass = "value" Settings passed via ``defaults`` will be added if they're not already present in ``settings``. To convert only a subset of the settings, pass ``prefix``; only the settings with a key matching ``prefix`` will be returned (see :func:`get_items_with_key_prefix` for details). Required fields can be listed in ``required``. If any required fields are missing, a ``ValueError`` will be raised. For each setting: - If the key specifies a type using ``key:type`` syntax, the specified type will be used to parse the value. The type can refer to any callable that accepts a single string. If the type is specified as ``object``, :func:`.load_object()` will be used to parse the value. The ``:type`` will be stripped from the key. - Otherwise, the value will be passed to ``json.loads()``. The original ``settings`` dict will not be modified. """ loads = json.loads parsed_settings = {} parsed_settings.update(defaults) if prefix is not None: settings = get_items_with_key_prefix(settings, prefix, strip_prefix) for key, value in settings.items(): value = value.strip() if not value: value = None else: key, *rest = key.split(':', 1) try: value = loads(value) except ValueError: message = 'Could not parse JSON value for {key}'.format(key=key) raise ValueError(message) from None if rest: kind = get_type(rest[0]) value = kind(value) parsed_settings[key] = value parsed_settings.update(extra) if required: check_required(parsed_settings, required) return parsed_settings
[docs]def check_required(settings, required): """Ensure ``settings`` contains the ``required`` keys.""" missing = [] for r in required: if r not in settings: missing.append(r) if missing: raise ValueError( 'Missing required settings: {}'.format(', '.join(missing)))
[docs]def get_type(name: str): """Get the type corresponding to ``name``.""" if name is None: return str if name == 'object': return load_object if hasattr(builtins, name): return getattr(builtins, name) if is_object_path(name): return load_object(name) raise TypeError('Unknown type: %s' % name)