"""
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