Skip to Content

There are a few blog posts about Python that show how easy it is. Many of them are more from a developer view in my opinion so many basis people may not even consider reading through them. Consequently they will be missing out on a great opportunity to make their lifes easier and their work more efficient.  I hope you enjoy this first part. There will be more depending on the time I have.

The Teaser

Scripting repetitive activities like a system copy or SAP refresh validation or other activities makes perfect sense. The problem is that many of the scripts developed over the years are not very reusable. One customer runs on Unix/Oracle, the next Windows/MSSQL and another perhaps DB2.

Ever since I came across Python I am simply blown away by how simple it is to create complex scripts even including GUIs and SAP integration. One of the refresh scripts I developed at one of my clients was written in VB script for Windows and SQL Server. The script was huge, complicated and very difficult to maintain for the “uninitiated”. The same functionality in Python would have been possible with a script less than 1/10th the number of lines. On top of that it would have been platform and even database independent! It would have also taken only a fraction of the time I required for the VBS script and would have been maintainable by almost to everybody.

For example these lines create a connection object to logon to SAP:

from pyrfc import Connection

conn = Connection(user=<username>,

                  passwd=<password>,

                  ashost=<hostname>,

                  sysnr=<system number>,

                  client=<client>)

Two commands and that’s it. From that point on, “conn” will be the name of the SAP connection object that can be used to interact with the SAP instance and allows you to do many really cool things. For example schedule a program as background job:

result_open = conn.call(‘SUBST_SCHEDULE_BATCHJOB’,

                         JOBNAME=<jobname>,

                         REPNAME=<program>,

                         VARNAME=<variant>,

                         AUTHCKNAM=<username>,

                         SDLSTRTTM=datetime.time(<hour>,<minute>,<second>),

                         STRTIMMED=’X’)

3 lines of code to schedule a background job using a simple script. Let’s look at some of the really good example in the SCN that demonstrate how to use perl for basis tasks: Perl and SAP Adventures, Part 2

The perl script looks like this:

#!/usr/bin/perl -w

use strict;

use sapnwrfc;                                       # load the SAPNW::Rfc module

SAPNW::Rfc->load_config;                            # load connection parameters

my $rfc = SAPNW::Rfc->rfc_connect;                  # connect to SAP system

my $rcb = $rfc->function_lookup(“TH_SERVER_LIST”);  # look up the function module

my $tsl = $rcb->create_function_call;               # create the function call

$tsl->invoke;                                       # execute the function call

foreach my $row (@{$tsl->LIST}) {                   # loop through each row in LIST array

   print “Server is $row->{‘NAME’}”;                # print the server NAME value for each

}

$rfc->disconnect(); 

This is already easy but for someone that has never seen perl there are a lot of weird looking characters in the mix. Let’s do this in Python:

#!/usr/bin/python
from pyrfc import Connection                 # load the pyrfc.Connection method
conn = Connection(user=<username>,           # logon to SAP
                  passwd=<password>,        

                  ashost=<hostname>,       
                  sysnr=<system number>,

                  client=<client>)

result=conn.call(‘TH_SERVER_LIST’)           # execute the function module

for server in result[‘LIST’]:                # loop over each row in the LIST list
    print “Server is ” + server[‘NAME’]      # print the NAME value for each server
conn.close()                                 # close the connection

Simple, elegant an very easy to read and understand.

What you need

You don’t need a lot to get started:

  • Obviosuly Python. If it’s not already part of your OS you can download it from Python.org. You need version 2.7.6 since SAP’s pyrfc module is only avaiable for that version. Python 3.4 is not supported (yet?). There are also python bundles available that have a lot of modules already bundled in. There is also a portable Python version that does not require installation called WinPython.
  • The PyRFC module: PyRFC – The Python RFC Connector . The module has excellent installation instructions. just follow them step by step. You may be required to download additional python modules to get the PyRFC module going.
  • Depending on what you want to do you may need other modules. For example the logger module for easy logging, or  something to generate HTML coding. The amount available modules are endless in case the already vast standard functionality is not sufficient.
  • It will be easier to use a dedicated Python IDE (Integrated Development Environment). Syntax highlighting and context sensitive online help makes the development even easier. An overview of the different IDEs available can be found in this article: Comparison of Python IDEs for Development | Python Central. I personally use PyCharm but sometimes it can be annoyingly slow to start.

Another thing is training and documentation. There are many, many books and online courses available for free. For example Google’s Python Class Or the free offerings from Codecademy. Or just work your way through the tons of example coding that is available all over the Internet.

Start with something useful: List of delta transports for a System Copy

We want to perform a system copy to refresh the QA system from the production system as part of a dress rehearsal.  After the system copy we need to import the transports that were in QA system but didn’t make it to production yet. The way that is described here does not necessarily mean that it works for your environment. So don’t accept this thing blindly without checking, actually better assume this script is not working at all and test it out in your environment.

Here are the steps we need to accomplish:

  • Logon to the source (PRD) and the target system (QAS)
  • download tables TPALOG from both systems
  • determine which transports are missing in the PRD system
  • determine the sequence of the missing transports
  • print the missing transports in the sequence they need to get applied.

Logging on

In this example we will store the logon information in a text file. This example the text contains a password stored in clear text. That’s obviously not a good thing. In some later parts we will discuss ways around this but for now let’s stick with this approach. This is the text file:

[target]
user = TESTUSER
passwd = A very secure password!
ashost = qasp00

client = 020
sysnr = 00
sid = QAS

[source]
user = TESTUSER
passwd = A very secure password, too!

ashost = prdpp00
client = 020
sysnr = 00
sid = PRD

