From 2295ce6d90d50001b10b24144c144b75a8035d16 Mon Sep 17 00:00:00 2001 From: Jeffrey Tratner Date: Fri, 14 Jun 2013 22:32:13 -0400 Subject: [PATCH 1/4] 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 --- pandas/util/testing.py | 87 ++++++++++++++++++++++++++++++++++++------ 1 file changed, 75 insertions(+), 12 deletions(-) diff --git a/pandas/util/testing.py b/pandas/util/testing.py index 5e1ab59305bab..91f8aefb05f58 100644 --- a/pandas/util/testing.py +++ b/pandas/util/testing.py @@ -3,6 +3,7 @@ # pylint: disable-msg=W0402 from datetime import datetime +from functools import wraps import random import string import sys @@ -12,6 +13,8 @@ from contextlib import contextmanager # contextlib is available since 2.5 from distutils.version import LooseVersion +import urllib2 +import nose from numpy.random import randn import numpy as np @@ -36,7 +39,7 @@ N = 30 K = 4 - +_RAISE_NETWORK_ERROR_DEFAULT = False def rands(n): choices = string.ascii_letters + string.digits @@ -663,18 +666,51 @@ def skip_if_no_package(*args, **kwargs): # Additional tags decorators for nose # +def optional_args(decorator): + """allows a decorator to take optional positional and keyword arguments. + Assumes that taking a single, callable, positional argument means that + it is decorating a function, i.e. something like this:: + + @my_decorator + def function(): pass + + Calls decorator with decorator(f, *args, **kwargs)""" + @wraps(decorator) + def wrapper(*args, **kwargs): + def dec(f): + return decorator(f, *args, **kwargs) + is_decorating = not kwargs and len(args) == 1 and callable(args[0]) + if is_decorating: + f = args[0] + args = [] + return dec(f) + else: + return dec + return wrapper -def network(t): +@optional_args +def network(t, raise_on_error=_RAISE_NETWORK_ERROR_DEFAULT, + error_classes=(IOError,)): """ - Label a test as requiring network connection. + Label a test as requiring network connection and skip test if it encounters a ``URLError``. In some cases it is not possible to assume network presence (e.g. Debian build hosts). + You can pass an optional ``raise_on_error`` argument to the decorator, in + which case it will always raise an error even if it's not a subclass of + ``error_classes``. + Parameters ---------- t : callable The test requiring network connectivity. + raise_on_error : bool + If True, never catches errors. + error_classes : iterable + error classes to ignore. If not in ``error_classes``, raises the error. + defaults to URLError. Be careful about changing the error classes here, + it may result in undefined behavior. Returns ------- @@ -685,19 +721,46 @@ def network(t): -------- A test can be decorated as requiring network like this:: - from pandas.util.testing import * - - @network - def test_network(self): - print 'Fetch the stars from http://' + >>> from pandas.util.testing import network + >>> import urllib2 + >>> @network + ... def test_network(): + ... urllib2.urlopen("rabbit://bonanza.com") + ... + >>> try: + ... test_network() + ... except nose.SkipTest: + ... print "SKIPPING!" + ... + SKIPPING! + + Alternatively, you can use set ``raise_on_error`` in order to get + the error to bubble up, e.g.:: + + >>> @network(raise_on_error=True) + ... def test_network(): + ... urllib2.urlopen("complaint://deadparrot.com") + ... + >>> test_network() + Traceback (most recent call last): + ... + URLError: And use ``nosetests -a '!network'`` to exclude running tests requiring - network connectivity. + network connectivity. ``_RAISE_NETWORK_ERROR_DEFAULT`` in + ``pandas/util/testing.py`` sets the default behavior (currently False). """ - t.network = True - return t - + @wraps(t) + def network_wrapper(*args, **kwargs): + if raise_on_error: + return t(*args, **kwargs) + else: + try: + return t(*args, **kwargs) + except error_classes as e: + raise nose.SkipTest("Skipping test %s" % e) + return network_wrapper class SimpleMock(object): """ From 68fc0147f5127b25ed6b7263c21980a36a126924 Mon Sep 17 00:00:00 2001 From: Jeffrey Tratner Date: Fri, 14 Jun 2013 22:34:17 -0400 Subject: [PATCH 2/4] TST: Remove explicit connectivity checks in test cases. Instead, network decorator in pandas.util.testing checks for that instead. You have to opt into failing on tests by setting `pandas.util.testing._FORCE_NETWORK_ERROR` to `True`. CLN: Move imports and test skip to top of file --- pandas/io/tests/test_fred.py | 22 ++---- pandas/io/tests/test_ga.py | 55 +++------------ pandas/io/tests/test_google.py | 124 ++++++++++----------------------- pandas/io/tests/test_yahoo.py | 28 ++------ 4 files changed, 61 insertions(+), 168 deletions(-) diff --git a/pandas/io/tests/test_fred.py b/pandas/io/tests/test_fred.py index 00a90ec3da402..c1b59f782bd09 100644 --- a/pandas/io/tests/test_fred.py +++ b/pandas/io/tests/test_fred.py @@ -26,22 +26,14 @@ def test_fred(self): start = datetime(2010, 1, 1) end = datetime(2013, 01, 27) - try: - self.assertEquals( - web.DataReader("GDP", "fred", start, end)['GDP'].tail(1), - 16004.5) + self.assertEquals( + web.DataReader("GDP", "fred", start, end)['GDP'].tail(1), + 16004.5) - self.assertRaises( - Exception, - lambda: web.DataReader("NON EXISTENT SERIES", 'fred', - start, end)) - except urllib2.URLError: - try: - urllib2.urlopen('http://google.com') - except urllib2.URLError: - raise nose.SkipTest - else: - raise + self.assertRaises( + Exception, + lambda: web.DataReader("NON EXISTENT SERIES", 'fred', + start, end)) @slow @network diff --git a/pandas/io/tests/test_ga.py b/pandas/io/tests/test_ga.py index 5fa2120090025..05f767b996462 100644 --- a/pandas/io/tests/test_ga.py +++ b/pandas/io/tests/test_ga.py @@ -1,26 +1,26 @@ +import os import unittest -import nose from datetime import datetime +import nose import pandas as pd -import pandas.core.common as com from pandas import DataFrame from pandas.util.testing import network, assert_frame_equal from numpy.testing.decorators import slow +try: + import httplib2 + from pandas.io.ga import GAnalytics, read_ga + from pandas.io.auth import AuthenticationConfigError, reset_token_store + from pandas.io import auth +except ImportError: + raise nose.SkipTest class TestGoogle(unittest.TestCase): _multiprocess_can_split_ = True def test_remove_token_store(self): - import os - try: - import pandas.io.auth as auth - from pandas.io.ga import reset_token_store - except ImportError: - raise nose.SkipTest - auth.DEFAULT_TOKEN_FILE = 'test.dat' with open(auth.DEFAULT_TOKEN_FILE, 'w') as fh: fh.write('test') @@ -31,13 +31,6 @@ def test_remove_token_store(self): @slow @network def test_getdata(self): - try: - import httplib2 - from pandas.io.ga import GAnalytics, read_ga - from pandas.io.auth import AuthenticationConfigError - except ImportError: - raise nose.SkipTest - try: end_date = datetime.now() start_date = end_date - pd.offsets.Day() * 5 @@ -76,24 +69,10 @@ def test_getdata(self): except AuthenticationConfigError: raise nose.SkipTest - except httplib2.ServerNotFoundError: - try: - h = httplib2.Http() - response, content = h.request("http://www.google.com") - raise - except httplib2.ServerNotFoundError: - raise nose.SkipTest @slow @network def test_iterator(self): - try: - import httplib2 - from pandas.io.ga import GAnalytics, read_ga - from pandas.io.auth import AuthenticationConfigError - except ImportError: - raise nose.SkipTest - try: reader = GAnalytics() @@ -129,13 +108,6 @@ def test_iterator(self): @slow @network def test_segment(self): - try: - import httplib2 - from pandas.io.ga import GAnalytics, read_ga - from pandas.io.auth import AuthenticationConfigError - except ImportError: - raise nose.SkipTest - try: end_date = datetime.now() start_date = end_date - pd.offsets.Day() * 5 @@ -186,16 +158,7 @@ def test_segment(self): except AuthenticationConfigError: raise nose.SkipTest - except httplib2.ServerNotFoundError: - try: - h = httplib2.Http() - response, content = h.request("http://www.google.com") - raise - except httplib2.ServerNotFoundError: - raise nose.SkipTest - if __name__ == '__main__': - import nose nose.runmodule(argv=[__file__, '-vvs', '-x', '--pdb', '--pdb-failure'], exit=False) diff --git a/pandas/io/tests/test_google.py b/pandas/io/tests/test_google.py index 8c16c60ac8b87..18ddf06d3c03e 100644 --- a/pandas/io/tests/test_google.py +++ b/pandas/io/tests/test_google.py @@ -13,46 +13,22 @@ class TestGoogle(unittest.TestCase): - @network + @with_connectivity_check('http://www.google.com') def test_google(self): # asserts that google is minimally working and that it throws - # an excecption when DataReader can't get a 200 response from + # an exception when DataReader can't get a 200 response from # google start = datetime(2010, 1, 1) end = datetime(2013, 01, 27) - try: - self.assertEquals( - web.DataReader("F", 'google', start, end)['Close'][-1], - 13.68) - except urllib2.URLError: - try: - urllib2.urlopen('http://www.google.com') - except urllib2.URLError: - raise nose.SkipTest - else: - raise - - @network - def test_google_non_existent(self): - # asserts that google is minimally working and that it throws - # an excecption when DataReader can't get a 200 response from - # google - start = datetime(2010, 1, 1) - end = datetime(2013, 01, 27) + self.assertEquals( + web.DataReader("F", 'google', start, end)['Close'][-1], + 13.68) - try: - self.assertRaises( - Exception, - lambda: web.DataReader("NON EXISTENT TICKER", 'google', - start, end)) - except urllib2.URLError: - try: - urllib2.urlopen('http://www.google.com') - except urllib2.URLError: - raise nose.SkipTest - else: - raise + self.assertRaises( + Exception, + lambda: web.DataReader("NON EXISTENT TICKER", 'google', + start, end)) @network @@ -60,64 +36,40 @@ def test_get_quote(self): self.assertRaises(NotImplementedError, lambda: web.get_quote_google(pd.Series(['GOOG', 'AAPL', 'GOOG']))) - @network + @with_connectivity_check('http://www.google.com') def test_get_goog_volume(self): - try: - df = web.get_data_google('GOOG') - assert df.Volume.ix['OCT-08-2010'] == 2863473 - except IOError: - try: - urllib2.urlopen('http://www.google.com') - except IOError: - raise nose.SkipTest - else: - raise + df = web.get_data_google('GOOG') + assert df.Volume.ix['OCT-08-2010'] == 2863473 - @network + @with_connectivity_check('http://www.google.com') def test_get_multi1(self): - try: - sl = ['AAPL', 'AMZN', 'GOOG'] - pan = web.get_data_google(sl, '2012') - ts = pan.Close.GOOG.index[pan.Close.AAPL > pan.Close.GOOG] - assert ts[0].dayofyear == 96 - except IOError: - try: - urllib2.urlopen('http://www.google.com') - except IOError: - raise nose.SkipTest - else: - raise + sl = ['AAPL', 'AMZN', 'GOOG'] + pan = web.get_data_google(sl, '2012') + ts = pan.Close.GOOG.index[pan.Close.AAPL > pan.Close.GOOG] + assert ts[0].dayofyear == 96 - @network + @with_connectivity_check('http://www.google.com') def test_get_multi2(self): - try: - pan = web.get_data_google(['GE', 'MSFT', 'INTC'], 'JAN-01-12', 'JAN-31-12') - expected = [19.02, 28.23, 25.39] - result = pan.Close.ix['01-18-12'][['GE', 'MSFT', 'INTC']].tolist() - assert result == expected - - # sanity checking - t= np.array(result) - assert np.issubdtype(t.dtype, np.floating) - assert t.shape == (3,) - - expected = [[ 18.99, 28.4 , 25.18], - [ 18.58, 28.31, 25.13], - [ 19.03, 28.16, 25.52], - [ 18.81, 28.82, 25.87]] - result = pan.Open.ix['Jan-15-12':'Jan-20-12'][['GE', 'MSFT', 'INTC']].values - assert (result == expected).all() - - # sanity checking - t= np.array(pan) - assert np.issubdtype(t.dtype, np.floating) - except IOError: - try: - urllib2.urlopen('http://www.google.com') - except IOError: - raise nose.SkipTest - else: - raise + pan = web.get_data_google(['GE', 'MSFT', 'INTC'], 'JAN-01-12', 'JAN-31-12') + expected = [19.02, 28.23, 25.39] + result = pan.Close.ix['01-18-12'][['GE', 'MSFT', 'INTC']].tolist() + assert result == expected + + # sanity checking + t= np.array(result) + assert np.issubdtype(t.dtype, np.floating) + assert t.shape == (3,) + + expected = [[ 18.99, 28.4 , 25.18], + [ 18.58, 28.31, 25.13], + [ 19.03, 28.16, 25.52], + [ 18.81, 28.82, 25.87]] + result = pan.Open.ix['Jan-15-12':'Jan-20-12'][['GE', 'MSFT', 'INTC']].values + assert (result == expected).all() + + # sanity checking + t= np.array(pan) + assert np.issubdtype(t.dtype, np.floating) if __name__ == '__main__': nose.runmodule(argv=[__file__, '-vvs', '-x', '--pdb', '--pdb-failure'], diff --git a/pandas/io/tests/test_yahoo.py b/pandas/io/tests/test_yahoo.py index 0e2c2022af422..3cf800c887e93 100644 --- a/pandas/io/tests/test_yahoo.py +++ b/pandas/io/tests/test_yahoo.py @@ -4,12 +4,7 @@ import pandas as pd import pandas.io.data as web -from pandas.util.testing import (network, assert_frame_equal, - assert_series_equal, - assert_almost_equal) -from numpy.testing.decorators import slow - -import urllib2 +from pandas.util.testing import network, assert_series_equal class TestYahoo(unittest.TestCase): @@ -22,23 +17,14 @@ def test_yahoo(self): start = datetime(2010, 1, 1) end = datetime(2013, 01, 27) - try: - self.assertEquals( - web.DataReader("F", 'yahoo', start, end)['Close'][-1], - 13.68) + self.assertEquals( + web.DataReader("F", 'yahoo', start, end)['Close'][-1], + 13.68) - self.assertRaises( - Exception, - lambda: web.DataReader("NON EXISTENT TICKER", 'yahoo', + self.assertRaises( + Exception, + lambda: web.DataReader("NON EXISTENT TICKER", 'yahoo', start, end)) - except urllib2.URLError: - try: - urllib2.urlopen('http://www.google.com') - except urllib2.URLError: - raise nose.SkipTest - else: - raise - @network def test_get_quote(self): From 2ea45834573e5143727055827d31d7aadb72fde9 Mon Sep 17 00:00:00 2001 From: Jeffrey Tratner Date: Mon, 17 Jun 2013 23:25:36 -0400 Subject: [PATCH 3/4] ENH: Add test decorator 'with_connectivity_check'. Allows tests to check that a url is available before bubbling up an error from a test case. TST: Change tests to use with_connectivity_check to better approximate previous behavior TST: Add check_before_test option to with_connectivity_check. CLN: PEP8 all the recent jratner code --- pandas/io/tests/test_fred.py | 4 +- pandas/io/tests/test_ga.py | 13 +---- pandas/io/tests/test_google.py | 9 +-- pandas/io/tests/test_yahoo.py | 6 +- pandas/util/testing.py | 103 ++++++++++++++++++++++++++++++++- 5 files changed, 112 insertions(+), 23 deletions(-) diff --git a/pandas/io/tests/test_fred.py b/pandas/io/tests/test_fred.py index c1b59f782bd09..cd52dca507841 100644 --- a/pandas/io/tests/test_fred.py +++ b/pandas/io/tests/test_fred.py @@ -8,7 +8,7 @@ import pandas.io.data as web from pandas.util.testing import (network, assert_frame_equal, assert_series_equal, - assert_almost_equal) + assert_almost_equal, with_connectivity_check) from numpy.testing.decorators import slow import urllib2 @@ -17,7 +17,7 @@ class TestFred(unittest.TestCase): @slow - @network + @with_connectivity_check("http://www.google.com") def test_fred(self): """ Throws an exception when DataReader can't get a 200 response from diff --git a/pandas/io/tests/test_ga.py b/pandas/io/tests/test_ga.py index 05f767b996462..d2061a6d0b57a 100644 --- a/pandas/io/tests/test_ga.py +++ b/pandas/io/tests/test_ga.py @@ -5,7 +5,7 @@ import nose import pandas as pd from pandas import DataFrame -from pandas.util.testing import network, assert_frame_equal +from pandas.util.testing import network, assert_frame_equal, with_connectivity_check from numpy.testing.decorators import slow try: @@ -71,7 +71,7 @@ def test_getdata(self): raise nose.SkipTest @slow - @network + @with_connectivity_check("http://www.google.com") def test_iterator(self): try: reader = GAnalytics() @@ -97,16 +97,9 @@ def test_iterator(self): except AuthenticationConfigError: raise nose.SkipTest - except httplib2.ServerNotFoundError: - try: - h = httplib2.Http() - response, content = h.request("http://www.google.com") - raise - except httplib2.ServerNotFoundError: - raise nose.SkipTest @slow - @network + @with_connectivity_check("http://www.google.com") def test_segment(self): try: end_date = datetime.now() diff --git a/pandas/io/tests/test_google.py b/pandas/io/tests/test_google.py index 18ddf06d3c03e..8ceda94f07a52 100644 --- a/pandas/io/tests/test_google.py +++ b/pandas/io/tests/test_google.py @@ -2,18 +2,15 @@ import nose from datetime import datetime +import numpy as np import pandas as pd import pandas.io.data as web -from pandas.util.testing import (network, assert_series_equal) -from numpy.testing.decorators import slow -import numpy as np - -import urllib2 +from pandas.util.testing import network, with_connectivity_check class TestGoogle(unittest.TestCase): - @with_connectivity_check('http://www.google.com') + @with_connectivity_check("http://www.google.com") def test_google(self): # asserts that google is minimally working and that it throws # an exception when DataReader can't get a 200 response from diff --git a/pandas/io/tests/test_yahoo.py b/pandas/io/tests/test_yahoo.py index 3cf800c887e93..712475f76f5ed 100644 --- a/pandas/io/tests/test_yahoo.py +++ b/pandas/io/tests/test_yahoo.py @@ -4,15 +4,15 @@ import pandas as pd import pandas.io.data as web -from pandas.util.testing import network, assert_series_equal +from pandas.util.testing import network, assert_series_equal, with_connectivity_check class TestYahoo(unittest.TestCase): - @network + @with_connectivity_check("http://www.google.com") def test_yahoo(self): # asserts that yahoo is minimally working and that it throws - # an excecption when DataReader can't get a 200 response from + # an exception when DataReader can't get a 200 response from # yahoo start = datetime(2010, 1, 1) end = datetime(2013, 01, 27) diff --git a/pandas/util/testing.py b/pandas/util/testing.py index 91f8aefb05f58..5a583ca3ae7d9 100644 --- a/pandas/util/testing.py +++ b/pandas/util/testing.py @@ -41,6 +41,7 @@ K = 4 _RAISE_NETWORK_ERROR_DEFAULT = False + def rands(n): choices = string.ascii_letters + string.digits return ''.join(random.choice(choices) for _ in xrange(n)) @@ -675,10 +676,12 @@ def optional_args(decorator): def function(): pass Calls decorator with decorator(f, *args, **kwargs)""" + @wraps(decorator) def wrapper(*args, **kwargs): def dec(f): return decorator(f, *args, **kwargs) + is_decorating = not kwargs and len(args) == 1 and callable(args[0]) if is_decorating: f = args[0] @@ -686,11 +689,13 @@ def dec(f): return dec(f) else: return dec + return wrapper + @optional_args def network(t, raise_on_error=_RAISE_NETWORK_ERROR_DEFAULT, - error_classes=(IOError,)): + error_classes=(IOError,)): """ Label a test as requiring network connection and skip test if it encounters a ``URLError``. @@ -751,6 +756,7 @@ def network(t, raise_on_error=_RAISE_NETWORK_ERROR_DEFAULT, ``pandas/util/testing.py`` sets the default behavior (currently False). """ t.network = True + @wraps(t) def network_wrapper(*args, **kwargs): if raise_on_error: @@ -760,8 +766,98 @@ def network_wrapper(*args, **kwargs): return t(*args, **kwargs) except error_classes as e: raise nose.SkipTest("Skipping test %s" % e) + return network_wrapper + +def can_connect(url): + """tries to connect to the given url. True if succeeds, False if IOError raised""" + try: + urllib2.urlopen(url) + except IOError: + return False + else: + return True + + +@optional_args +def with_connectivity_check(t, url="http://www.google.com", + raise_on_error=_RAISE_NETWORK_ERROR_DEFAULT, check_before_test=False, + error_classes=IOError): + """ + Label a test as requiring network connection and, if an error is + encountered, only raise if it does not find a network connection. + + In comparison to ``network``, this assumes an added contract to your test: + you must assert that, under normal conditions, your test will ONLY fail if + it does not have network connectivity. + + You can call this in 3 ways: as a standard decorator, with keyword + arguments, or with a positional argument that is the url to check. + + Parameters + ---------- + t : callable + The test requiring network connectivity. + url : path + The url to test via ``urllib2.urlopen`` to check for connectivity. + Defaults to 'http://www.google.com'. + raise_on_error : bool + If True, never catches errors. + check_before_test : bool + If True, checks connectivity before running the test case. + error_classes : tuple or Exception + error classes to ignore. If not in ``error_classes``, raises the error. + defaults to IOError. Be careful about changing the error classes here. + + NOTE: ``raise_on_error`` supercedes ``check_before_test`` + Returns + ------- + t : callable + The decorated test ``t``, with checks for connectivity errors. + + Example + ------- + + In this example, you see how it will raise the error if it can connect to + the url:: + >>> @with_connectivity_check("http://www.yahoo.com") + ... def test_something_with_yahoo(): + ... raise IOError("Failure Message") + >>> test_something_with_yahoo() + Traceback (most recent call last): + ... + IOError: Failure Message + + I you set check_before_test, it will check the url first and not run the test on failure:: + >>> @with_connectivity_check("failing://url.blaher", check_before_test=True) + ... def test_something(): + ... print("I ran!") + ... raise ValueError("Failure") + >>> test_something() + Traceback (most recent call last): + ... + SkipTest + """ + t.network = True + + @wraps(t) + def wrapper(*args, **kwargs): + if check_before_test and not raise_on_error: + if not can_connect(url): + raise nose.SkipTest + try: + return t(*args, **kwargs) + except error_classes as e: + if raise_on_error or can_connect(url): + raise + else: + raise nose.SkipTest("Skipping test due to lack of connectivity" + " and error %s" % e) + + return wrapper + + class SimpleMock(object): """ Poor man's mocking object @@ -806,11 +902,13 @@ def stdin_encoding(encoding=None): """ import sys + _stdin = sys.stdin sys.stdin = SimpleMock(sys.stdin, "encoding", encoding) yield sys.stdin = _stdin + def assertRaisesRegexp(exception, regexp, callable, *args, **kwargs): """ Port of assertRaisesRegexp from unittest in Python 2.7 - used in with statement. @@ -842,6 +940,7 @@ def assertRaisesRegexp(exception, regexp, callable, *args, **kwargs): """ import re + try: callable(*args, **kwargs) except Exception as e: @@ -855,7 +954,7 @@ def assertRaisesRegexp(exception, regexp, callable, *args, **kwargs): expected_regexp = re.compile(regexp) if not expected_regexp.search(str(e)): raise AssertionError('"%s" does not match "%s"' % - (expected_regexp.pattern, str(e))) + (expected_regexp.pattern, str(e))) else: # Apparently some exceptions don't have a __name__ attribute? Just aping unittest library here name = getattr(exception, "__name__", str(exception)) From 7ddb5860e75ccdf896a24ab1c0b6377ced4cb1d0 Mon Sep 17 00:00:00 2001 From: Jeffrey Tratner Date: Thu, 20 Jun 2013 16:08:47 -0400 Subject: [PATCH 4/4] DOC: Update docs with changes --- doc/source/release.rst | 5 +++++ doc/source/v0.11.1.txt | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/doc/source/release.rst b/doc/source/release.rst index 882826765d057..61cccaba44be7 100644 --- a/doc/source/release.rst +++ b/doc/source/release.rst @@ -91,6 +91,11 @@ pandas 0.11.1 integers or floats that are in an epoch unit of ``D, s, ms, us, ns``, thanks @mtkini (:issue:`3969`) (e.g. unix timestamps or epoch ``s``, with fracional seconds allowed) (:issue:`3540`) - DataFrame corr method (spearman) is now cythonized. + - Improved ``network`` test decorator to catch ``IOError`` (and therefore + ``URLError`` as well). Added ``with_connectivity_check`` decorator to allow + explicitly checking a website as a proxy for seeing if there is network + connectivity. Plus, new ``optional_args`` decorator factory for decorators. + (:issue:`3910`, :issue:`3914`) **API Changes** diff --git a/doc/source/v0.11.1.txt b/doc/source/v0.11.1.txt index 76c439afc452c..3202efbcef83a 100644 --- a/doc/source/v0.11.1.txt +++ b/doc/source/v0.11.1.txt @@ -386,6 +386,11 @@ Bug Fixes - ``read_html`` now correctly skips tests (:issue:`3741`) - Fixed a bug where ``DataFrame.replace`` with a compiled regular expression in the ``to_replace`` argument wasn't working (:issue:`3907`) + - Improved ``network`` test decorator to catch ``IOError`` (and therefore + ``URLError`` as well). Added ``with_connectivity_check`` decorator to allow + explicitly checking a website as a proxy for seeing if there is network + connectivity. Plus, new ``optional_args`` decorator factory for decorators. + (:issue:`3910`, :issue:`3914`) See the :ref:`full release notes ` or issue tracker