Question
· Aug 6

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 call
ClassMethod CollectStringFromBuffer() 
{
    set buffer = ##class(CustomClass.Buffer).%New()
    do buffer.BeginCaptureOutput()
    do ..PythonMethodWithPrintStatments() // Capture all statements written by this call
    do buffer.EndCaptureOutput()
    do buffer.ReadToString(.errorOutput)
}

/// Python code which executes Python print and IRIS write statement
ClassMethod 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!

Product version: IRIS 2025.1
$ZV: IRIS for UNIX (Red Hat Enterprise Linux 9 for x86-64) 2025.1
Discussion (4)3
Log in or sign up to continue

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:

  1. Using TCP Device for Output Redirection:
    Set up a TCP listener to capture the output from Python and use the write command for ObjectScript to send data. This approach ensures Python's output is redirected to a specific listener that can be handled by your Buffer.

    Example:

    • On the ObjectScript side, configure the TCP device:
      objectscript
      open "|TCP|4":("127.0.0.1":4200::$CHAR(3,4)):10
      use "|TCP|4"
  • From Python, write to the same device:
    python
    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].

  1. Custom Python Output Stream:
    Implement a custom output stream in Python to intercept the sys.stdout and 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 CustomStdout to marshal Python's print output to your Buffer [1].

  2. Combining Both Approaches with the iris object:
    Utilize the IRIS Python SDK to directly invoke ObjectScript's methods that align well with your Buffer functionality:

    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:

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)+1
	open 2:SpoolFile
	use 2
	do ..PythonPrint()
	close 2
	break
}

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=956

zwrite ^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))=s quit

And 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"