Source code for ship.utils.filetools

"""

 Summary:
    Contains file access functions and classes.

    This module contains convenience functions and classes for reading and 
    writing files and launching different types of File dialogue.
    
    Some of the functionality needed in this class is provided by the PyQt
    framework. When this module is loaded it tries to import the PyQt module.
    If it fails it sets a HAS_QT flag to False.
    
    Before any attempts to use the PyQt functions are made the HAS_QT flag
    is checked. If it's false the function will react.

 Author:  
     Duncan Runnacles
     
  Created:  
     01 Apr 2016
 
 Copyright:  
     Duncan Runnacles 2016

 TODO: This module, like a lot of other probably, needs reviewing for how
         'Pythonic' t is. There are a lot of places where generators,
         comprehensions, maps, etc should be used to speed things up and make
         them a bit clearer.
         
         More importantly there are a lot of places using '==' compare that
         should be using 'in' etc. This could cause bugs and must be fixed
         soon.

 Updates:

"""

from __future__ import unicode_literals

import os

import logging
logger = logging.getLogger(__name__)
"""logging references with a __name__ set to this module."""

from ship.utils import utilfunctions as uf


HAS_QT = True
"""If Qt is not installed on the machine running the library we can't use any 
of the GUI related code, such as file dialogs. This flag informs the code in
the module about the load status.
"""

try:
    from ship.utils.qtclasses import MyFileDialogs
    
except Exception:
    logger.warning('Unable to load Qt modules - cannot launch file dialogues')
    HAS_QT = False
    
    
