Skip to content

Commit 539c8b0

Browse files
authored
Merge pull request #66 from dwhswenson/pylint-wizard
pylint/flake8: `paths_cli.wizard`
2 parents 59bc1b9 + 3fb1571 commit 539c8b0

20 files changed

+368
-110
lines changed

paths_cli/tests/wizard/test_load_from_ops.py

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from paths_cli.tests.wizard.mock_wizard import mock_wizard
77

88
from paths_cli.wizard.load_from_ops import (
9-
named_objs_helper, _get_ops_storage, _get_ops_object, load_from_ops
9+
_get_ops_storage, _get_ops_object, load_from_ops
1010
)
1111

1212
# for some reason I couldn't get these to work with MagicMock
@@ -44,12 +44,6 @@ def ops_file_fixture():
4444
storage = FakeStorage(foo)
4545
return storage
4646

47-
def test_named_objs_helper(ops_file_fixture):
48-
helper_func = named_objs_helper(ops_file_fixture, 'foo')
49-
result = helper_func('any')
50-
assert "what I found" in result
51-
assert "bar" in result
52-
assert "baz" in result
5347

5448
@pytest.mark.parametrize('with_failure', [False, True])
5549
def test_get_ops_storage(tmpdir, with_failure):

paths_cli/wizard/core.py

Lines changed: 50 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
1-
import random
2-
from paths_cli.wizard.tools import a_an
3-
4-
from collections import namedtuple
1+
def interpret_req(req):
2+
"""Create user-facing string representation of the input requirement.
53
6-
WIZARD_STORE_NAMES = ['engines', 'cvs', 'states', 'networks', 'schemes']
7-
WizardSay = namedtuple("WizardSay", ['msg', 'mode'])
4+
Parameters
5+
----------
6+
req : Tuple[..., int, int]
7+
req[1] is the minimum number of objects to create; req[2] is the
8+
maximum number of objects to create
89
9-
def interpret_req(req):
10+
Returns
11+
-------
12+
str :
13+
human-reading string for how many objects to create
14+
"""
1015
_, min_, max_ = req
1116
string = ""
1217
if min_ == max_:
@@ -23,7 +28,32 @@ def interpret_req(req):
2328
return string
2429

2530

31+
# TODO: REFACTOR: It looks like get_missing_object may be redundant with
32+
# other code for obtaining prerequisites for a function
2633
def get_missing_object(wizard, obj_dict, display_name, fallback_func):
34+
"""Get a prerequisite object.
35+
36+
The ``obj_dict`` here is typically a mapping of objects known by the
37+
Wizard. If it is empty, the ``fallback_func`` is used to create a new
38+
object. If it has exactly 1 entry, that is used implicitly. If it has
39+
more than 1 entry, the user must select which one to use.
40+
41+
Parameters
42+
----------
43+
wizard : :class:`.Wizard`
44+
the wizard for user interaction
45+
obj_dict : Dict[str, Any]
46+
mapping of object name to object
47+
display_name: str
48+
the user-facing name of this type of object
49+
fallback_func: Callable[:class:`.Wizard`] -> Any
50+
method to create a new object of this type
51+
52+
Returns
53+
-------
54+
Any :
55+
the prerequisite object
56+
"""
2757
if len(obj_dict) == 0:
2858
obj = fallback_func(wizard)
2959
elif len(obj_dict) == 1:
@@ -37,10 +67,22 @@ def get_missing_object(wizard, obj_dict, display_name, fallback_func):
3767

3868

3969
def get_object(func):
70+
"""Decorator to wrap methods for obtaining objects from user input.
71+
72+
This decorator implements the user interaction loop when dealing with a
73+
single user input. The wrapped function is intended to create some
74+
object. If the user's input cannot create a valid object, the wrapped
75+
function should return None.
76+
77+
Parameters
78+
----------
79+
func : Callable
80+
object creation method to wrap; should return None on failure
81+
"""
82+
# TODO: use functools.wraps?
4083
def inner(*args, **kwargs):
4184
obj = None
4285
while obj is None:
4386
obj = func(*args, **kwargs)
4487
return obj
4588
return inner
46-

paths_cli/wizard/cvs.py

Lines changed: 50 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
1+
from functools import partial
2+
from collections import namedtuple
3+
import numpy as np
4+
15
from paths_cli.compiling.tools import mdtraj_parse_atomlist
26
from paths_cli.wizard.plugin_classes import (
37
LoadFromOPS, WizardObjectPlugin, WrapCategory
48
)
59
from paths_cli.wizard.core import get_object
610
import paths_cli.wizard.engines
711

