Application Development Blog Posts
Learn and share on deeper, cross technology development topics such as integration and connectivity, automation, cloud extensibility, developing at scale, and security.
cancel
Showing results for 
Search instead for 
Did you mean: 
Srdjan
Product and Topic Expert
Product and Topic Expert


A customer, partner or colleague of yours has a nice idea about new web or mobile application, but before starting a bigger project the concept should be quickly validated, fully integrated with SAP backend system. Or, you are just interested for experimenting and deeper understanding how web applications, REST and HTTP work, integrated with SAP ABAP systems.

Using Python RFC connector and Python web framework of choice, you can build the model for the web application prototyping, expose JSON resources or assemble a mini web app, in interactive, fun and convenient way.

Let take the CRM Customer Account as example and suppose two resources with JSON representation are needed, a single customer and the list of customers matching the first name, last name or customer id.

ABAP


Application resources on ABAP side can be implemented by encapsulation of existing BAPIs or by writing new Remote Function Modules (RFM), often called RFCs. There are practically no restrictions how to implement those RFMs, except you should try to keep RFM signatures simple and consistent, because it simplifies the modeling of Python objects for web and makes the communication among Web and ABAP experts easier.

You should also think about performance and returning as much as possible information on errors, like record not found etc., but that depends on available time, budget and goals of your prototype.

Suppose for the purpose of this exercise, two RFMs are implemented and tested: ZFRS_CUSTOMER_ACCOUNT_GET and ZFRS_CUSTOMER_ACCOUNT_GETLIST. From SE37 screenshots below (click to enlarge), you see the consistent naming of these two RFCs because they will soon become methods of Python web object Customer.

Customer GET


Customer GET, returns a single instance of CRM Customer Account


Customer GETLIST


Customer GETLIST, returns a list CRM Customer Accounts, based on search criteria.


Web


Python objects for web/mobile prototyping, you can create using the editor or IDE of choice, manually or automatically, by generating Python model from ABAP RFM metadata.

In any case, the source code below shows the final result. Pyton is expressive language, which means that using less code lines you can accomplish more, which means the code (if well written) is easy to read and maintain. Experience from projects shows that ABAP experts even with no experience in Python, have no troubles understanding and writing web/mobile prototype models in Python.

Looking into source code below, it is easy to recognize BAPI_RETURN structure and get a clue what the model is doing. In this particular case, the model is even more complicated than the how-to example should be, because of the additional handling of loyalty data, required in a real project.

From the perspective of the web developer (HTML5, JavaScript …), the object Customer looks like a quite standard object for web development, so the web developer can utilize all skills and weapons he/she regularly uses for web development. The difference here, in regard to other web applications, is that this object does not use ORM or another database for the persistence but the leading and only system for the persistence is SAP backend.

The get and search methods of the Customer object are implemented in ABAP system and persistent changes are done there. Although implemented in Python, the majority (ideally 100%) of the business logic remains in ABAP system, exposed via RFMs, as Python object methods. Web/mobile developers normally do not need to care about that, just use those objects like in other web/mobile application.

customermodel.py

