Skip to content

Commit

Permalink
Add --module CLI flag for enabling specific module
Browse files Browse the repository at this point in the history
Closes #14.
  • Loading branch information
JakobGM committed May 6, 2018
1 parent 56455f7 commit 69e6bfb
Show file tree
Hide file tree
Showing 8 changed files with 160 additions and 74 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ Added
variables by using the dictionary keys ``installed`` and ``env``
respectively.
- You can now set ``requires`` timeout on a case-by-case basis.
- Add new ``--module`` CLI flag for running specific modules.

Changed
-------
Expand Down
35 changes: 27 additions & 8 deletions astrality/astrality.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,29 @@
import logging
import os
import signal
from typing import Set
import subprocess
import sys
import time
from typing import Set, List

from astrality.config import user_configuration
from astrality.module import ModuleManager


logger = logging.getLogger(__name__)


def main(logging_level: str = 'INFO', test: bool = False):
def main(
modules: List[str] = [],
logging_level: str = 'INFO',
test: bool = False,
):
"""
Run the main process for Astrality.
If test is set to True, then only one main loop is run as an integration
test.
:param modules: Modules to be enabled. If empty, use astrality.yml.
:param logging_level: Loging level.
:param test: If True, return after one iteration loop.
"""
if 'ASTRALITY_LOGGING_LEVEL' in os.environ:
# Override logging level if env variable is set
Expand All @@ -30,8 +36,9 @@ def main(logging_level: str = 'INFO', test: bool = False):
# Set the logging level to the configured setting
logging.basicConfig(level=logging_level)

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

# How to quit this process
def exit_handler(signal=None, frame=None) -> None:
Expand Down Expand Up @@ -75,14 +82,26 @@ def exit_handler(signal=None, frame=None) -> None:
signal.signal(signal.SIGTERM, exit_handler)

try:
config, modules, global_context, directory = user_configuration()
(
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=modules,
modules=module_configs,
context=global_context,
directory=directory,
)
Expand Down
143 changes: 89 additions & 54 deletions astrality/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,61 @@
from astrality.module import ModuleConfigDict # noqa

logger = logging.getLogger(__name__)
ApplicationConfig = Dict[str, Dict[str, Any]]
ASTRALITY_DEFAULT_GLOBAL_SETTINGS = {'astrality': {
'hot_reload_config': False,
'startup_delay': 0,
}}


class EnablingStatementRequired(TypedDict):
"""Required items of astrality.yml::modules:enabled_modules."""

name: str


class EnablingStatement(EnablingStatementRequired, total=False):
"""Optional items of astrality.yml::modules:enabled_modules."""

trusted: bool
autoupdate: bool


class GlobalModulesConfigDict(TypedDict, total=False):
"""Optional items in astrality.yml::modules."""

requires_timeout: Union[int, float]
run_timeout: Union[int, float]
reprocess_modified_files: bool
modules_directory: str
enabled_modules: List[EnablingStatement]


class GlobalAstralityConfigDict(TypedDict, total=False):
"""Optional items in astrality.yml::astrality."""

hot_reload_config: bool
startup_delay: Union[int, float]


class AstralityYAMLConfigDict(TypedDict, total=False):
"""Optional items in astrality.yml."""

astrality: GlobalAstralityConfigDict
modules: GlobalModulesConfigDict


ASTRALITY_DEFAULT_GLOBAL_SETTINGS: AstralityYAMLConfigDict = {
'astrality': {
'hot_reload_config': False,
'startup_delay': 0,
},
'modules': {
'requires_timeout': 1,
'run_timeout': 0,
'reprocess_modified_files': False,
'modules_directory': 'modules',
'enabled_modules': [
{'name': '*'},
{'name': '*::*'},
],
},
}


def resolve_config_directory() -> Path:
Expand Down Expand Up @@ -97,7 +147,12 @@ def infer_config_location(

def user_configuration(
config_directory: Optional[Path] = None,
) -> Tuple[ApplicationConfig, Dict, Context, Path]:
) -> Tuple[
AstralityYAMLConfigDict,
Dict[str, 'ModuleConfigDict'],
Context,
Path,
]:
"""
Return instantiation parameters for ModuleManager.
Expand All @@ -117,26 +172,28 @@ def user_configuration(
global_context = Context()

# Global configuration options
config = utils.compile_yaml(
config: AstralityYAMLConfigDict = utils.compile_yaml( # type: ignore
path=config_file,
context=global_context,
)

# Insert default global settings that are not specified
user_settings = config.get('astrality', {})
config['astrality'] = \
ASTRALITY_DEFAULT_GLOBAL_SETTINGS['astrality'].copy()
config['astrality'].update(user_settings)
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_config = utils.compile_yaml(
modules = utils.compile_yaml(
path=modules_file,
context=global_context,
)
else:
modules = {}

return config, modules_config, global_context, config_directory
return config, modules, global_context, config_directory


def create_config_directory(path: Optional[Path] = None, empty=False) -> Path:
Expand Down Expand Up @@ -210,22 +267,6 @@ def expand_globbed_path(path: Path, config_directory: Path) -> Set[Path]:
return set(path for path in expanded_paths if path.is_file())


class EnablingStatementRequired(TypedDict):
"""The required fields of a config item which enables a specific module."""

name: str


class EnablingStatement(EnablingStatementRequired, total=False):
"""Dictionary defining an externally defined module."""

trusted: bool
autoupdate: bool


ModuleConfig = Dict[str, Any]


class ModuleSource(ABC):
"""Superclass for the source of an enabled module."""

Expand Down Expand Up @@ -253,8 +294,8 @@ class ModuleSource(ABC):
# Cached property containing module context
_context: Context

# Cached property containing entire configuration, modules + context
_config: Dict
# Options defined in astrality.yml
_config: Dict[str, 'ModuleConfigDict']

@abstractmethod
def __init__(
Expand All @@ -274,7 +315,7 @@ def modules(self, context: Context) -> Dict[Any, Any]:
'/module', and module configuration values.
"""
if not self.modules_file.exists():
return {}
self._modules = {}

