Module nlisim.config
Expand source code
from collections import OrderedDict
from configparser import ConfigParser
from importlib import import_module
from io import StringIO, TextIOBase
from pathlib import PurePath
import re
from typing import TYPE_CHECKING, List, Optional, TextIO, Type, Union
if TYPE_CHECKING:
from nlisim.module import ModuleModel
class SimulationConfig(ConfigParser):
"""
Internal representation of a simulation config.
This is based on the standard Python ConfigParser class.
"""
def __init__(self, *config_sources: Union[str, PurePath, TextIO, dict]) -> None:
super().__init__(allow_no_value=False, inline_comment_prefixes=('#',))
self._modules: OrderedDict[str, 'ModuleModel'] = OrderedDict()
for config_source in config_sources:
if isinstance(config_source, dict):
self.read_dict(config_source)
elif isinstance(config_source, TextIOBase):
self.read_file(config_source)
else:
self.read(config_source)
for module_path in self.getlist('simulation', 'modules'):
self.add_module(module_path)
@property
def modules(self) -> List['ModuleModel']:
"""Return a list of instantiated modules connected to this config."""
return list(self._modules.values())
def add_module(self, module_ref: Union[str, Type['ModuleModel']]):
if isinstance(module_ref, str):
module_func = self.load_module(module_ref)
else:
module_func = module_ref
self.validate_module(module_func)
module = module_func(self)
if module.name in self._modules:
raise ValueError(f'A module named {module.name} already exists')
self._modules[module.name] = module
@classmethod
def load_module(cls, path: str) -> Type['ModuleModel']:
"""Load a module class, returning the class constructor."""
module_path, func_name = path.rsplit('.', 1)
module = import_module(module_path)
func = getattr(module, func_name, None)
cls.validate_module(func, path)
return func
@classmethod
def validate_module(cls, func: Type['ModuleModel'], path: Optional[str] = None) -> None:
"""Validate basic aspects of a module class."""
from nlisim.module import ModuleModel # noqa avoid circular imports
if path is None:
path = repr(func)
if func is None or not issubclass(func, ModuleModel):
raise TypeError(f'Invalid module class for "{path}"')
if not func.name.isidentifier() or func.name.startswith('_'):
raise ValueError(f'Invalid module name "{func.name}" for "{path}')
# Wrapper so that this fails when a parameter is missing
def getint(self, section: str, option: str, **kwargs) -> int:
result = super().getint(section, option, **kwargs)
assert result is not None, f'Missing parameter {option} in section {section}'
return result
# Wrapper so that this fails when a parameter is missing
def getfloat(self, section, option, **kwargs) -> float:
result = super().getfloat(section, option, **kwargs)
assert result is not None, f'Missing parameter {option} in section {section}'
return result
# Wrapper so that this fails when a parameter is missing
def getboolean(self, section, option, **kwargs) -> bool:
result = super().getboolean(section, option, **kwargs)
assert result is not None, f'Missing parameter {option} in section {section}'
return result
# Wrapper so that this fails when a parameter is missing
def get(self, section, option, **kwargs):
result = super(ConfigParser, self).get(section, option, **kwargs)
assert result is not None, f'Missing parameter {option} in section {section}'
return result
# TODO: see if there is a slicker way to do these gettype wrappers.
# TODO: do checking on 'type-less' get (or implement one for strings)
def getlist(self, section: str, option: str) -> List[str]:
"""
Return a list of strings from a configuration value.
This method reads a string value from the configuration object and splits
it by: new lines, spaces, and commas. The values in the returned list are
stripped of all white space and removed if empty.
For example, the following values all parse as <code>`['a', 'b', 'c']`</code>:
value = a,b,c
value = a b c
value = a, b, c
value = a
b
c
value = a,
b,
c
value = a b
,c
"""
return self.parselist(self.get(section, option, fallback=''))
@classmethod
def parselist(cls, value: str) -> List[str]:
"""
Return a list of strings from a configuration value.
This is a helper method for `getlist`, split out for code sharing.
"""
values = []
for value in re.split('[\n ,]+', value):
stripped = value.strip()
if stripped:
values.append(stripped)
return values
def __str__(self):
f = StringIO()
self.write(f)
return f.getvalue()
Classes
class SimulationConfig (*config_sources: Union[str, pathlib.PurePath, TextIO, dict])
-
Internal representation of a simulation config.
This is based on the standard Python ConfigParser class.
Expand source code
class SimulationConfig(ConfigParser): """ Internal representation of a simulation config. This is based on the standard Python ConfigParser class. """ def __init__(self, *config_sources: Union[str, PurePath, TextIO, dict]) -> None: super().__init__(allow_no_value=False, inline_comment_prefixes=('#',)) self._modules: OrderedDict[str, 'ModuleModel'] = OrderedDict() for config_source in config_sources: if isinstance(config_source, dict): self.read_dict(config_source) elif isinstance(config_source, TextIOBase): self.read_file(config_source) else: self.read(config_source) for module_path in self.getlist('simulation', 'modules'): self.add_module(module_path) @property def modules(self) -> List['ModuleModel']: """Return a list of instantiated modules connected to this config.""" return list(self._modules.values()) def add_module(self, module_ref: Union[str, Type['ModuleModel']]): if isinstance(module_ref, str): module_func = self.load_module(module_ref) else: module_func = module_ref self.validate_module(module_func) module = module_func(self) if module.name in self._modules: raise ValueError(f'A module named {module.name} already exists') self._modules[module.name] = module @classmethod def load_module(cls, path: str) -> Type['ModuleModel']: """Load a module class, returning the class constructor.""" module_path, func_name = path.rsplit('.', 1) module = import_module(module_path) func = getattr(module, func_name, None) cls.validate_module(func, path) return func @classmethod def validate_module(cls, func: Type['ModuleModel'], path: Optional[str] = None) -> None: """Validate basic aspects of a module class.""" from nlisim.module import ModuleModel # noqa avoid circular imports if path is None: path = repr(func) if func is None or not issubclass(func, ModuleModel): raise TypeError(f'Invalid module class for "{path}"') if not func.name.isidentifier() or func.name.startswith('_'): raise ValueError(f'Invalid module name "{func.name}" for "{path}') # Wrapper so that this fails when a parameter is missing def getint(self, section: str, option: str, **kwargs) -> int: result = super().getint(section, option, **kwargs) assert result is not None, f'Missing parameter {option} in section {section}' return result # Wrapper so that this fails when a parameter is missing def getfloat(self, section, option, **kwargs) -> float: result = super().getfloat(section, option, **kwargs) assert result is not None, f'Missing parameter {option} in section {section}' return result # Wrapper so that this fails when a parameter is missing def getboolean(self, section, option, **kwargs) -> bool: result = super().getboolean(section, option, **kwargs) assert result is not None, f'Missing parameter {option} in section {section}' return result # Wrapper so that this fails when a parameter is missing def get(self, section, option, **kwargs): result = super(ConfigParser, self).get(section, option, **kwargs) assert result is not None, f'Missing parameter {option} in section {section}' return result # TODO: see if there is a slicker way to do these gettype wrappers. # TODO: do checking on 'type-less' get (or implement one for strings) def getlist(self, section: str, option: str) -> List[str]: """ Return a list of strings from a configuration value. This method reads a string value from the configuration object and splits it by: new lines, spaces, and commas. The values in the returned list are stripped of all white space and removed if empty. For example, the following values all parse as <code>`['a', 'b', 'c']`</code>: value = a,b,c value = a b c value = a, b, c value = a b c value = a, b, c value = a b ,c """ return self.parselist(self.get(section, option, fallback='')) @classmethod def parselist(cls, value: str) -> List[str]: """ Return a list of strings from a configuration value. This is a helper method for `getlist`, split out for code sharing. """ values = [] for value in re.split('[\n ,]+', value): stripped = value.strip() if stripped: values.append(stripped) return values def __str__(self): f = StringIO() self.write(f) return f.getvalue()
Ancestors
- configparser.ConfigParser
- configparser.RawConfigParser
- collections.abc.MutableMapping
- collections.abc.Mapping
- collections.abc.Collection
- collections.abc.Sized
- collections.abc.Iterable
- collections.abc.Container
Static methods
def load_module(path: str) ‑> Type[ModuleModel]
-
Load a module class, returning the class constructor.
Expand source code
@classmethod def load_module(cls, path: str) -> Type['ModuleModel']: """Load a module class, returning the class constructor.""" module_path, func_name = path.rsplit('.', 1) module = import_module(module_path) func = getattr(module, func_name, None) cls.validate_module(func, path) return func
def parselist(value: str) ‑> List[str]
-
Return a list of strings from a configuration value.
This is a helper method for
getlist
, split out for code sharing.Expand source code
@classmethod def parselist(cls, value: str) -> List[str]: """ Return a list of strings from a configuration value. This is a helper method for `getlist`, split out for code sharing. """ values = [] for value in re.split('[\n ,]+', value): stripped = value.strip() if stripped: values.append(stripped) return values
def validate_module(func: Type[ForwardRef('ModuleModel')], path: Optional[str] = None)
-
Validate basic aspects of a module class.
Expand source code
@classmethod def validate_module(cls, func: Type['ModuleModel'], path: Optional[str] = None) -> None: """Validate basic aspects of a module class.""" from nlisim.module import ModuleModel # noqa avoid circular imports if path is None: path = repr(func) if func is None or not issubclass(func, ModuleModel): raise TypeError(f'Invalid module class for "{path}"') if not func.name.isidentifier() or func.name.startswith('_'): raise ValueError(f'Invalid module name "{func.name}" for "{path}')
Instance variables
var modules : List[ModuleModel]
-
Return a list of instantiated modules connected to this config.
Expand source code
@property def modules(self) -> List['ModuleModel']: """Return a list of instantiated modules connected to this config.""" return list(self._modules.values())
Methods
def add_module(self, module_ref: Union[str, Type[ForwardRef('ModuleModel')]])
-
Expand source code
def add_module(self, module_ref: Union[str, Type['ModuleModel']]): if isinstance(module_ref, str): module_func = self.load_module(module_ref) else: module_func = module_ref self.validate_module(module_func) module = module_func(self) if module.name in self._modules: raise ValueError(f'A module named {module.name} already exists') self._modules[module.name] = module
def get(self, section, option, **kwargs)
-
Get an option value for a given section.
If
vars' is provided, it must be a dictionary. The option is looked up in
vars' (if provided),section', and in
DEFAULTSECT' in that order. If the key is not found andfallback' is provided, it is used as a fallback value.
None' can be provided as a `fallback' value.If interpolation is enabled and the optional argument `raw' is False, all interpolations are expanded in the return values.
Arguments
raw',
vars', and `fallback' are keyword only.The section DEFAULT is special.
Expand source code
def get(self, section, option, **kwargs): result = super(ConfigParser, self).get(section, option, **kwargs) assert result is not None, f'Missing parameter {option} in section {section}' return result
def getboolean(self, section, option, **kwargs) ‑> bool
-
Expand source code
def getboolean(self, section, option, **kwargs) -> bool: result = super().getboolean(section, option, **kwargs) assert result is not None, f'Missing parameter {option} in section {section}' return result
def getfloat(self, section, option, **kwargs) ‑> float
-
Expand source code
def getfloat(self, section, option, **kwargs) -> float: result = super().getfloat(section, option, **kwargs) assert result is not None, f'Missing parameter {option} in section {section}' return result
def getint(self, section: str, option: str, **kwargs) ‑> int
-
Expand source code
def getint(self, section: str, option: str, **kwargs) -> int: result = super().getint(section, option, **kwargs) assert result is not None, f'Missing parameter {option} in section {section}' return result
def getlist(self, section: str, option: str) ‑> List[str]
-
Return a list of strings from a configuration value.
This method reads a string value from the configuration object and splits it by: new lines, spaces, and commas. The values in the returned list are stripped of all white space and removed if empty.
For example, the following values all parse as
:['a', 'b', 'c']
value = a,b,c value = a b c value = a, b, c value = a b c value = a, b, c value = a b ,c
Expand source code
def getlist(self, section: str, option: str) -> List[str]: """ Return a list of strings from a configuration value. This method reads a string value from the configuration object and splits it by: new lines, spaces, and commas. The values in the returned list are stripped of all white space and removed if empty. For example, the following values all parse as <code>`['a', 'b', 'c']`</code>: value = a,b,c value = a b c value = a, b, c value = a b c value = a, b, c value = a b ,c """ return self.parselist(self.get(section, option, fallback=''))