Repository: spyder-ide/spyder

Issue:
Get long message in Spyder when I use SymPy consoles
## Description

### What steps will reproduce the problem?

<!--- You can use Markdown here --->

Hello!  I used SymPy 1.0 in the Spyder Console and now on every input I get this long message: Output from spyder call 'get_namespace_view':
C:\Users\rwden\AppData\Local\Programs\Spyder\pkgs\sympy\deprecated\class_registry.py:38: SymPyDeprecationWarning: 

C, including its class ClassRegistry, has been deprecated since SymPy
1.0. It will be last supported in SymPy version 1.0. Use direct
imports from the defining module instead. See
https://github.com/sympy/sympy/issues/9371 for more info.

  deprecated_since_version='1.0').warn(stacklevel=2)
  
  
  
How can I prevent this message from appearing every time I do an input?  Thank you.  I am new to Spyder and it is great!



## Versions

* Spyder version: 4.2.3 
* Python version: 3.7.9
* Qt version: 5.12.10
* PyQt5 version: 5.12.3
* Operating System: Windows 10

### Dependencies

```

# Mandatory:
atomicwrites >=1.2.0            :  1.4.0 (OK)
chardet >=2.0.0                 :  4.0.0 (OK)
cloudpickle >=0.5.0             :  1.6.0 (OK)
diff_match_patch >=20181111     :  20200713 (OK)
intervaltree                    :  None (OK)
IPython >=7.6.0                 :  7.21.0 (OK)
jedi =0.17.2                    :  0.17.2 (OK)
jsonschema >=3.2.0              :  3.2.0 (OK)
keyring >=17.0.0                :  22.3.0 (OK)
nbconvert >=4.0                 :  6.0.7 (OK)
numpydoc >=0.6.0                :  1.1.0 (OK)
paramiko >=2.4.0                :  2.7.2 (OK)
parso =0.7.0                    :  0.7.0 (OK)
pexpect >=4.4.0                 :  4.8.0 (OK)
pickleshare >=0.4               :  0.7.5 (OK)
psutil >=5.3                    :  5.8.0 (OK)
pygments >=2.0                  :  2.8.0 (OK)
pylint >=1.0                    :  2.7.2 (OK)
pyls >=0.36.2;<1.0.0            :  0.36.2 (OK)
pyls_black >=0.4.6              :  0.4.6 (OK)
pyls_spyder >=0.3.2             :  0.3.2 (OK)
qdarkstyle >=2.8                :  2.8.1 (OK)
qtawesome >=0.5.7               :  1.0.2 (OK)
qtconsole >=5.0.1               :  5.0.2 (OK)
qtpy >=1.5.0                    :  1.9.0 (OK)
rtree >=0.8.3                   :  0.9.4 (OK)
setuptools >=39.0.0             :  54.0.0 (OK)
sphinx >=0.6.6                  :  3.5.1 (OK)
spyder_kernels >=1.10.2;<1.11.0 :  1.10.2 (OK)
textdistance >=4.2.0            :  4.2.1 (OK)
three_merge >=0.1.1             :  0.1.1 (OK)
watchdog                        :  1.0.2 (OK)
zmq >=17                        :  22.0.3 (OK)

# Optional:
cython >=0.21                   :  0.29.22 (OK)
matplotlib >=2.0.0              :  3.3.4 (OK)
numpy >=1.7                     :  1.19.3 (OK)
pandas >=1.1.1                  :  1.2.3 (OK)
scipy >=0.17.0                  :  1.6.1 (OK)
sympy >=0.7.3                   :  1.7.1 (OK)
```



Discussion:
This will be added to https://github.com/sympy/sympy/wiki/Release-Notes-for-1.8.
If you can't wait for sympy 1.8 you can see if you can follow their instructions in your link
The same question. _"How can I prevent this message from appearing every time I do an input? "_
Hi @bob5280, @Dan-Patterson and @appomsk

Can you guys please open a Sympy console and see if the message persists? You can open one in the hamburger menu of the console > New special console > New Sympy console.

Please let me know and thanks for reporting!
Hi, @steff456 

Yes. When I open Sympy console I see:
```
Python 3.9.2 -- IPython 
Output from spyder call 'get_namespace_view':
C:\Users\yazu\AppData\Local\Programs\Python\Python39\lib\site-packages\sympy\deprecated\class_registry.py:33: SymPyDeprecationWarning: 

C, including its class ClassRegistry, has been deprecated since SymPy
1.0. It will be last supported in SymPy version 1.0. Use direct
imports from the defining module instead. See
https://github.com/sympy/sympy/issues/9371 for more info.

  SymPyDeprecationWarning(
```
And then this message appears after every any command.

