Python on the IBMi - Chapter 3

Python on the IBMi - Chapter 3

Our adventurer now finds himself at the edge of a vast canyon, one that separates the modern realm of Python from the ancient stronghold of RPG. With no clear bridge in sight, he grips the rope, takes a breath, and swings across the chasm, powered by pyodbc and the promise of discovery.

In this chapter, we’ll attempt a daring maneuver: using pyodbc to call an external stored procedure that, in turn, invokes an RPG program buried deep within the IBMi. The goal is to pass a message into the void and receive a returned value, a signal that the connection has been made, and the old magic still lives.

The drop below is steep, and the code must be precise. But fortune favors the bold. And on the other side of the canyon, new knowledge awaits. Grab a rope and hold on tight.

Prerequisites

Objective

  • Call a RPG program from a Python script using pyodbc.

Let's Code

Calling a RPG program using pyodbc proved to be more of a challenge than I first thought it would be. I had to enlist some help with this one from the gurus over at (https://chat.ibmioss.org/). Let me say if you aren't part of this group sign-up. A lot of good information gets passed around. This site is considered IBMi Open Source Chat Group but it's so much more. Korinne Adler showed me how to call an external stored procedure from pyodbc. If I really needed to call an RPG program I think the better solution would be to stick with iToolkit from the previous article. But it's always good to have a couple of tools in your toolbelt.

So from the previous article we created a RPG program called EXAMPLE1. Now we are going to create an external stored procedure to call that RPG program. I ran the following SQL in VS Code in the DB2 for i extension to create the external stored procedure.

DROP PROCEDURE RTHOMPSON1/EXAMPLE1;
CREATE PROCEDURE RTHOMPSON1/EXAMPLE1  (
 IN    p_InParm       CHAR(20),
 OUT   p_OutParm      CHAR(200))
 LANGUAGE RPGLE
 SPECIFIC RTHOMPSON1/EXAMPLE1
 NOT DETERMINISTIC
 CALLED ON NULL INPUT
 EXTERNAL NAME RTHOMPSON1/EXAMPLE1
 PARAMETER STYLE SQL ;        

Now lets test the external stored procedure to make sure it's working.

NOTE: There is a bug as of right now in the VS Code DB2 for i extension that will not return output parameters from stored procedures. Here is a work around but hopefully this will be fixed in the future. You have to put the output parameter in a global variable.

CREATE OR REPLACE VARIABLE RTHOMPSON1.OUT_PARM CHAR(200) CCSID 1208 DEFAULT '';

call rthompson1.example1 ('IBMi', RTHOMPSON1.OUT_PARM);

values (RTHOMPSON1.OUT_PARM);        

The results should look like the following in VS Code

Article content

From an ACS Run SQL Scripts session you can test the stored procedure by running the following:

cl: addlible rthompson1;
call rthompson1.example1 ('IBMi', ' ');        

The results should look like the following in ACS RSS.

Article content

Calling the RPG from Python using pyodbc

In my (/home/rthompson/python/chapter3) folder I created a new Python script called pyodbc_callrpg.py with the following code.

import pyodbc
connstring = 'DSN=*LOCAL;CommitMode=0;'
try:
    conn = pyodbc.connect(connstring)
    print("Connected to the DB2")

    # Create a variable to hold the output parameter
    conn.execute("CREATE OR REPLACE VARIABLE RTHOMPSON1.OUT_PARM CHAR(200) CCSID 1208 DEFAULT ''")
    # Set the output parameter value to the job name of the current job
    conn.execute("SET RTHOMPSON1.OUT_PARM = qsys2.job_name")
    # Fetch the value in our variable to get the job name running this Python Program
    # This can be handy if needing to debug the program
    data = conn.execute("VALUES(RTHOMPSON1.OUT_PARM)").fetchval()

    # Print the job name
    print(f"Output Parameter Value: {data}")
   
    # Reset the output parameter to an empty string before calling the stored procedure
    conn.execute("SET RTHOMPSON1.OUT_PARM = ''")

    # Define the stored procedure call
    sql = "{CALL RTHOMPSON1.EXAMPLE1(?, RTHOMPSON1.OUT_PARM)}"
    # Input parameter value
    in_parm = "IBMi"
    # Define the parameters 
    parms = (in_parm)

    # Bind parameters
    conn.execute(sql, parms)
    
    # Fetch the output parameter value
    data = conn.execute("VALUES(RTHOMPSON1.OUT_PARM)").fetchval()
    # Print the output parameter value
    print(f"Output Parameter Value: {data}") 

except pyodbc.Error as e:
    print(f"Error: {e}")

finally:
    # Close cursor and connection
    conn.close()        

I wanted to highlight the code below. The question mark (?) is a place holder for the variable we are passing in. Pyodbc doesn't have a way to retrieve an output parameter from a call statement so we must place it in our variable RTHOMPSON1.OUT_PARM so that we can retrieve it later.

# Define the stored procedure call
sql = "{CALL RTHOMPSON1.EXAMPLE1(?, RTHOMPSON1.OUT_PARM)}"
# Input parameter value
in_parm = "IBMi"
# Define the parameters 
parms = (in_parm)

# Bind parameters
conn.execute(sql, parms)
        

From VS Code Open a PASE terminal by pressing (Ctrl+Shift+J) and selecting PASE and then type the following:

cd python/chapter3
python3 pyodbc_callrpg.py        

If successful your results should look like the following.

Article content

BONUS TIP

If you wanted to debug this (like I had to do). You could put a delay job in the EXAMPLE1 RPG program. Then call the Python program it will output the job name and then call the RPG program. This would give you enough time to do a STRSRVJOB and STRDBG to debug the program using the job name from the Python output.

What's Next?

Just when our hero thought the path was clear, he stumbles into a sticky trap. Deep within the shadows of the web, a monstrous spider descends, it's tangled threads representing new challenges in serving data across the network.

In Chapter 4, we’ll explore how to spin up a simple Python web server on the IBMi. It’s not all magic, but when it works, it sure feels like it.

So grab your shield, your courage, and your browser, our journey into the web begins next.

Great job. Enjoying your articles and very well written. Fun too! We ibm folks could use a bit more fun. 🤩

Like
Reply

With the global variable approach is there the possibility that 2 jobs can collide and get that value back ? I had not heard of the global variable before. Interesting.

Like
Reply

To view or add a comment, sign in

More articles by Ricky Thompson

  • IBMi Object Utility

    After writing a series of LinkedIn articles on Python and IBM i, I decided to build a small utility for myself. I…

    3 Comments
  • VS Code - Code for i Spool File Browser

    One extension I find extremely useful is Matt Tyler's Spool File Browser. This extension allows you to view spool files…

    1 Comment
  • Python on the IBMi - Chapter 9

    At the edge of a quiet clearing, our hero meets a figure cloaked in legacy, the old RPG programmer. Weathered by…

    7 Comments
  • Python on the IBMi - Chapter 8

    Steel clashes with steel as our hero squares off against a formidable foe—the dark knight. This enemy doesn’t roar or…

  • Python on the IBMi - Chapter 7

    Our hero now stands before the dragon, not to conquer it, but to make a choice. Deletion is a powerful force, and once…

  • Python on the IBMi - Chapter 6

    As our hero presses forward, he encounters a powerful sorcerer one who twists raw data into unreadable chaos. JSON…

    1 Comment
  • Python on the IBMi - Chapter 5

    With the web server behind him and the shadows of the cave growing distant, our hero now faces a new challenge: a wide,…

  • Python on the IBMi - Chapter 4

    Our hero’s journey has brought him far from the quiet forests of “Hello, World” to the twisting caves of pyodbc and…

    3 Comments
  • Python on the IBMi - Chapter 2

    After learning the basic spells of Python, our adventurer now approaches a cave with the word pyodbc carved above the…

    10 Comments
  • Python on the IBMi - Chapter 1

    Disclaimer Before we begin. I am not a seasoned Python developer.

    22 Comments

Explore content categories