Error using xlwings package with streamlit - python

I have a streamlit app that allow user to read excel file and try to split each sheet into a different workbook for this I am using the xlwings package when I try to debug the code it crash and display the error below:
error :com_error:(-2147221008,'Colnitialize has not been called.',None,None)
What does this error mean? And how to fix it?
Code:
import streamlit as st
import pandas as pd
import numpy as np
from pathlib import Path
import xlwings as xw
sheet5_path = "C:/Users/gm/Documents"
st.markdown("""
<style>
.header_title {
font-size:30px !important;
color:black;
text-align :center
}
.big-font {
font-size:50px !important;
color:Red
</style>
""", unsafe_allow_html=True)
st.title("Excel Split & Send")
try:
data = st.sidebar.file_uploader("Upload Dataset",type=["csv","xlsx","xls"])
except Exception as e:
st.write("error is: {}".format(e))
if data is not None:
sheet1,sheet2,sheet3,sheet4,sheet5 = None,None,None,None,None
with pd.ExcelFile(data,) as reader:
sheet1 = pd.read_excel(reader,sheet_name="sheet1")
sheet2 = pd.read_excel(reader,sheet_name="sheet2")
sheet3 = pd.read_excel(reader,sheet_name="sheet3")
sheet4 = pd.read_excel(reader,sheet_name="sheet4")
sheet5 = pd.read_excel(reader,sheet_name="sheet5")
st.write(sheet1.finished[0])
st.write(sheet1.unfinished[0])
st.markdown('<p class="header_title">sheet1|{}</p>'.format(str(sheet1.shape[0])), unsafe_allow_html=True)
sheet1 = pd.read_excel(reader,sheet_name="sheet1",usecols=[0,1,2,3,4])
st.dataframe(sheet1)
st.write(sheet2.finished[0])
st.write(sheet2.unfinished[0])
st.markdown('<p class="header_title">sheet2|{}</p>'.format(str(sheet2.shape[0])), unsafe_allow_html=True)
sheet2 = pd.read_excel(reader,sheet_name="sheet2",usecols=[0,1,2,3,4])
st.dataframe(sheet2)
st.write(sheet3.finished[0])
st.write(sheet3.unfinished[0])
st.markdown('<p class="header_title">sheet3|{}</p>'.format(str(sheet3.shape[0])), unsafe_allow_html=True)
sheet3 = pd.read_excel(reader,sheet_name="sheet3",usecols=[0,1,2,3,4])
st.dataframe(sheet3)
st.write(sheet4.finished[0])
st.write(sheet4.unfinished[0])
st.markdown('<p class="header_title">sheet4|{} </p>'.format(str(sheet4.shape[0])), unsafe_allow_html=True)
sheet4 = pd.read_excel(reader,sheet_name="sheet4",usecols=[0,1,2,3,4])
st.dataframe(sheet4)
st.write(sheet5.finished[0])
st.write(sheet5.unfinished[0])
st.markdown('<p class="header_title">sheet5|{} </p>'.format(str(sheet5.shape[0])), unsafe_allow_html=True)
sheet5 = pd.read_excel(reader,sheet_name="sheet5",usecols=[0,1,2,3,4])
st.dataframe(sheet5)
if st.sidebar.button("Split & Send"):
#create xlwings object
app = xw.App(visible = False)
# create workbook
wb = xw.Book(data)
# iterate over the sheets of the created workbook
# if sheet name = total delete it (do not copy it)
for sheet in wb.sheets:
if "total" in sheet.name:
sheet.delete()
# create new work book copy the first sheet
else:
wb_new = app.books.add()
sheet.copy(after = wb_new.sheets[0])
wb_new.sheets[0].delete()
#delete the columns G:H
wb_new.sheets[0].range('G:H').delete()
# save the new excel files as name of each sheet
if "sheet5" in sheet.name:
wb_new.save(f"{sheet5_path}/{sheet.name}.xlsx")
else:
wb_new.save(f"{sheet.name}.xlsx")
# close the create workbook
wb_new.close()
else:
st.markdown('<p class="big-font">You Need to Upload a FILE !!</p>', unsafe_allow_html=True)

