Redirecting Python output to the device ObjectScript method is called from for output capture?
I have a custom Buffer class which is designed to capture written/printed statements to the device, to be able to transform the captured text to string or stream type. I have used this in ObjectScript to capture ObjectScript write statements and return a string. I would like to try to use this with a [ Language = python ] method as follows. This class will be called by a scheduled task.
/// ObjectScript code which initializes buffer to capture statements written in nested method callClassMethod CollectStringFromBuffer()
{
set buffer = ##class(CustomClass.Buffer).%New()
do buffer.BeginCaptureOutput()
do..PythonMethodWithPrintStatments() // Capture all statements written by this calldo buffer.EndCaptureOutput()
do buffer.ReadToString(.errorOutput)
}
/// Python code which executes Python print and IRIS write statementClassMethod PythonMethodWithPrintStatments() [ Language = python, Private ]
{
import iris
print("Python print this line to the buffer")
iris.execute('write"IRIS write this line to the buffer"')
}The Python method is not printing to the same device as the ObjectScript method is called from, so the buffer is not actually capturing these statements.
I am thinking through some alternative solutions, but I am curious first if anyone has advice on ways to redirect Python output that would be usable with the current solution? Thanks!
Comments
The issue arises because Python's print statements and IRIS's ObjectScript write statements operate on different devices, causing your custom Buffer class in ObjectScript to not capture these outputs. To address this, you can redirect Python's output to a stream more integrated with ObjectScript. Here are some possible solutions and approaches:
Using TCP Device for Output Redirection: Set up a TCP listener to capture the output from Python and use the
writecommand for ObjectScript to send data. This approach ensures Python's output is redirected to a specific listener that can be handled by yourBuffer.Example:
On the ObjectScript side, configure the TCP device:
open "|TCP|4":("127.0.0.1":4200::$CHAR(3,4)):10 use "|TCP|4"From Python, write to the same device:
import socket TCP_IP = '127.0.0.1' TCP_PORT = 4200 BUFFER_SIZE = 1024 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((TCP_IP, TCP_PORT)) s.send(b"Python print statement redirected!") s.close()
This setup will unify Python and ObjectScript outputs for capturing [1].
Custom Python Output Stream: Implement a custom output stream in Python to intercept the
sys.stdoutand redirect everything to an ObjectScript-compatible format:import sys class CustomStdout: def write(self, text): # Implement logic to capture and redirect text to ObjectScript print("Redirected:", text) def flush(self): pass sys.stdout = CustomStdout()You can use
CustomStdoutto marshal Python'sprintoutput to yourBuffer[1].Combining Both Approaches with the
irisobject: Utilize the IRIS Python SDK to directly invoke ObjectScript's methods that align well with yourBufferfunctionality:import iris iris.pyrun('write("Captured directly into Buffer")')This ensures a direct capture of Python's outputs into ObjectScript-compatible streams [1].
These configurations will help you unify Python and ObjectScript outputs, enabling your Buffer class to successfully capture statements irrespective of their source.
Sources:
I've never tried with a custom buffer but I know you can redirect python stdout pretty easily using something like this:
sys.stdout = open('file', 'w')
Or this to completely suppress output:
sys.stdout = open(os.devnull, "w")
And then when you're done, use this to revert:
sys.stdout = sys.__stdout__
How is the buffer class capturing the output? I did a very quick testing using the spool device and a USE statement and the output appeared in ^SPOOL as expected:
/// Open spool device and call Embedded Python method.ClassMethod TestSpool()
{
set SpoolFile = $order(^SPOOL(""),-1)+1open2:SpoolFile
use2do..PythonPrint()
close2break
}
ClassMethod PythonPrint() [ Language = python ]
{
import iris
print("Python print this line to the buffer")
iris.execute('write"IRIS write this line to the buffer"')
}
zwrite SpoolFile
SpoolFile=956zwrite^SPOOL(956)
^SPOOL(956,1)="Python print this line to the buffer"_$c(10)
^SPOOL(956,2)="IRIS write this line to the buffer"^SPOOL(956,2147483647)="{67424,57942{3{"Using a device mnemonic routine and redirecting IO also seems to work. I used this very basic routine to log to IO:
ZJES01 ;
rchr(c) quit
#;Read a string - we don't care about reading
rstr(sz,to) quit
#;Write a character - call the output label
wchr(s) do output($char(s)) quit
#;Write a form feed - call the output label
wff() do output($char(12)) quit
#;Write a newline - call the output label
wnl() do output($char(13,10)) quit
#;Write a string - call the output label
wstr(s) do output(s) quit
#;Write a tab - call the output label
wtab(s) do output($char(9)) quit
#;Output label - this is where you would handle what you actually want to do.
#; in our case, we want to write to str
output(s) set^ZJES($increment(^ZJES))=squitAnd then used the following classmethod to test:
ClassMethod TestRedirect()
{
set MnemonicRoutine = ##class(%Device).GetMnemonicRoutine()
use$io::("^ZJES01")
set RedirectIO=##class(%Device).ReDirectIO(1)
do..PythonPrint()
do##class(%Device).ReDirectIO(RedirectIO)
use$io::("^"_MnemonicRoutine)
quit
}
Which gave me:
zwrite^ZJES^ZJES=3^ZJES(1)="Python print this line to the buffer"^ZJES(2)=$c(13,10)
^ZJES(3)="IRIS write this line to the buffer"