513 lines
17 KiB
Python
513 lines
17 KiB
Python
|
# -*- coding: utf-8 -*-
|
||
|
import os
|
||
|
import locale
|
||
|
import codecs
|
||
|
import sys
|
||
|
from uuid import uuid4
|
||
|
from collections import OrderedDict
|
||
|
|
||
|
import pytest
|
||
|
from pandas.compat import intern, PY3
|
||
|
import pandas.core.common as com
|
||
|
from pandas.util._move import move_into_mutable_buffer, BadMove, stolenbuf
|
||
|
from pandas.util._decorators import deprecate_kwarg, make_signature
|
||
|
from pandas.util._validators import (validate_args, validate_kwargs,
|
||
|
validate_args_and_kwargs,
|
||
|
validate_bool_kwarg)
|
||
|
|
||
|
import pandas.util.testing as tm
|
||
|
import pandas.util._test_decorators as td
|
||
|
|
||
|
|
||
|
class TestDecorators(object):
|
||
|
|
||
|
def setup_method(self, method):
|
||
|
@deprecate_kwarg('old', 'new')
|
||
|
def _f1(new=False):
|
||
|
return new
|
||
|
|
||
|
@deprecate_kwarg('old', 'new', {'yes': True, 'no': False})
|
||
|
def _f2(new=False):
|
||
|
return new
|
||
|
|
||
|
@deprecate_kwarg('old', 'new', lambda x: x + 1)
|
||
|
def _f3(new=0):
|
||
|
return new
|
||
|
|
||
|
@deprecate_kwarg('old', None)
|
||
|
def _f4(old=True, unchanged=True):
|
||
|
return old
|
||
|
|
||
|
self.f1 = _f1
|
||
|
self.f2 = _f2
|
||
|
self.f3 = _f3
|
||
|
self.f4 = _f4
|
||
|
|
||
|
def test_deprecate_kwarg(self):
|
||
|
x = 78
|
||
|
with tm.assert_produces_warning(FutureWarning):
|
||
|
result = self.f1(old=x)
|
||
|
assert result is x
|
||
|
with tm.assert_produces_warning(None):
|
||
|
self.f1(new=x)
|
||
|
|
||
|
def test_dict_deprecate_kwarg(self):
|
||
|
x = 'yes'
|
||
|
with tm.assert_produces_warning(FutureWarning):
|
||
|
result = self.f2(old=x)
|
||
|
assert result
|
||
|
|
||
|
def test_missing_deprecate_kwarg(self):
|
||
|
x = 'bogus'
|
||
|
with tm.assert_produces_warning(FutureWarning):
|
||
|
result = self.f2(old=x)
|
||
|
assert result == 'bogus'
|
||
|
|
||
|
def test_callable_deprecate_kwarg(self):
|
||
|
x = 5
|
||
|
with tm.assert_produces_warning(FutureWarning):
|
||
|
result = self.f3(old=x)
|
||
|
assert result == x + 1
|
||
|
with pytest.raises(TypeError):
|
||
|
self.f3(old='hello')
|
||
|
|
||
|
def test_bad_deprecate_kwarg(self):
|
||
|
with pytest.raises(TypeError):
|
||
|
@deprecate_kwarg('old', 'new', 0)
|
||
|
def f4(new=None):
|
||
|
pass
|
||
|
|
||
|
def test_deprecate_keyword(self):
|
||
|
x = 9
|
||
|
with tm.assert_produces_warning(FutureWarning):
|
||
|
result = self.f4(old=x)
|
||
|
assert result is x
|
||
|
with tm.assert_produces_warning(None):
|
||
|
result = self.f4(unchanged=x)
|
||
|
assert result is True
|
||
|
|
||
|
|
||
|
def test_rands():
|
||
|
r = tm.rands(10)
|
||
|
assert(len(r) == 10)
|
||
|
|
||
|
|
||
|
def test_rands_array():
|
||
|
arr = tm.rands_array(5, size=10)
|
||
|
assert(arr.shape == (10,))
|
||
|
assert(len(arr[0]) == 5)
|
||
|
|
||
|
arr = tm.rands_array(7, size=(10, 10))
|
||
|
assert(arr.shape == (10, 10))
|
||
|
assert(len(arr[1, 1]) == 7)
|
||
|
|
||
|
|
||
|
class TestValidateArgs(object):
|
||
|
fname = 'func'
|
||
|
|
||
|
def test_bad_min_fname_arg_count(self):
|
||
|
msg = "'max_fname_arg_count' must be non-negative"
|
||
|
with tm.assert_raises_regex(ValueError, msg):
|
||
|
validate_args(self.fname, (None,), -1, 'foo')
|
||
|
|
||
|
def test_bad_arg_length_max_value_single(self):
|
||
|
args = (None, None)
|
||
|
compat_args = ('foo',)
|
||
|
|
||
|
min_fname_arg_count = 0
|
||
|
max_length = len(compat_args) + min_fname_arg_count
|
||
|
actual_length = len(args) + min_fname_arg_count
|
||
|
msg = (r"{fname}\(\) takes at most {max_length} "
|
||
|
r"argument \({actual_length} given\)"
|
||
|
.format(fname=self.fname, max_length=max_length,
|
||
|
actual_length=actual_length))
|
||
|
|
||
|
with tm.assert_raises_regex(TypeError, msg):
|
||
|
validate_args(self.fname, args,
|
||
|
min_fname_arg_count,
|
||
|
compat_args)
|
||
|
|
||
|
def test_bad_arg_length_max_value_multiple(self):
|
||
|
args = (None, None)
|
||
|
compat_args = dict(foo=None)
|
||
|
|
||
|
min_fname_arg_count = 2
|
||
|
max_length = len(compat_args) + min_fname_arg_count
|
||
|
actual_length = len(args) + min_fname_arg_count
|
||
|
msg = (r"{fname}\(\) takes at most {max_length} "
|
||
|
r"arguments \({actual_length} given\)"
|
||
|
.format(fname=self.fname, max_length=max_length,
|
||
|
actual_length=actual_length))
|
||
|
|
||
|
with tm.assert_raises_regex(TypeError, msg):
|
||
|
validate_args(self.fname, args,
|
||
|
min_fname_arg_count,
|
||
|
compat_args)
|
||
|
|
||
|
def test_not_all_defaults(self):
|
||
|
bad_arg = 'foo'
|
||
|
msg = ("the '{arg}' parameter is not supported "
|
||
|
r"in the pandas implementation of {func}\(\)".
|
||
|
format(arg=bad_arg, func=self.fname))
|
||
|
|
||
|
compat_args = OrderedDict()
|
||
|
compat_args['foo'] = 2
|
||
|
compat_args['bar'] = -1
|
||
|
compat_args['baz'] = 3
|
||
|
|
||
|
arg_vals = (1, -1, 3)
|
||
|
|
||
|
for i in range(1, 3):
|
||
|
with tm.assert_raises_regex(ValueError, msg):
|
||
|
validate_args(self.fname, arg_vals[:i], 2, compat_args)
|
||
|
|
||
|
def test_validation(self):
|
||
|
# No exceptions should be thrown
|
||
|
validate_args(self.fname, (None,), 2, dict(out=None))
|
||
|
|
||
|
compat_args = OrderedDict()
|
||
|
compat_args['axis'] = 1
|
||
|
compat_args['out'] = None
|
||
|
|
||
|
validate_args(self.fname, (1, None), 2, compat_args)
|
||
|
|
||
|
|
||
|
class TestValidateKwargs(object):
|
||
|
fname = 'func'
|
||
|
|
||
|
def test_bad_kwarg(self):
|
||
|
goodarg = 'f'
|
||
|
badarg = goodarg + 'o'
|
||
|
|
||
|
compat_args = OrderedDict()
|
||
|
compat_args[goodarg] = 'foo'
|
||
|
compat_args[badarg + 'o'] = 'bar'
|
||
|
kwargs = {goodarg: 'foo', badarg: 'bar'}
|
||
|
msg = (r"{fname}\(\) got an unexpected "
|
||
|
r"keyword argument '{arg}'".format(
|
||
|
fname=self.fname, arg=badarg))
|
||
|
|
||
|
with tm.assert_raises_regex(TypeError, msg):
|
||
|
validate_kwargs(self.fname, kwargs, compat_args)
|
||
|
|
||
|
def test_not_all_none(self):
|
||
|
bad_arg = 'foo'
|
||
|
msg = (r"the '{arg}' parameter is not supported "
|
||
|
r"in the pandas implementation of {func}\(\)".
|
||
|
format(arg=bad_arg, func=self.fname))
|
||
|
|
||
|
compat_args = OrderedDict()
|
||
|
compat_args['foo'] = 1
|
||
|
compat_args['bar'] = 's'
|
||
|
compat_args['baz'] = None
|
||
|
|
||
|
kwarg_keys = ('foo', 'bar', 'baz')
|
||
|
kwarg_vals = (2, 's', None)
|
||
|
|
||
|
for i in range(1, 3):
|
||
|
kwargs = dict(zip(kwarg_keys[:i],
|
||
|
kwarg_vals[:i]))
|
||
|
|
||
|
with tm.assert_raises_regex(ValueError, msg):
|
||
|
validate_kwargs(self.fname, kwargs, compat_args)
|
||
|
|
||
|
def test_validation(self):
|
||
|
# No exceptions should be thrown
|
||
|
compat_args = OrderedDict()
|
||
|
compat_args['f'] = None
|
||
|
compat_args['b'] = 1
|
||
|
compat_args['ba'] = 's'
|
||
|
kwargs = dict(f=None, b=1)
|
||
|
validate_kwargs(self.fname, kwargs, compat_args)
|
||
|
|
||
|
def test_validate_bool_kwarg(self):
|
||
|
arg_names = ['inplace', 'copy']
|
||
|
invalid_values = [1, "True", [1, 2, 3], 5.0]
|
||
|
valid_values = [True, False, None]
|
||
|
|
||
|
for name in arg_names:
|
||
|
for value in invalid_values:
|
||
|
with tm.assert_raises_regex(ValueError,
|
||
|
"For argument \"%s\" "
|
||
|
"expected type bool, "
|
||
|
"received type %s" %
|
||
|
(name, type(value).__name__)):
|
||
|
validate_bool_kwarg(value, name)
|
||
|
|
||
|
for value in valid_values:
|
||
|
assert validate_bool_kwarg(value, name) == value
|
||
|
|
||
|
|
||
|
class TestValidateKwargsAndArgs(object):
|
||
|
fname = 'func'
|
||
|
|
||
|
def test_invalid_total_length_max_length_one(self):
|
||
|
compat_args = ('foo',)
|
||
|
kwargs = {'foo': 'FOO'}
|
||
|
args = ('FoO', 'BaZ')
|
||
|
|
||
|
min_fname_arg_count = 0
|
||
|
max_length = len(compat_args) + min_fname_arg_count
|
||
|
actual_length = len(kwargs) + len(args) + min_fname_arg_count
|
||
|
msg = (r"{fname}\(\) takes at most {max_length} "
|
||
|
r"argument \({actual_length} given\)"
|
||
|
.format(fname=self.fname, max_length=max_length,
|
||
|
actual_length=actual_length))
|
||
|
|
||
|
with tm.assert_raises_regex(TypeError, msg):
|
||
|
validate_args_and_kwargs(self.fname, args, kwargs,
|
||
|
min_fname_arg_count,
|
||
|
compat_args)
|
||
|
|
||
|
def test_invalid_total_length_max_length_multiple(self):
|
||
|
compat_args = ('foo', 'bar', 'baz')
|
||
|
kwargs = {'foo': 'FOO', 'bar': 'BAR'}
|
||
|
args = ('FoO', 'BaZ')
|
||
|
|
||
|
min_fname_arg_count = 2
|
||
|
max_length = len(compat_args) + min_fname_arg_count
|
||
|
actual_length = len(kwargs) + len(args) + min_fname_arg_count
|
||
|
msg = (r"{fname}\(\) takes at most {max_length} "
|
||
|
r"arguments \({actual_length} given\)"
|
||
|
.format(fname=self.fname, max_length=max_length,
|
||
|
actual_length=actual_length))
|
||
|
|
||
|
with tm.assert_raises_regex(TypeError, msg):
|
||
|
validate_args_and_kwargs(self.fname, args, kwargs,
|
||
|
min_fname_arg_count,
|
||
|
compat_args)
|
||
|
|
||
|
def test_no_args_with_kwargs(self):
|
||
|
bad_arg = 'bar'
|
||
|
min_fname_arg_count = 2
|
||
|
|
||
|
compat_args = OrderedDict()
|
||
|
compat_args['foo'] = -5
|
||
|
compat_args[bad_arg] = 1
|
||
|
|
||
|
msg = (r"the '{arg}' parameter is not supported "
|
||
|
r"in the pandas implementation of {func}\(\)".
|
||
|
format(arg=bad_arg, func=self.fname))
|
||
|
|
||
|
args = ()
|
||
|
kwargs = {'foo': -5, bad_arg: 2}
|
||
|
tm.assert_raises_regex(ValueError, msg,
|
||
|
validate_args_and_kwargs,
|
||
|
self.fname, args, kwargs,
|
||
|
min_fname_arg_count, compat_args)
|
||
|
|
||
|
args = (-5, 2)
|
||
|
kwargs = {}
|
||
|
tm.assert_raises_regex(ValueError, msg,
|
||
|
validate_args_and_kwargs,
|
||
|
self.fname, args, kwargs,
|
||
|
min_fname_arg_count, compat_args)
|
||
|
|
||
|
def test_duplicate_argument(self):
|
||
|
min_fname_arg_count = 2
|
||
|
compat_args = OrderedDict()
|
||
|
compat_args['foo'] = None
|
||
|
compat_args['bar'] = None
|
||
|
compat_args['baz'] = None
|
||
|
kwargs = {'foo': None, 'bar': None}
|
||
|
args = (None,) # duplicate value for 'foo'
|
||
|
|
||
|
msg = (r"{fname}\(\) got multiple values for keyword "
|
||
|
r"argument '{arg}'".format(fname=self.fname, arg='foo'))
|
||
|
|
||
|
with tm.assert_raises_regex(TypeError, msg):
|
||
|
validate_args_and_kwargs(self.fname, args, kwargs,
|
||
|
min_fname_arg_count,
|
||
|
compat_args)
|
||
|
|
||
|
def test_validation(self):
|
||
|
# No exceptions should be thrown
|
||
|
compat_args = OrderedDict()
|
||
|
compat_args['foo'] = 1
|
||
|
compat_args['bar'] = None
|
||
|
compat_args['baz'] = -2
|
||
|
kwargs = {'baz': -2}
|
||
|
args = (1, None)
|
||
|
|
||
|
min_fname_arg_count = 2
|
||
|
validate_args_and_kwargs(self.fname, args, kwargs,
|
||
|
min_fname_arg_count,
|
||
|
compat_args)
|
||
|
|
||
|
|
||
|
class TestMove(object):
|
||
|
|
||
|
def test_cannot_create_instance_of_stolenbuffer(self):
|
||
|
"""Stolen buffers need to be created through the smart constructor
|
||
|
``move_into_mutable_buffer`` which has a bunch of checks in it.
|
||
|
"""
|
||
|
msg = "cannot create 'pandas.util._move.stolenbuf' instances"
|
||
|
with tm.assert_raises_regex(TypeError, msg):
|
||
|
stolenbuf()
|
||
|
|
||
|
def test_more_than_one_ref(self):
|
||
|
"""Test case for when we try to use ``move_into_mutable_buffer`` when
|
||
|
the object being moved has other references.
|
||
|
"""
|
||
|
b = b'testing'
|
||
|
|
||
|
with pytest.raises(BadMove) as e:
|
||
|
def handle_success(type_, value, tb):
|
||
|
assert value.args[0] is b
|
||
|
return type(e).handle_success(e, type_, value, tb) # super
|
||
|
|
||
|
e.handle_success = handle_success
|
||
|
move_into_mutable_buffer(b)
|
||
|
|
||
|
def test_exactly_one_ref(self):
|
||
|
"""Test case for when the object being moved has exactly one reference.
|
||
|
"""
|
||
|
b = b'testing'
|
||
|
|
||
|
# We need to pass an expression on the stack to ensure that there are
|
||
|
# not extra references hanging around. We cannot rewrite this test as
|
||
|
# buf = b[:-3]
|
||
|
# as_stolen_buf = move_into_mutable_buffer(buf)
|
||
|
# because then we would have more than one reference to buf.
|
||
|
as_stolen_buf = move_into_mutable_buffer(b[:-3])
|
||
|
|
||
|
# materialize as bytearray to show that it is mutable
|
||
|
assert bytearray(as_stolen_buf) == b'test'
|
||
|
|
||
|
@pytest.mark.skipif(PY3, reason='bytes objects cannot be interned in py3')
|
||
|
def test_interned(self):
|
||
|
salt = uuid4().hex
|
||
|
|
||
|
def make_string():
|
||
|
# We need to actually create a new string so that it has refcount
|
||
|
# one. We use a uuid so that we know the string could not already
|
||
|
# be in the intern table.
|
||
|
return ''.join(('testing: ', salt))
|
||
|
|
||
|
# This should work, the string has one reference on the stack.
|
||
|
move_into_mutable_buffer(make_string())
|
||
|
|
||
|
refcount = [None] # nonlocal
|
||
|
|
||
|
def ref_capture(ob):
|
||
|
# Subtract two because those are the references owned by this
|
||
|
# frame:
|
||
|
# 1. The local variables of this stack frame.
|
||
|
# 2. The python data stack of this stack frame.
|
||
|
refcount[0] = sys.getrefcount(ob) - 2
|
||
|
return ob
|
||
|
|
||
|
with pytest.raises(BadMove):
|
||
|
# If we intern the string it will still have one reference but now
|
||
|
# it is in the intern table so if other people intern the same
|
||
|
# string while the mutable buffer holds the first string they will
|
||
|
# be the same instance.
|
||
|
move_into_mutable_buffer(ref_capture(intern(make_string()))) # noqa
|
||
|
|
||
|
assert refcount[0] == 1
|
||
|
|
||
|
|
||
|
def test_numpy_errstate_is_default():
|
||
|
# The defaults since numpy 1.6.0
|
||
|
expected = {'over': 'warn', 'divide': 'warn', 'invalid': 'warn',
|
||
|
'under': 'ignore'}
|
||
|
import numpy as np
|
||
|
from pandas.compat import numpy # noqa
|
||
|
# The errstate should be unchanged after that import.
|
||
|
assert np.geterr() == expected
|
||
|
|
||
|
|
||
|
@td.skip_if_windows
|
||
|
class TestLocaleUtils(object):
|
||
|
|
||
|
@classmethod
|
||
|
def setup_class(cls):
|
||
|
cls.locales = tm.get_locales()
|
||
|
cls.current_locale = locale.getlocale()
|
||
|
|
||
|
if not cls.locales:
|
||
|
pytest.skip("No locales found")
|
||
|
|
||
|
@classmethod
|
||
|
def teardown_class(cls):
|
||
|
del cls.locales
|
||
|
del cls.current_locale
|
||
|
|
||
|
def test_get_locales(self):
|
||
|
# all systems should have at least a single locale
|
||
|
assert len(tm.get_locales()) > 0
|
||
|
|
||
|
def test_get_locales_prefix(self):
|
||
|
if len(self.locales) == 1:
|
||
|
pytest.skip("Only a single locale found, no point in "
|
||
|
"trying to test filtering locale prefixes")
|
||
|
first_locale = self.locales[0]
|
||
|
assert len(tm.get_locales(prefix=first_locale[:2])) > 0
|
||
|
|
||
|
def test_set_locale(self):
|
||
|
if len(self.locales) == 1:
|
||
|
pytest.skip("Only a single locale found, no point in "
|
||
|
"trying to test setting another locale")
|
||
|
|
||
|
if com._all_none(*self.current_locale):
|
||
|
# Not sure why, but on some travis runs with pytest,
|
||
|
# getlocale() returned (None, None).
|
||
|
pytest.skip("Current locale is not set.")
|
||
|
|
||
|
locale_override = os.environ.get('LOCALE_OVERRIDE', None)
|
||
|
|
||
|
if locale_override is None:
|
||
|
lang, enc = 'it_CH', 'UTF-8'
|
||
|
elif locale_override == 'C':
|
||
|
lang, enc = 'en_US', 'ascii'
|
||
|
else:
|
||
|
lang, enc = locale_override.split('.')
|
||
|
|
||
|
enc = codecs.lookup(enc).name
|
||
|
new_locale = lang, enc
|
||
|
|
||
|
if not tm._can_set_locale(new_locale):
|
||
|
with pytest.raises(locale.Error):
|
||
|
with tm.set_locale(new_locale):
|
||
|
pass
|
||
|
else:
|
||
|
with tm.set_locale(new_locale) as normalized_locale:
|
||
|
new_lang, new_enc = normalized_locale.split('.')
|
||
|
new_enc = codecs.lookup(enc).name
|
||
|
normalized_locale = new_lang, new_enc
|
||
|
assert normalized_locale == new_locale
|
||
|
|
||
|
current_locale = locale.getlocale()
|
||
|
assert current_locale == self.current_locale
|
||
|
|
||
|
|
||
|
def test_make_signature():
|
||
|
# See GH 17608
|
||
|
# Case where the func does not have default kwargs
|
||
|
sig = make_signature(validate_kwargs)
|
||
|
assert sig == (['fname', 'kwargs', 'compat_args'],
|
||
|
['fname', 'kwargs', 'compat_args'])
|
||
|
|
||
|
# Case where the func does have default kwargs
|
||
|
sig = make_signature(deprecate_kwarg)
|
||
|
assert sig == (['old_arg_name', 'new_arg_name',
|
||
|
'mapping=None', 'stacklevel=2'],
|
||
|
['old_arg_name', 'new_arg_name', 'mapping', 'stacklevel'])
|
||
|
|
||
|
|
||
|
def test_safe_import(monkeypatch):
|
||
|
assert not td.safe_import("foo")
|
||
|
assert not td.safe_import("pandas", min_version="99.99.99")
|
||
|
|
||
|
# Create dummy module to be imported
|
||
|
import types
|
||
|
import sys
|
||
|
mod_name = "hello123"
|
||
|
mod = types.ModuleType(mod_name)
|
||
|
mod.__version__ = "1.5"
|
||
|
|
||
|
assert not td.safe_import(mod_name)
|
||
|
monkeypatch.setitem(sys.modules, mod_name, mod)
|
||
|
assert not td.safe_import(mod_name, min_version="2.0")
|
||
|
assert td.safe_import(mod_name, min_version="1.0")
|