Define xw.App() after defining the existing data.
if st.sidebar.button("Split & Send"):
wb = xw.Book(data)
# Use context manager to prevent zombie processes.
with xw.App(visible=False) as app:
wb_new = app.books.add()
# ...
wb_new.close()
wb.close()

I solved this question by importing pythoncom package and call the pythoncom.CoInitialize() each time the button is pressed

Related

How to add buttons for macro by using xlwings or pywin32

I'd like to know how to add buttons for macro by using xlwings or pywin32.
I found the way for shape objects(execute test("shape")), but test("button") failed because of no-attribute error insheet1.api.Buttons.Add.
I simply implemented following because when I add a button manually in excel recording macro, I found ActiveSheet.Buttons.Add(288, 44.25, 151.5, 32.25).Select in VBA editor.
import xlwings as xw
def test_button(obj_type):
wb = xw.books.add()
wb.save("test.xlsm")
sheet1 = wb.sheets["Sheet1"]
if obj_type == "shape":
# Add Shape
sheet1.api.Shapes.AddShape(1, 100, 50, 150, 30)
shape_names = []
for shape in sheet1.shapes:
if shape.name not in shape_names:
shape_names.append(shape.name)
shape.characters.api.Text = "Shape Name = {}".format(shape.name)
shape.api.OnAction = "sample_sub"
print("shape names list:")
print(shape_names)
elif obj_type == "button":
button = sheet1.api.Buttons.Add(288, 44.25, 151.5, 32.25) # FIXME
button.api.OnAction = "sample_sub" # FIXME
button.api.Text = "sample button" # FIXME
else:
raise ValueError("Invalid obj_type : {}".format(obj_type))
return wb
sample_sub is difined by :
#xw.sub
def sample_sub():
wb = xw.Book.caller()
sheet1 = wb.sheets["Sheet1"]
sheet1.range("A1").value = "This is a test message."
According to MS documentation, there is no object named Buttons.
You can create a button using
FormControl, e.g. sheet.api.Shapes.AddFormControl(0,1,1,1,1), See xlformcontrol enumeration.

Openpyxl is unable to read after modifying

