Source code for psi

# This import automatically adds the TRACE logging level
import psiaudio

import logging
import importlib.util
import os
from pathlib import Path
import socket

log = logging.getLogger(__name__)
log.addHandler(logging.NullHandler())


# Flag indicating whether user configuration file was loaded.
CONFIG_LOADED = False


# Create default paths and then override using environment variables. All
# environment variables must be prefixed with `PSI_` to minimize the chance of
# collision with existing environment variables.
DEFAULT_CONFIG = {
    'LOG_ROOT': os.path.expanduser('~/Documents/psi/logs'),
    'DATA_ROOT': os.path.expanduser('~/Documents/psi/data'),
    'PROCESSED_ROOT': os.path.expanduser('~/Documents/psi/processed'),
    'PREFERENCES_ROOT': os.path.expanduser('~/Documents/psi/preferences'),
    'LAYOUT_ROOT': os.path.expanduser('~/Documents/psi/layout'),
    'CFTS_ROOT': os.path.expanduser('~/Documents/psi/cfts'),
    'IO_ROOT': os.path.expanduser('~/Documents/psi/io'),
    'HOSTNAME': socket.gethostname(),
}

for k, v in DEFAULT_CONFIG.items():
    DEFAULT_CONFIG[k] = os.environ.get(f'PSI_{k}', v)

# Container for configuration variables
_config = {}


[docs] def get_config_folder(): ''' Return configuration folder ''' default = Path('~') / 'psi' default = default.expanduser() return Path(os.environ.get('PSI_CONFIG', default))
[docs] def get_config_file(): ''' Return configuration file ''' default = get_config_folder() / 'config.py' return Path(os.environ.get('PSI_CONFIG_FILE', default))
[docs] def create_config(base_directory=None, log=None, data=None, processed=None, cal=None, preferences=None, layout=None, io=None, standard_io=None, paradigm_descriptions=None): # This approach allows code inspection to show valid function parameters # without hiding it behind an anonymous **kwargs definition. kwargs = locals() kwargs.pop('base_directory') kwargs.pop('standard_io') kwargs.pop('paradigm_descriptions') # Figure out where to save everything target = get_config_file() target.parent.mkdir(exist_ok=True, parents=True) if base_directory is None: base_directory = str(target.parent) defaults = { 'LOG_ROOT': "BASE_DIRECTORY / 'logs'", 'DATA_ROOT': "BASE_DIRECTORY / 'data'", 'PROCESSED_ROOT': "BASE_DIRECTORY / 'processed'", 'CAL_ROOT': "BASE_DIRECTORY / 'calibration'", 'PREFERENCES_ROOT': "BASE_DIRECTORY / 'settings' / 'preferences'", 'LAYOUT_ROOT': "BASE_DIRECTORY / 'settings' / 'layout'", 'IO_ROOT': "BASE_DIRECTORY / 'io'", } help_text = { 'LOG_ROOT': 'Location of log files for psiexperiment', 'DATA_ROOT': 'Location where experiment data is saved', 'PROCESSED_ROOT': 'Location where post-processed experiment data is saved', 'CAL_ROOT': 'Location where calibration data is saved', 'PREFERENCES_ROOT': 'Location where experiment-specific defaults are saved', 'LAYOUT_ROOT': 'Location where experiment-specific layouts are saved', 'IO_ROOT': 'Location where custom hardware configurations are saved', 'STANDARD_IO': 'List of standard hardware configurations the user can select from' } if standard_io is None: defaults['STANDARD_IO'] = "[]" else: engine_string = ',\n'.join(f" '{e}'" for e in standard_io) standard_io = f"[\n{engine_string}\n]" defaults['STANDARD_IO'] = standard_io for key, value in kwargs.items(): if value is None: continue config_key = f"{key.upper()}_ROOT" config_value = f"Path(r'{value}')" defaults[config_key] = config_value lines = [] for k, v in defaults.items(): if k in help_text: lines.append(f'# {help_text[k]}') lines.append(f'{k} = {v}\n') if paradigm_descriptions is not None: lines.append('# List of module paths containing experiment paradigm descriptions') lines.append('PARADIGM_DESCRIPTIONS = [') for description in paradigm_descriptions: lines.append(f' "{description}",') lines.append(']\n') paths = '\n'.join(lines) config_template = Path(__file__).parent / 'templates' / 'config.txt' config_text = config_template.read_text() config_text = config_text.format(base_directory, paths) target.write_text(config_text)
[docs] def create_io_manifest(template): io_template = Path(__file__).parent / 'templates' / 'io' / template io_template = io_template.with_suffix('.enaml') io = Path(get_config('IO_ROOT')) / template.lstrip('_') io = io.with_suffix('.enaml') io.parent.mkdir(exist_ok=True, parents=True) io_text = io_template.read_text() io.write_text(io_text)
[docs] def create_config_dirs(): config = load_config() for name, value in config.items(): if name.endswith('_ROOT'): Path(value).mkdir(exist_ok=True, parents=True)
[docs] def load_config(): # Load the default settings global CONFIG_LOADED config = DEFAULT_CONFIG.copy() config_path = get_config_file() if config_path.exists(): spec = importlib.util.spec_from_file_location('settings', config_path) module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) config.update({n: v for n, v in vars(module).items() if n == n.upper()}) CONFIG_LOADED = True log.debug('CONFIG: %r', config) return config
[docs] def reload_config(): global _config _config = load_config()
[docs] def set_config(setting, value): ''' Set value of setting ''' _config[setting] = value
CFG_ERR_MESG = f''' Could not find setting "{{}}" in the configuration file. This may be because the configuration file is missing. The configuration file was expected to be found at {get_config_file()}. You can either create this file manually or run `psi-config` to create it. A different location for the file can be specified by setting the environment variable PSI_CONFIG_FILE. ''' # Special singleton value that enables us to use `get_config` with None as the # default value. This value is a "flag" that, if used, indicates that the user # did not provide a default value. NoDefault = object()
[docs] def get_config(setting=None, default_value=NoDefault): ''' Get value of setting ''' if setting is not None: try: if default_value != NoDefault: return _config.get(setting, default_value) else: return _config[setting] except KeyError as e: if CONFIG_LOADED: raise mesg = CFG_ERR_MESG.strip().format(setting) raise SystemError(mesg) from e else: return _config.copy()
reload_config() from .version import __version__