Skip to content

Commit 1e979cf

Browse files
fix: correct TypeError and comparison issues discovered in DateArray compliance tests (#79)
* fix: address failing compliance tests in DateArray and TimeArray test: add a test session with prerelease versions of dependencies * fix min/max/median for 2D arrays * fixes except for null contains * actually use NaT as 'advertised' * fix!: use `pandas.NaT` for missing values in dbdate and dbtime dtypes This makes them consistent with other date/time dtypes, as well as internally consistent with the advertised `dtype.na_value`. BREAKING-CHANGE: dbdate and dbtime dtypes return NaT instead of None for missing values Release-As: 0.4.0 * more progress towards compliance * address errors in TestMethods * fix: correct dtype and interface compliance errors in DateArray * add compliance tests to github actions * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * split coverage * add nox session back * fix unit session * move compliance tests and remove unnecessary test * no need for coverage upload * fix coverage * restore coverage * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md Co-authored-by: Owl Bot <gcf-owl-bot[bot]@users.noreply.github.com>
1 parent 9757d38 commit 1e979cf

File tree

8 files changed

+239
-8
lines changed

8 files changed

+239
-8
lines changed

.github/workflows/compliance.yml

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
on:
2+
pull_request:
3+
branches:
4+
- main
5+
name: unittest
6+
jobs:
7+
compliance:
8+
runs-on: ubuntu-latest
9+
strategy:
10+
matrix:
11+
python: ['3.10']
12+
steps:
13+
- name: Checkout
14+
uses: actions/checkout@v3
15+
- name: Setup Python
16+
uses: actions/setup-python@v3
17+
with:
18+
python-version: ${{ matrix.python }}
19+
- name: Install nox
20+
run: |
21+
python -m pip install --upgrade setuptools pip wheel
22+
python -m pip install nox
23+
- name: Run compliance tests
24+
env:
25+
COVERAGE_FILE: .coverage-compliance-${{ matrix.python }}
26+
run: |
27+
nox -s compliance

db_dtypes/core.py

+13-3
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
import numpy
1818
import pandas
1919
import pandas.api.extensions
20-
from pandas.api.types import is_dtype_equal, is_list_like, pandas_dtype
20+
from pandas.api.types import is_dtype_equal, is_list_like, is_scalar, pandas_dtype
2121

2222
from db_dtypes import pandas_backports
2323

@@ -31,9 +31,14 @@ class BaseDatetimeDtype(pandas.api.extensions.ExtensionDtype):
3131
names = None
3232

3333
@classmethod
34-
def construct_from_string(cls, name):
34+
def construct_from_string(cls, name: str):
35+
if not isinstance(name, str):
36+
raise TypeError(
37+
f"'construct_from_string' expects a string, got {type(name)}"
38+
)
39+
3540
if name != cls.name:
36-
raise TypeError()
41+
raise TypeError(f"Cannot construct a '{cls.__name__}' from 'another_type'")
3742

3843
return cls()
3944

@@ -74,6 +79,11 @@ def astype(self, dtype, copy=True):
7479
return super().astype(dtype, copy=copy)
7580

7681
def _cmp_method(self, other, op):
82+
"""Compare array values, for use in OpsMixin."""
83+
84+
if is_scalar(other) and (pandas.isna(other) or type(other) == self.dtype.type):
85+
other = type(self)([other])
86+
7787
oshape = getattr(other, "shape", None)
7888
if oshape != self.shape and oshape != (1,) and self.shape != (1,):
7989
raise TypeError(

noxfile.py

+10-3
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
nox.options.sessions = [
3838
"lint",
3939
"unit",
40+
"compliance",
4041
"cover",
4142
"lint_setup_py",
4243
"blacken",
@@ -77,7 +78,7 @@ def lint_setup_py(session):
7778
session.run("python", "setup.py", "check", "--restructuredtext", "--strict")
7879

7980

80-
def default(session):
81+
def default(session, tests_path):
8182
# Install all test dependencies, then install this package in-place.
8283

8384
constraints_path = str(
@@ -106,15 +107,21 @@ def default(session):
106107
"--cov-config=.coveragerc",
107108
"--cov-report=",
108109
"--cov-fail-under=0",
109-
os.path.join("tests", "unit"),
110+
tests_path,
110111
*session.posargs,
111112
)
112113

113114

115+
@nox.session(python=UNIT_TEST_PYTHON_VERSIONS[-1])
116+
def compliance(session):
117+
"""Run the compliance test suite."""
118+
default(session, os.path.join("tests", "compliance"))
119+
120+
114121
@nox.session(python=UNIT_TEST_PYTHON_VERSIONS)
115122
def unit(session):
116123
"""Run the unit test suite."""
117-
default(session)
124+
default(session, os.path.join("tests", "unit"))
118125

119126

120127
@nox.session(python=SYSTEM_TEST_PYTHON_VERSIONS)

owlbot.py

+28
Original file line numberDiff line numberDiff line change
@@ -64,11 +64,39 @@
6464
new_sessions = """
6565
"lint",
6666
"unit",
67+
"compliance",
6768
"cover",
6869
"""
6970

7071
s.replace(["noxfile.py"], old_sessions, new_sessions)
7172

73+
# Add compliance tests.
74+
s.replace(
75+
["noxfile.py"], r"def default\(session\):", "def default(session, tests_path):"
76+
)
77+
s.replace(["noxfile.py"], r'os.path.join\("tests", "unit"\),', "tests_path,")
78+
s.replace(
79+
["noxfile.py"],
80+
r'''
81+
@nox.session\(python=UNIT_TEST_PYTHON_VERSIONS\)
82+
def unit\(session\):
83+
"""Run the unit test suite."""
84+
default\(session\)
85+
''',
86+
'''
87+
@nox.session(python=UNIT_TEST_PYTHON_VERSIONS[-1])
88+
def compliance(session):
89+
"""Run the compliance test suite."""
90+
default(session, os.path.join("tests", "compliance"))
91+
92+
93+
@nox.session(python=UNIT_TEST_PYTHON_VERSIONS)
94+
def unit(session):
95+
"""Run the unit test suite."""
96+
default(session, os.path.join("tests", "unit"))
97+
''',
98+
)
99+
72100
# ----------------------------------------------------------------------------
73101
# Samples templates
74102
# ----------------------------------------------------------------------------

tests/compliance/conftest.py

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# Copyright 2022 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import pandas
16+
import pytest
17+
18+
19+
@pytest.fixture(params=["ffill", "bfill"])
20+
def fillna_method(request):
21+
"""
22+
Parametrized fixture giving method parameters 'ffill' and 'bfill' for
23+
Series.fillna(method=<method>) testing.
24+
25+
See:
26+
https://github.com/pandas-dev/pandas/blob/main/pandas/tests/extension/conftest.py
27+
"""
28+
return request.param
29+
30+
31+
@pytest.fixture
32+
def na_value():
33+
return pandas.NaT
34+
35+
36+
@pytest.fixture
37+
def na_cmp():
38+
"""
39+
Binary operator for comparing NA values.
40+
41+
Should return a function of two arguments that returns
42+
True if both arguments are (scalar) NA for your type.
43+
44+
See:
45+
https://github.com/pandas-dev/pandas/blob/main/pandas/tests/extension/conftest.py
46+
and
47+
https://github.com/pandas-dev/pandas/blob/main/pandas/tests/extension/test_datetime.py
48+
"""
49+
50+
def cmp(a, b):
51+
return a is pandas.NaT and a is b
52+
53+
return cmp

tests/compliance/date/conftest.py

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# Copyright 2022 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import datetime
16+
17+
import numpy
18+
import pytest
19+
20+
from db_dtypes import DateArray, DateDtype
21+
22+
23+
@pytest.fixture
24+
def data():
25+
return DateArray(
26+
numpy.arange(
27+
datetime.datetime(1900, 1, 1),
28+
datetime.datetime(2099, 12, 31),
29+
datetime.timedelta(days=731),
30+
dtype="datetime64[ns]",
31+
)
32+
)
33+
34+
35+
@pytest.fixture
36+
def data_missing():
37+
"""Length-2 array with [NA, Valid]
38+
39+
See:
40+
https://github.com/pandas-dev/pandas/blob/main/pandas/tests/extension/conftest.py
41+
"""
42+
return DateArray([None, datetime.date(2022, 1, 27)])
43+
44+
45+
@pytest.fixture
46+
def dtype():
47+
return DateDtype()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# Copyright 2022 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
"""
15+
Tests for extension interface compliance, inherited from pandas.
16+
17+
See:
18+
https://github.com/pandas-dev/pandas/blob/main/pandas/tests/extension/decimal/test_decimal.py
19+
and
20+
https://github.com/pandas-dev/pandas/blob/main/pandas/tests/extension/test_period.py
21+
"""
22+
23+
from pandas.tests.extension import base
24+
25+
26+
class TestDtype(base.BaseDtypeTests):
27+
pass
28+
29+
30+
class TestInterface(base.BaseInterfaceTests):
31+
pass
32+
33+
34+
class TestConstructors(base.BaseConstructorsTests):
35+
pass
36+
37+
38+
class TestReshaping(base.BaseReshapingTests):
39+
pass
40+
41+
42+
class TestGetitem(base.BaseGetitemTests):
43+
pass
44+
45+
46+
class TestMissing(base.BaseMissingTests):
47+
pass

tests/unit/test_date.py

+14-2
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,27 @@
1313
# limitations under the License.
1414

1515
import datetime
16+
import operator
1617

1718
import pandas
19+
import pandas.testing
1820
import pytest
1921

20-
# To register the types.
21-
import db_dtypes # noqa
22+
import db_dtypes
2223
from db_dtypes import pandas_backports
2324

2425

26+
def test_construct_from_string_with_nonstring():
27+
with pytest.raises(TypeError):
28+
db_dtypes.DateDtype.construct_from_string(object())
29+
30+
31+
def test__cmp_method_with_scalar():
32+
input_array = db_dtypes.DateArray([datetime.date(1900, 1, 1)])
33+
got = input_array._cmp_method(datetime.date(1900, 1, 1), operator.eq)
34+
assert got[0]
35+
36+
2537
@pytest.mark.parametrize(
2638
"value, expected",
2739
[

0 commit comments

Comments
 (0)