sábado, 3 de noviembre de 2012

Revisiting Python and SAP (With PyRFC)


A couple of days back Srdjan Boskovic wrote a blog called Python / ABAP Stack. This for sure, bring me back memories as I already played a lot with sapnwrfc from Piers Harding.

After some email exchanges with Srdjan and Jonas Kunze I was able to set up and start working with it.

You may ask...why another Python to SAP connector? Simply, while Pier's one is awesome, it lacked some features that SAP needed for internal development, so this guys take the hard work of making from the scratch exactly what they needed...one of the beauties of being a developer...you can grab an idea...a turn it into a full blown application or RFC connector in this case...

As a way to introduce PyRFC, the best example is always showing something with the flights tables...which are always present in every ERP installation and as I have it used before on SAP HANA and Python? Yes Sir!, Bottle seemed to be a good option...because you don't want to see more command line screen, don't you?

I will show the code first and the I will tell you about some of the most significant changes that I can see from using PyRFC (And by the way...I fight myself to make my example better and have some error handling, which is always good, even for humble blogs like this one).


Bottle_PyRFC.py
from bottle import get, post, request, run, redirect
from sapnwrfc2 import Connection, ABAPApplicationError, LogonError
from ConfigParser import ConfigParser
 
conn = ""
 
@get('/login')
def login_form():
    return '''<DIV ALIGN='CENTER'><BR><BR><BR><BR>
                <H1>Python (Bottle) & SAP - using PyRFC</H1>
                <BR><TABLE BORDER='1' BORDERCOLOR='BLUE'
                     BGCOLOR='WHITE'>
                <FORM METHOD='POST'>
                <TR><TD>User</TD><TD>
                <INPUT TYPE='TEXT' NAME='User'></TD></TR>
                <TR><TD>Password</TD>
                <TD><INPUT TYPE='PASSWORD' NAME='Passwd'></TD></TR>
                <TR><TD COLSPAN='2' ALIGN='CENTER'>
                <INPUT TYPE='SUBMIT' value='Log In' NAME='LOG_IN'>
                <INPUT TYPE='RESET' value='Clear'></TD></TR>
                </FORM>
                <TABLE>
              </DIV>'''
 
@post('/login')
def login_submit():
    global conn
    try:
        user = request.forms.get('User')
        passwd = request.forms.get('Passwd')
        config = ConfigParser()
        config.read('sapnwrfc.cfg')
        params_connection = config._sections['connection']
        params_connection["user"] = user
        params_connection["passwd"] = passwd
        conn = Connection(**params_connection)
        redirect("/choose")
    except LogonError:
        redirect("/error")
 
@get('/choose')
def choose_table():
    return '''<CENTER>
                <FORM METHOD='POST'>
                <INPUT TYPE='TEXT' NAME='Table'><BR>
                <INPUT TYPE='SUBMIT' value='Show Table'
                 NAME='Show_Table'>
                </FORM>
              </CENTER>'''
 
@get('/error')
def error():
    output = "<div align='center'><h1>Invalid username or password</h1></div>"
    return output
 
@post('/choose')
def show_table():
    global conn
    fields = []
    fields_name = []
    counter = 0
    table = request.forms.get('Table')
    try:
        tables = conn.call("RFC_READ_TABLE", QUERY_TABLE=table, DELIMITER='|')
        data_fields = tables["DATA"]
        data_names = tables["FIELDS"]
        long_fields = len(data_fields)
        long_names = len(data_names)
 
        for line in range(0, long_fields):
            fields.append(data_fields[line]["WA"].strip())
        for line in range(0, long_names):
            fields_name.append(data_names[line]["FIELDNAME"].strip())
 
         output = "<div align='center'><h1>%s</h1></center>" % table
 
        output += "<table border='1'><tr>"
        for line in range(0, long_names):
            field_name = fields_name[line]
            output += "<th bgcolor='#B8D5F5'> %s </th>" % field_name
        output += "</tr>"
        for line in range(0, long_fields):
            counter += 1
            if(counter % 2 == 0):
                output += "<tr bgcolor='#DCE1E5'>"
            else:
                output += "<tr>"
            data_split = fields[line].split("|")
            for line in range(0, long_names):
                output += "<td> %s </td>" % data_split[line]
            output += "</tr>"
        output += "</table>"
 
     except ABAPApplicationError:
        output = "<div align='center'><h1>Table %s was not found</h1></div>" % table
        return output
 
     return output
    conn.close()
 
run(host='localhost', port=8080)


So, for me PyRFC has some mayor benefits, like the option to catch ABAPApplicationError and LoginError (I assume that Pier's version have it as well, but I never worried to look for it...shame on me), also the way to call the Function Module is very clean, a simple Python function that will receive as parameters the FM name and the parameters, taking from us the need to define each parameter as an attribute of the object. Also, it's really fast and it can be used on the Server side...but we will talk about that later...in other blog...when I got the chance to actually work with it...

Let's run this program and see how it looks...when running it from Python you might need to go to your browser a pass the following link (as we're executing a Bottle application):

http://localhost:8080/login


As always, was very enjoyable to work with Python, and of course, when mixing it my PyRFC the fun exceed my expectations as right now it's almost 8:00 pm on Saturday night...and I'm posting a blog of what I worked on almost all afternoon...programming is fun...don't forget it...

Greetings,

Blag.