Source code for schrodinger.application.jaguar.validation
"""
Jaguar keywords input validation and custom Exceptions
Copyright Schrodinger, LLC. All rights reserved.
"""
import schrodinger.application.jaguar.jaguar_keyword_utils as kwutils
from schrodinger.application.jaguar.exceptions import JaguarUserFacingException
from schrodinger.application.jaguar.keywordDB import Setting
from schrodinger.application.jaguar.keywordDB import load_keywords
STRING = 'string'
INTEGER = 'integer'
REAL = 'real'
#-----------------------------------------------------------------------------
[docs]class JaguarKeywordException(JaguarUserFacingException):
    """
    Base exception class for all custom Jaguar keyword validation errors
    """ 
[docs]class JaguarKeywordError(JaguarKeywordException):
    """
    Exception class raised when nonexistant Jaguar keyword is requested
    """
[docs]    def __init__(self, keyword, allowed_keywords):
        """
        :type  keyword: string
        :param keyword: input keyword
        :type  allowed_keywords: list
        :param allowed_keywords: list of allowed keywords
        """
        msg = "%s is not a Jaguar keyword." % keyword
        super(self.__class__, self).__init__(msg)
        self.keyword = keyword
        self.allowed_keywords = allowed_keywords  
[docs]class JaguarKeywordValueTypeError(JaguarKeywordException):
    """
    Exception class raised when Jaguar keyword value has wrong type
    """
[docs]    def __init__(self, keyword, value, valid_type):
        """
        :type  keyword: string
        :param keyword: input keyword
        :type  value: string
        :param value: input value
        :type  valid_type: string
        :param valid_type: types as described in keywordsDB_mod.py
        """
        msg = "%s is not a valid type for keyword %s; expected %s." % (
            value, keyword, valid_type)
        super(self.__class__, self).__init__(msg)
        self.keyword = keyword
        self.value = value
        self.valid_type = valid_type  
[docs]class JaguarKeywordValueError(JaguarKeywordException):
    """
    Exception class raised when Jaguar keyword value is invalid
    """
[docs]    def __init__(self, keyword, value, settings):
        """
        :type  keyword: string
        :param keyword: input keyword
        :type  value: string
        :param value: input value
        :type  settings: list of Settings objects
        :param settings: settings associated with a keyword
        """
        self.keyword = keyword
        self.value = value
        self.allowed_settings = settings
        self.allowed_values = [x.value for x in settings]
        msg = "%s is not an allowed value for keyword %s; allowed values are: %s" % (
            value, keyword, ", ".join(self.allowed_values))
        super(self.__class__, self).__init__(msg)  
#-----------------------------------------------------------------------------
[docs]def value_is_type(valid_type, value):
    """
    Check if value has type equivalent to valid_type after converting string
    :type  valid_type: string
    :param valid_type: types as described in keywordsDB.py
    :type  value: string
    :param value: keyword value from input
    :return: True or False
    """
    # unittested
    # Test if valid int
    try:
        int(value)
        is_int = True
    except ValueError:
        is_int = False
    # Test if valid float
    try:
        float(value)
        is_float = True
    except ValueError:
        is_float = False
    if valid_type == INTEGER:
        if is_int:
            return True
        else:
            return False
    elif valid_type == REAL:
        if is_float:
            return True
        else:
            return False
    elif valid_type == STRING:
        # e.g. accept neither '12' nor '12.0'
        if is_int or is_float:
            return False
        else:
            return True
    else:
        raise AttributeError("Jaguar keyword type not recognized") 
_keywords_list = _keywords_dict = None
[docs]def keyword_value_pair_is_valid(keyword, value):
    """
    Validate a specific keyword=value pair.
    The checks are case insensitive.
    :type  keyword: string
    :param keyword: e.g. 'igeopt'
    :type  value: string
    :param value: e.g. '2' or '0.004' or any string
    :return: True if all pairs valid, otherwise raise specialized exceptions.
    """
    # unittested
    global _keywords_list, _keywords_dict
    # Transform to lowercase for comparisons
    keywordl = keyword.lower()
    valuel = value.lower()
    if _keywords_list is None:
        # We only read the file in once because it is an expensive (~0.2
        # seconds) read and will never change during a session JAGUAR-7587.
        filename = kwutils.jaguar_keywords_xml_filename()
        _keywords_list, _keywords_dict = load_keywords(filename)
    # 1) Check for valid Jaguar keyword
    keywords = list(_keywords_dict)
    if keywordl not in keywords:
        raise JaguarKeywordError(keyword, keywords)
    # 2) Check for valid value type
    valid_type = _keywords_dict[keywordl].type
    if not value_is_type(valid_type, valuel):
        raise JaguarKeywordValueTypeError(keyword, value, valid_type)
    # 3) Check for valid value
    if keyword.lower() == 'dftname':
        # Handle dftname as a special case because choices are not present in .xml file
        allowed_values = [x.lower() for x in kwutils.all_dftnames()]
        settings = [Setting('dftname', 'dftname', v) for v in allowed_values]
    else:
        kwobj = _keywords_dict[keywordl]
        settings = kwobj.settings
        allowed_values = [x.value for x in kwobj.settings]
    if allowed_values:
        # If free to choose, settings attribute is empty list
        if valuel not in allowed_values:
            raise JaguarKeywordValueError(keyword, value, settings)
    return True 
[docs]def keyword_value_pairs_are_valid(pairs):
    """
    Validate a string of keyword=value pairs
    :type  pairs: string
    :param pairs: e.g. '-keyword1=val1 -keyword2=value2 -keyword3=value3'
    :return: True if all pairs valid, otherwise raise specialized exceptions.
    """
    for item in pairs.split():
        try:
            keyword, value = item.split("=")
        except ValueError:
            raise JaguarKeywordFormatError(item)
        keyword_value_pair_is_valid(keyword, value)
    return True