Skip to content

Commit

Permalink
Merge pull request #495 from ccordoba12/add-envinfo-handler
Browse files Browse the repository at this point in the history
PR: Add comm handler to get information about the Python environment associated to the kernel
  • Loading branch information
ccordoba12 authored Aug 19, 2024
2 parents c0c785c + c09aa62 commit e8a4a85
Show file tree
Hide file tree
Showing 9 changed files with 270 additions and 28 deletions.
7 changes: 5 additions & 2 deletions .github/workflows/linux-pip-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ concurrency:

jobs:
linux:
name: Py${{ matrix.PYTHON_VERSION }}
name: Linux (pip) - Py${{ matrix.PYTHON_VERSION }}
runs-on: ubuntu-latest
env:
CI: True
Expand All @@ -34,7 +34,7 @@ jobs:
- name: Install System Packages
run: |
sudo apt-get update
sudo apt-get install libegl1-mesa
sudo apt-get install libegl1-mesa libopengl0
- name: Install Conda
uses: conda-incubator/setup-miniconda@v2
with:
Expand All @@ -51,6 +51,9 @@ jobs:
run: |
conda info
conda list
# - name: Setup Remote SSH Connection
# uses: mxschmitt/action-tmate@v3
# timeout-minutes: 60
- name: Run tests
shell: bash -l {0}
run: |
Expand Down
7 changes: 5 additions & 2 deletions .github/workflows/linux-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ concurrency:

jobs:
linux:
name: Py${{ matrix.PYTHON_VERSION }}
name: Linux - Py${{ matrix.PYTHON_VERSION }}
runs-on: ubuntu-latest
env:
CI: True
Expand All @@ -34,7 +34,7 @@ jobs:
- name: Install System Packages
run: |
sudo apt-get update
sudo apt-get install libegl1-mesa
sudo apt-get install libegl1-mesa libopengl0
- name: Install Conda
uses: conda-incubator/setup-miniconda@v2
with:
Expand All @@ -59,6 +59,9 @@ jobs:
run: |
conda info
conda list
# - name: Setup Remote SSH Connection
# uses: mxschmitt/action-tmate@v3
# timeout-minutes: 60
- name: Run tests
shell: bash -l {0}
run: |
Expand Down
5 changes: 4 additions & 1 deletion .github/workflows/macos-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ concurrency:

jobs:
macos:
name: Py${{ matrix.PYTHON_VERSION }}
name: macOS - Py${{ matrix.PYTHON_VERSION }}
runs-on: macos-latest
env:
CI: True
Expand Down Expand Up @@ -55,6 +55,9 @@ jobs:
run: |
conda info
conda list
# - name: Setup Remote SSH Connection
# uses: mxschmitt/action-tmate@v3
# timeout-minutes: 60
- name: Run tests
shell: bash -l {0}
run: |
Expand Down
5 changes: 4 additions & 1 deletion .github/workflows/windows-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ concurrency:

jobs:
windows:
name: Py${{ matrix.PYTHON_VERSION }}
name: Windows - Py${{ matrix.PYTHON_VERSION }}
runs-on: windows-latest
env:
CI: True
Expand Down Expand Up @@ -55,6 +55,9 @@ jobs:
run: |
conda info
conda list
# - name: Setup Remote SSH Connection
# uses: mxschmitt/action-tmate@v3
# timeout-minutes: 60
- name: Run tests
shell: bash -l {0}
run: |
Expand Down
5 changes: 4 additions & 1 deletion spyder_kernels/comms/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,10 @@ def is_benign_message(self, message):
"Warning: Cannot change to a different GUI toolkit",
"%pylab is deprecated",
"Populating the interactive namespace",
"\n"
"\n",
# Fixes spyder-ide/spyder#21652
"WARNING",
"Active device does not have an attribute",
]

