def user_configuration( config_directory: Optional[Path] = None, ) -> Tuple[ AstralityYAMLConfigDict, Dict[str, 'ModuleConfigDict'], Context, Path, ]: """ Return instantiation parameters for ModuleManager. :return: Tuple containing: astrality.yml dictionary, global modules dictionary, global context dictionary, and path to config directory. """ config_directory, config_file = infer_config_location(config_directory) # First get global context, which we can use when compiling other files context_file = config_directory / 'context.yml' if context_file.exists(): global_context = Context(utils.compile_yaml( path=context_file, context=Context(), )) else: global_context = Context() # Global configuration options config: AstralityYAMLConfigDict = utils.compile_yaml( # type: ignore path=config_file, context=global_context, ) # Insert default global settings that are not specified for section_name in ('astrality', 'modules',): section_content = config.get(section_name, {}) config[section_name] = ASTRALITY_DEFAULT_GLOBAL_SETTINGS[section_name].copy() # type: ignore # noqa config[section_name].update(section_content) # type: ignore # Globally defined modules modules_file = config_directory / 'modules.yml' if modules_file.exists(): modules = utils.compile_yaml( path=modules_file, context=global_context, ) else: modules = {} return config, modules, global_context, config_directory
def dummy_config(): """Return dummy configuration YAML file.""" test_conf = Path(__file__).parents[1] / 'test_config' / 'test.yml' return compile_yaml( path=test_conf, context=Context(), )
def test_dict_from_config_file(self, dir_with_compilable_files): """Placeholders should be properly substituted.""" config = compile_yaml( path=dir_with_compilable_files / 'astrality.yml', context={}, ) assert config == { 'key1': 'test_value', 'key2': 'test', }
def test_hot_reloading( test_template_targets, test_config_directory, ): template_target1, template_target2 = test_template_targets config1 = test_config_directory / 'modules1.yml' config2 = test_config_directory / 'modules2.yml' target_config = test_config_directory / 'modules.yml' # Copy the first configuration into place shutil.copy(str(config1), str(target_config)) modules1 = utils.compile_yaml( config1, context={}, ) application_config1 = {'astrality': {'hot_reload_config': True}} module_manager = ModuleManager( config=application_config1, modules=modules1, directory=test_config_directory, ) # Before beginning, the template should not be compiled assert not template_target1.is_file() # But when we finalize tasks, it should be compiled module_manager.finish_tasks() assert template_target1.is_file() # Also check that the filewatcher has been started assert module_manager.directory_watcher.observer.is_alive() # We now "edit" the configuration file shutil.copy(str(config2), str(target_config)) # Since hot reloading is enabled, the new template target should be # compiled, and the old one cleaned up retry = Retry() assert retry(lambda: template_target2.is_file()) assert retry(lambda: not template_target1.is_file()) # And we switch back again shutil.copy(str(config1), str(target_config)) assert retry(lambda: template_target1.is_file()) assert retry(lambda: not template_target2.is_file()) # Cleanup config file if target_config.is_file(): os.remove(target_config) # Stop the filewatcher module_manager.directory_watcher.stop()
def context(self, context: Context = Context()) -> Context: """ Return context defined in module source. :param context: Context used when compiling "context.yml". :return: Context dictionary. """ if not self.context_file.exists(): return Context() if hasattr(self, '_context'): return self._context self._context = Context(utils.compile_yaml( path=self.context_file, context=context, )) return self._context
def import_context( self, from_path: Path, from_section: Optional[str] = None, to_section: Optional[str] = None, ) -> None: """ Insert context values from yml file. :param from_path: Path to .yml file or directory containing "context.yml". :param from_section: If given, only import specific section from path. :param to_section: If given, rename from_section to to_section. """ new_context = utils.compile_yaml( path=from_path, context=self, ) logger = logging.getLogger(__name__) if from_section is None and to_section is None: logger.info( f'[import_context] All sections from "{from_path}".', ) self.update(new_context) elif from_section and to_section: logger.info( f'[import_context] Section "{from_section}" from "{from_path}" ' f'into section "{to_section}".', ) self[to_section] = new_context[from_section] else: assert from_section logger.info( f'[import_context] Section "{from_section}" ' f'from "{from_path}" ', ) self[from_section] = new_context[from_section]
def filter_config_file( config_file: Path, context: Context, enabled_module_name: str, prepend: str, ) -> Dict[str, 'ModuleConfigDict']: """ Return a filtered dictionary representing `config_file`. Only modules given by `enabled_module_name` are kept, and their names are prepended with `prepend`. """ assert config_file.name == 'modules.yml' try: modules_dict = utils.compile_yaml( path=config_file, context=context, ) except FileNotFoundError: logger.warning( f'Non-existent module configuration file "{config_file}" ' 'Skipping enabled module ' f'"{prepend}{enabled_module_name}"', ) return {} if not modules_dict: # pragma: no cover logger.warning( f'Empty modules configuration "{config_file}".', ) return {} elif not isinstance(modules_dict, dict): # pragma: no cover logger.critical( f'Configuration file "{config_file}" not formated as ' 'a dictionary at root indentation.', ) raise MisconfiguredConfigurationFile modules = tuple(modules_dict.keys()) if enabled_module_name != '*' \ and enabled_module_name not in modules: raise NonExistentEnabledModule # We rename each module to module/{self.name}.module_name # in order to prevent naming conflicts when using modules provided # from a third party with the same name as another managed module. # This way you can use a module named "conky" from two third parties, # in addition to providing your own. for module_name in modules: if not enabled_module_name == '*' \ and enabled_module_name != module_name: # The module is not enabled, remove the module modules_dict.pop(module_name) continue # Replace the module name with folder_name.module_name non_conflicting_module_name = prepend + module_name module_section = modules_dict.pop(module_name) modules_dict[non_conflicting_module_name] = module_section return modules_dict
def dummy_config(): test_conf = Path(__file__).parents[1] / 'test_config' / 'test.yml' return compile_yaml( path=test_conf, context=Context(), )