Using the Python Standard Library

April 15, 2025

Introduction

One of the things that makes Python great is the rich standard libraries and an extensive 3rd party ecosystem. Unfortunately, with Jython Automation Scripts in Maximo, only a subset of the standard libraries such as os and sys are available by default, modules such as re, uuid or the httplib are not available. Since Jython is based on Java, you can use the available Java libraries instead, but this can be cumbersome and removes a key benefit using Python.

Fortunately, there are options to restore access to the standard library and load 3rd party modules. In this post we will discuss how to load the Python standard library, first using a simple, but limited approach provided by IBM and then a more robust and comprehensive solution using an Automation Script library.

Maximo Library Path

By default Maximo will look in two locations for Python libraries. The path defined by the mxe.pylib.path property. If this property is set, then the directory pylib is added to the path and the result is prepended to the Python engine path. For example if the mxe.pylib.path property is /opt/python, then the /opt/python/pylib directory will be added to the path.

If the mxe.pylib.create property is set to true and the mxe.pylib.path is not set, then the Java System property user.dir will be used. The user.dir property defines the path where the Java runtime process was started. The directory pylib is then added to the user.dir path, the same as with the mxe.lib.path Maximo property. If the path does not exist, it is created and the contents of the com/ibm/tivoli/maximo/script/pylib.zip, as found on the Java classpath, are extracted to the new directory.

Setting a static library path works well on Maximo 7.6 systems, where Maximo is installed and copying files to a specific directory is simple. However, this is not the case with the Maximo Application Suite, where pods are ephemeral and may be destroyed and recreated at any time.

The following two approaches provide a mechanism to ensure the Python libraries survive pod recreation.

Customization Archive

The pylib.zip is not included in the Maximo distribution and may be an artifact of an incomplete implementation or a feature that was previously available and later removed. But since the mechanism is still there, including a customization archive that contains a zip file named pylib.zip and containing the desired libraries in the maximo/applications/maximo/classes/com/ibm/tivoli/maximo/script/ directory would then be included in the pod build process and the resulting businessobjects.jar. The system would then extract the libraries to the specified path and add that directory to the Jython engine path.

Persistent Volume Share (PVC)

Another other option is to create a PVC and then copy the desired libraries to a directory in the PVC in a subdirectory named pylib, add a volume mount directive for the pod and then set the mxe.pylib.path property to that directory. It is important to note that this volume must be mounted on all pods where the script may be executed, including UI, MIF and CRON deployment types.

Getting Access to the Standard Library

Fortunately, the standard library is present within the jython.jar under a directory named Lib and is packaged with Maximo. But since it is inside a jar file, it is not available to the Jython script engine path. This is a problem we can solve and automate to provide the script engine access to the standard libraries.

Find the Lib Directory

The first thing we need to do is locate the jython.jar within the Maximo Java classpath. It is possible that the Lib directory is contained within multiple jar files, so we will use the getClassLoader().getResources(<path_here>) method to return an array of java.net.URI objects representing the path to the Lib directories on the Java classpath. The Py class is imported and used to the the ClassLoader object so we can ensure that we are using the ClassLoader that loaded the Py class from the jython.jar and therefore, where we will also find the Lib folder. The URI file paths that are return start with file: and have an exclamation mark (!) after the .jar extension to denote directories and files within a jar file. Removing these two elements and performing a substring of the middle content provides the path to the jython.jar.

The following code snippets do not include imports, this will be provided in the full script at the end.

# The path to the standard library within the jython.jar
lib_path = "Lib"
def getPythonJarLocations():
###
# Returns a list of jar locations that contain the Lib directory within the Jar.
# Example:
# getPythonJarLocations()
###
# Create a list to store the jar locations
libs = []
# Get the class loader and get the resources for the Lib directory
locations = Py.getClassLoader().getResources(File.separator + lib_path)
# Iterate through the locations and add the jar paths to the list
# The getPath() method returns a string representation of the URL
while locations.hasMoreElements():
location = locations.nextElement()
jarPath = location.getPath()
# Find the start and end of the jar path
start = int(jarPath.find("file:"))
end = int(jarPath.find("!"))
# If the start and end are valid, extract the jar path it should start with file:/ and end with !
if start > -1 and end > -1:
# Add the URL decoded jar path to the list
libs.append(
URLDecoder.decode(
File(URI(jarPath[start:end])).getAbsolutePath(), "UTF-8"
)
)
return libs

Extract the Lib Directory

Once we have found the jython.jar we need to extract the Lib folder. The function below accepts a path to a Jar file and a path to an extract directory, then extracts the Lib directory to the extract directory. It uses the JarFile class that is provided by the Java runtime library to loop through entries and create the necessary directory structure, writing out the files to the extract directory.