8-
from functools import partial
9-
from collections import namedtuple
10-
import numpy as np
11-
1212
from paths_cli.wizard.parameters import (
1313
FromWizardPrerequisite
1414
)
@@ -27,6 +27,10 @@
2727
"You should specify atom indices enclosed in double brackets, e.g. "
2828
"[{list_range_natoms}]"
2929
)
30+
_MDTrajParams = namedtuple("_MDTrajParams", ['period', 'n_atoms',
31+
'kwarg_name', 'cv_user_str'])
32+
_MDTRAJ_INTRO = "We'll make a CV that measures the {user_str}."
33+
3034

3135
# TODO: implement so the following can be the help string:
3236
# _ATOM_INDICES_HELP_STR = (
@@ -56,6 +60,25 @@
5660

5761
@get_object
5862
def _get_atom_indices(wizard, topology, n_atoms, cv_user_str):
63+
"""Parameter loader for atom_indices parameters in MDTraj.
64+
65+
Parameters
66+
----------
67+
wizard : :class:`.Wizard`
68+
wizard for user interaction
69+
topology :
70+
topology (reserved for future use)
71+
n_atoms : int
72+
number of atoms to define this CV (i.e., 2 for a distance; 3 for an
73+
angle; 4 for a dihedral)
74+
cv_user_str : str
75+
user-facing name for the CV being created
76+
77+
Returns
78+
-------
79+
:class`np.ndarray` :
80+
array of indices for the MDTraj function
81+
"""
5982
helper = Helper(_ATOM_INDICES_HELP_STR.format(
6083
list_range_natoms=list(range(n_atoms))
6184
))
@@ -67,14 +90,14 @@ def _get_atom_indices(wizard, topology, n_atoms, cv_user_str):
6790
except Exception as e:
6891
wizard.exception(f"Sorry, I didn't understand '{atoms_str}'.", e)
6992
helper("?")
70-
return
93+
return None
7194

7295
return arr
7396

74-
_MDTrajParams = namedtuple("_MDTrajParams", ['period', 'n_atoms',
75-
'kwarg_name', 'cv_user_str'])
7697

7798
def _mdtraj_cv_builder(wizard, prereqs, func_name):
99+
"""General function to handle building MDTraj CVs.
100+
"""
78101
from openpathsampling.experimental.storage.collective_variables import \
79102
MDTrajFunctionCV
80103
dct = TOPOLOGY_CV_PREREQ(wizard)
@@ -108,9 +131,9 @@ def _mdtraj_cv_builder(wizard, prereqs, func_name):
108131
return MDTrajFunctionCV(func, topology, period_min=period_min,
109132
period_max=period_max, **kwargs)
110133

111-
_MDTRAJ_INTRO = "We'll make a CV that measures the {user_str}."
112134

113135
def _mdtraj_summary(wizard, context, result):
136+
"""Standard summary of MDTraj CVs: function, atom, topology"""
114137
cv = result
115138
func = cv.func
116139
topology = cv.topology
@@ -121,6 +144,7 @@ def _mdtraj_summary(wizard, context, result):
121144
f" Topology: {repr(topology.mdtraj)}")
122145
return [summary]
123146

147+
124148
if HAS_MDTRAJ:
125149
MDTRAJ_DISTANCE = WizardObjectPlugin(
126150
name='Distance',
@@ -152,10 +176,24 @@ def _mdtraj_summary(wizard, context, result):
152176
"four atoms"),
153177
summary=_mdtraj_summary,
154178
)
155-
156179
# TODO: add RMSD -- need to figure out how to select a frame
157180

181+
158182
def coordinate(wizard, prereqs=None):
183+
"""Builder for coordinate CV.
184+
185+
Parameters
186+
----------
187+
wizard : :class:`.Wizard`
188+
wizard for user interaction
189+
prereqs :
190+
prerequisites (unused in this method)
191+
192+
Return
193+
------
194+
CoordinateFunctionCV :
195+
the OpenPathSampling CV for this selecting this coordinate
196+
"""
159197
# TODO: atom_index should be from wizard.ask_custom_eval
160198
from openpathsampling.experimental.storage.collective_variables import \
161199
CoordinateFunctionCV
@@ -174,12 +212,13 @@ def coordinate(wizard, prereqs=None):
174212
f"atom {atom_index}?")
175213
try:
176214
coord = {'x': 0, 'y': 1, 'z': 2}[xyz]
177-
except KeyError as e:
215+
except KeyError:
178216
wizard.bad_input("Please select one of 'x', 'y', or 'z'")
179217

180218
cv = CoordinateFunctionCV(lambda snap: snap.xyz[atom_index][coord])
181219
return cv
182220

221+
183222
COORDINATE_CV = WizardObjectPlugin(
184223
name="Coordinate",
185224
category="cv",
@@ -202,6 +241,7 @@ def coordinate(wizard, prereqs=None):
202241
"you can also create your own and load it from a file.")
203242
)
204243