My environment:

- Windows 10
- Python 3.9.2 (from python.org)
- Sympy 1.7.1 (installed  with pip)
- Spyder 4.2.3 (installed with windows installer)

`isympy` in windows terminal works without any problem. 

The same behavior is if I change Python interpreter to default Spyder's Python 3.7.9.
This is fixed in SymPy 1.8 which has been released some time ago. If you update to the latest SymPy with
```
pip install -U sympy
```
then you will not see this warning any more.

I'm not sure though given an issue like this what the best way is to deprecate something without warnings showing up from introspective tooling like in ipython.
@oscarbenjamin did you fixed your issue with the upgrade to SymPy 1.8?
I haven't personally noticed this issue (I don't use Spyder) but as a maintainer of SymPy I can tell you that this warning will not be seen any more. The `ClassRegistry` and all associated code including the code that emits the DeprecationWarning in the OP was fully removed from the SymPy codebase in https://github.com/sympy/sympy/pull/20896



Resolving patch:
diff --git a/external-deps/spyder-kernels/spyder_kernels/comms/frontendcomm.py b/external-deps/spyder-kernels/spyder_kernels/comms/frontendcomm.py
--- a/external-deps/spyder-kernels/spyder_kernels/comms/frontendcomm.py
+++ b/external-deps/spyder-kernels/spyder_kernels/comms/frontendcomm.py
@@ -281,9 +281,14 @@ def __init__(self, write, name):
 
     def __call__(self, string):
         """Print warning once."""
-        if not self._warning_shown:
-            self._warning_shown = True
-            self._write(
-                "\nOutput from spyder call "
-                + repr(self._name) + ":\n")
-        return self._write(string)
+        # Don't print DeprecationWarning's because they unnecessarily pollute
+        # the console.
+        # Fixes spyder-ide/spyder#14928
+        # Fixes spyder-ide/spyder-kernels#343
+        if 'DeprecationWarning' not in string:
+            if not self._warning_shown:
+                self._warning_shown = True
+                self._write(
+                    "\nOutput from spyder call "
+                    + repr(self._name) + ":\n")
+            return self._write(string)
diff --git a/external-deps/spyder-kernels/spyder_kernels/customize/spyderpdb.py b/external-deps/spyder-kernels/spyder_kernels/customize/spyderpdb.py
--- a/external-deps/spyder-kernels/spyder_kernels/customize/spyderpdb.py
+++ b/external-deps/spyder-kernels/spyder_kernels/customize/spyderpdb.py
@@ -34,14 +34,20 @@
 logger = logging.getLogger(__name__)
 
 
-def uses_comprehension(code):
-    """Check if given code uses comprehensions."""
+def capture_locals(code):
+    """Check if given code could capture locals."""
     comprehension_statements = (
         ast.ListComp,
         ast.SetComp,
         ast.GeneratorExp,
-        ast.DictComp
+        ast.DictComp,
+        ast.FunctionDef,
+        ast.ClassDef,
+        ast.Lambda
     )
+    if not PY2:
+        comprehension_statements += (
+            ast.AsyncFunctionDef, )
     nodes = ast.walk(ast.parse(code))
     return any(isinstance(node, comprehension_statements) for node in nodes)
 
@@ -192,12 +198,13 @@ def default(self, line):
                 if execute_events:
                      get_ipython().events.trigger('pre_execute')
 
-                 # Mitigates a CPython bug (https://bugs.python.org/issue41918)
-                 # that prevents running comprehensions with the frame locals
-                 # in Pdb.
-                 # See https://bugs.python.org/issue21161 and
-                 # spyder-ide/spyder#13909.
-                if uses_comprehension(line):
+                # Mitigates a CPython bug (https://bugs.python.org/issue41918)
+                # that prevents running comprehensions with the frame locals
+                # in Pdb.
+                # See https://bugs.python.org/issue21161 and
+                # spyder-ide/spyder#13909.
+                # See also spyder-ide/spyder-kernels#345
+                if capture_locals(line):
                     # There are three potential problems with this approach:
                     # 1. If the code access a globals variable that is
                     #    masked by a locals variable, it will get the locals
@@ -212,10 +219,12 @@ def default(self, line):
                     locals_keys = locals.keys()
                     # Don't pass locals, solves spyder-ide/spyder#16790
                     exec(code, fake_globals)