def extractJar(jarFilePath, outputFolderPath):
###
# Extracts the Lib directory from the jar file to the output folder.
# jarFilePath: The path to the jar file.
# outputFolderPath: The path to the output folder.
# Example:
# extractJar("C:\\jython\\jython.jar", "C:\\pylib")
###
jarFile = JarFile(jarFilePath)
entries = jarFile.entries()
while entries.hasMoreElements():
entry = entries.nextElement()
if entry.getName().startswith(lib_path):
entryFile = File(outputFolderPath, entry.getName()[3:])
if not entryFile.exists():
if entry.isDirectory():
entryFile.mkdirs()
else:
entryFile.getParentFile().mkdirs()
input = jarFile.getInputStream(entry)
output = FileOutputStream(entryFile)
buffer = array.array("b", [0] * 1024)
length = input.read(buffer)
while length > 0:
output.write(buffer, 0, length)
length = input.read(buffer)
output.flush()
output.close()
input.close()

Add Path to the System

To ensure that the path is writable to the Java runtime, we use the java.io.tmpdir to get the temporary directory path and then append pylib as the location to extract the library files. Next, we check if the directory exists and if it has files. If the directory exists and contains files we skip the process as the files have already be extracted. Finally we call Py.getSystemState() to get the Python runtime system state, which includes the system path. If it does not contain our new pythonPath directory path, we add it to the path.

# Use a StringBuilder to build the path because concatenating strings with the + operator results in encoding errors and conflicts between encoded types.
pythonPath = (
StringBuilder(System.getProperty("java.io.tmpdir"))
.append(File.separator)
.append("pylib")
.toString()
)
# Create a File object for the pythonPath
pyPathFile = File(pythonPath)
# Check that the pythonPath directory exists and that it is not empty
if not pyPathFile.exists() or pyPathFile.list() is None or len(pyPathFile.list()) == 0:
# Get the available jar locations that contain the Lib directory within the Jar. This should only be the jython.jar file, but to be safe, we will check all jars.
locations = getPythonJarLocations()
# For each jar location, extract the Lib directory to the pythonPath directory.
for location in locations:
extractJar(location, pyPathFile.getAbsolutePath())
# Get the system state and add the pythonPath to the system path if it is not already there.
state = Py.getSystemState()
# Check if the pythonPath is already in the system path and if not append it to the system path.
if pythonPath not in state.path:
state.path.insert(0, pythonPath)

Complete Solution

By dynamically locating and extracting the Lib directory from the jython.jar file we ensure that the files are available to the Jython engine even if a pod is recreated. Evrery time the script is called it checks and will extract the necessary library files before executing the rest of the script. Additionally, if other jar files are included on the classpath, using a custom archive, that contain a Lib directory, they too will be included in the available modules.

The complete script is available below and contains a scriptConfig variable that can be used to deploy the script to Maximo using the Maximo Developer tools:

https://marketplace.visualstudio.com/items?itemName=sharptree.maximo-script-deploy

Otherwise you can manually create a new script named PYLIB in your Maximo system.

