How do I use openpyxl and still maintain OOP structure? - python

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

Related

Python - Openpyxl - Adding Cell Value To Column P If Match Between Column A (list) and Tuple (list)

Background
I have a list of values in column A (which differ from worksheet to worksheet). I wanted to identify a subset of values to be matched and if a match was made between lists then the script will add the value from the tuple list to column P and moves downwards per value (see picture Script output).
Problem
The script kind of works, accept if the value in tuple is not found the Column A list, then it creates an empty cell in Excel and moves down to the next cell (see picture - Excel output).
Questions
What is the best way to avoid empty cells if the value from the tuple is not in the list?
Script
from openpyxl import Workbook
from openpyxl import load_workbook
from openpyxl.styles import Color, PatternFill, Font, Border
from openpyxl.styles import colors
from openpyxl.cell import Cell
import colorama
from colorama import init
init()
from colorama import Fore, Style, Back
import os
import shutil
import math
from decimal import Decimal
from openpyxl.styles import Alignment
from inspect import currentframe
import time
########################################
#########################################
src_path ="/Users/data/"
xlfileName = "test.xlsx"
xlfile = src_path + xlfileName
wb = Workbook()
wb = load_workbook(xlfile)
#########################################
#########################################
def get_linenumber():
cf = currentframe()
return cf.f_back.f_lineno
#########################################
#########################################
non_cash_tup = (
"Depreciation & amortisation", "Deferred income taxes", "Stock-based compensation", "Change in working capital",
"Accounts receivable", "Inventory", "Accounts payable", "Other non-cash items")
for xws in wb.sheetnames:
print(Style.RESET_ALL)
print(Fore.BLUE, "Looping Through Worksheet Name -- ", worksheet, "Script line is -- ", get_linenumber())
print(Style.RESET_ALL)
worksheet = wb[xws]
cell_list = []
for cell in worksheet['A']:
cell_list.append(cell.value)
for i, name in enumerate(non_cash_tup):
if name in cell_list:
print(Fore.LIGHTGREEN_EX, name, "is in the list")
column_cell = 16
worksheet.cell(column=column_cell, row=i+2, value=name)
else:
print(Fore.LIGHTRED_EX, name, "is not in the list")
wb.save(xlfile)
Use the set() function.
The set() function in Python uses to take an argument and convert it into a set object. It can take arguments like lists, tuples and dictionaries. The argument is called iterable. The output of elements might not be in the same order because items passed as list were not in order.
non_cash_tup = (
"Depreciation & amortisation", "Deferred income taxes", "Stock-based compensation", "Change in working capital",
"Accounts receivable", "Inventory", "Accounts payable", "Other non-cash items")
for xws in wb.sheetnames:
print(Style.RESET_ALL)
print(Fore.BLUE, "Looping Through Worksheet Name -- ", worksheet, "Script line is -- ", get_linenumber())
print(Style.RESET_ALL)
worksheet = wb[xws]
cell_list = []
for cell in worksheet['A']:
cell_list.append(cell.value)
matching = set(non_cash_tup) & set(cell_list)
#print(matching)
for i, name in enumerate(matching):
print(Fore.LIGHTGREEN_EX, name, "is in the list")
column_cell = 16
worksheet.cell(column=column_cell, row=i+2, value=name)
wb.save(xlfile)

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

how to add drop down list in excel cell using win32com python

I am trying to add drop down in excel cell using python win32com api. But not able to implement it.
Here is my code
from win32com.client import Dispatch
import os
import win32api
path = os.getcwd()
path1 = path + '\\myExcel.xlsx'
try:
xl = Dispatch("Excel.Application")
xl.Visible = 1 # fun to watch!
wb = xl.Workbooks.Open(Filename=path1)
ws = wb.Worksheets(1)
ws.Cells(1,1).Value = "GREEN"
ws.Cells(2,1).Value = "YELLOW"
ws.Cells(3,1).Value = "RED"
ws.Cells(4,1).Value = "WHITE"
ws.Cells(5,1).Value = "NOT SURE"
ws.Cells(6,1).Value = "["GREEN", "YELLOW", "RED", "WHITE", "NOT SURE"]" //I want drop down here
wb.Close(SaveChanges=True)
xl.Quit()
except Exception as e:
print(e)
What you are doing isn't working because this line
ws.Cells(6,1).Value = "["GREEN", "YELLOW", "RED", "WHITE", "NOT SURE"]" //I want drop down here
is setting the value of the cell, just like the previous lines did. (Or rather, attempting to set it: that line contains two syntax errors, one in the quoting and one in the comment.)
But you don't want to set the value of the cell, you want to apply validation to the cell. So you need to set attributes of the object ws.Cells(6,1).Validation.
Taking just the code inside your try...except clause, that would look like this:
xl = Dispatch("Excel.Application")
xl.Visible = 0 # Not really such fun to watch because the code below closes down Excel
# straightaway. xl.Visible = 1 will just show a screen flicker.
wb = xl.Workbooks.Open(Filename=path1)
ws = wb.Worksheets(1)
ws.Cells(1,1).Value = "GREEN"
ws.Cells(2,1).Value = "YELLOW"
ws.Cells(3,1).Value = "RED"
ws.Cells(4,1).Value = "WHITE"
ws.Cells(5,1).Value = "NOT SURE"
# Set up validation
val = ws.Cells(6,1).Validation
val.Add(Type=3, AlertStyle=1, Operator=1, Formula1="=Sheet1!A1:A5")
val.IgnoreBlank = -1
val.InCellDropdown = -1
val.InputTitle = ""
val.ErrorTitle = ""
val.InputMessage = ""
val.ErrorMessage = ""
val.ShowInput = -1
val.ShowError = -1
wb.Close(SaveChanges=True)
xl.Quit()
The lines that set up the validation follow exactly the example in the reference given in my comment. The objects that win32com gets from Excel are not Python objects: they are thin Python wrappers around VBA objects, and these VBA objects follow their own conventions, not Python's. So the Python code follows the VBA exactly, except for syntax. The only differences are cosmetic.
.Add gets parentheses because functions in Python have to have them (VBA methods don't).
Named parameters to methods get a Python = not a VBA :=.
Constants like xlBetween represent integer values; you can find the values on MSDN.
VBA defines True as -1. 1 or True will probably also work: I didn't try.
Python doesn't have an equivalent of VBA's with statement so val has to be explicit in assignments like val.ErrorMessage = "" instead of implicit as in VBA.
This is the result I got.

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 ?

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