Skip to content

Commit 2295ce6

Browse files
committed
TST: Change network decorator to auto-check for network errors
TST: Enforce more strict rules about ignoring IOErrors with network tests ENH: Allow keyword arguments to network decorator ENH: skip try/except checks if raise_on_error
1 parent 3aafd6a commit 2295ce6

File tree

1 file changed

+75
-12
lines changed

1 file changed

+75
-12
lines changed

pandas/util/testing.py

Lines changed: 75 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
# pylint: disable-msg=W0402
44

55
from datetime import datetime
6+
from functools import wraps
67
import random
78
import string
89
import sys
@@ -12,6 +13,8 @@
1213
from contextlib import contextmanager # contextlib is available since 2.5
1314

1415
from distutils.version import LooseVersion
16+
import urllib2
17+
import nose
1518

1619
from numpy.random import randn
1720
import numpy as np
@@ -36,7 +39,7 @@
3639

3740
N = 30
3841
K = 4
39-
42+
_RAISE_NETWORK_ERROR_DEFAULT = False
4043

4144
def rands(n):
4245
choices = string.ascii_letters + string.digits
@@ -663,18 +666,51 @@ def skip_if_no_package(*args, **kwargs):
663666
# Additional tags decorators for nose
664667
#
665668

669+
def optional_args(decorator):
670+
"""allows a decorator to take optional positional and keyword arguments.
671+
Assumes that taking a single, callable, positional argument means that
672+
it is decorating a function, i.e. something like this::
673+
674+
@my_decorator
675+
def function(): pass
676+
677+
Calls decorator with decorator(f, *args, **kwargs)"""
678+
@wraps(decorator)
679+
def wrapper(*args, **kwargs):
680+
def dec(f):
681+
return decorator(f, *args, **kwargs)
682+
is_decorating = not kwargs and len(args) == 1 and callable(args[0])
683+
if is_decorating:
684+
f = args[0]
685+
args = []
686+
return dec(f)
687+
else:
688+
return dec
689+
return wrapper
666690

667-
def network(t):
691+
@optional_args
692+
def network(t, raise_on_error=_RAISE_NETWORK_ERROR_DEFAULT,
693+
error_classes=(IOError,)):
668694
"""
669-
Label a test as requiring network connection.
695+
Label a test as requiring network connection and skip test if it encounters a ``URLError``.
670696
671697
In some cases it is not possible to assume network presence (e.g. Debian
672698
build hosts).
673699
700+
You can pass an optional ``raise_on_error`` argument to the decorator, in
701+
which case it will always raise an error even if it's not a subclass of
702+
``error_classes``.
703+
674704
Parameters
675705
----------
676706
t : callable
677707
The test requiring network connectivity.
708+
raise_on_error : bool
709+
If True, never catches errors.
710+
error_classes : iterable
711+
error classes to ignore. If not in ``error_classes``, raises the error.
712+
defaults to URLError. Be careful about changing the error classes here,
713+
it may result in undefined behavior.
678714
679715
Returns
680716
-------
@@ -685,19 +721,46 @@ def network(t):
685721
--------
686722
A test can be decorated as requiring network like this::
687723
688-
from pandas.util.testing import *
689-
690-
@network
691-
def test_network(self):
692-
print 'Fetch the stars from http://'
724+
>>> from pandas.util.testing import network
725+
>>> import urllib2
726+
>>> @network
727+
... def test_network():
728+
... urllib2.urlopen("rabbit://bonanza.com")
729+
...
730+
>>> try:
731+
... test_network()
732+
... except nose.SkipTest:
733+
... print "SKIPPING!"
734+
...
735+
SKIPPING!
736+
737+
Alternatively, you can use set ``raise_on_error`` in order to get
738+
the error to bubble up, e.g.::
739+
740+
>>> @network(raise_on_error=True)
741+
... def test_network():
742+
... urllib2.urlopen("complaint://deadparrot.com")
743+
...
744+
>>> test_network()
745+
Traceback (most recent call last):
746+
...
747+
URLError: <urlopen error unknown url type: complaint>
693748
694749
And use ``nosetests -a '!network'`` to exclude running tests requiring
695-
network connectivity.
750+
network connectivity. ``_RAISE_NETWORK_ERROR_DEFAULT`` in
751+
``pandas/util/testing.py`` sets the default behavior (currently False).
696752
"""
697-
698753
t.network = True
699-
return t
700-
754+
@wraps(t)
755+
def network_wrapper(*args, **kwargs):
756+
if raise_on_error:
757+
return t(*args, **kwargs)
758+
else:
759+
try:
760+
return t(*args, **kwargs)
761+
except error_classes as e:
762+
raise nose.SkipTest("Skipping test %s" % e)
763+
return network_wrapper
701764

702765
class SimpleMock(object):
703766
"""

0 commit comments

Comments
 (0)