244+
205245
if __name__ == "__main__": # no-cov
206246
from paths_cli.wizard.run_module import run_category
207247
run_category('cv')

paths_cli/wizard/engines.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
import paths_cli.wizard.openmm as openmm
21
from paths_cli.wizard.plugin_classes import LoadFromOPS, WrapCategory
3-
from functools import partial
42

53
_ENGINE_HELP = "An engine describes how you'll do the actual dynamics."
64
ENGINE_PLUGIN = WrapCategory(
@@ -17,4 +15,3 @@
1715
if __name__ == "__main__": # no-cov
1816
from paths_cli.wizard.run_module import run_category
1917
run_category('engine')
20-

paths_cli/wizard/errors.py

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,42 @@
11
class ImpossibleError(Exception):
2+
"""Error to throw for sections that should be unreachable code"""
23
def __init__(self, msg=None):
34
if msg is None:
45
msg = "Something went really wrong. You should never see this."
56
super().__init__(msg)
67

8+
79
class RestartObjectException(BaseException):
10+
"""Exception to indicate the restart of an object.
11+
12+
Raised when the user issues a command to cause an object restart.
13+
"""
814
pass
915

16+
1017
def not_installed(wizard, package, obj_type):
18+
"""Behavior when an integration is not installed.
19+
20+
In actual practice, this calling code should ensure this doesn't get
21+
used. However, we keep it around as a defensive practice.
22+
23+
Parameters
24+
----------
25+
package : str
26+
name of the missing package
27+
obj_type : str
28+
name of the object category that would have been created
29+
"""
1130
retry = wizard.ask(f"Hey, it looks like you don't have {package} "
1231
"installed. Do you want to try a different "
1332
f"{obj_type}, or do you want to quit?",
14-
options=["[R]etry", "[Q]uit"])
33+
options=["[r]etry", "[q]uit"])
1534
if retry == 'r':
1635
raise RestartObjectException()
17-
elif retry == 'q':
36+
if retry == 'q':
37+
# TODO: maybe raise QuitWizard instead?
1838
exit()
19-
else: # no-cov
20-
raise ImpossibleError()
39+
raise ImpossibleError() # -no-cov-
2140

2241

2342
FILE_LOADING_ERROR_MSG = ("Sorry, something went wrong when loading that "

paths_cli/wizard/helper.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,31 @@
1+
import sys
12
from .errors import RestartObjectException
23

34

45
class QuitWizard(BaseException):
5-
pass
6+
"""Exception raised when user expresses desire to quit the wizard"""
67

78

89
# the following command functions take cmd and ctx -- future commands might
910
# use the full command text or the context internally.
1011

1112
def raise_quit(cmd, ctx):
13+
"""Command function to quit the wizard (with option to save).
14+
"""
1215
raise QuitWizard()
1316

1417

1518
def raise_restart(cmd, ctx):
19+
"""Command function to restart the current object.
20+
"""
1621
raise RestartObjectException()
1722

1823

1924
def force_exit(cmd, ctx):
25+
"""Command function to force immediate exit.
26+
"""
2027
print("Exiting...")
21-
exit()
28+
sys.exit()
2229

2330

2431
HELPER_COMMANDS = {

paths_cli/wizard/joke.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,31 +16,38 @@
1616
"It would also be a good name for a death metal band.",
1717
]
1818

19+
1920
def _joke1(name, obj_type): # no-cov
2021
return (f"I probably would have named it something like "
2122
f"'{random.choice(_NAMES)}'.")
2223

24+
2325
def _joke2(name, obj_type): # no-cov
2426
thing = random.choice(_THINGS)
2527
joke = (f"I had {a_an(thing)} {thing} named '{name}' "
2628
f"when I was young.")
2729
return joke
2830

31+
2932
def _joke3(name, obj_type): # no-cov
3033
return (f"I wanted to name my {random.choice(_SPAWN)} '{name}', but my "
3134
f"wife wouldn't let me.")
3235

36+
3337
def _joke4(name, obj_type): # no-cov
3438
a_an_thing = a_an(obj_type) + f" {obj_type}"
3539
return random.choice(_MISC).format(name=name, obj_type=obj_type,
3640
a_an_thing=a_an_thing)
3741

42+
3843
def name_joke(name, obj_type): # no-cov
44+
"""Make a joke about the naming process."""
3945
jokes = [_joke1, _joke2, _joke3, _joke4]
4046
weights = [5, 5, 3, 7]
4147
joke = random.choices(jokes, weights=weights)[0]
4248
return joke(name, obj_type)
4349

50+
4451
if __name__ == "__main__": # no-cov
4552
for _ in range(5):
4653
print()

0 commit comments

Comments
 (0)