diff --git a/semantic-conventions/CHANGELOG.md b/semantic-conventions/CHANGELOG.md
index 0dc67ed4..a8b5810a 100644
--- a/semantic-conventions/CHANGELOG.md
+++ b/semantic-conventions/CHANGELOG.md
@@ -2,6 +2,11 @@
Please update the changelog as part of any significant pull request.
+## v0.4.0
+
+- Add stability fields. (#35)
+- Add Markdown render for code generation.
+
## v0.3.1
- Fix markdown generator for int enums. (#36)
diff --git a/semantic-conventions/requirements.txt b/semantic-conventions/requirements.txt
index e55c0210..6201369e 100644
--- a/semantic-conventions/requirements.txt
+++ b/semantic-conventions/requirements.txt
@@ -7,6 +7,6 @@ ruamel.yaml.clib==0.2.0
typed-ast==1.4.1
typing-extensions==3.7.4.2
Jinja2==2.11.2
-
pytest==6.1.1
-ipdb==0.13.4
\ No newline at end of file
+ipdb==0.13.4
+mistune==2.0.0a6
\ No newline at end of file
diff --git a/semantic-conventions/src/opentelemetry/semconv/main.py b/semantic-conventions/src/opentelemetry/semconv/main.py
index 5fa9f4b1..e40fd13d 100644
--- a/semantic-conventions/src/opentelemetry/semconv/main.py
+++ b/semantic-conventions/src/opentelemetry/semconv/main.py
@@ -23,6 +23,7 @@
from opentelemetry.semconv.templating.code import CodeRenderer
from opentelemetry.semconv.templating.markdown import MarkdownRenderer
+from opentelemetry.semconv.templating.markdown.options import MarkdownOptions
def parse_semconv(args, parser) -> SemanticConventionSet:
@@ -73,10 +74,16 @@ def main():
def process_markdown(semconv, args):
- exclude = exclude_file_list(args.markdown_root, args.exclude)
- md_renderer = MarkdownRenderer(
- args.markdown_root, semconv, exclude, args.md_break_conditional, args.md_check
+ options = MarkdownOptions(
+ check_only=args.md_check,
+ enable_stable=args.md_stable,
+ enable_experimental=args.md_experimental,
+ enable_deprecated=args.md_enable_deprecated,
+ use_badge=args.md_use_badges,
+ break_count=args.md_break_conditional,
+ exclude_files=exclude_file_list(args.markdown_root, args.exclude),
)
+ md_renderer = MarkdownRenderer(args.markdown_root, semconv, options)
md_renderer.render_md()
@@ -155,6 +162,32 @@ def add_md_parser(subparsers):
required=False,
action="store_true",
)
+ parser.add_argument(
+ "--md-use-badges",
+ help="Use stability badges instead of labels for attributes.",
+ required=False,
+ action="store_true",
+ )
+ parser.add_argument(
+ "--md-stable",
+ help="Add labels to attributes marked as stable.",
+ required=False,
+ action="store_true",
+ )
+ parser.add_argument(
+ "--md-experimental",
+ help="Add labels to attributes marked as experimental.",
+ required=False,
+ action="store_true",
+ )
+ parser.add_argument(
+ "--md-disable-deprecated",
+ help="Removes deprecated notes of deprecated attributes.",
+ required=False,
+ default=True,
+ dest="md_enable_deprecated",
+ action="store_false",
+ )
def setup_parser():
diff --git a/semantic-conventions/src/opentelemetry/semconv/model/semantic_attribute.py b/semantic-conventions/src/opentelemetry/semconv/model/semantic_attribute.py
index 6ea5de4a..5a2e11ae 100644
--- a/semantic-conventions/src/opentelemetry/semconv/model/semantic_attribute.py
+++ b/semantic-conventions/src/opentelemetry/semconv/model/semantic_attribute.py
@@ -35,9 +35,17 @@ class Required(Enum):
NO = 3
+class StabilityLevel(Enum):
+ STABLE = 1
+ EXPERIMENTAL = 2
+ DEPRECATED = 3
+
+
class HasAttributes:
- def _set_attributes(self, prefix, node):
- self.attrs_by_name = SemanticAttribute.parse(prefix, node.get("attributes"))
+ def _set_attributes(self, prefix, stability, node):
+ self.attrs_by_name = SemanticAttribute.parse(
+ prefix, stability, node.get("attributes")
+ )
@property
def attributes(self):
@@ -64,6 +72,7 @@ class SemanticAttribute:
brief: str
examples: List[Union[str, int, bool]]
tag: str
+ stability: StabilityLevel
deprecated: str
required: Required
required_msg: str
@@ -88,7 +97,7 @@ def is_enum(self):
return isinstance(self.attr_type, EnumAttributeType)
@staticmethod
- def parse(prefix, yaml_attributes):
+ def parse(prefix, semconv_stability, yaml_attributes):
""" This method parses the yaml representation for semantic attributes
creating the respective SemanticAttribute objects.
"""
@@ -101,6 +110,7 @@ def parse(prefix, yaml_attributes):
"ref",
"tag",
"deprecated",
+ "stability",
"required",
"sampling_relevant",
"note",
@@ -112,12 +122,13 @@ def parse(prefix, yaml_attributes):
validate_values(attribute, allowed_keys)
attr_id = attribute.get("id")
ref = attribute.get("ref")
- position = attribute.lc.data[list(attribute)[0]]
+ 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, attribute.lc.data["id"])
+ validate_id(attr_id, position_data["id"])
attr_type, brief, examples = SemanticAttribute.parse_id(attribute)
fqn = "{}.{}".format(prefix, attr_id)
attr_id = attr_id.strip()
@@ -125,7 +136,6 @@ def parse(prefix, yaml_attributes):
# 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")
@@ -144,32 +154,39 @@ def parse(prefix, yaml_attributes):
required = Required.CONDITIONAL
required_msg = required_val.get("conditional", None)
if required_msg is None:
- position = attribute.lc.data["required"]
+ 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 = attribute.lc.data["required"]
+ position = position_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"]
+ 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()
- 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()
+ 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")
@@ -177,23 +194,26 @@ def parse(prefix, yaml_attributes):
)
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=brief.strip() if brief else "",
+ brief=parsed_brief,
examples=examples,
tag=tag,
deprecated=deprecated,
+ stability=stability,
required=required,
required_msg=str(required_msg).strip(),
sampling_relevant=sampling_relevant,
- note=note.strip(),
+ note=parsed_note,
position=position,
)
if attr.fqn in attributes:
- position = attribute.lc.data[list(attribute)[0]]
+ position = position_data[list(attribute)[0]]
msg = (
"Attribute id "
+ fqn
@@ -253,6 +273,48 @@ def parse_id(attribute):
AttributeType.check_examples_type(attr_type, examples, zlass)
return attr_type, str(brief), examples
+ @staticmethod
+ 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
+
+ @staticmethod
+ 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 equivalent_to(self, other: "SemanticAttribute"):
if self.attr_id is not None:
if self.fqn == other.fqn:
@@ -395,7 +457,9 @@ def parse(attribute_type):
for member in attribute_type["members"]:
validate_values(member, allowed_keys, mandatory_keys)
if not EnumAttributeType.is_valid_enum_value(member["value"]):
- raise ValidationError(0, 0, "Invalid value used in enum: <{}>".format(member["value"]))
+ raise ValidationError(
+ 0, 0, "Invalid value used in enum: <{}>".format(member["value"])
+ )
members.append(
EnumMember(
member_id=member["id"],
@@ -419,3 +483,43 @@ class EnumMember:
value: str
brief: str
note: str
+
+
+class MdLink:
+ text: str
+ url: str
+
+ def __init__(self, text, url):
+ self.text = text
+ self.url = url
+
+ def __str__(self):
+ return "[{}]({})".format(self.text, self.url)
+
+
+class TextWithLinks(str):
+ parts: List[Union[str, MdLink]]
+ raw_text: str
+ md_link = re.compile("\[([^\[\]]+)\]\(([^)]+)")
+
+ def __init__(self, text):
+ super().__init__()
+ self.raw_text = text
+ self.parts = []
+ last_position = 0
+ for match in self.md_link.finditer(text):
+ prev_text = text[last_position : match.start()]
+ link = MdLink(match.group(1), match.group(2))
+ if prev_text:
+ self.parts.append(prev_text)
+ self.parts.append(link)
+ last_position = match.end() + 1
+ last_part = text[last_position:]
+ if last_part:
+ self.parts.append(last_part)
+
+ def __str__(self):
+ str_list = []
+ for elm in self.parts:
+ str_list.append(elm.__str__())
+ return "".join(str_list)
diff --git a/semantic-conventions/src/opentelemetry/semconv/model/semantic_convention.py b/semantic-conventions/src/opentelemetry/semconv/model/semantic_convention.py
index ce7902ea..627a2af8 100644
--- a/semantic-conventions/src/opentelemetry/semconv/model/semantic_convention.py
+++ b/semantic-conventions/src/opentelemetry/semconv/model/semantic_convention.py
@@ -25,6 +25,7 @@
from opentelemetry.semconv.model.semantic_attribute import (
HasAttributes,
SemanticAttribute,
+ StabilityLevel,
Required,
unique_attributes,
)
@@ -116,6 +117,12 @@ def __init__(self, group):
self.semconv_id = self.id
self.note = group.get("note", "").strip()
self.prefix = group.get("prefix", "").strip()
+ stability = group.get("stability")
+ deprecated = group.get("deprecated")
+ position_data = group.lc.data
+ self.stability, self.deprecated = SemanticAttribute.parse_stability_deprecated(
+ stability, deprecated, position_data
+ )
self.extends = group.get("extends", "").strip()
self.constraints = parse_constraints(group.get("constraints", ()))
@@ -187,6 +194,7 @@ class ResourceSemanticConvention(HasAttributes, BaseSemanticConvention):
"brief",
"note",
"prefix",
+ "stability",
"extends",
"attributes",
"constraints",
@@ -194,7 +202,7 @@ class ResourceSemanticConvention(HasAttributes, BaseSemanticConvention):
def __init__(self, group):
super().__init__(group)
- self._set_attributes(self.prefix, group)
+ self._set_attributes(self.prefix, self.stability, group)
class SpanSemanticConvention(HasAttributes, BaseSemanticConvention):
@@ -206,6 +214,7 @@ class SpanSemanticConvention(HasAttributes, BaseSemanticConvention):
"brief",
"note",
"prefix",
+ "stability",
"extends",
"span_kind",
"attributes",
@@ -214,7 +223,7 @@ class SpanSemanticConvention(HasAttributes, BaseSemanticConvention):
def __init__(self, group):
super().__init__(group)
- self._set_attributes(self.prefix, group)
+ self._set_attributes(self.prefix, self.stability, group)
self.span_kind = SpanKind.parse(group.get("span_kind"))
if self.span_kind is None:
position = group.lc.data["span_kind"]
diff --git a/semantic-conventions/src/opentelemetry/semconv/templating/code.py b/semantic-conventions/src/opentelemetry/semconv/templating/code.py
index a8019a35..4d147783 100644
--- a/semantic-conventions/src/opentelemetry/semconv/templating/code.py
+++ b/semantic-conventions/src/opentelemetry/semconv/templating/code.py
@@ -16,13 +16,96 @@
import os.path
import re
import typing
+import mistune
from jinja2 import Environment, FileSystemLoader, select_autoescape
+from opentelemetry.semconv.model.semantic_attribute import TextWithLinks
from opentelemetry.semconv.model.semantic_convention import SemanticConventionSet
from opentelemetry.semconv.model.utils import ID_RE
+def render_markdown(
+ txt: str,
+ html=True,
+ link=None,
+ image=None,
+ emphasis=None,
+ strong=None,
+ inline_html=None,
+ paragraph=None,
+ heading=None,
+ block_code=None,
+ block_quote=None,
+ list=None,
+ list_item=None,
+ code=None,
+):
+ class CustomRender(mistune.HTMLRenderer):
+ def link(self, url, text=None, title=None):
+ if link:
+ return link.format(url, text, title)
+ return super().link(url, text, title) if html else url
+
+ def image(self, src, alt="", title=None):
+ if image:
+ return image.format(src, alt, title)
+ return super().image(src, alt, title) if html else src
+
+ def emphasis(self, text):
+ if emphasis:
+ return emphasis.format(text)
+ return super().emphasis(text) if html else text
+
+ def strong(self, text):
+ if strong:
+ return strong.format(text)
+ return super().strong(text) if html else text
+
+ def inline_html(self, html_text):
+ if inline_html:
+ return inline_html.format(html_text)
+ return super().inline_html(html_text) if html else html_text
+
+ def paragraph(self, text):
+ if paragraph:
+ return paragraph.format(text)
+ return super().paragraph(text) if html else text
+
+ def heading(self, text, level):
+ if heading:
+ return heading.format(text, level)
+ return super().heading(text, level) if html else text
+
+ def block_code(self, code, info=None):
+ if block_code:
+ return block_code.format(code)
+ return super().block_code(code, info) if html else code
+
+ def block_quote(self, text):
+ if block_quote:
+ return block_quote.format(text)
+ return super().block_quote(text)
+
+ def list(self, text, ordered, level, start=None):
+ if list:
+ return list.format(text)
+ return super().list(text, ordered, level, start) if html else text
+
+ def list_item(self, text, level):
+ if list_item:
+ return list_item.format(text)
+ return super().list_item(text, level) if html else text
+
+ def codespan(self, text):
+ if code:
+ return code.format(text)
+ return super().codespan(text) if html else text
+
+ markdown = mistune.create_markdown(renderer=CustomRender())
+ return markdown(txt)
+
+
def to_doc_brief(doc_string: typing.Optional[str]) -> str:
if doc_string is None:
return ""
@@ -32,6 +115,29 @@ def to_doc_brief(doc_string: typing.Optional[str]) -> str:
return doc_string
+def to_html_links(doc_string: typing.Optional[typing.Union[str, TextWithLinks]]) -> str:
+ if doc_string is None:
+ return ""
+ if isinstance(doc_string, TextWithLinks):
+ str_list = []
+ for elm in doc_string.parts:
+ if isinstance(elm, str):
+ str_list.append(elm)
+ else:
+ str_list.append('{}'.format(elm.url, elm.text))
+ doc_string = "".join(str_list)
+ doc_string = doc_string.strip()
+ if doc_string.endswith("."):
+ return doc_string[:-1]
+ return doc_string
+
+
+def regex_replace(text: str, pattern: str, replace: str):
+ # convert standard dollar notation to python
+ replace = re.sub(r"\$", r"\\", replace)
+ return re.sub(pattern, replace, text, 0, re.U)
+
+
def merge(list: typing.List, elm):
return list.extend(elm)
@@ -93,6 +199,9 @@ def setup_environment(env: Environment):
env.filters["to_const_name"] = to_const_name
env.filters["merge"] = merge
env.filters["to_camelcase"] = to_camelcase
+ env.filters["to_html_links"] = to_html_links
+ env.filters["regex_replace"] = regex_replace
+ env.filters["render_markdown"] = render_markdown
@staticmethod
def prefix_output_file(file_name, pattern, semconv):
diff --git a/semantic-conventions/src/opentelemetry/semconv/templating/markdown.py b/semantic-conventions/src/opentelemetry/semconv/templating/markdown/__init__.py
similarity index 91%
rename from semantic-conventions/src/opentelemetry/semconv/templating/markdown.py
rename to semantic-conventions/src/opentelemetry/semconv/templating/markdown/__init__.py
index 30aec6cb..2deda4f8 100644
--- a/semantic-conventions/src/opentelemetry/semconv/templating/markdown.py
+++ b/semantic-conventions/src/opentelemetry/semconv/templating/markdown/__init__.py
@@ -23,6 +23,7 @@
from opentelemetry.semconv.model.constraints import AnyOf, Include
from opentelemetry.semconv.model.semantic_attribute import (
SemanticAttribute,
+ StabilityLevel,
EnumAttributeType,
Required,
EnumMember,
@@ -33,21 +34,14 @@
)
from opentelemetry.semconv.model.utils import ID_RE
+from opentelemetry.semconv.templating.markdown.options import MarkdownOptions
-class RenderContext:
- is_full: bool
- is_remove_constraint: bool
- group_key: str
- break_counter: int
- enums: list
- notes: list
- current_md: str
- def __init__(self, break_count):
+class RenderContext:
+ def __init__(self):
self.is_full = False
self.is_remove_constraint = False
self.group_key = ""
- self.break_count = break_count
self.enums = []
self.notes = []
self.units = []
@@ -78,24 +72,19 @@ class MarkdownRenderer:
table_headers = "| Attribute | Type | Description | Examples | Required |\n|---|---|---|---|---|\n"
def __init__(
- self,
- md_folder,
- semconvset: SemanticConventionSet,
- exclude: list = [],
- break_count=default_break_conditional_labels,
- check_only=False,
+ self, md_folder, semconvset: SemanticConventionSet, options=MarkdownOptions()
):
- self.render_ctx = RenderContext(break_count)
+ self.options = options
+ self.render_ctx = RenderContext()
self.semconvset = semconvset
# We load all markdown files to render
self.file_names = sorted(
set(glob.glob("{}/**/*.md".format(md_folder), recursive=True))
- - set(exclude)
+ - set(options.exclude_files)
)
# We build the dict that maps each attribute that has to be rendered to the latest visited file
# that contains it
self.filename_for_attr_fqn = self._create_attribute_location_dict()
- self.check_only = check_only
def to_markdown_attr(
self, attribute: SemanticAttribute, output: io.StringIO,
@@ -110,11 +99,27 @@ def to_markdown_attr(
else attribute.attr_type
)
description = ""
- if attribute.deprecated:
+ if attribute.deprecated and self.options.enable_deprecated:
if "deprecated" in attribute.deprecated.lower():
description = "**{}**
".format(attribute.deprecated)
else:
- description = "**Deprecated: {}**
".format(attribute.deprecated)
+ deprecated_msg = self.options.md_snippet_by_stability_level[
+ StabilityLevel.DEPRECATED
+ ].format(attribute.deprecated)
+ description = "{}
".format(deprecated_msg)
+ elif (
+ attribute.stability == StabilityLevel.STABLE and self.options.enable_stable
+ ):
+ description = "{}
".format(
+ self.options.md_snippet_by_stability_level[StabilityLevel.STABLE]
+ )
+ elif (
+ attribute.stability == StabilityLevel.EXPERIMENTAL
+ and self.options.enable_experimental
+ ):
+ description = "{}
".format(
+ self.options.md_snippet_by_stability_level[StabilityLevel.EXPERIMENTAL]
+ )
description += attribute.brief
if attribute.note:
self.render_ctx.add_note(attribute.note)
@@ -138,13 +143,15 @@ def to_markdown_attr(
example_list = attribute.examples if attribute.examples else []
# check for array types
if attribute.attr_type.endswith("[]"):
- examples = "`[" + ", ".join("{}".format(ex) for ex in example_list) + "]`"
+ examples = (
+ "`[" + ", ".join("{}".format(ex) for ex in example_list) + "]`"
+ )
else:
examples = "; ".join("`{}`".format(ex) for ex in example_list)
if attribute.required == Required.ALWAYS:
required = "Yes"
elif attribute.required == Required.CONDITIONAL:
- if len(attribute.required_msg) < self.render_ctx.break_count:
+ if len(attribute.required_msg) < self.options.break_count:
required = attribute.required_msg
else:
# We put the condition in the notes after the table
@@ -273,7 +280,7 @@ def render_md(self):
content = md_file.read()
output = io.StringIO()
self._render_single_file(content, md_filename, output)
- if self.check_only:
+ if self.options.check_only:
if content != output.getvalue():
sys.exit(
"File "
@@ -283,7 +290,7 @@ def render_md(self):
else:
with open(md_filename, "w", encoding="utf-8") as md_file:
md_file.write(output.getvalue())
- if self.check_only:
+ if self.options.check_only:
print("{} files left unchanged.".format(len(self.file_names)))
def _create_attribute_location_dict(self):
diff --git a/semantic-conventions/src/opentelemetry/semconv/templating/markdown/options.py b/semantic-conventions/src/opentelemetry/semconv/templating/markdown/options.py
new file mode 100644
index 00000000..64dd6848
--- /dev/null
+++ b/semantic-conventions/src/opentelemetry/semconv/templating/markdown/options.py
@@ -0,0 +1,48 @@
+# Copyright The OpenTelemetry Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from dataclasses import dataclass, field
+from typing import List
+
+from opentelemetry.semconv.model.semantic_attribute import StabilityLevel
+
+
+@dataclass()
+class MarkdownOptions:
+
+ _badge_map = {
+ StabilityLevel.DEPRECATED: "![Deprecated](https://img.shields.io/badge/-deprecated-red)",
+ StabilityLevel.EXPERIMENTAL: "![Experimental](https://img.shields.io/badge/-experimental-blue)",
+ StabilityLevel.STABLE: "![Stable](https://img.shields.io/badge/-stable-lightgreen)",
+ }
+
+ _label_map = {
+ StabilityLevel.DEPRECATED: "**Deprecated: {}**",
+ StabilityLevel.EXPERIMENTAL: "**Experimental**",
+ StabilityLevel.STABLE: "**Stable**",
+ }
+
+ check_only: bool = False
+ enable_stable: bool = False
+ enable_experimental: bool = False
+ enable_deprecated: bool = True
+ use_badge: bool = False
+ break_count: int = 50
+ exclude_files: List[str] = field(default_factory=list)
+
+ @property
+ def md_snippet_by_stability_level(self):
+ if self.use_badge:
+ return self._badge_map
+ return self._label_map
diff --git a/semantic-conventions/src/opentelemetry/semconv/version.py b/semantic-conventions/src/opentelemetry/semconv/version.py
index 2555f9fe..17f4e2f3 100644
--- a/semantic-conventions/src/opentelemetry/semconv/version.py
+++ b/semantic-conventions/src/opentelemetry/semconv/version.py
@@ -12,4 +12,4 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-__version__ = "0.3.1"
+__version__ = "0.4.0"
diff --git a/semantic-conventions/src/tests/data/markdown/stability/badges_expected.md b/semantic-conventions/src/tests/data/markdown/stability/badges_expected.md
new file mode 100644
index 00000000..7912b241
--- /dev/null
+++ b/semantic-conventions/src/tests/data/markdown/stability/badges_expected.md
@@ -0,0 +1,10 @@
+# Common Attributes
+
+
+| Attribute | Type | Description | Examples | Required |
+|---|---|---|---|---|
+| [`test.exp_attr`](labels_expected.md) | boolean | | | Yes |
+| [`test.stable_attr`](labels_expected.md) | boolean | ![Stable](https://img.shields.io/badge/-stable-lightgreen)
| | Yes |
+| [`test.deprecated_attr`](labels_expected.md) | boolean | | | Yes |
+| [`test.def_stability`](labels_expected.md) | boolean | ![Stable](https://img.shields.io/badge/-stable-lightgreen)
| | Yes |
+
diff --git a/semantic-conventions/src/tests/data/markdown/stability/input.md b/semantic-conventions/src/tests/data/markdown/stability/input.md
new file mode 100644
index 00000000..0109ba90
--- /dev/null
+++ b/semantic-conventions/src/tests/data/markdown/stability/input.md
@@ -0,0 +1,5 @@
+# Common Attributes
+
+
+
+
diff --git a/semantic-conventions/src/tests/data/markdown/stability/labels_expected.md b/semantic-conventions/src/tests/data/markdown/stability/labels_expected.md
new file mode 100644
index 00000000..5d19fb6a
--- /dev/null
+++ b/semantic-conventions/src/tests/data/markdown/stability/labels_expected.md
@@ -0,0 +1,10 @@
+# Common Attributes
+
+
+| Attribute | Type | Description | Examples | Required |
+|---|---|---|---|---|
+| [`test.exp_attr`](labels_expected.md) | boolean | | | Yes |
+| [`test.stable_attr`](labels_expected.md) | boolean | | | Yes |
+| [`test.deprecated_attr`](labels_expected.md) | boolean | | | Yes |
+| [`test.def_stability`](labels_expected.md) | boolean | | | Yes |
+
diff --git a/semantic-conventions/src/tests/data/markdown/stability/stability.yaml b/semantic-conventions/src/tests/data/markdown/stability/stability.yaml
new file mode 100644
index 00000000..47f416f9
--- /dev/null
+++ b/semantic-conventions/src/tests/data/markdown/stability/stability.yaml
@@ -0,0 +1,25 @@
+groups:
+ - id: test
+ type: span
+ brief: 'test'
+ prefix: test
+ attributes:
+ - id: exp_attr
+ type: boolean
+ required: always
+ stability: experimental
+ brief: ""
+ - id: stable_attr
+ type: boolean
+ required: always
+ stability: stable
+ brief: ""
+ - id: deprecated_attr
+ type: boolean
+ required: always
+ stability: deprecated
+ brief: ""
+ - id: def_stability
+ type: boolean
+ required: always
+ brief: ""
diff --git a/semantic-conventions/src/tests/data/yaml/errors/stability/semconv_stability_deprecated.yaml b/semantic-conventions/src/tests/data/yaml/errors/stability/semconv_stability_deprecated.yaml
new file mode 100644
index 00000000..80e447eb
--- /dev/null
+++ b/semantic-conventions/src/tests/data/yaml/errors/stability/semconv_stability_deprecated.yaml
@@ -0,0 +1,13 @@
+groups:
+ - id: test
+ type: span
+ brief: 'test'
+ prefix: http
+ stability: deprecated
+ attributes:
+ - id: test_attr
+ type: boolean
+ required: always
+ stability: stable
+ brief: ""
+
diff --git a/semantic-conventions/src/tests/data/yaml/errors/stability/stability_deprecated.yaml b/semantic-conventions/src/tests/data/yaml/errors/stability/stability_deprecated.yaml
new file mode 100644
index 00000000..ed0bbc60
--- /dev/null
+++ b/semantic-conventions/src/tests/data/yaml/errors/stability/stability_deprecated.yaml
@@ -0,0 +1,13 @@
+groups:
+ - id: test
+ type: span
+ brief: 'test'
+ prefix: http
+ attributes:
+ - id: test_attr
+ type: boolean
+ required: always
+ stability: stable
+ deprecated: should fail.
+ brief: ""
+
diff --git a/semantic-conventions/src/tests/data/yaml/errors/stability/wrong_value.yaml b/semantic-conventions/src/tests/data/yaml/errors/stability/wrong_value.yaml
new file mode 100644
index 00000000..0575474f
--- /dev/null
+++ b/semantic-conventions/src/tests/data/yaml/errors/stability/wrong_value.yaml
@@ -0,0 +1,12 @@
+groups:
+ - id: test
+ type: span
+ brief: 'test'
+ prefix: http
+ attributes:
+ - id: test_attr
+ type: boolean
+ required: always
+ stability: will_fail
+ brief: ""
+
diff --git a/semantic-conventions/src/tests/data/yaml/errors/wrong_double_type.yaml b/semantic-conventions/src/tests/data/yaml/errors/wrong_double_type.yaml
index 1660732f..2cd74fbf 100644
--- a/semantic-conventions/src/tests/data/yaml/errors/wrong_double_type.yaml
+++ b/semantic-conventions/src/tests/data/yaml/errors/wrong_double_type.yaml
@@ -8,4 +8,4 @@ groups:
- id: one
type: double
brief: it contains a float number.
- examples: [12, 1f, 1.0]
+ examples: [12, 1f, 1.0]
\ No newline at end of file
diff --git a/semantic-conventions/src/tests/data/yaml/links.yaml b/semantic-conventions/src/tests/data/yaml/links.yaml
new file mode 100644
index 00000000..2fc449e9
--- /dev/null
+++ b/semantic-conventions/src/tests/data/yaml/links.yaml
@@ -0,0 +1,22 @@
+groups:
+ - id: http
+ type: span
+ brief: 'test'
+ prefix: http
+ note: test
+ attributes:
+ - id: none
+ type: boolean
+ brief: 'simple text'
+ - id: single
+ type: boolean
+ brief: 'text [eg](https://opentelemetry.io/)'
+ - id: double
+ type: boolean
+ brief: 'text1 [eg](https://opentelemetry.io/) text2 [eg](https://opentelemetry.io/) end'
+ - id: start
+ type: boolean
+ brief: '[eg](https://opentelemetry.io/) text'
+ - id: multiple_end
+ type: boolean
+ brief: 'text1 [eg](https://opentelemetry.io/) text2 [eg](https://opentelemetry.io/)'
diff --git a/semantic-conventions/src/tests/data/yaml/numeric_attributes.yml b/semantic-conventions/src/tests/data/yaml/numeric_attributes.yml
index 7d60a33d..5752370d 100644
--- a/semantic-conventions/src/tests/data/yaml/numeric_attributes.yml
+++ b/semantic-conventions/src/tests/data/yaml/numeric_attributes.yml
@@ -18,4 +18,4 @@ groups:
brief: >
this is the description of
the second attribute. It's a number.
- examples: [12.01, 1.0]
+ examples: [12.01, 1.0]
\ No newline at end of file
diff --git a/semantic-conventions/src/tests/data/yaml/stability.yaml b/semantic-conventions/src/tests/data/yaml/stability.yaml
new file mode 100644
index 00000000..280e8c34
--- /dev/null
+++ b/semantic-conventions/src/tests/data/yaml/stability.yaml
@@ -0,0 +1,106 @@
+groups:
+ - id: test
+ type: span
+ brief: 'test'
+ prefix: http
+ attributes:
+ - id: exp_attr
+ type: boolean
+ required: always
+ stability: experimental
+ brief: ""
+ - id: stable_attr
+ type: boolean
+ required: always
+ stability: stable
+ brief: ""
+ - id: deprecated_attr
+ type: boolean
+ required: always
+ stability: deprecated
+ brief: ""
+ - id: def_stability
+ type: boolean
+ required: always
+ brief: ""
+
+ - id: parent_default
+ type: span
+ brief: 'test'
+ prefix: http
+ stability: experimental
+ attributes:
+ - id: test_attr
+ type: boolean
+ required: always
+ brief: ""
+ - id: dep
+ type: boolean
+ required: always
+ deprecated: should not fail.
+ brief: ""
+
+ - id: not_fail
+ type: span
+ brief: 'test'
+ prefix: http
+ stability: deprecated
+ attributes:
+ - id: test_attr
+ type: boolean
+ required: always
+ deprecated: should not fail.
+ brief: ""
+
+ - id: resource_test
+ type: resource
+ brief: 'test'
+ prefix: http
+ attributes:
+ - id: exp_attr
+ type: boolean
+ required: always
+ stability: experimental
+ brief: ""
+ - id: stable_attr
+ type: boolean
+ required: always
+ stability: stable
+ brief: ""
+ - id: deprecated_attr
+ type: boolean
+ required: always
+ stability: deprecated
+ brief: ""
+ - id: def_stability
+ type: boolean
+ required: always
+ brief: ""
+
+ - id: resource_parent_default
+ type: resource
+ brief: 'test'
+ prefix: http
+ stability: experimental
+ attributes:
+ - id: test_attr
+ type: boolean
+ required: always
+ brief: ""
+ - id: dep
+ type: boolean
+ required: always
+ deprecated: should not fail.
+ brief: ""
+
+ - id: resource_not_fail
+ type: resource
+ brief: 'test'
+ prefix: http
+ stability: deprecated
+ attributes:
+ - id: test_attr
+ type: boolean
+ required: always
+ deprecated: should not fail.
+ brief: ""
\ No newline at end of file
diff --git a/semantic-conventions/src/tests/semconv/model/test_correct_parse.py b/semantic-conventions/src/tests/semconv/model/test_correct_parse.py
index 576ba040..b906f1c4 100644
--- a/semantic-conventions/src/tests/semconv/model/test_correct_parse.py
+++ b/semantic-conventions/src/tests/semconv/model/test_correct_parse.py
@@ -16,7 +16,10 @@
import unittest
from opentelemetry.semconv.model.constraints import Include
-from opentelemetry.semconv.model.semantic_attribute import SemanticAttribute
+from opentelemetry.semconv.model.semantic_attribute import (
+ SemanticAttribute,
+ StabilityLevel,
+)
from opentelemetry.semconv.model.semantic_convention import (
parse_semantic_convention_groups,
SemanticConventionSet,
@@ -241,6 +244,16 @@ def test_rpc(self):
}
self.semantic_convention_check(list(semconv.models.values())[2], expected)
+ def test_markdown_link(self):
+ semconv = SemanticConventionSet(debug=False)
+ semconv.parse(self.load_file("yaml/links.yaml"))
+ semconv.finish()
+ self.assertEqual(len(semconv.models), 1)
+ s = list(semconv.models.values())[0]
+ for attr in s.attributes:
+ brief = attr.brief
+ self.assertEqual(brief.raw_text, brief.__str__())
+
# This fails until ONE-36916 is not addressed
def test_ref(self):
semconv = SemanticConventionSet(debug=False)
@@ -369,6 +382,52 @@ def test_deprecation(self):
)
self.assertIsNone(list(semconv.models.values())[0].attributes[3].deprecated)
+ def test_stability(self):
+ semconv = SemanticConventionSet(debug=False)
+ semconv.parse(self.load_file("yaml/stability.yaml"))
+ semconv.finish()
+ self.assertEqual(len(semconv.models), 6)
+
+ model = list(semconv.models.values())[0]
+ self.assertEqual(len(model.attributes), 4)
+ self.assertEqual(model.stability, None)
+
+ attr = model.attributes[0]
+ self.assertEqual(attr.attr_id, "exp_attr")
+ self.assertEqual(attr.stability, StabilityLevel.EXPERIMENTAL)
+
+ attr = model.attributes[1]
+ self.assertEqual(attr.attr_id, "stable_attr")
+ self.assertEqual(attr.stability, StabilityLevel.STABLE)
+
+ attr = model.attributes[2]
+ self.assertEqual(attr.attr_id, "deprecated_attr")
+ self.assertEqual(attr.stability, StabilityLevel.DEPRECATED)
+
+ attr = model.attributes[3]
+ self.assertEqual(attr.attr_id, "def_stability")
+ self.assertEqual(attr.stability, StabilityLevel.STABLE)
+
+ model = list(semconv.models.values())[1]
+ self.assertEqual(len(model.attributes), 2)
+ self.assertEqual(model.stability, StabilityLevel.EXPERIMENTAL)
+
+ attr = model.attributes[0]
+ self.assertEqual(attr.attr_id, "test_attr")
+ self.assertEqual(attr.stability, StabilityLevel.EXPERIMENTAL)
+
+ attr = model.attributes[1]
+ self.assertEqual(attr.attr_id, "dep")
+ self.assertEqual(attr.stability, StabilityLevel.DEPRECATED)
+
+ model = list(semconv.models.values())[2]
+ self.assertEqual(len(model.attributes), 1)
+ self.assertEqual(model.stability, StabilityLevel.DEPRECATED)
+
+ attr = model.attributes[0]
+ self.assertEqual(attr.attr_id, "test_attr")
+ self.assertEqual(attr.stability, StabilityLevel.DEPRECATED)
+
def test_populate_other_attributes(self):
semconv = SemanticConventionSet(debug=False)
semconv.parse(self.load_file("yaml/http.yaml"))
diff --git a/semantic-conventions/src/tests/semconv/model/test_error_detection.py b/semantic-conventions/src/tests/semconv/model/test_error_detection.py
index bae466e6..36607707 100644
--- a/semantic-conventions/src/tests/semconv/model/test_error_detection.py
+++ b/semantic-conventions/src/tests/semconv/model/test_error_detection.py
@@ -130,6 +130,35 @@ def test_invalid_key_in_constraint(self):
self.assertIn("myNewCnst", msg)
self.assertEqual(e.line, 10)
+ def test_invalid_stability(self):
+ with self.assertRaises(ValidationError) as ex:
+ self.open_yaml("yaml/errors/stability/wrong_value.yaml")
+ self.fail()
+ e = ex.exception
+ msg = e.message.lower()
+ self.assertIn("is not allowed as a stability marker", msg)
+ self.assertEqual(e.line, 10)
+
+ def test_invalid_stability_with_deprecated(self):
+ with self.assertRaises(ValidationError) as ex:
+ self.open_yaml("yaml/errors/stability/stability_deprecated.yaml")
+ self.fail()
+ e = ex.exception
+ msg = e.message.lower()
+ self.assertIn("there is a deprecation message but the stability is set to", msg)
+ self.assertEqual(e.line, 11)
+
+ def test_invalid_semconv_stability_with_deprecated(self):
+ with self.assertRaises(ValidationError) as ex:
+ self.open_yaml("yaml/errors/stability/semconv_stability_deprecated.yaml")
+ self.fail()
+ e = ex.exception
+ msg = e.message.lower()
+ self.assertIn(
+ "semantic convention stability set to deprecated but attribute", msg
+ )
+ self.assertEqual(e.line, 11)
+
def test_invalid_deprecated_empty_string(self):
with self.assertRaises(ValidationError) as ex:
self.open_yaml("yaml/errors/deprecated/deprecation_empty_string.yaml")
@@ -137,7 +166,7 @@ def test_invalid_deprecated_empty_string(self):
e = ex.exception
msg = e.message.lower()
self.assertIn(
- "a string that specify why the attribute is deprecated and/or what to use instead!",
+ "a string that specifies why the attribute is deprecated and/or what to use instead!",
msg,
)
self.assertEqual(e.line, 10)
@@ -149,7 +178,7 @@ def test_invalid_deprecated_boolean(self):
e = ex.exception
msg = e.message.lower()
self.assertIn(
- "a string that specify why the attribute is deprecated and/or what to use instead!",
+ "a string that specifies why the attribute is deprecated and/or what to use instead!",
msg,
)
self.assertEqual(e.line, 10)
@@ -161,7 +190,7 @@ def test_invalid_deprecated_number(self):
e = ex.exception
msg = e.message.lower()
self.assertIn(
- "a string that specify why the attribute is deprecated and/or what to use instead!",
+ "a string that specifies why the attribute is deprecated and/or what to use instead!",
msg,
)
self.assertEqual(e.line, 10)
diff --git a/semantic-conventions/src/tests/semconv/model/test_semantic_attribute.py b/semantic-conventions/src/tests/semconv/model/test_semantic_attribute.py
index a5dcb34a..c493127d 100644
--- a/semantic-conventions/src/tests/semconv/model/test_semantic_attribute.py
+++ b/semantic-conventions/src/tests/semconv/model/test_semantic_attribute.py
@@ -17,7 +17,7 @@
def test_parse(load_yaml):
yaml = load_yaml("semantic_attributes.yml")
- attributes = SemanticAttribute.parse("prefix", yaml.get("attributes"))
+ attributes = SemanticAttribute.parse("prefix", "", yaml.get("attributes"))
assert len(attributes) == 3
@@ -39,7 +39,7 @@ def test_parse(load_yaml):
def test_parse_deprecated(load_yaml):
yaml = load_yaml("semantic_attributes_deprecated.yml")
- attributes = SemanticAttribute.parse("", yaml.get("attributes"))
+ attributes = SemanticAttribute.parse("", "", yaml.get("attributes"))
assert len(attributes) == 1
assert list(attributes.keys()) == [".deprecated_attribute"]
diff --git a/semantic-conventions/src/tests/semconv/templating/test_markdown.py b/semantic-conventions/src/tests/semconv/templating/test_markdown.py
index ea897387..d4f47065 100644
--- a/semantic-conventions/src/tests/semconv/templating/test_markdown.py
+++ b/semantic-conventions/src/tests/semconv/templating/test_markdown.py
@@ -17,6 +17,7 @@
import unittest
from opentelemetry.semconv.model.semantic_convention import SemanticConventionSet
+from opentelemetry.semconv.templating.markdown.options import MarkdownOptions
from opentelemetry.semconv.templating.markdown import MarkdownRenderer
@@ -74,6 +75,40 @@ def testDeprecated(self):
expected,
)
+ def testStability(self):
+ semconv = SemanticConventionSet(debug=False)
+ semconv.parse(self.load_file("markdown/stability/stability.yaml"))
+ semconv.finish()
+ self.assertEqual(len(semconv.models), 1)
+ with open(self.load_file("markdown/stability/input.md"), "r") as markdown:
+ content = markdown.read()
+ # Labels
+ with open(
+ self.load_file("markdown/stability/labels_expected.md"), "r"
+ ) as markdown:
+ expected = markdown.read()
+ self.check_render(
+ semconv,
+ "markdown/stability/",
+ "markdown/stability/input.md",
+ content,
+ expected,
+ )
+ # Badges
+ with open(
+ self.load_file("markdown/stability/badges_expected.md"), "r"
+ ) as markdown:
+ expected = markdown.read()
+ options = MarkdownOptions(enable_stable=True, use_badge=True)
+ self.check_render(
+ semconv,
+ "markdown/stability/",
+ "markdown/stability/input.md",
+ content,
+ expected,
+ options,
+ )
+
def testSingle(self):
semconv = SemanticConventionSet(debug=False)
semconv.parse(self.load_file("markdown/single/http.yaml"))
@@ -85,11 +120,7 @@ def testSingle(self):
with open(self.load_file("markdown/single/expected.md"), "r") as markdown:
expected = markdown.read()
self.check_render(
- semconv,
- "markdown/single/",
- "markdown/single/input.md",
- content,
- expected,
+ semconv, "markdown/single/", "markdown/single/input.md", content, expected,
)
def testEmpty(self):
@@ -113,10 +144,16 @@ def testExampleArray(self):
self.assertEqual(len(semconv.models), 1)
with open(self.load_file("markdown/example_array/input.md"), "r") as markdown:
content = markdown.read()
- with open(self.load_file("markdown/example_array/expected.md"), "r") as markdown:
+ with open(
+ self.load_file("markdown/example_array/expected.md"), "r"
+ ) as markdown:
expected = markdown.read()
self.check_render(
- semconv, "markdown/example_array/", "markdown/example_array/input.md", content, expected
+ semconv,
+ "markdown/example_array/",
+ "markdown/example_array/input.md",
+ content,
+ expected,
)
def testMultiple(self):
@@ -160,9 +197,13 @@ def testExtendConstraint(self):
semconv.parse(self.load_file("markdown/extend_constraint/general.yaml"))
semconv.finish()
self.assertEqual(len(semconv.models), 7)
- with open(self.load_file("markdown/extend_constraint/input.md"), "r") as markdown:
+ with open(
+ self.load_file("markdown/extend_constraint/input.md"), "r"
+ ) as markdown:
content = markdown.read()
- with open(self.load_file("markdown/extend_constraint/expected.md"), "r") as markdown:
+ with open(
+ self.load_file("markdown/extend_constraint/expected.md"), "r"
+ ) as markdown:
expected = markdown.read()
self.check_render(
semconv,
@@ -181,7 +222,9 @@ def test_error_missing_end(self):
with open(self.load_file("markdown/missing_end_tag/input.md"), "r") as markdown:
content = markdown.read()
with self.assertRaises(Exception) as ex:
- renderer = MarkdownRenderer(self.load_file("markdown/missing_end_tag/"), semconv)
+ renderer = MarkdownRenderer(
+ self.load_file("markdown/missing_end_tag/"), semconv
+ )
renderer._render_single_file(
content, "markdown/missing_end_tag/input.md", io.StringIO()
)
@@ -193,10 +236,14 @@ def test_error_wrong_id(self):
semconv.parse(self.load_file("markdown/wrong_semconv_id/general.yaml"))
semconv.finish()
self.assertEqual(len(semconv.models), 5)
- with open(self.load_file("markdown/wrong_semconv_id/input.md"), "r") as markdown:
+ with open(
+ self.load_file("markdown/wrong_semconv_id/input.md"), "r"
+ ) as markdown:
content = markdown.read()
with self.assertRaises(Exception) as ex:
- renderer = MarkdownRenderer(self.load_file("markdown/wrong_semconv_id/"), semconv)
+ renderer = MarkdownRenderer(
+ self.load_file("markdown/wrong_semconv_id/"), semconv
+ )
renderer._render_single_file(
content, "markdown/wrong_semconv_id/input.md", io.StringIO()
)
@@ -230,7 +277,9 @@ def test_parameter_full(self):
self.assertEqual(len(semconv.models), 7)
with open(self.load_file("markdown/parameter_full/input.md"), "r") as markdown:
content = markdown.read()
- with open(self.load_file("markdown/parameter_full/expected.md"), "r") as markdown:
+ with open(
+ self.load_file("markdown/parameter_full/expected.md"), "r"
+ ) as markdown:
expected = markdown.read()
self.check_render(
semconv,
@@ -248,7 +297,9 @@ def test_parameter_tag(self):
self.assertEqual(len(semconv.models), 6)
with open(self.load_file("markdown/parameter_tag/input.md"), "r") as markdown:
content = markdown.read()
- with open(self.load_file("markdown/parameter_tag/expected.md"), "r") as markdown:
+ with open(
+ self.load_file("markdown/parameter_tag/expected.md"), "r"
+ ) as markdown:
expected = markdown.read()
self.check_render(
semconv,
@@ -264,9 +315,13 @@ def test_parameter_tag_empty(self):
semconv.parse(self.load_file("markdown/parameter_tag_empty/general.yaml"))
semconv.finish()
self.assertEqual(len(semconv.models), 6)
- with open(self.load_file("markdown/parameter_tag_empty/input.md"), "r") as markdown:
+ with open(
+ self.load_file("markdown/parameter_tag_empty/input.md"), "r"
+ ) as markdown:
content = markdown.read()
- with open(self.load_file("markdown/parameter_tag_empty/expected.md"), "r") as markdown:
+ with open(
+ self.load_file("markdown/parameter_tag_empty/expected.md"), "r"
+ ) as markdown:
expected = markdown.read()
self.check_render(
semconv,
@@ -303,11 +358,17 @@ def test_parameter_tag_no_attr(self):
def test_parameter_remove_constraint(self):
semconv = SemanticConventionSet(debug=False)
- semconv.parse(self.load_file("markdown/parameter_remove_constraint/database.yaml"))
- semconv.parse(self.load_file("markdown/parameter_remove_constraint/general.yaml"))
+ semconv.parse(
+ self.load_file("markdown/parameter_remove_constraint/database.yaml")
+ )
+ semconv.parse(
+ self.load_file("markdown/parameter_remove_constraint/general.yaml")
+ )
semconv.finish()
self.assertEqual(len(semconv.models), 6)
- with open(self.load_file("markdown/parameter_remove_constraint/input.md"), "r") as markdown:
+ with open(
+ self.load_file("markdown/parameter_remove_constraint/input.md"), "r"
+ ) as markdown:
content = markdown.read()
with open(
self.load_file("markdown/parameter_remove_constraint/expected.md"), "r"
@@ -330,7 +391,9 @@ def test_parameter_empty(self):
self.assertEqual(len(semconv.models), 7)
with open(self.load_file("markdown/parameter_empty/input.md"), "r") as markdown:
content = markdown.read()
- with open(self.load_file("markdown/parameter_empty/expected.md"), "r") as markdown:
+ with open(
+ self.load_file("markdown/parameter_empty/expected.md"), "r"
+ ) as markdown:
expected = markdown.read()
self.check_render(
semconv,
@@ -371,7 +434,9 @@ def test_wrong_syntax(self):
semconv.parse(self.load_file("markdown/parameter_wrong_syntax/general.yaml"))
semconv.finish()
self.assertEqual(len(semconv.models), 7)
- with open(self.load_file("markdown/parameter_wrong_syntax/input.md"), "r") as markdown:
+ with open(
+ self.load_file("markdown/parameter_wrong_syntax/input.md"), "r"
+ ) as markdown:
content = markdown.read()
expected = ""
with self.assertRaises(ValueError) as ex:
@@ -394,7 +459,9 @@ def test_wrong_duplicate(self):
semconv.parse(self.load_file("markdown/parameter_wrong_duplicate/general.yaml"))
semconv.finish()
self.assertEqual(len(semconv.models), 7)
- with open(self.load_file("markdown/parameter_wrong_duplicate/input.md"), "r") as markdown:
+ with open(
+ self.load_file("markdown/parameter_wrong_duplicate/input.md"), "r"
+ ) as markdown:
content = markdown.read()
expected = ""
with self.assertRaises(ValueError) as ex:
@@ -425,11 +492,19 @@ def test_units(self):
"markdown/metrics/",
"markdown/metrics/units_input.md",
content,
- expected
- )
+ expected,
+ )
- def check_render(self, semconv, folder, file_name, content: str, expected: str):
- renderer = MarkdownRenderer(self.load_file(folder), semconv)
+ def check_render(
+ self,
+ semconv,
+ folder,
+ file_name,
+ content: str,
+ expected: str,
+ options=MarkdownOptions(),
+ ):
+ renderer = MarkdownRenderer(self.load_file(folder), semconv, options)
output = io.StringIO()
renderer._render_single_file(content, self.load_file(file_name), output)
result = output.getvalue()
@@ -439,7 +514,7 @@ def check_render(self, semconv, folder, file_name, content: str, expected: str):
_TEST_DIR = os.path.dirname(__file__)
def read_file(self, filename):
- with open(self.load_file(filename), 'r') as test_file:
+ with open(self.load_file(filename), "r") as test_file:
return test_file.read()
def load_file(self, filename):