[docs]def getFile(file_path): """Text file reader. Reads a text file, appending each new line to a list. Args: filePath (str): File path for text file to load. Returns: List - contents of text file split by new lines. Raises: IOError: if problem in reading file. TypeError: if string not given for file_path """ file_contents = [] line = "" try: with open(file_path, 'rU') as f: for line in f: file_contents.append(uf.encodeStr(line)) except IOError: logger.error('Read file IOError') raise IOError ('Unable to read file at: ' + file_path) except TypeError: logger.error('Read file TypeError') raise TypeError return file_contents
[docs]def writeFile(contents, file_path, add_newline=True): """Text file writer Writes a list to file, adding a new-line add the end of each list item. Args: contents (List) - lines to be written. filename (str) - Name of file to create. add_newline=True (Bool): adds a '\n' to the end of each line written if set to True. Raises: IOError: if problem in reading file. TypeError: if string not given for file_path """ try: with open(file_path, 'w') as f: for line in contents: if add_newline: f.write(line + '\n') else: f.write(line) except IOError: logger.error('Write file IOError') raise IOError except TypeError: logger.error('Write file TypeError') raise TypeError
[docs]def directoryFileNames(path): """Get a list of filenames in a directory. Args: path (str) - Path to directory. Returns: List - filenames in directory. Raises: IOError: If there's a problem with the read/write or the directory doesn't exist TypeError: if string not given for file_path. """ files = [] try: for (dirpath, dirnames, filenames) in os.walk(path): files.extend(filenames) break except IOError: logger.error('Get directory names IOError') raise IOError except TypeError: logger.error('Get directory names TypeError') raise TypeError return files
[docs]def directoryDialog(path=''): """Launch a file dialog and return the user selected directory. If PyQt libraries are not available (HAS_QT == False) this function will raise an ImportError. Args: path (str): Optional - directory to open the dialog in. Returns: Str containing path is successful or False if not. Raises: ImportError: if PyQt libraries are not available. """ if not HAS_QT: logger.error('Qt libraries are not installed - cannot launch file dialog') raise ImportError ('Qt libraries could not be imported - cannot launch dialogs') fd = MyFileDialogs() dir_path = fd.dirFileDialog(path) if not dir_path == '' and not dir_path == False: logger.debug('Chosen save path = ' + dir_path) return dir_path else: logger.debug('No save path chosen in dialog') return False
[docs]def getOpenFileDialog(path='', types='All (*.*)', multi_file=True): """Launch an open file dialog and return the user selected path. The file types should follow the format: 'DAT (*.DAT);;TXT (*.txt);;IEF (*.IEF)' If PyQt libraries are not available (HAS_QT == False) this function will raise an ImportError. Args: path (str): Optional - directory to open the dialog in. types (str): Optional - file types to restrict to. multi_file=True(bool): If False user can only select a single file. Returns: Str containing path is successful or False if not. Raises: ImportError: if PyQt libraries are not available. """ if not HAS_QT: logger.error('Qt libraries are not installed - cannot launch file dialog') raise ImportError ('Qt libraries could not be imported - cannot launch dialogs') fd = MyFileDialogs() open_path = fd.openFileDialog(path, file_types=types, multi_file=multi_file) if not open_path == '' and not open_path == False: logger.debug('Chosen save path = ' + open_path) return open_path else: logger.debug('No save path chosen in dialog') return False
[docs]def getSaveFileDialog(path='', types='All (*.*)'): """Launch a save file dialog and return the user selected path. The file types should follow the format: "DAT (*.DAT);;TXT (*.txt);;IEF (*.IEF)" If PyQt libraries are not available (HAS_QT == False) this function will raise an ImportError. Args: path (str): Optional - directory to open the dialog in. types (str): Optional - file types to restrict to. Returns: Str containing path is successful or False if not. Raises: ImportError: if PyQt libraries are not available. """ if not HAS_QT: logger.error('Qt libraries are not installed - cannot launch file dialog') raise ImportError ('Qt libraries could not be imported - cannot launch dialogs') fd = MyFileDialogs() save_path = fd.saveFileDialog(path, file_types=types) if not save_path == '' and not save_path == False: logger.debug('Chosen save path = ' + save_path) return save_path else: logger.debug('No save path chosen in dialog') return False
""" ############################### Path Functions and classes ############################### """
[docs]def pathExists(path): """Test whether a path exists. Args: path (str): the path to test. Returns: True if the path exists or False if it doesn't. """ if os.path.exists(path): return True return False
[docs]def finalFolder(path): """Get the last folder in the path. Args: path (str): the path to extract the final folder from. Returns: str containing the name of the final folder in the path. """ return os.path.basename(os.path.normpath(path))
[docs]def setFinalFolder(path, folder_name): """Changes the final folder in the directory path to the given name. Args: path (str): the path to update. folder_name (str): the new name for the final folder in the path. Returns: str containing the updated path. """ # Normalise the path so that we don't get any funny behaviour norm_path= os.path.normpath(path) # Get the drive letter drive_letter = os.path.splitdrive(norm_path)[0] # Separate out the different folders. # If it's an absolute path then we need to deal with the usual drive # letter problems (doesn't join it back with a slash. plist = norm_path.split(os.sep) if drive_letter: all_but_final_folder = plist[1:-1] all_but_final_folder = os.path.join(*all_but_final_folder) all_but_final_folder = plist[0] + os.path.sep + all_but_final_folder all_but_final_folder = os.path.normpath(all_but_final_folder) # Otherwise just remove the final folder so that we can replace it # with the new one. else: all_but_final_folder = plist[:-1] all_but_final_folder = os.path.join(*all_but_final_folder) # Add the new folder name to the end return os.path.join(all_but_final_folder, folder_name)
[docs]def getFileName(in_path, with_extension=False): """Return the file name with the file extension appended. Args: in_path (str): the file path to extract the filename from. with_extension=False (Bool): flag denoting whether to return the filename with or without the extension. Returns: Str - file name, with or without the extension appended or a blank string if there is no file name in the path. """ filename = os.path.basename(in_path) if os.path.sep in in_path[-2:] or (with_extension and not '.' in filename): return '' if with_extension: return filename else: filename = os.path.splitext(filename)[0] return filename
[docs]def directory(in_path): """Get the directory component of the given path. Checks the given path to see if it has a file or not. If there is no file component in the path it will return the path as-is. If there is no directory component in the file it will return a blank string. Args: in_path (str): the path to extract the directory from. Returns: Str - directory component of the given file path. TODO: Is there any way to tell if it's only a directory without being able to check that it exists and it doesn't end in a slash? """ # If the path ends in one of the os designated separators, such as '\', # '\\', or '/' then it's already the directory. sep = os.path.sep if sep in in_path[-2:]: return in_path dirname = os.path.dirname(in_path) return dirname
[docs]class PathHolder(object): """Class for storing file paths. This holds a range of useful path variables, such as: # file name # directory # full path # extension #etc Contains functions useful for extracting and updating parts of a file path. """ # FilePathTypes constants ABSOLUTE, RELATIVE, DIRECTORY, NAME, EXTENSION, NAME_AND_EXTENSION = range(6) def __init__(self, path, root=None): """Constructor. Args: path (str): the path to the file that this object holds the meta data for. root (str): Optional - the path to the directory that is the root of this file. Most tuflow files are referenced relative to the file that they are called from. The root will allow for an absolute path to be created from the relative path. """ # if not isinstance(path, basestring): # if not type(path) in (str, unicode): # raise TypeError self.root = root self.relative_root = None self.filename = None self.extension = None self.path_as_read = None # self.parent_relative_root = '' self._setupVars(path) def _setupVars(self, path): """Sets up everything. Extracts everything that we need to get from the inputs given to the constructor. Including the directory, filename and extension. Args: path (str): the path to create this object for. """ self.path_as_read = path = path.strip() if os.path.isabs(path): root = directory(path) if self.root == None: self.root = root self.setFilename(getFileName(path, with_extension=True), True, True) else: self.relative_root = directory(path) self.setFilename(getFileName(path, True), True, True) # def getFinalFolder(self):
[docs] def finalFolder(self): """Get the last folder in the path. If the relative_root variable is set it will be taken from that. Otherwise if it is not and the root variable is set it will be taken from that. If nether are set it will return False. Returns: tuple - the first part of the directory path and the final folder in the directory path for this file, or False if the variable has not been set. """ final_folder = False if not self.relative_root == None and not self.relative_root == False: final_folder = finalFolder(self.relative_root) elif not self.root == None: final_folder = finalFolder(self.root) return final_folder
[docs] def setFinalFolder(self, folder_name): """Changes the final folder in the directory path to the given name Args: folder_name (str): the new name for the final folder in the path. """ if not self.relative_root == None and not self.relative_root == False: self.relative_root = setFinalFolder(self.relative_root, folder_name) elif not self.root == None: self.root = setFinalFolder(self.root, folder_name)
# def getAbsolutePath(self, filename=None, relative_roots=[], normalize=True):
[docs] def absolutePath(self, filename=None, relative_roots=[], normalize=True): """Get the absolute path of the file path stored in this object. If there is no root variables set it will return False because there is no way of knowing what the absolute path will be. Args: filename: Optional - if a different file name to the one currently stored in this object is required it can be specified with this variable. Returns: str - absolute path of this object. """ if filename is None: filename = self.filenameAndExtension() outpath = False if relative_roots and not self.root is None: # if not self.root == None and not self.relative_root == None: paths = [self.root] + relative_roots + [filename] if normalize: outpath = os.path.normpath(os.path.join(*paths)) else: outpath = os.path.join(*paths) # return os.path.join(self.root, self.parent_relative_root, # self.relative_root, filename) # if not filename == None: # return os.path.join(self.root, self.parent_relative_root, # self.relative_root, filename) # # else: # return os.path.join(self.root, self.parent_relative_root, # self.relative_root, self.filenameAndExtension()) # elif not self.root == None and self.relative_root == None: elif not self.root is None: if normalize: outpath = os.path.normpath(os.path.join(self.root, filename)) else: outpath = os.path.join(self.root, filename) # if not filename == None: # return os.path.join(self.root, filename) # # else: # return os.path.join(self.root, self.filenameAndExtension()) return outpath
# else: # return outpath # def getDirectory(self):
[docs] def directory(self): """Get the directory of the file path stored in this object. This makes sure that the correct path, taking into consideration any relative path links to the main root, is returned. If there is no root variable set it will return False because there is no way of knowing what the absolute path should be. Returns: str - absolute path of this object without the file name, or False if no root variables have been set. """ if not self.root == None and not self.relative_root == None: return os.path.join(self.root, self.relative_root) elif not self.root == None and self.relative_root == None: return self.root else: return False
# def getRelativePath(self, with_extension=True, filename=None):
[docs] def relativePath(self, with_extension=True, filename=None): """Returns the full relative root with filename for this object. If a relative root was given to the constructor or set later this will return it, otherwise it will return False. Args: with_extension=True (bool): append the extension to the end of the path if True. filename=None (str): if a different file name to the one currently stored in this object is required it can be specified with this variable. Returns: str - relative path of this object or False if there is no relative path set. """ if not self.relative_root == None: if filename is None: if not with_extension: return os.path.join(self.relative_root, self.filename) else: return os.path.join(self.relative_root, self.filenameAndExtension()) else: return os.path.join(self.relative_root, filename) return False
# def getFileNameAndExtension(self):
[docs] def filenameAndExtension(self): """Return the file name with the file extension appended. Returns: str - file name with the extension appended or a blank string if the file name does not exist. """ if self.filename and self.extension: return self.filename + '.' + self.extension elif self.filename: return self.filename else: return ''
# def setPathsWithAbsolutePath(self, absolute_path, keep_relative_root=False):
[docs] def setAbsolutePath(self, absolute_path, keep_relative_root=False): """Sets the absolute path of this object. Takes an absolute path and set the file variables in this object with it. Note: This function WILL NOT check that the path exists. If the path used to call this function must exist it is the responsibility of the caller to check this. keep_relative_root variable information: If a relative root is not set to None it will be included in any path that is returned by this object. i.e. when an absolute path is returned by this object it will return: path + relativepath + filename + extension Args: absolute_path (str): the new absolute path to use to set the file variables in this object. keep_relative_root=False (Bool): if a relative root exists for this object it will be set to None unless this variable is set to True. """ absolute_path = absolute_path.strip() self.root, filename = os.path.split(absolute_path) self.filename, ext = os.path.splitext(filename) self.extension = ext[1:] if not keep_relative_root: self.relative_root = None
# def setFileName(self, filename, has_extension=False, keep_extension=False):
[docs] def setFilename(self, filename, has_extension=False, keep_extension=False): """Updates the filename variables with the given file name. The caller can provide a filename with or without an extension and choose whether to keep the extension on the file or not. has_extension will only be checked if keep_extension == False. Args: filename (str): the new filename to set. has_extension=False (Bool): whether the filename has a file extension or not. This should be set to True if an extension exists. Otherwise the filename will be corrupted. keep_extension: if the caller want to keep the extension on the given filename this should be True. Otherwise the file extension on the new filename will be discarded. """ filename = filename.strip() if keep_extension: self.filename, ext = os.path.splitext(filename) self.extension = ext[1:].lower() else: if has_extension: self.filename = os.path.splitext(filename)[0] else: self.filename = filename
# def getPathExists(self, ext=None):
[docs] def pathExists(self, ext=None): """Test whether the path exists.. Note: If not self.root variable is set for this object then it is impossible to know if the path exists or not, so it will always return False. Returns: True if the path exists or False if it cannot be found or there has not been a root variable set for this object. """ if not self.root == None: path = self.root # If we have a relative root we need to use that as well. if the # relative root shouldn't be used we will have had a new root set # and the relative root set to None in the setAbsolutePath() # function. if not self.relative_root == None: path = os.path.join(path, self.relative_root) if ext == None: path = os.path.join(path, self.filenameAndExtension()) else: path = os.path.join(path, self.filename + '.' + ext) if os.path.exists(path): return True return False