Python

Python

Python

 View Only

Using Python with JCL & Rexx

By Steven Pitman posted Mon April 29, 2024 10:41 AM

  

For using the IBM Open Enterprise SDK for Python on z/OS, it’s interesting to be able to run Python not only from the z/OS Unix System Services environment, but also in a Batch environment. The inverse is also true however, that we may want to run Batch jobs from Python. Let’s look at some examples of being able to do both. 

  

Running Python from JCL 

JCL can be used to run Python from a Batch environment. To do this we can use the BPXBATCH utility, which provides a way to run any z/OS Unix System Services program or shell script from JCL. Here are two files, the first being the Python script that we’re going to run which simply prints out one message to stdout, and one message to stderr. The second is the JCL which uses BPXBATCH to run Python, running this script. 

hello.py:

import sys 
print('Message to stdout') 
print('Message to stderr', file=sys.stderr) 
exit(0)

pyjob.jcl:

//PYJOB    JOB 
//STEP1    EXEC PGM=BPXBATCH,PARM='PGM /u/ibmuser/python/usr/lpp/IBM/cy 
//             p/v3r12/pyz/bin/python3 /u/ibmuser/jcl/hello.py' 
//SYSPRINT DD SYSOUT=* 
//STDERR   DD PATH='/u/ibmuser/jcl/err',PATHOPTS=(OCREAT,OWRONLY), 
//            PATHMODE=(SIRWXU,SIRGRP) 
//STDOUT   DD PATH='/u/ibmuser/jcl/out',PATHOPTS=(OCREAT,OWRONLY), 
//            PATHMODE=(SIRWXU,SIRGRP) 
//STDENV   DD * 
PATH=/u/ibmuser/python/usr/lpp/IBM/cyp/v3r12/pyz/bin:/bin 
LIBPATH=/u/ibmuser/python/usr/lpp/IBM/cyp/v3r12/pyz/lib:/lib 
_BPXK_AUTOCVT=ON 
_CEE_RUNOPTS=FILETAG(AUTOCVT,AUTOTAG) POSIX(ON) 
/*

This runs a JCL job named PYJOB. There are 3 main important components for this: 

1) The BPXBATCH PARM parameter specifies to run the Python interpreter installed at /u/ibmuser/python/usr/lpp/IBM/cyp/v3r12/pyz, and has one argument, which is the path to test.py.  

2) The STDOUT & STDERR DD statements, which specify where to redirect the output to. In this case they are redirected to z/OS Unix files instead of the console or datasets. While it’s not in this example, note that there is a restriction for STDIN where it must be a z/OS Unix file and cannot be a dataset. 

3) The STDENV DD statement. IBM Open Enterprise SDK for Python requires certain environment variables to be set to be able to run it, and these can be set within STDENV.  

While BPXBATCH is used here, BPXBATCH has multiple aliases for running in different ways, such as BPXBATSL, which performs a local spawn to allow the process in the same address space – these will be used in the example below to allow using named DD statements within Python. To get access to them, the Python package pyzfile can be used. Below is an example of using pyzfile to open a DD statement named DDNAME. DDNAME is pointing to a dataset which contains banking information, a small sample is shown below. The script will read in the data, parse the JSON, and print out the total balance of all users in the dataset.  

 

dd.py: 

from pyzfile import * 
import json 

if __name__ == '__main__': 
    total_balance = 0.0 

    with ZFile("//dd:DDNAME", "rb,type=record", encoding='cp1047') as dd: 
        text = dd.reads() 
        parsed_json = json.loads(text) 

    for client in parsed_json: 
        total_balance += client["balance"] 

    print("Total balance within this dataset: {}".format(total_balance)) 

pyjob.jcl:

//JCLJOB   JOB 
//STEP1    EXEC PGM=BPXBATSL,PARM='PGM /u/ibmuser/python/usr/lpp/IBM/cy 
//             p/v3r12/pyz/bin/python3 /u/ibmuser/jcl/dd.py' 
//SYSPRINT DD SYSOUT=* 
//STDERR   DD PATH='/u/ibmuser/jcl/err',PATHOPTS=(OCREAT,OWRONLY), 
//            PATHMODE=(SIRWXU,SIRGRP) 
//STDOUT   DD PATH='/u/ibmuser/jcl/out',PATHOPTS=(OCREAT,OWRONLY), 
//            PATHMODE=(SIRWXU,SIRGRP) 
//DDNAME   DD DSN='IBMUSER.JCL.DEMO.DD',DISP=SHR 
//STDENV   DD * 
PATH=/u/ibmuser/python/usr/lpp/IBM/cyp/v3r12/pyz/bin:/bin 
LIBPATH=/u/ibmuser/python/usr/lpp/IBM/cyp/v3r12/pyz/lib:/lib 
_BPXK_AUTOCVT=ON 
_CEE_RUNOPTS=FILETAG(AUTOCVT,AUTOTAG) POSIX(ON) 
/* 

JSON contained within the dataset IBMUSER.JCL.DEMO.DD (not full sample): 

[{ 
  "id": "90a0ea85-94f8-4721-842c-5ff0cf708117", 
  "name": "John Doe", 
  "balance": 3835.61, 
  "registered_time": 1711631690 
},{ 
  "id": "e9d01d0c-952d-48b1-802d-55d10e1bde99", 
  "name": "Jane Doe", 
  "balance": 4971.01, 
  "registered_time": 1711630410 
}] 

Running this JCL will create the file /u/ibmuser/jcl/out containing the text: Total balance within this dataset: 29453551.32 

Running JCL Jobs from Python 

If you want to be able to run your JCL jobs directly from Python, IBM Z Open Automation Utilities (ZOAU) contains ways that you can do this. IBM ZOAU is a Python package for doing z/OS non-Unix related tasks when using both Python and Shell scripting. This includes tasks such as reading or writing datasets, submitting & querying jobs and running console commands. To download and install ZOAU, you can follow the installation instructions.  

  
For this example, the dataset IBMUSER.JCL.DEMO.SUBMIT contains the following:   

//JCLJOB   JOB 
//STEP0001 EXEC PGM=IEBGENER 
//SYSIN    DD DUMMY 
//SYSPRINT DD SYSOUT=* 
//SYSUT1   DD DSN='IBMUSER.JCL.DEMO.DD',DISP=SHR 
//SYSUT2 DD SYSOUT=* 
// 

  

This JCL uses the program IEBGENER to print out the dataset IBMUSER.JCL.DEMO.DD, which contains the same JSON data as seen above. Here’s an example of how you can both submit this job using ZOAU, wait for it to finish, and get its output, and then print out how many clients are in this dataset. 

 

from zoautil_py import jobs 
import json 

if __name__ == '__main__': 
    # Submit the JCL Job 
    jcljob = jobs.submit('IBMUSER.JCL.DEMO.SUBMIT')   

    # Wait for JCL job to finish 
    jcljob.wait()   

    # Get the jobs output and print it to stdout 
    output = jobs.read_output(jcljob.job_id, 'STEP0001', 'SYSUT2').encode("utf-8").decode("unicode_escape")   

    # Parse the JSON and print out the number of clients contained within 
    parsed_json = json.loads(output) 
    print('There are a total of {} clients in this dataset'.format(len(parsed_json))) 

Running this Python script will output the following: There are a total of 10000 clients in this dataset 


For ZOAU, you can view the documentation for Jobs related tasks here, which contains methods for submitting jobs, querying jobs, cancelling jobs, etc. 

Running Python from REXX 

Just like in JCL, if you want to run Python from a REXX script, it has a function for calling any z/OS Unix System Services program called bpxwunix. To use this function in REXX, you can specify the variables/stems to capture stdout/stderr, and to specify the command you’re running and where to find it. The accounts.txt mentioned here is the same as found in the dataset IBMUSER.JCL.DEMO.DD mentioned above.  Here’s an example of being able to run Python from REXX: 

run.rexx: 

/* REXX */ 
/* Set up the variables for running Python */ 
command = '/u/ibmuser/python/usr/lpp/IBM/cyp/v3r12/pyz/bin/python3 /u/ibmuser/rexx/run.py' 
stdout.0 = 0 
stderr.0 = 0 
stdin.0 = 0 
env.0 = 4 
env.1 = 'PATH=/u/ibmuser/python/usr/lpp/IBM/cyp/v3r12/pyz/bin:/bin' 
env.2 = 'LIBPATH=/u/ibmuser/python/usr/lpp/IBM/cyp/v3r12/pyz/lib:/lib' 
env.3 = '_BPXK_AUTOCVT=ON' 
env.4 = '_CEE_RUNOPTS=FILETAG(AUTOCVT,AUTOTAG) POSIX(ON)'   

