Skip to content

Commit

Permalink
Refactor ModuleManager file system callback
Browse files Browse the repository at this point in the history
  • Loading branch information
JakobGM committed Feb 19, 2018
1 parent eb3d9b1 commit 2935a78
Show file tree
Hide file tree
Showing 2 changed files with 91 additions and 88 deletions.
171 changes: 87 additions & 84 deletions astrality/module.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,12 +156,29 @@ def _import_event_block(
into['import_context'].extend(from_event_block_dict['import_context'])
into['compile'].extend(from_event_block_dict['compile'])

def startup_commands(self) -> Tuple[str, ...]:
"""Return commands to be run on Module instance startup."""
startup_commands: List[str] = self.module_config['on_startup']['run']
def commands(
self,
block: str,
modified_file: Optional[str] = None,
) -> Tuple[str, ...]:
"""
Return all shell commands to be run for a specific block, i.e.
any of on_startup, on_event, on_exit, or on_modified.
A modified file action block is given by block='on_modified:file/path'.
"""

startup_commands: List[str]

if modified_file:
assert block == 'on_modified'
startup_commands = self.module_config['on_modified'][modified_file]['run']
else:
assert block in ('on_startup', 'on_event', 'on_exit',)
startup_commands = self.module_config[block]['run']

if len(startup_commands) == 0:
logger.debug(f'[module/{self.name}] No startup command specified.')
logger.debug(f'[module/{self.name}] No {block} command specified.')
return ()
else:
return tuple(
Expand All @@ -170,48 +187,21 @@ def startup_commands(self) -> Tuple[str, ...]:
in startup_commands
)

def startup_commands(self) -> Tuple[str, ...]:
"""Return commands to be run on Module instance startup."""
return self.commands('on_startup')

def on_event_commands(self) -> Tuple[str, ...]:
"""Commands to be run when self.event_listener event changes."""
on_event_commands: List[str] = self.module_config['on_event']['run']

if len(on_event_commands) == 0:
logger.debug(f'[module/{self.name}] No event command specified.')
return ()
else:
return tuple(
self.interpolate_string(command)
for command
in on_event_commands
)
return self.commands('on_event')

def exit_commands(self) -> Tuple[str, ...]:
"""Commands to be run on Module instance shutdown."""
exit_commands: List[str] = self.module_config['on_exit']['run']
return self.commands('on_exit')

if len(exit_commands) == 0:
logger.debug(f'[module/{self.name}] No exit command specified.')
return ()
else:
return tuple(
self.interpolate_string(command)
for command
in exit_commands
)

def modified_commands(self, template_name: str) -> Tuple[str, ...]:
def modified_commands(self, specified_path: str) -> Tuple[str, ...]:
"""Commands to be run when a module template is modified."""
modified_commands: List[str] = self.module_config['on_modified'][template_name]['run']

if len(modified_commands) == 0:
logger.debug(f'[module/{self.name}] No modified command specified.')
return ()
else:
return tuple(
self.interpolate_string(command)
for command
in modified_commands
)

return self.commands('on_modified', specified_path)

def context_section_imports(
self,
Expand Down Expand Up @@ -347,7 +337,7 @@ def __init__(self, config: ApplicationConfig) -> None:
# Initialize the config directory watcher, but don't start it yet
self.directory_watcher = DirectoryWatcher(
directory=self.application_config['_runtime']['config_directory'],
on_modified=self.modified,
on_modified=self.file_system_modified,
)

def __len__(self) -> int:
Expand Down Expand Up @@ -603,52 +593,10 @@ def exit(self):
# Stop watching config directory for file changes
self.directory_watcher.stop()

def modified(self, modified: Path):
def on_modified(self, modified: Path) -> None:
"""
Callback for when files within the config directory are modified.
Run any context imports, compilations, and shell commands specified
within the on_modified event block of each module.
Also, if hot_reload is True, we reinstantiate the ModuleManager object
if the application configuration has been modified.
Perform actions when a watched file is modified.
"""
if modified == self.application_config['_runtime']['config_directory'] / 'astrality.yaml':
# The application configuration file has been modified

if not self.application_config['settings/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=modified.parent,
)

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:
# New configuration is invalid, just keep the old one
# TODO: Test this behaviour
logger.error('New configuration detected, but it is invalid!')
pass

return

if not modified in self.on_modified_paths:
# The modified file is not specified in any of the modules
return

watched_file = self.on_modified_paths[modified]
module = watched_file.module
specified_path = watched_file.specified_path
Expand All @@ -670,6 +618,61 @@ def modified(self, modified: Path):
module_name=module.name,
)


def file_system_modified(self, modified: Path) -> None:
"""
Callback for when files within the config directory are modified.
Run any context imports, compilations, and shell commands specified
within the on_modified event block of each module.
Also, if hot_reload is True, we reinstantiate the ModuleManager object
if the application configuration has been modified.
"""
if modified == self.application_config['_runtime']['config_directory'] / 'astrality.yaml':
self.on_application_config_modified()
return

if modified in self.on_modified_paths:
# The modified file is specified in one of the modules
self.on_modified(modified)
return

def on_application_config_modified(self):
"""
Reload the ModuleManager if astrality.yaml has been modified.
Reloadnig the module manager only occurs if the user has configured
`hot_reload_config`.
"""
if not self.application_config['settings/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:
# New configuration is invalid, just keep the old one
# TODO: Test this behaviour
logger.error('New configuration detected, but it is invalid!')
pass


def run_shell(
self,
command: str,
Expand Down
8 changes: 4 additions & 4 deletions astrality/tests/test_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ def test_running_module_startup_command_when_no_command_is_specified(
(
'astrality',
logging.DEBUG,
'[module/test_module] No startup command specified.',
'[module/test_module] No on_startup command specified.',
),
]

Expand Down Expand Up @@ -311,7 +311,7 @@ def test_running_module_on_event_command_when_no_command_is_specified(
(
'astrality',
logging.DEBUG,
'[module/test_module] No event command specified.',
'[module/test_module] No on_event command specified.',
),
]

Expand Down Expand Up @@ -348,7 +348,7 @@ def test_running_module_exit_command_when_no_command_is_specified(
(
'astrality',
logging.DEBUG,
'[module/test_module] No exit command specified.',
'[module/test_module] No on_exit command specified.',
),
]

Expand Down Expand Up @@ -913,7 +913,7 @@ def test_direct_invocation_of_modifed_method_of_module_manager(self, modules_con
empty_template.write_text('new content')

# And trigger the modified method manually
module_manager.modified(empty_template)
module_manager.file_system_modified(empty_template)

# And assert that the new template has been compiled
assert empty_template_target.is_file()
Expand Down

0 comments on commit 2935a78

Please sign in to comment.