use xlwings to call Python UDFs - python

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,

Related

RefreshAll function not working on excel when call from a python function on a unsaved excel workbook

I want to update all the functions on my current workbook when I am working on it by the press of a button from the ribbon but the refreshAll function does not work.
Following is the refresh_func function mapped to button clicked which run the logic
from win32com.client.gencache import EnsureDispatch
def refresh_func():
ExcelApp = EnsureDispatch('Excel.Application')
ExcelApp.Visible = True
ExcelWrkbook = ExcelApp.Workbooks(1)
ExcelWrkbook.RefreshAll()
Specs:
Python 3.8.6 32 bit
Pyxll 5.0.5 32 bit
Microsoft Excel 2016
There can be a situation that the excel file is not saved and the user would like to update the function on his working sheet but does not want to save it. Also, the solution should not force me to save or close a file for the solution to work or refresh the function
I am open to any github package that solves this issue.
Hey guys got the solution :
ExcelApp = EnsureDispatch('Excel.Application')
# Either of the following 3
# this will also work
ExcelApp.CalculateFull()
# this is basically using shortcut keys CTRL+ALT+F9 (CTRL+ALT+SHIFT+F9 this an alternative that also works)
ExcelApp.SendKeys("^%{F9}")
# this will also work
ExcelApp.CalculateFullRebuild()

name 'Application' not defined

I'm trying to open an excel workbook and run a macro using python. However, the macro exists in my Personal Macro Library and I need to run it from there. When I try to do that I got the following error:
"Cannot run the macro Macro may not be available in this workbook..."
To remedy this problem, I used Application.run() hoping that this would allow me to run a macro not present in the workbook I am trying to run it on. However, when I do this I get another error:
"name 'Application' is not defined"
Here is my code below. It's real simple.
.
.
.
# Open workbook
wb2 = xw.Book('C:/Users/AChakrav/Documents/LTspiceXVII/Circuits/INV/INVAMP_MEAS_Script.xlsm')
time.sleep(5)
Application.Run "'PERSONAL.XLSB'!CompiledTableGenerator"
#ExcelMacro_2 = wb2.macro('PERSONAL.XLSB!CompiledTableGenerator')
#ExcelMacro_2()
time.sleep(5)
You may take a look at the problem from a different angle - e.g., try to refer the Personal.xlsb file in Python and give it a try from there. E.g., to locate it, run this in the immediate window in VBA:
?Application.StartupPath
and hardcode it later in the Python code.
Then write something simple as this one in Personal.xlsb:
In Python, install pipiwin32 and run the code like this:
import win32com.client
xl=win32com.client.Dispatch('Excel.Application')
xl.Workbooks.Open('C:\\Users\\gropc\\AppData\\Roaming\\Microsoft\\Excel\\XLSTART\\Personal.xlsb')
xl.Application.Run('Personal.xlsb!Modul1.SomeApplication')
The message box with the date and time will show up. The VBA code should be well written, to make sure it refers correctly the workbooks you need, but this is another issue.

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

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

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

Categories

Resources