Skip to content

Commit

Permalink
added unload_all_loaded_modules method in loader. Used when changing …
Browse files Browse the repository at this point in the history
…user, on_change_callback( user)
  • Loading branch information
quintijn committed Oct 8, 2023
1 parent 1bc31e9 commit b390a1a
Show file tree
Hide file tree
Showing 2 changed files with 66 additions and 13 deletions.
18 changes: 15 additions & 3 deletions src/natlinkcore/loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -260,9 +260,12 @@ def _call_and_catch_all_exceptions(self, fn: Callable[[], None]) -> None:

def unload_module(self, module: ModuleType) -> None:
unload = getattr(module, 'unload', None)
if unload is not None:
self.logger.debug(f'unloading module: {module.__name__}')
self._call_and_catch_all_exceptions(unload)
if unload is None:
self.logger.info(f'cannot unload module {module.__name__}')
return
self.logger.debug(f'unloading module: {module.__name__}')
self._call_and_catch_all_exceptions(unload)


@staticmethod
def _import_module_from_path(mod_path: Path) -> ModuleType:
Expand Down Expand Up @@ -357,6 +360,13 @@ def load_or_reload_modules(self, mod_paths: Iterable[Path], force_load: bool = N
for mod_path in mod_paths:
self.load_or_reload_module(mod_path, force_load=force_load)
self.seen.add(mod_path)

def unload_all_loaded_modules(self):
"""unload the modules that are loaded, and empty the bad modules list
"""
for module in self.loaded_modules.values():
self.unload_module(module)
self.bad_modules.clear()

def remove_modules_that_no_longer_exist(self) -> None:
mod_paths = self.module_paths_for_user
Expand Down Expand Up @@ -401,6 +411,8 @@ def on_change_callback(self, change_type: str, args: Any) -> None:
self.set_user_language(args)
self.logger.debug(f'on_change_callback, user "{self.user}", profile: "{self.profile}", language: "{self.language}"')
if self.config.load_on_user_changed:
# added line, QH, 2023-10-08
self.unload_all_loaded_modules()
self.trigger_load(force_load=True)
elif change_type == 'mic' and args == 'on':
self.logger.debug('on_change_callback called with: "mic", "on"')
Expand Down
61 changes: 51 additions & 10 deletions tests/test_loader.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@

#pylint:disable= C0114, C0116, W0401, W0614, W0621, W0108. W0212, C2801, C3001

from pathlib import Path

import pytest
import pathlib as p
from natlinkcore.loader import *
import debugpy

Expand Down Expand Up @@ -35,7 +36,7 @@ def sample_config(sample_name) -> 'NatlinkConfig':
"""
load a config file from the config files subfolder
"""
sample_ini= (p.Path(__file__).parent) / "config_files" / sample_name
sample_ini= (Path(__file__).parent) / "config_files" / sample_name
test_config = NatlinkConfig.from_file(sample_ini)
return test_config

Expand Down Expand Up @@ -213,7 +214,7 @@ def test_load_single_good_script_from_user_dir(tmpdir, empty_config, logger, mon
assert set(main.load_attempt_times.keys()) == {a_path}
assert main.load_attempt_times[a_path] == mtime
assert main.loaded_modules[a_path].x == 0

assert logger.messages['info'][0] == 'loading module: _a'
del_loaded_modules(main)


Expand Down Expand Up @@ -242,7 +243,7 @@ def test_reload_single_changed_good_script(tmpdir, empty_config, logger, monkeyp
assert set(main.load_attempt_times.keys()) == {a_path}
assert main.load_attempt_times[a_path] == mtime
assert main.loaded_modules[a_path].x == 1

assert logger.messages['info'] == ['loading module: _a', 'reloading module: _a', 'cannot unload module _a']
del_loaded_modules(main)


Expand Down Expand Up @@ -284,10 +285,11 @@ def test_reload_should_skip_single_good_unchanged_script(tmpdir, empty_config, l

main.load_or_reload_modules(main.module_paths_for_user)

## changing script, but NOT changing mtime
a_script.write("""x=1""")
# set the mtime to the old mtime, so natlink should NOT reload
a_script.setmtime(mtime)
mtime += 1.0
# mtime += 1.0
main.seen.clear() # is done in trigger_load
main.load_or_reload_modules(main.module_paths_for_user)
assert set(main.loaded_modules.keys()) == {a_path}
Expand All @@ -298,9 +300,9 @@ def test_reload_should_skip_single_good_unchanged_script(tmpdir, empty_config, l
# make sure it still has the old value, not the new one
assert main.loaded_modules[a_path].x == 0

## TODO how solve this (QH)
msg = 'skipping unchanged loaded module: _a'
assert msg in logger.messages['debug']
# removed this message, because of too many messages...
# msg = 'skipping unchanged loaded module: _a'
assert logger.messages['debug'] == []

del_loaded_modules(main)

Expand Down Expand Up @@ -410,8 +412,10 @@ def test_reload_should_skip_single_bad_unchanged_script(tmpdir, empty_config, lo
assert set(main.load_attempt_times.keys()) == {a_path}
assert main.load_attempt_times[a_path] == mtime

msg = 'skipping unchanged bad module: _a'
assert msg in logger.messages['info']
# removed debug message, because of too many debug lines (QH)
# msg = 'skipping unchanged bad module: _a'
# the debug messages are the exception when loading the bad module
assert len(logger.messages['debug']) == 2

del_loaded_modules(main)

Expand Down Expand Up @@ -478,6 +482,43 @@ def test_load_single_bad_script_that_was_previously_good(tmpdir, empty_config, l
del_loaded_modules(main)
#

def test_unload_all_loaded_modules(tmpdir, empty_config, logger, monkeypatch):
config = empty_config
config.directories_by_user['user'] = [tmpdir.strpath]
a_script = tmpdir.join('_a.py')
a_path = Path(a_script.strpath)
mtime = 123456.0
a_script.write("""x=0""")
a_script.setmtime(mtime)
monkeypatch.setattr(time, 'time', lambda: mtime)

bad_script = tmpdir.join('_bad.py')
bad_path = Path(bad_script.strpath)
mtime = 123456.0
bad_script.write("""x=; #a syntax error.""")
bad_script.setmtime(mtime)
monkeypatch.setattr(time, 'time', lambda: mtime)

main = NatlinkMain(logger, config)
main.config = config
main.__init__(logger=logger, config=config)

_modules = main.module_paths_for_user
assert main.module_paths_for_user == []
main.user = 'user' # this way, because now user is a property
_modules = main.module_paths_for_user
assert main.module_paths_for_user == [a_path, bad_path]

main.load_or_reload_modules(main.module_paths_for_user)
_mainkeys = set(main.loaded_modules.keys())
assert set(main.loaded_modules.keys()) == {a_path}
assert len(main.bad_modules) == 1
# assume unloading takes place, because module has a unload attribute
main.unload_all_loaded_modules()
assert logger.messages['info'] == ['loading module: _a', 'loading module: _bad', 'cannot unload module _a']
assert main.bad_modules == set()




if __name__ == "__main__":
Expand Down

0 comments on commit b390a1a

Please sign in to comment.