return any([msg in message for msg in benign_messages])
Expand Down
40 changes: 40 additions & 0 deletions spyder_kernels/console/kernel.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
# Third-party imports
from ipykernel.ipkernel import IPythonKernel
from ipykernel import get_connection_info
from IPython.core import release as ipython_release
from traitlets.config.loader import Config, LazyConfigValue
import zmq
from zmq.utils.garbage import gc
Expand All @@ -35,6 +36,13 @@
from spyder_kernels.comms.frontendcomm import FrontendComm
from spyder_kernels.comms.decorators import (
register_comm_handlers, comm_handler)
from spyder_kernels.utils.pythonenv import (
get_env_dir,
is_conda_env,
is_pyenv_env,
PythonEnvInfo,
PythonEnvType,
)
from spyder_kernels.utils.iofuncs import iofunctions
from spyder_kernels.utils.mpl import automatic_backend, MPL_BACKENDS_TO_SPYDER
from spyder_kernels.utils.nsview import (
Expand Down Expand Up @@ -81,6 +89,9 @@ def __init__(self, *args, **kwargs):
# To track the interactive backend
self.interactive_backend = None

# To save the python env info
self.pythonenv_info: PythonEnvInfo = {}

@property
def kernel_info(self):
# Used for checking correct version by spyder
Expand Down Expand Up @@ -756,6 +767,35 @@ def update_syspath(self, path_dict, new_path_dict):
else:
os.environ.pop('PYTHONPATH', None)

@comm_handler
def get_pythonenv_info(self):
"""Get the Python env info in which this kernel is installed."""
# We only need to compute this once
if not self.pythonenv_info:
path = sys.executable.replace("pythonw.exe", "python.exe")

if is_conda_env(pyexec=path):
env_type = PythonEnvType.Conda
elif is_pyenv_env(path):
env_type = PythonEnvType.PyEnv
else:
env_type = PythonEnvType.Custom

self.pythonenv_info = PythonEnvInfo(
path=path,
env_type=env_type,
name=get_env_dir(path, only_dir=True),
python_version=".".join(
[str(n) for n in sys.version_info[:3]]
),
# These keys are necessary to build the console banner in
# Spyder
ipython_version=ipython_release.version,
sys_version=sys.version,
)

return self.pythonenv_info

# -- Private API ---------------------------------------------------
# --- For the Variable Explorer
def _get_len(self, var):
Expand Down
65 changes: 44 additions & 21 deletions spyder_kernels/console/tests/test_console_kernel.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,19 @@
from collections import namedtuple

# Test imports
import pytest
from flaky import flaky
from IPython.core import release as ipython_release
from jupyter_core import paths
from jupyter_client import BlockingKernelClient
import numpy as np
import pytest

# Local imports
from spyder_kernels.comms.commbase import CommBase
from spyder_kernels.customize.spyderpdb import SpyderPdb
from spyder_kernels.utils.iofuncs import iofunctions
from spyder_kernels.utils.pythonenv import PythonEnvType
from spyder_kernels.utils.test_utils import get_kernel, get_log_text
from spyder_kernels.customize.spyderpdb import SpyderPdb
from spyder_kernels.comms.commbase import CommBase

# =============================================================================
# Constants and utility functions
Expand Down Expand Up @@ -277,7 +279,7 @@ def test_get_namespace_view(kernel):
"""
Test the namespace view of the kernel.
"""
execute = asyncio.run(kernel.do_execute('a = 1', True))
asyncio.run(kernel.do_execute('a = 1', True))

nsview = repr(kernel.get_namespace_view())
assert "'a':" in nsview
Expand All @@ -293,7 +295,7 @@ def test_get_namespace_view_filter_on(kernel, filter_on):
"""
Test the namespace view of the kernel with filters on and off.
"""
execute = asyncio.run(kernel.do_execute('a = 1', True))
asyncio.run(kernel.do_execute('a = 1', True))
asyncio.run(kernel.do_execute('TestFilterOff = 1', True))

settings = kernel.namespace_view_settings
Expand Down Expand Up @@ -985,7 +987,7 @@ def test_namespaces_in_pdb(kernel):
Test namespaces in pdb
"""
# Define get_ipython for timeit
get_ipython = lambda: kernel.shell
get_ipython = lambda: kernel.shell # noqa
kernel.shell.user_ns["test"] = 0
pdb_obj = SpyderPdb()
pdb_obj.curframe = inspect.currentframe()
Expand Down Expand Up @@ -1061,7 +1063,7 @@ def test_functions_with_locals_in_pdb_2(kernel):
This is another regression test for spyder-ide/spyder-kernels#345
"""
baba = 1
baba = 1 # noqa
pdb_obj = SpyderPdb()
pdb_obj.curframe = inspect.currentframe()
pdb_obj.curframe_locals = pdb_obj.curframe.f_locals
Expand Down Expand Up @@ -1098,7 +1100,7 @@ def test_locals_globals_in_pdb(kernel):
"""
Test thal locals and globals work properly in Pdb.
"""
a = 1
a = 1 # noqa
pdb_obj = SpyderPdb()
pdb_obj.curframe = inspect.currentframe()
pdb_obj.curframe_locals = pdb_obj.curframe.f_locals
Expand Down Expand Up @@ -1140,10 +1142,8 @@ def test_locals_globals_in_pdb(kernel):
@pytest.mark.parametrize("backend", [None, 'inline', 'tk', 'qt'])
@pytest.mark.skipif(
os.environ.get('USE_CONDA') != 'true',
reason="Doesn't work with pip packages")
@pytest.mark.skipif(
sys.version_info[:2] < (3, 9),
reason="Too flaky in Python 3.8 and doesn't work in older versions")
reason="Doesn't work with pip packages"
)
def test_get_interactive_backend(backend):
"""
Test that we correctly get the interactive backend set in the kernel.
Expand All @@ -1157,14 +1157,17 @@ def test_get_interactive_backend(backend):
# Set backend
if backend is not None:
client.execute_interactive(
"%matplotlib {}".format(backend), timeout=TIMEOUT)
"%matplotlib {}".format(backend), timeout=TIMEOUT
)
client.execute_interactive(
"import time; time.sleep(.1)", timeout=TIMEOUT)
"import time; time.sleep(.1)", timeout=TIMEOUT
)

# Get backend
code = "backend = get_ipython().kernel.get_mpl_interactive_backend()"
reply = client.execute_interactive(
code, user_expressions={'output': 'backend'}, timeout=TIMEOUT)
code, user_expressions={'output': 'backend'}, timeout=TIMEOUT
)

# Get value obtained through user_expressions
user_expressions = reply['content']['user_expressions']
Expand Down Expand Up @@ -1239,7 +1242,7 @@ def test_debug_namespace(tmpdir):
d.write('def func():\n bb = "hello"\n breakpoint()\nfunc()')

# Run code file `d`
msg_id = client.execute("%runfile {}".format(repr(str(d))))
client.execute("%runfile {}".format(repr(str(d))))

# make sure that 'bb' returns 'hello'
client.get_stdin_msg(timeout=TIMEOUT)
Expand Down Expand Up @@ -1370,8 +1373,7 @@ def test_non_strings_in_locals(kernel):
This is a regression test for issue spyder-ide/spyder#19145
"""
execute = asyncio.run(kernel.do_execute('locals().update({1:2})', True))