-                    # Avoid mixing locals and globals
+                    # Avoid mixing locals and globals.
+                    # Need a copy as fake_globals might have been saved.
+                    fake_globals_copy = fake_globals.copy()
                     for key in locals_keys:
-                        locals[key] = fake_globals.pop(key, None)
-                    globals.update(fake_globals)
+                        locals[key] = fake_globals_copy.pop(key, None)
+                    globals.update(fake_globals_copy)
                 else:
                     exec(code, globals, locals)
 


Test patch:
diff --git a/external-deps/spyder-kernels/spyder_kernels/console/tests/test_console_kernel.py b/external-deps/spyder-kernels/spyder_kernels/console/tests/test_console_kernel.py
--- a/external-deps/spyder-kernels/spyder_kernels/console/tests/test_console_kernel.py
+++ b/external-deps/spyder-kernels/spyder_kernels/console/tests/test_console_kernel.py
@@ -19,6 +19,7 @@
 from subprocess import Popen, PIPE
 import sys
 import inspect
+from collections import namedtuple
 
 # Test imports
 import ipykernel
@@ -424,6 +425,8 @@ def test_cwd_in_sys_path():
         msg_id = client.execute("import sys; sys_path = sys.path",
                                 user_expressions={'output':'sys_path'})
         reply = client.get_shell_msg(timeout=TIMEOUT)
+        while 'user_expressions' not in reply['content']:
+            reply = client.get_shell_msg(timeout=TIMEOUT)
 
         # Transform value obtained through user_expressions
         user_expressions = reply['content']['user_expressions']
@@ -470,6 +473,8 @@ def f(x):
         # Verify that the `result` variable is defined
         client.inspect('result')
         msg = client.get_shell_msg(timeout=TIMEOUT)
+        while "found" not in msg['content']:
+            msg = client.get_shell_msg(timeout=TIMEOUT)
         content = msg['content']
         assert content['found']
 
@@ -512,6 +517,8 @@ def test_dask_multiprocessing(tmpdir):
         # Verify that the `x` variable is defined
         client.inspect('x')
         msg = client.get_shell_msg(timeout=TIMEOUT)
+        while "found" not in msg['content']:
+            msg = client.get_shell_msg(timeout=TIMEOUT)
         content = msg['content']
         assert content['found']
 
@@ -552,6 +559,8 @@ def test_runfile(tmpdir):
         # Verify that `result` is defined in the current namespace
         client.inspect('result')
         msg = client.get_shell_msg(timeout=TIMEOUT)
+        while "found" not in msg['content']:
+            msg = client.get_shell_msg(timeout=TIMEOUT)
         content = msg['content']
         assert content['found']
 
@@ -563,6 +572,8 @@ def test_runfile(tmpdir):
         # Verify that the variable `result2` is defined
         client.inspect('result2')
         msg = client.get_shell_msg(timeout=TIMEOUT)
+        while "found" not in msg['content']:
+            msg = client.get_shell_msg(timeout=TIMEOUT)
         content = msg['content']
         assert content['found']
 
@@ -575,12 +586,16 @@ def test_runfile(tmpdir):
         # Verify that the variable `result3` is defined
         client.inspect('result3')
         msg = client.get_shell_msg(timeout=TIMEOUT)
+        while "found" not in msg['content']:
+            msg = client.get_shell_msg(timeout=TIMEOUT)
         content = msg['content']
         assert content['found']
 
         # Verify that the variable `__file__` is undefined
         client.inspect('__file__')
         msg = client.get_shell_msg(timeout=TIMEOUT)
+        while "found" not in msg['content']:
+            msg = client.get_shell_msg(timeout=TIMEOUT)
         content = msg['content']
         assert not content['found']
 
@@ -622,24 +637,32 @@ def test_np_threshold(kernel):
         # Check correct decimal format
         client.inspect('a')
         msg = client.get_shell_msg(timeout=TIMEOUT)
+        while "data" not in msg['content']:
+            msg = client.get_shell_msg(timeout=TIMEOUT)
         content = msg['content']['data']['text/plain']
         assert "123412341234.12" in content
 
         # Check threshold value
         client.inspect('t')
         msg = client.get_shell_msg(timeout=TIMEOUT)
+        while "data" not in msg['content']:
+            msg = client.get_shell_msg(timeout=TIMEOUT)
         content = msg['content']['data']['text/plain']
         assert "inf" in content
 
         # Check suppress value
         client.inspect('s')
         msg = client.get_shell_msg(timeout=TIMEOUT)
