Try on DesignSafe

App Schema: opensees-express#

Dig Deep into an App Schema to Create the Input

by Silvia Mazzoni, DesignSafe, 2025

We are going to walk through the app schema to develop our job input, and then we will submit the job.

We will use the opensees-express app.

  • (OpenSees-Express GitHub Page)[https://github.com/TACC/WMA-Tapis-Templates/tree/main/applications/opensees-express]

  • (Tapis Apps Documentation)[https://tapis.readthedocs.io/en/latest/technical/apps.html]

  • (Tapis Jobs Documentation)[https://tapis.readthedocs.io/en/latest/technical/jobs.html]

from tapipy.tapis import TapisResult

Connect to Tapis#

t=OpsUtils.connect_tapis()
 -- Checking Tapis token --
 Token loaded from file. Token is still valid!
 Token expires at: 2025-09-19T21:03:18+00:00
 Token expires in: 2:47:28.669912
-- LOG IN SUCCESSFUL! --

App User Input#

Initialize#

# initalize
tapisInput = {}
tapisInput["name"] = 'OpenSeesExpressSubmit'
appId = 'opensees-express'
appVersion = 'latest'

Get app schema – input#

thisAppData_MP = OpsUtils.get_tapis_app_schema(t,appId,version=appVersion)
OpsUtils.display_tapis_app_schema(thisAppData_MP)
########################################
########### TAPIS-APP SCHEMA ###########
########################################
######## appID: opensees-express
######## version: latest
########################################
{
  sharedAppCtx: "wma_prtl"
  isPublic: True
  tenant: "designsafe"
  id: "opensees-express"
  version: "latest"
  description: "OpenSees-EXPRESS provides users with a sequential OpenSees interpreter. It is ideal to run small sequential scripts on DesignSafe resources freeing up your own machine."
  owner: "wma_prtl"
  enabled: True
  versionEnabled: True
  locked: False
  runtime: "ZIP"
  runtimeVersion: None
  runtimeOptions: None
  containerImage: "tapis://cloud.data/corral/tacc/aci/CEP/applications/v3/opensees/latest/OpenSees-EXPRESS/opensees_express.zip"
  jobType: "FORK"
  maxJobs: 2147483647
  maxJobsPerUser: 2147483647
  strictFileInputs: True
  uuid: "30cb1fa1-e7c7-44a8-a0e8-d2f64043fc65"
  deleted: False
  created: "2025-02-20T18:41:03.661272Z"
  updated: "2025-07-30T20:19:29.367292Z"
  sharedWithUsers: []
  tags: ["portalName: DesignSafe", "portalName: CEP"]
  jobAttributes: {
    description: None
    dynamicExecSystem: False
    execSystemConstraints: None
    execSystemId: "wma-exec-01"
    execSystemExecDir: "${JobWorkingDir}"
    execSystemInputDir: "${JobWorkingDir}"
    execSystemOutputDir: "${JobWorkingDir}"
    dtnSystemInputDir: "!tapis_not_set"
    dtnSystemOutputDir: "!tapis_not_set"
    execSystemLogicalQueue: None
    archiveSystemId: "cloud.data"
    archiveSystemDir: "/tmp/${JobOwner}/tapis-jobs-archive/${JobCreateDate}/${JobName}-${JobUUID}"
    archiveOnAppError: True
    isMpi: False
    mpiCmd: None
    cmdPrefix: None
    nodeCount: 1
    coresPerNode: 1
    memoryMB: 100
    maxMinutes: 1440
    fileInputs: [
      {
        name: "Input Directory"
        description: "Input directory that includes the tcl script as well as any other required files. Example input is in tapis://designsafe.storage.community/app_examples/opensees/OpenSeesEXPRESS"
        inputMode: "REQUIRED"
        autoMountLocal: True
        envKey: "inputDirectory"
        sourceUrl: None
        targetPath: "*"
        notes: {
          selectionMode: "directory"
        }
      }
    ]
    fileInputArrays: []
    subscriptions: []
    tags: []
    parameterSet: {
      appArgs: []
      containerArgs: []
      schedulerOptions: []
      envVariables: [
        {
          key: "mainProgram"
          value: "OpenSees"
          description: "Choose the OpenSees binary to use."
          inputMode: "REQUIRED"
          notes: {
            label: "Main Program"
            enum_values: [
              {
                OpenSees: "OpenSees"
              }
              {
                OpenSeesSP: "OpenSeesSP"
              }
            ]
          }
        }
        {
          key: "tclScript"
          value: ""
          description: "The filename of the OpenSees TCL script to execute, e.g. "freeFieldEffective.tcl"."
          inputMode: "REQUIRED"
          notes: {
            label: "Main Script"
            inputType: "fileInput"
          }
        }
      ]
      archiveFilter: {
        includeLaunchFiles: True
        includes: []
        excludes: ["opensees-express.zip", "tapisjob.env"]
      }
      logConfig: {
        stdoutFilename: ""
        stderrFilename: ""
      }
    }
  }
  notes: {
    icon: "OpenSees"
    label: "OpenSees-EXPRESS (VM)"
    helpUrl: "https://www.designsafe-ci.org/user-guide/tools/simulation/#opensees-user-guide"
    category: "Simulation"
    isInteractive: False
    hideNodeCountAndCoresPerNode: True
  }
}
########################################

Create a Job input#

# Initialize our input
TapisInput = {}

Break app schema into detailed parts and review them for possible input arguments#

# Convert TapisResults Objects to dictionaries
app_MetaData = thisAppData_MP.__dict__
# Review the keys to see if there is any input we are interested in. 
# You can view the full schema above to see the values
# print('app_MetaData.keys',app_MetaData.keys())
dictKeys = []
TapisResultKeys = []
listKeys = []
print('*** MAIN INPUT***')
for thisKey,thisValue in app_MetaData.items():
    if isinstance(thisValue, dict):
        dictKeys.append(thisKey)
    if isinstance(thisValue, TapisResult):
        TapisResultKeys.append(thisKey)
    elif isinstance(thisValue, list):
        listKeys.append(thisKey)
    else:
        print(f'{thisKey} = {thisValue}')
        
print('\n--- Nested Objects---')
print('dict-type input keys',dictKeys)
print('list-type input keys',listKeys)
print('TapisResult-type input keys',TapisResultKeys)
*** MAIN INPUT***
sharedAppCtx = wma_prtl
isPublic = True
tenant = designsafe
id = opensees-express
version = latest
description = OpenSees-EXPRESS provides users with a sequential OpenSees interpreter. It is ideal to run small sequential scripts on DesignSafe resources freeing up your own machine.
owner = wma_prtl
enabled = True
versionEnabled = True
locked = False
runtime = ZIP
runtimeVersion = None
runtimeOptions = None
containerImage = tapis://cloud.data/corral/tacc/aci/CEP/applications/v3/opensees/latest/OpenSees-EXPRESS/opensees_express.zip
jobType = FORK
maxJobs = 2147483647
maxJobsPerUser = 2147483647
strictFileInputs = True
uuid = 30cb1fa1-e7c7-44a8-a0e8-d2f64043fc65
deleted = False
created = 2025-02-20T18:41:03.661272Z
updated = 2025-07-30T20:19:29.367292Z

--- Nested Objects---
dict-type input keys []
list-type input keys ['sharedWithUsers', 'tags']
TapisResult-type input keys ['jobAttributes', 'notes']

Check App Basics#

A few things to check here:

  1. app name and version are what you want

  2. isPublic = True (you can use the app)

  3. if isPublic = False, make sure owner = usename

  4. enabled = True (app is usable)

  5. deleted = False (it exists)

  6. read the description


print(f'***** list-type INPUT ****')
if len(listKeys)>0:
    for thisKey in listKeys:
        thisList = app_MetaData[thisKey]
        print(f'{thisKey} : {thisList}')
else: 
    print('-none')
***** list-type INPUT ****
sharedWithUsers : []
tags : ['portalName: DesignSafe', 'portalName: CEP']
# Nothing related to input in the lists.

print(f'***** dict-type INPUT ****')
if len(dictKeys)>0:
    for thisKey in dictKeys:
        thisDict = app_MetaData[thisKey]
        print(f'{thisKey} : {thisDict}')
else: 
    print('there are no dictionaries within jobAttributes, as expected, since Tapis works with TapisResults objects instead')
***** dict-type INPUT ****
there are no dictionaries within jobAttributes, as expected, since Tapis works with TapisResults objects instead

print(f'***** TapisResult-type INPUT ****')
TR_dictKeys = {}
TR_TapisResultKeys = {}
TR_listKeys = {}
for hereKey in TapisResultKeys:
    print(f'\n*** {hereKey} ***')
    thisTapisResult = app_MetaData[hereKey]
    thisTapisResult_dict = thisTapisResult.__dict__
    TR_dictKeys[hereKey] = []
    TR_TapisResultKeys[hereKey] = []
    TR_listKeys[hereKey] = []
    for thisKey,thisValue in thisTapisResult_dict.items():
        if isinstance(thisValue, dict):
            TR_dictKeys[hereKey].append(thisKey)
        if isinstance(thisValue, TapisResult):
            TR_TapisResultKeys[hereKey].append(thisKey)
        elif isinstance(thisValue, list):
            TR_listKeys[hereKey].append(thisKey)
        else:
            print(f'  {thisKey} = {thisValue}')
    print(f'\n  --- {hereKey} -- Nested Objects---')
    print(f'   dict-type input keys',TR_dictKeys[hereKey])
    print(f'   list-type input keys',TR_listKeys[hereKey])
    print(f'   TapisResult-type input keys',TR_TapisResultKeys[hereKey])
    print('')
***** TapisResult-type INPUT ****

*** jobAttributes ***
  description = None
  dynamicExecSystem = False
  execSystemConstraints = None
  execSystemId = wma-exec-01
  execSystemExecDir = ${JobWorkingDir}
  execSystemInputDir = ${JobWorkingDir}
  execSystemOutputDir = ${JobWorkingDir}
  dtnSystemInputDir = !tapis_not_set
  dtnSystemOutputDir = !tapis_not_set
  execSystemLogicalQueue = None
  archiveSystemId = cloud.data
  archiveSystemDir = /tmp/${JobOwner}/tapis-jobs-archive/${JobCreateDate}/${JobName}-${JobUUID}
  archiveOnAppError = True
  isMpi = False
  mpiCmd = None
  cmdPrefix = None
  nodeCount = 1
  coresPerNode = 1
  memoryMB = 100
  maxMinutes = 1440

  --- jobAttributes -- Nested Objects---
   dict-type input keys []
   list-type input keys ['fileInputs', 'fileInputArrays', 'subscriptions', 'tags']
   TapisResult-type input keys ['parameterSet']


*** notes ***
  icon = OpenSees
  label = OpenSees-EXPRESS (VM)
  helpUrl = https://www.designsafe-ci.org/user-guide/tools/simulation/#opensees-user-guide
  category = Simulation
  isInteractive = False
  hideNodeCountAndCoresPerNode = True

  --- notes -- Nested Objects---
   dict-type input keys []
   list-type input keys []
   TapisResult-type input keys []

notes {}#

notes is just informational.

jobAttributes {}#

This is the content we will be submitting to the app.

jobAttributes has valuable input arguments as well as nested ones.

  1. We need to initialize this json object in our TapisInput.

  2. Let’s look at the high-level input

  3. Let’s look at the nested content

  1. Initialize TapisInput[‘jobAttributes’]

TapisInput['jobAttributes'] = {}
# start this input with app id and version to jobAttributes since that is what we send to tapis
TapisInput['jobAttributes']["name"] = f'My first Tapis Job on {appId}'
TapisInput['jobAttributes']["appId"] = appId
TapisInput['jobAttributes']["appVersion"] = appVersion
  1. Look at high-level variables to see if there is anything of value

  • This is where you find information about where the job is run (execSystemId)

  • In HPC applications this is where you define the SLURM input on queues and nodes, etc

# we see that OpenSees-Express is NOT run on stampede3, but on a Virtual Machine, no need to specify it
# TapisInput['jobAttributes']['execSystemId'] = 'wma-exec-01'
# slurmm-job input
# there is NO SLURM-JOB INPUT for Opensees Express, but we do have maxMinutes
TapisInput['jobAttributes']['maxMinutes'] = 7

3. Let’s dig into the first level – app.jobAttributes#

# Let's extract and study jobAttributes
myKey = 'jobAttributes'
app_jobAttributes = app_MetaData[myKey].__dict__
# we have already extracted the contents of this dict

print(f'*****  {myKey} dict-type Input ****')
if len(TR_dictKeys[myKey])>0:
    for thisKey in TR_dictKeys[myKey]:
        thisDict = app_jobAttributes[thisKey]
        print(f'{myKey}.{thisKey} : {thisDict}')
else:
    print('there are no dictionaries within jobAttributes, as expected, since Tapis works with TapisResults objects instead')
*****  jobAttributes dict-type Input ****
there are no dictionaries within jobAttributes, as expected, since Tapis works with TapisResults objects instead

print(f'*****  {myKey} list-type input****')
if len(TR_listKeys[myKey])>0:
    for thisKey in TR_listKeys[myKey]:
        thisList = app_jobAttributes[thisKey]
        if len(thisList)>0:
            print(f'  {thisKey} = ')
            print('    [')
            for thisValue in thisList:
                if isinstance(thisValue, TapisResult):
                    thisValue = thisValue.__dict__
                print(f'      {thisValue}')
            print('    ]')
        else:
            print(f'  {thisKey}: {thisList}')
else:
    print('none')
*****  jobAttributes list-type input****
  fileInputs = 
    [
      {'name': 'Input Directory', 'description': 'Input directory that includes the tcl script as well as any other required files. Example input is in tapis://designsafe.storage.community/app_examples/opensees/OpenSeesEXPRESS', 'inputMode': 'REQUIRED', 'autoMountLocal': True, 'envKey': 'inputDirectory', 'notes': 
selectionMode: directory, 'sourceUrl': None, 'targetPath': '*'}
    ]
  fileInputArrays: []
  subscriptions: []
  tags: []

fileInputs#

Here we find that jobAttributes.fileInputs is a REQUIRED input.

In this case we need to get the tapisURI for our input directory.

Storage SystemTapis & Tapis Base Path in URI format#

this is the very first part of your path, just above your home folder.

Options:

  • CommunityData

  • Published

The following options are user or project-dependent, and require unique path input.

The following option requires additional user-dependent input:

  • MyData

The following option requires additional user- and system- dependent input:

  • Work

The following option requires additional project-dependent input:

  • MyProjects

You can obtain a dependent tapis-URI path by performing the first step of submitting an OpenSeesMP job at the app portal: https://www.designsafe-ci.org/workspace/opensees-mp-s3

storage_system = 'MyData' # options: Community,MyData,Published,MyProjects,Work/stampede3,Work/frontera,Work/ls6
storage_system_baseURL = OpsUtils.get_user_path_tapis_uri(t,storage_system)

print('storage_system_baseURL:',storage_system_baseURL)
found paths file: /home/jupyter/MyData/.tapis_user_paths.json
storage_system_baseURL: tapis://designsafe.storage.default/silvia
input_folder = '_ToCommunityData/OpenSees/TrainingMaterial/training-OpenSees-on-DesignSafe/Examples_OpenSees/BasicExamples'
sourceUrl = f'{storage_system_baseURL}/{input_folder}'
print('sourceUrl',sourceUrl)
sourceUrl tapis://designsafe.storage.default/silvia/_ToCommunityData/OpenSees/TrainingMaterial/training-OpenSees-on-DesignSafe/Examples_OpenSees/BasicExamples
TapisInput['jobAttributes']['fileInputs'] = [{'name': 'Input Directory','sourceUrl':sourceUrl}]; # notice that it is a list!

print(f'***** {myKey} TapisResult-type input ****')
if len(TR_TapisResultKeys[myKey])>0:
    for hereKey in TR_TapisResultKeys[myKey]:
        print(f'\n*** {hereKey} ***')
        thisTapisResult = app_jobAttributes[hereKey]
        thisTapisResult_dict = thisTapisResult.__dict__
        TR_dictKeys[hereKey] = []
        TR_TapisResultKeys[hereKey] = []
        TR_listKeys[hereKey] = []
        for thisKey,thisValue in thisTapisResult_dict.items():
            if isinstance(thisValue, dict):
                TR_dictKeys[hereKey].append(thisKey)
            if isinstance(thisValue, TapisResult):
                TR_TapisResultKeys[hereKey].append(thisKey)
            elif isinstance(thisValue, list):
                TR_listKeys[hereKey].append(thisKey)
            else:
                print(f'  {myKey}.{thisKey} = {thisValue}')
        print(f'\n  --- {hereKey} -- Nested Objects---')
        print(f'   {myKey}.{hereKey} dict-type input keys',TR_dictKeys[hereKey])
        print(f'   {myKey}.{hereKey} list-type input keys',TR_listKeys[hereKey])
        print(f'   {myKey}.{hereKey} TapisResult-type input keys',TR_TapisResultKeys[hereKey])
        print('')
else:
    print('-none')
***** jobAttributes TapisResult-type input ****

*** parameterSet ***

  --- parameterSet -- Nested Objects---
   jobAttributes.parameterSet dict-type input keys []
   jobAttributes.parameterSet list-type input keys ['appArgs', 'containerArgs', 'schedulerOptions', 'envVariables']
   jobAttributes.parameterSet TapisResult-type input keys ['archiveFilter', 'logConfig']

app.jobAttributes.parameterSet is the interesting one#

# Let's extract and study jobAttributes
myKey = 'parameterSet'
app_parameterSet = app_jobAttributes[myKey].__dict__
# we have already extracted the contents of this dict
## parameterSet is a dictionary (TapisResult):
TapisInput['jobAttributes'][myKey] = {}

print(f'*****  {myKey} dict-type Input ****')
if len(TR_dictKeys[myKey])>0:
    for thisKey in TR_dictKeys[myKey]:
        thisDict = app_parameterSet[thisKey]
        print(f'{myKey}.{thisKey} : {thisDict}')
else:
    print('there are no dictionaries within app_parameterSet, as expected, since Tapis works with TapisResults objects instead')
*****  parameterSet dict-type Input ****
there are no dictionaries within app_parameterSet, as expected, since Tapis works with TapisResults objects instead

print(f'*****  {myKey} list-type input****')
if len(TR_listKeys[myKey])>0:
    for thisKey in TR_listKeys[myKey]:
        thisList = app_parameterSet[thisKey]
        if len(thisList)>0:
            print(f'  {thisKey} = ')
            print('    [')
            for thisValue in thisList:
                if isinstance(thisValue, TapisResult):
                    thisValue = thisValue.__dict__
                print(f'      {str(thisValue)}')
            print('    ]')
        else:
            print(f'  {thisKey}: {thisList}')
else:
    print('none')
*****  parameterSet list-type input****
  appArgs: []
  containerArgs: []
  schedulerOptions: []
  envVariables = 
    [
      {'key': 'mainProgram', 'value': 'OpenSees', 'description': 'Choose the OpenSees binary to use.', 'inputMode': 'REQUIRED', 'notes': 
enum_values: [
OpenSees: OpenSees, 
OpenSeesSP: OpenSeesSP]
label: Main Program}
      {'key': 'tclScript', 'value': '', 'description': 'The filename of the OpenSees TCL script to execute, e.g. "freeFieldEffective.tcl".', 'inputMode': 'REQUIRED', 'notes': 
inputType: fileInput
label: Main Script}
    ]

While the opensees-mp-s3 and opensees-sp-s3 apps use appArgs, this app uses envVariables for the input.

It looks like enum_values in notes is a list for a pull-down menu, and, interestingly, it is missing OpenSeesMP – from the app definition in github, they Removed because OpenSeesMP is unable to use multiple cores, essentially making it SP

print('review each of these items:', TR_listKeys[myKey])
review each of these items: ['appArgs', 'containerArgs', 'schedulerOptions', 'envVariables']

app.jobAttributes.parameterSet.appArgs#

thisKey = 'appArgs'
TapisInput['jobAttributes']['parameterSet'][thisKey] = [] # it's a list, initialize as a list!
print(f'** app.jobAttributes.parameterSet.{thisKey} **')
if len(app_parameterSet[thisKey])>0:
    for thisItem in app_parameterSet[thisKey]:
        print(thisItem)
else:
    print('.none.')
** app.jobAttributes.parameterSet.appArgs **
.none.

Nothing to input here!

app.jobAttributes.parameterSet.containerArgs#

thisKey = 'containerArgs'
TapisInput['jobAttributes']['parameterSet'][thisKey] = [] # it's a list, initialize as a list!
print(f'** app.jobAttributes.parameterSet.{thisKey} **')
if len(app_parameterSet[thisKey])>0:
    for thisItem in app_parameterSet[thisKey]:
        print(thisItem)
else:
    print('.none.')
** app.jobAttributes.parameterSet.containerArgs **
.none.
# do nothing for this app.

app.jobAttributes.parameterSet.schedulerOptions#

thisKey = 'schedulerOptions'
TapisInput['jobAttributes']['parameterSet'][thisKey] = [] # it's a list, initialize as a list!
print(f'** app.jobAttributes.parameterSet.{thisKey} **')
if len(app_parameterSet[thisKey])>0:
    for thisItem in app_parameterSet[thisKey]:
        print(thisItem)
else:
    print('.none.')
** app.jobAttributes.parameterSet.schedulerOptions **
.none.

There is no scheduler nor allocation used in OpenSees-Express.

NO NEED to add allocation to the scheduler option – not shown in the app schema#

# user_allocation = '-A DS-HPC1'; # you get this code from your allocation dashboard
# TapisInput['jobAttributes']['parameterSet']['schedulerOptions'].append({'name': 'TACC Allocation', 'arg': user_allocation})

app.jobAttributes.parameterSet.envVariables#

thisKey = 'envVariables'
TapisInput['jobAttributes']['parameterSet'][thisKey] = [] # it's a list, initialize as a list!
print(f'** app.jobAttributes.parameterSet.{thisKey} **')
if len(app_parameterSet[thisKey])>0:
    for thisItem in app_parameterSet[thisKey]:
        print(thisItem)
else:
    print('.none.')
** app.jobAttributes.parameterSet.envVariables **

description: Choose the OpenSees binary to use.
inputMode: REQUIRED
key: mainProgram
notes: 
enum_values: [
OpenSees: OpenSees, 
OpenSeesSP: OpenSeesSP]
label: Main Program
value: OpenSees

description: The filename of the OpenSees TCL script to execute, e.g. "freeFieldEffective.tcl".
inputMode: REQUIRED
key: tclScript
notes: 
inputType: fileInput
label: Main Script
value: 

YES this app does have input here, we need to specify the execution program and the tcl script#

Note that the names of the labels for the keys here are key and value , not name and arg, and the keys are different from opensees-mp-s3

Make sure your tcl script is for a sequential analysis.

TapisInput['jobAttributes']['parameterSet']['envVariables'].append({"key": "mainProgram", "value": 'OpenSees'})
TapisInput['jobAttributes']['parameterSet']['envVariables'].append({"key": "tclScript", "value": 'Ex1a.Canti2D.Push.tcl'})

print(f'***** {myKey} TapisResult-type input ****')
if len(TR_TapisResultKeys[myKey])>0:
    for hereKey in TR_TapisResultKeys[myKey]:
        print(f'\n*** {hereKey} ***')
        thisTapisResult = app_parameterSet[hereKey]
        thisTapisResult_dict = thisTapisResult.__dict__
        TR_dictKeys[hereKey] = []
        TR_TapisResultKeys[hereKey] = []
        TR_listKeys[hereKey] = []
        for thisKey,thisValue in thisTapisResult_dict.items():
            if isinstance(thisValue, dict):
                TR_dictKeys[hereKey].append(thisKey)
            if isinstance(thisValue, TapisResult):
                TR_TapisResultKeys[hereKey].append(thisKey)
            elif isinstance(thisValue, list):
                TR_listKeys[hereKey].append(thisKey)
            else:
                print(f'  {myKey}.{thisKey} = {thisValue}')
        print(f'\n  --- {hereKey} -- Nested Objects---')
        print(f'   {myKey}.{hereKey} dict-type input keys',TR_dictKeys[hereKey])
        print(f'   {myKey}.{hereKey} list-type input keys',TR_listKeys[hereKey])
        print(f'   {myKey}.{hereKey} TapisResult-type input keys',TR_TapisResultKeys[hereKey])
        print('')
else:
    print('-none')
***** parameterSet TapisResult-type input ****

*** archiveFilter ***
  parameterSet.includeLaunchFiles = True

  --- archiveFilter -- Nested Objects---
   parameterSet.archiveFilter dict-type input keys []
   parameterSet.archiveFilter list-type input keys ['includes', 'excludes']
   parameterSet.archiveFilter TapisResult-type input keys []


*** logConfig ***
  parameterSet.stdoutFilename = 
  parameterSet.stderrFilename = 

  --- logConfig -- Nested Objects---
   parameterSet.logConfig dict-type input keys []
   parameterSet.logConfig list-type input keys []
   parameterSet.logConfig TapisResult-type input keys []
# nothing of interest to us here...

We are done with our tapis-job input#

print('TapisInput')
display(TapisInput)
TapisInput
{'jobAttributes': {'name': 'My first Tapis Job on opensees-express',
  'appId': 'opensees-express',
  'appVersion': 'latest',
  'maxMinutes': 7,
  'fileInputs': [{'name': 'Input Directory',
    'sourceUrl': 'tapis://designsafe.storage.default/silvia/_ToCommunityData/OpenSees/TrainingMaterial/training-OpenSees-on-DesignSafe/Examples_OpenSees/BasicExamples'}],
  'parameterSet': {'appArgs': [],
   'containerArgs': [],
   'schedulerOptions': [],
   'envVariables': [{'key': 'mainProgram', 'value': 'OpenSees'},
    {'key': 'tclScript', 'value': 'Ex1a.Canti2D.Push.tcl'}]}}}
print('TapisInput.jobAttributes')
display(TapisInput['jobAttributes'])
TapisInput.jobAttributes
{'name': 'My first Tapis Job on opensees-express',
 'appId': 'opensees-express',
 'appVersion': 'latest',
 'maxMinutes': 7,
 'fileInputs': [{'name': 'Input Directory',
   'sourceUrl': 'tapis://designsafe.storage.default/silvia/_ToCommunityData/OpenSees/TrainingMaterial/training-OpenSees-on-DesignSafe/Examples_OpenSees/BasicExamples'}],
 'parameterSet': {'appArgs': [],
  'containerArgs': [],
  'schedulerOptions': [],
  'envVariables': [{'key': 'mainProgram', 'value': 'OpenSees'},
   {'key': 'tclScript', 'value': 'Ex1a.Canti2D.Push.tcl'}]}}
submitted_job = t.jobs.submitJob(**TapisInput['jobAttributes'])
print(submitted_job)
_fileInputsSpec: None
_parameterSetModel: None
appId: opensees-express
appVersion: latest
archiveCorrelationId: None
archiveOnAppError: True
archiveSystemDir: /tmp/silvia/tapis-jobs-archive/2025-09-19Z/My first Tapis Job on opensees-express-f708c941-f974-454d-b869-3687568aba4e-007
archiveSystemId: cloud.data
archiveTransactionId: None
blockedCount: 0
cmdPrefix: None
condition: None
coresPerNode: 1
created: 2025-09-19T18:15:50.754968765Z
createdby: silvia
createdbyTenant: designsafe
description: opensees-express-latest submitted by silvia@designsafe
dtnInputCorrelationId: None
dtnInputTransactionId: None
dtnOutputCorrelationId: None
dtnOutputTransactionId: None
dtnSystemId: None
dtnSystemInputDir: None
dtnSystemOutputDir: None
dynamicExecSystem: False
ended: None
execSystemConstraints: None
execSystemExecDir: /tmp/tapis-silvia/2025-09-19Z/f708c941-f974-454d-b869-3687568aba4e-007
execSystemId: wma-exec-01
execSystemInputDir: /tmp/tapis-silvia/2025-09-19Z/f708c941-f974-454d-b869-3687568aba4e-007
execSystemLogicalQueue: None
execSystemOutputDir: /tmp/tapis-silvia/2025-09-19Z/f708c941-f974-454d-b869-3687568aba4e-007
fileInputs: [{"name":"Input Directory","description":"Input directory that includes the tcl script as well as any other required files. Example input is in tapis://designsafe.storage.community/app_examples/opensees/OpenSeesEXPRESS","envKey":"inputDirectory","autoMountLocal":true,"sourceUrl":"tapis://designsafe.storage.default/silvia/_ToCommunityData/OpenSees/TrainingMaterial/training-OpenSees-on-DesignSafe/Examples_OpenSees/BasicExamples","targetPath":"BasicExamples","notes":"{\"selectionMode\":\"directory\"}","optional":false,"srcSharedAppCtx":"","destSharedAppCtx":"wma_prtl"}]
id: 0
inputCorrelationId: None
inputTransactionId: None
isMpi: False
jobType: FORK
lastMessage: Job created
lastUpdated: 2025-09-19T18:15:50.754968765Z
maxMinutes: 7
memoryMB: 100
mpiCmd: None
name: My first Tapis Job on opensees-express
nodeCount: 1
notes: {"icon":"OpenSees","label":"OpenSees-EXPRESS (VM)","helpUrl":"https://www.designsafe-ci.org/user-guide/tools/simulation/#opensees-user-guide","category":"Simulation","isInteractive":false,"hideNodeCountAndCoresPerNode":true}
owner: silvia
parameterSet: {"appArgs":[],"containerArgs":[],"schedulerOptions":[],"envVariables":[{"key":"_tapisAppId","value":"opensees-express","description":null,"include":null,"notes":null},{"key":"_tapisAppVersion","value":"latest","description":null,"include":null,"notes":null},{"key":"_tapisArchiveOnAppError","value":"true","description":null,"include":null,"notes":null},{"key":"_tapisArchiveSystemDir","value":"/tmp/silvia/tapis-jobs-archive/2025-09-19Z/My first Tapis Job on opensees-express-f708c941-f974-454d-b869-3687568aba4e-007","description":null,"include":null,"notes":null},{"key":"_tapisArchiveSystemId","value":"cloud.data","description":null,"include":null,"notes":null},{"key":"_tapisCoresPerNode","value":"1","description":null,"include":null,"notes":null},{"key":"_tapisDynamicExecSystem","value":"false","description":null,"include":null,"notes":null},{"key":"_tapisEffectiveUserId","value":"silvia","description":null,"include":null,"notes":null},{"key":"_tapisExecSystemExecDir","value":"/tmp/tapis-silvia/2025-09-19Z/f708c941-f974-454d-b869-3687568aba4e-007","description":null,"include":null,"notes":null},{"key":"_tapisExecSystemId","value":"wma-exec-01","description":null,"include":null,"notes":null},{"key":"_tapisExecSystemInputDir","value":"/tmp/tapis-silvia/2025-09-19Z/f708c941-f974-454d-b869-3687568aba4e-007","description":null,"include":null,"notes":null},{"key":"_tapisExecSystemOutputDir","value":"/tmp/tapis-silvia/2025-09-19Z/f708c941-f974-454d-b869-3687568aba4e-007","description":null,"include":null,"notes":null},{"key":"_tapisJobCreateDate","value":"2025-09-19Z","description":null,"include":null,"notes":null},{"key":"_tapisJobCreateTime","value":"18:15:50.754968765Z","description":null,"include":null,"notes":null},{"key":"_tapisJobCreateTimestamp","value":"2025-09-19T18:15:50.754968765Z","description":null,"include":null,"notes":null},{"key":"_tapisJobName","value":"My first Tapis Job on opensees-express","description":null,"include":null,"notes":null},{"key":"_tapisJobOwner","value":"silvia","description":null,"include":null,"notes":null},{"key":"_tapisJobUUID","value":"f708c941-f974-454d-b869-3687568aba4e-007","description":null,"include":null,"notes":null},{"key":"_tapisJobWorkingDir","value":"/tmp/tapis-silvia/2025-09-19Z/f708c941-f974-454d-b869-3687568aba4e-007","description":null,"include":null,"notes":null},{"key":"_tapisMaxMinutes","value":"7","description":null,"include":null,"notes":null},{"key":"_tapisMemoryMB","value":"100","description":null,"include":null,"notes":null},{"key":"_tapisNodes","value":"1","description":null,"include":null,"notes":null},{"key":"_tapisStderrFilename","value":"tapisjob.out","description":null,"include":null,"notes":null},{"key":"_tapisStdoutFilename","value":"tapisjob.out","description":null,"include":null,"notes":null},{"key":"_tapisSysBatchScheduler","value":"SLURM","description":null,"include":null,"notes":null},{"key":"_tapisSysHost","value":"wma-exec-01.tacc.utexas.edu","description":null,"include":null,"notes":null},{"key":"_tapisSysRootDir","value":"/","description":null,"include":null,"notes":null},{"key":"_tapisTenant","value":"designsafe","description":null,"include":null,"notes":null},{"key":"inputDirectory","value":"BasicExamples","description":"EnvKey from input file: Input Directory","include":true,"notes":"{}"},{"key":"mainProgram","value":"OpenSees","description":"Choose the OpenSees binary to use.","include":true,"notes":"{\"label\":\"Main Program\",\"enum_values\":[{\"OpenSees\":\"OpenSees\"},{\"OpenSeesSP\":\"OpenSeesSP\"}]}"},{"key":"tclScript","value":"Ex1a.Canti2D.Push.tcl","description":"The filename of the OpenSees TCL script to execute, e.g. \"freeFieldEffective.tcl\".","include":true,"notes":"{\"label\":\"Main Script\",\"inputType\":\"fileInput\"}"}],"archiveFilter":{"includes":[],"excludes":["opensees-express.zip","tapisjob.env"],"includeLaunchFiles":true},"logConfig":{"stdoutFilename":"tapisjob.out","stderrFilename":"tapisjob.out"}}
remoteChecksFailed: 0
remoteChecksSuccess: 0
remoteEnded: None
remoteJobId: None
remoteJobId2: None
remoteLastStatusCheck: None
remoteOutcome: None
remoteQueue: None
remoteResultInfo: None
remoteStarted: None
remoteSubmitRetries: 0
remoteSubmitted: None
sharedAppCtx: wma_prtl
sharedAppCtxAttribs: ['SAC_EXEC_SYSTEM_ID', 'SAC_ARCHIVE_SYSTEM_ID', 'SAC_EXEC_SYSTEM_INPUT_DIR', 'SAC_EXEC_SYSTEM_EXEC_DIR', 'SAC_EXEC_SYSTEM_OUTPUT_DIR', 'SAC_ARCHIVE_SYSTEM_DIR']
stageAppCorrelationId: None
stageAppTransactionId: None
status: PENDING
subscriptions: []
tags: []
tapisQueue: tapis.jobq.submit.DefaultQueue
tenant: designsafe
trackingId: None
uuid: f708c941-f974-454d-b869-3687568aba4e-007
visible: True

Once you have no errors you are done submitting the job.#

go to the job-status page on the web portal to monitor the your job.#

https://www.designsafe-ci.org/workspace/history

You will, likely, have to debug your OpenSees script…..

NOTE ONCE THE JOB HAS COMPLETED, go to view Output and pay attention to the location (path) of the output, it may not be in MyData, as expected.

print('done')
done