174 lines
5.0 KiB
Python
174 lines
5.0 KiB
Python
|
from datetime import datetime
|
||
|
import sys
|
||
|
|
||
|
import numpy as np
|
||
|
import pytest
|
||
|
|
||
|
from pandas.compat import PYPY
|
||
|
|
||
|
import pandas as pd
|
||
|
from pandas import (
|
||
|
DataFrame,
|
||
|
Index,
|
||
|
Series,
|
||
|
)
|
||
|
import pandas._testing as tm
|
||
|
from pandas.core.accessor import PandasDelegate
|
||
|
from pandas.core.base import (
|
||
|
NoNewAttributesMixin,
|
||
|
PandasObject,
|
||
|
)
|
||
|
|
||
|
|
||
|
@pytest.fixture(
|
||
|
params=[
|
||
|
Series,
|
||
|
lambda x, **kwargs: DataFrame({"a": x}, **kwargs)["a"],
|
||
|
lambda x, **kwargs: DataFrame(x, **kwargs)[0],
|
||
|
Index,
|
||
|
],
|
||
|
ids=["Series", "DataFrame-dict", "DataFrame-array", "Index"],
|
||
|
)
|
||
|
def constructor(request):
|
||
|
return request.param
|
||
|
|
||
|
|
||
|
class TestPandasDelegate:
|
||
|
class Delegator:
|
||
|
_properties = ["foo"]
|
||
|
_methods = ["bar"]
|
||
|
|
||
|
def _set_foo(self, value):
|
||
|
self.foo = value
|
||
|
|
||
|
def _get_foo(self):
|
||
|
return self.foo
|
||
|
|
||
|
foo = property(_get_foo, _set_foo, doc="foo property")
|
||
|
|
||
|
def bar(self, *args, **kwargs):
|
||
|
"""a test bar method"""
|
||
|
pass
|
||
|
|
||
|
class Delegate(PandasDelegate, PandasObject):
|
||
|
def __init__(self, obj) -> None:
|
||
|
self.obj = obj
|
||
|
|
||
|
def test_invalid_delegation(self):
|
||
|
# these show that in order for the delegation to work
|
||
|
# the _delegate_* methods need to be overridden to not raise
|
||
|
# a TypeError
|
||
|
|
||
|
self.Delegate._add_delegate_accessors(
|
||
|
delegate=self.Delegator,
|
||
|
accessors=self.Delegator._properties,
|
||
|
typ="property",
|
||
|
)
|
||
|
self.Delegate._add_delegate_accessors(
|
||
|
delegate=self.Delegator, accessors=self.Delegator._methods, typ="method"
|
||
|
)
|
||
|
|
||
|
delegate = self.Delegate(self.Delegator())
|
||
|
|
||
|
msg = "You cannot access the property foo"
|
||
|
with pytest.raises(TypeError, match=msg):
|
||
|
delegate.foo
|
||
|
|
||
|
msg = "The property foo cannot be set"
|
||
|
with pytest.raises(TypeError, match=msg):
|
||
|
delegate.foo = 5
|
||
|
|
||
|
msg = "You cannot access the property foo"
|
||
|
with pytest.raises(TypeError, match=msg):
|
||
|
delegate.foo()
|
||
|
|
||
|
@pytest.mark.skipif(PYPY, reason="not relevant for PyPy")
|
||
|
def test_memory_usage(self):
|
||
|
# Delegate does not implement memory_usage.
|
||
|
# Check that we fall back to in-built `__sizeof__`
|
||
|
# GH 12924
|
||
|
delegate = self.Delegate(self.Delegator())
|
||
|
sys.getsizeof(delegate)
|
||
|
|
||
|
|
||
|
class TestNoNewAttributesMixin:
|
||
|
def test_mixin(self):
|
||
|
class T(NoNewAttributesMixin):
|
||
|
pass
|
||
|
|
||
|
t = T()
|
||
|
assert not hasattr(t, "__frozen")
|
||
|
|
||
|
t.a = "test"
|
||
|
assert t.a == "test"
|
||
|
|
||
|
t._freeze()
|
||
|
assert "__frozen" in dir(t)
|
||
|
assert getattr(t, "__frozen")
|
||
|
msg = "You cannot add any new attribute"
|
||
|
with pytest.raises(AttributeError, match=msg):
|
||
|
t.b = "test"
|
||
|
|
||
|
assert not hasattr(t, "b")
|
||
|
|
||
|
|
||
|
class TestConstruction:
|
||
|
# test certain constructor behaviours on dtype inference across Series,
|
||
|
# Index and DataFrame
|
||
|
|
||
|
@pytest.mark.parametrize(
|
||
|
"klass",
|
||
|
[
|
||
|
Series,
|
||
|
lambda x, **kwargs: DataFrame({"a": x}, **kwargs)["a"],
|
||
|
lambda x, **kwargs: DataFrame(x, **kwargs)[0],
|
||
|
Index,
|
||
|
],
|
||
|
)
|
||
|
@pytest.mark.parametrize(
|
||
|
"a",
|
||
|
[
|
||
|
np.array(["2263-01-01"], dtype="datetime64[D]"),
|
||
|
np.array([datetime(2263, 1, 1)], dtype=object),
|
||
|
np.array([np.datetime64("2263-01-01", "D")], dtype=object),
|
||
|
np.array(["2263-01-01"], dtype=object),
|
||
|
],
|
||
|
ids=[
|
||
|
"datetime64[D]",
|
||
|
"object-datetime.datetime",
|
||
|
"object-numpy-scalar",
|
||
|
"object-string",
|
||
|
],
|
||
|
)
|
||
|
def test_constructor_datetime_outofbound(self, a, klass):
|
||
|
# GH-26853 (+ bug GH-26206 out of bound non-ns unit)
|
||
|
|
||
|
# No dtype specified (dtype inference)
|
||
|
# datetime64[non-ns] raise error, other cases result in object dtype
|
||
|
# and preserve original data
|
||
|
if a.dtype.kind == "M":
|
||
|
msg = "Out of bounds"
|
||
|
with pytest.raises(pd.errors.OutOfBoundsDatetime, match=msg):
|
||
|
klass(a)
|
||
|
else:
|
||
|
result = klass(a)
|
||
|
assert result.dtype == "object"
|
||
|
tm.assert_numpy_array_equal(result.to_numpy(), a)
|
||
|
|
||
|
# Explicit dtype specified
|
||
|
# Forced conversion fails for all -> all cases raise error
|
||
|
msg = "Out of bounds|Out of bounds .* present at position 0"
|
||
|
with pytest.raises(pd.errors.OutOfBoundsDatetime, match=msg):
|
||
|
klass(a, dtype="datetime64[ns]")
|
||
|
|
||
|
def test_constructor_datetime_nonns(self, constructor):
|
||
|
arr = np.array(["2020-01-01T00:00:00.000000"], dtype="datetime64[us]")
|
||
|
expected = constructor(pd.to_datetime(["2020-01-01"]))
|
||
|
result = constructor(arr)
|
||
|
tm.assert_equal(result, expected)
|
||
|
|
||
|
# https://github.com/pandas-dev/pandas/issues/34843
|
||
|
arr.flags.writeable = False
|
||
|
result = constructor(arr)
|
||
|
tm.assert_equal(result, expected)
|