Route Scripting

When the standard routing of bots does not fit your needs, use routescripts. Routescripts are python programs.

There are 2 types of routescripts:

  1. User exits: at certain points in a route, bots calls your user exit. See overview of exit points. Most common usage is pre-processing an edi file, see recipes.
  2. Your script takes over the whole route. This is done by using function main() in your routescript. See Example.

How to set up a route-script

  1. Use bots-monitor to add a new route.
  2. Make a routescript with the same name as the routeID in bots/usersys/routescripts/<routeid>.py

Overview Exit Points

These examples show all of the available exit points for route-scripts

def start(routedict,*args,**kwargs):
    # run before anything is done is route.
    print routedict['idroute'],'start'

def preincommunication(routedict,*args,**kwargs):
    # if there is a fromchannel: run before fromchannel communication.
    print routedict['idroute'],'preincommunication'

def postincommunication(routedict,*args,**kwargs):
    # if there is a fromchannel: run after fromchannel communication.
    print routedict['idroute'],'postincommunication'

def pretranslation(routedict,*args,**kwargs):
    # if translation is done in route: run before translation.
    print routedict['idroute'],'pretranslation'

def posttranslation(routedict,*args,**kwargs):
    # if translation is done in route: run after translation.
    print routedict['idroute'],'posttranslation'

def premerge(routedict,*args,**kwargs):
    # if there is a outchannel: run before merging.
    print routedict['idroute'],'premerge'

def postmerge(routedict,*args,**kwargs):
    # if there is a outchannel: run after merging.
    print routedict['idroute'],'postmerge'

def preoutcommunication(routedict,*args,**kwargs):
    # if there is a outchannel: run before outchannel communication.
    print routedict['idroute'],'preoutcommunication'

def postoutcommunication(routedict,*args,**kwargs):
    # if there is a outchannel: run after outchannel communication.
    # eg. call an external program to process files just sent into ERP system
    print routedict['idroute'],'postoutcommunication'

def end(routedict,*args,**kwargs):
    # after everything is done in route
    print routedict['idroute'],'end'

Preprocessing and Postprocessing Recipies

Some recipes for preprocessing edi files. Plugin demo_preprocessing at the bots sourceforge site demonstrates preprocessing.

Example 1: Discard input files that are too small

import os
import bots.preprocess as preprocess
from bots.botsconfig import *

def postincommunication(routedict,*args,**kwargs):
    ''' function is called after the communication in the route.'''
    preprocess.preprocess(routedict=routedict,function=discard_file)

def discard_file(ta_from,endstatus,*args,**kwargs):
    ''' discard files that are to small (zero files)'''
    ta_from.synall()
    filesize = ta_from.filesize
    if filesize < 100:    #filesize in bytes
        ta_from.update(statust=DONE)                       #statust=DONE: bots discards file, gives no errors.
    else:
        ta_to = ta_from.copyta(status=endstatus)           #make new transaction for bots database
        ta_to.update(statust=OK,filename=ta_from.filename) #update outmessage transaction (same) filename

Example 2: Extract data from PDF file (to csv)

# Extract data from PDF file (to csv)
# x_group: group text closer than this as one field (default 10)
# y_group: group lines closer than this as one line (default 5)
# password: if required

import bots.preprocess as preprocess

def postincommunication(routedict,*args,**kwargs):
    preprocess.preprocess(routedict,preprocess.extractpdf,x_group=12,y_group=3,password='secret')

Example 3: Manipulate records without BOTSID

import bots.preprocess as preprocess
import bots.botslib as botslib
import bots.botsglobal as botsglobal
from bots.botsconfig import *

def postincommunication(routedict,*args,**kwargs):
    preprocess.preprocess(routedict,custom_preprocess)

