Skip to content

Commit c57162e

Browse files
authored
Merge pull request #455 from OpenBioSim/backport_454
2 parents 23629d1 + 54ba6c4 commit c57162e

4 files changed

Lines changed: 88 additions & 7 deletions

File tree

src/sire/mm/__init__.py

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,6 @@
3636

3737
from ..legacy import MM as _MM
3838

39-
from .. import use_new_api as _use_new_api
40-
41-
_use_new_api()
42-
4339
AngleRestraint = _MM.AngleRestraint
4440
AngleRestraints = _MM.AngleRestraints
4541

@@ -217,3 +213,18 @@ def _fix_siremm():
217213
_fix_siremm()
218214
except ImportError:
219215
pass
216+
217+
# This must be called after _fix_siremm() above, not at the top of this
218+
# module (as in most other new-API submodules). use_new_api() eagerly loads
219+
# every other lazily-loaded new-API submodule, including sire.mol - and
220+
# sire.mol's own module-level code does 'from ..mm import _fix_siremm'. If
221+
# sire.mm is the first Sire submodule touched in a process, calling
222+
# use_new_api() before _fix_siremm is defined means that reentrant load of
223+
# sire.mol fails with 'cannot import name _fix_siremm from sire.mm', which
224+
# then cascades into 'sire.mm could not be loaded' via the lazy_import
225+
# wrapper. Nothing above this point depends on use_new_api() having run
226+
# (it only pythonizes names already pulled directly from the raw legacy
227+
# _MM module), so it is safe to defer to here.
228+
from .. import use_new_api as _use_new_api
229+
230+
_use_new_api()

src/sire/restraints/_restraints.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -447,7 +447,7 @@ def boresch(
447447
b = BoreschRestraints(name, b)
448448

449449
# Set the use_pbc flag.
450-
b._use_pbc = use_pbc
450+
b.set_uses_pbc(use_pbc)
451451

452452
return b
453453

@@ -876,7 +876,6 @@ def morse_potential(
876876
for bond in changed_bonds:
877877
bond_name, length0, length1, k0, k1 = bond
878878
if k1 == 0 or k0 == 0:
879-
880879
# If the bond is being created (k0 == 0), then we should
881880
# use the parameters from the final state (length1, k1).
882881
# If the bond is being annihilated (k1 == 0), then we don't

tests/restraints/test_boresch.py

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1+
import pickle
2+
13
import pytest
24

3-
import sire as sr
45
from sire.restraints import boresch
56

67
# Valid Boresch restraint parameters.
@@ -203,6 +204,35 @@ def test_boresch_restraint_params(thrombin_complex):
203204
assert boresch_restraint.phi0()[2].value() == 1.4147
204205

205206

207+
def test_boresch_restraints_pickles_ok(thrombin_complex):
208+
"""
209+
Regression test: sire.restraints.boresch() used to set a dynamic
210+
'_use_pbc' Python attribute on the returned BoreschRestraints instead of
211+
calling set_uses_pbc(), which broke pickling (fixed).
212+
"""
213+
boresch_restraints = boresch(
214+
thrombin_complex,
215+
receptor=thrombin_complex["protein"][
216+
BORESCH_PARAMS_DEFAULT["receptor_selection"]
217+
],
218+
ligand=thrombin_complex["resname LIG"][
219+
BORESCH_PARAMS_DEFAULT["ligand_selection"]
220+
],
221+
kr=BORESCH_PARAMS_DEFAULT["kr"],
222+
ktheta=BORESCH_PARAMS_DEFAULT["ktheta"],
223+
kphi=BORESCH_PARAMS_DEFAULT["kphi"],
224+
r0=BORESCH_PARAMS_DEFAULT["r0"],
225+
theta0=BORESCH_PARAMS_DEFAULT["theta0"],
226+
phi0=BORESCH_PARAMS_DEFAULT["phi0"],
227+
name=BORESCH_PARAMS_DEFAULT["name"],
228+
)
229+
230+
data = pickle.dumps(boresch_restraints)
231+
reloaded = pickle.loads(data)
232+
233+
assert reloaded[0].kr().value() == boresch_restraints[0].kr().value()
234+
235+
206236
@pytest.mark.parametrize(
207237
(
208238
"receptor_selection",

tests/stream/test_pickle.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import pickle
2+
3+
import pytest
4+
5+
import sire as sr
6+
7+
8+
def test_dynamic_attribute_breaks_pickling():
9+
"""
10+
General, still-open Sire issue: sire_pickle_suite<T> (wrapper/Qt/
11+
qdatastream.hpp) never overrides pickle_suite::getstate_manages_dict(),
12+
so any object using it that also has a dynamic Python attribute set on
13+
it (populating __dict__) fails to pickle with 'Incomplete pickle support
14+
(__getstate_manages_dict__ not set)', even though the object's own
15+
QDataStream-based serialisation (sire.stream.save/load) is unaffected.
16+
"""
17+
from sire.mm import BoreschRestraint, BoreschRestraints
18+
19+
b = BoreschRestraint(
20+
receptor=[1574, 1554, 1576],
21+
ligand=[4, 3, 5],
22+
r0=sr.u("7.687 A"),
23+
theta0=[sr.u("1.3031 rad"), sr.u("1.4777 rad")],
24+
phi0=[sr.u("2.5569 rad"), sr.u("2.9359 rad"), sr.u("1.4147 rad")],
25+
kr=sr.u("6.2012 kcal mol-1 A-2"),
26+
ktheta=[sr.u("28.7685 kcal mol-1 rad-2"), sr.u("24.8204 kcal mol-1 rad-2")],
27+
kphi=[
28+
sr.u("59.8626 kcal mol-1 rad-2"),
29+
sr.u("0.7923 kcal mol-1 rad-2"),
30+
sr.u("55.1775 kcal mol-1 rad-2"),
31+
],
32+
)
33+
restraints = BoreschRestraints(b)
34+
35+
# Pickles fine before any dynamic attribute is set.
36+
pickle.dumps(restraints)
37+
38+
restraints._some_dynamic_attribute = True
39+
40+
with pytest.raises(RuntimeError, match="Incomplete pickle support"):
41+
pickle.dumps(restraints)

0 commit comments

Comments
 (0)