XLwings - Can't get Excel application to quit even with kill() method - python

I have searched and cannot seem to find the answer to my issue. I’m hoping someone can help.
Here is a slimmed down version of my code. I have it within a unit test TestCase class. I open three books, one of which is an xlsm file (wb2), do some processing, and then save the wb2 file with another name and close all three workbooks.
What's happening is that all three workbooks are closing, but there still remains an instance of Excel open. Like, an empty shell with nothing in it. I searched and found the kill() method, but that is not killing it (whether I use self.xw.App().kill() or just xw.App().kill()). Also, I print out all of the open apps and it returns an empty list. So, I don't know what this instance remaining is all about and why it's not getting killed. BTW, I am using Excel 365. Thanks in advance.
import xlwings as xw
class TestClass(unittest.TestCase):
def test_xltest(self):
#Open 3 separate workbooks
self.wb1 = xw.Book(workbook1)
self.wb2 = xw.Book(workbook2)
self.wb3 = xw.Book(workbook3)
#Do some processing here
def tearDown(self):
self.wb1.close()
self.wb2.save(self.newDirectoryPath)
self.wb2.close()
self.wb3.close()
print (xw.apps)
self.xw.App().kill()
if __name__ == "__main__":
unittest.main()

Related

AttributeError: 'NoneType' object has no attribute 'Worksheets' in python win32com.client

def messageToCal(path, calculatorName,argList, eventName, fucName,PCBName):
# location of LED calculator
loc = path+calculatorName
#print loc
# open excel, run macro called "external_Run" with argList
xls=win32com.client.Dispatch("Excel.Application")
wb = xls.Workbooks.Open(Filename=loc)
try:
xls.Application.Run("external_Run",argList)
except Exception as e:
print "--------------- ERROR ------------------"
print(e)
print "=> No data was found, please check your input file"
raise
#xls.Visible = True
# disable asking dialog when close excel
xls.DisplayAlerts = False
# export first sheet (macro output) to csv
w=wb.Worksheets(1)
w.SaveAs(path +'#'+eventName+'_'+fucName+'_'+PCBName.replace(".", "")+'_' +str(argList[3])+ '.csv',6)
xls.Application.Quit()
del xls
While running this code it throws error as
**w=wb.Worksheets(1)
AttributeError: 'NoneType' object has no attribute 'Worksheets'**
Previously it was running fine. Suddenly it throwing the error.
Something is causing your code to fail to open the workbook correctly. This is causing wb to be set to None. That is why you are getting an AttributeError when attempting to access wb.Worksheets(1).
If nothing in the code has changed, it's likely that something in your spreadsheet is causing the error. You'll want to verify that the spreadsheet is in the correct location in addition to looking at any recent changes to the spreadsheet to determine what the actual cause of the problem is.
There is no problem with the Excel sheet, it's just that your code opens up excel when you run your code, but never closes it i.e. with wb.close() in your case.
If you open the Task Manager and click on more details you will see Excel running in the background even after the program has ended.
Yes, you can close it through the task manager each time after running the program, to work with that Excel file again.
Also, if you close it that way and open the file through Excel you will see that Excel provides the history (which will be the exact same data) of that file, as the program was closed abruptly before.
As I mentioned above you can use wb.close() and close your workbook through code. Although it will give you an error, but won't affect the outcome of your program in any noticeable way. I applied this patch and it all worked fine(ignoring the unharmful error).

Writing to an .ini file using configparser results in an empty file if restarted while program is running

I have a program that runs a method every five seconds. In this method, I require writing to the configuration .ini file and, because this is embedded software, it must be able to handle the system shutting down at unknown times. However, every time the system shuts down while the program is running, the .ini file is empty when the system starts up again.
Here is the code for the method being run:
def HandleBatteryMonitoring():
#some code before this...
systemConfig = ConfigObj('settings.ini')
systemConfig.filename = 'settings.ini'
systemConfig['systemsettings']['batterychargebuslsb'] = str(chargingBatteryMSB)
systemConfig['systemsettings']['batterychargebuslsb'] = str(chargingBatteryLSB)
systemConfig['systemsettings']['batterydischargebusmsb'] = str(dischargeBatteryMSB)
systemConfig['systemsettings']['batterydischargebuslsb'] = str(dischargeBatteryLSB)
systemConfig['systemsettings']['batterypercentage'] = str(batteryPercentage)
systemConfig.write()
Currently it is using ConfigObj, but only because the same problem happened with ConfigParser, and I was hoping a different library would help the problem. Here's the same code when it used ConfigParser:
def HandleBatteryMonitoring():
#some code before this...
systemConfig = configparser.ConfigParser('settings.ini')
systemConfig['systemsettings']['batterychargebuslsb'] = str(chargingBatteryMSB)
systemConfig['systemsettings']['batterychargebuslsb'] = str(chargingBatteryLSB)
systemConfig['systemsettings']['batterydischargebusmsb'] = str(dischargeBatteryMSB)
systemConfig['systemsettings']['batterydischargebuslsb'] = str(dischargeBatteryLSB)
systemConfig['systemsettings']['batterypercentage'] = str(batteryPercentage)
with open('settings.ini', 'w') as file:
systemConfig.write(file)
That method is called from here:
def OnHandleCharging():
while True:
HandleBatteryCharging()
time.sleep(5)
It should be noted that, while the program is running, the file is being written to correctly, and I can watch the values changing as the file changes. This only happens when the system is restarted during operation.
I need this config file to not be emptied upon restarting. Any solutions or workarounds for this would be greatly appreciated.
Try to open the file as 'r+' and just change the vaules, it is probably overwriting the file if you open it with 'w'.

