pythonpywin32sap-gui

Multiprocessing in SAP GUI using Python


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:

  1. 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
    
  2. 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
    
  3. 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.


Solution

  • 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)