Source code for trspectrometer.configuration

# Copyright 2020 Patrick C. Tapping
#
# This program is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation, either version 3 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program.  If not, see <http://www.gnu.org/licenses/>.

"""
Handle loading, storing, saving, and default values for program settings.
"""

import os
import io
import logging
import atexit
import re

import tomlkit
import appdirs


[docs]def set_defaults(): update(tomlkit.parse( """ [rawdata] [rawdata.units] # Default units for wavelength axis wavelength = "nm" # Default units for time axis time = "ps" # Default units for samples data = "ΔA" [directories] # Default directory to find data files data = "" # List of directories containing user plugin modules plugins = [] [plugins] # List of plugin modules to load load = ["aligncam", "delay", "chopper", "detector", "interface", "acquisition", "scope"] [hardware] # Whether to search for and initialise hardware devices. # If set to false, no hardware detection will take place. # The software will still be usable for data visualisation and analysis. init_hardware = true # In the future we may be able to detect laser rep rate. # For the meantime a temporary config option will do. # Rate in Hz. laser_reprate = 1000 """ )) # Set default directories if not data["directories"]["data"]: data["directories"]["data"] = os.path.expanduser("~")
[docs]def read(reset=False): """ Read and update configuration from the configuration file. If ``reset=True`` the current configuration will be reset prior to reading the file, otherwise the configuration will be preserved and only be updated with any values stored in the file. :param reset: Reset current configuration before reading file. """ global data newconfig = tomlkit.toml_document.TOMLDocument() if reset else data.copy() try: with io.open(configfile, encoding="utf-8") as f: _update_dict(newconfig, tomlkit.loads(f.read())) except: _log.exception(f"Error reading configuration from file {configfile}") return data = newconfig
[docs]def write(): """ Write the current configuration out the the configuration file. """ # The tomlkit library seems to have a bug where it keeps adding newlines after tables. # So after a few load-save cycles the newline start to get out of hand. # The regex is a dodgy hack to remove any triple+ newlines. try: with io.open(configfile, "w", encoding="utf-8", newline="\n") as f: f.write(re.sub("\n\n\n*", "\n\n", data.as_string())) except: _log.exception(f"Error writing configuration to file {configfile}")
[docs]def update(newdict): """ Update the current configuration using the provided dictionary. Any existing configuration entries will be overwritten by the new values. :param newdict: Dictionary of updated configuration values. """ _update_dict(data, newdict)
def _update_dict(olddict, newdict): """Recursively update dicts.""" for k, _ in newdict.items(): if k in olddict and isinstance(olddict[k], dict) and isinstance(newdict[k], dict): # Recurse into sub-dicts _update_dict(olddict[k], newdict[k]) else: # Overwrite existing values from newdict olddict[k] = newdict[k]
[docs]def add_defaults(defaultsdict): """ Update the current configuration using the provided dictionary of default values. Existing entries in the current configuration will not be overwritten. This allows adding any missing default values to the configuration without overwriting values which may already exist. :param defaultsdict: Dictionary of default configuration values. """ _add_defaults(data, defaultsdict)
def _add_defaults(olddict, defaultsdict): """Recursively add values to a dictionary if the value does not already exist.""" for k, _ in defaultsdict.items(): if k in olddict and isinstance(olddict[k], dict) and isinstance(defaultsdict[k], dict): # Recurse into sub-dicts _add_defaults(olddict[k], defaultsdict[k]) elif not k in olddict: # Add missing values from defaults dict olddict[k] = defaultsdict[k]
[docs]def plugin_dirs(): """ Return the list of plugin directories from configuration file, but also append the built-in plugin directory. :returns: List of configured plugin directories. """ plugin_dirs = data["directories"]["plugins"].copy() builtin_dir = os.path.join(os.path.dirname(__file__), "plugins") if not builtin_dir in plugin_dirs: plugin_dirs.append(builtin_dir) return plugin_dirs
# Module init ################################################################## _log = logging.getLogger(__name__) #: Reference to the main window class instance. mainwindow = None #: Path to the configuration file. configfile = os.path.join(appdirs.user_config_dir("trspectrometer", False), "trspectrometer.toml") _log.info(f"Configuration file location is {configfile}") # Start with empty TOMLDocument, add defaults, then load/overwrite from file #: Data structure holding the configuration as a TOMLDocument. data = tomlkit.toml_document.TOMLDocument() set_defaults() if not os.access(configfile, os.F_OK): # Create new file _log.info("Configuration file doesn't exist, creating default") try: os.mkdir(os.path.dirname(configfile)) except FileExistsError: pass except: _log.exception(f"Unable to create directory for configuration at {os.path.dirname(configfile)}") pass write() read(reset=False) # Save back out to disk when application shuts down atexit.register(write)