Source code for spinn_utilities.conf_loader
# Copyright (c) 2017 The University of Manchester
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import logging
import os
from typing import Callable, List, Optional
import appdirs
from typing_extensions import TypeAlias
from spinn_utilities import log
from spinn_utilities.configs import (
CamelCaseConfigParser, UnexpectedConfigException)
logger = log.FormatAdapter(logging.getLogger(__name__))
_SectionParser: TypeAlias = Callable[[CamelCaseConfigParser], None]
def _check_config(cfg_file: str, default_configs: CamelCaseConfigParser,
strict: bool) -> None:
"""
Checks the configuration read up to this point to see if it is outdated.
Once one difference is found a full reports is generated and an error
raised.
Any section specifically listed as Dead will cause a error
Any section in the default_cfg should not have extra values.
It will never have less as the default_cfg are in the configuration.
Errors on any values listed as PreviousValues.
These are specific values in specific options no longer supported.
For example old algorithm names.
:param cfg_file: Path of last file read in
:param default_configs:
configuration with just the default files in
:param strict: Flag to say an exception should be raised
"""
if not default_configs.sections(): # empty
logger.warning("Can not validate cfg files as no default.")
return
configs = CamelCaseConfigParser()
configs.read(cfg_file)
msg = ""
for section in configs.sections():
if default_configs.has_section(section):
for option in configs.options(section):
if not default_configs.has_option(section, option):
msg += f"Unexpected Option: [{section}]{option}\n"
else:
msg += f"Unexpected Section: [{section}]\n"
if msg:
msg += f"found in {cfg_file}"
if strict:
raise UnexpectedConfigException(msg)
else:
logger.warning(msg)
def _read_a_config(
configuration: CamelCaseConfigParser, cfg_file: str,
default_configs: CamelCaseConfigParser, strict: bool) -> None:
"""
Reads in a configuration file and then directly its `machine_spec_file`.
:param configuration:
configuration to be updated by the reading of a file
:param cfg_file: path to file which should be read in
:param default_configs:
configuration with just the default files in
:param strict: Flag to say checker should raise an exception
"""
_check_config(cfg_file, default_configs, strict)
configuration.read(cfg_file)
if configuration.has_option("Machine", "machine_spec_file"):
machine_spec_file = configuration.get("Machine", "machine_spec_file")
_check_config(machine_spec_file, default_configs, strict)
configuration.read(machine_spec_file)
configuration.remove_option("Machine", "machine_spec_file")
def _config_locations(filename: str) -> List[str]:
"""
Defines the list of places we can get configuration files from.
:param filename:
The local name of the configuration file, e.g., 'spynnaker.cfg'
:return: list of fully-qualified filenames
"""
dotname = "." + filename
# locations to read as well as default later overrides earlier
system_config_cfg_file = os.path.join(appdirs.site_config_dir(), dotname)
user_config_cfg_file = os.path.join(appdirs.user_config_dir(), dotname)
user_home_cfg_file = os.path.join(os.path.expanduser("~"), dotname)
# locations to read as well as default later overrides earlier
return [system_config_cfg_file, user_config_cfg_file,
user_home_cfg_file]
[docs]
def load_defaults(defaults: List[str]) -> CamelCaseConfigParser:
"""
Load the default configuration.
:param defaults:
The list of files to get default configurations from.
:return: the fully-loaded configuration
"""
default_configs = CamelCaseConfigParser()
default_configs.read(defaults)
return default_configs
[docs]
def load_config(local_name: Optional[str], user_cfg: Optional[str],
defaults: List[str]) -> CamelCaseConfigParser:
"""
Load the configuration.
:param local_name:
Name of the file to look for in the current directory
:param user_cfg:
Path to existing user cfg. This file must exist.
:param defaults:
The list of files to get default configurations from.
:return: the fully-loaded and checked configuration
"""
configs = load_defaults(defaults)
default_configs = load_defaults(defaults)
if user_cfg is not None:
_read_a_config(configs, user_cfg, default_configs, False)
if local_name is not None:
local_path = os.path.join(os.curdir, local_name)
_read_a_config(configs, local_path, default_configs, True)
# Log which configs files we read
logger.info("Read configs files: {}", ", ".join(configs.read_files))
return configs