Cannot iterate VBA macros from Python - python

I am using VBA in conjunction with Python.
I imported the module OS, and for working with excel - openpyxl. The problem occurs when it iterates the function for running the VBA macro from Excel.
import random
from openpyxl import load_workbook
import os, os.path, win32com.client
wbi = load_workbook('Input.xlsm')
wsi = wbi.get_active_sheet()
wbo = load_workbook('Output.xlsx')
wso = wbo.get_active_sheet()
def run_macro(fName, macName, path=os.getcwd()):
"""
pre: fName is the name of a valid Excel file with macro macName
post: fName!macName is run, fName saved and closed
"""
fName = os.path.join(path, fName)
xlApp = win32com.client.Dispatch("Excel.Application")
fTest = xlApp.Workbooks.Open(fName)
macName = fTest.Name + '!' + macName
xlApp.Run(macName)
fTest.Close(1)
xlApp.Quit()
xlApp = None
def IBP():
ibp = wsi.cell('G12')
ibpv = random.randint(0,45)
ibp.value = ibpv
return ibp.value
def BP10():
bp10 = wsi.cell('G13')
bpv10 = random.randint(30,50)
bp10.value = bpv10
return bp10.value
for n in range(6):
IBP()
print IBP()
BP10()
run_macro('Input.xlsm','macro1')
wbo.save('Output.xlsx')
I think that the error is in run_macro('Input.xlsm','macro1') - it cannot iterate.
The output:
Qt: Untested Windows version 6.2 detected!
35
4
Traceback (most recent call last):
File "C:\Users\User\Desktop\Python Exp\Pr 1.py", line 77, in <module>
run_macro('Input.xlsm','macro1')
File "C:\Users\User\Desktop\Python Exp\Pr 1.py", line 18, in run_macro
fTest = xlApp.Workbooks.Open(fName)
File "C:\Python27\lib\site-packages\win32com\client\dynamic.py", line 522, in __getattr__
raise AttributeError("%s.%s" % (self._username_, attr))
AttributeError: Excel.Application.Workbooks
What am I doing wrong?

I'm not sure this will help, but you can try early binding. Run this script and then try yours again:
import win32com.client
xl = win32com.client.gencache.EnsureDispatch ("Excel.Application")
print xl.__module__
If that does not work, you can alway go back to late binding by hooking to Excel like this:
xl = win32com.client.dynamic.Dispatch("Excel.Application")
or by simply deleting this folder: C:\Python27\Lib\site-packages\win32com\gen_py\00020813-0000-0000-C000-000000000046x0x1x7
From the error message, it looks like your problem is on the line wb = xlApp.Workbooks.Open(fname). If the Python hooks to the Excel com servers were working correctly, then that line would not raise the exception that it did. I don't see anything wrong with the code where the exception occured. Sometimes early binding helps in situations like this.
good luck
Mike

Related

If sheet does not exist go to next Python