Update Links in for Excel Spreadsheet Using Python

I am running simulations in Python that generate output that need to be directly consumed by a modeler in their excel workbooks. I have generated code that will directly output my data into their excel spreadsheet template. The code I have generated to output the data directly to their template is fine, but the problem I am running into is that the modeler has a series of workbooks that are "linked" together. If I insert my data into their spreadsheet, the links to that workbook do no update unless the user physically opens the workbook to "Edit Links" -> "Update Values". If there was one workbook, then the user can simply open the workbook with no problem. In reality, there will be over 100 workbooks that need the links updated. Unfortunately, there is nothing I can do to change the modeler's approach in linking workbooks -- the only thing I can do is accommodate their approach.
My goal is to create a Python solution that will allow me to 1) Generate the simulated Data, 2) Insert my generated data into the modeler's workbook, and 3) Update all of the links between workbooks. Ultimately, in order to be streamlined, I want to be able to do all three in one end-to-end python program. I have solved (1) and (2), and I have a solution for (3) that almost works. I have generated the following functional script:
from win32com.client import Dispatch
import pandas as pd
from openpyxl import load_workbook
import os
import time
def run_macro(workbook_name, vba_sub, com_instance):
wb = com_instance.workbooks.open(workbook_name)
wb.RefreshAll()
xl_module = wb.VBProject.VBComponents.Add(1)
xl_module.CodeModule.AddFromString(vba_sub.strip())
com_instance.Application.Run('UpdateLinkValues')
wb.Save()
wb.Close()
return True
def main():
dir_root = ("C:\\Model_Spreadsheets")
vba_sub = \
'''
sub UpdateLinkValues()
Application.AskToUpdateLinks = False
ActiveWorkbook.UpdateLink Name:=ActiveWorkbook.LinkSources
end sub
'''
xl_app = Dispatch("Excel.Application")
xl_app.Visible = False
xl_app.DisplayAlerts = False
for root, dirs, files in os.walk(dir_root):
for fn in files:
if fn.endswith(".xlsx") and fn[0] is not "~":
run_macro(os.path.join(root, fn), vba_sub, xl_app)
xl_app.Quit()
if __name__ == "__main__":
main()
This script is really close to the correct solution I am looking for, but I run into a VBA error seemingly 'randomly':
run-time error '1004' method 'updatelink' method of object '_workbook' failed
This error does appear each time I try to run this script, but it does not occur for the same workbook each time -- sometimes, it occurs on the first workbook, sometimes on the 15th, etc...
I have an option to debug in VBA, and the only way that I can continue on to the next workbook is if I change the macro to
sub UpdateLinkValues()
Application.AskToUpdateLinks = False
end sub
if I run this macro and exit debug, the program will continue to run until it encounters the same error again. My first thought was that maybe there is a timing issue between me opening the workbook and trying to run the macro. A workaround that I have found is that I can change the macro and the app visibility:
vba_sub = \
'''
sub UpdateLinkValues()
Application.AskToUpdateLinks = False
end sub
'''
and
xl_app.Visible = True
This works fine, but I am not a fan of having each of the workbooks open and close because it takes a long time. My question is, does anyone know why this run-time error is coming up -- with a solution? Or perhaps, does anyone know how to intercept this run-time error in Python as an exception? If I can intercept this error as an exception in python, then I could use my alternative solution for those particulars workbooks.
Thanks in advance!
Consider having Python directly run the method UpdateLink with the COM objects you initialize, namely the xl_app and wb objects. No need to build a macro in each workbook and then call it.
Below UpdateLink() is wrapped in a try/except/finally block in case workbook has no links as LinkSources will return an Empty value, raising a COM exception, the very error you receive:
run-time error '1004' method 'updatelink' method of object '_workbook'
failed
Also be sure to uninitialize objects (a good best practice in VBA too: Set wb = Nothing) after use to free CPU resources else they remain as background processes until garbage collection.
def run_macro(workbook_name, com_instance):
wb = com_instance.workbooks.open(workbook_name)
com_instance.AskToUpdateLinks = False
try:
wb.UpdateLink(Name=wb.LinkSources())
except Exception as e:
print(e)
finally:
wb.Close(True)
wb = None
return True
def main():
dir_root = ("C:\\Model_Spreadsheets")
xl_app = Dispatch("Excel.Application")
xl_app.Visible = False
xl_app.DisplayAlerts = False
for root, dirs, files in os.walk(dir_root):
for fn in files:
if fn.endswith(".xlsx") and fn[0] is not "~":
run_macro(os.path.join(root, fn), xl_app)
xl_app.Quit()
xl = None
Aside - though VBA ships by default with Excel and MS Office applications, it is actually a separate component. To check, under Tools \ References in VBA IDE, you will see VBA is the first checked item, nothing built-in. In fact, VBA does exactly what you are doing in Python: making a COM interface to the Excel Object Library. So in a sense VBA is just as related to Excel and Python is!

