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.