Example #1
0
 def test_update(self):
     one_conf_dict = {
         'key1': 'value1',
         1: 'value2',
         2: {
             1: 'some_value'
         },
     }
     another_conf_dict = {
         'key3': ('one', 'two', 'three'),
         'key4': {
             1: 'uno',
             'key4-2': 'dos'
         },
     }
     merged_conf_dicts = {
         'key1': 'value1',
         1: 'value2',
         2: {
             1: 'some_value'
         },
         'key3': ('one', 'two', 'three'),
         'key4': {
             1: 'uno',
             'key4-2': 'dos'
         },
     }
     config = Context(one_conf_dict)
     config.update(another_conf_dict)
     assert config == merged_conf_dicts
Example #2
0
def test_that_external_module_contexts_are_imported_correctly(
    test_config_directory, ):
    application_config = {
        'modules': {
            'modules_directory': 'test_modules',
            'enabled_modules': [{
                'name': 'module_with_context::*'
            }],
        },
    }

    context = Context({
        'china': {
            'capitol': 'beijing',
        },
    })
    module_manager = ModuleManager(
        config=application_config,
        context=context,
    )

    expected_context = Context({
        'laos': {
            'capitol': 'vientiane'
        },
        'china': {
            'capitol': 'beijing'
        },
    })
    assert module_manager.application_context == expected_context
Example #3
0
 def test_items(self):
     config = Context()
     config['4'] = 'test'
     config['font'] = 'Comic Sans'
     config['5'] = '8'
     assert list(config.items()) == [
         ('4', 'test'),
         ('font', 'Comic Sans'),
         ('5', '8'),
     ]
Example #4
0
def test_importing_context_from_compiled_yml_file():
    """YAML files should be importable into the context."""
    context = Context({'section1': {'key_one': 'value_one'}})
    assert context['section1']['key_one'] == 'value_one'

    test_config_file = Path(__file__).parent / 'test_config' / 'test.yml'
    context.import_context(
        from_path=test_config_file,
        from_section='section2',
        to_section='new_section',
    )

    assert context['section1']['key_one'] == 'value_one'
    assert context['new_section']['var3'] == 'value1'

    context.import_context(
        from_path=test_config_file,
        from_section='section3',
        to_section='section3',
    )
    assert context['section3']['env_variable'] == 'test_value, hello'

    context.import_context(
        from_path=test_config_file,
        from_section='section1',
        to_section='section1',
    )
    assert context['section1']['var2'] == 'value1/value2'
    assert 'key_one' not in context['section1']
Example #5
0
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
Example #6
0
def test_executing_several_action_blocks(test_config_directory, tmpdir):
    """Invoking execute() should execute all actions."""
    temp_dir = Path(tmpdir)
    target = temp_dir / 'target.tmp'
    touched = temp_dir / 'touched.tmp'

    action_block_dict = {
        'import_context': {
            'from_path': 'context/mercedes.yml'
        },
        'compile': [{
            'content': 'templates/a_car.template',
            'target': str(target),
        }],
        'run': {
            'shell': 'touch ' + str(touched)
        },
        'trigger': {
            'block': 'on_startup'
        },
    }
    context_store = Context()

    action_block = ActionBlock(
        action_block=action_block_dict,
        directory=test_config_directory,
        replacer=lambda x: x,
        context_store=context_store,
    )

    action_block.execute(default_timeout=1)
    assert context_store == {'car': {'manufacturer': 'Mercedes'}}
    assert target.read_text() == 'My car is a Mercedes'
    assert touched.is_file()
Example #7
0
def test_retrieving_triggers_from_action_block():
    """All trigger instructions should be returned."""
    action_block_dict = {
        'trigger': [
            {
                'block': 'on_startup'
            },
            {
                'block': 'on_modified',
                'path': 'test.template'
            },
        ],
    }
    action_block = ActionBlock(
        action_block=action_block_dict,
        directory=Path('/'),
        replacer=lambda x: x,
        context_store=Context(),
    )

    startup_trigger, on_modified_trigger = action_block.triggers()

    assert startup_trigger.block == 'on_startup'
    assert on_modified_trigger.block == 'on_modified'
    assert on_modified_trigger.specified_path == 'test.template'
    assert on_modified_trigger.relative_path == Path('test.template')
    assert on_modified_trigger.absolute_path == Path('/test.template')
Example #8
0
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(),
    )
Example #9
0
    def test_accessing_existing_key(self):
        config = Context()
        config['some_key'] = 'some_value'
        assert config['some_key'] == 'some_value'

        config[-2] = 'some_other_value'
        assert config[-2] == 'some_other_value'
Example #10
0
def test_executing_action_block_with_one_action(
    global_modules_config,
    test_config_directory,
    tmpdir,
):
    """Action block behaviour with only one action specified."""
    temp_dir = Path(tmpdir)
    touched = temp_dir / 'touched.tmp'

    action_block_dict = {
        'run': [{
            'shell': 'touch ' + str(touched)
        }],
    }

    action_block = ActionBlock(
        action_block=action_block_dict,
        directory=test_config_directory,
        replacer=lambda x: x,
        context_store=Context(),
        global_modules_config=global_modules_config,
        module_name='test',
    )

    action_block.execute(default_timeout=1)
    assert touched.is_file()
