I'm looking for win32com alternative which will work on Linux. As win33com won't work in Linux. Now, we are using win32com but we are planning to move to Linux. We are using below script for refresh Excel pivot table. We need python script for same which will work on Linux.
Kindly advise
'''
import win32com.client
# Start an instance of Excel
xlapp = win32com.client.DispatchEx("Excel.Application")
# Open the workbook in said instance of Excel
wb = xlapp.workbooks.open(<path_to_excel_workbook>)
# Optional, e.g. if you want to debug
# xlapp.Visible = True
# Refresh all data connections.
wb.RefreshAll()
wb.Save()
'''
Related
I manage to open Outlook Elements like mails or task with this code:
import win32com.client as win32
outlook = win32.gencache.EnsureDispatch('Outlook.Application')
new_mail = outlook.CreateItem(4)
new_mail.Display(True)
And its possible to open Excel with this code:
excel = win32.gencache.EnsureDispatch('Excel.Application')
excel.Visible = True
But how can I just open the main window of outlook? Can't find any functions or numbers for the "createItem" to just open the normal outlook window.
Someone got ideas?
I tried to search throu the MS VBA Api but couldnt find any solution
Inspired by this, one option might be:
outlook.Session.GetDefaultFolder(6).Display()
where 6 corresponds to the inbox as documented here.
You need to display an Explorer window of Outlook. The Explorers.Add method creates a new instance of the explorer window, then you just need to call the Explorer.Display method which displays a new Explorer object for the folder. The Display method is supported for explorer and inspector windows for the sake of backward compatibility. To activate an explorer or inspector window, use the Activate method instead. Here is the sample VBA code which opens a new explorer window for the Drafts folder in Outlook:
Sub DisplayDrafts()
Dim myExplorers As Outlook.Explorers
Dim myOlExpl As Outlook.Explorer
Dim myFolder As Outlook.Folder
Set myExplorers = Application.Explorers
Set myFolder = Application.GetNamespace("MAPI").GetDefaultFolder(olFolderDrafts)
Set myOlExpl = myExplorers.Add(myFolder, olFolderDisplayNoNavigation)
myOlExpl.Display
End Sub
The Outlook object model is common for all programming languages, so I think you will find the sequence of property and method calls that should be used to get the job done.
To start Outlook application, try os.startfile("outlook") or see example
import psutil
import os
class Outlook:
#staticmethod
def is_outlook_running():
for p in psutil.process_iter(attrs=['pid', 'name']):
if p.info['name'] == "OUTLOOK.EXE":
print("Yes", p.info['name'], "is running")
break
else:
print("No, Outlook is not running")
os.startfile("outlook")
print("Outlook is starting now...")
if __name__ == "__main__":
outlook = Outlook()
outlook.is_outlook_running()
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!
I've a macro in LibreOffice BASIC and I want to run it from my python program. I've found some threads in which they use this code:
import os
import win32com.client
if os.path.exists("excelsheet.xlsm"):
xl=win32com.client.Dispatch("Excel.Application")
xl.Workbooks.Open(Filename="C:\Full Location\To\excelsheet.xlsm", ReadOnly=1)
xl.Application.Run("excelsheet.xlsm!modulename.macroname")
## xl.Application.Save() # if you want to save then uncomment this line and change delete the ", ReadOnly=1" part from the open function.
xl.Application.Quit() # Comment this out if your excel script closes
del xl
But this is for windows Excell program and I want for the LibreOffice program. Is it possible to do this?
Thanks :)
My preferred way is to put the python script in the Scripts/python subfolder of your LibreOffice user directory. Then add this function at the bottom:
def call_basic_macro():
document = XSCRIPTCONTEXT.getDocument()
frame = document.getCurrentController().getFrame()
ctx = XSCRIPTCONTEXT.getComponentContext()
dispatcher = ctx.ServiceManager.createInstanceWithContext(
'com.sun.star.frame.DispatchHelper', ctx)
url = document.getURL()
macro_call = ('macro:///Standard.Module1.Macro1("%s")' % url)
dispatcher.executeDispatch(frame, macro_call, "", 0, ())
g_exported_scripts=call_basic_macro,
Now run the python script from Writer by going to Tools -> Macros -> Run Macro. Expand My Macros and select the name of the script.
Another way which seems closer to your Excel example is to start a listening instance of LibreOffice with a system call:
start soffice -accept=socket,host=0,port=2002;urp;
I typically do that part in a shell script (batch file on Windows) rather than python. Then in python, get the document context from the instance:
import uno
localContext = uno.getComponentContext()
After that, the code would look similar to what is above. Note that with this approach on Windows, the python.exe included with LibreOffice must be used in order to load the uno module.
A third way is to simply do a system call:
soffice "macro:///Standard.Module1.Macro1()"
For more on this third approach, see https://forum.openoffice.org/en/forum/viewtopic.php?f=20&t=8232.
I found xlwings is a very exceptional project as a bridge between Excel and Python. So I am applying it to my excel addin development.
But I got some problem.
When excel addin calls python module, the Workbook.caller() does not return Workbook object(addin workbook itself)! Just Error!
Instead of Workbook.caller(), I make use of 'Workbook() passing by 'Current ActiveWorkbook's name'. But in that case, I'm afraid that 'Optimize_connection= true' may raise memory garbage issue!
(If 'IsAddin' property in VBA is off, then it runs very well)
Is there anyone to help me?
Thank you in advance.
I have not tested this, but the solution may be as simple as replacing ThisWorkbook with ActiveWorkbook when using the xlwings VBA module as add-in. Anyhow, I've created an issue to get this resolved: https://github.com/ZoomerAnalytics/xlwings/issues/153
Here's a work-around to get the Excel add-in that's calling Python code:
import mock
import platform
import xlwings
from xlwings import Workbook
def get_add_in():
if platform.system() == 'Windows':
# Workbook.caller crashers instead of returning the add-in
get_add_in_caller_on_windows()
else:
return Workbook.caller()
#mock.patch('xlwings.Sheet.active')
def get_addin_caller_on_windows(mock_active):
# The xlwings.Sheet.active method is mocked because the add-in has no
# active worksheet.
xl_app = xlwings.xlplatform.get_xl_apps()[0]
return Workbook(xl_workbook=xl_app.ThisWorkbook)
It works with Python 3.4 and xlwings 0.6.4 on:
Windows 8.1 with Excel 2013
OS X 10.10 (Yosemite) with Excel for Mac 2011
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()