def parse_id(attribute): check_no_missing_keys(attribute, ["type", "brief"]) attr_val = attribute["type"] try: attr_type = EnumAttributeType.parse(attr_val) except ValidationError as e: position = attribute.lc.data["type"] raise ValidationError.from_yaml_pos(position, e.message) from e brief = attribute["brief"] zlass = (AttributeType.type_mapper(attr_type) if isinstance( attr_type, str) else "enum") examples = attribute.get("examples") is_simple_type = AttributeType.is_simple_type(attr_type) # if we are an array, examples must already be an array if (is_simple_type and attr_type.endswith("[]") and not isinstance(examples, CommentedSeq)): position = attribute.lc.data[list(attribute)[0]] msg = "Non array examples for {} are not allowed".format(attr_type) raise ValidationError.from_yaml_pos(position, msg) if ((is_simple_type or isinstance(attr_type, EnumAttributeType)) and not isinstance(examples, CommentedSeq) and examples is not None): examples = [examples] if is_simple_type and attr_type not in ["boolean", "boolean[]"]: if examples is None or (len(examples) == 0): position = attribute.lc.data[list(attribute)[0]] msg = "Empty examples for {} are not allowed".format(attr_type) raise ValidationError.from_yaml_pos(position, msg) if is_simple_type and attr_type not in ["boolean", "boolean[]"]: AttributeType.check_examples_type(attr_type, examples, zlass) return attr_type, str(brief), examples
def parse_constraint(yaml_constraints): """ This method parses the yaml representation for semantic convention attributes creating a list of Constraint objects. """ constraints = () allowed_keys = ("include", "any_of") for constraint in yaml_constraints: validate_values(constraint, allowed_keys) if len(constraint.keys()) > 1: position = constraint.lc.data[list(constraint)[1]] msg = "Invalid entry in constraint array - multiple top-level keys in entry." raise ValidationError.from_yaml_pos(position, msg) if "include" in constraint: constraints += (Include(constraint.get("include")),) elif "any_of" in constraint: choice_sets = () for constraint_list in constraint.get("any_of"): inner_id_list = () if isinstance(constraint_list, CommentedSeq): inner_id_list = tuple( attr_constraint for attr_constraint in constraint_list ) else: inner_id_list += (constraint_list,) choice_sets += (inner_id_list,) constraints += (AnyOf(choice_sets),) return constraints
def validate_id(semconv_id, position): if not ID_RE.fullmatch(semconv_id): raise ValidationError.from_yaml_pos( position, "Invalid id {}. Semantic Convention ids MUST be {}".format( semconv_id, ID_RE.pattern ), )
def validate_values(yaml, keys, mandatory=()): """ This method checks only valid keywords and value types are used """ unwanted = list(set(yaml) - set(keys)) if unwanted: position = yaml.lc.data[unwanted[0]] msg = "Invalid keys: {}".format(unwanted) raise ValidationError.from_yaml_pos(position, msg) if mandatory: check_no_missing_keys(yaml, mandatory)
def parse(attribute_type): """ This method parses the yaml representation for semantic attribute types. If the type is an enumeration, it generated the EnumAttributeType object, otherwise it returns the basic type as string. """ if isinstance(attribute_type, str): if AttributeType.is_simple_type(attribute_type): return attribute_type else: # Wrong type used - rise the exception and fill the missing data in the parent raise ValidationError( 0, 0, "Invalid type: {} is not allowed".format(attribute_type)) else: allowed_keys = ["allow_custom_values", "members"] mandatory_keys = ["members"] validate_values(attribute_type, allowed_keys, mandatory_keys) custom_values = (bool(attribute_type.get("allow_custom_values")) if "allow_custom_values" in attribute_type else False) members = [] if attribute_type["members"] is None or len( attribute_type["members"]) < 1: # Missing members - rise the exception and fill the missing data in the parent raise ValidationError(0, 0, "Enumeration without values!") allowed_keys = ["id", "value", "brief", "note"] mandatory_keys = ["id", "value"] for member in attribute_type["members"]: validate_values(member, allowed_keys, mandatory_keys) members.append( EnumMember( member_id=member["id"], value=member["value"], brief=member.get("brief") if "brief" in member else member["id"], note=member.get("note") if "note" in member else "", )) enum_type = AttributeType.get_type(members[0].value) for m in members: if enum_type != AttributeType.get_type(m.value): raise ValidationError(0, 0, "Enumeration type inconsistent!") return EnumAttributeType(custom_values, members, enum_type)
def _populate_extends_single( self, semconv: SemanticConvention, unprocessed: typing.Dict[str, SemanticConvention], ): """ Resolves the parent/child relationship for a single Semantic Convention. If the parent **p** of the input semantic convention **i** has in turn a parent **pp**, it recursively resolves **pp** before processing **p**. :param semconv: The semantic convention for which resolve the parent/child relationship. :param semconvs: The list of remaining semantic conventions to process. :return: A list of remaining semantic convention to process. """ # Resolve parent of current Semantic Convention if semconv.extends: extended = self.models.get(semconv.extends) if extended is None: raise ValidationError.from_yaml_pos( semconv._position, "Semantic Convention {} extends {} but the latter cannot be found!".format( semconv.semconv_id, semconv.extends ), ) # Process hierarchy chain not_yet_processed = extended.extends in unprocessed if extended.extends and not_yet_processed: # Recursion on parent if was not already processed parent_extended = self.models.get(extended.extends) self._populate_extends_single(parent_extended, unprocessed) # inherit prefix and constraints if not semconv.prefix: semconv.prefix = extended.prefix # Constraints for constraint in extended.constraints: if constraint not in semconv.constraints and isinstance( constraint, AnyOf ): semconv.constraints += (constraint.inherit_anyof(),) # Attributes parent_attributes = {} for ext_attr in extended.attrs_by_name.values(): parent_attributes[ext_attr.fqn] = ext_attr.inherit_attribute() # By induction, parent semconv is already correctly sorted parent_attributes.update( SemanticConventionSet._sort_attributes_dict(semconv.attrs_by_name) ) semconv.attrs_by_name = parent_attributes else: # No parent, sort of current attributes semconv.attrs_by_name = SemanticConventionSet._sort_attributes_dict( semconv.attrs_by_name ) # delete from remaining semantic conventions to process del unprocessed[semconv.semconv_id]
def check_examples_type(attr_type, examples, zlass): """ This method checks example are correctly typed """ index = -1 for example in examples: index += 1 if attr_type.endswith("[]") and isinstance(example, Iterable): # Multi array example for element in example: if not isinstance(element, zlass): position = examples.lc.data[index] msg = "Example with wrong type. Expected {} examples but is was {}.".format( attr_type, type(element)) raise ValidationError.from_yaml_pos(position, msg) else: # Single value example or array example with a single example array if not isinstance(example, zlass): position = examples.lc.data[index] msg = "Example with wrong type. Expected {} examples but is was {}.".format( attr_type, type(example)) raise ValidationError.from_yaml_pos(position, msg)
def to_bool(key, parent_object): """ This method translate yaml boolean values to python boolean values """ yaml_value = parent_object.get(key) if isinstance(yaml_value, bool): return yaml_value if isinstance(yaml_value, str): if AttributeType.bool_type_true.fullmatch(yaml_value): return True elif AttributeType.bool_type_false.fullmatch(yaml_value): return False position = parent_object.lc.data[key] msg = "Value '{}' for {} field is not allowed".format(yaml_value, key) raise ValidationError.from_yaml_pos(position, msg)
def resolve_include(self, semconv: SemanticConvention): fixpoint_inc = True for constraint in semconv.constraints: if isinstance(constraint, Include): include_semconv: SemanticConvention include_semconv = self.models.get(constraint.semconv_id) # include required attributes and constraints if include_semconv is None: raise ValidationError.from_yaml_pos( semconv._position, "Semantic Convention {} includes {} but the latter cannot be found!".format( semconv.semconv_id, constraint.semconv_id ), ) # We resolve the parent/child relationship of the included semantic convention, if any self._populate_extends_single( include_semconv, {include_semconv.semconv_id: include_semconv} ) attr: SemanticAttribute for attr in include_semconv.attributes: if semconv.contains_attribute(attr): if self.debug: print( "[Includes] {} already contains attribute {}".format( semconv.semconv_id, attr ) ) continue # There are changes fixpoint_inc = False semconv.attrs_by_name[attr.fqn] = attr.import_attribute() for inc_constraint in include_semconv.constraints: if ( isinstance(inc_constraint, Include) or inc_constraint in semconv.constraints ): # We do not include "include" constraint or the constraint was already imported continue # We know the type of the constraint inc_constraint: AnyOf # There are changes fixpoint_inc = False semconv.constraints += (inc_constraint.inherit_anyof(),) return fixpoint_inc
def parse(yaml_file): yaml = YAML().load(yaml_file) models = [] available_keys = ( "id", "brief", "note", "prefix", "extends", "span_kind", "attributes", "constraints", ) mandatory_keys = ("id", "brief") for group in yaml["groups"]: validate_values(group, available_keys, mandatory_keys) validate_id(group["id"], group.lc.data["id"]) span_kind = SpanKind.parse(group.get("span_kind")) if span_kind is None: position = group.lc.data["span_kind"] msg = "Invalid value for span_kind: {}".format(group.get("span_kind")) raise ValidationError.from_yaml_pos(position, msg) prefix = group.get("prefix", "") if prefix != "": validate_id(prefix, group.lc.data["prefix"]) position = group.lc.data["id"] model = SemanticConvention( semconv_id=group["id"].strip(), brief=str(group["brief"]).strip(), note=group.get("note", "").strip(), prefix=prefix.strip(), extends=group.get("extends", "").strip(), span_kind=span_kind, attrs_by_name=SemanticAttribute.parse(prefix, group.get("attributes")) if "attributes" in group else {}, constraints=SemanticConvention.parse_constraint( group.get("constraints", ()) ), _position=position, ) models.append(model) return models
def resolve_ref(self, semconv: SemanticConvention): fixpoint_ref = True attr: SemanticAttribute for attr in semconv.attributes: if attr.ref is not None and attr.attr_id is None: # There are changes fixpoint_ref = False ref_attr = self._lookup_attribute(attr.ref) if not ref_attr: raise ValidationError.from_yaml_pos( semconv._position, "Semantic Convention {} reference `{}` but it cannot be found!".format( semconv.semconv_id, attr.ref ), ) attr.attr_type = ref_attr.attr_type if not attr.brief: attr.brief = ref_attr.brief if not attr.note: attr.note = ref_attr.note if attr.examples is None: attr.examples = ref_attr.examples attr.attr_id = attr.ref return fixpoint_ref
def check_no_missing_keys(yaml, mandatory): missing = list(set(mandatory) - set(yaml)) if missing: position = yaml.lc.data[list(yaml)[0]] msg = "Missing keys: {}".format(missing) raise ValidationError.from_yaml_pos(position, msg)
def parse(prefix, yaml_attributes): """ This method parses the yaml representation for semantic attributes creating the respective SemanticAttribute objects. """ attributes = {} allowed_keys = ( "id", "type", "brief", "examples", "ref", "tag", "deprecated", "required", "sampling_relevant", "note", ) for attribute in yaml_attributes: validate_values(attribute, allowed_keys) attr_id = attribute.get("id") ref = attribute.get("ref") position = attribute.lc.data[list(attribute)[0]] if attr_id is None and ref is None: msg = "At least one of id or ref is required." raise ValidationError.from_yaml_pos(position, msg) if attr_id is not None: validate_id(attr_id, attribute.lc.data["id"]) attr_type, brief, examples = SemanticAttribute.parse_id( attribute) fqn = "{}.{}".format(prefix, attr_id) attr_id = attr_id.strip() else: # Ref attr_type = None if "type" in attribute: position = attribute.lc.data[list(attribute)[0]] msg = "Ref attribute '{}' must not declare a type".format( ref) raise ValidationError.from_yaml_pos(position, msg) brief = attribute.get("brief") examples = attribute.get("examples") ref = ref.strip() fqn = ref required_value_map = { "always": Required.ALWAYS, "conditional": Required.CONDITIONAL, "": Required.NO, } required_msg = "" required_val = attribute.get("required", "") if isinstance(required_val, CommentedMap): required = Required.CONDITIONAL required_msg = required_val.get("conditional", None) if required_msg is None: position = attribute.lc.data["required"] msg = "Missing message for conditional required field!" raise ValidationError.from_yaml_pos(position, msg) else: required = required_value_map.get(required_val) if required == Required.CONDITIONAL: position = attribute.lc.data["required"] msg = "Missing message for conditional required field!" raise ValidationError.from_yaml_pos(position, msg) if required is None: position = attribute.lc.data["required"] msg = "Value '{}' for required field is not allowed".format( required_val) raise ValidationError.from_yaml_pos(position, msg) tag = attribute.get("tag", "").strip() deprecated = attribute.get("deprecated") if deprecated is not None: if AttributeType.get_type( deprecated) != "string" or deprecated == "": position = attribute.lc.data["deprecated"] msg = ( "Deprecated field expects a string that specify why the attribute is deprecated and/or what" " to use instead! ") raise ValidationError.from_yaml_pos(position, msg) deprecated = deprecated.strip() sampling_relevant = (AttributeType.to_bool("sampling_relevant", attribute) if attribute.get("sampling_relevant") else False) note = attribute.get("note", "") fqn = fqn.strip() attr = SemanticAttribute( fqn=fqn, attr_id=attr_id, ref=ref, attr_type=attr_type, brief=brief.strip() if brief else "", examples=examples, tag=tag, deprecated=deprecated, required=required, required_msg=str(required_msg).strip(), sampling_relevant=sampling_relevant, note=note.strip(), position=position, ) if attr.fqn in attributes: position = attribute.lc.data[list(attribute)[0]] msg = ("Attribute id " + fqn + " is already present at line " + str(attributes.get(fqn).position[0] + 1)) raise ValidationError.from_yaml_pos(position, msg) attributes[fqn] = attr return attributes