from ui import Ui_MainWindow from errorDialog import ErrorDialog import ILParser from PyQt5 import QtWidgets from logging import debug, info, warning, exception as logException, error,DEBUG, INFO, WARNING, ERROR, basicConfig, getLogger from sys import argv from typing import Literal, Optional from pandas import DataFrame, ExcelWriter from datetime import datetime as dt from os import startfile from json import load, dump from time import sleep # Open the config file, create a dict, and set up logging with open("config.json") as configFile: config: dict[Literal["loggingLevel"], Literal["ERROR", "WARNING", "INFO", "DEBUG"]] = load(configFile) basicConfig(filename='ILFormatter.log', encoding='utf-8', level=config["loggingLevel"], filemode='w', force=True) info(f"Starting with log level: {getLogger().level}") # Change the current log level and save the change to config.json def change_log_level(newLevel: Literal["ERROR", "WARNING", "INFO", "DEBUG"]): config["loggingLevel"] = newLevel with open("config.json", 'w') as configFile: dump(config, configFile) getLogger().setLevel(newLevel) print(f"{now()} | New logging level: {getLogger().level}\n") # Creates an error dialog pop up # Based on the ui from errorDialog.py def open_error_dialog(errorLabel: str, errorDescription: str, errorText: str): dialog = QtWidgets.QDialog() dialog.ui = ErrorDialog() dialog.ui.setupUi(dialog) dialog.ui.setFields(errorLabel, errorDescription, errorText) dialog.exec_() # Used to easily record uniform timestamps now = lambda : dt.now().strftime("%H:%M-%S.%f") fileRoot = lambda filePath : '/'.join(filePath.split('/')[:-1]) # This class is responable for managing the UI of the application # and connection it to the functionality of ILParser.py class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): def __init__(self, *args, obj=None, **kwargs): debug("MainWindow class init..") super(MainWindow, self).__init__(*args, **kwargs) self.setupUi(self) self.processButton.setEnabled(False) self.openButton.setEnabled(False) # Fix log level action buttons level: QtWidgets.QAction for level in [self.actionError, self.warnAction, self.infoAction, self.debugAction]: inUse = level.text().upper() == config["loggingLevel"] level.setEnabled(not inUse) level.setChecked(inUse) if inUse: self.logLevel = level debug(f"Logging Level: {self.logLevel}") # File Locations self.assetFile = None self.custFile = None self.dobFile = None self.finFile = None self.outputLocation = None # Button Hooks self.assetButton.clicked.connect(lambda: self._set_file(lineEdit= self.assetLE, selfFile="ASSET")) self.custButton.clicked.connect(lambda: self._set_file(lineEdit= self.custLe, selfFile="CUST")) self.dobButton.clicked.connect(lambda: self._set_file(lineEdit= self.dobLE, selfFile="DOB")) self.finButton.clicked.connect(lambda: self._set_file(lineEdit= self.finLE, selfFile="FIN")) self.outputButton.clicked.connect(lambda: self._set_output()) self.processButton.clicked.connect(lambda: self._process()) self.openButton.clicked.connect(lambda: self._open_with_default_app(self.outputLocation)) # Action Hooks self.actionError.triggered.connect(lambda: self._switch_log_levels(self.actionError)) self.warnAction.triggered.connect(lambda: self._switch_log_levels(self.warnAction)) self.infoAction.triggered.connect(lambda: self._switch_log_levels(self.infoAction)) self.debugAction.triggered.connect(lambda: self._switch_log_levels(self.debugAction)) def _check_files(self): debug(self.assetFile) debug(self.custFile) debug(self.dobFile) debug(self.finFile) self.openButton.setEnabled(False) ready = ( self.assetFile != None and self.custFile != None and self.dobFile != None and self.finFile != None and self.outputLocation != None ) self.processButton.setEnabled(ready) def _set_file(self, lineEdit: QtWidgets.QLineEdit, selfFile: Literal["ASSET", "CUST", "DOB", "FIN"]) -> Optional[str] : defaultLocation = self._default_location(selfFile) selectedFile: list[str] = QtWidgets.QFileDialog.getOpenFileName(self, "OpenFile", directory=defaultLocation) debug(f"Selected file: {selectedFile}") lineEdit.setText(selectedFile[0]) file = selectedFile[0] if selectedFile[0] != '' else None if file!= None: self._default_location(selfFile, set=fileRoot(file)) if file != None and self.outputLocation == None: # Output will may be from memory, or based on file root self._auto_output_set(fileRootStr=fileRoot(file)) if selfFile == "ASSET": self.assetFile = file elif selfFile == "CUST": self.custFile = file elif selfFile == "DOB": self.dobFile = file elif selfFile == "FIN": self.finFile = file self._check_files() def _set_output(self): self.outputLocation = QtWidgets.QFileDialog.getSaveFileName(self, "Output file name") if QtWidgets.QFileDialog.getSaveFileName(self, "Output file name") != '' else None self.outputLE.setText(self.outputLocation if self.outputLocation != None else '') self._default_location("output", set=fileRoot(self.outputLocation), overwrite=True) debug(f"Output Location: {self.outputLocation}") def _default_location(self, file: Literal["ASSET", "CUST", "DOB", "FIN", "output"], set: str = None, overwrite: bool = False) -> Optional[str]: # Gets the default location for the a file # Set if set provided and default location is not set # Will always overwrite the default location if overwrite is True defaultLocation = config["directories"][file] debug(f"Default location: {defaultLocation}") if set != None: debug(f"Setting default location to: {set} | ({(overwrite | (defaultLocation == None))})") config["directories"][file] = set if (overwrite | (defaultLocation == None)) else defaultLocation with open("config.json", 'w') as configFile: dump(config, configFile) return set else: return defaultLocation def _auto_output_set(self, fileRootStr): # Check the config for a saved output location self.outputLocation = fileRootStr + f"/Portfolio Contracts - {dt.now().strftime('%Y-%m-%d')}.xlsx" self._default_location("output", set=fileRoot(self.outputLocation), overwrite=False) self.outputLE.setText(self.outputLocation if self.outputLocation != None else '') debug(f"Auto set output: {self.outputLocation}") def _open_with_default_app(self, item): """ Opens the linked item with it's default application (excel) """ debug(f"_open_with_default_app: {item}") startfile(item) def _switch_log_levels(self, newLevel: QtWidgets.QAction): oldLevel: QtWidgets.QAction = self.logLevel print(f"{now()} | Log Level Changed: {oldLevel.text()} -> {newLevel.text()}") newLevel.setChecked(True) oldLevel.setChecked(False) newLevel.setEnabled(False) oldLevel.setEnabled(True) self.logLevel = newLevel change_log_level(newLevel.text().upper()) def _parse_file(self, filePath: str, parseColumns: list[ILParser.Column]) -> Optional[DataFrame]: with open(filePath) as file: report = file.read() debug(f"Report: {report}") debug(f"Parse Columns:\n{parseColumns}") try: data: DataFrame = ILParser.parse(report, parseColumns) except Exception as e: logException(f"Failed to parse file-> {filePath} :\n{e}") open_error_dialog("Parsing Error:",f"Failed to parse file-> {filePath}",repr(e)) return None debug(f"Data: {data}") if data.empty: error(f"Dataframe empty -> {filePath} | Returning none") open_error_dialog("Data Processing Error:",f"Dataframe empty!",filePath) return None else: return data def _process(self): assetDf: Optional[DataFrame] = self._parse_file(filePath= self.assetFile, parseColumns= ILParser.ASSET_COLS) debug(f"AssetDF: {assetDf} | {type(assetDf)} ") if type(assetDf) != DataFrame: debug(f"Parse Columns: {ILParser.ASSET_COLS}") self.assetLE.setText("") self.assetFile = None return None custDf: DataFrame = self._parse_file(self.custFile, ILParser.CUST_COLS) debug(custDf) if type(custDf) != DataFrame: debug(f"Parse Columns: {ILParser.CUST_COLS}") self.custLe.setText("") self.custFile = None return None dobDf: DataFrame = self._parse_file(self.dobFile, ILParser.DOB_COL) debug(dobDf) if type(dobDf) != DataFrame: debug(f"Parse Columns: {ILParser.DOB_COL}") self.dobLE.setText("") self.dobFile = None return None finDf: DataFrame = self._parse_file(self.finFile, ILParser.FIN_COLUMNS) debug(finDf) if type(finDf) != DataFrame: debug(f"Parse Columns: {ILParser.FIN_COLUMNS}") self.finLE.setText("") self.finFile = None return None try: with ExcelWriter(self.outputLocation) as writer: assetDf.to_excel(writer, sheet_name="ASSET") custDf.to_excel(writer, sheet_name="CUST") dobDf.to_excel(writer, sheet_name="DOB") finDf.to_excel(writer, sheet_name="FIN") except Exception as e: logException(f"{now()} | Failed to write to excel -> {self.outputLocation} :\n{e}") open_error_dialog("Failed to Create Excel", f"Failed to write to excel -> {self.outputLocation}", repr(e)) return None debug("Finished writing to excel.") self.openButton.setEnabled(True) # Defines the app app = QtWidgets.QApplication(argv) # Sets the style app.setStyle("Fusion") # Builds the main window window = MainWindow() window.setWindowTitle("IL Extract") window.show() # Starts the app app.exec()