Source code for PythonExtensionsCollection.File.CFile

# **************************************************************************************************************
#
#  Copyright 2020-2022 Robert Bosch GmbH
#
#  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.
#
# **************************************************************************************************************
#
# CFile.py
#
# XC-CT/ECA3-Queckenstedt
#
# 26.01.2022
#
# **************************************************************************************************************

# -- import standard Python modules
import os, shutil, platform

# -- import Bosch Python modules
from PythonExtensionsCollection.String.CString import CString

# **************************************************************************************************************

[docs]class enFileStatiType: closed = "closed" openedforwriting = "openedforwriting" openedforappending = "openedforappending" openedforreading = "openedforreading"
# --------------------------------------------------------------------------------------------------------------
[docs]class CFile(object): """ | The class ``CFile`` provides a small set of file functions with extended parametrization (like switches defining if a file is allowed to be overwritten or not). Most of the functions at least returns ``bSuccess`` and ``sResult``. * ``bSuccess`` is ``True`` in case of no error occurred. * ``bSuccess`` is ``False`` in case of an error occurred. * ``bSuccess`` is ``None`` in case of a very fatal error occurred (exceptions). * ``sResult`` contains details about what happens during computation. Every instance of CFile handles one single file only and forces exclusive access to this file. It is not possible to create an instance of this class with a file that is already in use by another instance. It is also not possible to use ``CopyTo`` or ``MoveTo`` to overwrite files that are already in use by another instance. This makes the file handling more save against access violations. | """ # -------------------------------------------------------------------------------------------------------------- # TM*** def __init__(self, sFile=None): self.__sFile = CString.NormalizePath(sFile) self.__oFileHandle = None self.__oFileStatus = enFileStatiType.closed self.__sLastDestination = None try: CFile.__listFilesInUse except: CFile.__listFilesInUse = [] # exclusive access is required (checked by self.__bIsFreeToUse; relevant for destination in CopyTo and MoveTo) if self.__sFile in CFile.__listFilesInUse: raise Exception(f"The file '{self.__sFile}' is already in use by another CFile instance.") else: CFile.__listFilesInUse.append(self.__sFile) # eof def __init__(self, sFile=None): def __del__(self): self.Close() if self.__sFile in CFile.__listFilesInUse: CFile.__listFilesInUse.remove(self.__sFile) # eof def __del__(self): # -------------------------------------------------------------------------------------------------------------- # TM*** def __bIsFreeToUse(self, sFile=None): bIsFreeToUse = False # init if sFile is None: bIsFreeToUse = False # error handling else: if sFile in CFile.__listFilesInUse: bIsFreeToUse = False else: bIsFreeToUse = True return bIsFreeToUse # eof def __bIsFreeToUse(self, sFile=None): # -------------------------------------------------------------------------------------------------------------- # TM*** def __OpenForWriting(self): """ | Opens a text file for writing. Returns ``bSuccess`` and ``sResult`` (feedback). | """ sMethod = "CFile::__OpenForWriting" if self.__sFile is None: bSuccess = False sResult = "self.__sFile is None; please provide path and name of a file when creating a CFile object." sResult = CString.FormatResult(sMethod, bSuccess, sResult) return bSuccess, sResult bSuccess, sResult = self.Close() if bSuccess is not True: sResult = CString.FormatResult(sMethod, bSuccess, sResult) return bSuccess, sResult try: self.__oFileHandle = open(self.__sFile, "w", encoding="utf-8") self.__oFileStatus = enFileStatiType.openedforwriting bSuccess = True sResult = f"File '{self.__sFile}' is open for writing" except Exception as reason: self.Close() bSuccess = None sResult = f"Not possible to open file '{self.__sFile}' for writing.\nReason: " + str(reason) sResult = CString.FormatResult(sMethod, bSuccess, sResult) return bSuccess, sResult # eof def __OpenForWriting(self): # -------------------------------------------------------------------------------------------------------------- # TM*** def __OpenForAppending(self): """ | Opens a text file for appending. Returns ``bSuccess`` and ``sResult`` (feedback). | """ sMethod = "CFile::__OpenForAppending" if self.__sFile is None: bSuccess = False sResult = "self.__sFile is None; please provide path and name of a file when creating a CFile object." sResult = CString.FormatResult(sMethod, bSuccess, sResult) return bSuccess, sResult bSuccess, sResult = self.Close() if bSuccess is not True: sResult = CString.FormatResult(sMethod, bSuccess, sResult) return bSuccess, sResult try: self.__oFileHandle = open(self.__sFile, "a", encoding="utf-8") self.__oFileStatus = enFileStatiType.openedforappending bSuccess = True sResult = f"File '{self.__sFile}' is open for appending" except Exception as reason: self.Close() bSuccess = None sResult = f"Not possible to open file '{self.__sFile}' for appending.\nReason: " + str(reason) sResult = CString.FormatResult(sMethod, bSuccess, sResult) return bSuccess, sResult # eof def __OpenForAppending(self): # -------------------------------------------------------------------------------------------------------------- # TM*** def __OpenForReading(self): """ | Opens a text file for reading. Returns ``bSuccess`` and ``sResult`` (feedback). | """ sMethod = "CFile::__OpenForReading" if self.__sFile is None: bSuccess = False sResult = "self.__sFile is None; please provide path and name of a file when creating a CFile object." sResult = CString.FormatResult(sMethod, bSuccess, sResult) return bSuccess, sResult bSuccess, sResult = self.Close() if bSuccess is not True: sResult = CString.FormatResult(sMethod, bSuccess, sResult) return bSuccess, sResult try: self.__oFileHandle = open(self.__sFile, "r", encoding="utf-8") self.__oFileStatus = enFileStatiType.openedforreading bSuccess = True sResult = f"File '{self.__sFile}' is open for reading" except Exception as reason: self.Close() bSuccess = None sResult = f"Not possible to open file '{self.__sFile}' for reading.\nReason: " + str(reason) sResult = CString.FormatResult(sMethod, bSuccess, sResult) return bSuccess, sResult # eof def __OpenForReading(self): # -------------------------------------------------------------------------------------------------------------- # TM***
[docs] def Close(self): """ | Closes the opened file. Returns ``bSuccess`` and ``sResult`` (feedback). | """ sMethod = "CFile::Close" if self.__oFileHandle is not None: try: self.__oFileHandle.flush() self.__oFileHandle.close() bSuccess = True sResult = f"File '{self.__sFile}' closed" except Exception as reason: bSuccess = None sResult = f"Exception while closing file '{self.__sFile}'.\nReason: " + str(reason) self.__oFileHandle = None else: bSuccess = True sResult = "Done" self.__oFileStatus = enFileStatiType.closed sResult = CString.FormatResult(sMethod, bSuccess, sResult) return bSuccess, sResult
# eof def Close(self): # -------------------------------------------------------------------------------------------------------------- # TM***
[docs] def Delete(self, bConfirmDelete=True): """ | Deletes the current file. **bConfirmDelete** Defines if it will be handled as error if the file does not exist. If ``True``: If the file does not exist, the method indicates an error (``bSuccess = False``). If ``False``: It doesn't matter if the file exists or not. Returns ``bSuccess`` and ``sResult`` (feedback). | """ sMethod = "CFile::Delete" if self.__sFile is None: bSuccess = False sResult = "self.__sFile is None; please provide path and name of a file when creating a CFile object." sResult = CString.FormatResult(sMethod, bSuccess, sResult) return bSuccess, sResult if os.path.isfile(self.__sFile) is False: if bConfirmDelete is True: bSuccess = False else: bSuccess = True sResult = f"Nothing to delete. The file '{self.__sFile}' does not exist." sResult = CString.FormatResult(sMethod, bSuccess, sResult) return bSuccess, sResult bSuccess, sResult = self.Close() if bSuccess is not True: sResult = CString.FormatResult(sMethod, bSuccess, sResult) return bSuccess, sResult try: os.remove(self.__sFile) bSuccess = True sResult = f"File '{self.__sFile}' deleted." except Exception as reason: bSuccess = None sResult = f"Exception while deleting file '{self.__sFile}'.\nReason: " + str(reason) sResult = CString.FormatResult(sMethod, bSuccess, sResult) return bSuccess, sResult
# eof def Delete(self, bConfirmDelete=True): # -------------------------------------------------------------------------------------------------------------- # TM*** def __PrepareOutput(self, Content=""): """ | Helper for ``Write`` and ``Append`` (consideration of composite data types). Returns a list of strings (that will be written to file). | """ listOut = [] if type(Content) == list: for element in Content: listOut.append(str(element)) elif type(Content) == tuple: for element in Content: listOut.append(str(element)) elif type(Content) == set: for element in Content: listOut.append(str(element)) elif type(Content) == dict: listKeys = Content.keys() nRJust = 0 for key in listKeys: sKey = str(key) # because also numerical values can be keys if len(sKey) > nRJust: nRJust = len(sKey) for key in listKeys: sKey = str(key) # because also numerical values can be keys sOut = sKey.rjust(nRJust, ' ') + " : " + str(Content[key]) listOut.append(sOut) elif str(type(Content)).lower().find('dotdict') >=0: try: listKeys = Content.keys() nRJust = 0 for key in listKeys: sKey = str(key) # because also numerical values can be keys if len(sKey) > nRJust: nRJust = len(sKey) for key in listKeys: sKey = str(key) # because also numerical values can be keys sOut = sKey.rjust(nRJust, ' ') + " : " + str(Content[key]) listOut.append(sOut) except Exception as reason: listOut.append(str(Content)) else: listOut.append(str(Content)) return listOut # eof def __PrepareOutput(self, Content=""): # -------------------------------------------------------------------------------------------------------------- # TM***
[docs] def Write(self, Content="", nVSpaceAfter=0, sPrefix=None, bToScreen=False): """ | Writes the content of a variable ``Content`` to file. If ``Content`` is not a string, the ``Write`` method resolves the data structure (therefore ``Content`` can also be of type ``list``, ``tuple``, ``set``, ``dict``, ``dotdict``). Adds vertical space ``nVSpaceAfter`` (= number of blank lines) after ``Content``. Prints ``Content`` also to screen in case of ``bToScreen`` is ``True`` (default: ``False``). Returns ``bSuccess`` and ``sResult`` (feedback). | """ sMethod = "CFile::Write" if self.__oFileStatus != enFileStatiType.openedforwriting: bSuccess, sResult = self.__OpenForWriting() if bSuccess is not True: sResult = CString.FormatResult(sMethod, bSuccess, sResult) return bSuccess, sResult listOut = self.__PrepareOutput(Content) for nCnt in range(nVSpaceAfter): listOut.append("") if bToScreen is True: for sOut in listOut: if ( (sPrefix is not None) and (sOut != '') ): sOut = f"{sPrefix}{sOut}" print(sOut) bSuccess = True sResult = "Done" try: for sOut in listOut: if ( (sPrefix is not None) and (sOut != '') ): sOut = f"{sPrefix}{sOut}" self.__oFileHandle.write(sOut + "\n") except Exception as reason: bSuccess = None sResult = f"Not possible to write to file '{self.__sFile}'.\nReason: " + str(reason) sResult = CString.FormatResult(sMethod, bSuccess, sResult) return bSuccess, sResult
# eof def Write(self, Content="", nVSpaceAfter=0, sPrefix=None, bToScreen=False): # -------------------------------------------------------------------------------------------------------------- # TM***
[docs] def Append(self, Content="", nVSpaceAfter=0, sPrefix=None, bToScreen=False): """ | Appends the content of a variable ``Content`` to file. If ``Content`` is not a string, the ``Write`` method resolves the data structure (therefore ``Content`` can also be of type ``list``, ``tuple``, ``set``, ``dict``, ``dotdict``). Adds vertical space ``nVSpaceAfter`` (= number of blank lines) after ``Content``. Prints ``Content`` also to screen in case of ``bToScreen`` is ``True`` (default: ``False``). Returns ``bSuccess`` and ``sResult`` (feedback). | """ sMethod = "CFile::Append" if self.__oFileStatus != enFileStatiType.openedforappending: bSuccess, sResult = self.__OpenForAppending() if bSuccess is not True: sResult = CString.FormatResult(sMethod, bSuccess, sResult) return bSuccess, sResult listOut = self.__PrepareOutput(Content) for nCnt in range(nVSpaceAfter): listOut.append("") if bToScreen is True: for sOut in listOut: if ( (sPrefix is not None) and (sOut != '') ): sOut = f"{sPrefix}{sOut}" print(sOut) bSuccess = True sResult = "Done" try: for sOut in listOut: if ( (sPrefix is not None) and (sOut != '') ): sOut = f"{sPrefix}{sOut}" self.__oFileHandle.write(sOut + "\n") except Exception as reason: bSuccess = None sResult = f"Not possible to append to file '{self.__sFile}'.\nReason: " + str(reason) sResult = CString.FormatResult(sMethod, bSuccess, sResult) return bSuccess, sResult
# eof def Append(self, Content="", nVSpaceAfter=0, sPrefix=None, bToScreen=False): # -------------------------------------------------------------------------------------------------------------- # TM***
[docs] def ReadLines(self, bCaseSensitive = True, bSkipBlankLines = False, sComment = None, sStartsWith = None, sEndsWith = None, sStartsNotWith = None, sEndsNotWith = None, sContains = None, sContainsNot = None, sInclRegEx = None, sExclRegEx = None, bLStrip = False, bRStrip = True, bToScreen = False): """ | Reads content from current file. Returns an array of lines together with ``bSuccess`` and ``sResult`` (feedback). The method takes care of opening and closing the file. The complete file content is read by ``ReadLines`` in one step, but with the help of further parameters it is possible to reduce the content by including and excluding lines. T.B.C. | """ sMethod = "[CFile::ReadLines]" listLines = [] if os.path.isfile(self.__sFile) is False: bSuccess = False sResult = f"The file '{self.__sFile}' does not exist." sResult = CString.FormatResult(sMethod, bSuccess, sResult) return listLines, bSuccess, sResult # !!! independend from: self.__oFileStatus != enFileStatiType.openedforreading: !!! # Reason: Repeated call of ReadLines needs to have the read pointer at the beginning of the file. bSuccess, sResult = self.__OpenForReading() if bSuccess is not True: sResult = CString.FormatResult(sMethod, bSuccess, sResult) return listLines, bSuccess, sResult try: sFileContent = self.__oFileHandle.read() except Exception as reason: bSuccess = None sResult = f"Not possible to read from file '{self.__sFile}'.\nReason: " + str(reason) return listLines, bSuccess, sResult bSuccess, sResult = self.Close() if bSuccess is not True: sResult = CString.FormatResult(sMethod, bSuccess, sResult) return listLines, bSuccess, sResult listFileContent = sFileContent.splitlines() # in opposite to readlines this is OS independend! for sLine in listFileContent: if CString.StringFilter(sString = sLine, bCaseSensitive = bCaseSensitive, bSkipBlankStrings = bSkipBlankLines, sComment = sComment, sStartsWith = sStartsWith, sEndsWith = sEndsWith, sStartsNotWith = sStartsNotWith, sEndsNotWith = sEndsNotWith, sContains = sContains, sContainsNot = sContainsNot, sInclRegEx = sInclRegEx, sExclRegEx = sExclRegEx, bDebug = False) is True: if bLStrip is True: sLine = sLine.lstrip(" \t\r\n") if bRStrip is True: sLine = sLine.rstrip(" \t\r\n") if bToScreen is True: print(sLine) listLines.append(sLine) # eof for sLine in listFileContent: del listFileContent nNrOfLines = len(listLines) bSuccess = True sResult = f"Read {nNrOfLines} lines from '{self.__sFile}'." sResult = CString.FormatResult(sMethod, bSuccess, sResult) return listLines, bSuccess, sResult
# eof def ReadLines(...) # -------------------------------------------------------------------------------------------------------------- # TM***
[docs] def GetFileInfo(self): """ | Returns the following informations about the file (encapsulated within a dictionary): Key **sFile** Path and name of current file Key **bFileIsExisting** ``True`` if file is existing, otherwise not Key **sFileName** The name of the current file (incl. extension) Key **sFileExtension** The extension of the current file Key **sFileNameOnly** The pure name of the current file (without extension) Key **sFilePath** The the path to current file Key **bFilePathIsExisting** ``True`` if file path is existing, otherwise not | """ sMethod = "CFile::GetFileInfo" dFileInfo = {} dFileInfo['sFile'] = None dFileInfo['bFileIsExisting'] = None dFileInfo['sFileName'] = None dFileInfo['sFileExtension'] = None dFileInfo['sFileNameOnly'] = None dFileInfo['sFilePath'] = None dFileInfo['bFilePathIsExisting'] = None if self.__sFile is None: return None dFileInfo['sFile'] = self.__sFile dFileInfo['bFileIsExisting'] = os.path.isfile(self.__sFile) sFileName = os.path.basename(self.__sFile) dFileInfo['sFileName'] = sFileName sFileExtension = "" sFileNameOnly = "" listParts = sFileName.split('.') if len(listParts) > 1: sFileExtension = listParts[len(listParts)-1] sFileNameOnly = sFileName[:-len(sFileExtension)-1] else: sFileExtension = "" sFileNameOnly = sFileName dFileInfo['sFileExtension'] = sFileExtension dFileInfo['sFileNameOnly'] = sFileNameOnly dFileInfo['sFilePath'] = os.path.dirname(self.__sFile) dFileInfo['bFilePathIsExisting'] = os.path.isdir(dFileInfo['sFilePath']) return dFileInfo
# eof def GetFileInfo(self): # -------------------------------------------------------------------------------------------------------------- # TM***
[docs] def CopyTo(self, sDestination=None, bOverwrite=False): """ | Copies the current file to ``sDestination``, that can either be a path without file name or a path together with a file name. In case of the destination file already exists and ``bOverwrite`` is ``True``, than the destination file will be overwritten. In case of the destination file already exists and ``bOverwrite`` is ``False`` (default), than the destination file will not be overwritten and ``CopyTo`` returns ``bSuccess = False``. Returns ``bSuccess`` and ``sResult`` (feedback). | """ sMethod = "CFile::CopyTo" if self.__sFile is None: bSuccess = False sResult = "self.__sFile is None; please provide path and name of a file when creating a CFile object." sResult = CString.FormatResult(sMethod, bSuccess, sResult) return bSuccess, sResult if os.path.isfile(self.__sFile) is False: bSuccess = False sResult = f"The file '{self.__sFile}' does not exist, therefore nothing can be copied." sResult = CString.FormatResult(sMethod, bSuccess, sResult) return bSuccess, sResult if sDestination is None: bSuccess = False sResult = "sDestination is None; please provide path and name of destination file. Or at least the destination path. In this case the file name will be taken over." sResult = CString.FormatResult(sMethod, bSuccess, sResult) return bSuccess, sResult sDestination = CString.NormalizePath(sDestination) bDeleteDestFile = False sDestFile = sDestination # default if os.path.isdir(sDestination) is True: sFileName = os.path.basename(self.__sFile) sDestFile = f"{sDestination}/{sFileName}" # file name in destination is required for: shutil.copyfile if self.__bIsFreeToUse(sDestFile) is False: bSuccess = False sResult = f"The destination file '{sDestFile}' is already in use by another CFile instance." sResult = CString.FormatResult(sMethod, bSuccess, sResult) return bSuccess, sResult self.__sLastDestination = sDestFile if os.path.isfile(sDestFile) is True: # destination file already exists if sDestFile == self.__sFile: bSuccess = False sResult = f"Source file and destination file are the same: '{self.__sFile}'. Therefore nothing to do." sResult = CString.FormatResult(sMethod, bSuccess, sResult) return bSuccess, sResult if bOverwrite is True: bDeleteDestFile = True else: bSuccess = False sResult = f"Not allowed to overwrite existing destination file '{sDestFile}'. Therefore nothing to do." sResult = CString.FormatResult(sMethod, bSuccess, sResult) return bSuccess, sResult else: # destination file not yet exists # (we assume here that the destination shall be a file because we already have figured out that the destination is not a folder) # => we have to check if the path to the file exists sDestFilePath = os.path.dirname(sDestFile) if os.path.isdir(sDestFilePath) is True: bDeleteDestFile = False else: bSuccess = False sResult = f"The destination path '{sDestFilePath}' does not exist. The file '{self.__sFile}' cannot be copied." sResult = CString.FormatResult(sMethod, bSuccess, sResult) return bSuccess, sResult # eof else - if os.path.isfile(sDestFile) is True: # analysis done, now the action bSuccess, sResult = self.Close() if bSuccess is not True: sResult = CString.FormatResult(sMethod, bSuccess, sResult) return bSuccess, sResult if bDeleteDestFile is True: # To delete the destination file explicitely before executing any copy-function is an addon here in this library. # The purpose is to be independend from the way the used copy function is handling existing destination files. # But this makes only sense under Windows and not under Linux, because Windows is much more strict with access # violations than Linux. Therefore we avoid such kind of additional steps in case of the platform is not Windows. if platform.system() == "Windows": try: os.remove(sDestFile) bSuccess = True sResult = f"File '{sDestFile}' deleted." except Exception as reason: bSuccess = None sResult = f"Exception while deleting destination file '{sDestFile}'.\nReason: " + str(reason) sResult = CString.FormatResult(sMethod, bSuccess, sResult) return bSuccess, sResult try: shutil.copyfile(self.__sFile, sDestFile) bSuccess = True sResult = f"File '{self.__sFile}' copied to '{sDestFile}'." except Exception as reason: bSuccess = None sResult = f"Exception while copying file '{self.__sFile}' to '{sDestFile}'.\nReason: " + str(reason) sResult = CString.FormatResult(sMethod, bSuccess, sResult) return bSuccess, sResult
# eof def CopyTo(self, sDestination=None, bOverwrite=False): # -------------------------------------------------------------------------------------------------------------- # TM***
[docs] def MoveTo(self, sDestination=None, bOverwrite=False): """ | Moves the current file to ``sDestination``, that can either be a path without file name or a path together with a file name. In case of the destination file already exists and ``bOverwrite`` is ``True``, than the destination file will be overwritten. In case of the destination file already exists and ``bOverwrite`` is ``False`` (default), than the destination file will not be overwritten and ``CopyTo`` returns ``bSuccess = False``. Returns ``bSuccess`` and ``sResult`` (feedback). | """ sMethod = "CFile::MoveTo" bSuccess, sResult = self.CopyTo(sDestination, bOverwrite) if bSuccess is not True: sResult = CString.FormatResult(sMethod, bSuccess, sResult) return bSuccess, sResult if os.path.isfile(self.__sLastDestination) is False: # the copied file should exist at new location bSuccess = None sResult = f"Someting went wrong while copying the file '{self.__sFile}' to '{self.__sLastDestination}'. Aborting." sResult = CString.FormatResult(sMethod, bSuccess, sResult) return bSuccess, sResult else: bSuccess, sResult = self.Delete() if bSuccess is not True: sResult = CString.FormatResult(sMethod, bSuccess, sResult) return bSuccess, sResult bSuccess = True sResult = f"File moved from '{self.__sFile}' to '{self.__sLastDestination}'" sResult = CString.FormatResult(sMethod, bSuccess, sResult) return bSuccess, sResult
# eof def MoveTo(self, sDestination=None, bOverwrite=False): # -------------------------------------------------------------------------------------------------------------- # TM*** # eof class CFile(object): # **************************************************************************************************************