I'm writing a code that allows you to select the tables in a workbook sheet and send it by email. But it can happen that a sheet does not exist because there is no data.
I would like to know how I have the absence of a sheet and move to the other sends and also create a bouble for each sheet of the workbook instead of executing the same code on all the sheets.
I hope I have been precise.
I tried to continue without the error but the code (#import warnings #warnings.filterwarnings("ignore", category=DeprecationWarning)) does not work
like on "error resume next" in vba
Do you have another solution.
import win32com.client as win32
import xlwings as xw
import pandas as pd
import openpyxl
#import warnings
#warnings.filterwarnings("ignore", category=DeprecationWarning)
#wb = xw.Book()
wb = r"D:/Users/Desktop/Infos/MasterFile.xlsx"
data = xw.Book(wb)
Mylist_205 = data.sheets('Sheet_205')
selection_205=data.sheets('Sheet_205').used_range
Mylist_205.used_range.copy()
outlook = win32com.client.Dispatch("Outlook.Application")
# Create a new MailItem object
msg = outlook.CreateItem(0)
msg.To='servicesadvisor#infos.com'
msg.Subject = 'Subject'
msg.GetInspector.WordEditor.Range(Start=0, End=0).Paste()
msg.Display()
msg.Send()
Error:
Traceback (most recent call last):
File "C:/Users/sheets.py", line 17, in <module>
Mylist_205 = data.sheets('Sheet_205')
File "C:\Python\Python310\lib\site-packages\xlwings\main.py", line 4893, in __call__
return Sheet(impl=self.impl(name_or_index))
File "C:\Python\Python310\lib\site-packages\xlwings\_xlwindows.py", line 877, in __call__
return Sheet(xl=self.xl(name_or_index))
File "C:\Python\Python310\lib\site-packages\xlwings\_xlwindows.py", line 208, in __call__
v = self._inner(*args, **kwargs)
File "C:\Users\AppData\Local\Temp\gen_py\3.10\00020813-0000-0000-C000-000000000046x0x1x9.py", line 36625, in __call__
ret = self._oleobj_.InvokeTypes(0, LCID, 2, (9, 0), ((12, 1),),Index
pywintypes.com_error: (-2147352567,', (0, None, None, None, 0, -2147352565), None)
The first problem is that you are using () for .sheets. It is supposed to be []. I refactored it a bit because it is kind of confusing. I didn't test the email part of it, so I will only post what I tested.
I'm not sure what you are trying to put into your email, but after my code you can convert it to a dataframe or whatever.
One more thing about xlwings. Many times if you are working in a terminal, close the Excel application and then open the app again and try to run code against it, you might get the same pywintypes error. Try using the importlib module before you go nuts trying to debug it.
import importlib
import xlwings as xw
importlib.reload(xw)
Dummy file named MasterFile.xlsx:
import xlwings as xw
file_str= r"D:/Users/Desktop/Infos/MasterFile.xlsx"
wb = xw.Book(fpath)
dummy_sheets = [
'Sheet_201', 'Sheet_202', 'Sheet_204', 'Sheet_205', 'Sheet_206'
]
data = []
for sht in dummy_sheets:
try:
sht = wb.sheets[sht]
data.append(sht.used_range.value)
except:
# Do something else here.
print('Missing sheet.')

Using Python COM objects from Excel VBA

I am trying to create python classes to be used in Excel, taking references from Python: Programming on Win32.
Currently:
Python script (win32comLibrary.py)
class PythonUtilities:
_public_methods_ = [ "SplitString" ]
_reg_progid_ = "PythonDemos.Utilities"
_reg_clsid_ = "{AF272547-D5BC-4452-852E-3F8746672097}"
def SplitString(self, val, item = None):
import string
if item!=None: item = str(item)
return string.split(str(val), item)
if __name__== "__main__":
print("Registering COM server...")
import win32com.server.register
win32com.server.register.UseCommandLine(PythonUtilities)
Excel VBA
Sub test()
Set PythonUtils = CreateObject("PythonDemos.Utilities")
response = PythonUtils.SplitString("Hello From VB", " ")
For Each Item In response
MsgBox Item
Next Item
End Sub
Questions:
(a) at the reponse = ... line, there is a runtime error '-2147467259 (80004005)': Unexpected Python Error: Traceback (most recent call last): File .....
(b) when i typed python win32comLibrary.py --unregister in cmd, it returned Registering COM server...Traceback (most recent call last): File "win32comLibrary.py", line 19, in (module) import win32com.server.register ImportError: No module named win32com.server.register
I am currently using anaconda (spyder) and have installed pywin32. Invoking win32com.client methods from Python don't seem to throw any error.
Any assistance would be appreciated.
I have managed to solve the issue - just to close this off. Not sure if its the difference between older versions of python and 3.x but string.split(str(val), item) doesn't seem to work anymore. Instead, I have changed the return statement to return val.split(item). Oversight on my part - apologies.

How to pass a variable to a function in this example?

I wrote a very basic script to cleanup my downloads folder and everything worked fine, but I was not using any functions.
To clean things up a bit and make it more organized, I tried to create functions and pass the directory path as a variable "cleaningpath", but I think I am doing something incorrect.
import sys
import os
from os import listdir
from os.path import join
import shutil
#Variables
path="/Users/OwlFace/downloads"
cleaningpath=os.listdir(path)
def deleterars(cleaningpath):
rarcounter=0
for item in cleaningpath:
if item.endswith(".rar"):
os.remove(join(cleaningpath,item))
rarcounter+=1
print "you have succesfully removed", rarcounter, "rar files"
def organizemusic(cleaningpath):
mp3counter=0
if not os.path.exists("/Users/OwlFace/downloads/NewMusic/"):
os.makedirs("/Users/OwlFace/downloads/NewMusic/")
mp3folder="/Users/OwlFace/downloads/NewMusic/"
for item in cleaningpath:
if item.endswith(".mp3"):
location1 = join(cleaningpath,item)
location2 = join(mp3folder,item)
shutil.move(location1, location2)
mp3counter+=1
print "you have succesfully moved", mp3counter, "mp3's to the music folder"
if __name__ == "__main__":
deleterars(cleaningpath)
organizemusic(cleaning path)
Error:
Traceback (most recent call last):
File "cleaningscript.py", line 39, in <module>
organizemusic(cleaningpath)
File "cleaningscript.py", line 30, in organizemusic
location1 = join(cleaningpath,item)
File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/posixpath.py", line 70, in join
elif path == '' or path.endswith('/'):
AttributeError: 'list' object has no attribute 'endswith'
The error refers to the line:
location1 = join(cleaningpath,item)
This line doesn't work because cleaningpath is a list of file names, not a string. I think you want your global variable path as the first argument to join.
You have the same issue in your other function, on this line:
os.remove(join(cleaningpath,item))

removing py files and retaining pyc files breaks inspection code

The function below works just fine. But if I remove all py files (and leave the pycs intact) then I get an error:
To explain what I mean by 'intact' here is more or less what I did:
1. write a bunch of py files and stick them in a friendly directory structure
2. test code code. It works
3. compile all py files to get pyc files
4. delete py files
5. test code. It fails
The function:
def get_module_name_and_line():
"""
return the name of the module from which the method calling this method was called.
"""
import inspect
lStack = inspect.stack()
oStk = lStack[2]
oMod = inspect.getmodule(oStk[0])
oInfo = inspect.getframeinfo(oStk[0])
sName = oMod.__name__ #<<<<<<<<<<<<<<<<<< ERROR HERE
iLine = oInfo.lineno
return sName,iLine
The error:
AttributeError: 'NoneType' object has no attribute '__name__'
So oMod is None in this error. If the py files are around then oMod is never None.
The question:
Why does inspect only return a module if py files are intact? How can I make this function work without py files.
Full Traceback:
Original exception was:
Traceback (most recent call last):
File "/home/criticalid/programs/damn.py", line 630, in <module>
File "/home/criticalid/programs/golly/class_foo.py", line 121, in moo
File "/home/criticalid/programs/golly/class_foo.py", line 151, in get_module_name_and_line
AttributeError: 'NoneType' object has no attribute '__name__'
This works for me. It assumes that all modules are in packages within the current working directory. And it doesn't return the __main__ module, rather its file name.
I'm sure there is a better solution but this solves my problems.
def get_module_name_and_line():
"""
return the name of the module from which the method calling this method was called.
"""
def get_name_from_path(sPath):
import os
sCWD = os.getcwd()
lCWD = list(os.path.split(sCWD))
lPath = list(os.path.split(sPath))
lPath[-1] = '.'.join(lPath[-1].split('.')[:-1]) #remove file extension
lRet = [s for s in lPath[len(lCWD)-1:]]
return '.'.join(lRet)
import inspect
lStack = inspect.stack()
oStk = lStack[2]
iLine = inspect.getlineno(oStk[0])
sName = get_name_from_path(inspect.getfile(oStk[0]))
return sName,iLine

Running an Excel macro via Python?

I'm trying to run a macro via python but I'm not sure how to get it working...
I've got the following code so far, but it's not working.
import win32com.client
xl=win32com.client.Dispatch("Excel.Application")
xl.Workbooks.Open(Filename="C:\test.xlsm",ReadOnly=1)
xl.Application.Run("macrohere")
xl.Workbooks(1).Close(SaveChanges=0)
xl.Application.Quit()
xl=0
I get the following traceback:
Traceback (most recent call last):
File "C:\test.py", line 4, in <module>
xl.Application.Run("macrohere")
File "<COMObject <unknown>>", line 14, in Run
File "C:\Python27\lib\site-packages\win32com\client\dynamic.py", line 282, in _ApplyTypes_
result = self._oleobj_.InvokeTypes(*(dispid, LCID, wFlags, retType, argTypes) + args)
com_error: (-2147352567, 'Exception occurred.', (0, u'Microsoft Excel', u"Cannot run the macro 'macrohere'. The macro may not be available in this workbook or all macros may be disabled.", u'xlmain11.chm', 0, -2146827284), None)
EDIT
import win32com.client
xl=win32com.client.Dispatch("Excel.Application")
xl.Workbooks.Open(Filename="C:\test.xlsm",ReadOnly=1)
try:
xl.Application.Run("test.xlsm!testmacro.testmacro")
# It does run like this... but we get the following error:
# Traceback (most recent call last):
# File "C:\test.py", line 7, in <module>
# xl.Workbooks(1).Close(SaveChanges=0)
# File "C:\Python27\lib\site-packages\win32com\client\dynamic.py", line 192, in __call__
# return self._get_good_object_(self._oleobj_.Invoke(*allArgs),self._olerepr_.defaultDispatchName,None)
# com_error: (-2147352567, 'Exception occurred.', (0, None, None, None, 0, -2147352565), None)
except:
# Except isn't catching the above error... :(
xl.Workbooks(1).Close(SaveChanges=0)
xl.Application.Quit()
xl=0
I would expect the error is to do with the macro you're calling, try the following bit of code:
Code
import os, os.path
import win32com.client
if os.path.exists("excelsheet.xlsm"):
xl=win32com.client.Dispatch("Excel.Application")
xl.Workbooks.Open(os.path.abspath("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
I did some modification to the SMNALLY's code so it can run in Python 3.5.2. This is my result:
#Import the following library to make use of the DispatchEx to run the macro
import win32com.client as wincl
def runMacro():
if os.path.exists("C:\\Users\\Dev\\Desktop\\Development\\completed_apps\\My_Macr_Generates_Data.xlsm"):
# DispatchEx is required in the newest versions of Python.
excel_macro = wincl.DispatchEx("Excel.application")
excel_path = os.path.expanduser("C:\\Users\\Dev\\Desktop\\Development\\completed_apps\\My_Macr_Generates_Data.xlsm")
workbook = excel_macro.Workbooks.Open(Filename = excel_path, ReadOnly =1)
excel_macro.Application.Run\
("ThisWorkbook.Template2G")
#Save the results in case you have generated data
workbook.Save()
excel_macro.Application.Quit()
del excel_macro
Just a quick note with a xlsm with spaces.
file = 'file with spaces.xlsm'
excel_macro.Application.Run('\'' + file + '\'' + "!Module1.Macro1")
I suspect you haven't authorize your Excel installation to run macro from an automated Excel. It is a security protection by default at installation. To change this:
File > Options > Trust Center
Click on Trust Center Settings... button
Macro Settings > Check Enable all macros
Hmm i was having some trouble with that part (yes still xD):
xl.Application.Run("excelsheet.xlsm!macroname.macroname")
cos im not using excel often (same with vb or macros, but i need it to use femap with python) so i finaly resolved it checking macro list:
Developer -> Macros:
there i saw that: this macroname.macroname should be sheet_name.macroname like in "Macros" list.
(i spend something like 30min-1h trying to solve it, so it may be helpful for noobs like me in excel) xD
A variation on SMNALLY's code that doesn't quit Excel if you already have it open:
import os, os.path
import win32com.client
if os.path.exists("excelsheet.xlsm"):
xl=win32com.client.Dispatch("Excel.Application")
wb = xl.Workbooks.Open(os.path.abspath("excelsheet.xlsm"), ReadOnly=1) #create a workbook object
xl.Application.Run("excelsheet.xlsm!modulename.macroname")
wb.Close(False) #close the work sheet object rather than quitting excel
del wb
del xl
I tried the win32com way and xlwings way but I didn't get any luck. I use PyCharm and didn't see the .WorkBook option in the autocompletion for win32com.
I got the -2147352567 error when I tried to pass a workbook as variable.
Then, I found a work around using vba shell to run my Python script.
Write something on the XLS file you are working with when everything is done. So that Excel knows that it's time to run the VBA macro.
But the vba Application.wait function will take up 100% cpu which is wierd. Some people said that using the windows Sleep function would fix it.
Import xlsxwriter
Shell "C:\xxxxx\python.exe
C:/Users/xxxxx/pythonscript.py"
exitLoop = 0
wait for Python to finish its work.
Do
waitTime = TimeSerial(Hour(Now), Minute(Now), Second(Now) + 30)
Application.Wait waitTime
Set wb2 = Workbooks.Open("D:\xxxxx.xlsx", ReadOnly:=True)
exitLoop = wb2.Worksheets("blablabla").Cells(50, 1)
wb2.Close exitLoop
Loop While exitLoop <> 1
Call VbaScript
For Python 3.7 or later,(2018-10-10), I have to combine both #Alejandro BR and SMNALLY's answer, coz #Alejandro forget to define wincl.
import os, os.path
import win32com.client
if os.path.exists('C:/Users/jz/Desktop/test.xlsm'):
excel_macro = win32com.client.DispatchEx("Excel.Application") # DispatchEx is required in the newest versions of Python.
excel_path = os.path.expanduser('C:/Users/jz/Desktop/test.xlsm')
workbook = excel_macro.Workbooks.Open(Filename = excel_path, ReadOnly =1)
excel_macro.Application.Run("test.xlsm!Module1.Macro1") # update Module1 with your module, Macro1 with your macro
workbook.Save()
excel_macro.Application.Quit()
del excel_macro

Categories

Resources