Example #11
0
    def _action_block_factory(
            import_context={},
            compile={},
            copy={},
            run={},
            stow={},
            symlink={},
            directory=test_config_directory,
            replacer=lambda x: x,
            context_store=Context(),
    ):
        """Return module with given parameters."""
        config = {
            'import_context': import_context,
            'compile': compile,
            'copy': copy,
            'run': run,
            'stow': stow,
            'symlink': symlink,
        }

        return ActionBlock(
            action_block=config,
            directory=directory,
            replacer=replacer,
            context_store=context_store,
        )
def test_that_replacer_is_run_every_time(context_directory):
    """
    The replacer should be run a new every time self.execute() is invoked.
    """
    context_import_dict = {
        'from_path': 'several_sections.yml',
        'from_section': 'section1',
        'to_section': 'whatever',
    }
    context_store = Context()

    class Replacer:
        def __init__(self) -> None:
            self.invoke_number = 0

        def __call__(self, option: str) -> str:
            self.invoke_number += 1
            return option

    replacer = Replacer()
    import_context_action = ImportContextAction(
        options=context_import_dict,
        directory=context_directory,
        replacer=replacer,
        context_store=context_store,
        creation_store=CreatedFiles().wrapper_for(module='test'),
    )

    import_context_action.execute()
    assert replacer.invoke_number == 3

    import_context_action.execute()
    assert replacer.invoke_number == 6
def test_importing_entire_file(context_directory):
    """
    Test importing all sections from context file.

    All context sections should be imported in the absence of `from_section`.
    """
    context_import_dict = {
        'from_path': 'several_sections.yml',
    }
    context_store = Context()
    import_context_action = ImportContextAction(
        options=context_import_dict,
        directory=context_directory,
        replacer=lambda x: x,
        context_store=context_store,
        creation_store=CreatedFiles().wrapper_for(module='test'),
    )
    import_context_action.execute()

    expected_context = {
        'section1': {
            'k1_1': 'v1_1',
            'k1_2': 'v1_2',
        },
        'section2': {
            'k2_1': 'v2_1',
            'k2_2': 'v2_2',
        },
    }
    assert context_store == expected_context
Example #14
0
 def test_integer_index_resolution_without_earlier_index_key(self):
     config = Context()
     config['some_key'] = 'some_value'
     with pytest.raises(KeyError) as exception:
         config[2]
     assert exception.value.args[0] == \
         'Integer index "2" is non-existent and ' \
         'had no lower index to be substituted for'
Example #15
0
def single_module_manager(simple_application_config, valid_module_section):
    return ModuleManager(
        config=simple_application_config,
        modules=valid_module_section,
        context=Context({
            'fonts': {1: 'FuraMono Nerd Font'},
        }),
    )
Example #16
0
def test_importing_all_context_sections_from_file(
    test_config_directory,
    action_block_factory,
    module_factory,
    module_manager_factory,
):
    context_file = test_config_directory / 'context' / 'several_sections.yml'
    original_context = Context({
        'section2': {
            'k2_1': 'original_v2_1',
            'k2_2': 'original_v2_2',
        },
        'section3': {
            'k3_1': 'original_v3_1',
            'k3_2': 'original_v3_2',
        },
    })

    import_context = action_block_factory(
        import_context={'from_path': str(context_file)}, )
    module = module_factory(on_startup=import_context)
    module_manager = module_manager_factory(module, context=original_context)
    assert module_manager.application_context == original_context

    # We expect the following context after import
    expected_context = Context({
        'section1': {
            'k1_1': 'v1_1',
            'k1_2': 'v1_2',
        },
        'section2': {
            'k2_1': 'v2_1',
            'k2_2': 'v2_2',
        },
        'section3': {
            'k3_1': 'original_v3_1',
            'k3_2': 'original_v3_2',
        },
    })

    # Now run startup actions, resulting in the file being imported
    module_manager.finish_tasks()

    # Assert that the new application_context is as expected
    assert module_manager.application_context == expected_context
Example #17
0
def test_null_object_pattern():
    """Test initializing action with no behaviour."""
    import_context_action = ImportContextAction(
        options={},
        directory=Path('/'),
        replacer=lambda x: x,
        context_store=Context(),
    )
    import_context_action.execute()
Example #18
0
def test_recompile_templates_when_modified_overridden(
    three_watchable_files,
    test_config_directory,
):
    """
    If a file is watched in a on_modified block, it should override the
    reprocess_modified_files option.
    """
    template, target, touch_target = three_watchable_files
    template.touch()

    modules = {
        'module_name': {
            'on_startup': {
                'compile': {
                    'content': str(template),
                    'target': str(target),
                },
            },
            'on_modified': {
                str(template): {
                    'run': {'shell': 'touch ' + str(touch_target)},
                },
            },
        },
    }

    application_config = {'modules': {'reprocess_modified_files': True}}
    module_manager = ModuleManager(
        config=application_config,
        modules=modules,
        context=Context({
            'section': {1: 'value'},
        }),
        directory=test_config_directory,
    )

    # Sanity check before beginning testing
    with open(template) as file:
        assert file.read() == ''

    assert not target.is_file()

    # Compile the template
    module_manager.finish_tasks()
    with open(target) as file:
        assert file.read() == ''

    # Now write to the template and see if it is *compiled*, but the on_modified
    # command is run instead
    template.write_text('{{ section.2 }}')

    retry = Retry()
    assert retry(lambda: target.read_text() == '')
    assert retry(lambda: touch_target.is_file())

    module_manager.exit()
