SAP GUI script sometimes randomly crashes (object not found by ID) - python

So, I have two SAP GUI scripts, both of them are running a simple report in SE16, exporting it to Excel and that's all. My Python script heavily relies on this data, yet, half of the time when I go back to the computer the VBS script crashed.
Nothing changes between the attempts, sometimes it works, sometimes it doesn't, with the same data...
The GUI script itself was done with the help of the record function, after looking at the resulting code I realized that I have absolutely no desire of ever learning this language (how do you even figure out the objectID of each button, vkeys are weird, etc).
Is there a simple way to make this (and the same with EKPO) crash-proof? I guess I'd need to show which object is not found, but ever since I started to write this post, no crashes happened, it only hanged without an error message...
This is the Python part of the code, it runs the VBS script. Based on Googling, it is possible to include the VBS code directly in Python, based on the VBS below, how bothersome would it be and would it (crystal ball time) solve my random crashes?
def create_ausp(local_raw):
user = os.getlogin()
file = rf"C:\Users\{user}\Desktop\Reporting\AUSP.XLSX"
local_raw["Vendor"].drop_duplicates().to_clipboard(index=False)
if os.path.exists(file):
os.remove(file)
if not os.path.exists(file):
os.startfile(rf"C:\Users\{user}\AppData\Roaming\SAP\SAP GUI\Scripts\AUSP.vbs")
while not os.path.exists(file):
time.sleep(1)
if os.path.isfile(file):
ausp_headers = ["Vendor", "SAP default material group"]
df = pd.read_excel("AUSP.xlsx")
# df.drop(columns=["Internal char no."], inplace=True) #sometimes comes up in SAP, uncomment if value error happens
df.columns = ausp_headers
os.startfile(rf"C:\Users\{user}\Desktop\Reporting\AUSP close.ahk")
return df
VBS script:
If Not IsObject(application) Then
Set SapGuiAuto = GetObject("SAPGUI")
Set application = SapGuiAuto.GetScriptingEngine
End If
If Not IsObject(connection) Then
Set connection = application.Children(0)
End If
If Not IsObject(session) Then
Set session = connection.Children(0)
End If
If IsObject(WScript) Then
WScript.ConnectObject session, "on"
WScript.ConnectObject application, "on"
End If
session.findById("wnd[0]").maximize
session.findById("wnd[0]/tbar[0]/okcd").text = "se16n"
session.findById("wnd[0]").sendVKey 0
session.findById("wnd[1]").sendVKey 0
session.findById("wnd[0]/usr/ctxtGD-TAB").text = "ausp"
session.findById("wnd[0]/usr/ctxtGD-TAB").caretPosition = 4
session.findById("wnd[0]").sendVKey 0
session.findById("wnd[0]/usr/tblSAPLSE16NSELFIELDS_TC/btnPUSH[4,1]").setFocus
session.findById("wnd[0]/usr/tblSAPLSE16NSELFIELDS_TC/btnPUSH[4,1]").press
session.findById("wnd[1]").sendVKey 24
session.findById("wnd[1]").sendVKey 8
session.findById("wnd[0]/tbar[1]/btn[18]").press
session.findById("wnd[0]/usr/tblSAPLSE16NSELFIELDS_TC/chkGS_SELFIELDS-MARK[5,1]").selected = true
session.findById("wnd[0]/usr/tblSAPLSE16NSELFIELDS_TC/chkGS_SELFIELDS-MARK[5,7]").selected = true
session.findById("wnd[0]/usr/txtGD-MAX_LINES").text = ""
session.findById("wnd[0]/usr/tblSAPLSE16NSELFIELDS_TC/ctxtGS_SELFIELDS-LOW[2,2]").text = "DEFAULT_MATERIAL_GROUP"
session.findById("wnd[0]/usr/txtGD-MAX_LINES").setFocus
session.findById("wnd[0]/usr/txtGD-MAX_LINES").caretPosition = 0
session.findById("wnd[0]").sendVKey 0
session.findById("wnd[0]").sendVKey 8
session.findById("wnd[0]/usr/cntlRESULT_LIST/shellcont/shell").pressToolbarContextButton "&MB_VARIANT"
session.findById("wnd[0]/usr/cntlRESULT_LIST/shellcont/shell").pressToolbarContextButton "&MB_EXPORT"
session.findById("wnd[0]/usr/cntlRESULT_LIST/shellcont/shell").selectContextMenuItem "&XXL"
session.findById("wnd[1]/usr/ctxtDY_PATH").text = "C:\Users\...\Desktop\Reporting"
session.findById("wnd[1]/usr/ctxtDY_FILENAME").text = "AUSP.XLSX"
session.findById("wnd[1]/usr/ctxtDY_FILENAME").caretPosition = 9
session.findById("wnd[1]").sendVKey 11
session.findById("wnd[0]").sendVKey 3
session.findById("wnd[0]").sendVKey 3

