From 1f5bdaa61447fc7c8193100c5a8a3d4e1c83abbd Mon Sep 17 00:00:00 2001 From: Simon Hawkins Date: Sun, 7 Jun 2020 15:15:54 +0100 Subject: [PATCH 1/5] TYP: some type annotations in core\tools\datetimes.py --- pandas/core/tools/datetimes.py | 107 ++++++++++++++++++++++-------- pandas/tests/series/test_ufunc.py | 4 +- 2 files changed, 83 insertions(+), 28 deletions(-) diff --git a/pandas/core/tools/datetimes.py b/pandas/core/tools/datetimes.py index 42bffa0374472..ab84ed47aa9d5 100644 --- a/pandas/core/tools/datetimes.py +++ b/pandas/core/tools/datetimes.py @@ -2,7 +2,7 @@ from datetime import datetime from functools import partial from itertools import islice -from typing import TYPE_CHECKING, Optional, TypeVar, Union +from typing import TYPE_CHECKING, Callable, Optional, TypeVar, Union, overload import warnings import numpy as np @@ -15,7 +15,7 @@ _guess_datetime_format, ) from pandas._libs.tslibs.strptime import array_strptime -from pandas._typing import ArrayLike +from pandas._typing import ArrayLike, Label from pandas.core.dtypes.common import ( ensure_object, @@ -123,7 +123,12 @@ def should_cache( return do_caching -def _maybe_cache(arg, format, cache, convert_listlike): +def _maybe_cache( + arg: ArrayConvertible, + format: Optional[str], + cache: bool, + convert_listlike: Callable, +) -> "Series": """ Create a cache of unique dates from an array of dates @@ -159,7 +164,7 @@ def _maybe_cache(arg, format, cache, convert_listlike): def _box_as_indexlike( - dt_array: ArrayLike, utc: Optional[bool] = None, name: Optional[str] = None + dt_array: ArrayLike, utc: Optional[bool] = None, name: Label = None ) -> Index: """ Properly boxes the ndarray of datetimes to DatetimeIndex @@ -244,15 +249,15 @@ def _return_parsed_timezone_results(result, timezones, tz, name): def _convert_listlike_datetimes( arg, - format, - name=None, - tz=None, - unit=None, - errors=None, - infer_datetime_format=None, - dayfirst=None, - yearfirst=None, - exact=None, + format: Optional[str], + name: Label = None, + tz: Optional[str] = None, + unit: Optional[str] = None, + errors: Optional[str] = None, + infer_datetime_format: Optional[bool] = None, + dayfirst: Optional[bool] = None, + yearfirst: Optional[bool] = None, + exact: Optional[bool] = None, ): """ Helper function for to_datetime. Performs the conversions of 1D listlike @@ -539,19 +544,70 @@ def _adjust_to_origin(arg, origin, unit): return arg +@overload def to_datetime( - arg, - errors="raise", - dayfirst=False, - yearfirst=False, - utc=None, - format=None, - exact=True, - unit=None, - infer_datetime_format=False, + arg: DatetimeScalar, + errors: str = ..., + dayfirst: bool = ..., + yearfirst: bool = ..., + utc: Optional[bool] = ..., + format: Optional[str] = ..., + exact: bool = ..., + unit: Optional[str] = ..., + infer_datetime_format: bool = ..., + origin=..., + cache: bool = ..., +) -> Timestamp: + ... + + +@overload +def to_datetime( + arg: "Series", + errors: str = ..., + dayfirst: bool = ..., + yearfirst: bool = ..., + utc: Optional[bool] = ..., + format: Optional[str] = ..., + exact: bool = ..., + unit: Optional[str] = ..., + infer_datetime_format: bool = ..., + origin=..., + cache: bool = ..., +) -> "Series": + ... + + +@overload +def to_datetime( + arg: Union[list, tuple], + errors: str = ..., + dayfirst: bool = ..., + yearfirst: bool = ..., + utc: Optional[bool] = ..., + format: Optional[str] = ..., + exact: bool = ..., + unit: Optional[str] = ..., + infer_datetime_format: bool = ..., + origin=..., + cache: bool = ..., +) -> DatetimeIndex: + ... + + +def to_datetime( + arg: DatetimeScalarOrArrayConvertible, + errors: str = "raise", + dayfirst: bool = False, + yearfirst: bool = False, + utc: Optional[bool] = None, + format: Optional[str] = None, + exact: bool = True, + unit: Optional[str] = None, + infer_datetime_format: bool = False, origin="unix", - cache=True, -): + cache: bool = True, +) -> Union[DatetimeIndex, "Series", Timestamp]: """ Convert argument to datetime. @@ -746,8 +802,7 @@ def to_datetime( if not cache_array.empty: result = _convert_and_box_cache(arg, cache_array, name=arg.name) else: - convert_listlike = partial(convert_listlike, name=arg.name) - result = convert_listlike(arg, format) + result = convert_listlike(arg, format, name=arg.name) elif is_list_like(arg): try: cache_array = _maybe_cache(arg, format, cache, convert_listlike) diff --git a/pandas/tests/series/test_ufunc.py b/pandas/tests/series/test_ufunc.py index c7fc37a278e83..7046b71be0f04 100644 --- a/pandas/tests/series/test_ufunc.py +++ b/pandas/tests/series/test_ufunc.py @@ -255,8 +255,8 @@ def __add__(self, other): pd.array([1, 3, 2], dtype="int64"), pd.array([1, 10, 0], dtype="Sparse[int]"), pd.to_datetime(["2000", "2010", "2001"]), - pd.to_datetime(["2000", "2010", "2001"]).tz_localize("CET"), - pd.to_datetime(["2000", "2010", "2001"]).to_period(freq="D"), + pd.to_datetime(["2000", "2010", "2001"]).tz_localize("CET"), # type: ignore + pd.to_datetime(["2000", "2010", "2001"]).to_period(freq="D"), # type: ignore ], ) def test_reduce(values): From 22c1539700c0aa1443977ba0f473e44ab340809d Mon Sep 17 00:00:00 2001 From: Simon Hawkins Date: Sun, 7 Jun 2020 18:18:35 +0100 Subject: [PATCH 2/5] use generic types for List and Tuple --- pandas/core/tools/datetimes.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/pandas/core/tools/datetimes.py b/pandas/core/tools/datetimes.py index ab84ed47aa9d5..6bbc47865a544 100644 --- a/pandas/core/tools/datetimes.py +++ b/pandas/core/tools/datetimes.py @@ -2,7 +2,16 @@ from datetime import datetime from functools import partial from itertools import islice -from typing import TYPE_CHECKING, Callable, Optional, TypeVar, Union, overload +from typing import ( + TYPE_CHECKING, + Callable, + List, + Optional, + Tuple, + TypeVar, + Union, + overload, +) import warnings import numpy as np @@ -49,12 +58,10 @@ # --------------------------------------------------------------------- # types used in annotations -ArrayConvertible = Union[list, tuple, ArrayLike, "Series"] +ArrayConvertible = Union[List, Tuple, ArrayLike, "Series"] Scalar = Union[int, float, str] DatetimeScalar = TypeVar("DatetimeScalar", Scalar, datetime) -DatetimeScalarOrArrayConvertible = Union[ - DatetimeScalar, list, tuple, ArrayLike, "Series" -] +DatetimeScalarOrArrayConvertible = Union[DatetimeScalar, ArrayConvertible] # --------------------------------------------------------------------- @@ -580,7 +587,7 @@ def to_datetime( @overload def to_datetime( - arg: Union[list, tuple], + arg: Union[List, Tuple], errors: str = ..., dayfirst: bool = ..., yearfirst: bool = ..., From 3a9a54328f08993bd0bf60e877174558e35d7c67 Mon Sep 17 00:00:00 2001 From: Simon Hawkins Date: Mon, 8 Jun 2020 13:58:51 +0100 Subject: [PATCH 3/5] tz_localize and to_period --- pandas/core/indexes/datetimes.py | 25 ++++++++++++++++++++++--- pandas/core/series.py | 2 +- pandas/core/tools/datetimes.py | 4 +--- pandas/tests/series/test_ufunc.py | 4 ++-- 4 files changed, 26 insertions(+), 9 deletions(-) diff --git a/pandas/core/indexes/datetimes.py b/pandas/core/indexes/datetimes.py index 68c55426294ef..62cb18cadd077 100644 --- a/pandas/core/indexes/datetimes.py +++ b/pandas/core/indexes/datetimes.py @@ -10,7 +10,7 @@ from pandas._libs.tslibs.frequencies import get_freq_group from pandas._libs.tslibs.offsets import prefix_mapping from pandas._typing import DtypeObj, Label -from pandas.util._decorators import cache_readonly +from pandas.util._decorators import cache_readonly, doc from pandas.core.dtypes.common import ( DT64NS_DTYPE, @@ -64,9 +64,13 @@ def _new_DatetimeIndex(cls, d): @inherit_names( - ["to_period", "to_perioddelta", "to_julian_date", "strftime", "isocalendar"] + ["to_perioddelta", "to_julian_date", "strftime", "isocalendar"] + DatetimeArray._field_ops - + DatetimeArray._datetimelike_methods, + + [ + method + for method in DatetimeArray._datetimelike_methods + if method not in ("tz_localize",) + ], DatetimeArray, wrap=True, ) @@ -218,6 +222,21 @@ class DatetimeIndex(DatetimeTimedeltaMixin): _data: DatetimeArray tz: Optional[tzinfo] + # -------------------------------------------------------------------- + # methods that dispatch to array and wrap result in DatetimeIndex + + @doc(DatetimeArray.tz_localize) + def tz_localize( + self, tz, ambiguous="raise", nonexistent="raise" + ) -> "DatetimeIndex": + arr = self._data.tz_localize(tz, ambiguous, nonexistent) + return type(self)._simple_new(arr, name=self.name) + + @doc(DatetimeArray.to_period) + def to_period(self, freq=None) -> "DatetimeIndex": + arr = self._data.to_period(freq) + return type(self)._simple_new(arr, name=self.name) + # -------------------------------------------------------------------- # Constructors diff --git a/pandas/core/series.py b/pandas/core/series.py index 6b5ed86027806..ad78c20725388 100644 --- a/pandas/core/series.py +++ b/pandas/core/series.py @@ -4831,7 +4831,7 @@ def to_period(self, freq=None, copy=True) -> "Series": if not isinstance(self.index, DatetimeIndex): raise TypeError(f"unsupported Type {type(self.index).__name__}") - new_index = self.index.to_period(freq=freq) # type: ignore + new_index = self.index.to_period(freq=freq) return self._constructor(new_values, index=new_index).__finalize__( self, method="to_period" ) diff --git a/pandas/core/tools/datetimes.py b/pandas/core/tools/datetimes.py index 6bbc47865a544..95ea81c1ea62d 100644 --- a/pandas/core/tools/datetimes.py +++ b/pandas/core/tools/datetimes.py @@ -318,9 +318,7 @@ def _convert_listlike_datetimes( pass elif tz: # DatetimeArray, DatetimeIndex - # error: Item "DatetimeIndex" of "Union[DatetimeArray, DatetimeIndex]" has - # no attribute "tz_localize" - return arg.tz_localize(tz) # type: ignore + return arg.tz_localize(tz) return arg diff --git a/pandas/tests/series/test_ufunc.py b/pandas/tests/series/test_ufunc.py index 7046b71be0f04..c7fc37a278e83 100644 --- a/pandas/tests/series/test_ufunc.py +++ b/pandas/tests/series/test_ufunc.py @@ -255,8 +255,8 @@ def __add__(self, other): pd.array([1, 3, 2], dtype="int64"), pd.array([1, 10, 0], dtype="Sparse[int]"), pd.to_datetime(["2000", "2010", "2001"]), - pd.to_datetime(["2000", "2010", "2001"]).tz_localize("CET"), # type: ignore - pd.to_datetime(["2000", "2010", "2001"]).to_period(freq="D"), # type: ignore + pd.to_datetime(["2000", "2010", "2001"]).tz_localize("CET"), + pd.to_datetime(["2000", "2010", "2001"]).to_period(freq="D"), ], ) def test_reduce(values): From ed0db3b842fa8aed6e576a4f52ce8fe5de2f7864 Mon Sep 17 00:00:00 2001 From: Simon Hawkins Date: Mon, 8 Jun 2020 14:18:01 +0100 Subject: [PATCH 4/5] add Timezone alias to pandas._typing --- pandas/_typing.py | 3 ++- pandas/core/tools/datetimes.py | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/pandas/_typing.py b/pandas/_typing.py index 71df27119bd96..4892abc5f6f51 100644 --- a/pandas/_typing.py +++ b/pandas/_typing.py @@ -1,4 +1,4 @@ -from datetime import datetime, timedelta +from datetime import datetime, timedelta, tzinfo from pathlib import Path from typing import ( IO, @@ -52,6 +52,7 @@ TimedeltaConvertibleTypes = Union[ "Timedelta", timedelta, np.timedelta64, int, np.int64, float, str ] +Timezone = Union[str, tzinfo] # other diff --git a/pandas/core/tools/datetimes.py b/pandas/core/tools/datetimes.py index 95ea81c1ea62d..29887941fe442 100644 --- a/pandas/core/tools/datetimes.py +++ b/pandas/core/tools/datetimes.py @@ -24,7 +24,7 @@ _guess_datetime_format, ) from pandas._libs.tslibs.strptime import array_strptime -from pandas._typing import ArrayLike, Label +from pandas._typing import ArrayLike, Label, Timezone from pandas.core.dtypes.common import ( ensure_object, @@ -258,7 +258,7 @@ def _convert_listlike_datetimes( arg, format: Optional[str], name: Label = None, - tz: Optional[str] = None, + tz: Optional[Timezone] = None, unit: Optional[str] = None, errors: Optional[str] = None, infer_datetime_format: Optional[bool] = None, From 105505479c576b3ac8ccc4a0cce0e2609990c796 Mon Sep 17 00:00:00 2001 From: Simon Hawkins Date: Mon, 8 Jun 2020 14:47:08 +0100 Subject: [PATCH 5/5] update return type with DatetimeScalar --- pandas/core/tools/datetimes.py | 5 +++-- pandas/io/excel/_odfreader.py | 6 ++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/pandas/core/tools/datetimes.py b/pandas/core/tools/datetimes.py index 29887941fe442..0adab143f6052 100644 --- a/pandas/core/tools/datetimes.py +++ b/pandas/core/tools/datetimes.py @@ -54,6 +54,7 @@ if TYPE_CHECKING: from pandas import Series # noqa:F401 + from pandas._libs.tslibs.nattype import NaTType # noqa:F401 # --------------------------------------------------------------------- # types used in annotations @@ -562,7 +563,7 @@ def to_datetime( infer_datetime_format: bool = ..., origin=..., cache: bool = ..., -) -> Timestamp: +) -> Union[DatetimeScalar, "NaTType"]: ... @@ -612,7 +613,7 @@ def to_datetime( infer_datetime_format: bool = False, origin="unix", cache: bool = True, -) -> Union[DatetimeIndex, "Series", Timestamp]: +) -> Union[DatetimeIndex, "Series", DatetimeScalar, "NaTType"]: """ Convert argument to datetime. diff --git a/pandas/io/excel/_odfreader.py b/pandas/io/excel/_odfreader.py index 739c77d1c0b99..be86b57ca2066 100644 --- a/pandas/io/excel/_odfreader.py +++ b/pandas/io/excel/_odfreader.py @@ -1,4 +1,4 @@ -from typing import List +from typing import List, cast from pandas._typing import FilePathOrBuffer, Scalar from pandas.compat._optional import import_optional_dependency @@ -179,7 +179,9 @@ def _get_cell_value(self, cell, convert_float: bool) -> Scalar: cell_value = cell.attributes.get((OFFICENS, "date-value")) return pd.to_datetime(cell_value) elif cell_type == "time": - return pd.to_datetime(str(cell)).time() + result = pd.to_datetime(str(cell)) + result = cast(pd.Timestamp, result) + return result.time() else: raise ValueError(f"Unrecognized type {cell_type}")