if hasattr(self, '_modules'):
return self._modules
Expand Down Expand Up @@ -306,7 +347,10 @@ def context(self, context: Context = Context()) -> Context:
))
return self._context

def config(self, context: Context = Context()) -> Dict:
def config(
self,
context: Context = Context(),
) -> Dict[str, 'ModuleConfigDict']:
"""
Return all configuration options defined in module source.
Expand All @@ -325,7 +369,7 @@ def config(self, context: Context = Context()) -> Dict:

def __contains__(self, module_name: str) -> bool:
"""Return True if this source contains enabled module_name."""
return module_name in self._config
return module_name in self._modules

@classmethod
def represented_by(cls, module_name: str) -> bool:
Expand Down Expand Up @@ -380,7 +424,6 @@ class GithubModuleSource(ModuleSource):
"""Module defined in a GitHub repository."""

name_syntax = re.compile(r'^github::.+/.+(::(\w+|\*))?$')
_config: ApplicationConfig

def __init__(
self,
Expand Down Expand Up @@ -477,17 +520,22 @@ def __init__(
self.prepend = str(self.relative_directory_path) + '::'
self.trusted = enabling_statement.get('trusted', True)

self.directory = modules_directory / self.relative_directory_path
self.directory = expand_path(
path=self.relative_directory_path,
config_directory=modules_directory,
)
self.modules_file = self.directory / 'modules.yml'
self.context_file = self.directory / 'context.yml'

def __repr__(self):
"""Human-readable representation of a DirectoryModuleSource object."""
return f'DirectoryModuleSource(' \
'name={self.relative_directory_path}::'\
'{self.enabled_module_name}, '\
'directory={self.directory}, ' \
'trusted={self.trusted})'
return ''.join((
'DirectoryModuleSource(',
f'name={self.relative_directory_path}::',
f'{self.enabled_module_name}, ',
f'directory={self.directory}, ',
f'trusted={self.trusted})',
))

def __eq__(self, other) -> bool:
"""
Expand Down Expand Up @@ -608,9 +656,6 @@ def compile_config_files(

def __contains__(self, module_name: str) -> bool:
"""Return True if the given module name is supposed to be enabled."""
if module_name[:7].lower() == 'module/':
module_name = module_name[7:]

source_type = ModuleSource.type(of=module_name)
for module_source in self.source_types[source_type]:
if module_name in module_source:
Expand All @@ -627,16 +672,6 @@ def __repr__(self) -> str:
)


class GlobalModulesConfigDict(TypedDict, total=False):
"""Dictionary defining configuration options for Modules."""

requires_timeout: Union[int, float]
run_timeout: Union[int, float]
reprocess_modified_files: bool
modules_directory: str
enabled_modules: List[EnablingStatement]


class GlobalModulesConfig:
"""
User modules configuration.
Expand Down
9 changes: 4 additions & 5 deletions astrality/module.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@

from astrality.actions import ActionBlock, ActionBlockDict
from astrality.config import (
ApplicationConfig,
AstralityYAMLConfigDict,
GlobalModulesConfig,
expand_path,
user_configuration,
Expand Down Expand Up @@ -60,7 +60,6 @@ class ModuleActionBlocks(TypedDict):
on_modified: Dict[Path, ActionBlock]


ModuleConfig = Dict[str, ModuleConfigDict]
logger = logging.getLogger(__name__)


Expand Down Expand Up @@ -366,7 +365,7 @@ class ModuleManager:

def __init__(
self,
config: ApplicationConfig = {},
config: AstralityYAMLConfigDict = {},
modules: Dict[str, ModuleConfigDict] = {},
context: Context = Context(),
directory: Path = Path(__file__).parent / 'tests' / 'test_config',
Expand All @@ -380,7 +379,7 @@ def __init__(
self.last_module_events: Dict[str, str] = {}

# Get module configurations which are externally defined
self.global_modules_config = GlobalModulesConfig( # type: ignore
self.global_modules_config = GlobalModulesConfig(
config=config.get('modules', {}),
config_directory=self.config_directory,
)
Expand All @@ -399,7 +398,7 @@ def __init__(
module_context.update(self.application_context)
self.application_context = module_context

module_configs = external_module_source.config(
module_configs = external_module_source.modules(
context=self.application_context,
)
module_directory = external_module_source.directory
Expand Down
7 changes: 4 additions & 3 deletions astrality/tests/config/test_modules_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ def test_error_on_enabling_non_existent_module(
enabling_statement=enabling_statement,
modules_directory=test_config_directory / 'freezed_modules',
)
directory_module.config({})
directory_module.modules({})

def test_recursive_module_directory(
self,
Expand Down Expand Up @@ -255,7 +255,7 @@ def test_checking_if_directory_modules_contain_module_name(
enabling_statement=enabling_statement,
modules_directory=test_config_directory / 'freezed_modules',
)
directory_module.config({})
directory_module.modules({})

assert 'south_america::brazil' in directory_module

Expand Down Expand Up @@ -295,7 +295,8 @@ def test_that_enabled_modules_are_detected_correctly(self):
enabling_statement={'name': 'enabled_module'},
modules_directory=Path('/'),
)
global_module_source.config({})
global_module_source.modules({})

assert 'enabled_module' in global_module_source
assert 'disabled_module' not in global_module_source
assert '*' not in global_module_source
Expand Down

0 comments on commit 69e6bfb

Please sign in to comment.