Skip to content

Commit 6e5162b

Browse files
committed
hotfix custom interaction class error msg
1 parent 2153f71 commit 6e5162b

File tree

4 files changed

+47
-39
lines changed

4 files changed

+47
-39
lines changed

CHANGELOG.md

+9
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1111
### Removed
1212
### Fixed
1313

14+
## [0.3.3] - 2021-06-11
15+
### Changed
16+
- Custom interactions must return three values: a boolean for the interaction,
17+
and the indices of residue atoms responsible for the interaction
18+
### Fixed
19+
- Custom interactions that only returned a single value instead of three would
20+
raise an uninformative error message
21+
22+
1423
## [0.3.2] - 2021-06-11
1524
### Added
1625
- LigNetwork: an interaction diagram with atomistic details for the ligand and

docs/notebooks/how-to.ipynb

+2-21
Original file line numberDiff line numberDiff line change
@@ -222,9 +222,9 @@
222222
"\n",
223223
"This method takes exactly two positional arguments (and as many named arguments as you need): a ligand Residue or Molecule and a protein Residue or Molecule (in this order).\n",
224224
"\n",
225-
"* **Return value(s) for the `detect` method**\n",
225+
"* **Return values for the `detect` method**\n",
226226
"\n",
227-
"There are two possibilities here, depending on whether or not you want to access the indices of atoms responsible for the interaction. If you don't need this information, just return `True` if the interaction is detected, `False` otherwise. If you need to access atomic indices, you must return the following items in this order: \n",
227+
"You must return the following items in this order: \n",
228228
"\n",
229229
" * `True` or `False` for the detection of the interaction\n",
230230
" * The index of the ligand atom, or None if not detected\n",
@@ -239,25 +239,6 @@
239239
"source": [
240240
"from scipy.spatial import distance_matrix\n",
241241
"\n",
242-
"# without atom indices\n",
243-
"class CloseContact(plf.interactions.Interaction):\n",
244-
" def detect(self, res1, res2, threshold=2.0):\n",
245-
" dist_matrix = distance_matrix(res1.xyz, res2.xyz)\n",
246-
" if (dist_matrix <= threshold).any():\n",
247-
" return True\n",
248-
" return False\n",
249-
"\n",
250-
"fp = plf.Fingerprint()\n",
251-
"fp.closecontact(lmol, pmol[\"ASP129.A\"])"
252-
]
253-
},
254-
{
255-
"cell_type": "code",
256-
"execution_count": null,
257-
"metadata": {},
258-
"outputs": [],
259-
"source": [
260-
"# with atom indices\n",
261242
"class CloseContact(plf.interactions.Interaction):\n",
262243
" def detect(self, res1, res2, threshold=2.0):\n",
263244
" dist_matrix = distance_matrix(res1.xyz, res2.xyz)\n",

prolif/fingerprint.py

+14-5
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,12 @@
3030

3131
def _return_first_element(f):
3232
"""Modifies the return signature of a function by forcing it to return
33-
only the first element if multiple values were returned.
33+
only the first element when multiple values are returned
34+
35+
Raises
36+
------
37+
TypeError
38+
If the function doesn't return three values
3439
3540
Notes
3641
-----
@@ -57,10 +62,13 @@ def _return_first_element(f):
5762
def wrapper(*args, **kwargs):
5863
results = f(*args, **kwargs)
5964
try:
60-
value, *rest = results
61-
except TypeError:
62-
value = results
63-
return value
65+
bool_, lig_idx, prot_idx = results
66+
except (TypeError, ValueError):
67+
raise TypeError(
68+
"Incorrect function signature: the interaction class must "
69+
"return 3 values (boolean, int, int)"
70+
) from None
71+
return bool_
6472
return wrapper
6573

6674

@@ -218,6 +226,7 @@ def bitvector_atoms(self, res1, res2):
218226
A list containing indices for the protein atoms responsible for
219227
each interaction
220228
229+
221230
.. versionchanged:: 0.3.2
222231
Atom indices are returned as two separate lists instead of a single
223232
list of tuples

tests/test_fingerprint.py

+22-13
Original file line numberDiff line numberDiff line change
@@ -13,23 +13,32 @@
1313

1414
class Dummy(Interaction):
1515
def detect(self, res1, res2):
16-
return 1, 2, 3
16+
return True, 4, 2
1717

1818

19-
def func_return_single_val():
20-
return 0
19+
def return_value(*args):
20+
return args if len(args) > 1 else args[0]
2121

2222

2323
def test_wrapper_return():
24-
foo = Dummy().detect
25-
bar = _return_first_element(foo)
26-
assert foo("foo", "bar") == (1, 2, 3)
27-
assert bar("foo", "bar") == 1
28-
assert bar.__wrapped__("foo", "bar") == (1, 2, 3)
29-
baz = _return_first_element(func_return_single_val)
30-
assert baz() == 0
31-
assert baz.__wrapped__() == 0
32-
24+
detect = Dummy().detect
25+
mod = _return_first_element(detect)
26+
assert detect("foo", "bar") == (True, 4, 2)
27+
assert mod("foo", "bar") is True
28+
assert mod.__wrapped__("foo", "bar") == (True, 4, 2)
29+
30+
31+
@pytest.mark.parametrize("returned", [
32+
True,
33+
(True,),
34+
(True, 4)
35+
])
36+
def test_wrapper_incorrect_return(returned):
37+
mod = _return_first_element(return_value)
38+
assert mod.__wrapped__(returned) == returned
39+
with pytest.raises(TypeError,
40+
match="Incorrect function signature"):
41+
mod(returned)
3342

3443
class TestFingerprint:
3544
@pytest.fixture
@@ -60,7 +69,7 @@ def test_n_interactions(self, fp):
6069

6170
def test_wrapped(self, fp):
6271
assert fp.dummy("foo", "bar") == 1
63-
assert fp.dummy.__wrapped__("foo", "bar") == (1, 2, 3)
72+
assert fp.dummy.__wrapped__("foo", "bar") == (True, 4, 2)
6473

6574
def test_bitvector(self, fp):
6675
bv = fp.bitvector(ligand_mol, protein_mol["ASP129.A"])

0 commit comments

Comments
 (0)