The file syntax corresponds to a standard windows .ini file. The configuration section is stored in “[ ]” and its values below it. The file should reside in the same directory as the script with the name ‘sapsystems.cfg’. It gets read by the Module ConfigParser which returns a dictionary object consisting of the values that belong to a configuration item.

The Script

from pyrfc import Connection
from ConfigParser import ConfigParser

config = ConfigParser()      
config.read(‘sapsystems.cfg’)

source_transports = []
target_transports = []

params_source=config._sections[‘source’] #read the logon information of the source system
params_target=config._sections[‘target’] #read the logon information of the target system

target_conn = Connection(user=params_target[‘user’],
                         passwd=params_target[‘passwd’],
                         ashost=params_target[‘ashost’],
                         client=params_target[‘client’],
                         sysnr=params_target[‘sysnr’],
                         sid=params_target[‘sid’])

# that’s a lot of typing. The connection information is returned in a data

# structure called ‘dictionary’. Using the following syntax we basically ‘unpack’

# the content of the structure and make this whole thing a lot simpler:

source_conn = Connection(**params_source)

# The information we need is stored in table TPALOG. since we don’t
# want to download the entire table, we need to restrict the returned
# records. We are going to use funtion module RFC_READ_TABLE for this.
# the where clause there is stored in ABAP syntax. That means we need
# these statements:

target_where = “TARSYSTEM EQ ‘” + params_target[‘sid’] + \
               “‘ AND TRSTEP EQ ‘I'”

source_where = “TARSYSTEM EQ ‘” + params_source[‘sid’] + \
               “‘ AND TRSTEP EQ ‘I'”

# now let’s read the data for the target system
target_result = target_conn.call(‘RFC_READ_TABLE’,
                       QUERY_TABLE=’TPALOG’,
                       DELIMITER=’|’,
                       OPTIONS = [{‘TEXT’:target_where}])

# we are going to store the transports in a ‘set’. A set can do
# lots of things that would otherwise very difficult to perform
# otherwise
list_transports_sequence=list()

# the following list will be used for the sequence later on
target_transports=set()

for row in target_result[‘DATA’]:
    splitrow=row[‘WA’].split(‘|’)
    # here we add the transport to the set
    target_transports.update(str(splitrow[1].strip()))
    # and here we append the import date and the transport as dictionary

    # to the list of all transports
    list_transports_sequence.append(dict(date=splitrow[0].strip(),
                                         transport=splitrow[1].strip()))

source_result = source_conn.call(‘RFC_READ_TABLE’,
                       QUERY_TABLE=’TPALOG’,
                       DELIMITER=’|’,
                       OPTIONS = [{‘TEXT’:source_where}])

# the same for the transports in the source system
source_transports=set()

for row in source_result[‘DATA’]:
    splitrow=row[‘WA’].split(‘|’)
    source_transports.update(str(splitrow[1].strip()))

# now one of the cool things to do with sets:
missing_transports = target_transports – source_transports

# the returned set ‘missing_transports’ contains the list of

# transports that were imported into QAS but never made it to

# production. In the next step we need to determine the import sequence
for transport in missing_transports:
    # at first we delete all transports from the list of imported

    # transports that were imported into both systems already
    list_transports_sequence[:] = [dic for dic in list_transports_sequence \

                                   if dic.get(‘transport’) != transport]

print(“Missing transports in the sequence in which they need to get applied:”)

# we need to order the list of the transports by import date.
for transport in sorted(list_transports_sequence,key= lambda tp: tp[‘date’]):
    print(transport[‘transport’])

To report this post you need to login first.

3 Comments

You must be Logged on to comment or reply to a post.

  1. Nikolaos Papagrigoriou

    This is an excellent example.Thank you very much for this.

    I executed the example, trying to include a limit by TRTIME. But the response I got was:

    pyrfc._exception.ABAPRuntimeError:
         RFC_ABAP_RUNTIME_FAILURE (rc=3):
           key=SAPSQL_PARSE_ERROR, message=An error has occurred
               while parsing a dynamic entry. [MSG: class=, type=,
                number=, v1-4:=;;;]

    Then after a lot of searching I realized that the TEXT option is limited to 72 characters. I had to break the where clause into smaller chunks and pass a list of TEXT options.

    SAP note 382318 states that:

    Avoid to use the external generic table access (with function module RFC_READ_TABLE) in your solutions. The function is not meant to be publicly used.

    What is an alternative to RFC_READ_TABLE that can be used to query already imported transports?

    Kind regards,

    Nikos.

    (0) 
    1. Lars Fasel Post author

      Hi,

      I’m not aware of any officially SAP supported generic table reader function module. There is function module TABLE_ENTRIES_GET_VIA_RFC but it has the same comments regarding SAP support as RFC_READ_TABLE. It has the advantage to support a row length of 2048 characters I think.

      In your case it won’t help since the where clause field ‘SEL_TAB’ has the same length restriction as RFC_READ_TABLE.

      If that is not sufficient, then an ABAP developer may need to write a custom solution for your requirements.

      Best regards

      Lars

      (0) 
      1. Nikolaos Papagrigoriou

        Thanks Lars.

        For anyone interested, a long where clause can be broken down in smaller chunks like this:

        import textwrap

        long_where_clause = (“TARSYSTEM EQ ‘XXX’ AND TRSTEP EQ ‘I’ “

             “AND TRCLI EQ ‘999’ AND TRTIME GE ‘20141201232323’”)

        OPTIONS = [{‘TEXT’: line} for line in textwrap.wrap(long_where_clause, 72)]

        (0) 

Leave a Reply