I've done something very similar to your code.
I had a python script that first opened SAP and logged in, and then ran a VBS script to download two excel files, and then run another python script to process the data.
Only recently have I managed to merge it all into one python script. Although it's still VBS code, only embedded in python through the win32com module.
I haven't found any better way to create the SAP code than to record it in the SAP gui, just as you did. I did find it quite easy to do and merge it into python though.
Perhaps it can help you with your needs.
import psutil
import subprocess
import time
import win32com.client
from win32com import client
from win32com.client import Dispatch
if "saplogon.exe" in (i.name() for i in psutil.process_iter()):
print("SAP is running")
else:
print("SAP is not running. Starting...")
subprocess.check_call(['C:\\Program Files (x86)\\SAP\\FrontEnd\\SAPgui\\sapshcut.exe', '-system=PE1', '-client=500', f'-user={sapuser}', f'-pw={password}', '-language=EN'])
time.sleep(20)
SapGuiAuto = win32com.client.GetObject("SAPGUI")
if not type(SapGuiAuto) == win32com.client.CDispatch:
exit
application = SapGuiAuto.GetScriptingEngine
if not type(application) == win32com.client.CDispatch:
SapGuiAuto = None
exit
connection = application.Children(0)
if not type(connection) == win32com.client.CDispatch:
application = None
SapGuiAuto = None
exit
session = connection.Children(0)
if not type(session) == win32com.client.CDispatch:
connection = None
application = None
SapGuiAuto = None
exit
print("Downloading excel files from SAP")
session.findById("wnd[0]").maximize
session.findById("wnd[0]/tbar[0]/okcd").text = "/ncoois"
session.findById("wnd[0]").sendVKey(0)
session.findById("wnd[0]/usr/ssub%_SUBSCREEN_TOPBLOCK:PPIO_ENTRY:1100/cmbPPIO_ENTRY_SC1100-PPIO_LISTTYP").key = "PPIOO000"
session.findById("wnd[0]/usr/tabsTABSTRIP_SELBLOCK/tabpSEL_00/ssub%_SUBSCREEN_SELBLOCK:PPIO_ENTRY:1200/chkP_KZ_E1").selected = True
session.findById("wnd[0]/usr/tabsTABSTRIP_SELBLOCK/tabpSEL_00/ssub%_SUBSCREEN_SELBLOCK:PPIO_ENTRY:1200/chkP_KZ_E2").selected = True
session.findById("wnd[0]/usr/tabsTABSTRIP_SELBLOCK/tabpSEL_00/ssub%_SUBSCREEN_SELBLOCK:PPIO_ENTRY:1200/ctxtS_WERKS-LOW").text = "ika1"
session.findById("wnd[0]/usr/tabsTABSTRIP_SELBLOCK/tabpSEL_00/ssub%_SUBSCREEN_SELBLOCK:PPIO_ENTRY:1200/ctxtP_SYST1").text = "TECO"
session.findById("wnd[0]/usr/tabsTABSTRIP_SELBLOCK/tabpSEL_00/ssub%_SUBSCREEN_SELBLOCK:PPIO_ENTRY:1200/ctxtP_SYST2").text = "CNF"
session.findById("wnd[0]/usr/tabsTABSTRIP_SELBLOCK/tabpSEL_00/ssub%_SUBSCREEN_SELBLOCK:PPIO_ENTRY:1200/chkP_KZ_E2").setFocus()
session.findById("wnd[0]").sendVKey(8)
session.findById("wnd[0]/usr/cntlCUSTOM/shellcont/shell/shellcont/shell").setCurrentCell(-1,"")
session.findById("wnd[0]/usr/cntlCUSTOM/shellcont/shell/shellcont/shell").selectAll()
session.findById("wnd[0]/usr/cntlCUSTOM/shellcont/shell/shellcont/shell").contextMenu()
session.findById("wnd[0]/usr/cntlCUSTOM/shellcont/shell/shellcont/shell").selectContextMenuItem("&XXL")
session.findById("wnd[1]/tbar[0]/btn[0]").press()
session.findById("wnd[1]/usr/ctxtDY_PATH").setFocus()
session.findById("wnd[1]/usr/ctxtDY_PATH").caretPosition = 0
session.findById("wnd[1]").sendVKey(4)
session.findById("wnd[2]/tbar[0]/btn[12]").press()
session.findById("wnd[1]/usr/ctxtDY_PATH").text = f"C:\\users\\{user}\\Documents"
session.findById("wnd[1]/usr/ctxtDY_FILENAME").text = "operations.XLSX"
session.findById("wnd[1]/usr/ctxtDY_FILENAME").caretPosition = 15
session.findById("wnd[1]/tbar[0]/btn[11]").press()
session.findById("wnd[0]/tbar[0]/btn[15]").press()
session.findById("wnd[0]").resizeWorkingPane(267,40,False)
session.findById("wnd[0]/usr/ssub%_SUBSCREEN_TOPBLOCK:PPIO_ENTRY:1100/cmbPPIO_ENTRY_SC1100-PPIO_LISTTYP").key = "PPIOH000"
session.findById("wnd[0]/tbar[1]/btn[8]").press()
session.findById("wnd[0]/usr/cntlCUSTOM/shellcont/shell/shellcont/shell").setCurrentCell(-1,"")
session.findById("wnd[0]/usr/cntlCUSTOM/shellcont/shell/shellcont/shell").selectAll()
session.findById("wnd[0]/usr/cntlCUSTOM/shellcont/shell/shellcont/shell").contextMenu()
session.findById("wnd[0]/usr/cntlCUSTOM/shellcont/shell/shellcont/shell").selectContextMenuItem("&XXL")
session.findById("wnd[1]/tbar[0]/btn[0]").press()
session.findById("wnd[1]/usr/ctxtDY_PATH").text = f"C:\\users\\{user}\\Documents"
session.findById("wnd[1]/usr/ctxtDY_FILENAME").text = "header.XLSX"
session.findById("wnd[1]/usr/ctxtDY_FILENAME").caretPosition = 12
session.findById("wnd[1]/tbar[0]/btn[11]").press()

