Search code examples
pythonadagnat

Running a simple Python script from Ada program


Does someone would have a simplest as possible way to run a Python script from the Ada project?

The Python script in this case only has a print in a loop, with a sleep, so each iteration it should print the arguments.

The Python script I'm using is

import sys
import time

for index in range(10):
    print(sys.argv)
    time.sleep(1)

On approach I am trying is running this one as a batch script, so using

with Text_IO;
with Interfaces.C; use Interfaces.C;
procedure systest2 is
   function Sys (Arg : Char_Array) return Integer;
   pragma Import(C, Sys, "system");
   Ret_Val : Integer;
begin
   Ret_Val := Sys(To_C("python testpy.py arg1 arg2"));
end systest2;

The problem is that the execution blocks the script, meaning that the Python printouts are only printed at the end of the execution, at once.

I know that there is a solution (to run Python from Ada) based on GNATCOLL, but I couldn't find any example to run it.

Update

Just to clarify. So I will try to simplify a bit the question. I want to do the equivalent of this in C:

#include <stdio.h>
#include <stdlib.h>
int main(){
  system("python testpy.py ddddd");
  return 0;
}

In this case it does not block the testpy.py printouts.

But I am doing this

with Interfaces.C; use Interfaces.C;
procedure systest2 is
   function Sys (Arg : Char_Array) return Integer;
   pragma Import(C, Sys, "system");
   Ret_Val : Integer;
begin
   Ret_Val := Sys(To_C("python testpy.py arg1 arg2"));
end systest2;

which blocks the testpy.py script until its end. This should not happen.

So, please, how could I fix this?


Solution

  • Below is an example that might help (tested with GNAT CE 2019 on Linux). It's based on the GNAT.Expect package and the example that was given in one of AdaCore's Gem articles. In the example, a new process is spawned and pipes are automatically connected.

    Two remarks:

    • It's important to run the python interpreter with I/O buffering disabled (using the -u option). If this is undesirable, then you can, alternatively, also use the flush option on the print statement in Python 3 i.e. print(..., flush=True). When you don't disable the I/O buffering or flush the I/O buffer behind print, you will run into timeouts.

    • The Python script is, for now, just killed via the Close statement (it just sends the SIGKILL signal on Linux).

    main.adb

    with Ada.Text_IO; use Ada.Text_IO;
    with GNAT.Expect; use GNAT.Expect;
    with GNAT.OS_Lib; use GNAT.OS_Lib;
    
    procedure Main is
    
       Command : constant String := "python -u test.py 123";
       Pd      : Process_Descriptor;
       Args    : Argument_List_Access;
       Result  : Expect_Match;
    
    begin
    
       Args := Argument_String_To_List (Command);
    
       Non_Blocking_Spawn
          (Pd,
           Command     => Args (Args'First).all,
           Args        => Args (Args'First + 1 .. Args'Last),
           Buffer_Size => 0);  
    
       for I in 1 .. 10 loop
    
          Expect (Pd, Result, Regexp => "\d+", Timeout => 2_000);
    
          case Result is    
             when Expect_Timeout =>
                Put_Line ("Expect timed out.");   
             when 1  =>
                Put_Line ("Received: " & Expect_Out_Match (Pd));
             when others =>
                raise Program_Error;            
          end case;
    
          Put_Line ("Doing other stuff...");
    
       end loop;
    
       Close (Pd);
       Free (Args);
    
    exception
       when Process_Died =>
    
          Put_Line ("Process died.");
          Close (Pd);
          Free (Args);
    
    end Main;
    

    test.py

    import sys
    import time
    
    while True:
    
        print(sys.argv[1])
        time.sleep(1)
    

    output

    $ ./main
    Received: 123
    Doing other stuff...
    Received: 123
    Doing other stuff...
    Received: 123
    
    (repeated another 7 times).