Source code for qf_lib.documents_utils.excel.excel_exporter

#     Copyright 2016-present CERN – European Organization for Nuclear Research
#
#     Licensed under the Apache License, Version 2.0 (the "License");
#     you may not use this file except in compliance with the License.
#     You may obtain a copy of the License at
#
#         http://www.apache.org/licenses/LICENSE-2.0
#
#     Unless required by applicable law or agreed to in writing, software
#     distributed under the License is distributed on an "AS IS" BASIS,
#     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#     See the License for the specific language governing permissions and
#     limitations under the License.

from datetime import datetime
from os import makedirs, path, remove
from os.path import exists, isfile, join, dirname
from typing import Any, Union, Optional

import numpy
from openpyxl import Workbook, load_workbook
from openpyxl.styles import Font
from openpyxl.worksheet.worksheet import Worksheet
from pandas import Series, DataFrame

from qf_lib.common.tickers.tickers import Ticker
from qf_lib.common.utils.numberutils.is_finite_number import is_finite_number
from qf_lib.containers.dataframe.qf_dataframe import QFDataFrame
from qf_lib.containers.series.qf_series import QFSeries
from qf_lib.documents_utils.excel.helpers import row_and_column
from qf_lib.documents_utils.excel.write_mode import WriteMode
from qf_lib.settings import Settings
from qf_lib.starting_dir import get_starting_dir_abs_path


