Skip to content

Commit 5ac5636

Browse files
committed
core: better support for ad-hoc configs
properly reload/unload the relevant modules so hopefully no more weird hacks should be required relevant - karlicoss/promnesia#340 - #46
1 parent fb0c128 commit 5ac5636

File tree

4 files changed

+104
-8
lines changed

4 files changed

+104
-8
lines changed

my/core/cfg.py

Lines changed: 46 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,12 +44,53 @@ def override_config(config: F) -> Iterator[F]:
4444
delattr(config, k)
4545

4646

47-
# helper for tests? not sure if could be useful elsewhere
47+
import importlib
48+
import sys
49+
from typing import Optional, Set
50+
ModuleRegex = str
4851
@contextmanager
49-
def tmp_config():
50-
import my.config as C
51-
with override_config(C):
52-
yield C # todo not sure?
52+
def _reload_modules(modules: ModuleRegex) -> Iterator[None]:
53+
def loaded_modules() -> Set[str]:
54+
return {name for name in sys.modules if re.fullmatch(modules, name)}
55+
56+
modules_before = loaded_modules()
57+
58+
for m in modules_before:
59+
importlib.reload(sys.modules[m])
60+
61+
try:
62+
yield
63+
finally:
64+
modules_after = loaded_modules()
65+
for m in modules_after:
66+
if m in modules_before:
67+
# was previously loaded, so need to reload to pick up old config
68+
importlib.reload(sys.modules[m])
69+
else:
70+
# wasn't previously loaded, so need to unload it
71+
# otherwise it might fail due to missing config etc
72+
sys.modules.pop(m, None)
73+
74+
75+
from contextlib import ExitStack
76+
import re
77+
@contextmanager
78+
def tmp_config(*, modules: Optional[ModuleRegex]=None, config=None):
79+
if modules is None:
80+
assert config is None
81+
if modules is not None:
82+
assert config is not None
83+
84+
import my.config
85+
with ExitStack() as module_reload_stack, override_config(my.config) as new_config:
86+
if config is not None:
87+
overrides = {k: v for k, v in vars(config).items() if not k.startswith('__')}
88+
for k, v in overrides.items():
89+
setattr(new_config, k, v)
90+
91+
if modules is not None:
92+
module_reload_stack.enter_context(_reload_modules(modules))
93+
yield new_config
5394

5495

5596
def test_tmp_config() -> None:

my/simple.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
'''
2+
Just a demo module for testing and documentation purposes
3+
'''
4+
from dataclasses import dataclass
5+
from typing import Iterator
6+
7+
from my.core import make_config
8+
9+
from my.config import simple as user_config
10+
11+
12+
@dataclass
13+
class simple(user_config):
14+
count: int
15+
16+
17+
config = make_config(simple)
18+
19+
20+
def items() -> Iterator[int]:
21+
yield from range(config.count)

tests/test_tmp_config.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
from pathlib import Path
2+
import tempfile
3+
4+
from my.core.cfg import tmp_config
5+
6+
import pytest
7+
8+
9+
def _init_default_config():
10+
import my.config
11+
class default_config:
12+
count = 5
13+
my.config.simple = default_config # type: ignore[attr-defined]
14+
15+
16+
def test_tmp_config() -> None:
17+
## ugh. ideally this would be on the top level (would be a better test)
18+
## but pytest imports eveything first, executes hooks, and some reset_modules() fictures mess stuff up
19+
## later would be nice to be a bit more careful about them
20+
_init_default_config()
21+
from my.simple import items
22+
##
23+
24+
assert len(list(items())) == 5
25+
26+
class config:
27+
class simple:
28+
count = 3
29+
30+
with tmp_config(modules='my.simple', config=config):
31+
assert len(list(items())) == 3
32+
33+
assert len(list(items())) == 5

tox.ini

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,10 @@ passenv =
2020
commands =
2121
pip install -e .[testing]
2222
{envpython} -m pytest \
23-
tests/core.py \
24-
tests/sqlite.py \
25-
tests/get_files.py \
23+
tests/core.py \
24+
tests/sqlite.py \
25+
tests/get_files.py \
26+
tests/test_tmp_config.py \
2627
{posargs}
2728

2829

0 commit comments

Comments
 (0)