def custom_preprocess(ta_from,endstatus,*args,**kwargs):
    try:
        # copy ta for preprocessing
        ta_to = ta_from.copyta(status=endstatus)

        # open the files
        infile = botslib.opendata(ta_from.filename,'r')
        tofile = botslib.opendata(str(ta_to.idta),'wb')

        # preprocessing: read infile, write tofile
        # This file has headers and lines, but no field that can be used for BOTSID.
        # Determine the line type from the data, and add HDR or LIN in first column
        # Text heading lines and blank lines are omitted
        for line in infile:
            if '\tAU' in line:
                tofile.write('HDR\t' + line)
            elif ('\tWAIT' in line or
                  '\tFULL' in line or
                  '\tEMPTY' in line):
                tofile.write('LIN\t' + line)

        infile.close()
        tofile.close()

        ta_to.update(statust=OK,filename=str(ta_to.idta)) #update outmessage transaction with ta_info;
    except:
        txt=botslib.txtexc()
        botsglobal.logger.error(u'Custom preprocess failed. Error:\n%s',txt)
        raise botslib.InMessageError(u'Custom preprocess failed. Error:\n$error',error=txt)

Example 4: Sort input file

import bots.preprocess as preprocess
import bots.botslib as botslib
import bots.botsglobal as botsglobal
from bots.botsconfig import *

def postincommunication(routedict,*args,**kwargs):
    preprocess.preprocess(routedict,sort_file)

def sort_file(ta_from,endstatus,*args,**kwargs):
    try:
        # copy ta for preprocessing
        ta_to = ta_from.copyta(status=endstatus)

        # open the files
        infile = botslib.opendata(ta_from.filename,'r')
        tofile = botslib.opendata(str(ta_to.idta),'wb')

        # sort output
        lines = infile.readlines()
        lines.sort()
        for line in lines:
            tofile.write(line)

        infile.close()
        tofile.close()

        ta_to.update(statust=OK,filename=str(ta_to.idta)) #update outmessage transaction with ta_info;
    except:
        txt=botslib.txtexc()
        botsglobal.logger.error(u'Sort preprocess failed. Error:\n%s',txt)
        raise botslib.InMessageError(u'Sort preprocess failed. Error:\n$error',error=txt)

Example 5: Postprocessing; Post processing works the same way as pre processing, except it is done before out communication.

import bots.preprocess as preprocess
import bots.botslib as botslib
import bots.botsglobal as botsglobal
from bots.botsconfig import *

def preoutcommunication(routedict,*args,**kwargs):
    preprocess.postprocess(routedict,split_lines)

def split_lines(ta_from,endstatus,,*args,**kwargs):
    try:
        # copy ta for postprocessing, open the files
        ta_to = ta_from.copyta(status=endstatus)
        infile = botslib.opendata(ta_from.filename,'r')
        tofile = botslib.opendata(str(ta_to.idta),'wb')

        # split every line at the first separator (space)
        # output the two parts on separate lines
        for line in infile:
            part = line.partition(' ')
            tofile.write(part[0] + '\n' + part[2])

        # close files and update outmessage transaction with ta_info
        infile.close()
        tofile.close()
        ta_to.update(statust=OK,filename=str(ta_to.idta))

    except:
        txt=botslib.txtexc()
        botsglobal.logger.error(_(u'split_lines postprocess failed. Error:\n%s'),txt)
        raise botslib.OutMessageError(_(u'split_lines postprocess failed. Error:\n$error'),error=txt)

Example 6: Preprocessing an encrypted file

import bots.preprocess as preprocess
import bots.botslib as botslib
import gnupg

# Preprocessing - Decrypt infile using GPG
# Dependencies: python-gnupg-0.3.0
#   botssys/gnugpghome directory, containing:
#     gpg binary files (gpg.exe and iconv.dll)
#     keys (pubring.gpg, secring.gpg, trustdb.gpg)
#     passphrase.txt

def postincommunication(routedict,*args,**kwargs):
    # preprocess to decrypt, then passthrough (no translation)
    preprocess.preprocess(routedict,decrypt_GPG)
    transform.addinfo(change={'status':MERGED},where={'status':FILEIN,'idroute':routedict['idroute']})

