From b48ee6dc792154a1bf3f67a12e01708edffb1a8f Mon Sep 17 00:00:00 2001 From: Tim Swast Date: Thu, 27 Jan 2022 11:10:57 -0600 Subject: [PATCH 01/18] fix: address failing compliance tests in DateArray and TimeArray test: add a test session with prerelease versions of dependencies --- tests/unit/test_date_compliance.py | 51 ++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 tests/unit/test_date_compliance.py diff --git a/tests/unit/test_date_compliance.py b/tests/unit/test_date_compliance.py new file mode 100644 index 0000000..92080d7 --- /dev/null +++ b/tests/unit/test_date_compliance.py @@ -0,0 +1,51 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +Tests for extension interface compliance, inherited from pandas. + +See: +https://github.com/pandas-dev/pandas/blob/main/pandas/tests/extension/test_period.py +""" + +import datetime + +import numpy +from pandas.tests.extension import base +import pytest + +from db_dtypes import DateArray + +# NDArrayBacked2DTests suite added in https://github.com/pandas-dev/pandas/pull/44974 +pytest.importorskip("pandas", minversion="1.5.0dev") + + +@pytest.fixture +def data(): + return DateArray( + numpy.arange( + datetime.datetime(1900, 1, 1), + datetime.datetime(2099, 12, 31), + datetime.timedelta(days=13), + dtype="datetime64[ns]", + ) + ) + + +@pytest.fixture +def data_missing(): + return DateArray([None, datetime.date(2022, 1, 27)]) + + +class Test2DCompat(base.NDArrayBacked2DTests): + pass From 90e1573a54c05fa56cdeabbecbaa7a5b55d98ab5 Mon Sep 17 00:00:00 2001 From: Tim Swast Date: Thu, 27 Jan 2022 17:13:00 -0600 Subject: [PATCH 02/18] fix min/max/median for 2D arrays --- db_dtypes/core.py | 44 +++++++++++++++++++++++------------------ tests/unit/test_date.py | 14 +++++++++++++ 2 files changed, 39 insertions(+), 19 deletions(-) diff --git a/db_dtypes/core.py b/db_dtypes/core.py index 05daf37..3dccb56 100644 --- a/db_dtypes/core.py +++ b/db_dtypes/core.py @@ -135,29 +135,35 @@ def min(self, *, axis: Optional[int] = None, skipna: bool = True, **kwargs): result = pandas_backports.nanmin( values=self._ndarray, axis=axis, mask=self.isna(), skipna=skipna ) - return self._box_func(result) + if axis is None or self.ndim == 1: + return self._box_func(result) + return self._from_backing_data(result) def max(self, *, axis: Optional[int] = None, skipna: bool = True, **kwargs): pandas_backports.numpy_validate_max((), kwargs) result = pandas_backports.nanmax( values=self._ndarray, axis=axis, mask=self.isna(), skipna=skipna ) - return self._box_func(result) - - if pandas_release >= (1, 2): - - def median( - self, - *, - axis: Optional[int] = None, - out=None, - overwrite_input: bool = False, - keepdims: bool = False, - skipna: bool = True, - ): - pandas_backports.numpy_validate_median( - (), - {"out": out, "overwrite_input": overwrite_input, "keepdims": keepdims}, - ) - result = pandas_backports.nanmedian(self._ndarray, axis=axis, skipna=skipna) + if axis is None or self.ndim == 1: + return self._box_func(result) + return self._from_backing_data(result) + + def median( + self, + *, + axis: Optional[int] = None, + out=None, + overwrite_input: bool = False, + keepdims: bool = False, + skipna: bool = True, + ): + if not hasattr(pandas_backports, "numpy_validate_median"): + raise NotImplementedError("Need pandas 1.3 or later to calculate median.") + + pandas_backports.numpy_validate_median( + (), {"out": out, "overwrite_input": overwrite_input, "keepdims": keepdims}, + ) + result = pandas_backports.nanmedian(self._ndarray, axis=axis, skipna=skipna) + if axis is None or self.ndim == 1: return self._box_func(result) + return self._from_backing_data(result) diff --git a/tests/unit/test_date.py b/tests/unit/test_date.py index b906f24..3250eec 100644 --- a/tests/unit/test_date.py +++ b/tests/unit/test_date.py @@ -65,3 +65,17 @@ def test_date_parsing(value, expected): def test_date_parsing_errors(value, error): with pytest.raises(ValueError, match=error): pandas.Series([value], dtype="dbdate") + + +# TODO: skip if median not available +@pytest.mark.parametrize( + "values, expected", + [ + (["1970-01-01", "1900-01-01", "2000-01-01"], datetime.date(1970, 1, 1)), + ([None, "1900-01-01", None], datetime.date(1900, 1, 1)), + (["2222-02-01", "2222-02-03"], datetime.date(2222, 2, 2)), + ], +) +def test_date_median(values, expected): + series = pandas.Series(values, dtype="dbdate") + assert series.median() == expected From 47100f5fd567a95e1687115bad5b10f9b386d314 Mon Sep 17 00:00:00 2001 From: Tim Swast Date: Tue, 1 Feb 2022 16:46:20 -0600 Subject: [PATCH 03/18] fixes except for null contains --- db_dtypes/__init__.py | 2 +- db_dtypes/core.py | 18 ++++--- tests/unit/date_compliance/conftest.py | 48 +++++++++++++++++++ .../date_compliance/test_date_compliance.py | 39 +++++++++++++++ .../test_date_compliance_1_5.py} | 24 +--------- tests/unit/test_date.py | 6 ++- 6 files changed, 107 insertions(+), 30 deletions(-) create mode 100644 tests/unit/date_compliance/conftest.py create mode 100644 tests/unit/date_compliance/test_date_compliance.py rename tests/unit/{test_date_compliance.py => date_compliance/test_date_compliance_1_5.py} (70%) diff --git a/db_dtypes/__init__.py b/db_dtypes/__init__.py index a518a0b..d98d5f1 100644 --- a/db_dtypes/__init__.py +++ b/db_dtypes/__init__.py @@ -145,7 +145,7 @@ def _datetime( raise TypeError("Invalid value type", scalar) def _box_func(self, x): - if pandas.isnull(x): + if pandas.isna(x): return None try: diff --git a/db_dtypes/core.py b/db_dtypes/core.py index 3dccb56..e766af9 100644 --- a/db_dtypes/core.py +++ b/db_dtypes/core.py @@ -16,9 +16,8 @@ import numpy import pandas -from pandas import NaT import pandas.api.extensions -from pandas.api.types import is_dtype_equal, is_list_like, pandas_dtype +from pandas.api.types import is_dtype_equal, is_list_like, is_scalar, pandas_dtype from db_dtypes import pandas_backports @@ -27,14 +26,18 @@ class BaseDatetimeDtype(pandas.api.extensions.ExtensionDtype): - na_value = NaT - kind = "o" + na_value = pandas.NaT + kind = "O" names = None @classmethod - def construct_from_string(cls, name): + def construct_from_string(cls, name: str): + if not isinstance(name, str): + raise TypeError( + f"'construct_from_string' expects a string, got {type(name)}" + ) if name != cls.name: - raise TypeError() + raise TypeError(f"Cannot construct a '{cls.__name__}' from 'another_type'") return cls() @@ -75,6 +78,9 @@ def astype(self, dtype, copy=True): return super().astype(dtype, copy=copy) def _cmp_method(self, other, op): + if is_scalar(other) and (pandas.isna(other) or type(other) == self.dtype.type): + other = type(self)([other]) + oshape = getattr(other, "shape", None) if oshape != self.shape and oshape != (1,) and self.shape != (1,): raise TypeError( diff --git a/tests/unit/date_compliance/conftest.py b/tests/unit/date_compliance/conftest.py new file mode 100644 index 0000000..f8d2666 --- /dev/null +++ b/tests/unit/date_compliance/conftest.py @@ -0,0 +1,48 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import datetime + +import numpy +import pandas +import pytest + +from db_dtypes import DateArray, DateDtype + + +@pytest.fixture +def data(): + return DateArray( + numpy.arange( + datetime.datetime(1900, 1, 1), + datetime.datetime(2099, 12, 31), + datetime.timedelta(days=731), + dtype="datetime64[ns]", + ) + ) + + +@pytest.fixture +def data_missing(): + return DateArray([None, datetime.date(2022, 1, 27)]) + + +@pytest.fixture +def dtype(): + return DateDtype() + + +@pytest.fixture +def na_value(): + return pandas.NaT diff --git a/tests/unit/date_compliance/test_date_compliance.py b/tests/unit/date_compliance/test_date_compliance.py new file mode 100644 index 0000000..ca47fed --- /dev/null +++ b/tests/unit/date_compliance/test_date_compliance.py @@ -0,0 +1,39 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +Tests for extension interface compliance, inherited from pandas. + +See: +https://github.com/pandas-dev/pandas/blob/main/pandas/tests/extension/decimal/test_decimal.py +and +https://github.com/pandas-dev/pandas/blob/main/pandas/tests/extension/test_period.py +""" + +from pandas.tests.extension import base + + +class TestDtype(base.BaseDtypeTests): + pass + + +class TestInterface(base.BaseInterfaceTests): + pass + + +class TestConstructors(base.BaseConstructorsTests): + pass + + +class TestReshaping(base.BaseReshapingTests): + pass diff --git a/tests/unit/test_date_compliance.py b/tests/unit/date_compliance/test_date_compliance_1_5.py similarity index 70% rename from tests/unit/test_date_compliance.py rename to tests/unit/date_compliance/test_date_compliance_1_5.py index 92080d7..e8f2c93 100644 --- a/tests/unit/test_date_compliance.py +++ b/tests/unit/date_compliance/test_date_compliance_1_5.py @@ -15,37 +15,17 @@ Tests for extension interface compliance, inherited from pandas. See: +https://github.com/pandas-dev/pandas/blob/main/pandas/tests/extension/decimal/test_decimal.py +and https://github.com/pandas-dev/pandas/blob/main/pandas/tests/extension/test_period.py """ -import datetime - -import numpy from pandas.tests.extension import base import pytest -from db_dtypes import DateArray - # NDArrayBacked2DTests suite added in https://github.com/pandas-dev/pandas/pull/44974 pytest.importorskip("pandas", minversion="1.5.0dev") -@pytest.fixture -def data(): - return DateArray( - numpy.arange( - datetime.datetime(1900, 1, 1), - datetime.datetime(2099, 12, 31), - datetime.timedelta(days=13), - dtype="datetime64[ns]", - ) - ) - - -@pytest.fixture -def data_missing(): - return DateArray([None, datetime.date(2022, 1, 27)]) - - class Test2DCompat(base.NDArrayBacked2DTests): pass diff --git a/tests/unit/test_date.py b/tests/unit/test_date.py index 3250eec..79c705c 100644 --- a/tests/unit/test_date.py +++ b/tests/unit/test_date.py @@ -19,6 +19,7 @@ # To register the types. import db_dtypes # noqa +from db_dtypes import pandas_backports @pytest.mark.parametrize( @@ -67,7 +68,10 @@ def test_date_parsing_errors(value, error): pandas.Series([value], dtype="dbdate") -# TODO: skip if median not available +@pytest.mark.skipif( + not hasattr(pandas_backports, "numpy_validate_median"), + reason="median not available with this version of pandas", +) @pytest.mark.parametrize( "values, expected", [ From cece518d279e298125889c1fc4490095129136ea Mon Sep 17 00:00:00 2001 From: Tim Swast Date: Wed, 2 Feb 2022 11:15:45 -0600 Subject: [PATCH 04/18] actually use NaT as 'advertised' --- db_dtypes/__init__.py | 4 ++-- tests/unit/test_date.py | 5 ++++- tests/unit/test_dtypes.py | 2 +- tests/unit/test_time.py | 21 +++++++++++++++++++++ 4 files changed, 28 insertions(+), 4 deletions(-) diff --git a/db_dtypes/__init__.py b/db_dtypes/__init__.py index d98d5f1..1a250b0 100644 --- a/db_dtypes/__init__.py +++ b/db_dtypes/__init__.py @@ -146,7 +146,7 @@ def _datetime( def _box_func(self, x): if pandas.isna(x): - return None + return pandas.NaT try: return x.astype(" Date: Thu, 27 Jan 2022 11:10:57 -0600 Subject: [PATCH 05/18] 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 --- db_dtypes/__init__.py | 6 +-- db_dtypes/core.py | 5 +- tests/unit/test_date.py | 27 ++++++++++ tests/unit/test_dtypes.py | 104 +++++++++++++++++++++----------------- tests/unit/test_time.py | 30 +++++++++++ 5 files changed, 119 insertions(+), 53 deletions(-) diff --git a/db_dtypes/__init__.py b/db_dtypes/__init__.py index a518a0b..1a250b0 100644 --- a/db_dtypes/__init__.py +++ b/db_dtypes/__init__.py @@ -145,8 +145,8 @@ def _datetime( raise TypeError("Invalid value type", scalar) def _box_func(self, x): - if pandas.isnull(x): - return None + if pandas.isna(x): + return pandas.NaT try: return x.astype("= (1, 2): - assert empty.median() is None + assert empty.median() is pd.NaT empty = cls([None]) - assert empty.min() is None - assert empty.max() is None - assert empty.min(skipna=False) is None - assert empty.max(skipna=False) is None + assert empty.min() is pd.NaT + assert empty.max() is pd.NaT + assert empty.min(skipna=False) is pd.NaT + assert empty.max(skipna=False) is pd.NaT if pandas_release >= (1, 2): with pytest.warns(RuntimeWarning, match="empty slice"): # It's weird that we get the warning here, and not # below. :/ - assert empty.median() is None - assert empty.median(skipna=False) is None + assert empty.median() is pd.NaT + assert empty.median(skipna=False) is pd.NaT a = _make_one(dtype) assert a.min() == sample_values[0] @@ -563,14 +573,14 @@ def test_date_add(): times = _cls("dbtime")(SAMPLE_VALUES["dbtime"]) expect = dates.astype("datetime64") + times.astype("timedelta64") - assert np.array_equal(dates + times, expect) - assert np.array_equal(times + dates, expect) + np.testing.assert_array_equal(dates + times, expect) + np.testing.assert_array_equal(times + dates, expect) do = pd.DateOffset(days=1) expect = dates.astype("object") + do - assert np.array_equal(dates + do, expect) + np.testing.assert_array_equal(dates + do, expect) if pandas_release >= (1, 1): - assert np.array_equal(do + dates, expect) + np.testing.assert_array_equal(do + dates, expect) with pytest.raises(TypeError): dates + times.astype("timedelta64") @@ -587,8 +597,8 @@ def test_date_add(): do = pd.Series([pd.DateOffset(days=i) for i in range(4)]) expect = dates.astype("object") + do - assert np.array_equal(dates + do, expect) - assert np.array_equal(do + dates, expect) + np.testing.assert_array_equal(dates + do, expect) + np.testing.assert_array_equal(do + dates, expect) def test_date_sub(): @@ -602,11 +612,11 @@ def test_date_sub(): ) ) expect = dates.astype("datetime64") - dates2.astype("datetime64") - assert np.array_equal(dates - dates2, expect) + np.testing.assert_array_equal(dates - dates2, expect) do = pd.DateOffset(days=1) expect = dates.astype("object") - do - assert np.array_equal(dates - do, expect) + np.testing.assert_array_equal(dates - do, expect) with pytest.raises(TypeError): dates - 42 @@ -620,4 +630,4 @@ def test_date_sub(): do = pd.Series([pd.DateOffset(days=i) for i in range(4)]) expect = dates.astype("object") - do - assert np.array_equal(dates - do, expect) + np.testing.assert_array_equal(dates - do, expect) diff --git a/tests/unit/test_time.py b/tests/unit/test_time.py index ba45949..8ecb996 100644 --- a/tests/unit/test_time.py +++ b/tests/unit/test_time.py @@ -19,6 +19,7 @@ # To register the types. import db_dtypes # noqa +from db_dtypes import pandas_backports @pytest.mark.parametrize( @@ -82,3 +83,32 @@ def test_time_parsing(value, expected): def test_time_parsing_errors(value, error): with pytest.raises(ValueError, match=error): pandas.Series([value], dtype="dbtime") + + +@pytest.mark.skipif( + not hasattr(pandas_backports, "numpy_validate_median"), + reason="median not available with this version of pandas", +) +@pytest.mark.parametrize( + "values, expected", + [ + ( + ["00:00:00", "12:34:56.789101", "23:59:59.999999"], + datetime.time(12, 34, 56, 789101), + ), + ( + [ + None, + "06:30:00", + pandas.NA if hasattr(pandas, "NA") else None, + pandas.NaT, + float("nan"), + ], + datetime.time(6, 30), + ), + (["2:22:21.222222", "2:22:23.222222"], datetime.time(2, 22, 22, 222222)), + ], +) +def test_date_median(values, expected): + series = pandas.Series(values, dtype="dbtime") + assert series.median() == expected From cc713a8a6fade4c2249c35a084b13fdb32ca4bea Mon Sep 17 00:00:00 2001 From: Tim Swast Date: Tue, 8 Mar 2022 16:17:02 -0600 Subject: [PATCH 06/18] more progress towards compliance --- tests/unit/date_compliance/conftest.py | 57 +++++++++++++++++++ .../date_compliance/test_date_compliance.py | 26 +++++++++ .../test_date_compliance_1_5.py | 4 ++ 3 files changed, 87 insertions(+) diff --git a/tests/unit/date_compliance/conftest.py b/tests/unit/date_compliance/conftest.py index f8d2666..15b1476 100644 --- a/tests/unit/date_compliance/conftest.py +++ b/tests/unit/date_compliance/conftest.py @@ -21,6 +21,43 @@ from db_dtypes import DateArray, DateDtype +_all_numeric_reductions = [ + "sum", + "max", + "min", + "mean", + "prod", + "std", + "var", + "median", + "kurt", + "skew", +] + + +@pytest.fixture(params=_all_numeric_reductions) +def all_numeric_reductions(request): + """ + Fixture for numeric reduction names. + + See: https://github.com/pandas-dev/pandas/blob/main/pandas/conftest.py + """ + return request.param + + +_all_boolean_reductions = ["all", "any"] + + +@pytest.fixture(params=_all_boolean_reductions) +def all_boolean_reductions(request): + """ + Fixture for boolean reduction names. + + See: https://github.com/pandas-dev/pandas/blob/main/pandas/conftest.py + """ + return request.param + + @pytest.fixture def data(): return DateArray( @@ -43,6 +80,26 @@ def dtype(): return DateDtype() +@pytest.fixture(params=["ffill", "bfill"]) +def fillna_method(request): + """ + Parametrized fixture giving method parameters 'ffill' and 'bfill' for + Series.fillna(method=) testing. + + See: + https://github.com/pandas-dev/pandas/blob/main/pandas/tests/extension/conftest.py + """ + return request.param + + @pytest.fixture def na_value(): return pandas.NaT + + +@pytest.fixture +def na_cmp(): + def cmp(a, b): + return a is pandas.NaT and a is b + + return cmp diff --git a/tests/unit/date_compliance/test_date_compliance.py b/tests/unit/date_compliance/test_date_compliance.py index ca47fed..670fc41 100644 --- a/tests/unit/date_compliance/test_date_compliance.py +++ b/tests/unit/date_compliance/test_date_compliance.py @@ -20,8 +20,12 @@ https://github.com/pandas-dev/pandas/blob/main/pandas/tests/extension/test_period.py """ +import datetime + from pandas.tests.extension import base +import db_dtypes + class TestDtype(base.BaseDtypeTests): pass @@ -37,3 +41,25 @@ class TestConstructors(base.BaseConstructorsTests): class TestReshaping(base.BaseReshapingTests): pass + + +class TestGetitem(base.BaseGetitemTests): + def test_take_na_value_other_date(self): + arr = db_dtypes.DateArray( + [datetime.date(2022, 3, 8), datetime.date(2022, 3, 9)] + ) + result = arr.take( + [0, -1], allow_fill=True, fill_value=datetime.date(1969, 12, 31) + ) + expected = db_dtypes.DateArray( + [datetime.date(2022, 3, 8), datetime.date(1969, 12, 31)] + ) + self.assert_extension_array_equal(result, expected) + + +class TestMissing(base.BaseMissingTests): + pass + + +class TestMethods(base.BaseMethodsTests): + pass diff --git a/tests/unit/date_compliance/test_date_compliance_1_5.py b/tests/unit/date_compliance/test_date_compliance_1_5.py index e8f2c93..9c6da24 100644 --- a/tests/unit/date_compliance/test_date_compliance_1_5.py +++ b/tests/unit/date_compliance/test_date_compliance_1_5.py @@ -29,3 +29,7 @@ class Test2DCompat(base.NDArrayBacked2DTests): pass + + +class TestIndex(base.BaseIndexTests): + pass From 164101ab6c71e33b27768c169adb5ed7b2cd36b6 Mon Sep 17 00:00:00 2001 From: Tim Swast Date: Wed, 9 Mar 2022 12:52:27 -0600 Subject: [PATCH 07/18] address errors in TestMethods --- tests/unit/date_compliance/conftest.py | 146 +++++++++++++++++++++++++ 1 file changed, 146 insertions(+) diff --git a/tests/unit/date_compliance/conftest.py b/tests/unit/date_compliance/conftest.py index 15b1476..20ac9b3 100644 --- a/tests/unit/date_compliance/conftest.py +++ b/tests/unit/date_compliance/conftest.py @@ -58,6 +58,28 @@ def all_boolean_reductions(request): return request.param +@pytest.fixture(params=[True, False]) +def as_frame(request): + """ + Boolean fixture to support Series and Series.to_frame() comparison testing. + + See: + https://github.com/pandas-dev/pandas/blob/main/pandas/tests/extension/conftest.py + """ + return request.param + + +@pytest.fixture(params=[True, False]) +def as_series(request): + """ + Boolean fixture to support arr and Series(arr) comparison testing. + + See: + https://github.com/pandas-dev/pandas/blob/main/pandas/tests/extension/conftest.py + """ + return request.param + + @pytest.fixture def data(): return DateArray( @@ -70,11 +92,99 @@ def data(): ) +@pytest.fixture +def data_for_grouping(dtype): + """ + Data for factorization, grouping, and unique tests. + + Expected to be like [B, B, NA, NA, A, A, B, C] + + Where A < B < C and NA is missing + + See: + https://github.com/pandas-dev/pandas/blob/main/pandas/tests/extension/conftest.py + """ + b = datetime.date(2022, 3, 9) + a = datetime.date(1969, 12, 31) + na = pandas.NaT + return pandas.array([b, b, na, na, a, a, b], dtype=dtype) + + +@pytest.fixture +def data_for_sorting(): + """ + Length-3 array with a known sort order. + + This should be three items [B, C, A] with + A < B < C + + See: + https://github.com/pandas-dev/pandas/blob/main/pandas/tests/extension/conftest.py + """ + return DateArray( + [ + datetime.date(2022, 1, 27), + datetime.date(2022, 3, 9), + datetime.date(1969, 12, 31), + ] + ) + + +@pytest.fixture +def data_missing_for_sorting(): + """ + Length-3 array with a known sort order. + + This should be three items [B, NA, A] with + A < B and NA missing. + + See: + https://github.com/pandas-dev/pandas/blob/main/pandas/tests/extension/conftest.py + """ + return DateArray( + [datetime.date(2022, 1, 27), pandas.NaT, datetime.date(1969, 12, 31)] + ) + + @pytest.fixture def data_missing(): + """Length-2 array with [NA, Valid] + + See: + https://github.com/pandas-dev/pandas/blob/main/pandas/tests/extension/conftest.py + """ return DateArray([None, datetime.date(2022, 1, 27)]) +@pytest.fixture +def data_repeated(data): + """ + Generate many datasets. + + See: + https://github.com/pandas-dev/pandas/blob/main/pandas/tests/extension/conftest.py + """ + + def gen(count): + for _ in range(count): + yield data + + return gen + + +@pytest.fixture(params=["data", "data_missing"]) +def all_data(request, data, data_missing): + """Parametrized fixture giving 'data' and 'data_missing' + + See: + https://github.com/pandas-dev/pandas/blob/main/pandas/tests/arrays/floating/conftest.py + """ + if request.param == "data": + return data + elif request.param == "data_missing": + return data_missing + + @pytest.fixture def dtype(): return DateDtype() @@ -103,3 +213,39 @@ def cmp(a, b): return a is pandas.NaT and a is b return cmp + + +@pytest.fixture(params=[None, lambda x: x]) +def sort_by_key(request): + """ + Simple fixture for testing keys in sorting methods. + Tests None (no key) and the identity key. + + See: https://github.com/pandas-dev/pandas/blob/main/pandas/conftest.py + """ + return request.param + + +@pytest.fixture(params=[True, False]) +def use_numpy(request): + """ + Boolean fixture to support comparison testing of ExtensionDtype array + and numpy array. + + See: + https://github.com/pandas-dev/pandas/blob/main/pandas/tests/extension/conftest.py + """ + return request.param + + +@pytest.fixture +def invalid_scalar(data): + """ + A scalar that *cannot* be held by this ExtensionArray. + The default should work for most subclasses, but is not guaranteed. + If the array can hold any item (i.e. object dtype), then use pytest.skip. + + See: + https://github.com/pandas-dev/pandas/blob/main/pandas/tests/extension/conftest.py + """ + return object.__new__(object) From fb13f0c2101493f16ebaf9f10ee6592d36192528 Mon Sep 17 00:00:00 2001 From: Tim Swast Date: Wed, 9 Mar 2022 13:27:33 -0600 Subject: [PATCH 08/18] fix: correct dtype and interface compliance errors in DateArray --- db_dtypes/core.py | 45 ++--- tests/unit/date_compliance/conftest.py | 182 +----------------- .../date_compliance/test_date_compliance.py | 4 - .../test_date_compliance_1_5.py | 35 ---- 4 files changed, 28 insertions(+), 238 deletions(-) delete mode 100644 tests/unit/date_compliance/test_date_compliance_1_5.py diff --git a/db_dtypes/core.py b/db_dtypes/core.py index e766af9..581b3a5 100644 --- a/db_dtypes/core.py +++ b/db_dtypes/core.py @@ -36,6 +36,7 @@ def construct_from_string(cls, name: str): raise TypeError( f"'construct_from_string' expects a string, got {type(name)}" ) + if name != cls.name: raise TypeError(f"Cannot construct a '{cls.__name__}' from 'another_type'") @@ -141,35 +142,29 @@ def min(self, *, axis: Optional[int] = None, skipna: bool = True, **kwargs): result = pandas_backports.nanmin( values=self._ndarray, axis=axis, mask=self.isna(), skipna=skipna ) - if axis is None or self.ndim == 1: - return self._box_func(result) - return self._from_backing_data(result) + return self._box_func(result) def max(self, *, axis: Optional[int] = None, skipna: bool = True, **kwargs): pandas_backports.numpy_validate_max((), kwargs) result = pandas_backports.nanmax( values=self._ndarray, axis=axis, mask=self.isna(), skipna=skipna ) - if axis is None or self.ndim == 1: - return self._box_func(result) - return self._from_backing_data(result) - - def median( - self, - *, - axis: Optional[int] = None, - out=None, - overwrite_input: bool = False, - keepdims: bool = False, - skipna: bool = True, - ): - if not hasattr(pandas_backports, "numpy_validate_median"): - raise NotImplementedError("Need pandas 1.3 or later to calculate median.") - - pandas_backports.numpy_validate_median( - (), {"out": out, "overwrite_input": overwrite_input, "keepdims": keepdims}, - ) - result = pandas_backports.nanmedian(self._ndarray, axis=axis, skipna=skipna) - if axis is None or self.ndim == 1: + return self._box_func(result) + + if pandas_release >= (1, 2): + + def median( + self, + *, + axis: Optional[int] = None, + out=None, + overwrite_input: bool = False, + keepdims: bool = False, + skipna: bool = True, + ): + pandas_backports.numpy_validate_median( + (), + {"out": out, "overwrite_input": overwrite_input, "keepdims": keepdims}, + ) + result = pandas_backports.nanmedian(self._ndarray, axis=axis, skipna=skipna) return self._box_func(result) - return self._from_backing_data(result) diff --git a/tests/unit/date_compliance/conftest.py b/tests/unit/date_compliance/conftest.py index 20ac9b3..b1db676 100644 --- a/tests/unit/date_compliance/conftest.py +++ b/tests/unit/date_compliance/conftest.py @@ -21,65 +21,6 @@ from db_dtypes import DateArray, DateDtype -_all_numeric_reductions = [ - "sum", - "max", - "min", - "mean", - "prod", - "std", - "var", - "median", - "kurt", - "skew", -] - - -@pytest.fixture(params=_all_numeric_reductions) -def all_numeric_reductions(request): - """ - Fixture for numeric reduction names. - - See: https://github.com/pandas-dev/pandas/blob/main/pandas/conftest.py - """ - return request.param - - -_all_boolean_reductions = ["all", "any"] - - -@pytest.fixture(params=_all_boolean_reductions) -def all_boolean_reductions(request): - """ - Fixture for boolean reduction names. - - See: https://github.com/pandas-dev/pandas/blob/main/pandas/conftest.py - """ - return request.param - - -@pytest.fixture(params=[True, False]) -def as_frame(request): - """ - Boolean fixture to support Series and Series.to_frame() comparison testing. - - See: - https://github.com/pandas-dev/pandas/blob/main/pandas/tests/extension/conftest.py - """ - return request.param - - -@pytest.fixture(params=[True, False]) -def as_series(request): - """ - Boolean fixture to support arr and Series(arr) comparison testing. - - See: - https://github.com/pandas-dev/pandas/blob/main/pandas/tests/extension/conftest.py - """ - return request.param - - @pytest.fixture def data(): return DateArray( @@ -92,60 +33,6 @@ def data(): ) -@pytest.fixture -def data_for_grouping(dtype): - """ - Data for factorization, grouping, and unique tests. - - Expected to be like [B, B, NA, NA, A, A, B, C] - - Where A < B < C and NA is missing - - See: - https://github.com/pandas-dev/pandas/blob/main/pandas/tests/extension/conftest.py - """ - b = datetime.date(2022, 3, 9) - a = datetime.date(1969, 12, 31) - na = pandas.NaT - return pandas.array([b, b, na, na, a, a, b], dtype=dtype) - - -@pytest.fixture -def data_for_sorting(): - """ - Length-3 array with a known sort order. - - This should be three items [B, C, A] with - A < B < C - - See: - https://github.com/pandas-dev/pandas/blob/main/pandas/tests/extension/conftest.py - """ - return DateArray( - [ - datetime.date(2022, 1, 27), - datetime.date(2022, 3, 9), - datetime.date(1969, 12, 31), - ] - ) - - -@pytest.fixture -def data_missing_for_sorting(): - """ - Length-3 array with a known sort order. - - This should be three items [B, NA, A] with - A < B and NA missing. - - See: - https://github.com/pandas-dev/pandas/blob/main/pandas/tests/extension/conftest.py - """ - return DateArray( - [datetime.date(2022, 1, 27), pandas.NaT, datetime.date(1969, 12, 31)] - ) - - @pytest.fixture def data_missing(): """Length-2 array with [NA, Valid] @@ -156,35 +43,6 @@ def data_missing(): return DateArray([None, datetime.date(2022, 1, 27)]) -@pytest.fixture -def data_repeated(data): - """ - Generate many datasets. - - See: - https://github.com/pandas-dev/pandas/blob/main/pandas/tests/extension/conftest.py - """ - - def gen(count): - for _ in range(count): - yield data - - return gen - - -@pytest.fixture(params=["data", "data_missing"]) -def all_data(request, data, data_missing): - """Parametrized fixture giving 'data' and 'data_missing' - - See: - https://github.com/pandas-dev/pandas/blob/main/pandas/tests/arrays/floating/conftest.py - """ - if request.param == "data": - return data - elif request.param == "data_missing": - return data_missing - - @pytest.fixture def dtype(): return DateDtype() @@ -209,43 +67,19 @@ def na_value(): @pytest.fixture def na_cmp(): - def cmp(a, b): - return a is pandas.NaT and a is b - - return cmp - - -@pytest.fixture(params=[None, lambda x: x]) -def sort_by_key(request): """ - Simple fixture for testing keys in sorting methods. - Tests None (no key) and the identity key. - - See: https://github.com/pandas-dev/pandas/blob/main/pandas/conftest.py - """ - return request.param + Binary operator for comparing NA values. - -@pytest.fixture(params=[True, False]) -def use_numpy(request): - """ - Boolean fixture to support comparison testing of ExtensionDtype array - and numpy array. + Should return a function of two arguments that returns + True if both arguments are (scalar) NA for your type. See: https://github.com/pandas-dev/pandas/blob/main/pandas/tests/extension/conftest.py + and + https://github.com/pandas-dev/pandas/blob/main/pandas/tests/extension/test_datetime.py """ - return request.param + def cmp(a, b): + return a is pandas.NaT and a is b -@pytest.fixture -def invalid_scalar(data): - """ - A scalar that *cannot* be held by this ExtensionArray. - The default should work for most subclasses, but is not guaranteed. - If the array can hold any item (i.e. object dtype), then use pytest.skip. - - See: - https://github.com/pandas-dev/pandas/blob/main/pandas/tests/extension/conftest.py - """ - return object.__new__(object) + return cmp diff --git a/tests/unit/date_compliance/test_date_compliance.py b/tests/unit/date_compliance/test_date_compliance.py index 670fc41..a11452e 100644 --- a/tests/unit/date_compliance/test_date_compliance.py +++ b/tests/unit/date_compliance/test_date_compliance.py @@ -59,7 +59,3 @@ def test_take_na_value_other_date(self): class TestMissing(base.BaseMissingTests): pass - - -class TestMethods(base.BaseMethodsTests): - pass diff --git a/tests/unit/date_compliance/test_date_compliance_1_5.py b/tests/unit/date_compliance/test_date_compliance_1_5.py deleted file mode 100644 index 9c6da24..0000000 --- a/tests/unit/date_compliance/test_date_compliance_1_5.py +++ /dev/null @@ -1,35 +0,0 @@ -# Copyright 2022 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -""" -Tests for extension interface compliance, inherited from pandas. - -See: -https://github.com/pandas-dev/pandas/blob/main/pandas/tests/extension/decimal/test_decimal.py -and -https://github.com/pandas-dev/pandas/blob/main/pandas/tests/extension/test_period.py -""" - -from pandas.tests.extension import base -import pytest - -# NDArrayBacked2DTests suite added in https://github.com/pandas-dev/pandas/pull/44974 -pytest.importorskip("pandas", minversion="1.5.0dev") - - -class Test2DCompat(base.NDArrayBacked2DTests): - pass - - -class TestIndex(base.BaseIndexTests): - pass From 58d394190d81b8f1eafe891d466eb77a181588d6 Mon Sep 17 00:00:00 2001 From: Tim Swast Date: Wed, 9 Mar 2022 16:43:25 -0600 Subject: [PATCH 09/18] add compliance tests to github actions --- .github/workflows/unittest.yml | 27 +++++++++++ noxfile.py | 13 +++-- owlbot.py | 41 ++++++++++++++++ .../conftest.py | 32 ------------- tests/compliance/date/conftest.py | 47 +++++++++++++++++++ .../date}/test_date_compliance.py | 0 6 files changed, 125 insertions(+), 35 deletions(-) rename tests/{unit/date_compliance => compliance}/conftest.py (70%) create mode 100644 tests/compliance/date/conftest.py rename tests/{unit/date_compliance => compliance/date}/test_date_compliance.py (100%) diff --git a/.github/workflows/unittest.yml b/.github/workflows/unittest.yml index e5be6ed..f317e5c 100644 --- a/.github/workflows/unittest.yml +++ b/.github/workflows/unittest.yml @@ -31,6 +31,33 @@ jobs: name: coverage-artifacts path: .coverage-${{ matrix.python }} + compliance: + runs-on: ubuntu-latest + strategy: + matrix: + python: ['3.10'] + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Setup Python + uses: actions/setup-python@v3 + with: + python-version: ${{ matrix.python }} + - name: Install nox + run: | + python -m pip install --upgrade setuptools pip wheel + python -m pip install nox + - name: Run unit tests + env: + COVERAGE_FILE: .coverage-${{ matrix.python }} + run: | + nox -s compliance + - name: Upload coverage results + uses: actions/upload-artifact@v3 + with: + name: coverage-artifacts + path: .coverage-${{ matrix.python }} + cover: runs-on: ubuntu-latest needs: diff --git a/noxfile.py b/noxfile.py index 5f48361..54421d8 100644 --- a/noxfile.py +++ b/noxfile.py @@ -37,6 +37,7 @@ nox.options.sessions = [ "lint", "unit", + "compliance", "cover", "lint_setup_py", "blacken", @@ -77,7 +78,7 @@ def lint_setup_py(session): session.run("python", "setup.py", "check", "--restructuredtext", "--strict") -def default(session): +def default(session, tests_path): # Install all test dependencies, then install this package in-place. constraints_path = str( @@ -106,15 +107,21 @@ def default(session): "--cov-config=.coveragerc", "--cov-report=", "--cov-fail-under=0", - os.path.join("tests", "unit"), + tests_path, *session.posargs, ) +@nox.session(python=UNIT_TEST_PYTHON_VERSIONS[-1]) +def compliance(session): + """Run the compliance test suite.""" + default(session, os.path.join("tests", "compliance")) + + @nox.session(python=UNIT_TEST_PYTHON_VERSIONS) def unit(session): """Run the unit test suite.""" - default(session) + default(session, os.path.join("tests", "unit")) @nox.session(python=SYSTEM_TEST_PYTHON_VERSIONS) diff --git a/owlbot.py b/owlbot.py index 30f3b3d..dad53cd 100644 --- a/owlbot.py +++ b/owlbot.py @@ -64,11 +64,52 @@ new_sessions = """ "lint", "unit", + "compliance", "cover", """ s.replace(["noxfile.py"], old_sessions, new_sessions) +# Add compliance tests. +old_unittest = """ + cover: + runs-on: ubuntu-latest +""" + +new_unittest = """ + compliance: + runs-on: ubuntu-latest + strategy: + matrix: + python: ['3.10'] + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Setup Python + uses: actions/setup-python@v3 + with: + python-version: ${{ matrix.python }} + - name: Install nox + run: | + python -m pip install --upgrade setuptools pip wheel + python -m pip install nox + - name: Run unit tests + env: + COVERAGE_FILE: .coverage-${{ matrix.python }} + run: | + nox -s compliance + - name: Upload coverage results + uses: actions/upload-artifact@v3 + with: + name: coverage-artifacts + path: .coverage-${{ matrix.python }} + + cover: + runs-on: ubuntu-latest +""" + +s.replace([".github/workflows/unittest.yml"], old_sessions, new_sessions) + # ---------------------------------------------------------------------------- # Samples templates # ---------------------------------------------------------------------------- diff --git a/tests/unit/date_compliance/conftest.py b/tests/compliance/conftest.py similarity index 70% rename from tests/unit/date_compliance/conftest.py rename to tests/compliance/conftest.py index b1db676..bc76692 100644 --- a/tests/unit/date_compliance/conftest.py +++ b/tests/compliance/conftest.py @@ -12,41 +12,9 @@ # See the License for the specific language governing permissions and # limitations under the License. -import datetime - -import numpy import pandas import pytest -from db_dtypes import DateArray, DateDtype - - -@pytest.fixture -def data(): - return DateArray( - numpy.arange( - datetime.datetime(1900, 1, 1), - datetime.datetime(2099, 12, 31), - datetime.timedelta(days=731), - dtype="datetime64[ns]", - ) - ) - - -@pytest.fixture -def data_missing(): - """Length-2 array with [NA, Valid] - - See: - https://github.com/pandas-dev/pandas/blob/main/pandas/tests/extension/conftest.py - """ - return DateArray([None, datetime.date(2022, 1, 27)]) - - -@pytest.fixture -def dtype(): - return DateDtype() - @pytest.fixture(params=["ffill", "bfill"]) def fillna_method(request): diff --git a/tests/compliance/date/conftest.py b/tests/compliance/date/conftest.py new file mode 100644 index 0000000..e25ccc9 --- /dev/null +++ b/tests/compliance/date/conftest.py @@ -0,0 +1,47 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import datetime + +import numpy +import pytest + +from db_dtypes import DateArray, DateDtype + + +@pytest.fixture +def data(): + return DateArray( + numpy.arange( + datetime.datetime(1900, 1, 1), + datetime.datetime(2099, 12, 31), + datetime.timedelta(days=731), + dtype="datetime64[ns]", + ) + ) + + +@pytest.fixture +def data_missing(): + """Length-2 array with [NA, Valid] + + See: + https://github.com/pandas-dev/pandas/blob/main/pandas/tests/extension/conftest.py + """ + return DateArray([None, datetime.date(2022, 1, 27)]) + + +@pytest.fixture +def dtype(): + return DateDtype() diff --git a/tests/unit/date_compliance/test_date_compliance.py b/tests/compliance/date/test_date_compliance.py similarity index 100% rename from tests/unit/date_compliance/test_date_compliance.py rename to tests/compliance/date/test_date_compliance.py From 7b58a6d9d9d3f4ea47550e533d45128869c02228 Mon Sep 17 00:00:00 2001 From: Owl Bot Date: Wed, 9 Mar 2022 22:45:28 +0000 Subject: [PATCH 10/18] =?UTF-8?q?=F0=9F=A6=89=20Updates=20from=20OwlBot=20?= =?UTF-8?q?post-processor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md --- .github/workflows/unittest.yml | 27 --------------------------- noxfile.py | 12 +++--------- 2 files changed, 3 insertions(+), 36 deletions(-) diff --git a/.github/workflows/unittest.yml b/.github/workflows/unittest.yml index f317e5c..e5be6ed 100644 --- a/.github/workflows/unittest.yml +++ b/.github/workflows/unittest.yml @@ -31,33 +31,6 @@ jobs: name: coverage-artifacts path: .coverage-${{ matrix.python }} - compliance: - runs-on: ubuntu-latest - strategy: - matrix: - python: ['3.10'] - steps: - - name: Checkout - uses: actions/checkout@v3 - - name: Setup Python - uses: actions/setup-python@v3 - with: - python-version: ${{ matrix.python }} - - name: Install nox - run: | - python -m pip install --upgrade setuptools pip wheel - python -m pip install nox - - name: Run unit tests - env: - COVERAGE_FILE: .coverage-${{ matrix.python }} - run: | - nox -s compliance - - name: Upload coverage results - uses: actions/upload-artifact@v3 - with: - name: coverage-artifacts - path: .coverage-${{ matrix.python }} - cover: runs-on: ubuntu-latest needs: diff --git a/noxfile.py b/noxfile.py index 54421d8..102be0d 100644 --- a/noxfile.py +++ b/noxfile.py @@ -78,7 +78,7 @@ def lint_setup_py(session): session.run("python", "setup.py", "check", "--restructuredtext", "--strict") -def default(session, tests_path): +def default(session): # Install all test dependencies, then install this package in-place. constraints_path = str( @@ -107,21 +107,15 @@ def default(session, tests_path): "--cov-config=.coveragerc", "--cov-report=", "--cov-fail-under=0", - tests_path, + os.path.join("tests", "unit"), *session.posargs, ) -@nox.session(python=UNIT_TEST_PYTHON_VERSIONS[-1]) -def compliance(session): - """Run the compliance test suite.""" - default(session, os.path.join("tests", "compliance")) - - @nox.session(python=UNIT_TEST_PYTHON_VERSIONS) def unit(session): """Run the unit test suite.""" - default(session, os.path.join("tests", "unit")) + default(session) @nox.session(python=SYSTEM_TEST_PYTHON_VERSIONS) From 881142201057a3ae8c8db7919048c9aa777c5de2 Mon Sep 17 00:00:00 2001 From: Tim Swast Date: Wed, 9 Mar 2022 16:49:51 -0600 Subject: [PATCH 11/18] split coverage --- .github/workflows/unittest.yml | 6 +++--- owlbot.py | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/unittest.yml b/.github/workflows/unittest.yml index f317e5c..6a070ce 100644 --- a/.github/workflows/unittest.yml +++ b/.github/workflows/unittest.yml @@ -47,16 +47,16 @@ jobs: run: | python -m pip install --upgrade setuptools pip wheel python -m pip install nox - - name: Run unit tests + - name: Run compliance tests env: - COVERAGE_FILE: .coverage-${{ matrix.python }} + COVERAGE_FILE: .coverage-compliance-${{ matrix.python }} run: | nox -s compliance - name: Upload coverage results uses: actions/upload-artifact@v3 with: name: coverage-artifacts - path: .coverage-${{ matrix.python }} + path: .coverage-compliance-${{ matrix.python }} cover: runs-on: ubuntu-latest diff --git a/owlbot.py b/owlbot.py index dad53cd..0a5a6e8 100644 --- a/owlbot.py +++ b/owlbot.py @@ -93,16 +93,16 @@ run: | python -m pip install --upgrade setuptools pip wheel python -m pip install nox - - name: Run unit tests + - name: Run compliance tests env: - COVERAGE_FILE: .coverage-${{ matrix.python }} + COVERAGE_FILE: .coverage-compliance-${{ matrix.python }} run: | nox -s compliance - name: Upload coverage results uses: actions/upload-artifact@v3 with: name: coverage-artifacts - path: .coverage-${{ matrix.python }} + path: .coverage-compliance-${{ matrix.python }} cover: runs-on: ubuntu-latest From 9fafe8ec383583c50e2d6e20e82b9270e2441a9f Mon Sep 17 00:00:00 2001 From: Tim Swast Date: Wed, 9 Mar 2022 16:59:34 -0600 Subject: [PATCH 12/18] add nox session back --- noxfile.py | 10 ++++++++-- owlbot.py | 23 +++++++++++++++++++++++ 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/noxfile.py b/noxfile.py index 102be0d..0e37164 100644 --- a/noxfile.py +++ b/noxfile.py @@ -78,7 +78,7 @@ def lint_setup_py(session): session.run("python", "setup.py", "check", "--restructuredtext", "--strict") -def default(session): +def default(session, tests_path): # Install all test dependencies, then install this package in-place. constraints_path = str( @@ -107,11 +107,17 @@ def default(session): "--cov-config=.coveragerc", "--cov-report=", "--cov-fail-under=0", - os.path.join("tests", "unit"), + tests_path, *session.posargs, ) +@nox.session(python=UNIT_TEST_PYTHON_VERSIONS[-1]) +def compliance(session): + """Run the compliance test suite.""" + default(session, os.path.join("tests", "compliance")) + + @nox.session(python=UNIT_TEST_PYTHON_VERSIONS) def unit(session): """Run the unit test suite.""" diff --git a/owlbot.py b/owlbot.py index 4ea653d..0abf825 100644 --- a/owlbot.py +++ b/owlbot.py @@ -71,6 +71,29 @@ s.replace(["noxfile.py"], old_sessions, new_sessions) # Add compliance tests. +s.replace( + ["noxfile.py"], r"def default\(session\):", "def default(session, tests_path):" +) +s.replace(["noxfile.py"], r'os.path.join\("tests", "unit"\),', "tests_path,") +s.replace( + ["noxfile.py"], + r""" +@nox.session\(python=UNIT_TEST_PYTHON_VERSIONS\) +def unit\(session\): +""", + ''' +@nox.session(python=UNIT_TEST_PYTHON_VERSIONS[-1]) +def compliance(session): + """Run the compliance test suite.""" + default(session, os.path.join("tests", "compliance")) + + +@nox.session(python=UNIT_TEST_PYTHON_VERSIONS) +def unit(session): +''', +) + + old_unittest = """ cover: runs-on: ubuntu-latest From 58da8e09f0eacfdeeb2a5cf4680d4030754b1dc3 Mon Sep 17 00:00:00 2001 From: Tim Swast Date: Wed, 9 Mar 2022 17:03:26 -0600 Subject: [PATCH 13/18] fix unit session --- noxfile.py | 2 +- owlbot.py | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/noxfile.py b/noxfile.py index 0e37164..54421d8 100644 --- a/noxfile.py +++ b/noxfile.py @@ -121,7 +121,7 @@ def compliance(session): @nox.session(python=UNIT_TEST_PYTHON_VERSIONS) def unit(session): """Run the unit test suite.""" - default(session) + default(session, os.path.join("tests", "unit")) @nox.session(python=SYSTEM_TEST_PYTHON_VERSIONS) diff --git a/owlbot.py b/owlbot.py index 0abf825..429ab86 100644 --- a/owlbot.py +++ b/owlbot.py @@ -77,10 +77,12 @@ s.replace(["noxfile.py"], r'os.path.join\("tests", "unit"\),', "tests_path,") s.replace( ["noxfile.py"], - r""" + r''' @nox.session\(python=UNIT_TEST_PYTHON_VERSIONS\) def unit\(session\): -""", + """Run the unit test suite.""" + default\(session\) +''', ''' @nox.session(python=UNIT_TEST_PYTHON_VERSIONS[-1]) def compliance(session): @@ -90,6 +92,8 @@ def compliance(session): @nox.session(python=UNIT_TEST_PYTHON_VERSIONS) def unit(session): + """Run the unit test suite.""" + default(session, os.path.join("tests", "unit")) ''', ) From d654527ee662ecb72174cfa3e04b99e24b5fc661 Mon Sep 17 00:00:00 2001 From: Tim Swast Date: Thu, 10 Mar 2022 12:10:01 -0600 Subject: [PATCH 14/18] move compliance tests and remove unnecessary test --- .github/workflows/compliance.yml | 32 +++++++++++++++ .github/workflows/unittest.yml | 27 ------------- owlbot.py | 40 ------------------- tests/compliance/date/test_date_compliance.py | 16 +------- 4 files changed, 33 insertions(+), 82 deletions(-) create mode 100644 .github/workflows/compliance.yml diff --git a/.github/workflows/compliance.yml b/.github/workflows/compliance.yml new file mode 100644 index 0000000..5a607f5 --- /dev/null +++ b/.github/workflows/compliance.yml @@ -0,0 +1,32 @@ +on: + pull_request: + branches: + - main +name: unittest +jobs: + compliance: + runs-on: ubuntu-latest + strategy: + matrix: + python: ['3.10'] + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Setup Python + uses: actions/setup-python@v3 + with: + python-version: ${{ matrix.python }} + - name: Install nox + run: | + python -m pip install --upgrade setuptools pip wheel + python -m pip install nox + - name: Run compliance tests + env: + COVERAGE_FILE: .coverage-compliance-${{ matrix.python }} + run: | + nox -s compliance + - name: Upload coverage results + uses: actions/upload-artifact@v3 + with: + name: coverage-artifacts + path: .coverage-compliance-${{ matrix.python }} diff --git a/.github/workflows/unittest.yml b/.github/workflows/unittest.yml index 6a070ce..e5be6ed 100644 --- a/.github/workflows/unittest.yml +++ b/.github/workflows/unittest.yml @@ -31,33 +31,6 @@ jobs: name: coverage-artifacts path: .coverage-${{ matrix.python }} - compliance: - runs-on: ubuntu-latest - strategy: - matrix: - python: ['3.10'] - steps: - - name: Checkout - uses: actions/checkout@v3 - - name: Setup Python - uses: actions/setup-python@v3 - with: - python-version: ${{ matrix.python }} - - name: Install nox - run: | - python -m pip install --upgrade setuptools pip wheel - python -m pip install nox - - name: Run compliance tests - env: - COVERAGE_FILE: .coverage-compliance-${{ matrix.python }} - run: | - nox -s compliance - - name: Upload coverage results - uses: actions/upload-artifact@v3 - with: - name: coverage-artifacts - path: .coverage-compliance-${{ matrix.python }} - cover: runs-on: ubuntu-latest needs: diff --git a/owlbot.py b/owlbot.py index 429ab86..6c59671 100644 --- a/owlbot.py +++ b/owlbot.py @@ -97,46 +97,6 @@ def unit(session): ''', ) - -old_unittest = """ - cover: - runs-on: ubuntu-latest -""" - -new_unittest = """ - compliance: - runs-on: ubuntu-latest - strategy: - matrix: - python: ['3.10'] - steps: - - name: Checkout - uses: actions/checkout@v3 - - name: Setup Python - uses: actions/setup-python@v3 - with: - python-version: ${{ matrix.python }} - - name: Install nox - run: | - python -m pip install --upgrade setuptools pip wheel - python -m pip install nox - - name: Run compliance tests - env: - COVERAGE_FILE: .coverage-compliance-${{ matrix.python }} - run: | - nox -s compliance - - name: Upload coverage results - uses: actions/upload-artifact@v3 - with: - name: coverage-artifacts - path: .coverage-compliance-${{ matrix.python }} - - cover: - runs-on: ubuntu-latest -""" - -s.replace([".github/workflows/unittest.yml"], old_unittest, new_unittest) - # ---------------------------------------------------------------------------- # Samples templates # ---------------------------------------------------------------------------- diff --git a/tests/compliance/date/test_date_compliance.py b/tests/compliance/date/test_date_compliance.py index a11452e..a805ecd 100644 --- a/tests/compliance/date/test_date_compliance.py +++ b/tests/compliance/date/test_date_compliance.py @@ -20,12 +20,8 @@ https://github.com/pandas-dev/pandas/blob/main/pandas/tests/extension/test_period.py """ -import datetime - from pandas.tests.extension import base -import db_dtypes - class TestDtype(base.BaseDtypeTests): pass @@ -44,17 +40,7 @@ class TestReshaping(base.BaseReshapingTests): class TestGetitem(base.BaseGetitemTests): - def test_take_na_value_other_date(self): - arr = db_dtypes.DateArray( - [datetime.date(2022, 3, 8), datetime.date(2022, 3, 9)] - ) - result = arr.take( - [0, -1], allow_fill=True, fill_value=datetime.date(1969, 12, 31) - ) - expected = db_dtypes.DateArray( - [datetime.date(2022, 3, 8), datetime.date(1969, 12, 31)] - ) - self.assert_extension_array_equal(result, expected) + pass class TestMissing(base.BaseMissingTests): From 69560d2398d9d6570f322ecad58bac49496b33ea Mon Sep 17 00:00:00 2001 From: Tim Swast Date: Thu, 10 Mar 2022 12:23:51 -0600 Subject: [PATCH 15/18] no need for coverage upload --- .github/workflows/compliance.yml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/.github/workflows/compliance.yml b/.github/workflows/compliance.yml index 5a607f5..77e6b05 100644 --- a/.github/workflows/compliance.yml +++ b/.github/workflows/compliance.yml @@ -25,8 +25,3 @@ jobs: COVERAGE_FILE: .coverage-compliance-${{ matrix.python }} run: | nox -s compliance - - name: Upload coverage results - uses: actions/upload-artifact@v3 - with: - name: coverage-artifacts - path: .coverage-compliance-${{ matrix.python }} From ce8d12a2d4e8e4a8e8a42b4317f9ac18fd8bc9db Mon Sep 17 00:00:00 2001 From: Tim Swast Date: Thu, 10 Mar 2022 13:12:54 -0600 Subject: [PATCH 16/18] fix coverage --- .coveragerc | 39 --------------------------------------- db_dtypes/core.py | 2 ++ tests/unit/test_date.py | 16 ++++++++++++++-- 3 files changed, 16 insertions(+), 41 deletions(-) delete mode 100644 .coveragerc diff --git a/.coveragerc b/.coveragerc deleted file mode 100644 index 0f8f905..0000000 --- a/.coveragerc +++ /dev/null @@ -1,39 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright 2020 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Generated by synthtool. DO NOT EDIT! -[run] -branch = True -omit = - google/__init__.py - db_dtypes/requirements.py - -[report] -fail_under = 100 -show_missing = True -exclude_lines = - # Re-enable the standard pragma - pragma: NO COVER - # Ignore debug-only repr - def __repr__ - # Ignore abstract methods - raise NotImplementedError -omit = - */gapic/*.py - */proto/*.py - */core/*.py - */site-packages/*.py - db_dtypes/requirements.py diff --git a/db_dtypes/core.py b/db_dtypes/core.py index 581b3a5..b5b0b7a 100644 --- a/db_dtypes/core.py +++ b/db_dtypes/core.py @@ -79,6 +79,8 @@ def astype(self, dtype, copy=True): return super().astype(dtype, copy=copy) def _cmp_method(self, other, op): + """Compare array values, for use in OpsMixin.""" + if is_scalar(other) and (pandas.isna(other) or type(other) == self.dtype.type): other = type(self)([other]) diff --git a/tests/unit/test_date.py b/tests/unit/test_date.py index bf877ea..bce2dc1 100644 --- a/tests/unit/test_date.py +++ b/tests/unit/test_date.py @@ -13,15 +13,27 @@ # limitations under the License. import datetime +import operator import pandas +import pandas.testing import pytest -# To register the types. -import db_dtypes # noqa +import db_dtypes from db_dtypes import pandas_backports +def test_construct_from_string_with_nonstring(): + with pytest.raises(TypeError): + db_dtypes.DateDtype.construct_from_string(object()) + + +def test__cmp_method_with_scalar(): + input_array = db_dtypes.DateArray([datetime.date(1900, 1, 1)]) + got = input_array._cmp_method(datetime.date(1900, 1, 1), operator.eq) + assert got[0] + + @pytest.mark.parametrize( "value, expected", [ From 8c5804f133412883c3741a01c92fdbb12bfd47ed Mon Sep 17 00:00:00 2001 From: Tim Swast Date: Thu, 10 Mar 2022 13:13:58 -0600 Subject: [PATCH 17/18] restore coverage --- .coveragerc | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 .coveragerc diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 0000000..0f8f905 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- +# +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Generated by synthtool. DO NOT EDIT! +[run] +branch = True +omit = + google/__init__.py + db_dtypes/requirements.py + +[report] +fail_under = 100 +show_missing = True +exclude_lines = + # Re-enable the standard pragma + pragma: NO COVER + # Ignore debug-only repr + def __repr__ + # Ignore abstract methods + raise NotImplementedError +omit = + */gapic/*.py + */proto/*.py + */core/*.py + */site-packages/*.py + db_dtypes/requirements.py From c2b0db69fd4b5baafed63bfdcdd36dd68b532342 Mon Sep 17 00:00:00 2001 From: Owl Bot Date: Thu, 10 Mar 2022 19:14:53 +0000 Subject: [PATCH 18/18] =?UTF-8?q?=F0=9F=A6=89=20Updates=20from=20OwlBot=20?= =?UTF-8?q?post-processor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md --- .coveragerc | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 .coveragerc diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 0000000..0f8f905 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- +# +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Generated by synthtool. DO NOT EDIT! +[run] +branch = True +omit = + google/__init__.py + db_dtypes/requirements.py + +[report] +fail_under = 100 +show_missing = True +exclude_lines = + # Re-enable the standard pragma + pragma: NO COVER + # Ignore debug-only repr + def __repr__ + # Ignore abstract methods + raise NotImplementedError +omit = + */gapic/*.py + */proto/*.py + */core/*.py + */site-packages/*.py + db_dtypes/requirements.py