How to detect a .lock file in a geodatabase

I am very new to python, but I have written a simple python script tool for automating the process of updating mosaic datasets at my job. The tool runs great, but sometimes I get the dreaded 9999999 error, or "the geodatase already exists" when I try to overwrite the data.
The file structure is c:\users\my.name\projects\ImageryMosaic\Alachua_2014\Alachua_2014_mosaic.gdb. After some research, I determined that the lock was being placed on the FGDB whenever I opened the newly created mosaic dataset inside of the FGDB to check for errors after running the tool. I would like to be able to overwrite the data instead of having to delete it, so I am using the arcpy.env.overwriteOutput statement in my script. This works fine unless I open the dataset after running the tool. Since other people will be using this tool, I don't want them scratching thier heads for hours like me, so it would be nice if the script tool could look for the presence of a .Lock file in the geodatabase. That way I could at least provide a statement in the script as to why the tool failed in lieu of the unhelpful 9999999 error. I know about arcpy.TestSchemaLock, but I don't think that will work in this case since I am not trying to place a lock and I want to overwrite the FGDB, not edit it.
Late but this function below will check for lock files in given (gdb) path.
def lockFileExist(path = None):
if path == None:
import traceback
raise Exception("Invalid Path!")
else:
import glob
full_file_paths = glob.glob(path+"\\*.lock")
for f in full_file_paths:
if f.endswith(".lock"):
return True
return False
if lockFileExist(r"D:\sample.gdb"):
print "Lock file found in gdb. Aborting..."
else:
print "No lock files found!. Ready for processing..."

Interaction between open and dispatched excel processes, win32com

I am using the win32com (Python 2.7 (Anaconda) in LiClipse) to start separate instances of excel...
class ExcelDocument(object):
"""Excel class
"""
def __init__(self, xlDocIn, make_visible=False):
"""Open spreadsheet"""
self.excelApp = DispatchEx('Excel.Application')
I then do bits and bobs to the excel document (using MS Office 2013), including opening another document with DispatchEx...
objExcel1 = ExcelDocument(PATH_TABLE,False)
objExcel1.update_sheets()
...
objExcel2 = ExcelDocument(PATH_BACKG, False)
Trying to assign a second ExceLDocument class will crash the script.
If I change init to
def __init__(self, xlDocIn, make_visible=False):
"""Open spreadsheet"""
try:
self.excelApp = GetActiveObject('Excel.Application')
except:
self.excelApp = DispatchEx('Excel.Application')
the script runs fine.
If i have an excel file open when I run the script either
a. the script will crash upon editing the open file.
b. the open excel file will close when these lines of code are executed...
def close(self):
"""Close spreadsheet resources"""
self.excelApp.DisplayAlerts = True
self.workbook.Saved = 0
self.workbook.Close(SaveChanges=0)
self.excelApp.Visible = 0
Is there a reason I cannot call DispatchEx the second time, as my script should run in the background and not interrupt the any open excel files?
You should design your script assuming there can only be one instance of Excel running, although it can contain multiple workbooks. If you use GetObject (instead of GetActiveObject) then win32com will return a handle to the existing app if one exists, or start the app if it doesn't. So you won't need the if/else.
Also what this means in terms of design is that you should have a way of tracking which workbooks get opened, and close only those, so that the final state of the Excel application is the same as when you started script. So you would have one instance of ExcelDocument per workbook, each using GetObject, and each one closing the workbook that it represents. Before you create the first ExcelDocument, save GetActiveObject so your script knows if it should close the app on exit.
Basically:
activeXlApp = win32com.client.GetActiveObject('Excel.Application')
objExcel1 = ExcelDocument(PATH_TABLE,False) # uses GetObject()
objExcel1.update_sheets()
...
objExcel2 = ExcelDocument(PATH_BACKG, False)
if activeXlApp is not None:
activeXlApp.Close()

Categories

Resources