Skip to content

Commit a9de2e3

Browse files
committed
Remove debugpy calls from initialization methods and enhance debugpy configuration in relevant classes
1 parent 1f636ca commit a9de2e3

File tree

8 files changed

+110
-101
lines changed

8 files changed

+110
-101
lines changed

src/iop/_business_operation.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@ def _set_iris_handles(self, handle_current: Any, handle_partner: Any) -> None:
4747

4848
def _dispatch_on_init(self, host_object: Any) -> None:
4949
"""For internal use only."""
50-
self._debugpy(host_object=host_object)
5150
create_dispatch(self)
5251
self.on_init()
5352
return

src/iop/_business_process.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,6 @@ def _dispatch_on_connected(self, host_object: Any) -> None:
141141
def _dispatch_on_init(self, host_object: Any) -> None:
142142
"""For internal use only."""
143143
self._restore_persistent_properties(host_object)
144-
self._debugpy(host_object=host_object)
145144
create_dispatch(self)
146145
self.on_init()
147146
self._save_persistent_properties(host_object)

src/iop/_business_service.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,6 @@ class _BusinessService(_BusinessHost):
1818

1919
def _dispatch_on_init(self, host_object) -> None:
2020
"""For internal use only."""
21-
22-
self._debugpy(host_object=host_object)
23-
2421
self.on_init()
2522

2623
return

src/iop/_common.py

Lines changed: 7 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88

99
from iop._log_manager import LogManager, logging
1010

