Beispiel #1
0
    def test_missing_global_configuration_file(self, monkeypatch, tmpdir):
        """Missing astrality.yml should result in default values."""
        # Create directory used as astrality config directory
        config_home = Path(tmpdir)
        monkeypatch.setattr(
            os,
            'environ',
            {'ASTRALITY_CONFIG_HOME': str(config_home)},
        )

        # Sanity check
        assert len(list(config_home.iterdir())) == 0

        # Create modules and context files, but *not* astrality.yml
        modules = {'A': {'enabled': False}}
        dump_yaml(path=config_home / 'modules.yml', data=modules)

        context = {'section': {'key': 'value'}}
        dump_yaml(path=config_home / 'context.yml', data=context)

        (
            global_config,
            global_modules,
            global_context,
            inferred_path,
        ) = user_configuration()
        assert global_config == ASTRALITY_DEFAULT_GLOBAL_SETTINGS
        assert global_modules == modules
        assert global_context == context
        assert inferred_path == config_home
Beispiel #2
0
    def on_application_config_modified(self):
        """
        Reload the ModuleManager if astrality.yml has been modified.

        Reloadnig the module manager only occurs if the user has configured
        `hot_reload_config`.
        """
        if not self.application_config['config/astrality']['hot_reload_config']:
            # Hot reloading is not enabled, so we return early
            return

        # Hot reloading is enabled, get the new configuration dict
        new_application_config = user_configuration(
            config_directory=self.config_directory, )

        try:
            # Reinstantiate this object
            new_module_manager = ModuleManager(new_application_config)

            # Run all old exit actions, since the new config is valid
            self.exit()

            # Swap place with the new configuration
            self = new_module_manager

            # Run startup commands from the new configuration
            self.finish_tasks()
        except Exception:
            # New configuration is invalid, just keep the old one
            # TODO: Test this behaviour
            logger.error('New configuration detected, but it is invalid!')
            pass
Beispiel #3
0
    def on_application_config_modified(self):
        """
        Reload the ModuleManager if astrality.yml has been modified.

        Reloadnig the module manager only occurs if the user has configured
        `hot_reload_config`.
        """
        if not self.application_config.get(
            'astrality',
            {},
        ).get(
            'hot_reload_config',
            False,
        ):
            # Hot reloading is not enabled, so we return early
            logger.info('"hot_reload" disabled.')
            return

        # Hot reloading is enabled, get the new configuration dict
        logger.info('Reloading $ASTRALITY_CONFIG_HOME...')
        (
            new_application_config,
            new_modules,
            new_context,
            directory,
        ) = user_configuration(
            config_directory=self.config_directory,
        )

        try:
            # Reinstantiate this object
            new_module_manager = ModuleManager(
                config=new_application_config,
                modules=new_modules,
                context=new_context,
                directory=directory,
            )

            # Run all old exit actions, since the new config is valid
            self.exit()

            # Swap place with the new configuration
            self = new_module_manager

            # Run startup commands from the new configuration
            self.finish_tasks()
        except Exception:
            # New configuration is invalid, just keep the old one
            # TODO: Test this behaviour
            logger.error('New configuration detected, but it is invalid!')
            pass
Beispiel #4
0
 def test_get_user_configuration(self, dir_with_compilable_files):
     user_conf = user_configuration(dir_with_compilable_files)
     assert user_conf['key1'] == 'test_value'
     assert user_conf['key2'] == 'test'
Beispiel #5
0
def main(
    modules: List[str] = [],
    logging_level: str = 'INFO',
    dry_run: bool = False,
    test: bool = False,
):
    """
    Run the main process for Astrality.

    :param modules: Modules to be enabled. If empty, use astrality.yml.
    :param logging_level: Loging level.
    :param dry_run: If file system actions should be printed and skipped.
    :param test: If True, return after one iteration loop.
    """
    if 'ASTRALITY_LOGGING_LEVEL' in os.environ:
        # Override logging level if env variable is set
        logging_level = os.environ['ASTRALITY_LOGGING_LEVEL']

    # Set the logging level to the configured setting
    logging.basicConfig(
        level=logging.getLevelName(logging_level),  # type: ignore
    )

    if not modules and not dry_run and not test:
        # Quit old astrality instances
        kill_old_astrality_processes()

    # How to quit this process
    def exit_handler(signal=None, frame=None) -> None:
        """
        Cleanup all temporary files and run module exit handlers.

        The temp directory is left alone, for two reasons:
        1: An empty directory uses neglible disk space.
        2: If this process is interrupted by another Astrality instance,
           we might experience race conditions when the exit handler deletes
           the temporary directory *after* the new Astrality instance creates
           it.
        """
        logger.critical('Astrality was interrupted')
        logger.info('Cleaning up temporary files before exiting...')

        try:
            # Run all the module exit handlers
            module_manager.exit()
        except NameError:
            # The module_manager instance has not been assigned yet.
            pass

        try:
            sys.exit(0)
        except SystemExit:
            os._exit(0)

    # Some SIGINT signals are not properly interupted by python and converted
    # into KeyboardInterrupts, so we have to register a signal handler to
    # safeguard against such cases. This seems to be the case when conky is
    # launched as a subprocess, making it the process that receives the SIGINT
    # signal and not python. These signal handlers cause issues for \
    # NamedTemporaryFile.close() though, so they are only registrered when
    # we are not testing.
    if not test:
        signal.signal(signal.SIGINT, exit_handler)

        # Also catch kill-signkal from OS,
        # e.g. `kill $(pgrep -f "python astrality.py")`
        signal.signal(signal.SIGTERM, exit_handler)

    try:
        (
            config,
            module_configs,
            global_context,
            directory,
        ) = user_configuration()

        if modules:
            config['modules']['enabled_modules'] = [{
                'name': module_name
            } for module_name in modules]

        # Delay further actions if configuration says so
        time.sleep(config['astrality']['startup_delay'])

        module_manager = ModuleManager(
            config=config,
            modules=module_configs,
            context=global_context,
            directory=directory,
            dry_run=dry_run,
        )
        module_manager.finish_tasks()

        while True:
            if module_manager.has_unfinished_tasks():
                # TODO: Log which new event which has been detected
                logger.info('New event detected.')
                module_manager.finish_tasks()
                logger.info(f'Event change routine finished.')

            if test or dry_run:
                logger.debug('Main loop interupted due to --dry-run.')
                return
            elif not module_manager.keep_running:
                logger.info(
                    'No more tasks to be performed. '
                    'Executing on_exit blocks.', )
                module_manager.exit()
                return
            else:
                logger.info(
                    f'Waiting {module_manager.time_until_next_event()} '
                    'until next event change and ensuing update.', )

                # Weird bug related to sleeping more than 10e7 seconds
                # on MacOS, causing OSError: Invalid Argument
                wait = module_manager.time_until_next_event().total_seconds()
                if wait >= 10e7:
                    wait = 10e7

                time.sleep(wait)

    except KeyboardInterrupt:  # pragma: no cover
        exit_handler()
 def test_get_user_configuration(self, dir_with_compilable_files):
     """user_configuration should use compile_yaml properly."""
     user_conf, *_ = user_configuration(dir_with_compilable_files)
     assert user_conf['key1'] == 'test_value'
     assert user_conf['key2'] == 'test'
Beispiel #7
0
def modules():
    """Return the modules object for the example configuration."""
    this_test_file = os.path.abspath(__file__)
    conf_path = Path(this_test_file).parents[1] / 'config'
    return user_configuration(conf_path)[1]