__author__ = 'hofmann' __version__ = '0.1.7' import os import glob import math import string from numbers import Number from scripts.loggingwrapper import DefaultLogging class Validator(DefaultLogging): _boolean_states = { 'yes': True, 'true': True, 'on': True, 'no': False, 'false': False, 'off': False, 'y': True, 't': True, 'n': False, 'f': False} def __init__(self, logfile=None, verbose=False, debug=False, label="Validator"): super(Validator, self).__init__(label=label, logfile=logfile, verbose=verbose, debug=debug) def is_boolean_state(self, word): """ Test for boolean state @param word: A word @type word: str | unicode @return: True if word is identified as an word equivalent to true or false @rtype: bool """ return str(word) in self._boolean_states def get_boolean_state(self, word): """ Get boolean from word @param word: A word @type word: str | unicode @return: True if word is identified as an word equivalent to true @rtype: bool """ assert str(word) in self._boolean_states return self._boolean_states[str(word)] def validate_file(self, file_path, executable=False, key=None, silent=False): """ Collection of methods for value validations @attention: config_file argument may be file path or stream. @param file_path: path to a file @type file_path: basestring @param silent: If True, no error message will be made @type silent: bool @return: True if valid @rtype: bool """ assert isinstance(executable, bool) assert isinstance(silent, bool) assert key is None or isinstance(key, basestring) assert file_path is None or isinstance(file_path, basestring) prefix = "" if key: prefix = "'{}' ".format(key) if file_path is None: if not silent: self._logger.error("{}Invalid file path!".format(prefix)) return False parent_directory, filename = os.path.split(file_path) if parent_directory and not self.validate_dir(parent_directory, key=key, silent=silent): if not silent: self._logger.error("{}Directory of file does not exist: '{}'".format(prefix, parent_directory)) return False if executable and not parent_directory and not os.path.isfile(file_path): for path in os.environ["PATH"].split(os.pathsep): path = path.strip('"') exe_file = os.path.join(path, filename) if os.path.isfile(exe_file): file_path = exe_file break else: file_path = self.get_full_path(file_path) if not os.path.isfile(file_path): if not silent: self._logger.error("{}File does not exist: '{}'".format(prefix, file_path)) return False if executable and not os.access(file_path, os.X_OK): if not silent: self._logger.error("{}Permission error. File can not be executed '{}'".format(prefix, file_path)) return False return True def validate_characters(self, text, legal_alphabet=string.printable, key=None, silent=False): """ Validate that only legal characters are contained in a text @attention: @param text: Some string @type text: str | unicode @param legal_alphabet: String of legal characters @type legal_alphabet: str | unicode @param key: If True, no error message will be made @type key: basestring | None @param silent: If True, no error message will be made @type silent: bool @return: bool @rtype: bool """ prefix = "" if key: prefix = "'{}' ".format(key) set_legal_alphabet = set(legal_alphabet) set_text = set(text) if not set_legal_alphabet.issuperset(set_text): if not silent: difference = set_text.difference(set_legal_alphabet) difference.discard(set_legal_alphabet) self._logger.error("{}Invalid characters: '{}'".format(prefix, ", ".join(difference))) return False return True def validate_dir(self, directory, only_parent=False, sub_directories=None, file_names=None, key=None, silent=False): """ Validate existence of directory or parent directory or sub directories and files. @attention: @param directory: directory path of a folder @type directory: basestring @param only_parent: test only the existence of the parent directory @type only_parent: bool @param sub_directories: test the existence of sub directories @type sub_directories: list[basestring] @param file_names: test the existence of files within the directory @type file_names: list[basestring] @param silent: If True, no error message will be made @type silent: bool @return: bool @rtype: bool """ # TODO: test for valid characters assert isinstance(silent, bool) assert key is None or isinstance(key, basestring) assert isinstance(only_parent, bool) assert not (only_parent and sub_directories is not None) assert not (only_parent and file_names is not None) if sub_directories is None: sub_directories = [] if file_names is None: file_names = [] assert directory is None or isinstance(directory, basestring) assert isinstance(sub_directories, list) assert isinstance(file_names, list) prefix = "" if key: prefix = "'{}' ".format(key) if directory is None: if not silent: self._logger.error("{}Invalid directory".format(prefix)) return False if directory == '': if not silent: self._logger.error("{}Invalid directory: '{}'".format(prefix, directory)) return False directory = self.get_full_path(directory) parent_directory = os.path.dirname(directory) if not os.path.isdir(parent_directory): if not silent: self._logger.error("{}Directory does not exist: '{}'".format(prefix, parent_directory)) return False if not only_parent and not os.path.isdir(directory): if not silent: self._logger.error("{}Directory does not exist: '{}'".format(prefix, directory)) return False for sub_directory in sub_directories: if not os.path.isabs(sub_directory): sub_directory = os.path.join(directory, sub_directory) if not self.validate_dir(sub_directory, key=key, silent=silent): return False for file_name in file_names: if not os.path.isabs(file_name): file_name = os.path.join(directory, file_name) if not self.validate_file(file_name, key=key, silent=silent): return False return True @staticmethod def get_full_path(value): """ Get the normalized absolute path. @attention: @param value: directory path or file path @type value: basestring @return: full path @rtype: str """ assert isinstance(value, basestring) parent_directory, filename = os.path.split(value) if not parent_directory and not os.path.isfile(value): for path in os.environ["PATH"].split(os.pathsep): path = path.strip('"') exe_file = os.path.join(path, filename) if os.path.isfile(exe_file): value = exe_file break value = os.path.expanduser(value) value = os.path.normpath(value) value = os.path.abspath(value) return value @staticmethod def get_files_in_directory(directory, extension=None): """ Get all files within a directory @param directory: A directory @type directory: basestring @param extension: file extension to be filtered for @type extension: str | unicode | None @return: list of files that reflect the filter @rtype: list[str|unicode] """ assert extension is None or isinstance(extension, basestring) assert isinstance(directory, basestring) directory = Validator.get_full_path(directory) assert os.path.isdir(directory) if extension.startswith('.'): extension = extension[1:] list_of_file = [] if extension is None: list_of_items = glob.glob(os.path.join(directory, "*")) else: list_of_items = glob.glob(os.path.join(directory, "*.{}".format(extension))) for item in list_of_items: if os.path.isfile(item): list_of_file.append(item) return list_of_file def validate_number(self, digit, minimum=None, maximum=None, zero=True, key=None, silent=False): """ Validate that a variable is a number within a specific range if given. @attention: valid minimum <= digit <= maximum @param digit: Any number such as int, float, long @type digit: Number @param minimum: valid minimum <= digit @type minimum: Number @param maximum: valid digit <= maximum @type maximum: Number @param zero: If 0 is to be excluded @type zero: bool @param silent: If True, no error message will be made @type silent: bool @return: bool @rtype: bool """ # TODO: digit >= -1 can not be properly tested yet assert isinstance(digit, Number), type(digit) prefix = "" if key: prefix = "'{}' ".format(key) if minimum and digit < minimum: if not silent: self._logger.error("{}Invalid digit, must be bigger than {}, but was {}".format(prefix, minimum, digit)) return False if maximum and digit > maximum: if not silent: self._logger.error("{}Invalid digit, must be smaller than {}, but was {}".format(prefix, maximum, digit)) return False if not zero and digit == 0: if not silent: self._logger.error("{}Invalid digit, must not be {}".format(prefix, digit)) return False return True def validate_free_space( self, directory, required_space_in_bytes=None, required_space_in_kb=None, required_space_in_mb=None, required_space_in_gb=None, key=None, silent=False ): """ Validate that sufficient free space is available at a target directory. @attention: Only one 'required space' argument will be accepted @param directory: directory path of a folder @type directory: basestring @param required_space_in_bytes: Required available space in bytes @type required_space_in_bytes: Number @param required_space_in_kb: Required available space in kilobytes @type required_space_in_kb: Number @param required_space_in_mb: Required available space in megabytes @type required_space_in_mb: Number @param required_space_in_gb: Required available space in gigabytes @type required_space_in_gb: Number @param silent: If True, no error message will be made @type silent: bool @return: bool @rtype: bool """ required_space = None count = 4 for argument in [required_space_in_bytes, required_space_in_kb, required_space_in_mb, required_space_in_gb]: if argument is None: count -= 1 else: required_space = argument assert count == 1 # required_space = required_space_in_bytes or required_space_in_kb or required_space_in_mb or required_space_in_gb # print required_space, required_space_in_bytes, required_space_in_kb, required_space_in_mb, required_space_in_gb assert self.validate_number(required_space, minimum=0) assert self.validate_dir(directory, key=key, silent=silent) prefix = "" if key: prefix = "'{}' ".format(key) size_label = "" free_space = 0 if required_space_in_bytes is not None: size_label = "bytes" free_space = self.free_space_in_bytes(directory) if required_space_in_kb is not None: size_label = "kb" free_space = self.free_space_in_kilo_bytes(directory) if required_space_in_mb is not None: size_label = "mb" free_space = self.free_space_in_mega_bytes(directory) if required_space_in_gb is not None: size_label = "gb" free_space = self.free_space_in_giga_bytes(directory) if not required_space < free_space: if not silent: self._logger.error("{}Insufficient space! {:.2f}{label} of {:.2f}{label} available at '{dir}'".format( prefix, free_space, required_space, label=size_label, dir=directory)) return False return True def free_space_in_giga_bytes(self, directory): """ Get available free space at a target directory. @param directory: directory path of a folder @type directory: basestring @return: Available free space @rtype: float """ assert self.validate_dir(directory) return self._free_space(directory, 3) def free_space_in_mega_bytes(self, directory): """ Get available free space at a target directory. @param directory: directory path of a folder @type directory: basestring @return: Available free space @rtype: float """ assert self.validate_dir(directory) return self._free_space(directory, 2) def free_space_in_kilo_bytes(self, directory): """ Get available free space at a target directory. @param directory: directory path of a folder @type directory: basestring @return: Available free space @rtype: float """ assert self.validate_dir(directory) return self._free_space(directory, 1) def free_space_in_bytes(self, directory): """ Get available free space at a target directory. @param directory: directory path of a folder @type directory: basestring @return: Available free space @rtype: float """ assert self.validate_dir(directory) return self._free_space(directory) def _free_space(self, directory, power=0): """ Get available free space at a target directory. @param directory: directory path of a folder @type directory: basestring @return: Available free space @rtype: float """ assert power >= 0 assert isinstance(directory, basestring) assert self.validate_dir(directory) if not directory or not os.path.isdir(directory): return 0 statvfs = os.statvfs(directory) free_space = statvfs.f_frsize * statvfs.f_bfree return free_space / math.pow(1024, power) def get_available_file_path(self, proposed_path): """ Get available file path. @param proposed_path: Directory or file path @type proposed_path: str | unicode @return: Available free space @rtype: str """ assert self.validate_dir(proposed_path, only_parent=True), "Bad path '{}'".format(proposed_path) if self.validate_dir(proposed_path, silent=True): extension = '' path = proposed_path else: path, extension = os.path.splitext(proposed_path) index = 1 new_path = proposed_path while os.path.exists(new_path): new_path = "{base}_{index}{ext}".format(base=path, index=index, ext=extension) index += 1 return new_path