Related

Opening SAP via python

I have sucessfully managed to open SAP, but I am struggling to make python click on the logon button that appears when the program is opened. How could I achieve that?
import subprocess
subprocess.run(r'C:\Program Files (x86)\SAP\FrontEnd\SAPgui\saplogon.exe')
# I would like python to press the logon button in the window that has just been opened.
This is what I use in python
import subprocess
subprocess.check_call(['C:\\Program Files (x86)\\SAP\\FrontEnd\\SAPgui\\sapshcut.exe', '-system=PE1', '-client=500', '-user=user', '-pw=password', '-language=EN'])
In my opinion, python is not exactly the best language when it comes to automate windows in "makro-style" maybe you could use something lilke AutoHotkey (AHK) for that.
For completion's sake:
There seem to be python modules like PyAutoGUI which could help you achieve what you want.
Or you could import win32api and try to control windows that way.
If you really try to automate SAP with python you should try to get information about an official API of SAP which you could call.
Maybe this will help: https://blogs.sap.com/2020/06/09/connecting-python-with-sap-step-by-step-guide/
You can use win32com to connect to the SAPGUI.
Find the "Script Recording and Playback" in your SAP menu to record your SAP transactions in a script. Find that script you can see all your transaction in code.
Use the SAPGUI with Python example to create a session on an opened SAP. Replace the SAP script in that example with your recorded script
Try this:
import win32com.client
import subprocess
import sys
def saplogin():
try:
path = r"C:\Program Files (x86)\SAP\FrontEnd\SAPgui\saplogon.exe"
sap = subprocess.Popen(path)
time.sleep(10)
SapGuiAuto = win32com.client.GetObject('SAPGUI')
if not type(SapGuiAuto) == win32com.client.CDispatch:
return
application = SapGuiAuto.GetScriptingEngine
if not type(application) == win32com.client.CDispatch:
SapGuiAuto = None
return
connection = application.OpenConnection("PCL", True)
if not type(connection) == win32com.client.CDispatch:
application = None
SapGuiAuto = None
return
session = connection.Children(0)
if not type(session) == win32com.client.CDispatch:
connection = None
application = None
SapGuiAuto = None
return
session.findById("wnd[0]/usr/txtRSYST-BNAME").text = "username"
session.findById("wnd[0]/usr/pwdRSYST-BCODE").text = "password"
session.findById("wnd[0]").sendVKey(0)
print(sys.exc_info())
finally:
session = None
connection = None
application = None
SapGuiAuto = None
saplogin()