11+
from iop._debugpy import debugpython
12+
1113
class _Common(metaclass=abc.ABCMeta):
1214
"""Base class that defines common methods for all component types.
1315
@@ -59,85 +61,20 @@ def _dispatch_on_connected(self, host_object: Any) -> None:
5961

6062
def _dispatch_on_init(self, host_object: Any) -> None:
6163
"""Initialize component when started."""
62-
self._debugpy(host_object)
6364
self.on_init()
6465

65-
def _debugpy(self, host_object: Any) -> None:
66-
"""Enable debugpy for debugging purposes."""
67-
# iris.cls('%SYS.Python').Debugging(1)
68-
from iop._debugpy import enable_debugpy, wait_for_debugpy_connected, find_free_port, is_debugpy_installed
69-
import sys, os
70-
71-
# hack to set __file__ for os module in debugpy
72-
# This is a workaround for the issue where debugpy cannot find the __file__ attribute of the os module.
73-
if not hasattr(os, '__file__'):
74-
setattr(os, '__file__', __file__)
75-
76-
if host_object is not None:
77-
port = host_object.port
78-
timeout = host_object.timeout
79-
enabled = host_object.enable
80-
python_interpreter_path = host_object.PythonInterpreterPath
81-
else:
82-
self.log_alert("No host object found, cannot enable debugpy.")
83-
return
84-
85-
if python_interpreter_path != "":
86-
# the user has set a specific python interpreter path, so we need to set the path to the python executable
87-
# to the one that is running this script
88-
if os.path.exists(python_interpreter_path):
89-
sys.executable = python_interpreter_path
90-
else:
91-
self.log_alert(f"Python path {python_interpreter_path} does not exist, cannot set python path for debugpy.")
92-
return
93-
elif sys.executable.find('irisdb') > 0:
94-
# the executable is the IRIS executable, so we need to set the path to the python executable
95-
# to the one that is running this script
96-
installdir = os.environ.get('IRISINSTALLDIR') or os.environ.get('ISC_PACKAGE_INSTALLDIR')
97-
if installdir is not None:
98-
if sys.platform == 'win32':
99-
python_path = os.path.join(installdir, 'bin', 'irispython.exe')
100-
else:
101-
python_path = os.path.join(installdir, 'bin', 'irispython')
102-
if os.path.exists(python_path):
103-
sys.executable = python_path
104-
else:
105-
self.log_alert(f"Python path {python_path} does not exist, cannot set python path for debugpy.")
106-
return
107-
else:
108-
self.log_alert("IRISINSTALLDIR or ISC_PACKAGE_INSTALLDIR not set, cannot set python path for debugpy.")
109-
return
110-
111-
if enabled:
112-
self.log_info(f"Debugpy is running in {sys.executable}.")
113-
if is_debugpy_installed():
114-
if port is None or port <= 0:
115-
port = find_free_port()
116-
self.log_info(f"Debugpy enabled.")
117-
try:
118-
enable_debugpy(port=port, address=None)
119-
except Exception as e:
120-
self.log_alert(f"Error enabling debugpy: {e}")
121-
return
122-
123-
self.trace(f"Waiting for {timeout} sec to debugpy connection on port {port}...")
124-
if wait_for_debugpy_connected(timeout=timeout, port=port):
125-
self.log_info("Debugpy connected.")
126-
else:
127-
self.log_alert(f"Debugpy connection timed out after {timeout} seconds.")
128-
else:
129-
self.log_alert("Debugpy is not installed.")
130-
else:
131-
self.log_info("Debugpy is not enabled.")
132-
133-
13466
def _dispatch_on_tear_down(self, host_object: Any) -> None:
13567
self.on_tear_down()
13668

13769
def _set_iris_handles(self, handle_current: Any, handle_partner: Any) -> None:
13870
"""Internal method to set IRIS handles."""
13971
pass
14072

73+
def _debugpy(self, host) -> None:
74+
"""Set up debugpy for debugging."""
75+
if debugpython is not None:
76+
debugpython(self=self, host_object=host)
77+
14178
# Component information methods
14279
@classmethod
14380
def _get_info(cls) -> List[str]:

src/iop/_debugpy.py

Lines changed: 97 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@
33
import time
44
import contextlib
55
import socket
6+
import os
7+
import sys
68
from contextlib import closing
7-
8-
from typing import Optional, Tuple, Union, Sequence, cast, Callable, TypeVar, Dict, Any
9+
from typing import Optional, cast, Any, Dict
910

1011
def find_free_port(start: Optional[int] = None, end: Optional[int] = None) -> int:
1112
port = start
@@ -32,38 +33,113 @@ def find_free_port(start: Optional[int] = None, end: Optional[int] = None) -> in
3233
return find_free_port(None)
3334
raise
3435

35-
3636
def is_debugpy_installed() -> bool:
3737
try:
3838
__import__("debugpy")
39+
return True
3940
except ImportError:
4041
return False
41-
return True
4242

43+
def _get_python_interpreter_path(install_dir: Optional[str]) -> Optional[str]:
44+
"""Get the path to the Python interpreter."""
45+
if not install_dir:
46+
return None
47+
48+
python_exe = 'irispython.exe' if sys.platform == 'win32' else 'irispython'
49+
python_path = os.path.join(install_dir, 'bin', python_exe)
50+
51+
return python_path if os.path.exists(python_path) else None
52+
53+
def _get_debugpy_config(python_path: str) -> Dict[str, str]:
54+
"""Get the debugpy configuration."""
55+
return {"python": python_path}
56+
57+
def configure_debugpy(self, python_path: Optional[str] = None) -> bool:
58+
"""Configure debugpy with the appropriate Python interpreter."""
59+
import debugpy
60+
61+
if not python_path:
62+
install_dir = os.environ.get('IRISINSTALLDIR') or os.environ.get('ISC_PACKAGE_INSTALLDIR')
63+
python_path = _get_python_interpreter_path(install_dir)
64+
65+
if not python_path:
66+
self.log_alert("Could not determine Python interpreter path")
67+
return False
68+
69+
try:
70+
debugpy.configure(_get_debugpy_config(python_path))
71+
self.log_info(f"Debugpy configured with Python interpreter: {python_path}")
72+
return True
73+
except Exception as e:
74+
self.log_alert(f"Failed to configure debugpy: {e}")
75+
return False
4376

44-
def wait_for_debugpy_connected(timeout: float = 30,port=0) -> bool:
45-
import debugpy # noqa: T100
77+
def wait_for_debugpy_connected(timeout: float = 30, port: int = 0) -> bool:
78+
"""Wait for debugpy client to connect."""
79+
import debugpy
4680

4781
if not is_debugpy_installed():
4882
return False
4983

50-
class T(threading.Thread):
51-
daemon = True
52-
def run(self):
53-
time.sleep(timeout)
54-
debugpy.wait_for_client.cancel()
55-
T().start()
56-
debugpy.wait_for_client()
57-
if debugpy.is_client_connected():
58-
return True
84+
def timeout_handler():
85+
time.sleep(timeout)
86+
debugpy.wait_for_client.cancel()
87+
88+
threading.Thread(target=timeout_handler, daemon=True).start()
89+
90+
try:
91+
debugpy.wait_for_client()
92+
return debugpy.is_client_connected()
93+
except Exception:
94+
import pydevd # type: ignore
95+
pydevd.stoptrace()
96+
return False
5997

60-
return False
98+
def enable_debugpy(port: int, address: str = "0.0.0.0") -> None:
99+
"""Enable debugpy server on specified port and address."""
100+
import debugpy
101+
debugpy.listen((address, port))
61102

62-
def enable_debugpy(port: int, address = None) -> bool:
103+
def debugpython(self, host_object: Any) -> None:
104+
"""Enable and configure debugpy for debugging purposes."""
105+
# hack to set __file__ for os module in debugpy
106+
# This is a workaround for the issue where debugpy cannot find the __file__ attribute of the os module.
107+
if not hasattr(os, '__file__'):
108+
setattr(os, '__file__', __file__)
63109

64-
import debugpy # noqa: T100
65110

66-
if address is None:
67-
address = "0.0.0.0"
111+
if host_object is None:
112+
self.log_alert("No host object found, cannot enable debugpy.")
113+
return
68114

69-
debugpy.listen((address, port))
115+
if host_object.enable != 1:
116+
self.log_info("Debugpy is not enabled.")
117+
return
118+
119+
if not is_debugpy_installed():
120+
self.log_alert("Debugpy is not installed.")
121+
return
122+
123+
# Configure Python interpreter
124+
if host_object.PythonInterpreterPath != '':
125+
success = configure_debugpy(self, host_object.PythonInterpreterPath)
126+
else:
127+
success = configure_debugpy(self)
128+
129+
if not success:
130+
return
131+
132+
# Setup debugging server
133+
port = host_object.port if host_object.port and host_object.port > 0 else find_free_port()
134+
135+
try:
136+
enable_debugpy(port=port)
137+
self.log_info(f"Debugpy enabled on port {port}")
138+
139+
self.trace(f"Waiting {host_object.timeout} seconds for debugpy connection...")
140+
if wait_for_debugpy_connected(timeout=host_object.timeout, port=port):
141+
self.log_info("Debugpy connected successfully")
142+
else:
143+
self.log_alert(f"Debugpy connection timed out after {host_object.timeout} seconds")
144+
except Exception as e:
145+
self.log_alert(f"Error enabling debugpy: {e}")

src/iop/cls/IOP/Common.cls

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,9 @@ Method GetModule() As %String
4040
Method %OnNew(pConfigName As %String) As %Status
4141
{
4242
$$$ThrowOnError(..Connect())
43-
Quit $method($this,"initConfig",.pConfigName) ; call subclass
43+
$$$ThrowOnError($method($this,"initConfig",.pConfigName)) ; call subclass
44+
do ..%class."_debugpy"($this)
45+
Quit $$$OK
4446
}
4547

4648
Method OnInit() As %Status

src/tests/bench/bench_bo.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,6 @@
22

33
class BenchIoPOperation(BusinessOperation):
44

5-
def on_init(self):
6-
self.log_info("BenchIoPOperation initialized")
7-
85
def on_message(self, request):
9-
6+
self.log_info("BenchIoPOperation received message")
107
return request

src/tests/bench/bench_bp.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from iop import BusinessProcess
2+
import debugpy
23

34
class BenchIoPProcess(BusinessProcess):
45
def on_init(self):
@@ -8,5 +9,6 @@ def on_init(self):
89
self.target = 'Python.BenchIoPOperation'
910

1011
def on_message(self, request):
12+
debugpy.is_client_connected()
1113
for _ in range(self.size):
1214
_ = self.send_request_sync(self.target,request)

0 commit comments

Comments
 (0)