Requirement :
1.create a gui using Tkinter
2.Update the excel by fetching values from Tkinter entry widget
3.Read another sheet of the same workbook
4.plot graph using inside the Tkinter window.
Problem: All the functionality is working fine, except when modifying and reading one after another at same time.
Loaded the work book with data_only=False to preserve formulas. I have modified the excel successfully in "INPUT" sheet.Then when I am reading the cells from "SIMULATION" sheets which are linked to the "Input" sheets with formulas , no data is coming.
Opening the excel file with Excel and closing it ,and now if i run the python program again without the modify functionality, program is able to read cell value and plot graph.
During read functionality of the program workbook is loaded 2nd time using data_only = True to get cell values .
Any suggestions will be very helpful for me.
from tkinter import *
from tkinter import ttk
from openpyxl import load_workbook
import datetime
import matplotlib
matplotlib.use("TkAgg")
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
class Root(Tk):
f = Figure(figsize=(20,8))
a = f.add_subplot(111)
entryString1=0
entryString2=0
entryString3=0
entryString4=0
entryString5=0
entryString6=0
def __init__(self):
super(Root, self).__init__()
self.title("Python Tkinter Dialog Widget")
self.state("zoomed")
self.frame = ttk.Frame(self,borderwidth=2, relief="solid")
self.frame.pack(side='top',fill=X,padx=10,pady=20)
self.entry()
self.button()
self.canvas = FigureCanvasTkAgg(self.f, self.frame)
self.canvas.get_tk_widget().pack(fill="both",padx=10,pady=20)
self.canvas.draw()
def entry(self):
self.frame2 = ttk.Frame(self)
self.frame2.pack(fill=X,padx=10,pady=20)
self.labelentry1 = ttk.Label(self.frame2,text = "V Past Main begin period: ")
self.labelentry1.pack(fill=X,side='left',anchor=W)
self.entry1 = ttk.Entry(self.frame2)
self.entry1.pack(fill=X,side='left')
self.labelentry2 = ttk.Label(self.frame2,text = " V Past PS begin period: ")
self.labelentry2.pack(fill=X,side='left')
self.entry2 = ttk.Entry(self.frame2)
self.entry2.pack(fill=X,side='left')
self.labelentry3 = ttk.Label(self.frame2,text = " Adv 0% Main: ")
self.labelentry3.pack(fill=X,side='left')
self.entry3 = ttk.Entry(self.frame2)
self.entry3.pack(fill=X,side='left')
self.labelentry4 = ttk.Label(self.frame2,text = " Adv 0% PS: ")
self.labelentry4.pack(fill=X,side='left')
self.entry4 = ttk.Entry(self.frame2)
self.entry4.pack(fill=X,side='left')
self.labelentry5 = ttk.Label(self.frame2,text = " Single premium already paid: ")
self.labelentry5.pack(fill=X,side='left')
self.entry5 = ttk.Entry(self.frame2)
self.entry5.pack(fill=X,side='left')
self.labelentry6 = ttk.Label(self.frame2,text = " Yearly premium already paid: ")
self.labelentry6.pack(fill=X,side='left')
self.entry6 = ttk.Entry(self.frame2)
self.entry6.pack(fill=X,side='left')
def button(self):
self.frame3 = ttk.Frame(self)
self.frame3.pack(side='bottom')
self.labelFrame = ttk.LabelFrame(self.frame3,text = "Regenrate Graph")
self.labelFrame.pack(padx=10)
self.button = ttk.Button(self.labelFrame, text = "Click",command = self.fileDialog)
self.button.pack(padx=10)
def fileDialog(self):
self.entryString1 = self.entry1.get()
self.entryString2 = self.entry2.get()
self.entryString3 = self.entry3.get()
self.entryString4 = self.entry4.get()
self.entryString5 = self.entry5.get()
self.entryString6 = self.entry6.get()
#Load excel file
self.filename = "C:\\Users\\ramit\\Desktop\\Projection V0.3_Shankha .xlsx"
#load excel file to modify
self.work_book = load_workbook (self.filename)
self.sheet = self.work_book['Inputs']
if len(self.entryString1) != 0:
self.sheet.cell(row=4,column=5).value=int(self.entryString1)
if len(self.entryString2) != 0:
self.sheet.cell(row=5,column=5).value=int(self.entryString2)
if len(self.entryString3) != 0:
self.sheet.cell(row=6,column=5).value=int(self.entryString3)
if len(self.entryString4) != 0:
self.sheet.cell(row=7,column=5).value=int(self.entryString4)
if len(self.entryString5) != 0:
self.sheet.cell(row=9,column=5).value=int(self.entryString5)
if len(self.entryString6) != 0:
self.sheet.cell(row=10,column=5).value=int(self.entryString6)
self.work_book.save(self.filename)
self.work_book = None
#load excel file to read
self.work_book = load_workbook (self.filename,data_only=True)
self.sheet_1 = self.work_book['Simulation']
self.x = []
self.y = []
for i in range(10, 17):
self.x.append (self.sheet_1.cell(row=i + 1,column=1).value)
self.y.append (self.sheet_1.cell(row=i + 1,column= 77).value)
self.a.clear()
print(self.x)
print(self.y)
self.a.set_xlabel('Simulation date')
self.a.set_ylabel('Reserve')
self.a.plot(self.x, self.y, color='cyan', label='Projection')
self.canvas.draw()
root = Root()
root.mainloop()
The issue is that openpyxl doesn't evaluate the formula in excel. it will only return the last value saved by excel or 'None' (with data_only=True). The latter is what is happening when you change an input cell and use [cell].value to call the value of the cell with the formula. When you don't change the sheet, you get the value set in excel, which is why your code works when you disable/don't do the input to excel functionality.
Easiest way to around the issue is to use xlwings, this should work the way you intend it to. There are also a few other options. such as directly using windows excel commands to update the sheet then using openpyxl to read but I feel swapping modules is the simpler solution. You may also want to consider bringing the function to the python side of the process and just writing the result to the excel sheet.
how to get formula result in excel using xlwings
import xlwings as xw
sheet = xw.Book(r'C:/path/to/file.xlsx').sheets['sheetname']
result = sheet['X2'].value