from org.python.core import Py
from java.util.jar import JarFile
from java.net import URLDecoder, URI
from java.io import File, FileOutputStream
from java.lang import System, StringBuilder
import array
lib_path = "Lib"
def getPythonJarLocations():
###
# Returns a list of jar locations that contain the Lib directory within the Jar.
# Example:
# getPythonJarLocations()
###
# Create a list to store the jar locations
libs = []
# Get the class loader and get the resources for the Lib directory
locations = Py.getClassLoader().getResources(File.separator + lib_path)
# Iterate through the locations and add the jar paths to the list
# The getPath() method returns a string representation of the URL
while locations.hasMoreElements():
location = locations.nextElement()
jarPath = location.getPath()
# Find the start and end of the jar path
start = int(jarPath.find("file:"))
end = int(jarPath.find("!"))
# If the start and end are valid, extract the jar path it should start with file:/ and end with !
if start > -1 and end > -1:
# Add the URL decoded jar path to the list
libs.append(
URLDecoder.decode(
File(URI(jarPath[start:end])).getAbsolutePath(), "UTF-8"
)
)
return libs
def extractJar(jarFilePath, outputFolderPath):
###
# Extracts the Lib directory from the jar file to the output folder.
# jarFilePath: The path to the jar file.
# outputFolderPath: The path to the output folder.
# Example:
# extractJar("C:\\jython\\jython.jar", "C:\\pylib")
###
jarFile = JarFile(jarFilePath)
entries = jarFile.entries()
while entries.hasMoreElements():
entry = entries.nextElement()
if entry.getName().startswith(lib_path):
entryFile = File(outputFolderPath, entry.getName()[3:])
if not entryFile.exists():
if entry.isDirectory():
entryFile.mkdirs()
else:
entryFile.getParentFile().mkdirs()
input = jarFile.getInputStream(entry)
output = FileOutputStream(entryFile)
buffer = array.array("b", [0] * 1024)
length = input.read(buffer)
while length > 0:
output.write(buffer, 0, length)
length = input.read(buffer)
output.flush()
output.close()
input.close()
# Use a StringBuilder to build the path because concatenating strings with the + operator results in encoding errors and conflicts between encoded types.
pythonPath = (
StringBuilder(System.getProperty("java.io.tmpdir"))
.append(File.separator)
.append("pylib")
.toString()
)
# Create a File object for the pythonPath
pyPathFile = File(pythonPath)
# Check that the pythonPath directory exists and that it is not empty
if not pyPathFile.exists() or pyPathFile.list() is None or len(pyPathFile.list()) == 0:
# Get the available jar locations that contain the Lib directory within the Jar. This should only be the jython.jar file, but to be safe, we will check all jars.
locations = getPythonJarLocations()
# For each jar location, extract the Lib directory to the pythonPath directory.
for location in locations:
extractJar(location, pyPathFile.getAbsolutePath())
# Get the system state and add the pythonPath to the system path if it is not already there.
state = Py.getSystemState()
# Check if the pythonPath is already in the system path and if not append it to the system path.
if pythonPath not in state.path:
state.path.insert(0, pythonPath)
scriptConfig="""{
"autoscript": "PYLIB",
"description": "Python Standard Library",
"version": "1.0.0",
"active": true,
"logLevel": "ERROR",
"allowInvokingScriptFunctions": false
}"""

Creating a Standard Solution

Including the entire solution at the beginning of every script that uses the standard library is cumbersome and difficult to maintain. Fortunately, there is a mechanism that allows a script into another, using the exec function.

Once the above PYLIB script has been created, the source for the PYLIB script can be obtained using the ScriptCache.getInstance().getScript("PYLIB").getScriptSource() function. This retrieves the script source from the Maximo script cache minimizing any performance penalty since no database calls are required. Once the source is obtained it is included in the script by calling exec and passing the source of the PYLIB script.

This can be accomplished in as little as two lines as shown below.

from com.ibm.tivoli.maximo.script import ScriptCache
exec(ScriptCache.getInstance().getScriptInfo("PYLIB").getScriptSource())

However, in production code we recommend using this more robust example that checks that the PYLIB script exists and ensures it is a Jython script.

from com.ibm.tivoli.maximo.script import ScriptCache, JSR223ScriptDriver
# Note the use of "snake_case" for variable names as is the PEP8 naming convention standard for Python
def get_auto_script(auto_script_name):
# Use the ScriptCache to get the script source so you are not making a database call, which is otherwise expensive
scriptInfo = ScriptCache.getInstance().getScriptInfo(auto_script_name)
if scriptInfo is not None:
# Make sure that the script is Jython / Python. Note that we are comparing the engine name because both Jython and Python use the same engine.
# Also note that getScriptLanguge() is the correct method name and Language is misspelled in the Maximo API
if JSR223ScriptDriver.getSupportedScriptEngineInfo(scriptInfo.getScriptLanguge()).getEngineName() == "jython":
return scriptInfo.getScriptSource()
else:
raise Error("The script " + auto_script_name + " is not a Jython script.")
else:
raise Error("The script " + auto_script_name + " was not found.")
exec(get_auto_script("PYLIB"))

Calling exec on untrusted code can be extremely dangerous. Do not use this feature to execute untrusted scripts.

Once the PYLIB script has been called, any of the standard library modules can be imported and used. In the simple example below the httplib module is used to connecto the the https://www.python.org site and print the resulting status code and message.

import httplib
conn = httplib.HTTPSConnection("www.python.org")
conn.request("GET", "/")
r1 = conn.getresponse()
print(str(r1.status) + " " + r1.reason)

A full list of modules can be found here https://docs.python.org/2.7/library/index.html

Final Thoughts

In this post, we explored the standard Maximo features for adding Python libraries to the system path and then demonstrated how to dynamically locate and load the Python standard library modules. This allows the Python standard libraries or any other pure Python 2.7 libraries to be added to the system path. This allows the rich ecosystem of native Python modules to be utilized within Maximo and greatly enhances what is possible with Jython in Automation Scripts.

If you have any questions, comments or have suggestions for topics you would like to see us cover, please reach out to us at [email protected]

In the time it took you to read this blog post...

You could have deployed Opqo, our game-changing mobile solution for Maximo.

Opqo is simple to acquire, simple to deploy and simple to use, with clear transparent monthly pricing that is flexible to your usage.