def parse_stability_deprecated(stability, deprecated, position_data):
     if deprecated is not None and stability is None:
         stability = "deprecated"
     if deprecated is not None:
         if stability is not None and stability != "deprecated":
             position = position_data["deprecated"]
             msg = "There is a deprecation message but the stability is set to '{}'".format(
                 stability
             )
             raise ValidationError.from_yaml_pos(position, msg)
         if AttributeType.get_type(deprecated) != "string" or deprecated == "":
             position = position_data["deprecated"]
             msg = (
                 "Deprecated field expects a string that specifies why the attribute is deprecated and/or what"
                 " to use instead! "
             )
             raise ValidationError.from_yaml_pos(position, msg)
         deprecated = deprecated.strip()
     if stability is not None:
         stability = SemanticAttribute.check_stability(
             stability,
             position_data["stability"]
             if "stability" in position_data
             else position_data["deprecated"],
         )
     return stability, deprecated
Пример #2
0
    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(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
            # Wrong type used - raise the exception and fill the missing data in the parent
            raise ValidationError(
                0, 0, "Invalid type: {} is not allowed".format(attribute_type)
            )
        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 (
            not isinstance(attribute_type["members"], CommentedSeq)
            or len(attribute_type["members"]) < 1
        ):
            raise ValidationError.from_yaml_pos(
                attribute_type.lc.data["members"], "Enumeration without members!"
            )

        allowed_keys = ["id", "value", "brief", "note"]
        mandatory_keys = ["id", "value"]
        for member in attribute_type["members"]:
            validate_values(member, allowed_keys, mandatory_keys)
            if not EnumAttributeType.is_valid_enum_value(member["value"]):
                raise ValidationError.from_yaml_pos(
                    member.lc.data["value"][:2],
                    "Invalid value used in enum: <{}>".format(member["value"]),
                )
            validate_id(member["id"], member.lc.data["id"])
            members.append(
                EnumMember(
                    member_id=member["id"],
                    value=member["value"],
                    brief=member.get("brief", member["id"]).strip(),
                    note=member.get("note", "").strip(),
                )
            )
        enum_type = AttributeType.get_type(members[0].value)
        for myaml, m in zip(attribute_type["members"], members):
            if enum_type != AttributeType.get_type(m.value):
                raise ValidationError.from_yaml_pos(
                    myaml.lc.data["value"],
                    "Enumeration member does not have type {}!".format(enum_type),
                )
        return EnumAttributeType(custom_values, members, enum_type)
    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:
            if e.line != 0:  # Does the error already have a position?
                raise
            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 not isinstance(examples, CommentedSeq) and examples is not None:
            # TODO: If validation fails later, this will crash when trying to access position data
            # since a list, contrary to a CommentedSeq, does not have position data
            examples = [examples]
        if is_simple_type and attr_type not in (
            "boolean",
            "boolean[]",
            "int",
            "int[]",
            "double",
            "double[]",
        ):
            if not examples:
                position = attribute.lc.data[list(attribute)[0]]
                msg = "Empty examples for {} are not allowed".format(attr_type)
                raise ValidationError.from_yaml_pos(position, msg)

        # TODO: Implement type check for enum examples or forbid them
        if examples is not None and is_simple_type:
            AttributeType.check_examples_type(attr_type, examples, zlass)
        return attr_type, str(brief), examples
Пример #5
0
 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
Пример #6
0
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 match {}".format(
                semconv_id, ID_RE.pattern),
        )
 def __init__(self, group):
     super().__init__(group)
     self.name = group.get("name", self.prefix)
     if not self.name:
         raise ValidationError.from_yaml_pos(
             self._position,
             "Event must define at least one of name or prefix")
 def __init__(self, group):
     super().__init__(group)
     self.span_kind = SpanKind.parse(group.get("span_kind"))
     if self.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)
Пример #9
0
    def validate_keys(cls, node):
        unwanted = [key for key in node.keys() if key not in cls.allowed_keys]
        if unwanted:
            position = node.lc.data[unwanted[0]]
            msg = "Invalid keys: {}".format(unwanted)
            raise ValidationError.from_yaml_pos(position, msg)

        if cls.mandatory_keys:
            check_no_missing_keys(node, cls.mandatory_keys)
Пример #10
0
def validate_values(yaml, keys, mandatory=()):
    """This method checks only valid keywords and value types are used"""
    unwanted = [k for k in yaml.keys() if k not in 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)
Пример #11
0
 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)
Пример #12
0
    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 _populate_events(self):
     for semconv in self.models.values():
         events: typing.List[EventSemanticConvention] = []
         for event_id in semconv.events:
             event = self.models.get(event_id)
             if event is None:
                 raise ValidationError.from_yaml_pos(
                     semconv._position,
                     "Semantic Convention {} has {} as event but the latter cannot be found!"
                     .format(semconv.semconv_id, event_id),
                 )
             if not isinstance(event, EventSemanticConvention):
                 raise ValidationError.from_yaml_pos(
                     semconv._position,
                     "Semantic Convention {} has {} as event but"
                     " the latter is not a semantic convention for events!".
                     format(semconv.semconv_id, event_id),
                 )
             events.append(event)
         semconv.events = events
    def check_stability(stability_value, position):

        stability_value_map = {
            "deprecated": StabilityLevel.DEPRECATED,
            "experimental": StabilityLevel.EXPERIMENTAL,
            "stable": StabilityLevel.STABLE,
        }
        val = stability_value_map.get(stability_value)
        if val is not None:
            return val
        msg = "Value '{}' is not allowed as a stability marker".format(stability_value)
        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
         if 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)