[docs]class ExcelExporter: def __init__(self, settings: Settings): self.settings = settings
[docs] def export_container( self, container: Union[QFSeries, QFDataFrame], file_path: str, write_mode: WriteMode = WriteMode.CREATE_IF_DOESNT_EXIST, starting_cell: str = 'A1', sheet_name: str = None, include_index: bool = True, include_column_names: bool = False, remove_old_file=False) -> Union[bytes, str]: """ Exports the container (QFSeries, QFDataFrame) to the excel file. Returns the absolute file path of the exported file. Parameters ---------- container container with data to be exported file_path path (relative to the output root directory) to the file to which data should be exported write_mode mode in which the file should be opened; default: WriteMode.CREATE_IF_DOESNT_EXIST starting_cell the address of the cell which should be the top left corner of the exporter container default: 'A1' sheet_name the name of the sheet to which the container should be exported. If a sheet of this name doesn't exist it will be created. If it does: it will be edited (but not cleared). If no sheet_name is specified, then the currently active one will be picked include_index determines whether the index should be written together with the data. include_column_names determines whether the column names should be written together with the data. For series containers the column names are always "Index" and "Values". remove_old_file if true it first deletes the old file before creating new """ starting_row, starting_column = row_and_column(starting_cell) file_path = join(get_starting_dir_abs_path(), self.settings.output_directory, file_path) # Make sure an old version of this file is removed. if remove_old_file and path.exists(file_path): remove(file_path) work_book = self.get_workbook(file_path, write_mode) work_sheet = self.get_worksheet(work_book, sheet_name) self.write_to_worksheet( container, work_sheet, starting_row, starting_column, include_index, include_column_names) work_book.save(file_path) return file_path
[docs] def apply_font_style_to_area(self, file_path: str, specified_area: str, write_mode: WriteMode = WriteMode.CREATE_IF_DOESNT_EXIST, sheet_name: str = None, **font_setting): """ Apply a font style to a certain area in excel Parameters ---------- file_path path to the xlsl file where the cell should be written in specified_area: str the specified area where the formatting should take place in the following format: 'A1:Z12' write_mode mode in which the file should be opened sheet_name: str the name of the sheet where the cell should be written. If a sheet of this name doesn't exist it will be created. If it does: it will be edited (but not cleared). If no sheet_name is specified, then the currently active one will be picked font_setting additional font settings for the cell fonts """ work_book = self.get_workbook(file_path, write_mode) work_sheet = self.get_worksheet(work_book, sheet_name) for cell in work_sheet[specified_area][0]: cell.font = Font(**font_setting) work_book.save(file_path)
[docs] def write_cell(self, file_path: str, cell_reference: str, value: Any, write_mode: WriteMode = WriteMode.CREATE_IF_DOESNT_EXIST, sheet_name: str = None): """ Writes a value into the specified ``cell_reference``. Parameters ---------- file_path path to the xlsl file where the cell should be written in cell_reference the address of the cell where the value should be placed, for example 'C10' value the value to write in the cell write_mode mode in which the file should be opened sheet_name the name of the sheet where the cell should be written. If a sheet of this name doesn't exist it will be created. If it does: it will be edited (but not cleared). If no sheet_name is specified, then the currently active one will be picked """ row, column = row_and_column(cell_reference) work_book = self.get_workbook(file_path, write_mode) work_sheet = self.get_worksheet(work_book, sheet_name) self.write_to_worksheet(value, work_sheet, row, column, include_index=False, include_column_names=False) work_book.save(file_path)
[docs] def get_workbook(self, file_path: str, write_mode: WriteMode) -> Workbook: """ Takes a path to the file (creates it if necessary), opens the file and retrieves a Workbook object from it. """ work_book = None if write_mode == WriteMode.CREATE_IF_DOESNT_EXIST: if exists(file_path) and isfile(file_path): write_mode = WriteMode.OPEN_EXISTING else: write_mode = WriteMode.CREATE dir_path = dirname(file_path) makedirs(dir_path, exist_ok=True) if write_mode == WriteMode.CREATE: assert not exists(file_path) or not isfile(file_path) work_book = Workbook() elif write_mode == WriteMode.OPEN_EXISTING: assert exists(file_path) and isfile(file_path) work_book = load_workbook(file_path) return work_book
[docs] def get_worksheet(self, work_book: Workbook, sheet_name: str = None) -> Worksheet: """ Gets a worksheet of given name from a provided workbook. If :sheet_name is None, then the active sheet from the workbook is returned. """ if sheet_name is None: work_sheet = work_book.active else: work_sheet = self._get_or_create_worksheet(work_book, sheet_name) return work_sheet
[docs] def write_to_worksheet(self, exported_value: Any, work_sheet: Worksheet, starting_row: int, starting_column: int, include_index: bool, include_column_names: bool): """ Exports a given value to Excel worksheet. If the :exported_value is a series or dataframe, then the :starting_row and :starting_column correspond to the top left corner of the container's values in the worksheet. If the :exported_value isn't a series nor dataframe, then the :include_index and :include_column_names parameters should be False. """ if isinstance(exported_value, Series): self._write_series_to_worksheet(exported_value, work_sheet, starting_row, starting_column, include_index, exported_value.name if include_column_names else None) elif isinstance(exported_value, DataFrame): self._write_dataframe_to_worksheet(exported_value, work_sheet, starting_row, starting_column, include_index, include_column_names) else: assert not include_index and not include_column_names work_sheet.cell(row=starting_row, column=starting_column, value=self._to_supported_type(exported_value))
def _get_or_create_worksheet(self, work_book, sheet_name): if sheet_name in work_book: work_sheet = work_book[sheet_name] else: work_sheet = work_book.create_sheet(sheet_name) return work_sheet def _write_series_to_worksheet(self, series, work_sheet, starting_row: int, starting_column: int, include_index: bool, column_name: Optional[str]): column = starting_column if include_index: self._export_index(series, starting_column, starting_row, work_sheet, column_name is not None) column += 1 row = starting_row if column_name is not None: work_sheet.cell(row=row, column=column, value=self._to_supported_type(column_name)) row += 1 for date, value in series.items(): work_sheet.cell(row=row, column=column, value=self._to_supported_type(value)) row += 1 def _write_dataframe_to_worksheet(self, dataframe, work_sheet, starting_row: int, starting_column: int, include_index: bool, include_column_names: bool): if include_index: self._export_index(dataframe, starting_column, starting_row, work_sheet, include_column_names) column = starting_column + 1 else: column = starting_column for series_name, series in dataframe.items(): self._write_series_to_worksheet(series, work_sheet, starting_row, column, include_index=False, column_name=series_name if include_column_names else None) column += 1 def _export_index(self, container, starting_column, starting_row, work_sheet, include_column_names): row = starting_row if include_column_names: work_sheet.cell(row=row, column=starting_column, value="Index") row += 1 for date in container.index: work_sheet.cell(row=row, column=starting_column, value=self._to_supported_type(date)) row += 1 def _to_supported_type(self, value): if isinstance(value, (numpy.int64, numpy.int32)): return int(value) elif isinstance(value, str) or is_finite_number(value) or isinstance(value, datetime): return value elif isinstance(value, Ticker): return value.as_string() else: return str(value)