From c2a93c359bbe643702dc772defef50a743367ed9 Mon Sep 17 00:00:00 2001 From: Ryan Nazareth Date: Thu, 26 Dec 2019 18:45:28 +0000 Subject: [PATCH 01/10] Deprecate pandas.datetime and add whats new --- doc/source/whatsnew/v1.0.0.rst | 1 + pandas/__init__.py | 39 +++++++++++++++++++++++++++++++--- 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/doc/source/whatsnew/v1.0.0.rst b/doc/source/whatsnew/v1.0.0.rst index 9023cf2ab1b4f..7bd5aa64ad557 100644 --- a/doc/source/whatsnew/v1.0.0.rst +++ b/doc/source/whatsnew/v1.0.0.rst @@ -207,6 +207,7 @@ Other enhancements - The ``partition_cols`` argument in :meth:`DataFrame.to_parquet` now accepts a string (:issue:`27117`) - :func:`to_parquet` now appropriately handles the ``schema`` argument for user defined schemas in the pyarrow engine. (:issue: `30270`) - DataFrame constructor preserve `ExtensionArray` dtype with `ExtensionArray` (:issue:`11363`) +- The pandas.datetime submodule is now deprecated. Import datetime directly instead(:issue:`30296`) Build Changes diff --git a/pandas/__init__.py b/pandas/__init__.py index 30b7e5bafe1df..2eba81988dc3e 100644 --- a/pandas/__init__.py +++ b/pandas/__init__.py @@ -39,8 +39,6 @@ "the C extensions first.".format(module) ) -from datetime import datetime - from pandas._config import ( get_option, set_option, @@ -211,6 +209,20 @@ class Panel: pass return Panel + + elif name == "datetime": + warnings.warn( + "The datetime class is removed from pandas. Accessing it from " + "the top-level namespace will also be removed in the next " + "version", + FutureWarning, + stacklevel=2, + ) + + from datetime import datetime + + return datetime + elif name in {"SparseSeries", "SparseDataFrame"}: warnings.warn( "The {} class is removed from pandas. Accessing it from " @@ -224,7 +236,6 @@ class Panel: raise AttributeError("module 'pandas' has no attribute '{}'".format(name)) - else: class Panel: @@ -236,6 +247,28 @@ class SparseDataFrame: class SparseSeries: pass + class Datetime: + def __init__(self): + from datetime import datetime + import warnings + + self.datetime = datetime + self.warnings = warnings + + def __getattr__(self, item): + self.warnings.warn( + "The pandas.datetime module is deprecated and will be removed from pandas in a future version. " + "Import numpy directly instead", + FutureWarning, + stacklevel=2, + ) + try: + return getattr(self.datetime, item) + except AttributeError: + raise AttributeError(f"module datetime has no attribute {item}") + + datetime = Datetime() + # module level doc-string __doc__ = """ From 6d47e446f99db24b328858967cd3b7a91f510635 Mon Sep 17 00:00:00 2001 From: Ryan Nazareth Date: Thu, 26 Dec 2019 18:45:55 +0000 Subject: [PATCH 02/10] add tests --- pandas/tests/api/test_api.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/pandas/tests/api/test_api.py b/pandas/tests/api/test_api.py index 900ba878e4c0a..482856d5c2d48 100644 --- a/pandas/tests/api/test_api.py +++ b/pandas/tests/api/test_api.py @@ -93,6 +93,7 @@ class TestPDApi(Base): ] if not compat.PY37: classes.extend(["Panel", "SparseSeries", "SparseDataFrame"]) + deprecated_modules.extend("datetime") # these are already deprecated; awaiting removal deprecated_classes: List[str] = [] @@ -101,7 +102,7 @@ class TestPDApi(Base): deprecated_classes_in_future: List[str] = [] # external modules exposed in pandas namespace - modules = ["np", "datetime"] + modules = ["np"] # top-level functions funcs = [ @@ -221,6 +222,15 @@ def test_api(self): ) +def test_datetime(): + from datetime import datetime + import warnings + + with warnings.catch_warnings(): + warnings.simplefilter("ignore", FutureWarning) + assert datetime.date(2015, 7, 10) == pd.datetime.date(2015, 7, 10) + + class TestApi(Base): allowed = ["types", "extensions", "indexers"] From 0329d2445fa084b10fd4a8a0a1ff08afe8a7bd13 Mon Sep 17 00:00:00 2001 From: Ryan Nazareth Date: Thu, 26 Dec 2019 18:51:41 +0000 Subject: [PATCH 03/10] Fix test and warning message --- pandas/__init__.py | 12 +++++++----- pandas/tests/api/test_api.py | 13 +++++++++---- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/pandas/__init__.py b/pandas/__init__.py index 2eba81988dc3e..3b7ee928efe6f 100644 --- a/pandas/__init__.py +++ b/pandas/__init__.py @@ -212,9 +212,9 @@ class Panel: elif name == "datetime": warnings.warn( - "The datetime class is removed from pandas. Accessing it from " - "the top-level namespace will also be removed in the next " - "version", + "The pandas.datetime module is deprecated " + "and will be removed from pandas in a future version. " + "Import datetime directly instead.", FutureWarning, stacklevel=2, ) @@ -236,6 +236,7 @@ class Panel: raise AttributeError("module 'pandas' has no attribute '{}'".format(name)) + else: class Panel: @@ -257,8 +258,9 @@ def __init__(self): def __getattr__(self, item): self.warnings.warn( - "The pandas.datetime module is deprecated and will be removed from pandas in a future version. " - "Import numpy directly instead", + "The pandas.datetime module is deprecated " + "and will be removed from pandas in a future version. " + "Import datetime directly instead.", FutureWarning, stacklevel=2, ) diff --git a/pandas/tests/api/test_api.py b/pandas/tests/api/test_api.py index 482856d5c2d48..14494d356259b 100644 --- a/pandas/tests/api/test_api.py +++ b/pandas/tests/api/test_api.py @@ -224,11 +224,16 @@ def test_api(self): def test_datetime(): from datetime import datetime - import warnings - with warnings.catch_warnings(): - warnings.simplefilter("ignore", FutureWarning) - assert datetime.date(2015, 7, 10) == pd.datetime.date(2015, 7, 10) + msg = ( + "The pandas.datetime module is deprecated " + "and will be removed from pandas in a future version. " + "Import datetime directly instead." + ) + + with tm.assert_produces_warning(FutureWarning) as w: + assert datetime(2015, 1, 2, 0, 0) == pd.datetime(2015, 1, 2, 0, 0) + assert msg in str(w[-1].message) class TestApi(Base): From b45a47d656557c2ee5977afad2a4d08b68c26c68 Mon Sep 17 00:00:00 2001 From: Ryan Nazareth Date: Thu, 26 Dec 2019 20:42:32 +0000 Subject: [PATCH 04/10] minor syntax changes --- doc/source/whatsnew/v1.0.0.rst | 2 +- pandas/__init__.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/source/whatsnew/v1.0.0.rst b/doc/source/whatsnew/v1.0.0.rst index 7bd5aa64ad557..c69b9a6f51c53 100644 --- a/doc/source/whatsnew/v1.0.0.rst +++ b/doc/source/whatsnew/v1.0.0.rst @@ -207,7 +207,7 @@ Other enhancements - The ``partition_cols`` argument in :meth:`DataFrame.to_parquet` now accepts a string (:issue:`27117`) - :func:`to_parquet` now appropriately handles the ``schema`` argument for user defined schemas in the pyarrow engine. (:issue: `30270`) - DataFrame constructor preserve `ExtensionArray` dtype with `ExtensionArray` (:issue:`11363`) -- The pandas.datetime submodule is now deprecated. Import datetime directly instead(:issue:`30296`) +- The ``pandas.datetime`` submodule is now deprecated. Import ``datetime`` directly instead(:issue:`30296`) Build Changes diff --git a/pandas/__init__.py b/pandas/__init__.py index 3b7ee928efe6f..003a862e34c08 100644 --- a/pandas/__init__.py +++ b/pandas/__init__.py @@ -248,7 +248,7 @@ class SparseDataFrame: class SparseSeries: pass - class Datetime: + class __Datetime: def __init__(self): from datetime import datetime import warnings @@ -269,7 +269,7 @@ def __getattr__(self, item): except AttributeError: raise AttributeError(f"module datetime has no attribute {item}") - datetime = Datetime() + datetime = __Datetime() # module level doc-string From 624f2deb206ce97ec833e0ef037c1b8ba210dc13 Mon Sep 17 00:00:00 2001 From: Ryan Nazareth Date: Fri, 27 Dec 2019 00:42:07 +0000 Subject: [PATCH 05/10] Fix tests using pd.datetime --- pandas/tests/api/test_api.py | 2 +- pandas/tests/dtypes/test_common.py | 3 ++- pandas/tests/frame/indexing/test_indexing.py | 8 ++++---- pandas/tests/groupby/test_groupby.py | 4 +--- pandas/tests/indexing/test_iloc.py | 3 ++- pandas/tests/io/excel/test_writers.py | 2 +- pandas/tests/io/sas/test_sas7bdat.py | 3 ++- 7 files changed, 13 insertions(+), 12 deletions(-) diff --git a/pandas/tests/api/test_api.py b/pandas/tests/api/test_api.py index 14494d356259b..f60ea28731299 100644 --- a/pandas/tests/api/test_api.py +++ b/pandas/tests/api/test_api.py @@ -93,7 +93,7 @@ class TestPDApi(Base): ] if not compat.PY37: classes.extend(["Panel", "SparseSeries", "SparseDataFrame"]) - deprecated_modules.extend("datetime") + deprecated_modules.append("datetime") # these are already deprecated; awaiting removal deprecated_classes: List[str] = [] diff --git a/pandas/tests/dtypes/test_common.py b/pandas/tests/dtypes/test_common.py index 667ee467f2f29..a10d0d34685f4 100644 --- a/pandas/tests/dtypes/test_common.py +++ b/pandas/tests/dtypes/test_common.py @@ -1,3 +1,4 @@ +from datetime import datetime from typing import List import numpy as np @@ -488,7 +489,7 @@ def test_is_numeric_v_string_like(): def test_is_datetimelike_v_numeric(): - dt = np.datetime64(pd.datetime(2017, 1, 1)) + dt = np.datetime64(datetime(2017, 1, 1)) assert not com.is_datetimelike_v_numeric(1, 1) assert not com.is_datetimelike_v_numeric(dt, dt) diff --git a/pandas/tests/frame/indexing/test_indexing.py b/pandas/tests/frame/indexing/test_indexing.py index cd384d6fdbfad..9a53caa491970 100644 --- a/pandas/tests/frame/indexing/test_indexing.py +++ b/pandas/tests/frame/indexing/test_indexing.py @@ -1146,18 +1146,18 @@ def test_setitem_mixed_datetime(self): { "a": [0, 0, 0, 0, 13, 14], "b": [ - pd.datetime(2012, 1, 1), + datetime(2012, 1, 1), 1, "x", "y", - pd.datetime(2013, 1, 1), - pd.datetime(2014, 1, 1), + datetime(2013, 1, 1), + datetime(2014, 1, 1), ], } ) df = pd.DataFrame(0, columns=list("ab"), index=range(6)) df["b"] = pd.NaT - df.loc[0, "b"] = pd.datetime(2012, 1, 1) + df.loc[0, "b"] = datetime(2012, 1, 1) df.loc[1, "b"] = 1 df.loc[[2, 3], "b"] = "x", "y" A = np.array( diff --git a/pandas/tests/groupby/test_groupby.py b/pandas/tests/groupby/test_groupby.py index 8f88f68c69f2b..7384bd5591784 100644 --- a/pandas/tests/groupby/test_groupby.py +++ b/pandas/tests/groupby/test_groupby.py @@ -1715,9 +1715,7 @@ def test_pivot_table_values_key_error(): # This test is designed to replicate the error in issue #14938 df = pd.DataFrame( { - "eventDate": pd.date_range( - pd.datetime.today(), periods=20, freq="M" - ).tolist(), + "eventDate": pd.date_range(datetime.today(), periods=20, freq="M").tolist(), "thename": range(0, 20), } ) diff --git a/pandas/tests/indexing/test_iloc.py b/pandas/tests/indexing/test_iloc.py index 2f27757d6a754..d4731bcdc5b46 100644 --- a/pandas/tests/indexing/test_iloc.py +++ b/pandas/tests/indexing/test_iloc.py @@ -1,5 +1,6 @@ """ test positional based indexing with iloc """ +from datetime import datetime from warnings import catch_warnings, simplefilter import numpy as np @@ -122,7 +123,7 @@ def check(result, expected): [ ([slice(None), ["A", "D"]]), (["1", "2"], slice(None)), - ([pd.datetime(2019, 1, 1)], slice(None)), + ([datetime(2019, 1, 1)], slice(None)), ], ) def test_iloc_non_integer_raises(self, index, columns, index_vals, column_vals): diff --git a/pandas/tests/io/excel/test_writers.py b/pandas/tests/io/excel/test_writers.py index e0cb75b0a6c99..c394bc87c99e7 100644 --- a/pandas/tests/io/excel/test_writers.py +++ b/pandas/tests/io/excel/test_writers.py @@ -252,7 +252,7 @@ def test_read_excel_parse_dates(self, ext): res = pd.read_excel(pth, parse_dates=["date_strings"], index_col=0) tm.assert_frame_equal(df, res) - date_parser = lambda x: pd.datetime.strptime(x, "%m/%d/%Y") + date_parser = lambda x: datetime.strptime(x, "%m/%d/%Y") res = pd.read_excel( pth, parse_dates=["date_strings"], date_parser=date_parser, index_col=0 ) diff --git a/pandas/tests/io/sas/test_sas7bdat.py b/pandas/tests/io/sas/test_sas7bdat.py index d3480b246b91f..c89342627f796 100644 --- a/pandas/tests/io/sas/test_sas7bdat.py +++ b/pandas/tests/io/sas/test_sas7bdat.py @@ -1,3 +1,4 @@ +from datetime import datetime import io import os from pathlib import Path @@ -23,7 +24,7 @@ def setup_method(self, datapath): for j in 1, 2: fname = os.path.join(self.dirpath, f"test_sas7bdat_{j}.csv") df = pd.read_csv(fname) - epoch = pd.datetime(1960, 1, 1) + epoch = datetime(1960, 1, 1) t1 = pd.to_timedelta(df["Column4"], unit="d") df["Column4"] = epoch + t1 t2 = pd.to_timedelta(df["Column12"], unit="d") From 342038f50c3fceca6a40ff3555115488c76bfda7 Mon Sep 17 00:00:00 2001 From: Ryan Nazareth Date: Fri, 27 Dec 2019 23:52:55 +0000 Subject: [PATCH 06/10] alias import name to fix mypy error clash with local name datetime --- pandas/__init__.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pandas/__init__.py b/pandas/__init__.py index 9f9b4511f6b60..158eb83758eeb 100644 --- a/pandas/__init__.py +++ b/pandas/__init__.py @@ -217,9 +217,9 @@ class Panel: stacklevel=2, ) - from datetime import datetime + from datetime import datetime as dt - return datetime + return dt elif name == "np": @@ -285,10 +285,10 @@ def __getattr__(self, item): class __Datetime: def __init__(self): - from datetime import datetime + from datetime import datetime as dt import warnings - self.datetime = datetime + self.datetime = dt self.warnings = warnings def __getattr__(self, item): From 7dfce826c1db2210d5e13c9c4d36f3fdcd3a6352 Mon Sep 17 00:00:00 2001 From: Ryan Nazareth Date: Sat, 28 Dec 2019 00:45:37 +0000 Subject: [PATCH 07/10] Fix CI errors for other bad tests using pd.datetime --- pandas/tests/indexes/datetimes/test_constructors.py | 12 ++---------- pandas/tests/indexes/datetimes/test_misc.py | 3 ++- 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/pandas/tests/indexes/datetimes/test_constructors.py b/pandas/tests/indexes/datetimes/test_constructors.py index 58ab44fba08cf..28e011718a756 100644 --- a/pandas/tests/indexes/datetimes/test_constructors.py +++ b/pandas/tests/indexes/datetimes/test_constructors.py @@ -1,4 +1,4 @@ -from datetime import timedelta +from datetime import datetime, timedelta from functools import partial from operator import attrgetter @@ -10,15 +10,7 @@ from pandas._libs.tslibs import OutOfBoundsDatetime, conversion import pandas as pd -from pandas import ( - DatetimeIndex, - Index, - Timestamp, - date_range, - datetime, - offsets, - to_datetime, -) +from pandas import DatetimeIndex, Index, Timestamp, date_range, offsets, to_datetime from pandas.core.arrays import DatetimeArray, period_array import pandas.util.testing as tm diff --git a/pandas/tests/indexes/datetimes/test_misc.py b/pandas/tests/indexes/datetimes/test_misc.py index c144f2a447ed3..afc3bed85a8d2 100644 --- a/pandas/tests/indexes/datetimes/test_misc.py +++ b/pandas/tests/indexes/datetimes/test_misc.py @@ -1,4 +1,5 @@ import calendar +from datetime import datetime import locale import unicodedata @@ -6,7 +7,7 @@ import pytest import pandas as pd -from pandas import DatetimeIndex, Index, Timestamp, date_range, datetime, offsets +from pandas import DatetimeIndex, Index, Timestamp, date_range, offsets import pandas.util.testing as tm From 54ce63e88418b348fd944ab2f8e881e57d11b4a1 Mon Sep 17 00:00:00 2001 From: Ryan Nazareth Date: Sat, 28 Dec 2019 05:00:32 +0000 Subject: [PATCH 08/10] Type hints and revert back to catch warnings --- pandas/__init__.py | 6 +++--- pandas/tests/api/test_api.py | 13 ++++--------- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/pandas/__init__.py b/pandas/__init__.py index 158eb83758eeb..6c77da8ff9169 100644 --- a/pandas/__init__.py +++ b/pandas/__init__.py @@ -294,8 +294,8 @@ def __init__(self): def __getattr__(self, item): self.warnings.warn( "The pandas.datetime module is deprecated " - "and will be removed from pandas in a future version." - "Import datetime directly instead.", + "and will be removed from pandas in a future version. " + "Import datetime directly instead", FutureWarning, stacklevel=2, ) @@ -305,7 +305,7 @@ def __getattr__(self, item): except AttributeError: raise AttributeError(f"module datetime has no attribute {item}") - datetime = __Datetime() + datetime = __Datetime().datetime # module level doc-string diff --git a/pandas/tests/api/test_api.py b/pandas/tests/api/test_api.py index a62e235e39c81..9229bde932931 100644 --- a/pandas/tests/api/test_api.py +++ b/pandas/tests/api/test_api.py @@ -101,7 +101,7 @@ class TestPDApi(Base): deprecated_classes_in_future: List[str] = [] # external modules exposed in pandas namespace - modules = [] + modules: List[str] = [] # top-level functions funcs = [ @@ -239,16 +239,11 @@ def test_depr(self): def test_datetime(): from datetime import datetime + import warnings - msg = ( - "The pandas.datetime module is deprecated " - "and will be removed from pandas in a future version. " - "Import datetime directly instead." - ) - - with tm.assert_produces_warning(FutureWarning) as w: + with warnings.catch_warnings(): + warnings.simplefilter("ignore", FutureWarning) assert datetime(2015, 1, 2, 0, 0) == pd.datetime(2015, 1, 2, 0, 0) - assert msg in str(w[-1].message) def test_np(): From ba8cbb09a597ff9ff7fec311a52d620a86f28b4b Mon Sep 17 00:00:00 2001 From: Ryan Nazareth Date: Thu, 2 Jan 2020 00:29:14 +0000 Subject: [PATCH 09/10] few changes to warning messages --- doc/source/whatsnew/v1.0.0.rst | 2 +- pandas/__init__.py | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/doc/source/whatsnew/v1.0.0.rst b/doc/source/whatsnew/v1.0.0.rst index 4703443927d03..411d639a671e9 100755 --- a/doc/source/whatsnew/v1.0.0.rst +++ b/doc/source/whatsnew/v1.0.0.rst @@ -222,7 +222,7 @@ Other enhancements - :meth:`DataFrame.sort_values` and :meth:`Series.sort_values` have gained ``ignore_index`` keyword to be able to reset index after sorting (:issue:`30114`) - :meth:`DataFrame.to_markdown` and :meth:`Series.to_markdown` added (:issue:`11052`) - :meth:`DataFrame.drop_duplicates` has gained ``ignore_index`` keyword to reset index (:issue:`30114`) -- The ``pandas.datetime`` submodule is now deprecated. Import ``datetime`` directly instead(:issue:`30296`) +- The ``pandas.datetime`` class is now deprecated. Import from ``datetime`` instead(:issue:`30296`) Build Changes diff --git a/pandas/__init__.py b/pandas/__init__.py index 6c77da8ff9169..b3f80bee3684f 100644 --- a/pandas/__init__.py +++ b/pandas/__init__.py @@ -210,9 +210,9 @@ class Panel: elif name == "datetime": warnings.warn( - "The pandas.datetime module is deprecated " + "The pandas.datetime class is deprecated " "and will be removed from pandas in a future version. " - "Import datetime directly instead.", + "Import from datetime module instead.", FutureWarning, stacklevel=2, ) @@ -293,15 +293,15 @@ def __init__(self): def __getattr__(self, item): self.warnings.warn( - "The pandas.datetime module is deprecated " + "The pandas.datetime class is deprecated " "and will be removed from pandas in a future version. " - "Import datetime directly instead", + "Import from datetime instead", FutureWarning, stacklevel=2, ) try: - return getattr(self.datetime, item) + return getattr(dt, item) except AttributeError: raise AttributeError(f"module datetime has no attribute {item}") From a133d1b5bf15671c7dbda018c7fa5d76f60e9d80 Mon Sep 17 00:00:00 2001 From: Ryan Nazareth Date: Thu, 2 Jan 2020 01:58:45 +0000 Subject: [PATCH 10/10] add condition in test_depr to fix CI test error --- pandas/__init__.py | 8 ++++---- pandas/tests/api/test_api.py | 3 +++ 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/pandas/__init__.py b/pandas/__init__.py index b3f80bee3684f..84e435cd8033f 100644 --- a/pandas/__init__.py +++ b/pandas/__init__.py @@ -286,13 +286,13 @@ def __getattr__(self, item): class __Datetime: def __init__(self): from datetime import datetime as dt - import warnings self.datetime = dt - self.warnings = warnings def __getattr__(self, item): - self.warnings.warn( + import warnings + + warnings.warn( "The pandas.datetime class is deprecated " "and will be removed from pandas in a future version. " "Import from datetime instead", @@ -301,7 +301,7 @@ def __getattr__(self, item): ) try: - return getattr(dt, item) + return getattr(self.datetime, item) except AttributeError: raise AttributeError(f"module datetime has no attribute {item}") diff --git a/pandas/tests/api/test_api.py b/pandas/tests/api/test_api.py index 9229bde932931..d865f26983579 100644 --- a/pandas/tests/api/test_api.py +++ b/pandas/tests/api/test_api.py @@ -232,6 +232,9 @@ def test_depr(self): with tm.assert_produces_warning(FutureWarning): if compat.PY37: getattr(pd, depr) + elif depr == "datetime": + deprecated = getattr(pd, "__Datetime") + deprecated().__getattr__(dir(pd.datetime)[-1]) else: deprecated = getattr(pd, depr) deprecated.__getattr__(dir(deprecated)[-1])