Openpyxl NamedStyle isn't applying alignment to excel cells

I'm using openpyxl to write data into excel files. I defined a function create_style to create named styles by passing a name and font, alignment, border styles etc. Once a style is created it'll be added to workbook and can be used anytime. In my case, NamedStyle is setting the alignment attributes to None. However, fonts,fills are working fine. I couldn't figure the problem.
Here's the code.
import os
from openpyxl import Workbook,load_workbook
from openpyxl.styles import Alignment, Font, NamedStyle
# to create a style passing workbook, name and optional arguments like font etc
def create_style(wb,stylename,**kwargs):
if stylename not in wb.named_styles:
style = NamedStyle(name=stylename)
for key,value in kwargs.items():
if value is not None:
setattr(style,key,value)
wb.add_named_style(style)
excelpath = '/path/to/my/master-excel-file.xlsx'
workbook = load_workbook(excelpath) if os.path.isfile(excelpath) else Workbook()
sheetname = 'sheet1'
sheets = workbook.get_sheet_names()
if sheetname in sheets:
print('loading existing sheet')
worksheet = workbook.get_sheet_by_name(sheetname)
else:
print('creating sheet...')
worksheet = workbook.create_sheet(sheetname)
font_1 = Font(name='Arial', size=18)
alignment_1 = Alignment(horizontal='general',
vertical='center')
create_style(workbook,'style1',font=font_1,alignment=alignment_1)
workbook.save(excelpath)
# My doubt is here
print(workbook._named_styles['style1'].font)
print('\n')
print(workbook._named_styles['style1'].alignment)
Let's assume there's no excel file in the path, then the code will create new excel file and create 'style1' and saves it in the workbook.
output1 :
creating sheet...
<openpyxl.styles.fonts.Font object>
Parameters:
name='Arial', charset=None, family=None, b=False, i=False, strike=None,
outline=None, shadow=None, condense=None, color=None, extend=None, sz=18.0,
u=None, vertAlign=None, scheme=None
<openpyxl.styles.alignment.Alignment object>
Parameters:
horizontal='general', vertical='center', textRotation=0, wrapText=None,
shrinkToFit=None, indent=0.0, relativeIndent=0.0, justifyLastLine=None,
readingOrder=0.0
When I run the same code again, None is assigned to all alignment parameters.
output 2:
loading existing sheet
<openpyxl.styles.fonts.Font object>
Parameters:
name='Arial', charset=None, family=None, b=False, i=False, strike=None,
outline=None, shadow=None, condense=None, color=None, extend=None, sz=18.0,
u=None, vertAlign=None, scheme=None
<openpyxl.styles.alignment.Alignment object>
Parameters:
horizontal=None, vertical=None, textRotation=0, wrapText=None,
shrinkToFit=None, indent=0.0, relativeIndent=0.0, justifyLastLine=None,
readingOrder=0.0
which means the style1 losing data regarding alignment on every run while font is working just fine. In other words, style1 isn't retaining the alignment. Why is it happening ?

How do I use openpyxl and still maintain OOP structure?

