Skip to content

Commit 01b8eaa

Browse files
committed
Attempt to work around bug PY-40661 in PyCharm
Fixes #5
1 parent 9004753 commit 01b8eaa

File tree

3 files changed

+130
-8
lines changed

3 files changed

+130
-8
lines changed

pyxll_pycharm/__init__.py

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
Requires:
77
- PyXLL >= 5.0.0
88
- PyCharm Professional
9+
- Python >= 3.7
910
1011
To install this package use::
1112
@@ -16,7 +17,9 @@
1617
[PYCHARM]
1718
port = 5000
1819
suspend = 0
20+
fix_pydevd_import = 1
1921
"""
22+
from ._import_pydevd import _import_pydevd
2023
from pyxll import get_config
2124
import pkg_resources
2225
import ctypes
@@ -25,23 +28,21 @@
2528

2629
_log = logging.getLogger(__name__)
2730

31+
2832
_MB_YESNO = 0x04
2933
_MB_OK = 0x0
3034
_IDYES = 0x6
3135

3236

37+
#
3338
def connect_to_pycharm(*args):
3439
"""Connect to the remote PyCharm debugger."""
35-
# Defer importing pydevd until it's actually needed as it will conflict with using
36-
# other debuggers such as VS Code.
37-
import pydevd_pycharm
38-
import pydevd
39-
4040
# Get the settings from the config
4141
port = 5000
4242
suspend = False
4343
stdout_to_server = True
4444
stderr_to_server = True
45+
fix_pydevd_import = True
4546

4647
cfg = get_config()
4748
if cfg.has_option("PYCHARM", "port"):
@@ -68,6 +69,16 @@ def connect_to_pycharm(*args):
6869
except (ValueError, TypeError):
6970
_log.error("Unexpected value for PYCHARM.stderr_to_server.")
7071

72+
if cfg.has_option("PYCHARM", "fix_pydevd_import"):
73+
try:
74+
fix_pydevd_import = bool(int(cfg.get("PYCHARM", "fix_pydevd_import")))
75+
except (ValueError, TypeError):
76+
_log.error("Unexpected value for PYCHARM.fix_pydevd_import.")
77+
78+
# Import pydevd_pycharm and pydevd, working around issues with multiple versions
79+
# of pydevd being installed (e.g. when using ipython and debugpy).
80+
pydevd_pycharm, pydevd = _import_pydevd(fix_pydevd_import)
81+
7182
# If the debugger is not already running ask the user if they have started the debug server
7283
if not pydevd.connected:
7384
result = ctypes.windll.user32.MessageBoxA(

pyxll_pycharm/_import_pydevd.py

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
from os.path import expandvars
2+
from itertools import chain
3+
from glob import glob
4+
from pathlib import Path
5+
from functools import lru_cache
6+
import contextlib
7+
import logging
8+
import sys
9+
10+
_log = logging.getLogger(__name__)
11+
12+
13+
@contextlib.contextmanager
14+
def _restore_sys_modules():
15+
# Take a copy of sys.modules and sys.path
16+
sys_modules = dict(sys.modules)
17+
sys_path = list(sys.path)
18+
19+
yield
20+
21+
# Restore sys.modules and sys.path
22+
sys.modules.clear()
23+
sys.modules.update(sys_modules)
24+
sys.path.clear()
25+
sys.path.extend(sys_path)
26+
27+
28+
def _path_is_relative_to(a, b):
29+
# Path.is_relative_to is new in Python 3.9
30+
try:
31+
Path(a).relative_to(b)
32+
return Path(a).relative_to(b)
33+
except ValueError:
34+
return False
35+
36+
37+
@lru_cache(maxsize=2)
38+
def _import_pydevd(apply_fix=True):
39+
"""Imports pydevd from the pydevd_pycharm package but leaves sys.modules as if
40+
this function was never called.
41+
This works around issues with debugpy also being installed.
42+
See https://youtrack.jetbrains.com/issue/PY-40661/pydevd-pycharm-conflicts-with-pydevd-package.
43+
"""
44+
# If apply_fix is False then simply import and return the modules
45+
if not apply_fix:
46+
import pydevd_pycharm
47+
import pydevd
48+
49+
pydevd_pycharm_dir = Path(pydevd_pycharm.__file__).parent
50+
pydevd_dir = Path(pydevd.__file__).parent
51+
if pydevd_pycharm_dir != pydevd_dir:
52+
_log.warning("Potential pydevd version conflict found. " +
53+
f"Try uninstalling pydevd from '{pydevd_dir}' and reinstall pydevd_pycharm.")
54+
55+
return pydevd_pycharm, pydevd
56+
57+
# Restore sys.modules and sys.path once we're done
58+
with _restore_sys_modules():
59+
try:
60+
# Try importing pydevd_pycharm from the default sys.path first
61+
import pydevd_pycharm
62+
except ImportError:
63+
# If that fails look for PyCharm and add it to sys.path
64+
_log.debug("pydevd_pycharm not found on sys.path. Looking for PyCharm install...")
65+
pydevd_eggs = list(reversed(sorted(chain.from_iterable(
66+
glob(expandvars(f"{env_var}\\JetBrains\\PyCharm*\\debug-eggs\\pydevd-pycharm.egg"), recursive=True)
67+
for env_var in ("${ProgramFiles}", "${ProgramFiles(x86)}")))))
68+
69+
if pydevd_eggs:
70+
pydevd_egg = pydevd_eggs[0]
71+
_log.debug(f"Found pydevd-pycharm in PyCharm install: {pydevd_egg}")
72+
sys.path.insert(0, pydevd_egg)
73+
74+
# Try to import pydevd_pycharm again (sys.path may have changed)
75+
import pydevd_pycharm
76+
77+
# Next import pydev
78+
import pydevd
79+
80+
# pydevd should be distributed as part of pydevd_pycharm
81+
pydevd_pycharm_dir = Path(pydevd_pycharm.__file__).parent
82+
pydevd_dir = Path(pydevd.__file__).parent
83+
if pydevd_pycharm_dir != pydevd_dir:
84+
_log.debug(f"Incompatible version of pydevd found: {pydevd.__file__}")
85+
86+
# Remove all the existing pydevd modules from sys.modules
87+
pydev_modules = set()
88+
for name, module in sys.modules.items():
89+
if ((name.startswith("pydev") or name.startswith("_pydev"))
90+
and (_path_is_relative_to(module.__file__, pydevd_dir) or
91+
_path_is_relative_to(module.__file__, pydevd_pycharm_dir))):
92+
pydev_modules.add(name)
93+
94+
for name in pydev_modules:
95+
del sys.modules[name]
96+
97+
# Re-import pydevd_pycharm and pydevd with the PyCharm path appearing first on sys.path
98+
sys.path.insert(0, str(pydevd_pycharm_dir))
99+
import pydevd_pycharm
100+
import pydevd
101+
102+
# Check what we've imported is now correct
103+
pydevd_pycharm_dir = Path(pydevd_pycharm.__file__).parent
104+
pydevd_dir = Path(pydevd.__file__).parent
105+
if pydevd_pycharm_dir != pydevd_dir:
106+
_log.warning("Potential pydevd version conflict found. " +
107+
f"Try uninstalling pydevd from '{pydevd_dir}' and reinstall pydevd_pycharm.")
108+
109+
return pydevd_pycharm, pydevd

setup.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
PyCharm debugging support for PyXLL.
55
66
Requires:
7+
- Python >= 3.7
78
- PyXLL >= 5.0.0
89
- PyCharm Professional
910
@@ -31,7 +32,7 @@
3132
description="Adds PyCharm debugging support to PyXLL.",
3233
long_description=long_description,
3334
long_description_content_type='text/markdown',
34-
version="0.2.1",
35+
version="0.3.0",
3536
packages=find_packages(),
3637
include_package_data=True,
3738
package_data={
@@ -45,7 +46,7 @@
4546
"Tracker": "https://github.com/pyxll/pyxll-pycharm/issues",
4647
},
4748
classifiers=[
48-
"Programming Language :: Python",
49+
"Programming Language :: Python :: 3 :: Only",
4950
"License :: OSI Approved :: MIT License",
5051
"Operating System :: Microsoft :: Windows"
5152
],
@@ -58,5 +59,6 @@
5859
install_requires=[
5960
"pyxll >= 5.0.0",
6061
"pydevd-pycharm"
61-
]
62+
],
63+
python_requires=">=3.7"
6264
)

0 commit comments

Comments
 (0)