def _validate_variable_data(question_name, var_name, var_data): # type: (str, str, Dict[str, Any]) -> bool """Perform validation on variable metadata and fix style if necessary. :raises QuestionValidationException if metadata is invalid. """ var_type = var_data.get('type', '').strip() if not var_type: raise QuestionValidationException( "Question {} is missing type for variable {}".format( question_name, var_name)) var_data['type'] = var_type var_desc = var_data.get('description', '').strip() if not var_desc: raise QuestionValidationException( "Question {} is missing description for variable {}".format( question_name, var_name)) if not var_desc.endswith('.'): var_desc += '.' var_data['description'] = var_desc return True
def validate_question_name(name): # type: (str) -> bool """Check if a question name is valid. :param name: question name :type name: str :returns True if the question name is valid :raises QuestionValidationException if the name is not valid """ try: if "/" in name: raise QuestionValidationException( "Question name cannot contain slashes ('/')" ) if len(name) > _MAX_FILENAME_LEN: raise QuestionValidationException( "Question name cannot be longer than {} characters".format( _MAX_FILENAME_LEN ) ) except (TypeError, AttributeError): raise QuestionValidationException( "Question name has the wrong type ({}), a string is expected".format( type(name) ) ) return True
def _load_question_dict(question): # type: (Dict[str, Any]) -> Tuple[str, QuestionMeta] """Create a question from a dictionary which contains a template. :return the name of the question """ # Perform series of validations on the question. # Try to have meaningful error messages. # Check has instance data instance_data = question.get('instance') if not instance_data: raise QuestionValidationException("Missing instance data") # name validation given_question_name = instance_data.get('instanceName') if not given_question_name or not validate_question_name( given_question_name): raise QuestionValidationException( "Invalid question name: {}".format(given_question_name)) question_name = str(given_question_name) # type: str # description validation question_description = instance_data.get( 'description', '').strip() # type: str if not question_description: raise QuestionValidationException( "Missing description for question '{}'".format(question_name)) if not question_description.endswith('.'): question_description += '.' # Extend description if we can long_description = instance_data.get( 'longDescription', '').strip() # type: str if long_description: if not long_description.endswith('.'): long_description += '.' question_description = "\n\n".join( [question_description, long_description]) # Extract question tags tags = sorted(map(str, instance_data.get('tags', []))) _tags.update(tags) # Validate question variables ivars = instance_data.get('variables') variables = _process_variables(question_name, ivars) # Compute docstring docstring = _compute_docstring(question_description, variables, ivars) # Make new Question class question_class = QuestionMeta(question_name, (QuestionBase,), { 'docstring': docstring, 'description': question_description, 'tags': tags, 'template': deepcopy(question), 'variables': variables, }) return question_name, question_class
def constructor(self, *args, **kwargs): """Create a new question.""" # Reject positional args; this way is PY2-compliant if args: raise TypeError("Please use keyword arguments") # Call super (i.e., QuestionBase) super(new_cls, self).__init__(new_cls.template, new_cls.session) # Update well-known params, if passed in if "exclusions" in kwargs: self._dict['exclusions'] = kwargs.get("exclusions") if "question_name" in kwargs: self._dict['instance']['instanceName'] = kwargs.get( "question_name") else: self._dict['instance']['instanceName'] = ("__{}_{}".format( self._dict['instance']['instanceName'], get_uuid())) # Validate that we are not accepting invalid kwargs/variables instance_vars = self._dict['instance'].get('variables', {}) allowed_kwargs = set(instance_vars) allowed_kwargs.update(additional_kwargs) var_difference = set(kwargs.keys()).difference(allowed_kwargs) if var_difference: raise QuestionValidationException( "Received unsupported parameters/variables: {}".format( var_difference)) # Set question-specific parameters for var_name, var_value in kwargs.items(): if var_name not in additional_kwargs: instance_vars[var_name]['value'] = var_value
def constructor(self, question_name=None, exclusions=None, **kwargs): """Create a new question.""" # Call super (i.e., QuestionBase) super(new_cls, self).__init__(new_cls.template) # Update well-known params, if passed in if exclusions is not None: self._dict['exclusions'] = exclusions if question_name: self._dict['instance']['instanceName'] = question_name else: self._dict['instance']['instanceName'] = ( "__{}_{}".format( self._dict['instance']['instanceName'], get_uuid())) # Validate that we are not accepting invalid kwargs/variables instance_vars = self._dict['instance'].get('variables', {}) var_difference = set(kwargs.keys()).difference(instance_vars) if var_difference: raise QuestionValidationException( "Received unsupported parameters/variables: {}".format( var_difference)) # Set question-specific parameters for var_name, var_value in kwargs.items(): instance_vars[var_name]['value'] = var_value
def _validate_variable_data(question_name, var_name, var_data): # type: (str, str, Dict[str, Any]) -> bool """Perform validation on variable metadata. :raises QuestionValidationException if metadata is invalid. """ if var_data.get('type') is None: raise QuestionValidationException( "Question {} is missing type for variable {}".format( question_name, var_name)) if var_data.get('description') is None: raise QuestionValidationException( "Question {} is missing description for variable {}".format( question_name, var_name)) return True
def _validate_variable_name(question_name, var_name): # type: (str, str) -> bool """Check if the variable name is valid.""" if not re.match(_VALID_VARIABLE_NAME_REGEX, var_name): raise QuestionValidationException( "Question {} has invalid variable name: {}. Only alphanumeric characters are allowed" .format(question_name, var_name)) return True
def validate_json_path_regex(s): # type: (str) -> bool """Check if the given string is a valid JsonPath regex. :param s: string to check :type s: str :return True if `s` is valid :raises QuestionValidationException if `s` is not valid """ if not s.startswith('/'): raise QuestionValidationException( "Expected '/' at the start of JsonPath regex") if not (s.endswith('/i') or s.endswith('/')): raise QuestionValidationException( "JsonPath regex must end with either '/' or '/i'") return True
def _load_question_disk(question_path, session): # type: (str, Session) -> Tuple[str, QuestionMeta] """Load a question template from disk and instantiate a new `:py:class:Question`.""" with open(question_path, 'r') as question_file: question_dict = json.load(question_file) try: return _load_question_dict(question_dict, session) except QuestionValidationException as e: raise QuestionValidationException( "Error loading question from {}".format(question_path), e)
def _validate(questionJson): valid = True errorMessage = '\n' instanceData = questionJson['instance'] if 'variables' in instanceData: variables = instanceData['variables'] for variableName, variable in variables.items(): # First check for missing mandatory parameters optional = False if 'optional' in variable: optional = variable['optional'] if not optional: if 'value' not in variable: valid = False errorMessage += " Missing value for mandatory parameter: '" + variableName + "'\n" # Now do some dynamic type-checking allowed_values = _build_allowed_values(variable) if 'value' in variable: value = variable['value'] variableType = variable['type'] minLength = None if 'minLength' in variable: minLength = variable['minLength'] isArray = 'minElements' in variable if isArray: if not isinstance(value, list): valid = False errorMessage += " Expected a list for parameter: '" + variableName + "'\n" else: minElements = variable['minElements'] if len(value) < minElements: valid = False errorMessage += " Number of elements provided for parameter: '" + variableName + "' less than the minimum: " + str( minElements) + "\n" else: for i in range(0, len(value)): valueElement = value[i] typeValid = _validateType( valueElement, variableType) if not typeValid: valid = False errorMessage += " Expected type: '" + variableType + "' for element: " + str( i ) + " of parameter: " ' + variableName + ' "\n" elif minLength and len( valueElement) < minLength: valid = False errorMessage += " Length of value: '" + valueElement + "' for element : " + str( i ) + " of parameter: '" + variableName + "' below minimum length: " + str( minLength) + "\n" elif allowed_values is not None and valueElement not in \ [v.name for v in allowed_values]: valid = False errorMessage += " Value: '{}' is not among allowed values {} of parameter: '{}'\n".format( valueElement, [v.name for v in allowed_values], variableName) else: typeValid, typeValidErrorMessage = _validateType( value, variableType) if not typeValid: valid = False if typeValidErrorMessage: errorMessage += " Expected type: '" + variableType + "' for parameter: '" + variableName + "'. Got error: '" + typeValidErrorMessage + "'\n" else: errorMessage += " Expected type: '" + variableType + "' for parameter: '" + variableName + "'\n" elif minLength and len(value) < minLength: valid = False errorMessage += " Length of value: '" + value + "' for parameter: '" + variableName + "' below minimum length: " + str( minLength) + "\n" elif allowed_values is not None and value not in \ [v.name for v in allowed_values]: valid = False errorMessage += " Value: '{}' is not among allowed values {} of parameter: '{}'\n".format( value, [v.name for v in allowed_values], variableName) if not valid: raise QuestionValidationException(errorMessage) return True
def _load_question_dict(question, session): # type: (Dict[str, Any], Session) -> Tuple[str, QuestionMeta] """Create a question from a dictionary which contains a template. :return the name of the question """ # Perform series of validations on the question. # Try to have meaningful error messages. # Check has instance data instance_data = question.get("instance") if not instance_data: raise QuestionValidationException("Missing instance data") # name validation given_question_name = instance_data.get("instanceName") if not given_question_name or not validate_question_name( given_question_name): raise QuestionValidationException( "Invalid question name: {}".format(given_question_name)) question_name = str(given_question_name) # type: str # description validation question_description = instance_data.get("description", "").strip() # type: str if not question_description: raise QuestionValidationException( "Missing description for question '{}'".format(question_name)) if not question_description.endswith("."): question_description += "." # Extend description if we can long_description = instance_data.get("longDescription", "").strip() # type: str if long_description: if not long_description.endswith("."): long_description += "." question_description = "\n\n".join( [question_description, long_description]) # Extract question tags tags = sorted(map(str, instance_data.get("tags", []))) _tags.update(tags) # Validate question variables ivars = instance_data.get("variables", {}) ordered_variable_names = instance_data.get("orderedVariableNames", []) variables = _process_variables(question_name, ivars, ordered_variable_names) # Compute docstring docstring = _compute_docstring(question_description, variables, ivars) # Make new Question class question_class = QuestionMeta( question_name, (QuestionBase, ), { "docstring": docstring, "description": question_description, "session": session, "tags": tags, "template": deepcopy(question), "variables": variables, }, ) return question_name, question_class
def _load_question_dict(question, question_path=None, module_name=bfq.__name__): # type: (Dict, Optional[str], str) -> str """Create a question from a dictionary which contains a template. :return the name of the question """ # Perform series of validations on the question. # Try to have meaningful error messages. # Check has instance data instance_data = question.get('instance') if not instance_data: raise QuestionValidationException( "Missing instance data in question (file: {})".format( question_path)) # name validation given_question_name = instance_data.get('instanceName') if not given_question_name or not validate_question_name( given_question_name): raise QuestionValidationException( "Invalid question name: {}".format(given_question_name)) question_name = str(given_question_name) # type: str # description validation question_description = instance_data.get('description') if not question_description: raise QuestionValidationException( "Missing description for question '{}'".format(question_name)) # Extend description if we can long_description = instance_data.get('longDescription') if long_description: question_description = "\n".join( [question_description, long_description]) # Extract question tags tags = sorted(map(str, instance_data.get('tags', []))) _tags.update(tags) # Validate question variables ivars = instance_data.get('variables') variables = _process_variables(question_name, ivars) # Compute docstring docstring = _compute_docstring(question_description, ivars) # Make new Question class and set it in the specified module module = sys.modules[module_name] setattr( module, question_name, QuestionMeta( question_name, (QuestionBase, ), { 'docstring': docstring, 'description': question_description, 'module': module_name, 'tags': tags, 'template': deepcopy(question), 'variables': variables, })) return question_name