Source code for sofia_redux.pipeline.configuration

# Licensed under a 3-clause BSD style license - see LICENSE.rst
"""Redux configuration."""

import os
import tempfile

from configobj import ConfigObj, ParseError

from sofia_redux.pipeline.chooser import Chooser

__all__ = ['Configuration']


[docs] class Configuration(object): """ Set Redux configuration. Configuration values are set as attributes of this object, so that they can be accessed as ``config.value``. If the value is not set in the configuration, None will be returned. Subclasses should set the chooser attribute as appropriate. If unset, this class will use the default `redux.Chooser`. Attributes ---------- config_file : str or dict-like Initial configuration file. config: `ConfigObj` Loaded configuration object. """ def __init__(self, config_file=None): """ Initialize with an optional configuration file. Parameters ---------- config_file : str or dict-like, optional File path to an INI-format configuration file. May alternately be any object accepted by the ConfigObj constructor (e.g. a dictionary of configuration key-value pairs). """ self.chooser = Chooser() self.config_file = None self.config = ConfigObj() if config_file is not None: self.load(config_file) def __getattr__(self, key): """Return chooser or value from config.""" if self.config is not None and key in self.config: return self.config[key] else: return None def __setattr__(self, key, value): """Set value in config.""" if key not in ['chooser', 'config_file', 'config']: if self.config is not None: self.config[key] = value else: super().__setattr__(key, value) else: super().__setattr__(key, value)
[docs] def load(self, config_file): """ Read config file into ConfigObj object. Parameters ---------- config_file : str, dict, or ConfigObj File path to an INI-format configuration file. Alternately, may be a dict or ConfigObj that can be directly read by the ConfigObj constructor, or it may be a string containing INI formatted values. """ # allow command line strings for quick config if isinstance(config_file, str) and not os.path.isfile(config_file): # do nothing if config is set to "None", for default specification if config_file.lower().strip() == 'none': self.config_file = None co = ConfigObj(interpolation=False) else: # unescape anything escaped by the command line parser unescaped = config_file.encode( 'utf-8').decode('unicode_escape') # write to temporary file for configobj read in with tempfile.NamedTemporaryFile() as cfg: cfg.write(unescaped.encode('utf-8')) cfg.seek(0) try: co = ConfigObj(cfg, interpolation=False) except ParseError: raise IOError(f'Unable to read configuration ' f'from: {config_file}') from None self.config_file = None else: # otherwise load as file co = config_file self.config_file = config_file self.config = ConfigObj(co, interpolation=False) # fix any "False" top-level keys for key in self.config: if str(self.config[key]).lower().strip() == 'false': self.config[key] = False
[docs] def to_text(self): """ Print the current configuration to a text list. Returns ------- list of str The parameters in INI-formatted strings. """ if self.config is None: return [] else: return self.config.write()
[docs] def update(self, config_file): """ Update configuration from a new config file. Parameters ---------- config_file : str, dict, or ConfigObj File path to an INI-format configuration file. Alternately, may be a dict or ConfigObj that can be directly read by the ConfigObj constructor, or it may be a string containing INI formatted values. """ new_config = ConfigObj(config_file, interpolation=False) # fix any "False" top-level keys for key in new_config: if str(new_config[key]).lower().strip() == 'false': new_config[key] = False self.config.merge(new_config)