My SAP is very old and I can't make API calls with it. So, I have to manipulate the GUI of SAP to do my stuff.
I'm trying to access two SAP transactions at the same time in two different windows using Python.
To do this I'm using the libraries: pywin32, subprocess and multiprocessing.
But I'm getting the following error:
TypeError: cannot pickle 'PyIDispatch' object
and
PermissionError: [WinError 5] Acess denied
What I got until now is to open two windows (create two SAP GUI sessions) and access the transaction in different windows but one after the other, in other words, not at the same time.
This test program constitutes in 3 separated scripts:
The Scripts:
createconnection.py
from subprocess import Popen
import time
from win32com.client import GetObject
class Sap:
def __init__(self, sap_env, user_id, user_password, language="EN",
newSession=False, connectBy=2):
self.sap_file = "C:\\Program Files (x86)\\SAP\\FrontEnd\\SapGui" +\
"\\saplogon.exe"
self.sap_env = sap_env
self.user_id = user_id
self.user_password = user_password
self.language = language
self.connectBy = connectBy
self.newSession = newSession
def __get_sap_gui__(self):
try:
return GetObject('SAPGUI').GetScriptingEngine
except:
time.sleep(0.5)
return self.__get_sap_gui__()
def get_sap_connection(self):
if self.connectBy == 3:
Popen(self.sap_file + ' ' + self.sap_env)
sapGui = self.__get_sap_gui__()
conn = sapGui.Connections(0)
timeout = 10
while conn.Sessions.Count == 0 and timeout:
time.sleep(1)
timeout -= 1
if timeout == 0: raise Exception("Fail to connect")
else:
Popen(self.sap_file)
sapGui = self.__get_sap_gui__()
conn = None
if self.connectBy == 1:
if sapGui.Connections.Count > 0: # it's not good, I'll fix this later
for conn in sapGui.Connections:
if conn.Description == self.sap_env:
break
if not conn:
conn = sapGui.OpenConnection(self.sap_env)
else:
if sapGui.Connections.Count > 0:
for conn in sapGui.Connections:
if self.sap_env in conn.ConnectionString:
break
if not conn:
conn = sapGui.OpenConnectionByConnectionString(self.sap_env)
return conn
def get_sap_session(self, conn):
if self.newSession:
numSessions = conn.Sessions.Count + 1
conn.Sessions(0).createsession()
while conn.Sessions.Count < numSessions: pass
session = conn.Sessions(numSessions-1)
else:
session = conn.Sessions(0)
if session.findById('wnd[0]/sbar').text.startswith('SNC logon'):
session.findById('wnd[0]/usr/txtRSYST-LANGU').text = self.language
session.findById('wnd[0]').sendVKey(0)
session.findById('wnd[0]').sendVKey(0)
elif session.Info.User == '':
session.findById('wnd[0]/usr/txtRSYST-BNAME').text = self.user_id
session.findById('wnd[0]/usr/pwdRSYST-BCODE').text =\
self.user_password
session.findById('wnd[0]/usr/txtRSYST-LANGU').text = self.language
session.findById('wnd[0]').sendVKey(0)
session.findById('wnd[0]').maximize()
return session
manipulatesap.py
from createconnection import Sap
class QuerySap(Sap):
def __init__(self, sap_env, user_id, user_password, language):
super().__init__(sap_env, user_id, user_password, language=language)
self.connection = self.get_sap_connection()
self.session = self.get_sap_session(self.connection)
self.new_session = None
def open_new_windows(self):
self.connection.Sessions(0).createsession()
self.connection.Sessions(0).createsession()
self.new_session = self.connection.Sessions(1)
@property
def sess1(self):
return self.session
@property
def sess2(self):
return self.new_session
main.py
from manipulatesap import QuerySap
from multiprocessing import Pool, Process
from time import sleep
def goto_trasaction(session, transacion):
session.findById("wnd[0]/tbar[0]/okcd").text = transacion
session.findById("wnd[0]").sendVKey(0)
sleep(5)
def sap_interface_multi_process(usr, pw, env):
sap_nav = QuerySap(sap_env=env, user_id=usr,user_password=pw,
language="PT")
sap_nav.open_new_windows()
session1 = sap_nav.sess1
session2 = sap_nav.sess2
p1 = Process(target=goto_trasaction, args=(session1, "TRANSACION A"))
p2 = Process(target=goto_trasaction, args=(session2, "TRANSACTION B"))
p1.start()
p2.start()
p1.join()
p1.join()
def main():
print(">>> Start")
sap_env = "string_for_connection"
sap_interface_multi_process("usr_id", "usr_pw", sap_env)
print(">>> Finish")
if __name__ == "__main__":
main()
Could you guys help me to find what I missing and what I should do?
Thank you very much.
Finally I got the solution after sometime of vaction.
But I had to refactory a lot of my code.
What I did was to instantiate the sap class to an object and pass this object to a function that will be executed in parallel. Inside of this function I use the sap class method to create a connection and create a session.
Here is my solution. Not pretty but worked:
from Modules.Sap.sapinit import Sap
def create_sap_session(sap_obj, extra_num_sessions):
sap_conn = sap_obj.get_sap_connection()
sap_obj.get_sap_session(sap_conn)
if extra_num_sessions < 1:
return
for _ in range(extra_num_sessions):
sap_conn.Sessions(0).createsession()
return
def parallel_sap_query(sap_obj, sessions_num, transaction):
sap_conn = sap_obj.get_sap_connection()
sap_session = sap_conn.Sessions(sessions_num)
session.findById("wnd[0]/tbar[0]/okcd").text = transaction
session.findById("wnd[0]").sendVKey(0)
def execute_cancellations(sap_obj):
create_sap_session(sap_obj, 2)
sleep(3)
p1 = Process(target=parallel_sap_query, args=(sap_obj, 0, "A", ))
p2 = Process(target=parallel_sap_query, args=(sap_obj, 1, "B", ))
p3 = Process(target=parallel_sap_query, args=(sap_obj, 2, "C", ))
p1.start()
p2.start()
p3.start()
p1.join()
p2.join()
p3.join()
close_sap_sessions(sap_obj, 2, 1, 0)
def close_sap_sessions(sap_obj, *sessions):
sap_conn = sap_obj.get_sap_connection()
for session in sessions:
sap_session = sap_conn.Sessions(session)
sap_session.findById("wnd[0]").close()
sap_session.findById("wnd[1]/usr/btnSPOP-OPTION1").press()
def main():
sap_obj = Sap(sap_env, sap_id, sap_pw, "PT")
execute_cancellations(sap_obj)