Skip to content
This repository was archived by the owner on Sep 11, 2023. It is now read-only.

Commit db11507

Browse files
committed
[msm] use Ipython's ShimModule approach to warn upon access of a moved module. (#805)
* [msm] use IPython's ShimModule approach to warn upon access of a moved module. * This addresses #550. Remove those packages again in version 2.3 * remove io->dtraj alias (has already been deprecated ages ago) * fix #806
1 parent c00f8fd commit db11507

File tree

11 files changed

+348
-4
lines changed

11 files changed

+348
-4
lines changed

pyemma/msm/__init__.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -84,14 +84,21 @@
8484
msm.flux
8585
8686
"""
87-
from __future__ import absolute_import, print_function
87+
from __future__ import absolute_import as _
8888

8989
#####################################################
9090
# Low-level MSM functions (imported from msmtools)
9191
# backward compatibility to PyEMMA 1.2.x
92-
from msmtools import analysis, estimation, generation, dtraj, flux
93-
from msmtools.analysis.dense.pcca import PCCA
92+
# TODO: finally remove this stuff...
93+
import warnings as _warnings
94+
from pyemma.util.exceptions import PyEMMA_DeprecationWarning as _dep_warning
95+
with _warnings.catch_warnings():
96+
_warnings.filterwarnings('ignore', category=_dep_warning)
97+
from . import analysis, estimation, generation, dtraj, flux
9498
io = dtraj
99+
del _warnings, _dep_warning
100+
######################################################
101+
from msmtools.analysis.dense.pcca import PCCA
95102

96103
#####################################################
97104
# Estimators and models

pyemma/msm/analysis/__init__.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import sys
2+
import warnings
3+
4+
from pyemma.util._ext.shimmodule import ShimModule
5+
from pyemma.util.exceptions import PyEMMA_DeprecationWarning
6+
7+
warnings.warn("The pyemma.msm.analysis module has been deprecated. "
8+
"You should import msmtools.analysis now.", PyEMMA_DeprecationWarning)
9+
10+
sys.modules['pyemma.msm.analysis'] = ShimModule(src='pyemma.msm.analysis', mirror='msmtools.analysis')
11+
12+
from msmtools.analysis import *

pyemma/msm/dtraj/__init__.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import sys
2+
import warnings
3+
4+
from pyemma.util._ext.shimmodule import ShimModule
5+
from pyemma.util.exceptions import PyEMMA_DeprecationWarning
6+
7+
warnings.warn("The pyemma.msm.dtraj module has been deprecated. "
8+
"You should import msmtools.dtraj now.", PyEMMA_DeprecationWarning)
9+
10+
sys.modules['pyemma.msm.dtraj'] = ShimModule(src='pyemma.msm.dtraj', mirror='msmtools.dtraj')
11+
#sys.modules['pyemma.msm.io'] = sys.modules['pyemma.msm.dtraj']
12+

pyemma/msm/estimation/__init__.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import sys
2+
import warnings
3+
4+
from pyemma.util._ext.shimmodule import ShimModule
5+
from pyemma.util.exceptions import PyEMMA_DeprecationWarning
6+
7+
warnings.warn("The pyemma.msm.estimation module has been deprecated. "
8+
"You should import msmtools.estimation now.", PyEMMA_DeprecationWarning)
9+
10+
sys.modules['pyemma.msm.estimation'] = ShimModule(src='pyemma.msm.estimation', mirror='msmtools.estimation')
11+

pyemma/msm/flux/__init__.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import sys
2+
import warnings
3+
4+
from pyemma.util._ext.shimmodule import ShimModule
5+
from pyemma.util.exceptions import PyEMMA_DeprecationWarning
6+
7+
warnings.warn("The pyemma.msm.flux module has been deprecated. "
8+
"You should import msmtools.flux now.", PyEMMA_DeprecationWarning)
9+
10+
sys.modules['pyemma.msm.flux'] = ShimModule(src='pyemma.msm.flux', mirror='msmtools.flux')
11+

pyemma/msm/generation/__init__.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import sys
2+
import warnings
3+
4+
from pyemma.util._ext.shimmodule import ShimModule
5+
from pyemma.util.exceptions import PyEMMA_DeprecationWarning
6+
7+
warnings.warn("The pyemma.msm.generation module has been deprecated. "
8+
"You should import msmtools.generation now.", PyEMMA_DeprecationWarning)
9+
10+
sys.modules['pyemma.msm.generation'] = ShimModule(src='pyemma.msm.generation', mirror='msmtools.generation')
11+
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
import sys
2+
import unittest
3+
import warnings
4+
5+
import mock
6+
7+
import pyemma
8+
from pyemma.util.exceptions import PyEMMA_DeprecationWarning
9+
10+
11+
@unittest.skipIf(sys.version_info.major == 2, "disabled on py2 for nosetest stupidness")
12+
class TestShowDeprecationWarningOnLowLevelAPIUsage(unittest.TestCase):
13+
14+
@classmethod
15+
def setUpClass(cls):
16+
cls.old_filters = warnings.filters[:]
17+
if sys.version_info.major == 2:
18+
warnings.filters = []
19+
20+
@classmethod
21+
def tearDownClass(cls):
22+
warnings.filters = cls.old_filters
23+
24+
def test_analysis(self):
25+
with warnings.catch_warnings(record=True) as cm:
26+
warnings.simplefilter("always")
27+
from pyemma.msm import analysis
28+
analysis.is_transition_matrix
29+
30+
self.assertEqual(len(cm), 1)
31+
self.assertIn('analysis', cm[0].message.args[0])
32+
33+
with warnings.catch_warnings(record=True) as cm:
34+
warnings.simplefilter("always")
35+
pyemma.msm.analysis.is_transition_matrix
36+
self.assertEqual(len(cm), 1)
37+
self.assertIsInstance(cm[0].message, PyEMMA_DeprecationWarning)
38+
self.assertIn('analysis', cm[0].message.args[0])
39+
40+
@unittest.skipIf(sys.version_info.major == 2, "not on py2")
41+
def test_warn_was_called(self):
42+
shim_mod = sys.modules['pyemma.msm.analysis']
43+
with mock.patch.object(shim_mod, '_warn') as m:
44+
from pyemma.msm import analysis
45+
analysis.is_transition_matrix
46+
47+
m.assert_called_once()
48+
49+
def test_estimation(self):
50+
with warnings.catch_warnings(record=True) as cm:
51+
warnings.simplefilter("always")
52+
from pyemma.msm import estimation
53+
estimation.count_matrix
54+
55+
self.assertEqual(len(cm), 1)
56+
self.assertIsInstance(cm[0].message, PyEMMA_DeprecationWarning)
57+
58+
self.assertIn('estimation', cm[0].message.args[0])
59+
60+
with warnings.catch_warnings(record=True) as cm:
61+
warnings.simplefilter("always")
62+
pyemma.msm.estimation.count_matrix
63+
self.assertEqual(len(cm), 1)
64+
self.assertIsInstance(cm[0].message, PyEMMA_DeprecationWarning)
65+
66+
self.assertIn('estimation', cm[0].message.args[0])
67+
68+
def test_generation(self):
69+
with warnings.catch_warnings(record=True) as cm:
70+
warnings.simplefilter("always")
71+
from pyemma.msm import generation
72+
generation.generate_traj
73+
74+
self.assertEqual(len(cm), 1)
75+
self.assertIsInstance(cm[0].message, PyEMMA_DeprecationWarning)
76+
77+
self.assertIn('generation', cm[0].message.args[0])
78+
79+
with warnings.catch_warnings(record=True) as cm:
80+
warnings.simplefilter("always")
81+
pyemma.msm.generation.generate_traj
82+
self.assertEqual(len(cm), 1)
83+
self.assertIsInstance(cm[0].message, PyEMMA_DeprecationWarning)
84+
85+
self.assertIn('generation', cm[0].message.args[0])
86+
87+
def test_dtraj(self):
88+
with warnings.catch_warnings(record=True) as cm:
89+
warnings.simplefilter("always")
90+
from pyemma.msm import dtraj
91+
dtraj.load_discrete_trajectory
92+
93+
self.assertEqual(len(cm), 1)
94+
self.assertIsInstance(cm[0].message, PyEMMA_DeprecationWarning)
95+
96+
self.assertIn('dtraj', cm[0].message.args[0])
97+
98+
with warnings.catch_warnings(record=True) as cm:
99+
warnings.simplefilter("always")
100+
pyemma.msm.dtraj.load_discrete_trajectory
101+
self.assertEqual(len(cm), 1)
102+
self.assertIsInstance(cm[0].message, PyEMMA_DeprecationWarning)
103+
104+
self.assertIn('dtraj', cm[0].message.args[0])
105+
106+
def test_io(self):
107+
with warnings.catch_warnings(record=True) as cm:
108+
warnings.simplefilter("always")
109+
from pyemma.msm import io as dtraj
110+
dtraj.load_discrete_trajectory
111+
112+
self.assertEqual(len(cm), 1)
113+
self.assertIsInstance(cm[0].message, PyEMMA_DeprecationWarning)
114+
115+
self.assertIn('dtraj', cm[0].message.args[0])
116+
117+
with warnings.catch_warnings(record=True) as cm:
118+
warnings.simplefilter("always")
119+
pyemma.msm.dtraj.load_discrete_trajectory
120+
self.assertEqual(len(cm), 1)
121+
self.assertIsInstance(cm[0].message, PyEMMA_DeprecationWarning)
122+
123+
self.assertIn('dtraj', cm[0].message.args[0])
124+
125+
def test_flux(self):
126+
with warnings.catch_warnings(record=True) as cm:
127+
warnings.simplefilter("always")
128+
from pyemma.msm import flux
129+
flux.total_flux
130+
131+
self.assertEqual(len(cm), 1)
132+
self.assertIsInstance(cm[0].message, PyEMMA_DeprecationWarning)
133+
134+
self.assertIn('flux', cm[0].message.args[0])
135+
136+
with warnings.catch_warnings(record=True) as cm:
137+
warnings.simplefilter("always")
138+
pyemma.msm.flux.total_flux
139+
self.assertEqual(len(cm), 1)
140+
self.assertIsInstance(cm[0].message, PyEMMA_DeprecationWarning)
141+
142+
self.assertIn('flux', cm[0].message.args[0])

pyemma/util/_ext/__init__.py

Whitespace-only changes.

pyemma/util/_ext/shimmodule.py

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
"""A shim module for deprecated imports
2+
"""
3+
# Copyright (c) IPython Development Team.
4+
# Distributed under the terms of the Modified BSD License.
5+
6+
import sys
7+
import types
8+
import warnings
9+
10+
11+
def import_item(name):
12+
"""Import and return ``bar`` given the string ``foo.bar``.
13+
Calling ``bar = import_item("foo.bar")`` is the functional equivalent of
14+
executing the code ``from foo import bar``.
15+
Parameters
16+
----------
17+
name : string
18+
The fully qualified name of the module/package being imported.
19+
Returns
20+
-------
21+
mod : module object
22+
The module that was imported.
23+
"""
24+
25+
parts = name.rsplit('.', 1)
26+
if len(parts) == 2:
27+
# called with 'foo.bar....'
28+
package, obj = parts
29+
module = __import__(package, fromlist=[obj])
30+
try:
31+
pak = getattr(module, obj)
32+
except AttributeError:
33+
raise ImportError('No module named %s' % obj)
34+
return pak
35+
else:
36+
# called with un-dotted string
37+
return __import__(parts[0])
38+
39+
40+
class ShimImporter(object):
41+
"""Import hook for a shim.
42+
43+
This ensures that submodule imports return the real target module,
44+
not a clone that will confuse `is` and `isinstance` checks.
45+
"""
46+
47+
def __init__(self, src, mirror):
48+
self.src = src
49+
self.mirror = mirror
50+
51+
def _mirror_name(self, fullname):
52+
"""get the name of the mirrored module"""
53+
54+
return self.mirror + fullname[len(self.src):]
55+
56+
def find_module(self, fullname, path=None):
57+
"""Return self if we should be used to import the module."""
58+
if fullname.startswith(self.src + '.'):
59+
mirror_name = self._mirror_name(fullname)
60+
try:
61+
mod = import_item(mirror_name)
62+
except ImportError:
63+
return
64+
else:
65+
if not isinstance(mod, types.ModuleType):
66+
# not a module
67+
return None
68+
return self
69+
70+
def load_module(self, fullname):
71+
"""Import the mirrored module, and insert it into sys.modules"""
72+
mirror_name = self._mirror_name(fullname)
73+
mod = import_item(mirror_name)
74+
sys.modules[fullname] = mod
75+
return mod
76+
77+
78+
class ShimModule(types.ModuleType):
79+
def __init__(self, *args, **kwargs):
80+
self._mirror = kwargs.pop("mirror")
81+
src = kwargs.pop("src", None)
82+
if src:
83+
kwargs['name'] = src.rsplit('.', 1)[-1]
84+
super(ShimModule, self).__init__(*args, **kwargs)
85+
# add import hook for descendent modules
86+
if src:
87+
sys.meta_path.append(
88+
ShimImporter(src=src, mirror=self._mirror)
89+
)
90+
self.msg = kwargs.pop("msg", None)
91+
self.default_msg = "Access to a moved module '%s' detected!" \
92+
" Please use '%s' in the future." % (src, self._mirror)
93+
94+
@property
95+
def __path__(self):
96+
return []
97+
98+
@property
99+
def __spec__(self):
100+
"""Don't produce __spec__ until requested"""
101+
self._warn()
102+
return __import__(self._mirror).__spec__
103+
104+
def __dir__(self):
105+
self._warn()
106+
return dir(__import__(self._mirror))
107+
108+
@property
109+
def __all__(self):
110+
"""Ensure __all__ is always defined"""
111+
self._warn()
112+
mod = __import__(self._mirror)
113+
try:
114+
return mod.__all__
115+
except AttributeError:
116+
return [name for name in dir(mod) if not name.startswith('_')]
117+
118+
def __getattr__(self, key):
119+
# Use the equivalent of import_item(name), see below
120+
name = "%s.%s" % (self._mirror, key)
121+
try:
122+
item = import_item(name)
123+
self._warn()
124+
return item
125+
except ImportError:
126+
raise AttributeError(key)
127+
128+
def _warn(self):
129+
from pyemma.util.exceptions import PyEMMA_DeprecationWarning
130+
warnings.warn(self.msg if self.msg else self.default_msg,
131+
category=PyEMMA_DeprecationWarning)

pyemma/util/annotators.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ def foo(self):
3939
from decorator import decorator, decorate
4040
from inspect import stack
4141

42+
from pyemma.util.exceptions import PyEMMA_DeprecationWarning
43+
4244
__all__ = ['alias',
4345
'aliased',
4446
'deprecated',
@@ -213,7 +215,7 @@ def _deprecated(func, *args, **kw):
213215

214216
warnings.warn_explicit(
215217
user_msg,
216-
category=DeprecationWarning,
218+
category=PyEMMA_DeprecationWarning,
217219
filename=filename,
218220
lineno=lineno
219221
)

0 commit comments

Comments
 (0)