When Excel addin runs 'RunPython()', Workbook.caller() raise an error - python

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

Related

Python win32com functions for Linux

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()
'''

use xlwings to call Python UDFs

I'm trying to call two Python UDFs in excel via xlwings, i saw the following error message:
Make sure that this workbook contains the xlwings module and you are trusting access to the VBA project object module(Options).
Macro setting is already enabled, the workbook also referenced to xlwings. The xlwings version in Anaconda is 0.20.0, i think this is the latest version, but i can only see "import Python UDFs" button under xlwings Add-in settings, nothing else, which is different from the Add-in settings i saw in some videos. I'm using Jupyter Notebook, my python code is saved in "M:\SolverFunction.ipynb". I also saved the code in .py extension with the same name in the same directory, not sure if i can just use .ipynb extension directly. Below is my function settings in VBA, can someone please check if everything is correct:
PYTHON_WIN = "C:\Program Files\Anaconda3\pythonw.exe"
PYTHON_MAC = ""
PYTHON_FROZEN = ThisWorkbook.Path & "\build\exe.win32-2.7"
PYTHONPATH = "M:\"
UDF_MODULES = "SolverFunction"
UDF_DEBUG_SERVER = False
LOG_FILE = ""
SHOW_LOG = True
OPTIMIZED_CONNECTION = False
when i run the macro Run_Python_Function, it shows SyntaxError:
Syntax Error in Macro
I need to call two python UDFs,don't really know how to fix this error, there is no error in the python code itself.
Another question: in the python code i use 'xw.Book' to call the existing sheet
wb = xw.Book(r'M:\SolverFunction.xlsm')
If the sheet is saved in another directory with another name, say "C:\Desktop\DVA Totem Submission\xyz.xlsm", except modifying the code
wb = xw.Book(r'C:\Desktop\DVA Totem Submission\xyz.xlsm')
do i need to change the function settings in VBA as well?
Thanks,

Running an Excel macro via Python (using a Mac)?

I was wondering whether it would be possible to run an excel macro via Python (using a Mac, I specify the machine because I know that most of the codes used win32., which is not for Mac users). Did someone try to figure it out?
Have a look at xlwings. It is a well thought out python package that allows you to control an excel application from python (and vice versa). It supports both Windows and Mac. On Mac it uses psutil and appscript behind the scenes to communicate with the excel application.
The xlwings documentation gives the following example for executing an excel VBA macro from python code:
Examples
This VBA function:
Function MySum(x, y)
MySum = x + y
End Function
can be accessed like this:
>>> import xlwings as xw
>>> wb = xw.books.active
>>> my_sum = wb.macro('MySum')
>>> my_sum(1, 2)
3

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 do I call an Excel macro from Python using xlwings?

I've read the API docs for xlwings, and played around with Workbook and Sheet objects in the interpreter, but I can't figure out how to call a macro from Python.
How do I use xlwings to call an Excel macro from Python?
This is not implemented yet, but there's an open issue for it, see here. In the meantime, you can work around it like so (this is for Windows, but the Mac version works accordingly, see again in the issue):
from xlwings import Workbook
wb = Workbook(...)
wb.application.xl_app.Run("your_macro")
update: for more recent versions, you have to do:
from xlwings import Workbook, Application
wb = Workbook(...)
Application(wb).xl_app.Run("your_macro")
update 2: This functionality is now natively supported from >=v0.7.1. Let's assume, there is a VBA function YourMacro that sums up two numbers:
>>> import xlwings as xw
>>> wb = xw.Book(r'C:\path\to\mybook.xlsm')
>>> your_macro = wb.macro('YourMacro')
>>> your_macro(1, 2)
3.0
I got issues when I updated xlwings to 0.9+ version.
To run vba macro with xlwings, I used the code written below for running macros inside the personal workbook (PERSONAL.XLSB).
The updated code no2 of Felix didn't work for me, for macro inside the personal workbook.
import xlwings as xw
wb = xw.Book(excel_file_path)
app = wb.app
# into brackets, the path of the macro
macro_vba = app.macro("'PERSONAL.XLSB'!my_macro")
macro_vba()
Hope it will help.
I know it is a late answer, but all the ways posted above didn't work for me.
But I have found another way, by using the awesome api-interface provided by xlwings.
Here is my code I used to run a macro:
xlApp = xw.App(visible=False)
wb= xw.books.open('.\\Path\\To\\File.xlsm')
a = xlApp.api.Application.Run("macroTest")
My macro opened a MsgBox and returned the value 1 just for test and it worked very well.
Although one should avoid using MsgBox, since it was opened in background.
Btw. the api-interface is available in many (when not all) objects and it is really powerfull if you are used to VBA programming.
Another simple way if you already open a excel.
xw.apps.active.macro('yourMacro')(1, 2)
Extending #Felix Zumstein's answer and all other answers here, please ensure you have disabled Macro settings as you may face COM issues later.
You can change that under,
File > Options > Trust Center > Trust Center Settings > Macro Settings > Enable all macros (not recommended...)

Categories

Resources