Skip to content

Commit 0ed505c

Browse files
committed
Add IOP.Wrapper class for simplified Python module import with remote debugging support; update changelog and documentation
1 parent f3fd60d commit 0ed505c

File tree

6 files changed

+159
-1
lines changed

6 files changed

+159
-1
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

88
## [unreleased] - Unreleased
9+
### Added
10+
- New IOP.Wrapper class for simplified python module import into IRIS with remote debugging support and traceback handling
11+
912
### Changed
1013
- Improve module loading and initialization for better performance in BusinessProcess by checking if the module is already loaded
1114
- Change OnMessage to MessageHandler in BusinessOperation for better performance and clarity
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import os
2+
3+
def main():
4+
# Get the value of the environment variable
5+
my_env_var = os.getenv('MY_ENV_VAR', 'default_value')
6+
7+
# Print the value of the environment variable
8+
print(f'MY_ENV_VAR: {my_env_var}')
9+
10+
11+
if __name__ == "__main__":
12+
main()

docs/wrapper.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# IoP.Wrapper
2+
3+
IoP.Wrapper is a class designed to simplify the import of Python modules into InterSystems IRIS, providing remote debugging support and handling tracebacks effectively. It is particularly useful for developers who want to integrate Python code with IRIS while maintaining a smooth debugging experience.
4+
5+
## Features
6+
7+
- **Simplified Module Import**: Easily import Python modules into IRIS without complex configurations.
8+
- **Remote Debugging Support**: Debug Python code running in IRIS from your local development environment.
9+
- **Traceback Handling**: Automatically capture and format Python tracebacks for easier debugging.
10+
11+
## Usage
12+
13+
To use IoP.Wrapper, simply use the helper method `Import` to import your Python module. The method will handle the rest, including setting up the necessary environment for remote debugging and managing tracebacks.
14+
15+
```python
16+
# my_script.py
17+
import os
18+
19+
def main():
20+
# Get the value of the environment variable
21+
my_env_var = os.getenv('MY_ENV_VAR', 'default_value')
22+
23+
# Print the value of the environment variable
24+
print(f'MY_ENV_VAR: {my_env_var}')
25+
26+
27+
if __name__ == "__main__":
28+
main()
29+
```
30+
31+
The ObjectScript code to import the Python module would look like this:
32+
33+
```objectscript
34+
Set pythonModule = "my_script"
35+
Set pythonPath = "/path/to/your/python/scripts"
36+
Set debugPort = 5678 ; Set the port for remote debugging
37+
Set myModule = ##class(IoP.Wrapper).Import(pythonModule, pythonPath, debugPort)
38+
// The process will automatically handle the import and setup for remote debugging and wait for the client debugger to attach.
39+
// Once the client debugger is attached, you can run the main function of your Python module.
40+
do myModule.main()
41+
```
42+

mkdocs.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ nav:
1818
- Debugging: debug.md
1919
- Production Settings: prod-settings.md
2020
- Venv Support: venv.md
21+
- Wrapper Support: wrapper.md
2122
- Contributing:
2223
- Contributing: contributing.md
2324
- Code of Conduct: code-of-conduct.md

src/iop/_debugpy.py

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,4 +142,46 @@ def debugpython(self, host_object: Any) -> None:
142142
else:
143143
self.log_alert(f"Debugpy connection timed out after {host_object._timeout} seconds")
144144
except Exception as e:
145-
self.log_alert(f"Error enabling debugpy: {e}")
145+
self.log_alert(f"Error enabling debugpy: {e}")
146+
147+
def debugpy_in_iris(iris_dir, port) -> bool:
148+
149+
if not hasattr(os, '__file__'):
150+
setattr(os, '__file__', __file__)
151+
152+
if not is_debugpy_installed():
153+
print("debugpy is not installed.")
154+
return False
155+
if not iris_dir:
156+
print("IRIS directory is not specified.")
157+
return False
158+
159+
import debugpy
160+
python_path = _get_python_interpreter_path(mgr_dir_to_install_dir(iris_dir))
161+
if not python_path:
162+
return False
163+
debugpy.configure(_get_debugpy_config(python_path))
164+
165+
try:
166+
enable_debugpy(port=port)
167+
except Exception as e:
168+
print(f"Failed to enable debugpy: {e}")
169+
return False
170+
171+
print(f"Debugpy is waiting for connection on port {port}...")
172+
if wait_for_debugpy_connected(timeout=30, port=port):
173+
print(f"Debugpy is connected on port {port}")
174+
return True
175+
else:
176+
print(f"Debugpy connection timed out after 30 seconds on port {port}")
177+
return False
178+
179+
def mgr_dir_to_install_dir(mgr_dir: str) -> Optional[str]:
180+
"""Convert manager directory to install directory."""
181+
import os
182+
if not mgr_dir:
183+
return None
184+
install_dir = os.path.dirname(os.path.dirname(mgr_dir))
185+
if os.path.exists(install_dir):
186+
return install_dir
187+
return None

src/iop/cls/IOP/Wrapper.cls

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
Class IOP.Wrapper Extends %RegisteredObject
2+
{
3+
4+
ClassMethod Import(
5+
moduleName As %String,
6+
path As %String = "",
7+
debugPort As %Integer = 0) As %Status
8+
{
9+
set tSC = $$$OK
10+
Try {
11+
do ##class(IOP.Common).SetPythonPath(path)
12+
// For traceback debugging
13+
do $system.Python.Debugging(1)
14+
if debugPort > 0 {
15+
16+
set debugpy = ##class(%SYS.Python).Import("iop._debugpy")
17+
do debugpy."debugpy_in_iris"($zu(12),debugPort)
18+
}
19+
20+
// Import the module
21+
set tModule = ##class(%SYS.Python).Import(moduleName)
22+
23+
}
24+
Catch ex {
25+
Set tSC= ##class(IOP.Wrapper).DisplayTraceback(ex)
26+
throw ex
27+
}
28+
return tModule
29+
}
30+
31+
ClassMethod DisplayTraceback(ex) As %Status
32+
{
33+
set tSC = ex.AsStatus()
34+
35+
// Import Modules
36+
set sys = ##class(%SYS.Python).Import("sys")
37+
set tracebackModule = ##class(%SYS.Python).Import("traceback")
38+
set builtins = ##class(%SYS.Python).Import("builtins")
39+
// Get the last traceback
40+
set traceback = sys."last_traceback"
41+
set exType = sys."last_type"."__name__"
42+
set exValue = sys."last_value"."__str__"()
43+
// Check if traceback is an object
44+
if $isObject(traceback) {
45+
// Format the traceback
46+
set tb = tracebackModule."format_exception"(sys."last_type", sys."last_value", traceback)
47+
set tbString = ""
48+
for i=0:1:(tb."__len__"()-1) {
49+
set tbString = tbString _ $c(10)_$c(13) _ tb."__getitem__"(i)
50+
}
51+
w tbString
52+
set tSC = $$$ERROR("Exception in Python - "_exType_" - "_exValue)
53+
}
54+
55+
return tSC
56+
}
57+
58+
}

0 commit comments

Comments
 (0)