def decrypt_GPG(ta_from,endstatus,*args,**kwargs):

    # copy ta for preprocessing
    ta_to = ta_from.copyta(status=endstatus)

    # gnupghome contains the gpg binary files, public/private keys, and passphrase
    gnupghome = botslib.join(botsglobal.ini.get('directories','botssys'),'gnupghome')
    passphrase = open(botslib.join(gnupghome,'passphrase.txt'),'r').read()
    gpgbinary = botslib.join(gnupghome,'gpg.exe')

    # Here is where we do the actual decryption
    gpg = gnupg.GPG(gnupghome=gnupghome,gpgbinary=gpgbinary)
    with botslib.opendata(ta_from.filename,'rb') as input:
        status = gpg.decrypt_file(input, passphrase=passphrase,output=botslib.abspathdata(str(ta_to.idta)))

    # log the results and finish
    botsglobal.logger.debug(status.stderr)
    if status.ok:
        botsglobal.logger.info(status.status)
        ta_to.update(statust=OK,filename=str(ta_to.idta))
    else:
        botsglobal.logger.error(status.status)
        ta_to.update(statust=ERROR,filename=str(ta_to.idta))
        raise PreprocessError(status.status + '\n' + status.stderr)

class PreprocessError(botslib.BotsError):
    pass

Example 7: Preprocessing to ignore/remove XML namespaces; This example changes the default namespace to a namespace prefix (so it is ignored). It also removes a namespace prefix (ENV). You may need to use either or both of these methods, depending on the content of your XML file.

#-------------------------------------------------------------------------------
# preprocess - Remove XML namespaces to simplify grammar and mapping
# Generally Bots does not need to use the xmlns for incoming files
# This example handles both default and prefix namespaces

def postincommunication(routedict):

    def _preprocess(ta_from,endstatus,**argv):

        # copy ta for preprocessing
        ta_to = ta_from.copyta(status=endstatus)

        # open the files
        infile = botslib.opendata(ta_from.filename,'r')
        tofile = botslib.opendata(str(ta_to.idta),'wb')

        for line in infile:
            tofile.write(line.replace('xmlns=','xmlns:NOTUSED=').replace('<ENV:','<').replace('</ENV:','</'))

        # close files and update outmessage transaction
        infile.close()
        tofile.close()
        ta_to.update(statust=OK,filename=str(ta_to.idta))

    preprocess.preprocess(routedict,_preprocess)

Take Over Whole Route

The below example takes oves the the whole route such that only the main() function defined here will be executed as part of the route.

# imports needed for some functions below
from bots.botsconfig import *
import bots.transform as transform
import bots.botsglobal as botsglobal
import bots.botslib as botslib
import bots.cleanup as cleanup
import bots.pluglib as pluglib
import os
import time

def main(routedict,*args,**kwargs):
    # if main is present, it takes over the whole route (nothing is done in route except this function).
    # for example, create a daily route that does a backup and cleanup

    # Prevent this script from accidentally running more than once per day
    if transform.persist_lookup(routedict['idroute'],'run_date') != time.strftime('%Y%m%d'):
        transform.persist_add_update(routedict['idroute'],'run_date',time.strftime('%Y%m%d'))

        # backup directory and subdirectory - one per weekday gives a 7 day rolling backup
        backup_dir = botslib.join(botsglobal.ini.get('directories','botssys'), 'backup')
        makedir(backup_dir)
        backup_dir = botslib.join(backup_dir, time.strftime('%w-%a'))
        makedir(backup_dir)

        # Create a bots backup (as a plugin)
        botsglobal.logger.info('Create a bots backup')
        pluglib.plugoutcore({
             'databaseconfiguration': True,
             'umlists': True,
             'fileconfiguration': True,
             'infiles': False,
             'charset': False,
             'databasetransactions': False,
             'data': False,
             'logfiles': True,
             'config': True,
             'database': True,
             'filename': botslib.join(backup_dir,'bots-backup.zip')})

        # Do cleanup, if scheduled daily
        # In bots.ini set whencleanup=daily (other values are always or never)
        if botsglobal.ini.get('settings','whencleanup','always') == 'daily':
            botsglobal.logger.info(u'Cleanup of database and files')
            cleanup.cleanup()

def makedir(dir):
    try: os.makedirs(dir)
    except: pass