Skip to content

Commit 68fc7de

Browse files
authored
Merge pull request #62 from dwhswenson/wizard-pauses
Wizard pauses
2 parents 0226749 + 61b8da4 commit 68fc7de

File tree

8 files changed

+172
-3
lines changed

8 files changed

+172
-3
lines changed

paths_cli/tests/wizard/conftest.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,15 @@
55

66
from paths_cli.compat.openmm import HAS_OPENMM, mm, unit
77

8+
from paths_cli.wizard import pause
9+
10+
11+
@pytest.fixture(autouse=True, scope='session')
12+
def pause_style_testing():
13+
pause.set_pause_style('testing')
14+
yield
15+
16+
817
# TODO: this isn't wizard-specific, and should be moved somwhere more
918
# generally useful (like, oh, maybe openpathsampling.tests.fixtures?)
1019
@pytest.fixture

paths_cli/tests/wizard/mock_wizard.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ def input(self, content):
3737
self.log.append(content + " " + user_input)
3838
return user_input
3939

40+
def draw_hline(self):
41+
# we don't even bother for the mock console
42+
pass
43+
4044
@property
4145
def log_text(self):
4246
return "\n".join(self.log)
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import pytest
2+
import time
3+
from paths_cli.tests.wizard.mock_wizard import mock_wizard
4+
5+
from paths_cli.wizard.pause import *
6+
7+
@pytest.fixture(autouse=True, scope="module")
8+
def use_default_pause_style():
9+
with pause_style('default'):
10+
yield
11+
12+
13+
def test_get_pause_style():
14+
default_style = PAUSE_STYLES['default']
15+
assert get_pause_style() == default_style
16+
17+
18+
@pytest.mark.parametrize('input_type', ['string', 'tuple', 'PauseStyle'])
19+
def test_set_pause_style(input_type):
20+
# check that we have the default settings
21+
default_style = PAUSE_STYLES['default']
22+
test_style = PAUSE_STYLES['testing']
23+
input_val = {
24+
'string': 'testing',
25+
'tuple': tuple(test_style),
26+
'PauseStyle': PauseStyle(*test_style)
27+
}[input_type]
28+
assert input_val is not test_style # always a different object
29+
assert get_pause_style() == default_style
30+
set_pause_style(input_val)
31+
assert get_pause_style() != default_style
32+
assert get_pause_style() == test_style
33+
set_pause_style('default') # explicitly reset default
34+
35+
36+
def test_set_pause_bad_name():
37+
with pytest.raises(RuntimeError, match="Unknown pause style"):
38+
set_pause_style('foo')
39+
40+
# ensure we didn't break anything for later tests
41+
assert get_pause_style() == PAUSE_STYLES['default']
42+
43+
44+
def test_pause_style_context():
45+
assert get_pause_style() == PAUSE_STYLES['default']
46+
with pause_style('testing'):
47+
assert get_pause_style() == PAUSE_STYLES['testing']
48+
assert get_pause_style() == PAUSE_STYLES['default']
49+
50+
51+
def _run_pause_test(func):
52+
test_style = PAUSE_STYLES['testing']
53+
expected = getattr(test_style, func.__name__)
54+
wiz = mock_wizard([])
55+
with pause_style(test_style):
56+
start = time.time()
57+
func(wiz)
58+
duration = time.time() - start
59+
assert expected <= duration < 1.1 * duration
60+
return wiz
61+
62+
63+
def test_short():
64+
_ = _run_pause_test(short)
65+
66+
67+
def test_long():
68+
_ = _run_pause_test(long)
69+
70+
71+
def test_section():
72+
wiz = _run_pause_test(section)

paths_cli/tests/wizard/test_volumes.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,13 @@
99
volume_ask
1010
)
1111

12-
1312
import openpathsampling as paths
1413
from openpathsampling.experimental.storage.collective_variables import \
1514
CoordinateFunctionCV
1615

1716
from openpathsampling.tests.test_helpers import make_1d_traj
1817

18+
1919
def _wrap(x, period_min, period_max):
2020
# used in testing periodic CVs
2121
while x >= period_max:

paths_cli/wizard/pause.py

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import time
2+
import contextlib
3+
from collections import namedtuple
4+
5+
PauseStyle = namedtuple("PauseStyle", ['short', 'long', 'section'])
6+
7+
PAUSE_STYLES = {
8+
'testing': PauseStyle(0.01, 0.03, 0.05),
9+
'default': PauseStyle(0.1, 0.5, 0.75),
10+
'nopause': PauseStyle(0.0, 0.0, 0.0),
11+
}
12+
13+
_PAUSE_STYLE = PAUSE_STYLES['default']
14+
15+
16+
@contextlib.contextmanager
17+
def pause_style(style):
18+
"""Context manager for pause styles.
19+
20+
Parameters
21+
----------
22+
style : :class:`.PauseStyle`
23+
pause style to use within the context
24+
"""
25+
old_style = get_pause_style()
26+
try:
27+
set_pause_style(style)
28+
yield
29+
finally:
30+
set_pause_style(old_style)
31+
32+
33+
def get_pause_style():
34+
"""Get the current pause style"""
35+
return _PAUSE_STYLE
36+
37+
38+
def set_pause_style(style):
39+
"""Set the pause style
40+
41+
Parameters
42+
----------
43+
pause_style : :class:`.PauseStyle` or str
44+
pause style to use, can be a string if the style is registered in
45+
pause.PAUSE_STYLES
46+
"""
47+
global _PAUSE_STYLE
48+
if isinstance(style, str):
49+
try:
50+
_PAUSE_STYLE = PAUSE_STYLES[style]
51+
except KeyError as exc:
52+
raise RuntimeError(f"Unknown pause style: '{style}'") from exc
53+
else:
54+
_PAUSE_STYLE = style
55+
56+
57+
def section(wizard):
58+
"""Section break (pause and possible visual cue).
59+
"""
60+
time.sleep(_PAUSE_STYLE.section)
61+
wizard.console.draw_hline()
62+
63+
64+
def long(wizard):
65+
"""Long pause from the wizard"""
66+
time.sleep(_PAUSE_STYLE.long)
67+
68+
69+
def short(wizard):
70+
"""Short pause from the wizard"""
71+
time.sleep(_PAUSE_STYLE.short)

paths_cli/wizard/two_state_tps.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
SINGLE_ENGINE_STEP, CVS_STEP, WizardStep
55
)
66
from paths_cli.wizard.wizard import Wizard
7+
from paths_cli.wizard import pause
78

89
volumes = get_category_wizard('volume')
910
from paths_cli.wizard.volumes import _FIRST_STATE, _VOL_DESC
@@ -18,6 +19,7 @@ def two_state_tps(wizard, fixed_length=False):
1819
]
1920
initial_state = volumes(wizard, context={'intro': intro})
2021
wizard.register(initial_state, 'initial state', 'states')
22+
pause.section(wizard)
2123
intro = [
2224
"Next let's define your final state.",
2325
_VOL_DESC

paths_cli/wizard/volumes.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,21 @@
88
from paths_cli.wizard.helper import EvalHelperFunc, Helper
99
from paths_cli.wizard.core import interpret_req
1010
import paths_cli.compiling.volumes
11+
from paths_cli.wizard import pause
1112

1213

1314
def _binary_func_volume(wizard, context, op):
14-
wizard.say("Let's make the first constituent volume:")
15+
wizard.say("Let's make the first constituent volume.")
16+
pause.long(wizard)
1517
new_context = volume_set_context(wizard, context, selected=None)
1618
new_context['part'] = 1
1719
vol1 = VOLUMES_PLUGIN(wizard, new_context)
18-
wizard.say("Let's make the second constituent volume:")
20+
pause.section(wizard)
21+
wizard.say("Let's make the second constituent volume.")
22+
pause.long(wizard)
1923
new_context['part'] = 2
2024
vol2 = VOLUMES_PLUGIN(wizard, new_context)
25+
pause.section(wizard)
2126
wizard.say("Now we'll combine those two constituent volumes...")
2227
vol = op(vol1, vol2)
2328
return vol

paths_cli/wizard/wizard.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
from paths_cli.wizard.helper import Helper, QuitWizard
1313
from paths_cli.compiling.tools import custom_eval
1414

15+
from paths_cli.wizard import pause
16+
1517
import shutil
1618

1719
class Console: # no-cov
@@ -26,6 +28,9 @@ def input(self, content):
2628
def width(self):
2729
return shutil.get_terminal_size((80, 24)).columns
2830

31+
def draw_hline(self):
32+
self.print('═' * self.width)
33+
2934
class Wizard:
3035
def __init__(self, steps):
3136
self.steps = steps
@@ -304,6 +309,7 @@ def _do_one(self, step, req):
304309
self.say("Okay, let's try that again.")
305310
return True
306311
self.register(obj, step.display_name, step.store_name)
312+
pause.section(self)
307313
requires_another, allows_another = self._req_do_another(req)
308314
if requires_another:
309315
do_another = True

0 commit comments

Comments
 (0)