/* Call Python to parse the data */ 
call bpxwunix command,stdin.,stdout.,stderr.,env. 
      /* Print the output from running Python */ 
      do i=1 to stdout.0 
         say stdout.i 
      end 

      do i=1 to stderr.0 
         say stderr.i 
      end 

Run.py: 

import json   

# Load JSON 
with open('/u/ibmuser/rexx/accounts.json', 'r') as f: 
   data = json.load(f) 

# Calculate the total & average account balance 
total_balance = sum(map(lambda x: x['balance'], data)) 
total_accounts = len(data)   

# Print out the average balance 
print('Average account balance: {}'.format(total_balance / total_accounts)) 

After running this Rexx, the output will be the following: Average account balance: 2945.35 

 

Running Rexx from Python 

If your Rexx script is located within your z/OS Unix System Services filesystem, you’ll be able to run it using Python the same way as any other z/OS Unix program. Here’s one example of how you can accomplish this: 

runrexx.py: 

import subprocess   

if __name__ == '__main__': 
    output = subprocess.check_output(['./script.rexx']) 
    print(output) 

  

script.rexx:  

/* REXX */ 
say 'Beginning processing' 
/* Do some work here ... */ 
say 'Finished processing' 

Running this Python script would output the following: 
Beginning processing 
Finished processing 

 

The subprocess module contains the check_output function, which you pass in both the path to what you want to run, and any arguments it may have. It is a blocking function, so waits until the subprocess finishes running before returning with its output. Python also provides several other functions which could be used such as os.system or subprocess.Popen

If the Rexx script you have is located in a z/OS dataset, then you’ll need some JCL to run it. The same steps from using IBM Z Open Automation Utilities to run the JCL as documented above can be used in this case.  

1 comment
97 views

Permalink

Comments

Fri August 16, 2024 05:21 PM

Thank you Steven for this interesting read. I tried a few of these approaches. 

I experimented with Rexx from Python. I have a question now - how do I read a dataset in uss-rexx? A simple execio * diskr is failing to respond to me if I just run my script as ./script.rexx from USS, and I couldn't proceed much in that direction to call the script.rexx from python.

I generally use zoautil commands and zoautil_py for manipulating my zos datasets.

Regards,

Hamid