"""Utility methods to assist with file operations.""" import csv from datetime import datetime from fnmatch import fnmatch import io import psutil import os import shutil import stat def get_s3_file_key(*args): """Get the s3 file key from env and filename. Args: path (str): The path to use. filename (str): The filename to use. """ # return '{}/{}'.format(path, filename) return os.path.join(*args).replace('\\', '/') # TODO: add args, etc. to docstring def create_path_with_todays_date(output_path, subdir=False, skip_date=False): """Create and return tmp path. Choose whether or not to use this function, not passing args that eliminate it's main function. (i.e. skip_date arg)""" date = datetime.today().strftime('%Y-%m-%d') date = date.replace('-', '_') if skip_date: temp_path = output_path elif subdir: temp_path = os.path.join(output_path, date) else: if '{date}' not in output_path: if output_path[-1:] == '/': output_path = output_path[:-1] output_path = output_path + '_{date}' temp_path = output_path.format(date=date) if not os.path.isdir(temp_path): os.makedirs(temp_path) return temp_path def copy_file(src, dst, buffer_size=10 * 1024 * 1024, preserve_file_date=True): """ Copy a file to a new location. Much faster performance than Apache. Commons due to use of larger buffer @param src: Source File @param dst: Destination File (not file path) @param buffer_size: Buffer size to use during copy @param preserve_file_date: Preserve the original file date """ # Check to make sure destination directory exists. # If it doesn't create the directory dst_parent, dst_filename = os.path.split(dst) os.makedirs(dst_parent, exist_ok=True) # Optimize the buffer for small files buffer_size = min(buffer_size, os.path.getsize(src)) if buffer_size == 0: buffer_size = 1024 for fn in [src, dst]: try: st = os.stat(fn) except OSError: # File most likely does not exist pass else: # XXX What about other special files? (sockets, devices...) if stat.S_ISFIFO(st.st_mode): raise shutil.SpecialFileError('`%s` is a named pipe' % fn) with open(src, 'rb') as fsrc: with open(dst, 'wb') as fdst: shutil.copyfileobj(fsrc, fdst, buffer_size) if preserve_file_date: shutil.copystat(src, dst) def increment_filename(file_name, use_datetime=True): """Produce a filename appended with '_##' or date.""" root, ext = os.path.splitext(file_name) path, file = os.path.split(file_name) os.makedirs(path, exist_ok=True) count = 0 return_file_name = None if use_datetime: date = datetime.today().strftime('%Y-%m-%d_%H_%M_%S') return root + '_' + date + ext while True: count += 1 try: return_file_name = root + '_' + str(count).zfill(4) + ext open(return_file_name) except IOError: return return_file_name def get_folders_at_path(path): """Return a list of folders ata given path.""" if not os.path.exists(path): return None folder_list = [f for f in os.listdir(path) if os.path.isdir(os.path.join(path, f))] return folder_list def get_files_at_path(path, ext=None): """Return a list of files at a given path. Args: path: (str) The path from which to get files ext: (str, list) An extension or list of extensions (without periods) to filter the results by. Returns: list, None: The list of files or None """ # Path doesn't exist if not os.path.exists(path): return None # Dummy return val file_list = None # No extension is passed if ext is None: file_list = [f for f in os.listdir(path) if os.path.isfile(os.path.join(path, f))] # Extension passed else: # Convert str to list of one element if type(ext) is str: ext = list(ext) # Check formatting of passed extensions for each in ext: if each[0] == '.': raise SyntaxError( 'Please remove periods (.) from the extension(s) ' 'which you pass.') file_list = [f for f in os.listdir(path) if os.path.isfile(os.path.join(path, f)) and os.path.splitext(f)[1][1:] in ext] if type(ext) is str: if ext[0] == '.': raise SyntaxError( 'Please remove periods (.) from the extensions ' 'in your extension list.') file_list = [f for f in os.listdir(path) if os.path.isfile(os.path.join(path, f)) and os.path.splitext(f)[1][1:] == ext] return file_list def get_local_path(file_name, path): """Create path to local file.""" return os.path.join( create_path_with_todays_date(path), os.path.basename(file_name)) # TODO: add args, etc. to docstring def write_to_file(file_name, item, path): """Write to file.""" # Create path to local file local_file_full_path = get_local_path(file_name, path) # Write file from pieces. with io.open(local_file_full_path, 'w', encoding='utf8') as outfile: outfile.writelines(str(item)) return local_file_full_path def write_csv(csv_dict, filename): with open(filename, 'a') as f: w = csv.DictWriter(f, csv_dict.keys()) w.writerow(csv_dict) def file_len(fname): """Return the length of a text file in rows. Args: fname: (str) The file to measure. Returns: int: The number of rows in the file. """ with open(fname, encoding='utf8') as f: i = 1 for i, l in enumerate(f): pass return i + 1 def count_file_rows(fname): """Return the length of a text file in rows. Convenience method. Args: fname: (str) The file to measure. Returns: int: The number of rows in the file. """ return file_len(fname) def check_free_space(path, min_space): """ Prevent processing if disk space is low Args: path: (str) A path on the disk to check. min_space: (int) Minimum acceptable amount of free space in gigabytes. Returns: (bool) True if disk free space is greater than or above the min_space; False, otherwise. """ free_space = psutil.disk_usage(path).free / 1024 / 1024 / 1024 return free_space >= min_space def is_on_mount(path): while True: if path == os.path.dirname(path): # we've hit the root dir return False elif os.path.ismount(path): return True path = os.path.dirname(path) def check_physical_device_id(file): return int(os.stat(file).st_dev & 0xff) def on_same_filesystem(src, dst): return os.stat(src).st_dev == os.stat(dst).st_dev def get_file_list(path, filter_ext=None): if os.path.isdir(path): file_list = [ os.path.join(path, f) for f in os.listdir(path)] if filter_ext: file_list = [ f for f in file_list if os.path.splitext(f)[1][1:] == filter_ext ] return file_list else: if filter_ext and os.path.splitext(path)[1][1:] == filter_ext: return [path] else: return None def get_all_files_in_subdirs(root, pattern='*', output_file=None): """Get all files in a root path and all subdirs. Args: root: (str) The root path to traverse down from. pattern: (str) A pattern by which to limit the list of returned files. output_file: (str) Optional. A file to which to output the resultant data Returns: list: The list of files found. """ file_list = [] for path, subdirs, files in os.walk(root): for name in files: if fnmatch(name, pattern): file_list.append(name) if output_file is not None: with open(output_file, 'w') as fo: fo.write('\n'.join(file_list)) return file_list def get_all_dirs_in_subdirs( root, root_only=False, pattern='*', output_file=None): """Get all the subdirs in a root path and all subdirs. Args: root: (str) The root path to traverse down from. root_only: (bool) Whether to limit the results to top level path only pattern: (str) A pattern by which to limit the list of returned files. output_file: (str) Optional. A file to which to output the resultant data Returns: list: The list of directories found. """ dir_list = [] for path, subdirs, files in os.walk(root): if root_only and path != root: break for name in subdirs: if fnmatch(name, pattern): dir_list.append(name) if output_file is not None: with open(output_file, 'w') as fo: fo.write('\n'.join(dir_list)) return dir_list