Example #19
0
    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
Example #20
0
def test_null_object_pattern():
    """An empty action block should have no behaviour."""
    action_block = ActionBlock(
        action_block={},
        directory=Path('/'),
        replacer=lambda x: x,
        context_store=Context(),
    )
    action_block.execute(default_timeout=1)
Example #21
0
 def test_initialization_of_config_class_with_dict(self):
     conf_dict = {
         'key1': 'value1',
         'key2': 'value2',
         'key3': ('one', 'two', 'three'),
         'key4': {'key4-1': 'uno', 'key4-2': 'dos'},
     }
     config = Context(conf_dict)
     assert config == conf_dict
Example #22
0
def test_retrieving_triggers_from_action_block_without_triggers():
    """Action block with no triggers should return empty tuple."""
    action_block = ActionBlock(
        action_block={},
        directory=Path('/'),
        replacer=lambda x: x,
        context_store=Context(),
    )

    assert action_block.triggers() == tuple()
def test_null_object_pattern():
    """Test initializing action with no behaviour."""
    import_context_action = ImportContextAction(
        options={},
        directory=Path('/'),
        replacer=lambda x: x,
        context_store=Context(),
        creation_store=CreatedFiles().wrapper_for(module='test'),
    )
    import_context_action.execute()
Example #24
0
    def test_context_class(self):
        context = Context()
        context[1] = 'firs_value'
        context[2] = 'second_value'
        context['string_key'] = 'string_value'

        assert context[1] == 'firs_value'
        assert context[2] == 'second_value'
        assert context[3] == 'second_value'
        assert context['string_key'] == 'string_value'
Example #25
0
def test_null_object_pattern(global_modules_config):
    """An empty action block should have no behaviour."""
    action_block = ActionBlock(
        action_block={},
        directory=Path('/'),
        replacer=lambda x: x,
        context_store=Context(),
        global_modules_config=global_modules_config,
        module_name='test',
    )
    action_block.execute(default_timeout=1)
Example #26
0
def test_instantiating_context_object_with_path():
    """Paths should be read into the context object."""
    test_context = Path(__file__).parent / 'test_config' / 'test.yml'
    context = Context(test_context)
    assert context == Context({
        'section1': {
            'var1': 'value1',
            'var2': 'value1/value2',
        },
        'section2': {
            'var3': 'value1',
            'empty_string_var': '',
        },
        'section3': {
            'env_variable': 'test_value, hello',
        },
        'section4': {
            1: 'primary_value',
        },
    })
Example #27
0
def module(
    valid_module_section,
    test_config_directory,
):
    return Module(
        name='test_module',
        module_config=valid_module_section['test_module'],
        module_directory=test_config_directory,
        context_store=Context({
            'fonts': {1: 'FuraMono Nerd Font'},
        }),
    )
Example #28
0
    def test_getter(self):
        config = Context()
        assert config.get('from_empty_config') is None

        config['test'] = 'something'
        assert config.get('test') == 'something'
        assert config.get('test', '4') == 'something'

        assert config.get('non_existent_key') is None
        assert config.get('non_existent_key', '4') == '4'
Example #29
0
    def test_updating_context_with_context(self):
        context1 = Context({'key1': 1})
        context2 = Context({'key2': 2})

        context1.update(context2)
        expected_result = Context({'key1': 1, 'key2': 2})
        assert context1 == expected_result
Example #30
0
    def test_compilation_of_template(
        self,
        valid_module_section,
        simple_application_config,
        module,
        conf,
        caplog,
    ):
        valid_module_section[
            'test_module'
        ][
            'event_listener'
        ][
            'type'
        ] = 'solar'

        compiled_template_content = 'some text\n' + os.environ['USER'] \
            + '\nFuraMono Nerd Font'
        module_manager = ModuleManager(
            config=simple_application_config,
            modules=valid_module_section,
            context=Context({
                'fonts': {1: 'FuraMono Nerd Font'},
            }),
        )
        directory = module_manager.config_directory

        caplog.clear()
        module_manager.execute(action='compile', block='on_startup')

        template_file = str(
            (directory / '../templates/test_template.conf').resolve(),
        )
        compiled_template = str(
            list(
                module_manager.modules['test_module']
                .performed_compilations()[Path(template_file)],
            )[0],
        )

        with open('/tmp/compiled_result', 'r') as file:
            compiled_result = file.read()

        assert compiled_template_content == compiled_result
        assert caplog.record_tuples == [
            (
                'astrality.compiler',
                logging.INFO,
                f'[Compiling] Template: "{template_file}" '
                f'-> Target: "{compiled_template}"',
            ),
        ]