asyncio.run(kernel.do_execute('locals().update({1:2})', True))
nsview = repr(kernel.get_namespace_view())
assert "1:" in nsview

Expand All @@ -1382,9 +1384,7 @@ def test_django_settings(kernel):
This is a regression test for issue spyder-ide/spyder#19516
"""
execute = asyncio.run(kernel.do_execute(
'from django.conf import settings', True))

asyncio.run(kernel.do_execute('from django.conf import settings', True))
nsview = repr(kernel.get_namespace_view())
assert "'settings':" in nsview

Expand All @@ -1410,5 +1410,28 @@ def test_hard_link_pdb(tmpdir):
assert pdb_obj.canonic(str(d)) == pdb_obj.canonic(str(hard_link))


@pytest.mark.skipif(not os.environ.get('CI'), reason="Only works on CIs")
def test_get_pythonenv_info(kernel):
"""Test the output we get from this method."""
output = kernel.get_pythonenv_info()
assert output["path"] == sys.executable

if os.environ.get('USE_CONDA'):
assert output["name"] == "test"
assert output["env_type"] == PythonEnvType.Conda
else:
assert output["env_type"] in [
# This Custom here accounts for Linux packagers that run our tests
# in their CIs
PythonEnvType.Custom,
PythonEnvType.Conda,
]

# Check these keys are present. Otherwise we'll break Spyder.
assert output["python_version"] == sys.version.split()[0]
assert output["ipython_version"] == ipython_release.version
assert output["sys_version"] == sys.version


if __name__ == "__main__":
pytest.main()
Loading

0 comments on commit e8a4a85

Please sign in to comment.