pythoncom.CoInitialize() is not called and the program terminates

I´m working on a Python program supposed to read incoming MS-Word documents in a client/server fashion, i.e. the client sends a request (one or multiple MS-Word documents) and the server reads specific content from those requests using pythoncom and win32com.
Because I want to minimize waiting time for the client (client needs a status message from server, I do not want to open an MS-Word instance for every request. Hence, I intend to have a pool of running MS-Word instances from which the server can pick and choose. This, in turn, means I have to reuse those instances from the pool in different threads and this is what causes trouble right now.
After I fixed the following error I asked previously on stack overflow, my code looks now like this:
import pythoncom, win32com.client, threading, psutil, os, queue, time, datetime
class WordInstance:
def __init__(self,app):
self.app = app
self.flag = True
appPool = {'WINWORD.EXE': queue.Queue()}
def initAppPool():
global appPool
wordApp = win32com.client.DispatchEx('Word.Application')
appPool["WINWORD.EXE"].put(wordApp) # For testing purpose I only use one MS-Word instance currently
def run_in_thread(instance,appid, path):
print(f"[{datetime.now()}] open doc ... {threading.current_thread().name}")
pythoncom.CoInitialize()
wordApp = win32com.client.Dispatch(pythoncom.CoGetInterfaceAndReleaseStream(appid, pythoncom.IID_IDispatch))
doc = wordApp.Documents.Open(path)
doc.SaveAs(rf'{path}.FB.pdf', FileFormat=17)
doc.Close()
print(f"[{datetime.now()}] close doc ... {threading.current_thread().name}")
instance.flag = True
if __name__ == '__main__':
initAppPool()
pathOfFile2BeRead1 = r'C:\Temp\file4.docx'
pathOfFile2BeRead2 = r'C:\Temp\file5.docx'
#treat first request
wordApp = appPool["WINWORD.EXE"].get(True, 10)
wordApp.flag = False
pythoncom.CoInitialize()
wordApp_id = pythoncom.CoMarshalInterThreadInterfaceInStream(pythoncom.IID_IDispatch, wordApp.app)
readDocjob1 = threading.Thread(target=run_in_thread,args=(wordApp,wordApp_id,pathOfFile2BeRead1), daemon=True)
readDocjob1.start()
appPool["WINWORD.EXE"].put(wordApp)
#wait here until readDocjob1 is done
wait = True
while wait:
try:
wordApp = appPool["WINWORD.EXE"].get(True, 1)
if wordApp.flag:
print(f"[{datetime.now()}] ok appPool extracted")
wait = False
else:
appPool["WINWORD.EXE"].put(wordApp)
except queue.Empty:
print(f"[{datetime.datetime.now()}] error: appPool empty")
except BaseException as err:
print(f"[{datetime.datetime.now()}] error: {err}")
wordApp.flag = False
openDocjob2 = threading.Thread(target=run_in_thread,args=(wordApp,wordApp_id,pathOfFile2BeRead2), daemon=True)
openDocjob2.start()
When I run the script I receive the following output printed on the terminal:
[2022-03-29 11:41:08.217678] open doc ... Thread-1
[2022-03-29 11:41:10.085999] close doc ... Thread-1
[2022-03-29 11:41:10.085999] ok appPool extracted
[2022-03-29 11:41:10.085999] open doc ... Thread-2
Process finished with exit code 0
And only the first word file is converted to a pdf. It seems like def run_in_thread terminates after the print statement and before/during pythoncom.CoInitialize(). Sadly I do not receive any error message which makes it quite hard to understand the cause of this behavior.
After reading into Microsofts documentation I tried using
pythoncom.CoInitializeEx(pythoncom.APARTMENTTHREADED) instead of pythoncom.CoInitialize(). Since my COM object needs to be called by multiple threads. However this changed nothing.

COM Error while trying to perform action on SAP application

We are using Python pywin32 com library for scripting SAP GUI application running on Windows.
Things were working until yesterday.
Now, while trying to access the line of code below which performs the maximize(), we are getting
com_error: (-2147417851, 'The server threw an exception.', None, None)
And getting the following error while trying to access any object in the SAP window (the last line in code).
AttributeError: Property '.text' cannot be set.
Can someone help? Let me know if more information is needed.
Below is the code snippet which we use to get a new scripting session, launch SAP and perform actions:
from subprocess import call
import win32com.client
import time
GUIPath = 'C:/Program Files (x86)/SAP/FrontEnd/SAPgui/'
WinTitle = 'SAP'
SID = 'xxxxxx.sap.xxxxx.com'
InstanceNo = 'xx'
shell = win32com.client.Dispatch("WScript.Shell")
cmdString = os.path.join(GUIPath, 'SAPgui.exe') + " " + SID + " " + InstanceNo
call(cmdString)
while not shell.AppActivate(WinTitle):
time.sleep(1)
checkGUIExists = False
while not checkGUIExists:
try:
SAP = win32com.client.GetObject("SAPGUI").GetScriptingEngine
session = SAP.FindById("/app/con[0]/ses[0]") # session
checkGUIExists = True
except:
time.sleep(1)
continue
//The lines failing//
session.findById("wnd[0]").maximize()
session.findById("wnd[0]/tbar[0]/okcd).text = <transaction>

Reuse opened connection to another script

I'm trying to build project consists of multiple python files. The first file is called "startup.py" and just responsible of opening connections to multiple routers and switches (each device allow only one connection at a time) and save them to the list. This script should be running all the time so other files can use it
#startup.py
def validate_connections_to_leaves():
leaves = yaml_utils.load_yaml_file_from_directory("inventory", topology)["fabric_leaves"]
leaves_connections = []
for leaf in leaves:
leaf_ip = leaf["ansible_host"]
leaf_user = leaf["ansible_user"]
leaf_pass = leaf["ansible_pass"]
leaf_cnx = junos_utils.open_fabric_connection(host=leaf_ip, user=leaf_user, password=leaf_pass)
if leaf_cnx:
leaves_connections.append(leaf_cnx)
else:
log.script_logger(severity="ERROR", message="Unable to connect to Leaf", data=leaf_ip, debug=debug,
indent=0)
return leaves_connections
if __name__ == '__main__':
leaves = validate_connections_to_leaves()
pprint(leaves)
#Keep script running
while True:
time.sleep(10)
now I want to re-use these opened connections in another python file(s) without having to establish connections again. if I just import it to another file it will re-execute the startup script one more time.
can anyone help me to identify which part I'm missing here?
You should consider your startup.py file as your entry point where all the logic is. You other files should be imported and used inside this file.
import otherfile1
import otherfile2
# import other file here
def validate_connections_to_leaves:
# ...
if __name__ == '__main__':
leaves = validate_connections_to_leaves()
otherfile1.do_something_with_the_connection(leaves)
#Keep script running
while True:
time.sleep(10)
And in your other file it will be simply:
def do_something_with_the_connection(leaves):
# do something with the connections

Python watchdog windows wait till copy finishes

I am using the Python watchdog module on a Windows 2012 server to monitor new files appearing on a shared drive. When watchdog notices the new file it kicks off a database restore process.
However, it seems that watchdog will attempt to restore the file the second it is created and not wait till the file has finished copying to the shared drive. So I changed the event to on_modified but there are two on_modified events, one when the file is initially being copied and one when it is finished being copied.
How can I handle the two on_modified events to only fire when the file being copied to the shared drive has finished?
What happens when multiple files are copied to the shared drive at the same time?
Here is my code
import time
import subprocess
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler
class NewFile(FileSystemEventHandler):
def process(self, event):
if event.is_directory:
return
if event.event_type == 'modified':
if getext(event.src_path) == 'gz':
load_pgdump(event.src_path)
def on_modified(self, event):
self.process(event)
def getext(filename):
"Get the file extension"
file_ext = filename.split(".",1)[1]
return file_ext
def load_pgdump(src_path):
restore = 'pg_restore command ' + src_path
subprocess.call(restore, shell=True)
def main():
event_handler = NewFile()
observer = Observer()
observer.schedule(event_handler, path='Y:\\', recursive=True)
observer.start()
try:
while True:
time.sleep(1)
except KeyboardInterrupt:
observer.stop()
observer.join()
if __name__ == '__main__':
main()
In your on_modified event, just wait until the file is finished being copied, via watching the filesize.
Offering a Simpler Loop:
historicalSize = -1
while (historicalSize != os.path.getsize(filename)):
historicalSize = os.path.getsize(filename)
time.sleep(1)
print "file copy has now finished"
I'm using following code to wait until file copied (for Windows only):
from ctypes import windll
import time
def is_file_copy_finished(file_path):
finished = False
GENERIC_WRITE = 1 << 30
FILE_SHARE_READ = 0x00000001
OPEN_EXISTING = 3
FILE_ATTRIBUTE_NORMAL = 0x80
if isinstance(file_path, str):
file_path_unicode = file_path.decode('utf-8')
else:
file_path_unicode = file_path
h_file = windll.Kernel32.CreateFileW(file_path_unicode, GENERIC_WRITE, FILE_SHARE_READ, None, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, None)
if h_file != -1:
windll.Kernel32.CloseHandle(h_file)
finished = True
print 'is_file_copy_finished: ' + str(finished)
return finished
def wait_for_file_copy_finish(file_path):
while not is_file_copy_finished(file_path):
time.sleep(0.2)
wait_for_file_copy_finish(r'C:\testfile.txt')
The idea is to try open a file for write with share read mode. It will fail if someone else is writing to it.
Enjoy ;)
I would add a comment as this isn't an answer to your question but a different approach... but I don't have enough rep yet. You could try monitoring filesize, if it stops changing you can assume copy has finished:
copying = True
size2 = -1
while copying:
size = os.path.getsize('name of file being copied')
if size == size2:
break
else:
size2 = os.path.getsize('name of file being copied')
time.sleep(2)
On linux you also get close event. Than solution would be to wait with processing file until file gets closed.
My approach would be to add on_closed handling.
class Handler(FileSystemEventHandler):
def __init__(self):
self.files_to_process = set()
def dispatch(self, event):
_method_map = {
'created': self.on_created,
'closed': self.on_closed
}
def on_created(self, event):
self.files_to_process.add(event.src_path)
def on_closed(self, event):
self.files_to_process.remove(event.src_path)
actual_processing(event.src_path)
I had a similar issue recently with watchdog. A rather simple but not very smart workaround was for me to check the change of file size in a while loop using a two-element list, one for 'past', one for 'now'. Once the the values are equal the copying is finished.
Edit: something like this.
past = 0
now = 1
value = [past, now]
while True:
# change
# test
if value[0] == value[1]:
break
else:
value = [value[1], value[0]]
This works for me. Tested in windows as well with python3.7
while True:
size_now = os.path.getsize(event.src_path)
if size_now == size_past:
log.debug("file has copied completely now size: %s", size_now)
break
# TODO: why sleep is not working here ?
else:
size_past = os.path.getsize(event.src_path)
log.debug("file copying size: %s", size_past)
Old I know, but I recently came up with a solution for this exact problem. In my case, I was only concerned with wav and mp3 files. This function will ensure that only files that are completely copied will be sent to makerCore() because the created placeholder files do not have any extension and will always end up in 'not ready'. Once the file is completed it will trigger the watchdog module again except this time with an extension. This will work on multiple files simultaneously as well.
def on_created(event):
#print(event)
if str(event.src_path).endswith('.mp3') or str(event.src_path).endswith('.wav'):
makerCore(event)
else:
print('not ready')
I am using a different approach that might not be the most elegant one but is easy to do on any plateform if you have control on the side copying the file.
Just had 'in-progress' to the name of the file until the copying is complete, and then rename the file. You can then have a while loop waiting for the file with the name without 'in-progress' to exist and you're good.
I've tried the check filesize - wait - check again routine many have suggested above but it's not very reliable. To make it work better I've added a check if the file is still locked.
file_done = False
file_size = -1
while file_size != os.path.getsize(file_path):
file_size = os.path.getsize(file_path)
time.sleep(1)
while not file_done:
try:
os.rename(file_path, file_path)
file_done = True
except:
return True
Following up to ravenwing's answer, more details can be found about on_closed in watchdog here.
As mentioned in the documented issue, there is no documentation available for on_closed yet and it can only be used with unix.

Categories

Resources