I am using python to do some simulations and using openpyxl to generate the reports. Now the simulation is results are to be divided into several sheets of an excel file. By the principles of OOP my structure should have a base simulator class which implements basic operations and several derived classes which implement modifications to simulator. Since functions related to a class should remain with the class I want the report sheets to be generated by the derived classes (with all its styling and formatting etc). Then maybe a driver class or function which takes all these report sheets and puts them in one work book. But as far as I can tell there is no way to copy a worksheet in openpyxl. Now it seems like I have broken the OOP models. Is there a way out of this?
Edit
Here is an example for my code. This is a trimmed fat free version, the real class is not that simple
from openpyxl import Workbook
from openpyxl.styles import Font
class sim1:#this will inherit from another class sim which has basic operations
def __init__(self):
#assume function calling and complex math here
self.x = 1
def f1OverRide(self):
#over rides some function in sim to implement custom method for sim1 (several of these)
return 23
def get_sheet(self):
wb = Workbook()
ws = wb.active
ws['A1'] = self.x
#example formatting real formatting is pretty complex
ws['A1'].font = Font(size=12,name='Calibri')
return ws
class sim2:#this will inherit from another class sim which has basic operations
def __init__(self):
#assume function calling and complex math here
self.x = 12
def f1OverRide(self):
#over rides some function in sim to implement custom method for sim1 (several of these)
return 42
def get_sheet(self):
wb = Workbook()
ws = wb.active
ws['A1'] = self.x
#example formatting, real formatting is pretty complex
ws['A1'].font = Font(size=14,name='Calibri',color='ff2223')
return ws
s1 = sim1()
s2 = sim2()
# now I want to get the sheets for sim1 and sim2 and combine in 1 workbook
wb = Workbook()
ws1 = s1.get_sheet()
ws2 = s2.get_sheet()
# dont know what to do now :( openpyxl can not copy sheet into this workbook
OOP copy Worksheets between Workbooks, for instance:
from openpyxl import Workbook
from openpyxl.styles import Font
from copy import copy
class sim():
def __init__(self, n):
self.n = n
def get_sheet(self):
#...
wb = Workbook()
ws = wb.active
ws['A1'] = 'sim'+str(self.n)
if self.n == 1:
ws['A1'].font = Font(size=12,name='Calibri')
else:
ws['A1'].font = Font(size=14, name='Calibri', color='ff2223')
return ws
class sim_Workbook(Workbook):
# overload Workbook.copy_worksheet
def copy_worksheet(self, from_worksheet):
# Create new empty sheet and append it to self(Workbook)
ws = self.create_sheet( title=from_worksheet.title )
for row, row_data in enumerate(from_worksheet.rows,1):
for column, from_cell in enumerate(row_data,1):
cell = ws.cell(row=row, column=column)
cell.value = from_cell.value
cell.font = copy(from_cell.font)
s1 = sim(1)
s2 = sim(2)
wb = sim_Workbook()
wb.copy_worksheet( s1.get_sheet() )
wb.copy_worksheet( s2.get_sheet() )
wb.save('../test/test.xlsx')
#example formatting real formatting is pretty complex
You have to copy your complex formatting, style by style as shown by font in the example. This could lead to huge workload, depending how many cells you have to copy.
Read this to get a hint about this, but you can't do it 1:1 as you copy from workbook to workbook copying-styles-from-a-range-to-another-range
Tested with Python:3.4.2 - openpyxl:2.4.1 - LibreOffice: 4.3.3.2

Blank workbook is output with WriteOnlyCell and example from documentation

I am using openpyxl 2.2.6 from the bitbucket repository. I tried the simple example from the documentation
This however produces a blank workbook with a single sheet approproately named "MyTestSheet" . but values in any cell.
What am I missing to get write to a cell to work.
from openpyxl import Workbook
wb = Workbook(optimized_write = True)
ws = wb.create_sheet(title="MyTestSheet")
from openpyxl.writer.dump_worksheet import WriteOnlyCell
from openpyxl.comments import Comment
from openpyxl.styles import Style, Font
cell = WriteOnlyCell(ws, value="hello world")
cell.font = Font(name='Courrier', size=36)
cell.comment = Comment(text="A comment", author="Author's Name")
wb.save("testwr_so.xlsx")
You need to append the cell to the worksheet:
ws.append([cell]) # you must always append a sequence

Categories