#
# CRM Customer GET and GETLIST model
#
import base64
from datetime import datetime
from model import RfcModel, EnhancedRfcModel
from fields import Char, Date, Struct, Many, NumC, Decimal, String, Boolean, Time, Exception_, Single
class ETReturn(RfcModel):
"""Error Messages"""
id_ = Char(20, name="ID" ) # Message Class
type_ = Char(1, name="TYPE" ) # Message type: S Success, E Error, W Warning, I Info, A Abort
number = NumC(3, name="NUMBER" ) # Message Number
system = Char(10, name="SYSTEM" ) # Logical system from which message originates
parameter = Char(32, name="PARAMETER" ) # Parameter Name
field = Char(30, name="FIELD" ) # Field in parameter
message = Char(220, name="MESSAGE" ) # Message Text
message_v1 = Char(50, name="MESSAGE_V1") # Message Variable
message_v2 = Char(50, name="MESSAGE_V2") # Message Variable
message_v3 = Char(50, name="MESSAGE_V3") # Message Variable
message_v4 = Char(50, name="MESSAGE_V4") # Message Variable
log_no = Char(20, name="LOG_NO" ) # Application log: log number
log_msg_no = NumC(6, name="LOG_MSG_NO") # Application log: Internal message serial number
row = NumC(4, name="ROW" ) # Lines in parameter
def __str__(self):
return "{message} (Msg {number})".format(**self.__dict__)
class CustomerDetail(RfcModel):
"""Customer Account details"""
id = Char(10, name="KUNNR" ) # Customer Number
name = Char(35, name="NAME1" ) # Name 1
city = Char(35, name="ORT01" ) # City
zip = Char(10, name="PSTLZ" ) # Postal Code
street = Char(35, name="STRAS" ) # House number and street
email = Char(241, name="SMTP_ADDR") # E-Mail Address
card_id = Char(25, name="CARD_ID" ) # External Card ID
def __repr__(self):
return "<CustomerDetail> " + str(self.__dict__)
class CustomerDetails(RfcModel):
"""Customer Account details"""
id = Char(10, name="KUNNR" ) # Customer Number
name = Char(35, name="NAME1" ) # Name 1
city = Char(35, name="ORT01" ) # City
zip = Char(10, name="PSTLZ" ) # Postal Code
street = Char(35, name="STRAS" ) # House number and street
email = Char(241, name="SMTP_ADDR") # E-Mail Address
card_id = Char(25, name="CARD_ID" ) # External Card ID
def __repr__(self):
return "<CustomerDetails> " + str(self.__dict__)
class CustomerLoyalty(RfcModel):
"""Customer loyalty program"""
name = Char (40, name="PROGRAM" ) # Text, 40 Characters Long
tires = Char (40, name="TIRES" ) # Text, 40 Characters Long
status = Char (40, name="STATUS" ) # Text, 40 Characters Long
start_date = Date ( name="START_DATE") # Date
end_date = Date ( name="END_DATE" ) # Date
balance = Decimal(6, name="POINT_BAL" ) # Number of Points
description = Char (40, name="PROG_DESCR") # Language-Dependent Short Text
def __repr__(self):
return "<CustomerLoyalty> " + str(self.__dict__)
class Customer(EnhancedRfcModel):
"""ZFRS_CUSTOMER_GET Model """
id = Char(10, name="IV_CUSTOMER" ) # Business Partner ID
card_id = Char(25, name="IV_CARD_ID", optional=True) # External Card ID
firstname = Char(40, name="IV_NAME_FIRST" ) # First Name of Business Partner (Person)
lastname = Char(40, name="IV_NAME_LAST" ) # Last Name of Business Partner (Person)
raw_image = String(0, name="EV_FOTO", optional=True) # Foto
url = Char (452, name="EV_URI", optional=True) # SAP URL
details = Struct(CustomerDetail, name="ES_CUSTOMER_DETAIL", optional=True) # Account data
search_results = Many (CustomerDetails, name="ET_CUSTOMER_DATA", optional=True)
loyalties = Many (CustomerLoyalty, name="ET_LOY_DATA", optional=True) # Loyalty data
es_return = Struct(ETReturn, name="ES_RETURN" ) # Return Parameter
def __json__(self):
"""One customer details as JSON (Customer GET)"""
# not found or another error
if self.es_return.type_ in ['A','E']:
result={}
for key in ('id_', 'type_', 'number', 'message'):
result[key] = getattr(self.es_return, key)
return result
# customer account found
result = {
'image': len(self.image) if self.image else None,
'image_type': 'image/jpeg' if self.image else None,
'url': self.url
}
# customer details
for key in ('id', 'name', 'city', 'zip', 'street', 'email', 'card_id'):
result[key] = getattr(self.details, key)
# loyalty infos
level = ''
if self.loyalties:
level = self.loyalties[0].tires # TODO, for now just take the first level found
result['loyalty'] = {
'points': 0,
'level': level,
'sets': []
}
for loy in self.loyalties:
loyalty = {
'start_date': datetime.strftime(loy.start_date, '%d.%m.%Y'),
'end_date': datetime.strftime(loy.end_date, '%d.%m.%Y'),
}
for key in ('name', 'description', 'tires', 'status', 'balance'):
loyalty[key] = getattr(loy, key)
result['loyalty']['sets'].append(loyalty)
if loy.balance:
result['loyalty']['points'] += loy.balance
return result
def __json_list__(self):
"""Customer list search result as JSON (Customer GETLIST)"""
# error check
if self.es_return.type_ in ['A','E']:
result={}
for key in ('id_', 'type_', 'number', 'message'):
result[key] = getattr(self.es_return, key)
return result
result = []
for customer in self.search_results:
result.append(
{key: getattr(customer, key) for key in (
'id', 'name', 'city', 'zip', 'street', 'email', 'card_id')})
return {'customers': result}
@classmethod
def get(cls, conn, id='', card_id='', firstname='', lastname='', **params):
result = conn.call("ZFRS_CUSTOMER_GET",
**cls.rfc_fields(id=id,
card_id=card_id,
firstname=firstname,
lastname=lastname,
**params))
return cls.init_from_rfc(result)
@classmethod
def search(cls, conn, id='', firstname='', lastname='', **params):
result = conn.call("ZFRS_CUSTOMER_GETLIST",
**cls.rfc_fields(id=id,
firstname=firstname,
lastname=lastname,
**params))
return cls.init_from_rfc(result)
@property
def image(self):
return base64.b64encode(self.raw_image.rstrip("\x00"))


