Skip to content

Commit 21c6f25

Browse files
authored
Merge pull request #69 from bashtage/update-latest-numpy
UPD: Update to reflect upstream changes in NumPy
2 parents f5615f1 + 3ee4c51 commit 21c6f25

File tree

7 files changed

+369
-54
lines changed

7 files changed

+369
-54
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
[![PyPI version](https://badge.fury.io/py/randomstate.svg)](https://badge.fury.io/py/randomstate)
66

77
This is a library and generic interface for alternative random
8-
generators in Python and Numpy.
8+
generators in Python and NumPy.
99

1010
## Features
1111

@@ -125,7 +125,7 @@ need to be smoothed.
125125
Building requires:
126126

127127
* Python (2.7, 3.4, 3.5)
128-
* Numpy (1.9, 1.10, 1.11)
128+
* NumPy (1.9, 1.10, 1.11)
129129
* Cython (0.22, 0.23, 0.24)
130130
* tempita (0.5+), if not provided by Cython
131131

README.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ randomstate
44
|Travis Build Status| |Appveyor Build Status|
55

66
This is a library and generic interface for alternative random
7-
generators in Python and Numpy.
7+
generators in Python and NumPy.
88

99
Features
1010
--------
@@ -135,7 +135,7 @@ Requirements
135135
Building requires:
136136

137137
- Python (2.7, 3.4, 3.5)
138-
- Numpy (1.9, 1.10, 1.11)
138+
- NumPy (1.9, 1.10, 1.11)
139139
- Cython (0.22, 0.23, 0.24)
140140
- tempita (0.5+), if not provided by Cython
141141

licenses/NUMPY-LICENSE.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
*************
2-
Numpy License
2+
NumPy License
33
*************
44

55
Copyright (c) 2005, NumPy Developers

randomstate/compat.py

Lines changed: 311 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,311 @@
1+
"""
2+
Shim for NumPy's suppress_warnings
3+
"""
4+
5+
6+
try:
7+
from numpy.testing import suppress_warnings
8+
except ImportError:
9+
10+
# The following two classes are copied from python 2.6 warnings module (context
11+
# manager)
12+
class WarningMessage(object):
13+
14+
"""
15+
Holds the result of a single showwarning() call.
16+
Deprecated in 1.8.0
17+
Notes
18+
-----
19+
`WarningMessage` is copied from the Python 2.6 warnings module,
20+
so it can be used in NumPy with older Python versions.
21+
"""
22+
23+
_WARNING_DETAILS = ("message", "category", "filename", "lineno", "file",
24+
"line")
25+
26+
def __init__(self, message, category, filename, lineno, file=None,
27+
line=None):
28+
local_values = locals()
29+
for attr in self._WARNING_DETAILS:
30+
setattr(self, attr, local_values[attr])
31+
if category:
32+
self._category_name = category.__name__
33+
else:
34+
self._category_name = None
35+
36+
def __str__(self):
37+
return ("{message : %r, category : %r, filename : %r, lineno : %s, "
38+
"line : %r}" % (self.message, self._category_name,
39+
self.filename, self.lineno, self.line))
40+
41+
import re
42+
import warnings
43+
from functools import wraps
44+
45+
class suppress_warnings(object):
46+
"""
47+
Context manager and decorator doing much the same as
48+
``warnings.catch_warnings``.
49+
However, it also provides a filter mechanism to work around
50+
http://bugs.python.org/issue4180.
51+
This bug causes Python before 3.4 to not reliably show warnings again
52+
after they have been ignored once (even within catch_warnings). It
53+
means that no "ignore" filter can be used easily, since following
54+
tests might need to see the warning. Additionally it allows easier
55+
specificity for testing warnings and can be nested.
56+
Parameters
57+
----------
58+
forwarding_rule : str, optional
59+
One of "always", "once", "module", or "location". Analogous to
60+
the usual warnings module filter mode, it is useful to reduce
61+
noise mostly on the outmost level. Unsuppressed and unrecorded
62+
warnings will be forwarded based on this rule. Defaults to "always".
63+
"location" is equivalent to the warnings "default", match by exact
64+
location the warning warning originated from.
65+
Notes
66+
-----
67+
Filters added inside the context manager will be discarded again
68+
when leaving it. Upon entering all filters defined outside a
69+
context will be applied automatically.
70+
When a recording filter is added, matching warnings are stored in the
71+
``log`` attribute as well as in the list returned by ``record``.
72+
If filters are added and the ``module`` keyword is given, the
73+
warning registry of this module will additionally be cleared when
74+
applying it, entering the context, or exiting it. This could cause
75+
warnings to appear a second time after leaving the context if they
76+
were configured to be printed once (default) and were already
77+
printed before the context was entered.
78+
Nesting this context manager will work as expected when the
79+
forwarding rule is "always" (default). Unfiltered and unrecorded
80+
warnings will be passed out and be matched by the outer level.
81+
On the outmost level they will be printed (or caught by another
82+
warnings context). The forwarding rule argument can modify this
83+
behaviour.
84+
Like ``catch_warnings`` this context manager is not threadsafe.
85+
Examples
86+
--------
87+
>>> with suppress_warnings() as sup:
88+
... sup.filter(DeprecationWarning, "Some text")
89+
... sup.filter(module=np.ma.core)
90+
... log = sup.record(FutureWarning, "Does this occur?")
91+
... command_giving_warnings()
92+
... # The FutureWarning was given once, the filtered warnings were
93+
... # ignored. All other warnings abide outside settings (may be
94+
... # printed/error)
95+
... assert_(len(log) == 1)
96+
... assert_(len(sup.log) == 1) # also stored in log attribute
97+
Or as a decorator:
98+
>>> sup = suppress_warnings()
99+
>>> sup.filter(module=np.ma.core) # module must match exact
100+
>>> @sup
101+
>>> def some_function():
102+
... # do something which causes a warning in np.ma.core
103+
... pass
104+
"""
105+
def __init__(self, forwarding_rule="always"):
106+
self._entered = False
107+
108+
# Suppressions are either instance or defined inside one with block:
109+
self._suppressions = []
110+
111+
if forwarding_rule not in {"always", "module", "once", "location"}:
112+
raise ValueError("unsupported forwarding rule.")
113+
self._forwarding_rule = forwarding_rule
114+
115+
def _clear_registries(self):
116+
if hasattr(warnings, "_filters_mutated"):
117+
# clearing the registry should not be necessary on new pythons,
118+
# instead the filters should be mutated.
119+
warnings._filters_mutated()
120+
return
121+
# Simply clear the registry, this should normally be harmless,
122+
# note that on new pythons it would be invalidated anyway.
123+
for module in self._tmp_modules:
124+
if hasattr(module, "__warningregistry__"):
125+
module.__warningregistry__.clear()
126+
127+
def _filter(self, category=Warning, message="", module=None, record=False):
128+
if record:
129+
record = [] # The log where to store warnings
130+
else:
131+
record = None
132+
if self._entered:
133+
if module is None:
134+
warnings.filterwarnings(
135+
"always", category=category, message=message)
136+
else:
137+
module_regex = module.__name__.replace('.', '\.') + '$'
138+
warnings.filterwarnings(
139+
"always", category=category, message=message,
140+
module=module_regex)
141+
self._tmp_modules.add(module)
142+
self._clear_registries()
143+
144+
self._tmp_suppressions.append(
145+
(category, message, re.compile(message, re.I), module, record))
146+
else:
147+
self._suppressions.append(
148+
(category, message, re.compile(message, re.I), module, record))
149+
150+
return record
151+
152+
def filter(self, category=Warning, message="", module=None):
153+
"""
154+
Add a new suppressing filter or apply it if the state is entered.
155+
Parameters
156+
----------
157+
category : class, optional
158+
Warning class to filter
159+
message : string, optional
160+
Regular expression matching the warning message.
161+
module : module, optional
162+
Module to filter for. Note that the module (and its file)
163+
must match exactly and cannot be a submodule. This may make
164+
it unreliable for external modules.
165+
Notes
166+
-----
167+
When added within a context, filters are only added inside
168+
the context and will be forgotten when the context is exited.
169+
"""
170+
self._filter(category=category, message=message, module=module,
171+
record=False)
172+
173+
def record(self, category=Warning, message="", module=None):
174+
"""
175+
Append a new recording filter or apply it if the state is entered.
176+
All warnings matching will be appended to the ``log`` attribute.
177+
Parameters
178+
----------
179+
category : class, optional
180+
Warning class to filter
181+
message : string, optional
182+
Regular expression matching the warning message.
183+
module : module, optional
184+
Module to filter for. Note that the module (and its file)
185+
must match exactly and cannot be a submodule. This may make
186+
it unreliable for external modules.
187+
Returns
188+
-------
189+
log : list
190+
A list which will be filled with all matched warnings.
191+
Notes
192+
-----
193+
When added within a context, filters are only added inside
194+
the context and will be forgotten when the context is exited.
195+
"""
196+
return self._filter(category=category, message=message, module=module,
197+
record=True)
198+
199+
def __enter__(self):
200+
if self._entered:
201+
raise RuntimeError("cannot enter suppress_warnings twice.")
202+
203+
self._orig_show = warnings.showwarning
204+
if hasattr(warnings, "_showwarnmsg"):
205+
self._orig_showmsg = warnings._showwarnmsg
206+
self._filters = warnings.filters
207+
warnings.filters = self._filters[:]
208+
209+
self._entered = True
210+
self._tmp_suppressions = []
211+
self._tmp_modules = set()
212+
self._forwarded = set()
213+
214+
self.log = [] # reset global log (no need to keep same list)
215+
216+
for cat, mess, _, mod, log in self._suppressions:
217+
if log is not None:
218+
del log[:] # clear the log
219+
if mod is None:
220+
warnings.filterwarnings(
221+
"always", category=cat, message=mess)
222+
else:
223+
module_regex = mod.__name__.replace('.', '\.') + '$'
224+
warnings.filterwarnings(
225+
"always", category=cat, message=mess,
226+
module=module_regex)
227+
self._tmp_modules.add(mod)
228+
warnings.showwarning = self._showwarning
229+
if hasattr(warnings, "_showwarnmsg"):
230+
warnings._showwarnmsg = self._showwarnmsg
231+
self._clear_registries()
232+
233+
return self
234+
235+
def __exit__(self, *exc_info):
236+
warnings.showwarning = self._orig_show
237+
if hasattr(warnings, "_showwarnmsg"):
238+
warnings._showwarnmsg = self._orig_showmsg
239+
warnings.filters = self._filters
240+
self._clear_registries()
241+
self._entered = False
242+
del self._orig_show
243+
del self._filters
244+
245+
def _showwarnmsg(self, msg):
246+
self._showwarning(msg.message, msg.category, msg.filename, msg.lineno,
247+
msg.file, msg.line, use_warnmsg=msg)
248+
249+
def _showwarning(self, message, category, filename, lineno,
250+
*args, **kwargs):
251+
use_warnmsg = kwargs.pop("use_warnmsg", None)
252+
for cat, _, pattern, mod, rec in (
253+
self._suppressions + self._tmp_suppressions)[::-1]:
254+
if (issubclass(category, cat) and
255+
pattern.match(message.args[0]) is not None):
256+
if mod is None:
257+
# Message and category match, either recorded or ignored
258+
if rec is not None:
259+
msg = WarningMessage(message, category, filename,
260+
lineno, **kwargs)
261+
self.log.append(msg)
262+
rec.append(msg)
263+
return
264+
# Use startswith, because warnings strips the c or o from
265+
# .pyc/.pyo files.
266+
elif mod.__file__.startswith(filename):
267+
# The message and module (filename) match
268+
if rec is not None:
269+
msg = WarningMessage(message, category, filename,
270+
lineno, **kwargs)
271+
self.log.append(msg)
272+
rec.append(msg)
273+
return
274+
275+
# There is no filter in place, so pass to the outside handler
276+
# unless we should only pass it once
277+
if self._forwarding_rule == "always":
278+
if use_warnmsg is None:
279+
self._orig_show(message, category, filename, lineno,
280+
*args, **kwargs)
281+
else:
282+
self._orig_showmsg(use_warnmsg)
283+
return
284+
285+
if self._forwarding_rule == "once":
286+
signature = (message.args, category)
287+
elif self._forwarding_rule == "module":
288+
signature = (message.args, category, filename)
289+
elif self._forwarding_rule == "location":
290+
signature = (message.args, category, filename, lineno)
291+
292+
if signature in self._forwarded:
293+
return
294+
self._forwarded.add(signature)
295+
if use_warnmsg is None:
296+
self._orig_show(message, category, filename, lineno, *args,
297+
**kwargs)
298+
else:
299+
self._orig_showmsg(use_warnmsg)
300+
301+
def __call__(self, func):
302+
"""
303+
Function decorator to apply certain suppressions to a whole
304+
function.
305+
"""
306+
@wraps(func)
307+
def new_func(*args, **kwargs):
308+
with self:
309+
return func(*args, **kwargs)
310+
311+
return new_func

0 commit comments

Comments
 (0)