Repository: pandas-dev/pandas
Pull request: Compat for util.testing import
Closes #30869

Problem statement:
pandas.util.testing no longer imported with pandas
0.25.3

```python
In [1]: import pandas

In [2]: pandas.util.testing
Out[2]: <module 'pandas.util.testing' from '/private/tmp/pandas/util/testing.py'>
```

1.0

```python
In [1]: import pandas

In [2]: pandas.util.testing
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-2-968e027d039f> in <module>
----> 1 pandas.util.testing

AttributeError: module 'pandas.util' has no attribute 'testing'
```

Hints from issue discussion:
hmm it i don’t think is a problem
and wouldn’t do anything about it
It's perfect legitimate usage pattern of util.testing, and it's also easy to fix (we can still import it while hiding the warning)
> It's perfect legitimate usage pattern of util.testing, and it's also easy to fix (we can still import it while hiding the warning)

missing the point - why should be supporting a non explicit import? i would simply break this


> why should be supporting a non explicit import?

Because we have always supported this?
> > why should be supporting a non explicit import?
> 
> Because we have always supported this?

i disagree

it doesn’t matter if it’s easy


> >  Because we have always supported this?
>
> i disagree

I am assuming that your "I disagree" is about this being a good reason to keep supporting it? Because AFAIK, it's a *fact* that we have always supported this.
> > > Because we have always supported this?
> > 
> > 
> > i disagree
> 
> I am assuming that your "I disagree" is about this being a good reason to keep supporting it? Because AFAIK, it's a _fact_ that we have always supported this.

my point is that @TomAugspurger did enough on the current warning for an explicit import 

an implicit import is not a reason to even have a depreciation 

so -1 for fixing this and a hard break is fine
I don’t think it’ll be hard. Similar to the np deprecation. 

> On Jan 10, 2020, at 04:57, Jeff Reback <notifications@github.com> wrote:
> 
> ﻿
> Because we have always supported this?
> 
> i disagree
> 
> I am assuming that your "I disagree" is about this being a good reason to keep supporting it? Because AFAIK, it's a fact that we have always supported this.
> 
> my point is that @TomAugspurger did enough on the current warning for an explicit import
> 
> an implicit import is not a reason to even have a depreciation
> 
> so -1 for fixing this and a hard break is fine
> 
> —
> You are receiving this because you were mentioned.
> Reply to this email directly, view it on GitHub, or unsubscribe.

Commit message: toplevel

Patch to review:
diff --git a/pandas/tests/api/test_api.py b/pandas/tests/api/test_api.py
index 8b897524cb053..d9fd157e39f86 100644
--- a/pandas/tests/api/test_api.py
+++ b/pandas/tests/api/test_api.py
@@ -1,6 +1,9 @@
+import subprocess
 import sys
 from typing import List
 
+import pytest
+
 import pandas as pd
 from pandas import api, compat
 import pandas._testing as tm
@@ -311,3 +314,14 @@ def test_util_testing_deprecated_direct(self):
 
         assert "pandas.util.testing is deprecated" in str(m[0].message)
         assert "pandas.testing instead" in str(m[0].message)
+
+    def test_util_in_top_level(self):
+        # in a subprocess to avoid import caching issues
+        out = subprocess.check_output(
+            ["python", "-c", "import pandas; pandas.util.testing"],
+            stderr=subprocess.STDOUT,
+        ).decode()
+        assert "pandas.util.testing is deprecated" in out
+
+        with pytest.raises(AttributeError, match="foo"):
+            pd.util.foo
diff --git a/pandas/util/__init__.py b/pandas/util/__init__.py
index d906c0371d207..fc993eb164f3b 100644
--- a/pandas/util/__init__.py
+++ b/pandas/util/__init__.py
@@ -1,3 +1,28 @@
 from pandas.util._decorators import Appender, Substitution, cache_readonly  # noqa
 
+from pandas import compat
 from pandas.core.util.hashing import hash_array, hash_pandas_object  # noqa
+
+# compatibility for import pandas; pandas.util.testing
+
+if compat.PY37:
+
+    def __getattr__(name):
+        if name == "testing":
+            import pandas.util.testing  # noqa: F401
+        else:
+            raise AttributeError(f"module 'pandas.util' has no attribute '{name}'")
+
+
+else:
+
+    class _testing:
+        def __getattr__(self, item):
+            import pandas.util.testing
+
+            return getattr(pandas.util.testing, item)
+
+    testing = _testing()
+
+
+del compat


Code review comments:
diff_hunk: @@ -311,3 +314,14 @@ def test_util_testing_deprecated_direct(self):
 
         assert "pandas.util.testing is deprecated" in str(m[0].message)
         assert "pandas.testing instead" in str(m[0].message)
+
+    def test_util_in_top_level(self):
+        # in a subprocess to avoid import caching issues
+        out = subprocess.check_output(
+            ["python", "-c", "import pandas; pandas.util.testing"],
path: pandas/tests/api/test_api.py
text: @user1:
i think we usually use sys.exe instead of "python" for these