App


App can be many different things, a couple of data interfaces, JSON or OData, for the consumption by native, usually mobile app, or the standard web application with HTML templates, rendering, static content, media etc., for the mobile, tablet or desktop device.

This example shows the simplest case, a JSON representation of SAP application resources, basically RESTful data interfaces. However, you can check the web framework documentation (in this example Flask) and easily extend the app with HTML templates, rendering, error handling and so on.

Using a console like IPython (see videos) can be very helpful for  rapid experimenting, prototyping and learning more about REST and HTTP, in a very convenient way, fully integrated with SAP system. It can also help you quickly accomplish an integrated HTML5/REST demo or proof of concept.

Support for OData is not yet implemented, can be provided if needed.

Similar SAP Research prototype example you may find here and more about prototypes done this way you may find here .

app.py




from sapnwrfc2 import Connection
import customermodel
# Connect to SAP CRM
params = {'user' : 'sb-test',
'passwd' : 'sb-test',
'ashost' : '10.117.24.153',
'saprouter' : '/H/203.13.155.17/W/xjkb3d/H/172.19.137.194/H/',
'sysnr' : '00',
'client' : '765',
'lang' : 'EN'}
print 'Connecting ...', params['ashost']
conn = Connection(config={'return_import_params': True}, **params)
# Serve web app
print 'Serve ...'
from flask import Flask, request
app = Flask(__name__)
@app.route("/")
def index():
response = """
<HTML><HEAD><TITLE>Customer</TITLE></HEAD>
<BODY>
<h1>Usage examples</h1>
<p></p>
<p>/customer/710160</p>
<p></p>
<p>/customerlist?lastname=Crimson</p>
</BODY></HTML>
"""
return response
@app.route("/customer/<id>")
def get(id=None):
customer_by_id = Customer.get(conn, id=id)
response = str(customer_by_id.__json__())
return response

@app.route("/customerlist")
def getlist(lastname=None):
for header in request.headers:
print header
id = request.args.get('id')
if id == None:
id = ''

lastname = request.args.get('lastname')
if lastname == None:
lastname = ''
firstname = request.args.get('firstname')
if firstname == None:
firstname = ''
customerlist = Customer.search(conn, id=id, firstname=firstname, lastname=lastname)
response = str(customerlist.__json_list__())
return response
if __name__ == "__main__":
app.run(debug=True, port=5000)

Even without  modeling, direct calling ABAP function modules from  Python requires only couple if lines of code, as shown on screenshot below (click to enlarge). Compact, clean and highly readable code is a great advantage for rapid prototyping, also for the software maintenance and iterative development, often following the prototyping.



2 Comments