+        while "data" not in msg['content']:
+            msg = client.get_shell_msg(timeout=TIMEOUT)
         content = msg['content']['data']['text/plain']
         assert "True" in content
 
         # Check formatter
         client.inspect('f')
         msg = client.get_shell_msg(timeout=TIMEOUT)
+        while "data" not in msg['content']:
+            msg = client.get_shell_msg(timeout=TIMEOUT)
         content = msg['content']['data']['text/plain']
         assert "{'float_kind': <built-in method format of str object" in content
 
@@ -697,6 +720,8 @@ def test_turtle_launch(tmpdir):
         # Verify that the `tess` variable is defined
         client.inspect('tess')
         msg = client.get_shell_msg(timeout=TIMEOUT)
+        while "found" not in msg['content']:
+            msg = client.get_shell_msg(timeout=TIMEOUT)
         content = msg['content']
         assert content['found']
 
@@ -713,6 +738,8 @@ def test_turtle_launch(tmpdir):
         # Verify that the `a` variable is defined
         client.inspect('a')
         msg = client.get_shell_msg(timeout=TIMEOUT)
+        while "found" not in msg['content']:
+            msg = client.get_shell_msg(timeout=TIMEOUT)
         content = msg['content']
         assert content['found']
 
@@ -728,6 +755,8 @@ def test_matplotlib_inline(kernel):
         code = "import matplotlib; backend = matplotlib.get_backend()"
         client.execute(code, user_expressions={'output': 'backend'})
         reply = client.get_shell_msg(timeout=TIMEOUT)
+        while 'user_expressions' not in reply['content']:
+            reply = client.get_shell_msg(timeout=TIMEOUT)
 
         # Transform value obtained through user_expressions
         user_expressions = reply['content']['user_expressions']
@@ -903,5 +932,75 @@ def error_wrapper(*args, **kwargs):
     pdb_obj.curframe_locals = None
 
 
+def test_functions_with_locals_in_pdb(kernel):
+    """
+    Test that functions with locals work in Pdb.
+
+    This is a regression test for spyder-ide/spyder-kernels#345
+    """
+    pdb_obj = SpyderPdb()
+    Frame = namedtuple("Frame", ["f_globals"])
+    pdb_obj.curframe = Frame(f_globals=kernel.shell.user_ns)
+    pdb_obj.curframe_locals = kernel.shell.user_ns
+    kernel.shell.pdb_session = pdb_obj
+
+    # Create a local function.
+    kernel.shell.pdb_session.default(
+        'def fun_a(): return [i for i in range(1)]')
+    kernel.shell.pdb_session.default(
+        'zz = fun_a()')
+    assert kernel.get_value('zz') == [0]
+
+    kernel.shell.pdb_session.default(
+        'a = 1')
+    kernel.shell.pdb_session.default(
+        'def fun_a(): return a')
+    kernel.shell.pdb_session.default(
+        'zz = fun_a()')
+    assert kernel.get_value('zz') == 1
+
+
+    pdb_obj.curframe = None
+    pdb_obj.curframe_locals = None
+
+def test_functions_with_locals_in_pdb_2(kernel):
+    """
+    Test that functions with locals work in Pdb.
+
+    This is another regression test for spyder-ide/spyder-kernels#345
+    """
+    baba = 1
+    pdb_obj = SpyderPdb()
+    pdb_obj.curframe = inspect.currentframe()
+    pdb_obj.curframe_locals = pdb_obj.curframe.f_locals
+    kernel.shell.pdb_session = pdb_obj
+
+    # Create a local function.
+    kernel.shell.pdb_session.default(
+        'def fun_a(): return [i for i in range(1)]')
+    kernel.shell.pdb_session.default(
+        'zz = fun_a()')
+    assert kernel.get_value('zz') == [0]
+
+    kernel.shell.pdb_session.default(
+        'a = 1')
+    kernel.shell.pdb_session.default(
+        'def fun_a(): return a')
+    kernel.shell.pdb_session.default(
+        'zz = fun_a()')
+    assert kernel.get_value('zz') == 1
+
+    # Check baba is in locals and not globals
+    kernel.shell.pdb_session.default(
+        'll = locals().keys()')
+    assert "baba" in kernel.get_value('ll')
+    kernel.shell.pdb_session.default(
+        'gg = globals().keys()')
+    assert "baba" not in kernel.get_value('gg')
+
+    pdb_obj.curframe = None
+    pdb_obj.curframe_locals = None
+
+
 if __name__ == "__main__":
     pytest.main()