Пример #16
0
 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
Пример #17
0
 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 _populate_anyof_attributes(self):
     any_of: AnyOf
     for semconv in self.models.values():
         for any_of in semconv.constraints:
             if not isinstance(any_of, AnyOf):
                 continue
             for index, attr_ids in enumerate(any_of.choice_list_ids):
                 constraint_attrs = []
                 for attr_id in attr_ids:
                     ref_attr = self._lookup_attribute(attr_id)
                     if ref_attr is None:
                         raise ValidationError.from_yaml_pos(
                             any_of._yaml_src_position[index],
                             "Any_of attribute '{}' of semantic convention {} does not exists!"
                             .format(attr_id, semconv.semconv_id),
                         )
                     constraint_attrs.append(ref_attr)
                 if constraint_attrs:
                     any_of.add_attributes(constraint_attrs)
 def resolve_ref(self, semconv):
     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 SemanticConvention(group):
    type_value = group.get("type")
    if type_value is None:
        line = group.lc.data["id"][1] + 1
        print(
            "Using default SPAN type for semantic convention '{}' @ line {}".
            format(group["id"], line),
            file=sys.stderr,
        )

    convention_type = parse_semantic_convention_type(type_value)
    if convention_type is None:
        position = group.lc.data["type"] if "type" in group else group.lc.data[
            "id"]
        msg = "Invalid value for semantic convention type: {}".format(
            group.get("type"))
        raise ValidationError.from_yaml_pos(position, msg)

    # First, validate that the correct fields are available in the yaml
    convention_type.validate_keys(group)
    model = convention_type(group)
    # Also, validate that the value of the fields is acceptable
    model.validate_values()
    return model
Пример #21
0
def check_no_missing_keys(yaml, mandatory):
    missing = list(set(mandatory) - set(yaml))
    if missing:
        position = (yaml.lc.line, yaml.lc.col)
        msg = "Missing keys: {}".format(missing)
        raise ValidationError.from_yaml_pos(position, msg)
Пример #22
0
    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
    def parse(
        prefix, semconv_stability, yaml_attributes
    ) -> "Dict[str, SemanticAttribute]":
        """This method parses the yaml representation for semantic attributes
        creating the respective SemanticAttribute objects.
        """
        attributes = {}  # type: Dict[str, SemanticAttribute]
        allowed_keys = (
            "id",
            "type",
            "brief",
            "examples",
            "ref",
            "tag",
            "deprecated",
            "stability",
            "required",
            "sampling_relevant",
            "note",
        )
        if not yaml_attributes:
            return attributes

        for attribute in yaml_attributes:
            validate_values(attribute, allowed_keys)
            attr_id = attribute.get("id")
            ref = attribute.get("ref")
            position_data = attribute.lc.data
            position = position_data[next(iter(attribute))]
            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, position_data["id"])
                attr_type, brief, examples = SemanticAttribute.parse_id(attribute)
                if prefix:
                    fqn = "{}.{}".format(prefix, attr_id)
                else:
                    fqn = attr_id
            else:
                # Ref
                attr_type = None
                if "type" in attribute:
                    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", "")
            required: Optional[Required]
            if isinstance(required_val, CommentedMap):
                required = Required.CONDITIONAL
                required_msg = required_val.get("conditional", None)
                if required_msg is None:
                    position = position_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 = position_data["required"]
                    msg = "Missing message for conditional required field!"
                    raise ValidationError.from_yaml_pos(position, msg)
            if required is None:
                position = position_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()
            stability, deprecated = SemanticAttribute.parse_stability_deprecated(
                attribute.get("stability"), attribute.get("deprecated"), position_data
            )
            if (
                semconv_stability == StabilityLevel.DEPRECATED
                and stability is not StabilityLevel.DEPRECATED
            ):
                position = (
                    position_data["stability"]
                    if "stability" in position_data
                    else position_data["deprecated"]
                )
                msg = "Semantic convention stability set to deprecated but attribute '{}' is {}".format(
                    attr_id, stability
                )
                raise ValidationError.from_yaml_pos(position, msg)
            stability = stability or semconv_stability or StabilityLevel.STABLE
            sampling_relevant = (
                AttributeType.to_bool("sampling_relevant", attribute)
                if attribute.get("sampling_relevant")
                else False
            )
            note = attribute.get("note", "")
            fqn = fqn.strip()
            parsed_brief = TextWithLinks(brief.strip() if brief else "")
            parsed_note = TextWithLinks(note.strip())
            attr = SemanticAttribute(
                fqn=fqn,
                attr_id=attr_id,
                ref=ref,
                attr_type=attr_type,
                brief=parsed_brief,
                examples=examples,
                tag=tag,
                deprecated=deprecated,
                stability=stability,
                required=required,
                required_msg=str(required_msg).strip(),
                sampling_relevant=sampling_relevant,
                note=parsed_note,
                position=position,
            )
            if attr.fqn in attributes:
                position = position_data[list(attribute)[0]]
                msg = (
                    "Attribute id "
                    + fqn
                    + " is already present at line "
                    + str(attributes[fqn].position[0] + 1)
                )
                raise ValidationError.from_yaml_pos(position, msg)
            attributes[fqn] = attr
        return attributes