Skip to content

Commit

Permalink
Refactored template attributes generation
Browse files Browse the repository at this point in the history
Signed-off-by: Alexander Wert <[email protected]>
  • Loading branch information
AlexanderWert committed Jul 19, 2023
1 parent 111b560 commit 0c6d950
Show file tree
Hide file tree
Showing 7 changed files with 176 additions and 243 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
from opentelemetry.semconv.model.exceptions import ValidationError
from opentelemetry.semconv.model.utils import (
check_no_missing_keys,
is_template_attribute,
validate_attribute_id,
validate_id,
validate_values,
)
Expand Down Expand Up @@ -67,6 +69,7 @@ class SemanticAttribute:
position: List[int]
inherited: bool = False
imported: bool = False
is_template: bool = False

def import_attribute(self):
return replace(self, imported=True)
Expand All @@ -84,7 +87,7 @@ def is_enum(self):

@staticmethod
def parse(
prefix, semconv_stability, yaml_attributes
prefix, semconv_stability, yaml_attributes, parse_template_attributes=False
) -> "Dict[str, SemanticAttribute]":
"""This method parses the yaml representation for semantic attributes
creating the respective SemanticAttribute objects.
Expand All @@ -107,138 +110,136 @@ def parse(
return attributes

for attribute in yaml_attributes:
attr = SemanticAttribute.parse_semantic_attribute(
prefix, attribute, semconv_stability, allowed_keys
)
if attr.fqn in attributes:
position_data = attribute.lc.data
position = position_data[list(attribute)[0]]
msg = (
"Attribute id "
+ attr.fqn
+ " is already present at line "
+ str(attributes[attr.fqn].position[0] + 1)
)
raise ValidationError.from_yaml_pos(position, msg)
attributes[attr.fqn] = attr
return attributes

@staticmethod
def parse_semantic_attribute(
prefix, attribute, semconv_stability, allowed_keys
) -> "SemanticAttribute":
"""This method parses the yaml representation for a single semantic attribute
creating the respective SemanticAttribute object.
"""
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_attribute_id(attr_id, position_data["id"])
attr_type, brief, examples = SemanticAttribute.parse_id(attribute)
if prefix:
fqn = f"{prefix}.{attr_id}"
else:
fqn = attr_id
else:
# Ref
attr_type = None
if "type" in attribute:
msg = f"Ref attribute '{ref}' must not declare a type"
raise ValidationError.from_yaml_pos(position, msg)
brief = attribute.get("brief")
examples = attribute.get("examples")
ref = ref.strip()
fqn = ref

is_template = is_template_attribute(fqn)
if (is_template and not parse_template_attributes) or (
not is_template and parse_template_attributes
):
continue

required_value_map = {
"required": RequirementLevel.REQUIRED,
"conditionally_required": RequirementLevel.CONDITIONALLY_REQUIRED,
"recommended": RequirementLevel.RECOMMENDED,
"opt_in": RequirementLevel.OPT_IN,
}
requirement_level_msg = ""
requirement_level_val = attribute.get("requirement_level", "")
requirement_level: Optional[RequirementLevel]
if isinstance(requirement_level_val, CommentedMap):

if len(requirement_level_val) != 1:
position = position_data["requirement_level"]
msg = "Multiple requirement_level values are not allowed!"
raise ValidationError.from_yaml_pos(position, msg)

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 = f"{prefix}.{attr_id}"
recommended_msg = requirement_level_val.get("recommended", None)
condition_msg = requirement_level_val.get(
"conditionally_required", None
)
if condition_msg is not None:
requirement_level = RequirementLevel.CONDITIONALLY_REQUIRED
requirement_level_msg = condition_msg
elif recommended_msg is not None:
requirement_level = RequirementLevel.RECOMMENDED
requirement_level_msg = recommended_msg
else:
fqn = attr_id
else:
# Ref
attr_type = None
if "type" in attribute:
msg = f"Ref attribute '{ref}' must not declare a type"
raise ValidationError.from_yaml_pos(position, msg)
brief = attribute.get("brief")
examples = attribute.get("examples")
ref = ref.strip()
fqn = ref

required_value_map = {
"required": RequirementLevel.REQUIRED,
"conditionally_required": RequirementLevel.CONDITIONALLY_REQUIRED,
"recommended": RequirementLevel.RECOMMENDED,
"opt_in": RequirementLevel.OPT_IN,
}
requirement_level_msg = ""
requirement_level_val = attribute.get("requirement_level", "")
requirement_level: Optional[RequirementLevel]
if isinstance(requirement_level_val, CommentedMap):
requirement_level = required_value_map.get(requirement_level_val)

if len(requirement_level_val) != 1:
if requirement_level_val and requirement_level is None:
position = position_data["requirement_level"]
msg = "Multiple requirement_level values are not allowed!"
msg = (
f"Value '{requirement_level_val}' for required field is not allowed"
)
raise ValidationError.from_yaml_pos(position, msg)

recommended_msg = requirement_level_val.get("recommended", None)
condition_msg = requirement_level_val.get("conditionally_required", None)
if condition_msg is not None:
requirement_level = RequirementLevel.CONDITIONALLY_REQUIRED
requirement_level_msg = condition_msg
elif recommended_msg is not None:
requirement_level = RequirementLevel.RECOMMENDED
requirement_level_msg = recommended_msg
else:
requirement_level = required_value_map.get(requirement_level_val)

if requirement_level_val and requirement_level is None:
position = position_data["requirement_level"]
msg = f"Value '{requirement_level_val}' for required field is not allowed"
raise ValidationError.from_yaml_pos(position, msg)

if (
requirement_level == RequirementLevel.CONDITIONALLY_REQUIRED
and not requirement_level_msg
):
position = position_data["requirement_level"]
msg = "Missing message for conditionally required field!"
raise ValidationError.from_yaml_pos(position, msg)
if (
requirement_level == RequirementLevel.CONDITIONALLY_REQUIRED
and not requirement_level_msg
):
position = position_data["requirement_level"]
msg = "Missing message for conditionally required field!"
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"]
tag = attribute.get("tag", "").strip()
stability, deprecated = SemanticAttribute.parse_stability_deprecated(
attribute.get("stability"), attribute.get("deprecated"), position_data
)
msg = f"Semantic convention stability set to deprecated but attribute '{attr_id}' is {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,
requirement_level=requirement_level,
requirement_level_msg=str(requirement_level_msg).strip(),
sampling_relevant=sampling_relevant,
note=parsed_note,
position=position,
)
return attr
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 = f"Semantic convention stability set to deprecated but attribute '{attr_id}' is {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,
requirement_level=requirement_level,
requirement_level_msg=str(requirement_level_msg).strip(),
sampling_relevant=sampling_relevant,
note=parsed_note,
position=position,
is_template=is_template,
)
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

@staticmethod
def parse_id(attribute):
Expand Down Expand Up @@ -340,71 +341,6 @@ def equivalent_to(self, other: "SemanticAttribute"):
return False


@dataclass
class AttributeTemplate(SemanticAttribute):
parameter_name: str = "key"

@staticmethod
def parse_attribute_templates(
prefix, semconv_stability, yaml_attributes
) -> "Dict[str, AttributeTemplate]":
"""This method parses the yaml representation for attribute templates
creating the respective AttributeTemplate objects.
"""
attributes = {} # type: Dict[str, AttributeTemplate]
allowed_keys = (
"id",
"type",
"brief",
"examples",
"ref",
"tag",
"deprecated",
"stability",
"requirement_level",
"sampling_relevant",
"note",
"parameter_name",
)
if not yaml_attributes:
return attributes

for attribute in yaml_attributes:
attr = SemanticAttribute.parse_semantic_attribute(
prefix, attribute, semconv_stability, allowed_keys
)
if attr.fqn in attributes:
position_data = attribute.lc.data
position = position_data[list(attribute)[0]]
msg = (
"Attribute id "
+ attr.fqn
+ " is already present at line "
+ str(attributes[attr.fqn].position[0] + 1)
)
raise ValidationError.from_yaml_pos(position, msg)
parameter_name = attribute.get("parameter_name")
attr_template = AttributeTemplate(
fqn=attr.fqn,
attr_id=attr.attr_id,
ref=attr.ref,
attr_type=attr.attr_type,
brief=attr.brief,
examples=attr.examples,
tag=attr.tag,
deprecated=attr.deprecated,
stability=attr.stability,
requirement_level=attr.requirement_level,
requirement_level_msg=attr.requirement_level_msg,
sampling_relevant=attr.sampling_relevant,
note=attr.note,
position=attr.position,
parameter_name=parameter_name if parameter_name is not None else "key",
)
attributes[attr_template.fqn] = attr_template
return attributes


class AttributeType:

# https://yaml.org/type/bool.html
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
from opentelemetry.semconv.model.constraints import AnyOf, Include, parse_constraints
from opentelemetry.semconv.model.exceptions import ValidationError
from opentelemetry.semconv.model.semantic_attribute import (
AttributeTemplate,
RequirementLevel,
SemanticAttribute,
unique_attributes,
Expand Down Expand Up @@ -125,6 +124,10 @@ def attribute_templates(self):

return list(self.attr_templates_by_name.values())

@property
def attributes_and_templates(self):
return self.attributes + self.attribute_templates

def __init__(self, group):
super().__init__(group)

Expand All @@ -143,8 +146,8 @@ def __init__(self, group):
self.attrs_by_name = SemanticAttribute.parse(
self.prefix, self.stability, group.get("attributes")
)
self.attr_templates_by_name = AttributeTemplate.parse_attribute_templates(
self.prefix, self.stability, group.get("attribute_templates")
self.attr_templates_by_name = SemanticAttribute.parse(
self.prefix, self.stability, group.get("attributes"), True
)

def contains_attribute(self, attr: "SemanticAttribute"):
Expand Down
Loading

0 comments on commit 0c6d950

Please sign in to comment.