From 6bdca2f2a3981e201dbd56709bee768b1e9b8ba7 Mon Sep 17 00:00:00 2001 From: Liudmila Molkova Date: Fri, 1 Dec 2023 15:29:26 -0800 Subject: [PATCH 01/18] up --- .../semconvgen-0.8.0-py3-none-any.whl | Bin 0 -> 47551 bytes .../src/opentelemetry/semconv/main.py | 2 +- .../opentelemetry/semconv/templating/code.py | 75 +++++++++++++++++- .../jinja/attributesv2/FirstAttributes.java | 18 +++++ .../jinja/attributesv2/FooAttributes.java | 13 +++ .../jinja/attributesv2/OtherAttributes.java | 8 ++ .../jinja/attributesv2/SecondAttributes.java | 8 ++ .../jinja/attributesv2/ThirdAttributes.java | 13 +++ .../data/jinja/attributesv2/attributes.yml | 57 +++++++++++++ .../attributes_no_group_prefix.yml | 20 +++++ .../tests/data/jinja/attributesv2/template | 74 +++++++++++++++++ .../src/tests/semconv/templating/test_code.py | 71 +++++++++++++++++ 12 files changed, 357 insertions(+), 2 deletions(-) create mode 100644 semantic-conventions/semconvgen-0.8.0-py3-none-any.whl create mode 100644 semantic-conventions/src/tests/data/jinja/attributesv2/FirstAttributes.java create mode 100644 semantic-conventions/src/tests/data/jinja/attributesv2/FooAttributes.java create mode 100644 semantic-conventions/src/tests/data/jinja/attributesv2/OtherAttributes.java create mode 100644 semantic-conventions/src/tests/data/jinja/attributesv2/SecondAttributes.java create mode 100644 semantic-conventions/src/tests/data/jinja/attributesv2/ThirdAttributes.java create mode 100644 semantic-conventions/src/tests/data/jinja/attributesv2/attributes.yml create mode 100644 semantic-conventions/src/tests/data/jinja/attributesv2/attributes_no_group_prefix.yml create mode 100644 semantic-conventions/src/tests/data/jinja/attributesv2/template diff --git a/semantic-conventions/semconvgen-0.8.0-py3-none-any.whl b/semantic-conventions/semconvgen-0.8.0-py3-none-any.whl new file mode 100644 index 0000000000000000000000000000000000000000..edfef77d3d76f46ebfae564e2be3cb3e9b0c4143 GIT binary patch literal 47551 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 RQ71_Nk_6nG!WTdO_kR^eczFN- literal 0 HcmV?d00001 diff --git a/semantic-conventions/src/opentelemetry/semconv/main.py b/semantic-conventions/src/opentelemetry/semconv/main.py index ce144d94..55f6c23f 100644 --- a/semantic-conventions/src/opentelemetry/semconv/main.py +++ b/semantic-conventions/src/opentelemetry/semconv/main.py @@ -73,7 +73,7 @@ def main(): renderer = CodeRenderer.from_commandline_params( args.parameters, args.trim_whitespace ) - renderer.render(semconv, args.template, args.output, args.pattern) + renderer.render2(semconv, args.template, args.output) elif args.flavor == "markdown": process_markdown(semconv, args) diff --git a/semantic-conventions/src/opentelemetry/semconv/templating/code.py b/semantic-conventions/src/opentelemetry/semconv/templating/code.py index b84a39fd..91c5a25e 100644 --- a/semantic-conventions/src/opentelemetry/semconv/templating/code.py +++ b/semantic-conventions/src/opentelemetry/semconv/templating/code.py @@ -22,6 +22,8 @@ from opentelemetry.semconv.model.semantic_attribute import ( RequirementLevel, + SemanticAttribute, + StabilityLevel, TextWithLinks, ) from opentelemetry.semconv.model.semantic_convention import SemanticConventionSet @@ -144,7 +146,6 @@ def regex_replace(text: str, pattern: str, replace: str): def merge(elems: typing.List, elm): return elems.extend(elm) - def to_const_name(name: str) -> str: return name.upper().replace(".", "_").replace("-", "_") @@ -155,6 +156,11 @@ def to_camelcase(name: str, first_upper=False) -> str: first = first.capitalize() return first + "".join(word.capitalize() for word in rest) +def is_stable(attribute: SemanticAttribute) -> bool: + return attribute.stability == StabilityLevel.STABLE + +def is_definition(attribute: SemanticAttribute) -> bool: + return attribute.ref is None class CodeRenderer: pattern = f"{{{ID_RE.pattern}}}" @@ -209,6 +215,8 @@ def setup_environment(env: Environment, trim_whitespace: bool): env.filters["to_html_links"] = to_html_links env.filters["regex_replace"] = regex_replace env.filters["render_markdown"] = render_markdown + env.filters["is_stable"] = is_stable + env.filters["is_definition"] = is_definition env.trim_blocks = trim_whitespace env.lstrip_blocks = trim_whitespace @@ -219,6 +227,12 @@ def prefix_output_file(file_name, pattern, semconv): value = getattr(semconv, pattern) return os.path.join(dirname, to_camelcase(value, True), basename) + @staticmethod + def prefix_output_filev2(file_name, prefix): + basename = os.path.basename(file_name) + dirname = os.path.dirname(file_name) + return os.path.join(dirname, to_camelcase(prefix, True) + basename) + def render( self, semconvset: SemanticConventionSet, @@ -249,3 +263,62 @@ def render( template.globals["version"] = os.environ.get("ARTIFACT_VERSION", "dev") template.globals["RequirementLevel"] = RequirementLevel template.stream(data).dump(output_file) + + def renderv2( + self, + semconvset: SemanticConventionSet, + template_path: str, + output_file, + stable: bool + ): + file_name = os.path.basename(template_path) + folder = os.path.dirname(template_path) + env = Environment( + loader=FileSystemLoader(searchpath=folder), + autoescape=select_autoescape([""]), + ) + self.setup_environment(env, self.trim_whitespace) + + for group in self._get_all_groups(semconvset, stable): + output_name = self.prefix_output_filev2(str(output_file), group) + + data = { + "template": template_path, + "attributes": self._filter_attributes(semconvset, group, stable), + "attribute_templates": self._filter_attribute_templates(semconvset, group, stable), + "group": group} + data.update(self.parameters) + + template = env.get_template(file_name, globals=data) + template.globals["now"] = datetime.datetime.utcnow() + template.globals["version"] = os.environ.get("ARTIFACT_VERSION", "dev") + template.globals["RequirementLevel"] = RequirementLevel + template.stream(data).dump(output_name) + + def _get_root_namespace(self, attr): + namespaces = attr.fqn.split(".") + return namespaces[0] if len(namespaces) > 1 else "other" + + def _get_all_groups(self, semconvset, stable): + groups = set() + for semconv in semconvset.models.values(): + for attr in filter(lambda a: self._filter(a, stable), semconv.attributes_and_templates): + groups.add(self._get_root_namespace(attr)) + return groups + + def _filter(self, attr, stable): + return stable == is_stable(attr) and attr.is_local and attr.ref is None + + def _filter_attributes(self, semconvset, group, stable): + attr_in_group = [] + for semconv in semconvset.models.values(): + for attr in filter(lambda a: self._filter(a, stable) and self._get_root_namespace(a) == group, semconv.attributes): + attr_in_group.append(attr) + return attr_in_group + + def _filter_attribute_templates(self, semconvset, group, stable): + templates_in_group = [] + for semconv in semconvset.models.values(): + for attr_template in filter(lambda a: self._filter(a, stable) and self._get_root_namespace(a) == group, semconv.attribute_templates): + templates_in_group.append(attr_template) + return templates_in_group \ No newline at end of file diff --git a/semantic-conventions/src/tests/data/jinja/attributesv2/FirstAttributes.java b/semantic-conventions/src/tests/data/jinja/attributesv2/FirstAttributes.java new file mode 100644 index 00000000..f5779efa --- /dev/null +++ b/semantic-conventions/src/tests/data/jinja/attributesv2/FirstAttributes.java @@ -0,0 +1,18 @@ +package io.opentelemetry.instrumentation.api.semconv; + +class FirstAttributes { + /** + * short description of attr_one + */ + public static final AttributeKey FIRST_ATTR_ONE = booleanKey("first.attr_one"); + + /** + * short description of attr_one_a + */ + public static final AttributeKey FIRST_ATTR_ONE_A = longKey("first.attr_one_a"); + + /** + * this is the description of attribute template + */ + public static final AttributeKey FIRST_ATTR_TEMPLATE_ONE = stringKey("first.attr_template_one"); +} \ No newline at end of file diff --git a/semantic-conventions/src/tests/data/jinja/attributesv2/FooAttributes.java b/semantic-conventions/src/tests/data/jinja/attributesv2/FooAttributes.java new file mode 100644 index 00000000..b09c7dc5 --- /dev/null +++ b/semantic-conventions/src/tests/data/jinja/attributesv2/FooAttributes.java @@ -0,0 +1,13 @@ +package io.opentelemetry.instrumentation.api.semconv; + +class FooAttributes { + /** + * short description of attr_one + */ + public static final AttributeKey FOO_ATTR_ONE = booleanKey("foo.attr_one"); + + /** + * short description of foo.attr_two + */ + public static final AttributeKey FOO_ATTR_TWO = stringKey("foo.attr_two"); +} \ No newline at end of file diff --git a/semantic-conventions/src/tests/data/jinja/attributesv2/OtherAttributes.java b/semantic-conventions/src/tests/data/jinja/attributesv2/OtherAttributes.java new file mode 100644 index 00000000..9a7b38b2 --- /dev/null +++ b/semantic-conventions/src/tests/data/jinja/attributesv2/OtherAttributes.java @@ -0,0 +1,8 @@ +package io.opentelemetry.instrumentation.api.semconv; + +class OtherAttributes { + /** + * short description of bar_attr + */ + public static final AttributeKey BAR_ATTR = stringKey("bar_attr"); +} \ No newline at end of file diff --git a/semantic-conventions/src/tests/data/jinja/attributesv2/SecondAttributes.java b/semantic-conventions/src/tests/data/jinja/attributesv2/SecondAttributes.java new file mode 100644 index 00000000..abc59440 --- /dev/null +++ b/semantic-conventions/src/tests/data/jinja/attributesv2/SecondAttributes.java @@ -0,0 +1,8 @@ +package io.opentelemetry.instrumentation.api.semconv; + +class SecondAttributes { + /** + * short description of attr_two + */ + public static final AttributeKey SECOND_ATTR_TWO = stringKey("second.attr_two"); +} \ No newline at end of file diff --git a/semantic-conventions/src/tests/data/jinja/attributesv2/ThirdAttributes.java b/semantic-conventions/src/tests/data/jinja/attributesv2/ThirdAttributes.java new file mode 100644 index 00000000..3aa18f32 --- /dev/null +++ b/semantic-conventions/src/tests/data/jinja/attributesv2/ThirdAttributes.java @@ -0,0 +1,13 @@ +package io.opentelemetry.instrumentation.api.semconv; + +class ThirdAttributes { + /** + * short description of attr_three + */ + public static final AttributeKey THIRD_ATTR_THREE = stringKey("third.attr_three"); + + /** + * this is the description of attribute template + */ + public static final AttributeKey THIRD_ATTR_TEMPLATE_THREE = stringKey("third.attr_template_three"); +} \ No newline at end of file diff --git a/semantic-conventions/src/tests/data/jinja/attributesv2/attributes.yml b/semantic-conventions/src/tests/data/jinja/attributesv2/attributes.yml new file mode 100644 index 00000000..b5401c47 --- /dev/null +++ b/semantic-conventions/src/tests/data/jinja/attributesv2/attributes.yml @@ -0,0 +1,57 @@ +groups: + - id: first_group_id + type: attribute_group + brief: first description + prefix: first + attributes: + - id: attr_one + type: boolean + brief: short description of attr_one + - id: attr_template_one + type: template[string] + brief: > + this is the description of attribute template + examples: 'example' + + - id: second_group_id + brief: second description + prefix: second + span_kind: client + extends: first_group_id + attributes: + - id: attr_two + type: string + brief: short description of attr_two + examples: ['example_one', 'example_two'] + - id: first_group_part_two + type: resource + brief: first_a description + prefix: first + attributes: + - id: attr_one_a + type: int + brief: short description of attr_one_a + - ref: second.attr_two + - ref: third.attr_template_three + - id: third_group_id + brief: third description + prefix: third + attributes: + - id: attr_three + type: string + brief: short description of attr_three + examples: "3" + stability: stable + - id: attr_template_three + type: template[string] + brief: > + this is the description of attribute template + examples: 'example' + stability: stable + - id: forth_group_id + brief: forth description + attributes: + - id: attr_four + type: string + brief: short description of attr_four + examples: "4" diff --git a/semantic-conventions/src/tests/data/jinja/attributesv2/attributes_no_group_prefix.yml b/semantic-conventions/src/tests/data/jinja/attributesv2/attributes_no_group_prefix.yml new file mode 100644 index 00000000..d2dcb72e --- /dev/null +++ b/semantic-conventions/src/tests/data/jinja/attributesv2/attributes_no_group_prefix.yml @@ -0,0 +1,20 @@ +groups: + - id: group_with_prefix + type: attribute_group + brief: description + prefix: foo + attributes: + - id: attr_one + type: boolean + brief: short description of attr_one + - id: group_with_no_prefix + brief: description + attributes: + - id: foo.attr_two + type: string + brief: short description of foo.attr_two + examples: "foo" + - id: bar_attr + type: string + brief: short description of bar_attr + examples: "bar" \ No newline at end of file diff --git a/semantic-conventions/src/tests/data/jinja/attributesv2/template b/semantic-conventions/src/tests/data/jinja/attributesv2/template new file mode 100644 index 00000000..e7326518 --- /dev/null +++ b/semantic-conventions/src/tests/data/jinja/attributesv2/template @@ -0,0 +1,74 @@ +{%- macro to_java_return_type(type) -%} + {%- if type == "string" -%} + String + {%- elif type == "string[]" -%} + List + {%- elif type == "boolean" -%} + boolean + {%- elif type == "int" -%} + long + {%- elif type == "double" -%} + double + {%- else -%} + {{type}} + {%- endif -%} +{%- endmacro %} +{%- macro to_java_key_type(type) -%} + {%- if type == "string" -%} + stringKey + {%- elif type == "string[]" -%} + stringArrayKey + {%- elif type == "boolean" -%} + booleanKey + {%- elif type == "int" -%} + longKey + {%- elif type == "double" -%} + doubleKey + {%- else -%} + {{lowerFirst(type)}}Key + {%- endif -%} +{%- endmacro %} +{%- macro print_value(type, value) -%} + {{ "\"" if type == "String"}}{{value}}{{ "\"" if type == "String"}} +{%- endmacro %} +{%- macro upFirst(text) -%} + {{ text[0]|upper}}{{text[1:] }} +{%- endmacro %} +{%- macro lowerFirst(text) -%} + {{ text[0]|lower}}{{text[1:] }} +{%- endmacro %} +package io.opentelemetry.instrumentation.api.semconv; + +class {{ group | to_camelcase(True) }}Attributes { +{%- for attribute in attributes %} + + /** + * {{attribute.brief | render_markdown(code="{{@code {0}}}", paragraph="{0}")}} + {%- if attribute.note %} + * + *

Notes: +

    {{attribute.note | render_markdown(code="{{@code {0}}}", paragraph="
  • {0}
  • ", list="{0}")}}
+ {%- endif %} + {%- if (attribute.stability | string()) == "StabilityLevel.DEPRECATED" %} + * + {%- endif %} + + */ + public static final AttributeKey<{{upFirst(to_java_return_type(attribute.instantiated_type | string))}}> {{attribute.fqn | to_const_name}} = {{to_java_key_type(attribute.instantiated_type | string)}}("{{attribute.fqn}}"); +{# Extra line #} +{%- endfor %} +{%- for attribute_template in attribute_templates %} + + /** + * {{attribute_template.brief | render_markdown(code="{{@code {0}}}", paragraph="{0}")}} + {%- if attribute_template.note %} + * + *

Notes: +

    {{attribute_template.note | render_markdown(code="{{@code {0}}}", paragraph="
  • {0}
  • ", list="{0}")}}
+ {%- endif %} + + */ + public static final AttributeKey<{{upFirst(to_java_return_type(attribute_template.instantiated_type | string))}}> {{attribute_template.fqn | to_const_name}} = {{to_java_key_type(attribute_template.instantiated_type | string)}}("{{attribute_template.fqn}}"); +{# Extra line #} +{%- endfor %} +} \ No newline at end of file diff --git a/semantic-conventions/src/tests/semconv/templating/test_code.py b/semantic-conventions/src/tests/semconv/templating/test_code.py index 643505c9..40ee889e 100644 --- a/semantic-conventions/src/tests/semconv/templating/test_code.py +++ b/semantic-conventions/src/tests/semconv/templating/test_code.py @@ -1,4 +1,6 @@ import io +import os +import tempfile from opentelemetry.semconv.model.semantic_convention import SemanticConventionSet from opentelemetry.semconv.templating.code import CodeRenderer @@ -60,3 +62,72 @@ def test_codegen_attribute_templates(test_file_path, read_test_file): expected = read_test_file("jinja", "attribute_templates", "expected.java") assert result == expected + + +def test_codegen_attribute_v2_experimental(test_file_path, read_test_file): + semconv = SemanticConventionSet(debug=False) + semconv.parse( + test_file_path("jinja", "attributesv2", "attributes.yml") + ) + semconv.finish() + + template_path = test_file_path("jinja", "attributesv2", "template") + renderer = CodeRenderer({}, trim_whitespace=True) + + tmppath = tempfile.mkdtemp() + renderer.renderv2(semconv, template_path, os.path.join(tmppath, "Attributes.java"), stable=False) + with open(os.path.join(tmppath, "FirstAttributes.java")) as first: + data = first.read() + print(data) + expected = read_test_file("jinja", "attributesv2", "FirstAttributes.java") + assert data == expected + + with open(os.path.join(tmppath, "SecondAttributes.java")) as second: + data = second.read() + expected = read_test_file("jinja", "attributesv2", "SecondAttributes.java") + assert data == expected + + assert not os.path.isfile(os.path.join(tmppath, "ThirdAttributes.java")) + +def test_codegen_attribute_v2_stable(test_file_path, read_test_file): + semconv = SemanticConventionSet(debug=False) + semconv.parse( + test_file_path("jinja", "attributesv2", "attributes.yml") + ) + semconv.finish() + + template_path = test_file_path("jinja", "attributesv2", "template") + renderer = CodeRenderer({}, trim_whitespace=True) + + tmppath = tempfile.mkdtemp() + renderer.renderv2(semconv, template_path, os.path.join(tmppath, "Attributes.java"), stable=True) + + with open(os.path.join(tmppath, "ThirdAttributes.java")) as second: + data = second.read() + expected = read_test_file("jinja", "attributesv2", "ThirdAttributes.java") + assert data == expected + + assert not os.path.isfile(os.path.join(tmppath, "FirstAttributes.java")) + assert not os.path.isfile(os.path.join(tmppath, "SecondAttributes.java")) + +def test_codegen_attribute_v2_no_group_prefix(test_file_path, read_test_file): + semconv = SemanticConventionSet(debug=False) + semconv.parse( + test_file_path("jinja", "attributesv2", "attributes_no_group_prefix.yml") + ) + semconv.finish() + + template_path = test_file_path("jinja", "attributesv2", "template") + renderer = CodeRenderer({}, trim_whitespace=True) + + tmppath = tempfile.mkdtemp() + renderer.renderv2(semconv, template_path, os.path.join(tmppath, "Attributes.java"), stable=False) + with open(os.path.join(tmppath, "FooAttributes.java")) as foo: + data = foo.read() + expected = read_test_file("jinja", "attributesv2", "FooAttributes.java") + assert data == expected + + with open(os.path.join(tmppath, "OtherAttributes.java")) as other: + data = other.read() + expected = read_test_file("jinja", "attributesv2", "OtherAttributes.java") + assert data == expected From 4a7bee5b2aceb24450f43a73f9dd50081d688c82 Mon Sep 17 00:00:00 2001 From: Liudmila Molkova Date: Fri, 1 Dec 2023 18:34:15 -0800 Subject: [PATCH 02/18] up --- .../semconvgen-0.8.0-py3-none-any.whl | Bin 47551 -> 48778 bytes .../src/opentelemetry/semconv/main.py | 40 +++++++++- .../opentelemetry/semconv/templating/code.py | 42 ++++++---- .../data/jinja/attribute_templates/template | 27 +++---- ...ttributes.java => ThirdAttributesAll.java} | 0 .../attributesv2/ThirdAttributesStable.java | 8 ++ .../data/jinja/attributesv2/attributes.yml | 1 - .../tests/data/jinja/attributesv2/template | 74 ------------------ .../data/jinja/attributesv2/template_all | 50 ++++++++++++ .../jinja/attributesv2/template_only_stable | 55 +++++++++++++ .../src/tests/semconv/templating/test_code.py | 21 ++--- 11 files changed, 199 insertions(+), 119 deletions(-) rename semantic-conventions/src/tests/data/jinja/attributesv2/{ThirdAttributes.java => ThirdAttributesAll.java} (100%) create mode 100644 semantic-conventions/src/tests/data/jinja/attributesv2/ThirdAttributesStable.java delete mode 100644 semantic-conventions/src/tests/data/jinja/attributesv2/template create mode 100644 semantic-conventions/src/tests/data/jinja/attributesv2/template_all create mode 100644 semantic-conventions/src/tests/data/jinja/attributesv2/template_only_stable diff --git a/semantic-conventions/semconvgen-0.8.0-py3-none-any.whl b/semantic-conventions/semconvgen-0.8.0-py3-none-any.whl index edfef77d3d76f46ebfae564e2be3cb3e9b0c4143..6d012b3620ea785b0d6ddcd00ab9e78072f90063 100644 GIT binary patch delta 35458 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≀o2 z+v}@^0d@Ue{gA9S@O|1%s67N{Q6DUlchLP@9rQ_f!i<1v{-aIIEdXYa5kM1 zyB}7IEz3O|RdnAU@w056C7Af3cmQVDC?eUmF3u zB)<4@+}>-;Mx62E$Ws{N3g^L|L9m@*K4%N3eb~X?6}-VtVn> zQrUAuMZb(WgmiuYM6~WPahko~6aKkpaFF-$q%Mhl7s_E!pSwW}0!tkh0+0@rEF+w3 zP*TYm5908AQ@Cw!jR8LtNL(0NbKsWfjHm#> zY#ocl5Y7#7z#<`c!*7<|C~A)71m^7IReq`oHV@sM#q*t5+v@6(h3ZYvACcD!bt=PX zxIw}SaD1X|1_by(O#GG>29qCft7At$%HPsDDEVTU*!azOwOk%Lp3RfI(}GGWgq%z< ziR<%Rx78f6pzs=%=k`luopLZB8hAm7La(5w$bLvMN~=nC5rx9iRarFN)cKvBOn z+}M#h7u&hC4hbS$!_r^M6{{-=4sM0GD%Qx%BhJnm1t9uPX$mASrBDEpnYYeStTj$B z4BG7LaC>&PRUvI|91|14A&mToO)Zmt(8ujv?+`ykwx|$$TN<$%y&I0Y|K-d`j+&dsGmiyeaYLj2vFDW%)S# z?(Iot0fP5;b!D@H5zoh-#7^;PfF7JOGfQ21^QYeNuHv{4R@j_-xAvC*X(k`O2>ITGAF~GA0gx8~dW}Xa7P6V4ldcH_A@#EII zB|_h{W$eYmn9{Di?W0z*CW2r6JNjddqd_#v2KvJH_dj`%ge00Z=Z{GFW{6TU8AX(a zOoHwQ)3Tl&c_T{Y< z7v*NV>3)P8OiaP3!twXf8y;I-GpMIQ48Vs7u|d2!SmMA_R)ig@I(I+B%*|KsNi@=G zU9|;@ico6(jx=3wImfO7iTJX>fx{K5Vo*F|Jk-tK(&MiW!%^%9@Cyb=sC=XaxE**y zb7Yf%h#HO@C=8w)S*-QCv__P3c1Py{+^tmI48uW3*3x=t;&gh4ov_QytzQ|O5MYNW zDrDWc0&|g?_TpuPMr&~Q(R!~tV%Q>P5`~J`S$l3Jib$aVtarzN>s!(6_oXjOQwFIF zVsLDSfk#FF?Mh$E^X?}>2d>7k>a`s3=stG;FXa0imd#?4gwbqAWoA+hax7=(Y@^MO zB%3o+G227@>gUYn3vKyWWDz)N3%~{PRE2a1T?!ejlZs&X4s=aDCWrirJ73%P7`PVm z3-Ge_dqf?Gp-DoEVL4Fuxbu{*Np`V@hadGWqK6H2_~my#GeKhFke#Tq?%3QbT{qB7 z$l6WBYG{pcYlMavVHWaYq5}eKD*dlt?^B9I>eI~HV1Ib!Dlq@f+pqf}WdNY~ib<%< znu2MK_gD@Gc8hEAh20mcu;}k(*)iZFCWMZ=WaU{(V#T{%;%|pl!BSgK#12c~E{D!= zfjj*(l%qzHXvnA|2B9b{ZGRUlshpSFj0T1invx1fD4JWZxx@WFkXbLo0Z?4U&k{%W zbhJ`axF=ws!VlRL?9pDz4FTAjNol#k)0GeT)##?~L+oj_vbGWa)dYzDfN^U@Xrz8%E>pXH8HDravf@C8N8hGH*eYZt?C#OVE zXivJ0nx28f_je$2wg5-ots;*0F6_=z{m;!VB+i>5QK;4WL3B?eQx3}Goz8B~ovo^i z&qHz!wKtkAL$#s&Me^lrwaJ)^tyTm&7P5!KN`a&ch*sGe6EzVhKSp);GHr4do@=3c zr{Ma;5_@C7hw-j)#A?}q>DKGQ-aVpc>nEC=B&yubPsV2g1VH67wpV2^ML+S^6VjnO z6Z5F|;r_-HReKGJ3Jh_6rwyb;$AMGAz!8k(4Yt@x?Q4Wv{Opq2XXyu7ibYLfoSK5g zxK7T_Sl6lE(qG5GZDR=FlCi``Qc6#vJyt3l|D@BqZMbWdHkmlWMjhMx;y(6-6-$8`5u%xXg8>}aVmlDnkX<%2TT?;@Wv zk=Ni){n(36@?CvLFY_R8`7(L-0uXQM=_aa-ks`hUPzDll9@c_(t4|dN}N5ouxI^7IQ~> zjns1^uR9 zByAoLt;oWz~XCxe*e=B~J@jv0QhosqrNHnST=V0cvo)mKjsLE~$KMT4E)(go-MI@6y|qZdgX)E9{bL&rADhd>lpAM$a`OD_<|5{r-$5YBxz@X_~U1 zFHNQB!N+*HvYnH&uOnQw5+K905un@kFYTbYv(Xw*3dC4bMq{%Qi%GTK! z*=B%UGSm$&K81}}IKlWzTB<1&kdFPuTLk!7M7-i(UEzJOkIVMN62E)q@(Y7{E=$v? zQ3vw!wt*Mz6dOD?fRlETsy5r{sQ$%;FZGht<&;V38&)e~Qy3^zs)ylVz$tO0Ia{dF z21CsKbR|o69|mUEgKsfjqAxPPW5L;!V!*~XXRq{BAIUUFKRD>rv*xNPnDLBp5CqKJ z!1d|ndR+NcO8cZ_O|-%9S4szQ$+ANYRs6FTsaqjLwsu}9vkZ5CMbfWO>&Ku>eRM|H zPcL*wG|*iY=Eb%F(uO^)b`-BVJUsDombxae8`*buvfB)n!3iG)sJ=XinKkAk5+C6|d z1PN*>k?93v>UpJx_k}s7FDgqN&7_fRT8PYNX8+Y-AIyuS=aY*4L&j*LD8h3m+Upd& zzwa`Oj}cHpuqH29Q{icv<}+sNvg+9jn-X{z@C_7x_(by2Tu~^wcJ$#dvI&q;L>Nb) zkO<9ssTuR0vmlcyNbhj_nI!HBYtQd0^{efUCbSa zN>@O2#1apsA>7vh#zz#D;LGF2_4;uxZ#T2EX%(hsk6u3h*{_jlG-}Kh{1E(KDgM8O zEhG|Mlb`XHYO=zV$y;8iRd>5eiC(fnawj3 z;0MUoteI{3tur49MZrFgXQ&=I^P5nARnGh157kUMDpfnHNI$h+a3jiSp=85yG=2hy zI25`5=n$xa*Jzf4P9kvtwzKJb1(j3}Sfs}!`@+yf(&eaTk7R7fqQUsw4Y=|kQMg%q zX+{y#v)KJwL`YnMr%}Z5cIMe%s$al#G-It8Se&-aE)=W%$=;1QN z1c1vp%U)Rd1@_IiTBGWESvwD)YRKA@iwH zTk6^=Ev~+NId!L0v*BEuGSke;-VVxvu~&bxT&r*#q_=F=1+837JOyslM#hkZ3ObIv z9W7k17&0v}FeqFAU7qS0g)BvUq(djhbDojljTs+goza~M*|kwZlSMf^X$bol z)%~6vX+u{1I)qEb@{bs%Gq_w+7lj?&Qr(tSVk9W|^`3Nq;KWISdG^HxptF^j*Po*o+;(=Lw{+XeWm)OQe0TNqp>8(VVh{U{fV z*(gT~|HO0tnn>)NSOU(M`SKUlOs5C1pf&}f$|RILUkF=cq*gqvH7k^j_q2zanNLnT zRUiYVSbOdO_x6O}?6)b}`ODvN%fc0}`sR#m2-9@o3bqXPk-I9&4tFfWKV9sx6b?&I zbA?lDYV+ax-75KRwyx9;L$Q{d-8LI|9ej9=FlRjyKeDd|t2;*n%*W`#OhjMX`v3Q8 zu)hbln*sp>GKK&GV*K|-=VI#Y(lC|)0t)t@g3~aQEcyR;J~iN^b%3E90S9WpcbafN z|2qNmkBJ}$O4@57N~&)82T*JD+3hnSb&=j7sw)VK2t58S5BsZJvNfQtX|-y7`ezKz2mFd$1Q5Rx8S<}}fG#zgd&ng9nl`dnTgv!b>uJawN88?8lET$4xvJp1#>@24|0)o8ma zIJ>jLWQaeS?j649{45(>uEu%JKP@+@HEjla(=Sx9Uc@SG^9w_cx84F+BqdjSoJ+4e zsyib$HPK*l00VXlVtI!+!R>u2$wAyXLX3TTk?O(w!&kt6w%QXwqBDt}iO&lPrh7LdqY+7rw@F zpGgo`u9>)#_i@1_9kyR_l$|LjSnchm&e&?W15a&Yu$a4*d@N1l>v>22O>|X55~w3OInD<$1jn+8#@8>nG@ks0AOoAx z03Zw^OjiWcYdVzob0YHAtdG9YWPqn-CyJc4p;b1xMG-bkAJrslAVKGg^S=uAtmjqZ z3J`rO5=p=vNC+U64N%X-6S)SD3;1iRINFJBT!+iQ`J$+ zk2p2O|MATNvK?_g-l|-6I;zXFSD#o~?DNs7{&~4IoD$gP6WHFYNsB`F_AqHdd~u(6 zy5A$n`<}47{xRsGySXWao3(32?L>=g6jB0TL)#zsr}dA;(morU2NyGc%AUWUPVG*gYXpjImGdQ4=_7{=3a^ zs$zF3i2^O+>c1r$W~SiBWW~UZQ~=62NNf$jh|`LHS4}Qq03FD=PPP!nbRj=qSu=$$ zBV%E5k@<>6r*=7-SZV3KbrFPHf_1u%z8({dcrRR^dtB`kd-29Qzwaq`?_~J*bC*_Z zM&Q+C)~p@>^H7xV)}uu-65asNI!oLr|beOETPk1d1B^bCjAN& zT9)Nihk=7Zenl@ReocBqjb)61f)pmRg@o9JRJY#6s7fqrGopFoaLib0Fix4fI;mLF z!$B7R*KWBOs^RlgwM9yQ41i}w4kQLhJsfpZO2IM4 z@uvnOT%l?wemOL^2#}B6j9vO}VV9C(7+cD5)W&(k?g7{=Lgs5)4GRyEvq*8YWN0Pq zY_Q{=hsI#=t64>5$(C2;`^A@SUc6AQ$CI5M9o@N?zr_s|6+cYDEr21SU7EQ(zgHi* zgq%dGcrHoU5oO!ZN_+JpJFm;8i@GEHC{8!N4H=tL{e=K!mPYWCXgoJK(P{A(V{(@K ze%p-F1ELPTGNn1%lfZSZH<;3f0o?cjWA>`Byoq?+qG_l^;qMj+nHtV5SqTd_vl8Tt zPzO9c%Wxk|`|UZtH~_AS+3w=!bm&Eg0#S|y4gCcA5h&? zj$nJIoFS~UMmb6-E|Y^)-n6zG-_Z+*namsq*h4}D;Qcq1!x=vGyFB>4{ARB_-0yog zHClgw)-J~HqZjETronpn^#&7#ORp7XnVgMfbF>}gUHj`6u8EI5_ zqp71P1BGhqi6+jl$k`iZT=$9ztNkA}zBod#+_)z{tzM(hq7QYv;WfwF&=W42XW!fCZO-9;jp(=R^w(}K8r8F5T9ed2O3 zoZ@$%^K_(fIY4~XrMS$$OzJrw(yR}ro7sdI3NIV(IFDYxV`EZz4V~5yW8C|a*xN$^ zlsKs{rcA$xgDScxM+^Sah1Mnh{xb{Qemg8k*R;rL3JI&5aBr?JF3M%~&hSnuc(nxU zP~G;XU%G*lA0wO3#LpeeFWZLCqtwkBZPi)0#fq&T<^Z}u{7=7J-+uwO^_P!@L5A({=OcHptmprJ8(E(`Me-tLLVY&3 zoEnE1)!>g3NCo9bh`}XR`uPe z%$80cn1F1y64L3`#zv3uE$_-xlpPUP4RG<}^s(r{jk5DYT`fGDm}5Woz5yeUhzP#r zu;Wgu^{x~M8t3}TYi^XLNJSKIn~Oluike3qi2^WVWaR0}DMy%)zLwPJNFMFYzueFi zts}}@zcS!J!Iu;YI@anr@TAHmM*JmMDw`OGNA8KwwhgXR%)Fl%<2b~Tag=@B&IyWD zDNlHZO3SLhdZevsd>Y04!b}69u)skLS6PhpJymFAqJYldmz;W$(m-@e4>`+_!S`5G z@&Oz%Nq>olGT(9Fa|#8xO$VQI)nzpNwk{mHepd~29Fa;Ff?gnP1`B=7nu@PlYHg7< z(@%V7jYm5A)@sDOrJ=P=G@Ff3KLqw#`;BZm)P%Tt-U0r%kCjIa)F5uS_RatX0!sNe z$&&tiik(eujqUB+%uVg+nHZ7;+YplYN?-sJb{kAc;x8bNh!3F2Hjox7APiPyQKeG2 zMKJAzD#j7ugfxT0-i(ZU^wf4%1!E(r`V>{RF6yglL7&3jL*;D@&Y>WW}gYftJ`hpLpA%i#IAGUG-V7!W9iz9(RJ$LJx+9kvm56ut1D)<6Vm|> z1~<$l)Z@+gE;tVbO54p}mrUTQWIoY>yk+@_SH#u}QHNa-wz^kNIKJ@?g7aC^aVf1t zfk76e!rZQCxnqs-QO*j{02ZFoRkXiz-PLwYB$(Vgb|UPFtH~YL@TEnF>L5JbLn(b& zPfDmKt)-~gf|}1^42I*##lh@Qs~G^x!IfM1FbDpp?534NwEx%EwLn9)e(`ar#&{(R z4VTe)CzTZCRY}xT^blecjRqN-B4jd#imo&`O33@>H8l*=c&q8{S)P&H{3~~gi0d(F znsLvJ`roYXS?jFteEa+Dz0djfT4(LG_qX;|nQfx(o=%%Mm=g0k)chs^A$)YC1|Qq5 z)fFtu$}OAW55-0I2AJPpQ>#5ysX7_;7nj5>_YEVlb0fkU-wWnaTBdZhMn)_J&0Cz6 zY1|byL0W)q&e171M*~|sm9XC@2z6f-6iw%qyITF)B$-0hO>_B!POneRB@rTIa}E*_J>#=qhR!|*PbU|(LpW}3(uT<2%G-^SBxCQf$9pBf5m#fN@n7>$jQv>w`!v7^g%ul|uaa;9wThv;F_n(a%uoLKGhe09)$wG{88Mnh_xrEPN&X5~fOGiV&*iwABSd-EQfjg~ZD%li=A zu9@EHH`klGkq^iCQ_21}C`(kDS1m_(pYWy)#TvdjWW+nM+uPZ1f1alSw(Yblr_7T= z9<>~_TCK6-^2oDiYW6P@2=>CAGf|u7$By@xZHa%(qSzRrvbP^U?ZJfw3T>B1y1-FB;WPYXE?Y|x~Vis^N0rJ?jt6JK50#yo=;~>!Q4*ViFRoX45^d1(_KzY7yeqw zBF6V1MaqDlax^qTOSVI!={R@guuWGgvvdh_wR|+x6}>5juKYCIbDukkG`iGlR|d{U z=P~p7is@qpv72mP+RZc^%SR#VBeZAEKD~t0@E_mWAD7f4Lmx)ak%iYo!Y(P%nmkD( zAK`P^4591GVzZ9vT5h&6r*Z6B)*T$n}xn=Q}LW|DxZ2pK;(z{S(1+^ zMZE2`Dh^_|>zFmA1QXl2o7W~{Z{|8_N-9S`I47T&YopzlH$MNS%y#`$!jsQV3O6iq zl%{y!hmv!DRVT;4EH>V6d&OOEZ`#iBv$(lI`-9tH5&RqNl8FSh@fv_l z9E{npqmuis8!rg$=BK+NrwhT8{8Z8|O8YXx2+R5n&qdA=cK$RXRx&mjFf4zryVjgj znxOk+*4W|f)(=-*Zy}ycq5eF1&wJ&pm-MbC8nQ~EO`>3Bj8LL2tO_>amu4Q>!pKO$ ztG+Y26?BIztrE~+{r<^b9@2*SfL_<>t>ZB7XfYptpMN+!P=k0)`lAnmP7JtYLs_41 zmn^e7xGVF$NgaW8PIFS=M_^T_tHEQAPY)nNZ<@h?>(CI9r$CQ7wg1H z*E20he^i{~0K|5fJtx}<(>;a^=gJ27-b+#mqe%6wFt4!IS2M5z_03;ONWXu*)u;uA zt_ej|;Xgf&X6guzPM1x6;gk=qr(~)AHfFgW;bXD%_SFHs@tc2ROAa&=_Px)y`lt_d zFALnZ?px8lKIz-1-foyr|3c2@qO)B{f=q48mYz;`;o#{t*z9^oMc*#wrD&SWpnpH( zyo~baluqH0s%1C!Z76;n)w6!P*2{|9l;Pg=;lk)am+RY}g;ZD)l(aQ;D>Wz;GtKZr zeSn*%{gzU>J-J@q+=RYAw;iO+`R%}b4X7R*PrEtfawszCgXO_jZwlWTrIjUATow3Z zwA;=cXJ5E{k<_|cvQ*vqzPhpI-IU`(N2Kp)IV3xeEN5EiRdSC;?10czO9GO+1 zRhbd6MGowc(*#FYs^XU7U|tytpo(b5;74dVNlCmcRFVU?mdm0SH5+$=!JyK8|G(JO zi%3w>NFV%$lm&Aj@P9RE`hjLsuNp#NGjP7#2smUTRB>nmi@zZ4T(#DlSifEZD2LC_rnFRmm4gKb;{#_bn_X?SVS$yr7uT}s3N zwpO5kf)n4@lt9f&S@fa<^^m8tSP&{e41Q554J!DF1%Zi`nt*M{cd|8bD-2X%Sp&Uc z;67;Zi2xH>_CRI?loLRTtbzXZ;MTuy%BH>&0nHao=6M$d1jj*hbr5@Tg|W!^%CLnn z$gg>YpM^Y2g0c)&3qlmxC}1QNn(KfJHUj)st{@IPfc)4v)&jNzqB&)V6~e1b zf#z%|_^1vxW{STP{cCLUXPxpn-y@YuVpA-R(fkZ_$cKRD=>PI5f56z(?h>(2Vs#4+ z;;LogBCyD10ZG+JfI%0p6;>-QI4E`1D8TWyn8q$zzG$07d=nOKAxKEFXyXiiXn;mG cwOPcp>0jq2s0In>-4P2as6j66s}Q080sI;ckpKVy delta 34276 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 diff --git a/semantic-conventions/src/opentelemetry/semconv/main.py b/semantic-conventions/src/opentelemetry/semconv/main.py index 55f6c23f..38a2ad02 100644 --- a/semantic-conventions/src/opentelemetry/semconv/main.py +++ b/semantic-conventions/src/opentelemetry/semconv/main.py @@ -73,7 +73,12 @@ def main(): renderer = CodeRenderer.from_commandline_params( args.parameters, args.trim_whitespace ) - renderer.render2(semconv, args.template, args.output) + renderer.render(semconv, args.template, args.output, args.pattern) + if args.flavor == "code_easy": + renderer = CodeRenderer.from_commandline_params( + args.parameters, args.trim_whitespace + ) + renderer.renderv2(semconv, args.template, args.output) elif args.flavor == "markdown": process_markdown(semconv, args) @@ -163,6 +168,38 @@ def add_code_parser(subparsers): action="store_true", ) +def add_code_easy_parser(subparsers): + parser = subparsers.add_parser("code_easy") + parser.add_argument( + "--output", + "-o", + help="Specify the output file for the code generation.", + type=str, + required=True, + ) + parser.add_argument( + "--template", + "-t", + help="Specify the template to use for code generation", + type=str, + required=True, + ) + parser.add_argument( + "--parameters", + "-D", + dest="parameters", + action="append", + help="List of key=value pairs separated by comma. These values are fed into the template as is.", + type=str, + ) + parser.add_argument( + "--trim-whitespace", + help="Allow customising whitespace control in Jinja templates." + " Providing the flag will enable both `lstrip_blocks` and `trim_blocks`", + required=False, + action="store_true", + ) + def add_md_parser(subparsers): parser = subparsers.add_parser("markdown") @@ -257,6 +294,7 @@ def setup_parser(): ) subparsers = parser.add_subparsers(dest="flavor") add_code_parser(subparsers) + add_code_easy_parser(subparsers) add_md_parser(subparsers) return parser diff --git a/semantic-conventions/src/opentelemetry/semconv/templating/code.py b/semantic-conventions/src/opentelemetry/semconv/templating/code.py index 91c5a25e..1ab22fc4 100644 --- a/semantic-conventions/src/opentelemetry/semconv/templating/code.py +++ b/semantic-conventions/src/opentelemetry/semconv/templating/code.py @@ -159,8 +159,11 @@ def to_camelcase(name: str, first_upper=False) -> str: def is_stable(attribute: SemanticAttribute) -> bool: return attribute.stability == StabilityLevel.STABLE +def is_deprecated(attribute: SemanticAttribute) -> bool: + return attribute.stability == StabilityLevel.DEPRECATED + def is_definition(attribute: SemanticAttribute) -> bool: - return attribute.ref is None + return attribute.is_local and attribute.ref is None class CodeRenderer: pattern = f"{{{ID_RE.pattern}}}" @@ -215,8 +218,11 @@ def setup_environment(env: Environment, trim_whitespace: bool): env.filters["to_html_links"] = to_html_links env.filters["regex_replace"] = regex_replace env.filters["render_markdown"] = render_markdown - env.filters["is_stable"] = is_stable + env.filters["is_deprecated"] = is_deprecated env.filters["is_definition"] = is_definition + env.filters["is_stable"] = is_stable + env.tests["is_stable"] = is_stable + env.tests["is_definition"] = is_definition env.trim_blocks = trim_whitespace env.lstrip_blocks = trim_whitespace @@ -268,8 +274,7 @@ def renderv2( self, semconvset: SemanticConventionSet, template_path: str, - output_file, - stable: bool + output_file ): file_name = os.path.basename(template_path) folder = os.path.dirname(template_path) @@ -279,13 +284,13 @@ def renderv2( ) self.setup_environment(env, self.trim_whitespace) - for group in self._get_all_groups(semconvset, stable): + for group in self._get_all_groups(semconvset): output_name = self.prefix_output_filev2(str(output_file), group) data = { "template": template_path, - "attributes": self._filter_attributes(semconvset, group, stable), - "attribute_templates": self._filter_attribute_templates(semconvset, group, stable), + "attributes": self._filter_attributes(semconvset, group), + "attribute_templates": self._filter_attribute_templates(semconvset, group), "group": group} data.update(self.parameters) @@ -293,32 +298,37 @@ def renderv2( template.globals["now"] = datetime.datetime.utcnow() template.globals["version"] = os.environ.get("ARTIFACT_VERSION", "dev") template.globals["RequirementLevel"] = RequirementLevel - template.stream(data).dump(output_name) + content = template.render(data) + if (content == ""): + continue + + with open(output_name, "w") as f: + f.write(content) def _get_root_namespace(self, attr): namespaces = attr.fqn.split(".") return namespaces[0] if len(namespaces) > 1 else "other" - def _get_all_groups(self, semconvset, stable): + def _get_all_groups(self, semconvset): groups = set() for semconv in semconvset.models.values(): - for attr in filter(lambda a: self._filter(a, stable), semconv.attributes_and_templates): + for attr in filter(lambda a: self._filter(a), semconv.attributes_and_templates): groups.add(self._get_root_namespace(attr)) return groups - def _filter(self, attr, stable): - return stable == is_stable(attr) and attr.is_local and attr.ref is None + def _filter(self, attr): + return attr.is_local and attr.ref is None - def _filter_attributes(self, semconvset, group, stable): + def _filter_attributes(self, semconvset, group): attr_in_group = [] for semconv in semconvset.models.values(): - for attr in filter(lambda a: self._filter(a, stable) and self._get_root_namespace(a) == group, semconv.attributes): + for attr in filter(lambda a: self._filter(a) and self._get_root_namespace(a) == group, semconv.attributes): attr_in_group.append(attr) return attr_in_group - def _filter_attribute_templates(self, semconvset, group, stable): + def _filter_attribute_templates(self, semconvset, group): templates_in_group = [] for semconv in semconvset.models.values(): - for attr_template in filter(lambda a: self._filter(a, stable) and self._get_root_namespace(a) == group, semconv.attribute_templates): + for attr_template in filter(lambda a: self._filter(a) and self._get_root_namespace(a) == group, semconv.attribute_templates): templates_in_group.append(attr_template) return templates_in_group \ No newline at end of file diff --git a/semantic-conventions/src/tests/data/jinja/attribute_templates/template b/semantic-conventions/src/tests/data/jinja/attribute_templates/template index 486ddce2..d320e990 100644 --- a/semantic-conventions/src/tests/data/jinja/attribute_templates/template +++ b/semantic-conventions/src/tests/data/jinja/attribute_templates/template @@ -25,22 +25,13 @@ {%- elif type == "double" -%} doubleKey {%- else -%} - {{lowerFirst(type)}}Key + {{ type | to_camelcase(False)}}Key {%- endif -%} {%- endmacro %} -{%- macro print_value(type, value) -%} - {{ "\"" if type == "String"}}{{value}}{{ "\"" if type == "String"}} -{%- endmacro %} -{%- macro upFirst(text) -%} - {{ text[0]|upper}}{{text[1:] }} -{%- endmacro %} -{%- macro lowerFirst(text) -%} - {{ text[0]|lower}}{{text[1:] }} -{%- endmacro %} package io.opentelemetry.instrumentation.api.attributetemplates; class AttributesTemplate { -{%- for attribute_template in attribute_templates if attribute_template.is_local and not attribute_template.ref %} +{%- for attribute_template in attribute_templates | select("is_definition") %} /** * {{attribute_template.brief | render_markdown(code="{{@code {0}}}", paragraph="{0}")}} @@ -49,17 +40,17 @@ class AttributesTemplate { *

Notes:

    {{attribute_template.note | render_markdown(code="{{@code {0}}}", paragraph="
  • {0}
  • ", list="{0}")}}
{%- endif %} - {%- if (attribute_template.stability | string()) == "StabilityLevel.DEPRECATED" %} + {%- if attribute_template | is_deprecated %} * * @deprecated {{attribute_template.brief | to_doc_brief}}. {%- endif %} */ - {%- if (attribute_template.stability | string()) == "StabilityLevel.DEPRECATED" %} + {%- if attribute_template | is_deprecated %} @Deprecated {%- endif %} - public static final AttributeKey<{{upFirst(to_java_return_type(attribute_template.instantiated_type | string))}}> {{attribute_template.fqn | to_const_name}} = {{to_java_key_type(attribute_template.instantiated_type | string)}}("{{attribute_template.fqn}}"); + public static final AttributeKey<{{ to_java_return_type(attribute_template.instantiated_type | string) | to_camelcase(True)}}> {{attribute_template.fqn | to_const_name}} = {{to_java_key_type(attribute_template.instantiated_type | string)}}("{{attribute_template.fqn}}"); {%- endfor %} -{%- for attribute in attributes if attribute.is_local and not attribute.ref %} +{%- for attribute in attributes | select("is_definition") %} /** * {{attribute.brief | render_markdown(code="{{@code {0}}}", paragraph="{0}")}} @@ -68,14 +59,14 @@ class AttributesTemplate { *

Notes:

    {{attribute.note | render_markdown(code="{{@code {0}}}", paragraph="
  • {0}
  • ", list="{0}")}}
{%- endif %} - {%- if (attribute.stability | string()) == "StabilityLevel.DEPRECATED" %} + {%- if attribute | is_deprecated %} * * @deprecated {{attribute.brief | to_doc_brief}}. {%- endif %} */ - {%- if (attribute.stability | string()) == "StabilityLevel.DEPRECATED" %} + {%- if attribute | is_deprecated %} @Deprecated {%- endif %} - public static final AttributeKey<{{upFirst(to_java_return_type(attribute.instantiated_type | string))}}> {{attribute.fqn | to_const_name}} = {{to_java_key_type(attribute.instantiated_type | string)}}("{{attribute.fqn}}"); + public static final AttributeKey<{{ to_java_return_type(attribute.instantiated_type | string) | to_camelcase(True)}}> {{attribute.fqn | to_const_name}} = {{to_java_key_type(attribute.instantiated_type | string)}}("{{attribute.fqn}}"); {%- endfor %} } diff --git a/semantic-conventions/src/tests/data/jinja/attributesv2/ThirdAttributes.java b/semantic-conventions/src/tests/data/jinja/attributesv2/ThirdAttributesAll.java similarity index 100% rename from semantic-conventions/src/tests/data/jinja/attributesv2/ThirdAttributes.java rename to semantic-conventions/src/tests/data/jinja/attributesv2/ThirdAttributesAll.java diff --git a/semantic-conventions/src/tests/data/jinja/attributesv2/ThirdAttributesStable.java b/semantic-conventions/src/tests/data/jinja/attributesv2/ThirdAttributesStable.java new file mode 100644 index 00000000..71263bf3 --- /dev/null +++ b/semantic-conventions/src/tests/data/jinja/attributesv2/ThirdAttributesStable.java @@ -0,0 +1,8 @@ +package io.opentelemetry.instrumentation.api.semconv; + +class ThirdAttributes { + /** + * short description of attr_three + */ + public static final AttributeKey THIRD_ATTR_THREE = stringKey("third.attr_three"); +} \ No newline at end of file diff --git a/semantic-conventions/src/tests/data/jinja/attributesv2/attributes.yml b/semantic-conventions/src/tests/data/jinja/attributesv2/attributes.yml index b5401c47..f4a1a6cc 100644 --- a/semantic-conventions/src/tests/data/jinja/attributesv2/attributes.yml +++ b/semantic-conventions/src/tests/data/jinja/attributesv2/attributes.yml @@ -47,7 +47,6 @@ groups: brief: > this is the description of attribute template examples: 'example' - stability: stable - id: forth_group_id brief: forth description attributes: diff --git a/semantic-conventions/src/tests/data/jinja/attributesv2/template b/semantic-conventions/src/tests/data/jinja/attributesv2/template deleted file mode 100644 index e7326518..00000000 --- a/semantic-conventions/src/tests/data/jinja/attributesv2/template +++ /dev/null @@ -1,74 +0,0 @@ -{%- macro to_java_return_type(type) -%} - {%- if type == "string" -%} - String - {%- elif type == "string[]" -%} - List - {%- elif type == "boolean" -%} - boolean - {%- elif type == "int" -%} - long - {%- elif type == "double" -%} - double - {%- else -%} - {{type}} - {%- endif -%} -{%- endmacro %} -{%- macro to_java_key_type(type) -%} - {%- if type == "string" -%} - stringKey - {%- elif type == "string[]" -%} - stringArrayKey - {%- elif type == "boolean" -%} - booleanKey - {%- elif type == "int" -%} - longKey - {%- elif type == "double" -%} - doubleKey - {%- else -%} - {{lowerFirst(type)}}Key - {%- endif -%} -{%- endmacro %} -{%- macro print_value(type, value) -%} - {{ "\"" if type == "String"}}{{value}}{{ "\"" if type == "String"}} -{%- endmacro %} -{%- macro upFirst(text) -%} - {{ text[0]|upper}}{{text[1:] }} -{%- endmacro %} -{%- macro lowerFirst(text) -%} - {{ text[0]|lower}}{{text[1:] }} -{%- endmacro %} -package io.opentelemetry.instrumentation.api.semconv; - -class {{ group | to_camelcase(True) }}Attributes { -{%- for attribute in attributes %} - - /** - * {{attribute.brief | render_markdown(code="{{@code {0}}}", paragraph="{0}")}} - {%- if attribute.note %} - * - *

Notes: -

    {{attribute.note | render_markdown(code="{{@code {0}}}", paragraph="
  • {0}
  • ", list="{0}")}}
- {%- endif %} - {%- if (attribute.stability | string()) == "StabilityLevel.DEPRECATED" %} - * - {%- endif %} - - */ - public static final AttributeKey<{{upFirst(to_java_return_type(attribute.instantiated_type | string))}}> {{attribute.fqn | to_const_name}} = {{to_java_key_type(attribute.instantiated_type | string)}}("{{attribute.fqn}}"); -{# Extra line #} -{%- endfor %} -{%- for attribute_template in attribute_templates %} - - /** - * {{attribute_template.brief | render_markdown(code="{{@code {0}}}", paragraph="{0}")}} - {%- if attribute_template.note %} - * - *

Notes: -

    {{attribute_template.note | render_markdown(code="{{@code {0}}}", paragraph="
  • {0}
  • ", list="{0}")}}
- {%- endif %} - - */ - public static final AttributeKey<{{upFirst(to_java_return_type(attribute_template.instantiated_type | string))}}> {{attribute_template.fqn | to_const_name}} = {{to_java_key_type(attribute_template.instantiated_type | string)}}("{{attribute_template.fqn}}"); -{# Extra line #} -{%- endfor %} -} \ No newline at end of file diff --git a/semantic-conventions/src/tests/data/jinja/attributesv2/template_all b/semantic-conventions/src/tests/data/jinja/attributesv2/template_all new file mode 100644 index 00000000..2d15368d --- /dev/null +++ b/semantic-conventions/src/tests/data/jinja/attributesv2/template_all @@ -0,0 +1,50 @@ +{%- macro to_java_return_type(type) -%} + {%- if type == "string" -%} + String + {%- elif type == "string[]" -%} + List + {%- elif type == "boolean" -%} + boolean + {%- elif type == "int" -%} + long + {%- elif type == "double" -%} + double + {%- else -%} + {{type}} + {%- endif -%} +{%- endmacro %} +{%- macro to_java_key_type(type) -%} + {%- if type == "string" -%} + stringKey + {%- elif type == "string[]" -%} + stringArrayKey + {%- elif type == "boolean" -%} + booleanKey + {%- elif type == "int" -%} + longKey + {%- elif type == "double" -%} + doubleKey + {%- else -%} + {{ type | to_camelcase(False)}}Key + {%- endif -%} +{%- endmacro %} +package io.opentelemetry.instrumentation.api.semconv; + +class {{ group | to_camelcase(True) }}Attributes { +{%- for attribute in attributes %} + + /** + * {{attribute.brief | render_markdown(code="{{@code {0}}}", paragraph="{0}")}} + */ + public static final AttributeKey<{{ to_java_return_type(attribute.instantiated_type | string) | to_camelcase(True) }}> {{attribute.fqn | to_const_name}} = {{to_java_key_type(attribute.instantiated_type | string)}}("{{attribute.fqn}}"); +{# Extra line #} +{%- endfor %} +{%- for attribute_template in attribute_templates %} + + /** + * {{attribute_template.brief | render_markdown(code="{{@code {0}}}", paragraph="{0}")}} + */ + public static final AttributeKey<{{ to_java_return_type(attribute_template.instantiated_type | string) | to_camelcase(True)}}> {{attribute_template.fqn | to_const_name}} = {{to_java_key_type(attribute_template.instantiated_type | string)}}("{{attribute_template.fqn}}"); +{# Extra line #} +{%- endfor %} +} \ No newline at end of file diff --git a/semantic-conventions/src/tests/data/jinja/attributesv2/template_only_stable b/semantic-conventions/src/tests/data/jinja/attributesv2/template_only_stable new file mode 100644 index 00000000..45b342a5 --- /dev/null +++ b/semantic-conventions/src/tests/data/jinja/attributesv2/template_only_stable @@ -0,0 +1,55 @@ +{%- macro to_java_return_type(type) -%} + {%- if type == "string" -%} + String + {%- elif type == "string[]" -%} + List + {%- elif type == "boolean" -%} + boolean + {%- elif type == "int" -%} + long + {%- elif type == "double" -%} + double + {%- else -%} + {{type}} + {%- endif -%} +{%- endmacro %} +{%- macro to_java_key_type(type) -%} + {%- if type == "string" -%} + stringKey + {%- elif type == "string[]" -%} + stringArrayKey + {%- elif type == "boolean" -%} + booleanKey + {%- elif type == "int" -%} + longKey + {%- elif type == "double" -%} + doubleKey + {%- else -%} + {{ type | to_camelcase(False)}}Key + {%- endif -%} +{%- endmacro %} +{%- set stable_attributes = attributes | select("is_stable") | list %} +{%- set stable_attribute_templates = attribute_templates | select("is_stable") | list %} + +{%- if stable_attributes | count > 0 or stable_attribute_templates | count > 0 %} +package io.opentelemetry.instrumentation.api.semconv; + +class {{ group | to_camelcase(True) }}Attributes { +{%- for attribute in stable_attributes %} + + /** + * {{attribute.brief | render_markdown(code="{{@code {0}}}", paragraph="{0}")}} + */ + public static final AttributeKey<{{ to_java_return_type(attribute.instantiated_type | string) | to_camelcase(True)}}> {{attribute.fqn | to_const_name}} = {{to_java_key_type(attribute.instantiated_type | string)}}("{{attribute.fqn}}"); +{# Extra line #} +{%- endfor %} +{%- for attribute_template in stable_attribute_templates %} + + /** + * {{attribute_template.brief | render_markdown(code="{{@code {0}}}", paragraph="{0}")}} + */ + public static final AttributeKey<{{ to_java_return_type(attribute_template.instantiated_type | string) | to_camelcase(True)}}> {{attribute_template.fqn | to_const_name}} = {{to_java_key_type(attribute_template.instantiated_type | string)}}("{{attribute_template.fqn}}"); +{# Extra line #} +{%- endfor %} +} +{%- endif %} \ No newline at end of file diff --git a/semantic-conventions/src/tests/semconv/templating/test_code.py b/semantic-conventions/src/tests/semconv/templating/test_code.py index 40ee889e..7763bbe0 100644 --- a/semantic-conventions/src/tests/semconv/templating/test_code.py +++ b/semantic-conventions/src/tests/semconv/templating/test_code.py @@ -71,14 +71,13 @@ def test_codegen_attribute_v2_experimental(test_file_path, read_test_file): ) semconv.finish() - template_path = test_file_path("jinja", "attributesv2", "template") + template_path = test_file_path("jinja", "attributesv2", "template_all") renderer = CodeRenderer({}, trim_whitespace=True) tmppath = tempfile.mkdtemp() - renderer.renderv2(semconv, template_path, os.path.join(tmppath, "Attributes.java"), stable=False) + renderer.renderv2(semconv, template_path, os.path.join(tmppath, "Attributes.java")) with open(os.path.join(tmppath, "FirstAttributes.java")) as first: data = first.read() - print(data) expected = read_test_file("jinja", "attributesv2", "FirstAttributes.java") assert data == expected @@ -87,7 +86,11 @@ def test_codegen_attribute_v2_experimental(test_file_path, read_test_file): expected = read_test_file("jinja", "attributesv2", "SecondAttributes.java") assert data == expected - assert not os.path.isfile(os.path.join(tmppath, "ThirdAttributes.java")) + with open(os.path.join(tmppath, "ThirdAttributes.java")) as second: + data = second.read() + expected = read_test_file("jinja", "attributesv2", "ThirdAttributesAll.java") + assert data == expected + def test_codegen_attribute_v2_stable(test_file_path, read_test_file): semconv = SemanticConventionSet(debug=False) @@ -96,15 +99,15 @@ def test_codegen_attribute_v2_stable(test_file_path, read_test_file): ) semconv.finish() - template_path = test_file_path("jinja", "attributesv2", "template") + template_path = test_file_path("jinja", "attributesv2", "template_only_stable") renderer = CodeRenderer({}, trim_whitespace=True) tmppath = tempfile.mkdtemp() - renderer.renderv2(semconv, template_path, os.path.join(tmppath, "Attributes.java"), stable=True) + renderer.renderv2(semconv, template_path, os.path.join(tmppath, "Attributes.java")) with open(os.path.join(tmppath, "ThirdAttributes.java")) as second: data = second.read() - expected = read_test_file("jinja", "attributesv2", "ThirdAttributes.java") + expected = read_test_file("jinja", "attributesv2", "ThirdAttributesStable.java") assert data == expected assert not os.path.isfile(os.path.join(tmppath, "FirstAttributes.java")) @@ -117,11 +120,11 @@ def test_codegen_attribute_v2_no_group_prefix(test_file_path, read_test_file): ) semconv.finish() - template_path = test_file_path("jinja", "attributesv2", "template") + template_path = test_file_path("jinja", "attributesv2", "template_all") renderer = CodeRenderer({}, trim_whitespace=True) tmppath = tempfile.mkdtemp() - renderer.renderv2(semconv, template_path, os.path.join(tmppath, "Attributes.java"), stable=False) + renderer.renderv2(semconv, template_path, os.path.join(tmppath, "Attributes.java")) with open(os.path.join(tmppath, "FooAttributes.java")) as foo: data = foo.read() expected = read_test_file("jinja", "attributesv2", "FooAttributes.java") From 3a99737f786a33abf86f2606bb7ef7eac94a7378 Mon Sep 17 00:00:00 2001 From: Liudmila Molkova Date: Sat, 2 Dec 2023 11:34:59 -0800 Subject: [PATCH 03/18] support codegen by attribute root namespace --- semantic-conventions/CHANGELOG.md | 7 +- semantic-conventions/README.md | 60 +++++++- .../semconvgen-0.8.0-py3-none-any.whl | Bin 48778 -> 0 bytes .../src/opentelemetry/semconv/main.py | 46 +----- .../semconv/model/semantic_attribute.py | 7 +- .../opentelemetry/semconv/templating/code.py | 136 +++++++++--------- .../all}/FirstAttributes.java | 2 +- .../all}/SecondAttributes.java | 0 .../all/ThirdAttributes.java} | 8 +- .../attributes.yml | 0 .../no_group_prefix}/FooAttributes.java | 0 .../no_group_prefix}/OtherAttributes.java | 0 .../attributes_no_group_prefix.yml | 0 .../stable}/ThirdAttributesStable.java | 0 .../stable/template_only_stable | 49 +++++++ .../template_all | 21 ++- .../jinja/attributesv2/template_only_stable | 55 ------- .../src/tests/semconv/templating/test_code.py | 98 ++++++------- 18 files changed, 252 insertions(+), 237 deletions(-) delete mode 100644 semantic-conventions/semconvgen-0.8.0-py3-none-any.whl rename semantic-conventions/src/tests/data/jinja/{attributesv2 => attributes_root_ns/all}/FirstAttributes.java (78%) rename semantic-conventions/src/tests/data/jinja/{attributesv2 => attributes_root_ns/all}/SecondAttributes.java (100%) rename semantic-conventions/src/tests/data/jinja/{attributesv2/ThirdAttributesAll.java => attributes_root_ns/all/ThirdAttributes.java} (70%) rename semantic-conventions/src/tests/data/jinja/{attributesv2 => attributes_root_ns}/attributes.yml (100%) rename semantic-conventions/src/tests/data/jinja/{attributesv2 => attributes_root_ns/no_group_prefix}/FooAttributes.java (100%) rename semantic-conventions/src/tests/data/jinja/{attributesv2 => attributes_root_ns/no_group_prefix}/OtherAttributes.java (100%) rename semantic-conventions/src/tests/data/jinja/{attributesv2 => attributes_root_ns/no_group_prefix}/attributes_no_group_prefix.yml (100%) rename semantic-conventions/src/tests/data/jinja/{attributesv2 => attributes_root_ns/stable}/ThirdAttributesStable.java (100%) create mode 100644 semantic-conventions/src/tests/data/jinja/attributes_root_ns/stable/template_only_stable rename semantic-conventions/src/tests/data/jinja/{attributesv2 => attributes_root_ns}/template_all (54%) delete mode 100644 semantic-conventions/src/tests/data/jinja/attributesv2/template_only_stable diff --git a/semantic-conventions/CHANGELOG.md b/semantic-conventions/CHANGELOG.md index 153d7587..4c3c50c9 100644 --- a/semantic-conventions/CHANGELOG.md +++ b/semantic-conventions/CHANGELOG.md @@ -4,6 +4,11 @@ Please update the changelog as part of any significant pull request. ## Unreleased +- Added code-generation mode that groups attributes by the root namespace and ability to wring each group into individual file. + [BREAKING] The `--file-per-group ` that used to create multiple directories (like `output//file`) now generates + multiple files (`output/file`) instead. + ([#TODO](https://github.com/open-telemetry/build-tools/pull/TODO)) + ## v0.23.0 - Rephrase and relax sampling-relevant description @@ -17,7 +22,7 @@ Please update the changelog as part of any significant pull request. ([#205](https://github.com/open-telemetry/build-tools/pull/205)) - Fix referencing template attributes ([#206](https://github.com/open-telemetry/build-tools/pull/206)) - + ## v0.21.0 - Render template-type attributes from yaml files diff --git a/semantic-conventions/README.md b/semantic-conventions/README.md index 9d1bc5a7..c3578607 100644 --- a/semantic-conventions/README.md +++ b/semantic-conventions/README.md @@ -85,8 +85,8 @@ convention that have the tag `network`. `` will print the constraints and attributes of both `http` and `http.server` semantic conventions that have the tag `network`. -`` will print a table describing a single metric -`http.server.active_requests`. +`` will print a table describing a single metric +`http.server.active_requests`. ## Code Generator @@ -125,12 +125,66 @@ This way, multiple files are generated. The value of `pattern` can be one of the - `semconv_id`: The id of the semantic convention. - `prefix`: The prefix with which all attributes starts with. - `extends`: The id of the parent semantic convention. +- `root_namespace`: The root namespace of attribute to group by. Finally, additional value can be passed to the template in form of `key=value` pairs separated by comma using the `--parameters [{key=value},]+` or `-D` flag. ### Customizing Jinja's Whitespace Control -The image also supports customising +The image also supports customizing [Whitespace Control in Jinja templates](https://jinja.palletsprojects.com/en/3.1.x/templates/#whitespace-control) via the additional flag `--trim-whitespace`. Providing the flag will enable both `lstrip_blocks` and `trim_blocks`. + +### Accessing Semantic Conventions in the templated + +When template is processed, it has access to a set of variables that depends on the `--file-per-group` value (or lack of it). +You can access properties of these variables and call Jinja or Python functions defined on them. + +#### Single file (no `--file-per-group` pattern is provided) + +Processes all parsed semantic conventions + +- `semconvs` - the dictionary containing parsed `BaseSemanticConvention` instances with semconv `id` as a key +- `attributes_and_templates` - the dictionary containing all attributes (including template ones) grouped by their root namespace. + Each element in the dictionary is a list of attributes that share the same root namespace. Attributes that don't have a namespace + appear under `""` key. +- `attributes` - the list of all attributes from all parsed semantic conventions. Does not include template attributes. +- `attribute_templates` - the list of all attribute templates from all parsed semantic conventions. + +#### The `root_namespace` pattern + +Processes a single namespace and is called for each namespace detected. + +- `attributes_and_templates` - the list containing all attributes (including template ones) in the given root namespace. +- `root_namespace` - the root namespace being processed. + +#### Other patterns + +Processes a single pattern value and is called for each distinct value. + +- `semconv` - the instance of parsed `BaseSemanticConvention` being processed. + +### Filtering attributes + +Jinja templates has a notion of [filters](https://jinja.palletsprojects.com/en/2.11.x/templates/#list-of-builtin-filters) allowing to transform objects or filter lists. + +Semconvgen supports following additional filters to simplify common operations in templates. + +#### Attribute filters + +1. `is_definition` - Checks if the attribute is the original definition of the attribute and not a reference. +2. `is_deprecated` - Checks if the attribute is deprecated. The same check can also be done with `(attribute.stability | string()) == "StabilityLevel.DEPRECATED"` +3. `is_experimental` - Checks if the attribute is experimental. The same check can also be done with `(attribute.stability | string()) == "StabilityLevel.EXPERIMENTAL"` +4. `is_stable` - Checks if the attribute is experimental. The same check can also be done with `(attribute.stability | string()) == "StabilityLevel.STABLE"` +5. `is_template` - Checks if the attribute is a template attribute. The same check can also be done with `(attribute.stability | string()) == "StabilityLevel.STABLE"` + +#### String operations + +1. `first_up` - Upper-cases the first character in the string. Does not modify anything else +2. `regex_replace(text, pattern, replace)` - Makes regex-based replace in `text` string using `pattern`` +3. `to_camelcase` - Converts a string to camel case (using `.` and `_` as words delimiter in the original string). + The first character of every word is upper-cased, other characters are lower-cased. E.g. `foo.bAR_baz` becomes `fooBarBaz` +4. `to_const_name` - Converts a string to Python or Java constant name (SNAKE_CASE) replacing `.` or `-` with `_`. E.g. + `foo.bAR-baz` becomes `FOO_BAR_BAZ`. +5. `to_doc_brief` - Trims whitespace and removes dot at the end. E.g. ` Hello world.\t` becomes `Hello world` diff --git a/semantic-conventions/semconvgen-0.8.0-py3-none-any.whl b/semantic-conventions/semconvgen-0.8.0-py3-none-any.whl deleted file mode 100644 index 6d012b3620ea785b0d6ddcd00ab9e78072f90063..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 48778 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
GX#QwZZj(;2w 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 W2Lr^GJzFD*fKmXP)A`Eh|NbAjf`1_Z diff --git a/semantic-conventions/src/opentelemetry/semconv/main.py b/semantic-conventions/src/opentelemetry/semconv/main.py index 38a2ad02..9a967277 100644 --- a/semantic-conventions/src/opentelemetry/semconv/main.py +++ b/semantic-conventions/src/opentelemetry/semconv/main.py @@ -74,11 +74,6 @@ def main(): args.parameters, args.trim_whitespace ) renderer.render(semconv, args.template, args.output, args.pattern) - if args.flavor == "code_easy": - renderer = CodeRenderer.from_commandline_params( - args.parameters, args.trim_whitespace - ) - renderer.renderv2(semconv, args.template, args.output) elif args.flavor == "markdown": process_markdown(semconv, args) @@ -134,7 +129,7 @@ def add_code_parser(subparsers): parser.add_argument( "--output", "-o", - help="Specify the output file for the code generation.", + help="Specify the output file name for the code generation. See also --file-per-group on how to generate multiple files.", type=str, required=True, ) @@ -148,41 +143,11 @@ def add_code_parser(subparsers): parser.add_argument( "--file-per-group", dest="pattern", - help="Each Semantic Convention is processed by the template and store in a different file. PATTERN is expected " - "to be the name of a SemanticConvention field and is prepended as a prefix to the output argument", - type=str, - ) - parser.add_argument( - "--parameters", - "-D", - dest="parameters", - action="append", - help="List of key=value pairs separated by comma. These values are fed into the template as is.", - type=str, - ) - parser.add_argument( - "--trim-whitespace", - help="Allow customising whitespace control in Jinja templates." - " Providing the flag will enable both `lstrip_blocks` and `trim_blocks`", - required=False, - action="store_true", - ) - -def add_code_easy_parser(subparsers): - parser = subparsers.add_parser("code_easy") - parser.add_argument( - "--output", - "-o", - help="Specify the output file for the code generation.", + help="Semantic conventions are processed by the template and stored in a different file. " + "File names start with a 'pattern' and end with the name specified in the 'output' argument. " + "The 'pattern' can either match 'root_namespace' to group attributes by the root namespace or " + "match a name of Semantic Convention property which value will be used as a file name prefix.", type=str, - required=True, - ) - parser.add_argument( - "--template", - "-t", - help="Specify the template to use for code generation", - type=str, - required=True, ) parser.add_argument( "--parameters", @@ -294,7 +259,6 @@ def setup_parser(): ) subparsers = parser.add_subparsers(dest="flavor") add_code_parser(subparsers) - add_code_easy_parser(subparsers) add_md_parser(subparsers) return parser diff --git a/semantic-conventions/src/opentelemetry/semconv/model/semantic_attribute.py b/semantic-conventions/src/opentelemetry/semconv/model/semantic_attribute.py index f6df923d..16073ccd 100644 --- a/semantic-conventions/src/opentelemetry/semconv/model/semantic_attribute.py +++ b/semantic-conventions/src/opentelemetry/semconv/model/semantic_attribute.py @@ -60,6 +60,7 @@ class SemanticAttribute: sampling_relevant: bool note: str position: List[int] + root_namespace: str inherited: bool = False imported: bool = False @@ -203,6 +204,10 @@ def parse( fqn = fqn.strip() parsed_brief = TextWithLinks(brief.strip() if brief else "") parsed_note = TextWithLinks(note.strip()) + + namespaces = fqn.split(".") + root_namespace = namespaces[0] if len(namespaces) > 1 else "" + attr = SemanticAttribute( fqn=fqn, attr_id=attr_id, @@ -218,6 +223,7 @@ def parse( sampling_relevant=sampling_relevant, note=parsed_note, position=position, + root_namespace=root_namespace, ) if attr.fqn in attributes: position = position_data[list(attribute)[0]] @@ -338,7 +344,6 @@ def equivalent_to(self, other: "SemanticAttribute"): return True return False - class AttributeType: # https://yaml.org/type/bool.html diff --git a/semantic-conventions/src/opentelemetry/semconv/templating/code.py b/semantic-conventions/src/opentelemetry/semconv/templating/code.py index 1ab22fc4..577033b6 100644 --- a/semantic-conventions/src/opentelemetry/semconv/templating/code.py +++ b/semantic-conventions/src/opentelemetry/semconv/templating/code.py @@ -21,6 +21,7 @@ from jinja2 import Environment, FileSystemLoader, select_autoescape from opentelemetry.semconv.model.semantic_attribute import ( + AttributeType, RequirementLevel, SemanticAttribute, StabilityLevel, @@ -149,22 +150,30 @@ def merge(elems: typing.List, elm): def to_const_name(name: str) -> str: return name.upper().replace(".", "_").replace("-", "_") - def to_camelcase(name: str, first_upper=False) -> str: first, *rest = name.replace("_", ".").split(".") if first_upper: first = first.capitalize() return first + "".join(word.capitalize() for word in rest) +def first_up(name: str) -> str: + return name[0].upper() + name[1:] + def is_stable(attribute: SemanticAttribute) -> bool: return attribute.stability == StabilityLevel.STABLE def is_deprecated(attribute: SemanticAttribute) -> bool: return attribute.stability == StabilityLevel.DEPRECATED +def is_experimental(attribute: SemanticAttribute) -> bool: + return attribute.stability == StabilityLevel.EXPERIMENTAL + def is_definition(attribute: SemanticAttribute) -> bool: return attribute.is_local and attribute.ref is None +def is_template(attribute: SemanticAttribute) -> bool: + return AttributeType.is_template_type(attribute.attr_type) + class CodeRenderer: pattern = f"{{{ID_RE.pattern}}}" @@ -197,6 +206,7 @@ def get_data_single_file( "semconvs": semconvset.models, "attributes": semconvset.attributes(), "attribute_templates": semconvset.attribute_templates(), + "attributes_and_templates": self._grouped_attribute_definitions(semconvset), } data.update(self.parameters) return data @@ -215,26 +225,25 @@ def setup_environment(env: Environment, trim_whitespace: bool): env.filters["to_const_name"] = to_const_name env.filters["merge"] = merge env.filters["to_camelcase"] = to_camelcase + env.filters["first_up"] = first_up env.filters["to_html_links"] = to_html_links env.filters["regex_replace"] = regex_replace env.filters["render_markdown"] = render_markdown env.filters["is_deprecated"] = is_deprecated env.filters["is_definition"] = is_definition env.filters["is_stable"] = is_stable + env.filters["is_experimental"] = is_experimental + env.filters["is_template"] = is_template env.tests["is_stable"] = is_stable + env.tests["is_experimental"] = is_experimental + env.tests["is_deprecated"] = is_deprecated env.tests["is_definition"] = is_definition + env.tests["is_template"] = is_template env.trim_blocks = trim_whitespace env.lstrip_blocks = trim_whitespace @staticmethod - def prefix_output_file(file_name, pattern, semconv): - basename = os.path.basename(file_name) - dirname = os.path.dirname(file_name) - value = getattr(semconv, pattern) - return os.path.join(dirname, to_camelcase(value, True), basename) - - @staticmethod - def prefix_output_filev2(file_name, prefix): + def prefix_output_file(file_name, prefix): basename = os.path.basename(file_name) dirname = os.path.dirname(file_name) return os.path.join(dirname, to_camelcase(prefix, True) + basename) @@ -253,82 +262,69 @@ def render( autoescape=select_autoescape([""]), ) self.setup_environment(env, self.trim_whitespace) - if pattern: - for semconv in semconvset.models.values(): - output_name = self.prefix_output_file(output_file, pattern, semconv) - data = self.get_data_multiple_files(semconv, template_path) - template = env.get_template(file_name, globals=data) - template.globals["now"] = datetime.datetime.utcnow() - template.globals["version"] = os.environ.get("ARTIFACT_VERSION", "dev") - template.globals["RequirementLevel"] = RequirementLevel - template.stream(data).dump(output_name) + if pattern == "root_namespace": + self._render_group_by_root_namespace(semconvset, template_path, file_name, output_file, env) + elif pattern is not None: + self._render_by_pattern(semconvset, template_path, file_name, output_file, pattern, env) else: data = self.get_data_single_file(semconvset, template_path) template = env.get_template(file_name, globals=data) - template.globals["now"] = datetime.datetime.utcnow() - template.globals["version"] = os.environ.get("ARTIFACT_VERSION", "dev") - template.globals["RequirementLevel"] = RequirementLevel - template.stream(data).dump(output_file) + self._write_template_to_file(template, data, output_file) - def renderv2( + + def _render_by_pattern( self, semconvset: SemanticConventionSet, template_path: str, - output_file + file_name: str, + output_file: str, + pattern: str, + env: Environment, ): - file_name = os.path.basename(template_path) - folder = os.path.dirname(template_path) - env = Environment( - loader=FileSystemLoader(searchpath=folder), - autoescape=select_autoescape([""]), - ) - self.setup_environment(env, self.trim_whitespace) + for semconv in semconvset.models.values(): + prefix = getattr(semconv, pattern) + output_name = self.prefix_output_file(output_file, prefix) + data = self.get_data_multiple_files(semconv, template_path) + template = env.get_template(file_name, globals=data) + self._write_template_to_file(template, data, output_name) - for group in self._get_all_groups(semconvset): - output_name = self.prefix_output_filev2(str(output_file), group) + def _render_group_by_root_namespace( + self, + semconvset: SemanticConventionSet, + template_path: str, + file_name: str, + output_file: str, + env: Environment, + ): + root_namespaces = self._grouped_attribute_definitions(semconvset) + for ns in root_namespaces: + sanitized_ns = ns if ns != "" else "other" + output_name = self.prefix_output_file(output_file, sanitized_ns) data = { "template": template_path, - "attributes": self._filter_attributes(semconvset, group), - "attribute_templates": self._filter_attribute_templates(semconvset, group), - "group": group} + "attributes_and_templates": sorted(root_namespaces[ns], key=lambda a: a.fqn), + "root_namespace": sanitized_ns} data.update(self.parameters) template = env.get_template(file_name, globals=data) - template.globals["now"] = datetime.datetime.utcnow() - template.globals["version"] = os.environ.get("ARTIFACT_VERSION", "dev") - template.globals["RequirementLevel"] = RequirementLevel - content = template.render(data) - if (content == ""): - continue + self._write_template_to_file(template, data, output_name) + def _grouped_attribute_definitions(self, semconvset): + root_namespaces = {} + for semconv in semconvset.models.values(): + for attr in filter(lambda a: is_definition(a), semconv.attributes_and_templates): + if attr.root_namespace not in root_namespaces: + root_namespaces[attr.root_namespace] = [] + root_namespaces[attr.root_namespace].append(attr) + return root_namespaces + + def _write_template_to_file(self, template, data, output_name): + template.globals["now"] = datetime.datetime.utcnow() + template.globals["version"] = os.environ.get("ARTIFACT_VERSION", "dev") + template.globals["RequirementLevel"] = RequirementLevel + + content = template.render(data) + if (content != ""): with open(output_name, "w") as f: f.write(content) - - def _get_root_namespace(self, attr): - namespaces = attr.fqn.split(".") - return namespaces[0] if len(namespaces) > 1 else "other" - - def _get_all_groups(self, semconvset): - groups = set() - for semconv in semconvset.models.values(): - for attr in filter(lambda a: self._filter(a), semconv.attributes_and_templates): - groups.add(self._get_root_namespace(attr)) - return groups - - def _filter(self, attr): - return attr.is_local and attr.ref is None - - def _filter_attributes(self, semconvset, group): - attr_in_group = [] - for semconv in semconvset.models.values(): - for attr in filter(lambda a: self._filter(a) and self._get_root_namespace(a) == group, semconv.attributes): - attr_in_group.append(attr) - return attr_in_group - - def _filter_attribute_templates(self, semconvset, group): - templates_in_group = [] - for semconv in semconvset.models.values(): - for attr_template in filter(lambda a: self._filter(a) and self._get_root_namespace(a) == group, semconv.attribute_templates): - templates_in_group.append(attr_template) - return templates_in_group \ No newline at end of file diff --git a/semantic-conventions/src/tests/data/jinja/attributesv2/FirstAttributes.java b/semantic-conventions/src/tests/data/jinja/attributes_root_ns/all/FirstAttributes.java similarity index 78% rename from semantic-conventions/src/tests/data/jinja/attributesv2/FirstAttributes.java rename to semantic-conventions/src/tests/data/jinja/attributes_root_ns/all/FirstAttributes.java index f5779efa..9281a5f2 100644 --- a/semantic-conventions/src/tests/data/jinja/attributesv2/FirstAttributes.java +++ b/semantic-conventions/src/tests/data/jinja/attributes_root_ns/all/FirstAttributes.java @@ -14,5 +14,5 @@ class FirstAttributes { /** * this is the description of attribute template */ - public static final AttributeKey FIRST_ATTR_TEMPLATE_ONE = stringKey("first.attr_template_one"); + public static final AttributeKeyTemplate FIRST_ATTR_TEMPLATE_ONE = stringKey("first.attr_template_one"); } \ No newline at end of file diff --git a/semantic-conventions/src/tests/data/jinja/attributesv2/SecondAttributes.java b/semantic-conventions/src/tests/data/jinja/attributes_root_ns/all/SecondAttributes.java similarity index 100% rename from semantic-conventions/src/tests/data/jinja/attributesv2/SecondAttributes.java rename to semantic-conventions/src/tests/data/jinja/attributes_root_ns/all/SecondAttributes.java diff --git a/semantic-conventions/src/tests/data/jinja/attributesv2/ThirdAttributesAll.java b/semantic-conventions/src/tests/data/jinja/attributes_root_ns/all/ThirdAttributes.java similarity index 70% rename from semantic-conventions/src/tests/data/jinja/attributesv2/ThirdAttributesAll.java rename to semantic-conventions/src/tests/data/jinja/attributes_root_ns/all/ThirdAttributes.java index 3aa18f32..e4a9bca1 100644 --- a/semantic-conventions/src/tests/data/jinja/attributesv2/ThirdAttributesAll.java +++ b/semantic-conventions/src/tests/data/jinja/attributes_root_ns/all/ThirdAttributes.java @@ -2,12 +2,12 @@ class ThirdAttributes { /** - * short description of attr_three + * this is the description of attribute template */ - public static final AttributeKey THIRD_ATTR_THREE = stringKey("third.attr_three"); + public static final AttributeKeyTemplate THIRD_ATTR_TEMPLATE_THREE = stringKey("third.attr_template_three"); /** - * this is the description of attribute template + * short description of attr_three */ - public static final AttributeKey THIRD_ATTR_TEMPLATE_THREE = stringKey("third.attr_template_three"); + public static final AttributeKey THIRD_ATTR_THREE = stringKey("third.attr_three"); } \ No newline at end of file diff --git a/semantic-conventions/src/tests/data/jinja/attributesv2/attributes.yml b/semantic-conventions/src/tests/data/jinja/attributes_root_ns/attributes.yml similarity index 100% rename from semantic-conventions/src/tests/data/jinja/attributesv2/attributes.yml rename to semantic-conventions/src/tests/data/jinja/attributes_root_ns/attributes.yml diff --git a/semantic-conventions/src/tests/data/jinja/attributesv2/FooAttributes.java b/semantic-conventions/src/tests/data/jinja/attributes_root_ns/no_group_prefix/FooAttributes.java similarity index 100% rename from semantic-conventions/src/tests/data/jinja/attributesv2/FooAttributes.java rename to semantic-conventions/src/tests/data/jinja/attributes_root_ns/no_group_prefix/FooAttributes.java diff --git a/semantic-conventions/src/tests/data/jinja/attributesv2/OtherAttributes.java b/semantic-conventions/src/tests/data/jinja/attributes_root_ns/no_group_prefix/OtherAttributes.java similarity index 100% rename from semantic-conventions/src/tests/data/jinja/attributesv2/OtherAttributes.java rename to semantic-conventions/src/tests/data/jinja/attributes_root_ns/no_group_prefix/OtherAttributes.java diff --git a/semantic-conventions/src/tests/data/jinja/attributesv2/attributes_no_group_prefix.yml b/semantic-conventions/src/tests/data/jinja/attributes_root_ns/no_group_prefix/attributes_no_group_prefix.yml similarity index 100% rename from semantic-conventions/src/tests/data/jinja/attributesv2/attributes_no_group_prefix.yml rename to semantic-conventions/src/tests/data/jinja/attributes_root_ns/no_group_prefix/attributes_no_group_prefix.yml diff --git a/semantic-conventions/src/tests/data/jinja/attributesv2/ThirdAttributesStable.java b/semantic-conventions/src/tests/data/jinja/attributes_root_ns/stable/ThirdAttributesStable.java similarity index 100% rename from semantic-conventions/src/tests/data/jinja/attributesv2/ThirdAttributesStable.java rename to semantic-conventions/src/tests/data/jinja/attributes_root_ns/stable/ThirdAttributesStable.java diff --git a/semantic-conventions/src/tests/data/jinja/attributes_root_ns/stable/template_only_stable b/semantic-conventions/src/tests/data/jinja/attributes_root_ns/stable/template_only_stable new file mode 100644 index 00000000..deb83073 --- /dev/null +++ b/semantic-conventions/src/tests/data/jinja/attributes_root_ns/stable/template_only_stable @@ -0,0 +1,49 @@ +{%- macro to_java_return_type(type) -%} + {%- if type == "string" -%} + String + {%- elif type == "string[]" -%} + List + {%- elif type == "boolean" -%} + boolean + {%- elif type == "int" -%} + long + {%- elif type == "double" -%} + double + {%- else -%} + {{type}} + {%- endif -%} +{%- endmacro %} +{%- macro to_java_key_type(type) -%} + {%- if type == "string" -%} + stringKey + {%- elif type == "string[]" -%} + stringArrayKey + {%- elif type == "boolean" -%} + booleanKey + {%- elif type == "int" -%} + longKey + {%- elif type == "double" -%} + doubleKey + {%- else -%} + {{ type | to_camelcase(False)}}Key + {%- endif -%} +{%- endmacro %} +{%- set stable_attributes_and_templates = attributes_and_templates | select("is_stable") | list %} + +{%- if stable_attributes_and_templates | count > 0 %} +package io.opentelemetry.instrumentation.api.semconv; + +class {{ root_namespace | to_camelcase(True) }}Attributes { +{%- for attribute in stable_attributes_and_templates %} + + /** + * {{attribute.brief | render_markdown(code="{{@code {0}}}", paragraph="{0}")}} + */ + {% if attribute | is_template %} + public static final AttributeKeyTemplate<{{ to_java_return_type(attribute.instantiated_type | string) | first_up}}> {{attribute.fqn | to_const_name}} = {{to_java_key_type(attribute.instantiated_type | string)}}("{{attribute.fqn}}"); + {% else %} + public static final AttributeKey<{{ to_java_return_type(attribute.instantiated_type | string) | first_up }}> {{attribute.fqn | to_const_name}} = {{to_java_key_type(attribute.instantiated_type | string)}}("{{attribute.fqn}}"); + {% endif %} +{% endfor %} +} +{%- endif %} \ No newline at end of file diff --git a/semantic-conventions/src/tests/data/jinja/attributesv2/template_all b/semantic-conventions/src/tests/data/jinja/attributes_root_ns/template_all similarity index 54% rename from semantic-conventions/src/tests/data/jinja/attributesv2/template_all rename to semantic-conventions/src/tests/data/jinja/attributes_root_ns/template_all index 2d15368d..2b26c2a7 100644 --- a/semantic-conventions/src/tests/data/jinja/attributesv2/template_all +++ b/semantic-conventions/src/tests/data/jinja/attributes_root_ns/template_all @@ -30,21 +30,16 @@ {%- endmacro %} package io.opentelemetry.instrumentation.api.semconv; -class {{ group | to_camelcase(True) }}Attributes { -{%- for attribute in attributes %} +class {{ root_namespace | to_camelcase(True) }}Attributes { +{%- for attribute in attributes_and_templates %} /** * {{attribute.brief | render_markdown(code="{{@code {0}}}", paragraph="{0}")}} */ - public static final AttributeKey<{{ to_java_return_type(attribute.instantiated_type | string) | to_camelcase(True) }}> {{attribute.fqn | to_const_name}} = {{to_java_key_type(attribute.instantiated_type | string)}}("{{attribute.fqn}}"); -{# Extra line #} -{%- endfor %} -{%- for attribute_template in attribute_templates %} - - /** - * {{attribute_template.brief | render_markdown(code="{{@code {0}}}", paragraph="{0}")}} - */ - public static final AttributeKey<{{ to_java_return_type(attribute_template.instantiated_type | string) | to_camelcase(True)}}> {{attribute_template.fqn | to_const_name}} = {{to_java_key_type(attribute_template.instantiated_type | string)}}("{{attribute_template.fqn}}"); -{# Extra line #} -{%- endfor %} + {% if attribute | is_template %} + public static final AttributeKeyTemplate<{{ to_java_return_type(attribute.instantiated_type | string) | first_up}}> {{attribute.fqn | to_const_name}} = {{to_java_key_type(attribute.instantiated_type | string)}}("{{attribute.fqn}}"); + {% else %} + public static final AttributeKey<{{ to_java_return_type(attribute.instantiated_type | string) | first_up }}> {{attribute.fqn | to_const_name}} = {{to_java_key_type(attribute.instantiated_type | string)}}("{{attribute.fqn}}"); + {% endif %} +{% endfor %} } \ No newline at end of file diff --git a/semantic-conventions/src/tests/data/jinja/attributesv2/template_only_stable b/semantic-conventions/src/tests/data/jinja/attributesv2/template_only_stable deleted file mode 100644 index 45b342a5..00000000 --- a/semantic-conventions/src/tests/data/jinja/attributesv2/template_only_stable +++ /dev/null @@ -1,55 +0,0 @@ -{%- macro to_java_return_type(type) -%} - {%- if type == "string" -%} - String - {%- elif type == "string[]" -%} - List - {%- elif type == "boolean" -%} - boolean - {%- elif type == "int" -%} - long - {%- elif type == "double" -%} - double - {%- else -%} - {{type}} - {%- endif -%} -{%- endmacro %} -{%- macro to_java_key_type(type) -%} - {%- if type == "string" -%} - stringKey - {%- elif type == "string[]" -%} - stringArrayKey - {%- elif type == "boolean" -%} - booleanKey - {%- elif type == "int" -%} - longKey - {%- elif type == "double" -%} - doubleKey - {%- else -%} - {{ type | to_camelcase(False)}}Key - {%- endif -%} -{%- endmacro %} -{%- set stable_attributes = attributes | select("is_stable") | list %} -{%- set stable_attribute_templates = attribute_templates | select("is_stable") | list %} - -{%- if stable_attributes | count > 0 or stable_attribute_templates | count > 0 %} -package io.opentelemetry.instrumentation.api.semconv; - -class {{ group | to_camelcase(True) }}Attributes { -{%- for attribute in stable_attributes %} - - /** - * {{attribute.brief | render_markdown(code="{{@code {0}}}", paragraph="{0}")}} - */ - public static final AttributeKey<{{ to_java_return_type(attribute.instantiated_type | string) | to_camelcase(True)}}> {{attribute.fqn | to_const_name}} = {{to_java_key_type(attribute.instantiated_type | string)}}("{{attribute.fqn}}"); -{# Extra line #} -{%- endfor %} -{%- for attribute_template in stable_attribute_templates %} - - /** - * {{attribute_template.brief | render_markdown(code="{{@code {0}}}", paragraph="{0}")}} - */ - public static final AttributeKey<{{ to_java_return_type(attribute_template.instantiated_type | string) | to_camelcase(True)}}> {{attribute_template.fqn | to_const_name}} = {{to_java_key_type(attribute_template.instantiated_type | string)}}("{{attribute_template.fqn}}"); -{# Extra line #} -{%- endfor %} -} -{%- endif %} \ No newline at end of file diff --git a/semantic-conventions/src/tests/semconv/templating/test_code.py b/semantic-conventions/src/tests/semconv/templating/test_code.py index 7763bbe0..0a3f3b5c 100644 --- a/semantic-conventions/src/tests/semconv/templating/test_code.py +++ b/semantic-conventions/src/tests/semconv/templating/test_code.py @@ -14,9 +14,10 @@ def test_codegen_units(test_file_path, read_test_file): template_path = test_file_path("jinja", "metrics", "units_template") renderer = CodeRenderer({}, trim_whitespace=False) - output = io.StringIO() - renderer.render(semconv, template_path, output, None) - result = output.getvalue() + filename = os.path.join(tempfile.mkdtemp(), "Attributes.java") + renderer.render(semconv, template_path, filename, None) + with open(filename) as f: + result = f.read() expected = read_test_file("jinja", "metrics", "expected.java") @@ -34,9 +35,10 @@ def test_strip_blocks_enabled(test_file_path, read_test_file): ) renderer = CodeRenderer({}, trim_whitespace=True) - output = io.StringIO() - renderer.render(semconv, template_path, output, None) - result = output.getvalue() + filename = os.path.join(tempfile.mkdtemp(), "Attributes.java") + renderer.render(semconv, template_path, filename, None) + with open(filename) as f: + result = f.read() expected = read_test_file( "jinja", "metrics", "expected_trim_whitespace_enabled.java" @@ -55,82 +57,82 @@ def test_codegen_attribute_templates(test_file_path, read_test_file): template_path = test_file_path("jinja", "attribute_templates", "template") renderer = CodeRenderer({}, trim_whitespace=False) - output = io.StringIO() - renderer.render(semconv, template_path, output, None) - result = output.getvalue() - + filename = os.path.join(tempfile.mkdtemp(), "Attributes.java") + renderer.render(semconv, template_path, filename, None) + with open(filename) as f: + result = f.read() expected = read_test_file("jinja", "attribute_templates", "expected.java") assert result == expected -def test_codegen_attribute_v2_experimental(test_file_path, read_test_file): +def test_codegen_attribute_root_ns(test_file_path, read_test_file): semconv = SemanticConventionSet(debug=False) + semconv.parse( - test_file_path("jinja", "attributesv2", "attributes.yml") + test_file_path("jinja", "attributes_root_ns", "attributes.yml") ) semconv.finish() - template_path = test_file_path("jinja", "attributesv2", "template_all") + template_path = test_file_path("jinja", "attributes_root_ns", "template_all") renderer = CodeRenderer({}, trim_whitespace=True) + test_path = os.path.join("attributes_root_ns", "all") tmppath = tempfile.mkdtemp() - renderer.renderv2(semconv, template_path, os.path.join(tmppath, "Attributes.java")) - with open(os.path.join(tmppath, "FirstAttributes.java")) as first: - data = first.read() - expected = read_test_file("jinja", "attributesv2", "FirstAttributes.java") - assert data == expected + renderer.render(semconv, template_path, os.path.join(tmppath, "Attributes.java"), "root_namespace") + + first = read_test_file("jinja", test_path, "FirstAttributes.java") + check_file(tmppath, "FirstAttributes.java", first) - with open(os.path.join(tmppath, "SecondAttributes.java")) as second: - data = second.read() - expected = read_test_file("jinja", "attributesv2", "SecondAttributes.java") - assert data == expected + second = read_test_file("jinja", test_path, "SecondAttributes.java") + check_file(tmppath, "SecondAttributes.java", second) - with open(os.path.join(tmppath, "ThirdAttributes.java")) as second: - data = second.read() - expected = read_test_file("jinja", "attributesv2", "ThirdAttributesAll.java") - assert data == expected + third = read_test_file("jinja",test_path, "ThirdAttributes.java") + check_file(tmppath, "ThirdAttributes.java", third) -def test_codegen_attribute_v2_stable(test_file_path, read_test_file): +def test_codegen_attribute_root_ns_stable(test_file_path, read_test_file): semconv = SemanticConventionSet(debug=False) semconv.parse( - test_file_path("jinja", "attributesv2", "attributes.yml") + test_file_path("jinja", "attributes_root_ns", "attributes.yml") ) semconv.finish() - template_path = test_file_path("jinja", "attributesv2", "template_only_stable") + test_path = os.path.join("attributes_root_ns", "stable") + template_path = test_file_path("jinja", test_path, "template_only_stable") renderer = CodeRenderer({}, trim_whitespace=True) tmppath = tempfile.mkdtemp() - renderer.renderv2(semconv, template_path, os.path.join(tmppath, "Attributes.java")) - - with open(os.path.join(tmppath, "ThirdAttributes.java")) as second: - data = second.read() - expected = read_test_file("jinja", "attributesv2", "ThirdAttributesStable.java") - assert data == expected + renderer.render(semconv, template_path, os.path.join(tmppath, "Attributes.java"), "root_namespace") + thirdStable = read_test_file("jinja", test_path, "ThirdAttributesStable.java") + check_file(tmppath, "ThirdAttributes.java", thirdStable) assert not os.path.isfile(os.path.join(tmppath, "FirstAttributes.java")) assert not os.path.isfile(os.path.join(tmppath, "SecondAttributes.java")) -def test_codegen_attribute_v2_no_group_prefix(test_file_path, read_test_file): +def test_codegen_attribute_root_ns_no_group_prefix(test_file_path, read_test_file): semconv = SemanticConventionSet(debug=False) + + test_path = os.path.join("attributes_root_ns", "no_group_prefix") semconv.parse( - test_file_path("jinja", "attributesv2", "attributes_no_group_prefix.yml") + test_file_path("jinja", test_path, "attributes_no_group_prefix.yml") ) semconv.finish() - template_path = test_file_path("jinja", "attributesv2", "template_all") + template_path = test_file_path("jinja", "attributes_root_ns", "template_all") renderer = CodeRenderer({}, trim_whitespace=True) tmppath = tempfile.mkdtemp() - renderer.renderv2(semconv, template_path, os.path.join(tmppath, "Attributes.java")) - with open(os.path.join(tmppath, "FooAttributes.java")) as foo: - data = foo.read() - expected = read_test_file("jinja", "attributesv2", "FooAttributes.java") - assert data == expected - - with open(os.path.join(tmppath, "OtherAttributes.java")) as other: - data = other.read() - expected = read_test_file("jinja", "attributesv2", "OtherAttributes.java") - assert data == expected + renderer.render(semconv, template_path, os.path.join(tmppath, "Attributes.java"), "root_namespace") + + foo = read_test_file("jinja", test_path, "FooAttributes.java") + check_file(tmppath, "FooAttributes.java", foo) + + other = read_test_file("jinja", test_path, "OtherAttributes.java") + check_file(tmppath, "OtherAttributes.java", other) + + +def check_file(tmppath, actual_filename, expected_content): + with open(os.path.join(tmppath, actual_filename)) as f: + actual = f.read() + assert actual == expected_content \ No newline at end of file From 9b390551b82c73239707c26142d7de92ad324a04 Mon Sep 17 00:00:00 2001 From: Liudmila Molkova Date: Sat, 2 Dec 2023 12:09:41 -0800 Subject: [PATCH 04/18] more tests --- .../opentelemetry/semconv/templating/code.py | 5 +- .../single_file/AllAttributes.java | 41 ++++++++++++ .../single_file/template_single_file | 65 +++++++++++++++++++ .../src/tests/semconv/templating/test_code.py | 18 +++++ 4 files changed, 128 insertions(+), 1 deletion(-) create mode 100644 semantic-conventions/src/tests/data/jinja/attributes_root_ns/single_file/AllAttributes.java create mode 100644 semantic-conventions/src/tests/data/jinja/attributes_root_ns/single_file/template_single_file diff --git a/semantic-conventions/src/opentelemetry/semconv/templating/code.py b/semantic-conventions/src/opentelemetry/semconv/templating/code.py index 577033b6..bb8f5e93 100644 --- a/semantic-conventions/src/opentelemetry/semconv/templating/code.py +++ b/semantic-conventions/src/opentelemetry/semconv/templating/code.py @@ -303,7 +303,7 @@ def _render_group_by_root_namespace( data = { "template": template_path, - "attributes_and_templates": sorted(root_namespaces[ns], key=lambda a: a.fqn), + "attributes_and_templates": root_namespaces[ns], "root_namespace": sanitized_ns} data.update(self.parameters) @@ -317,6 +317,9 @@ def _grouped_attribute_definitions(self, semconvset): if attr.root_namespace not in root_namespaces: root_namespaces[attr.root_namespace] = [] root_namespaces[attr.root_namespace].append(attr) + + for ns in root_namespaces: + root_namespaces[ns] = sorted(root_namespaces[ns], key=lambda a: a.fqn) return root_namespaces def _write_template_to_file(self, template, data, output_name): diff --git a/semantic-conventions/src/tests/data/jinja/attributes_root_ns/single_file/AllAttributes.java b/semantic-conventions/src/tests/data/jinja/attributes_root_ns/single_file/AllAttributes.java new file mode 100644 index 00000000..1b7d3657 --- /dev/null +++ b/semantic-conventions/src/tests/data/jinja/attributes_root_ns/single_file/AllAttributes.java @@ -0,0 +1,41 @@ +package io.opentelemetry.instrumentation.api.semconv; + +class AllAttributes { + class FirstAttributes { + /** + * short description of attr_one + */ + public static final AttributeKey FIRST_ATTR_ONE = booleanKey("first.attr_one"); + + /** + * short description of attr_one_a + */ + public static final AttributeKey FIRST_ATTR_ONE_A = longKey("first.attr_one_a"); + + /** + * this is the description of attribute template + */ + public static final AttributeKeyTemplate FIRST_ATTR_TEMPLATE_ONE = stringKey("first.attr_template_one"); + } + class SecondAttributes { + /** + * short description of attr_two + */ + public static final AttributeKey SECOND_ATTR_TWO = stringKey("second.attr_two"); + } + class ThirdAttributes { + /** + * this is the description of attribute template + */ + public static final AttributeKeyTemplate THIRD_ATTR_TEMPLATE_THREE = stringKey("third.attr_template_three"); + + /** + * short description of attr_three + */ + public static final AttributeKey THIRD_ATTR_THREE = stringKey("third.attr_three"); + } + /** + * short description of attr_four + */ + public static final AttributeKey ATTR_FOUR = stringKey("attr_four"); +} \ No newline at end of file diff --git a/semantic-conventions/src/tests/data/jinja/attributes_root_ns/single_file/template_single_file b/semantic-conventions/src/tests/data/jinja/attributes_root_ns/single_file/template_single_file new file mode 100644 index 00000000..168c8323 --- /dev/null +++ b/semantic-conventions/src/tests/data/jinja/attributes_root_ns/single_file/template_single_file @@ -0,0 +1,65 @@ +{%- macro to_java_return_type(type) -%} + {%- if type == "string" -%} + String + {%- elif type == "string[]" -%} + List + {%- elif type == "boolean" -%} + boolean + {%- elif type == "int" -%} + long + {%- elif type == "double" -%} + double + {%- else -%} + {{type}} + {%- endif -%} +{%- endmacro %} +{%- macro to_java_key_type(type) -%} + {%- if type == "string" -%} + stringKey + {%- elif type == "string[]" -%} + stringArrayKey + {%- elif type == "boolean" -%} + booleanKey + {%- elif type == "int" -%} + longKey + {%- elif type == "double" -%} + doubleKey + {%- else -%} + {{ type | to_camelcase(False)}}Key + {%- endif -%} +{%- endmacro %} +package io.opentelemetry.instrumentation.api.semconv; + +class AllAttributes { +{%- for root_ns in attributes_and_templates %} + + {% if root_ns != "" %} + class {{root_ns | first_up}}Attributes { + {%- for attribute in attributes_and_templates[root_ns] %} + + /** + * {{attribute.brief | render_markdown(code="{{@code {0}}}", paragraph="{0}")}} + */ + {% if attribute | is_template %} + public static final AttributeKeyTemplate<{{ to_java_return_type(attribute.instantiated_type | string) | first_up}}> {{attribute.fqn | to_const_name}} = {{to_java_key_type(attribute.instantiated_type | string)}}("{{attribute.fqn}}"); + {% else %} + public static final AttributeKey<{{ to_java_return_type(attribute.instantiated_type | string) | first_up }}> {{attribute.fqn | to_const_name}} = {{to_java_key_type(attribute.instantiated_type | string)}}("{{attribute.fqn}}"); + {% endif %} + + {%- endfor %} + } + {%- endif %} +{%- endfor %} + {# non-namespaced attributes #} + {%- for attribute in attributes_and_templates[""] %} + /** + * {{attribute.brief | render_markdown(code="{{@code {0}}}", paragraph="{0}")}} + */ + {% if attribute | is_template %} + public static final AttributeKeyTemplate<{{ to_java_return_type(attribute.instantiated_type | string) | first_up}}> {{attribute.fqn | to_const_name}} = {{to_java_key_type(attribute.instantiated_type | string)}}("{{attribute.fqn}}"); + {% else %} + public static final AttributeKey<{{ to_java_return_type(attribute.instantiated_type | string) | first_up }}> {{attribute.fqn | to_const_name}} = {{to_java_key_type(attribute.instantiated_type | string)}}("{{attribute.fqn}}"); + {% endif %} + + {%- endfor %} +} \ No newline at end of file diff --git a/semantic-conventions/src/tests/semconv/templating/test_code.py b/semantic-conventions/src/tests/semconv/templating/test_code.py index 0a3f3b5c..a7dfd115 100644 --- a/semantic-conventions/src/tests/semconv/templating/test_code.py +++ b/semantic-conventions/src/tests/semconv/templating/test_code.py @@ -131,8 +131,26 @@ def test_codegen_attribute_root_ns_no_group_prefix(test_file_path, read_test_fil other = read_test_file("jinja", test_path, "OtherAttributes.java") check_file(tmppath, "OtherAttributes.java", other) +def test_codegen_attribute_root_ns_single_file(test_file_path, read_test_file): + semconv = SemanticConventionSet(debug=False) + + semconv.parse( + test_file_path("jinja", "attributes_root_ns", "attributes.yml") + ) + semconv.finish() + + test_path = os.path.join("attributes_root_ns", "single_file") + template_path = test_file_path("jinja", test_path, "template_single_file") + renderer = CodeRenderer({}, trim_whitespace=True) + + tmppath = tempfile.mkdtemp() + renderer.render(semconv, template_path, os.path.join(tmppath, "AllAttributes.java"), None) + + all = read_test_file("jinja", test_path, "AllAttributes.java") + check_file(tmppath, "AllAttributes.java", all) def check_file(tmppath, actual_filename, expected_content): with open(os.path.join(tmppath, actual_filename)) as f: actual = f.read() + print(actual) assert actual == expected_content \ No newline at end of file From 2b9c55fb13381d7d397371b8597a3ad085d45194 Mon Sep 17 00:00:00 2001 From: Liudmila Molkova Date: Sat, 2 Dec 2023 12:14:20 -0800 Subject: [PATCH 05/18] lint --- semantic-conventions/CHANGELOG.md | 2 +- .../src/opentelemetry/semconv/main.py | 4 +- .../semconv/model/constraints.py | 3 +- .../semconv/model/semantic_attribute.py | 11 ++-- .../semconv/model/semantic_convention.py | 12 ++--- .../opentelemetry/semconv/templating/code.py | 43 +++++++++------ .../semconv/templating/markdown/__init__.py | 22 +++----- .../tests/semconv/model/test_correct_parse.py | 7 +-- .../semconv/model/test_error_detection.py | 7 +-- .../semconv/model/test_semantic_convention.py | 4 +- .../model/test_semantic_convention_units.py | 5 +- .../src/tests/semconv/model/test_utils.py | 1 - .../src/tests/semconv/templating/test_code.py | 52 ++++++++++++------- .../tests/semconv/templating/test_markdown.py | 3 +- 14 files changed, 88 insertions(+), 88 deletions(-) diff --git a/semantic-conventions/CHANGELOG.md b/semantic-conventions/CHANGELOG.md index 4c3c50c9..a0633aa3 100644 --- a/semantic-conventions/CHANGELOG.md +++ b/semantic-conventions/CHANGELOG.md @@ -7,7 +7,7 @@ Please update the changelog as part of any significant pull request. - Added code-generation mode that groups attributes by the root namespace and ability to wring each group into individual file. [BREAKING] The `--file-per-group ` that used to create multiple directories (like `output//file`) now generates multiple files (`output/file`) instead. - ([#TODO](https://github.com/open-telemetry/build-tools/pull/TODO)) + ([#243](https://github.com/open-telemetry/build-tools/pull/243)) ## v0.23.0 diff --git a/semantic-conventions/src/opentelemetry/semconv/main.py b/semantic-conventions/src/opentelemetry/semconv/main.py index 9a967277..d9d6db84 100644 --- a/semantic-conventions/src/opentelemetry/semconv/main.py +++ b/semantic-conventions/src/opentelemetry/semconv/main.py @@ -20,9 +20,7 @@ from typing import List from opentelemetry.semconv.model.semantic_convention import ( - CONVENTION_CLS_BY_GROUP_TYPE, - SemanticConventionSet, -) + CONVENTION_CLS_BY_GROUP_TYPE, SemanticConventionSet) from opentelemetry.semconv.templating.code import CodeRenderer from opentelemetry.semconv.templating.markdown import MarkdownRenderer from opentelemetry.semconv.templating.markdown.options import MarkdownOptions diff --git a/semantic-conventions/src/opentelemetry/semconv/model/constraints.py b/semantic-conventions/src/opentelemetry/semconv/model/constraints.py index 2e44119c..77af3682 100644 --- a/semantic-conventions/src/opentelemetry/semconv/model/constraints.py +++ b/semantic-conventions/src/opentelemetry/semconv/model/constraints.py @@ -15,11 +15,10 @@ from dataclasses import dataclass, replace from typing import List, Tuple -from ruamel.yaml.comments import CommentedSeq - from opentelemetry.semconv.model.exceptions import ValidationError from opentelemetry.semconv.model.semantic_attribute import SemanticAttribute from opentelemetry.semconv.model.utils import validate_values +from ruamel.yaml.comments import CommentedSeq # We cannot frozen due to later evaluation of the attributes diff --git a/semantic-conventions/src/opentelemetry/semconv/model/semantic_attribute.py b/semantic-conventions/src/opentelemetry/semconv/model/semantic_attribute.py index 16073ccd..75a0c43d 100644 --- a/semantic-conventions/src/opentelemetry/semconv/model/semantic_attribute.py +++ b/semantic-conventions/src/opentelemetry/semconv/model/semantic_attribute.py @@ -18,14 +18,10 @@ from enum import Enum from typing import Dict, List, Optional, Union -from ruamel.yaml.comments import CommentedMap, CommentedSeq - from opentelemetry.semconv.model.exceptions import ValidationError -from opentelemetry.semconv.model.utils import ( - check_no_missing_keys, - validate_id, - validate_values, -) +from opentelemetry.semconv.model.utils import (check_no_missing_keys, + validate_id, validate_values) +from ruamel.yaml.comments import CommentedMap, CommentedSeq TEMPLATE_PREFIX = "template[" TEMPLATE_SUFFIX = "]" @@ -344,6 +340,7 @@ def equivalent_to(self, other: "SemanticAttribute"): return True return False + class AttributeType: # https://yaml.org/type/bool.html diff --git a/semantic-conventions/src/opentelemetry/semconv/model/semantic_convention.py b/semantic-conventions/src/opentelemetry/semconv/model/semantic_convention.py index 13ade651..adca8b35 100644 --- a/semantic-conventions/src/opentelemetry/semconv/model/semantic_convention.py +++ b/semantic-conventions/src/opentelemetry/semconv/model/semantic_convention.py @@ -18,16 +18,14 @@ from enum import Enum from typing import Dict, Optional, Tuple, Union -from ruamel.yaml import YAML - -from opentelemetry.semconv.model.constraints import AnyOf, Include, parse_constraints +from opentelemetry.semconv.model.constraints import (AnyOf, Include, + parse_constraints) from opentelemetry.semconv.model.exceptions import ValidationError -from opentelemetry.semconv.model.semantic_attribute import ( - AttributeType, - SemanticAttribute, -) +from opentelemetry.semconv.model.semantic_attribute import (AttributeType, + SemanticAttribute) from opentelemetry.semconv.model.unit_member import UnitMember from opentelemetry.semconv.model.utils import ValidatableYamlNode, validate_id +from ruamel.yaml import YAML class SpanKind(Enum): diff --git a/semantic-conventions/src/opentelemetry/semconv/templating/code.py b/semantic-conventions/src/opentelemetry/semconv/templating/code.py index bb8f5e93..2a9b6c12 100644 --- a/semantic-conventions/src/opentelemetry/semconv/templating/code.py +++ b/semantic-conventions/src/opentelemetry/semconv/templating/code.py @@ -19,15 +19,13 @@ import mistune from jinja2 import Environment, FileSystemLoader, select_autoescape - -from opentelemetry.semconv.model.semantic_attribute import ( - AttributeType, - RequirementLevel, - SemanticAttribute, - StabilityLevel, - TextWithLinks, -) -from opentelemetry.semconv.model.semantic_convention import SemanticConventionSet +from opentelemetry.semconv.model.semantic_attribute import (AttributeType, + RequirementLevel, + SemanticAttribute, + StabilityLevel, + TextWithLinks) +from opentelemetry.semconv.model.semantic_convention import \ + SemanticConventionSet from opentelemetry.semconv.model.utils import ID_RE @@ -147,33 +145,42 @@ def regex_replace(text: str, pattern: str, replace: str): def merge(elems: typing.List, elm): return elems.extend(elm) + def to_const_name(name: str) -> str: return name.upper().replace(".", "_").replace("-", "_") + def to_camelcase(name: str, first_upper=False) -> str: first, *rest = name.replace("_", ".").split(".") if first_upper: first = first.capitalize() return first + "".join(word.capitalize() for word in rest) + def first_up(name: str) -> str: return name[0].upper() + name[1:] + def is_stable(attribute: SemanticAttribute) -> bool: return attribute.stability == StabilityLevel.STABLE + def is_deprecated(attribute: SemanticAttribute) -> bool: return attribute.stability == StabilityLevel.DEPRECATED + def is_experimental(attribute: SemanticAttribute) -> bool: return attribute.stability == StabilityLevel.EXPERIMENTAL + def is_definition(attribute: SemanticAttribute) -> bool: return attribute.is_local and attribute.ref is None + def is_template(attribute: SemanticAttribute) -> bool: return AttributeType.is_template_type(attribute.attr_type) + class CodeRenderer: pattern = f"{{{ID_RE.pattern}}}" @@ -263,15 +270,18 @@ def render( ) self.setup_environment(env, self.trim_whitespace) if pattern == "root_namespace": - self._render_group_by_root_namespace(semconvset, template_path, file_name, output_file, env) + self._render_group_by_root_namespace( + semconvset, template_path, file_name, output_file, env + ) elif pattern is not None: - self._render_by_pattern(semconvset, template_path, file_name, output_file, pattern, env) + self._render_by_pattern( + semconvset, template_path, file_name, output_file, pattern, env + ) else: data = self.get_data_single_file(semconvset, template_path) template = env.get_template(file_name, globals=data) self._write_template_to_file(template, data, output_file) - def _render_by_pattern( self, semconvset: SemanticConventionSet, @@ -304,7 +314,8 @@ def _render_group_by_root_namespace( data = { "template": template_path, "attributes_and_templates": root_namespaces[ns], - "root_namespace": sanitized_ns} + "root_namespace": sanitized_ns, + } data.update(self.parameters) template = env.get_template(file_name, globals=data) @@ -313,7 +324,9 @@ def _render_group_by_root_namespace( def _grouped_attribute_definitions(self, semconvset): root_namespaces = {} for semconv in semconvset.models.values(): - for attr in filter(lambda a: is_definition(a), semconv.attributes_and_templates): + for attr in filter( + lambda a: is_definition(a), semconv.attributes_and_templates + ): if attr.root_namespace not in root_namespaces: root_namespaces[attr.root_namespace] = [] root_namespaces[attr.root_namespace].append(attr) @@ -328,6 +341,6 @@ def _write_template_to_file(self, template, data, output_name): template.globals["RequirementLevel"] = RequirementLevel content = template.render(data) - if (content != ""): + if content != "": with open(output_name, "w") as f: f.write(content) diff --git a/semantic-conventions/src/opentelemetry/semconv/templating/markdown/__init__.py b/semantic-conventions/src/opentelemetry/semconv/templating/markdown/__init__.py index 64c8b744..63b6dd4a 100644 --- a/semantic-conventions/src/opentelemetry/semconv/templating/markdown/__init__.py +++ b/semantic-conventions/src/opentelemetry/semconv/templating/markdown/__init__.py @@ -21,21 +21,15 @@ from pathlib import PurePath from opentelemetry.semconv.model.constraints import AnyOf, Include -from opentelemetry.semconv.model.semantic_attribute import ( - AttributeType, - EnumAttributeType, - EnumMember, - RequirementLevel, - SemanticAttribute, - StabilityLevel, -) +from opentelemetry.semconv.model.semantic_attribute import (AttributeType, + EnumAttributeType, + EnumMember, + RequirementLevel, + SemanticAttribute, + StabilityLevel) from opentelemetry.semconv.model.semantic_convention import ( - BaseSemanticConvention, - EventSemanticConvention, - MetricSemanticConvention, - SemanticConventionSet, - UnitSemanticConvention, -) + BaseSemanticConvention, EventSemanticConvention, MetricSemanticConvention, + SemanticConventionSet, UnitSemanticConvention) from opentelemetry.semconv.model.utils import ID_RE from opentelemetry.semconv.templating.markdown.options import MarkdownOptions 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 11597797..9f7c7fff 100644 --- a/semantic-conventions/src/tests/semconv/model/test_correct_parse.py +++ b/semantic-conventions/src/tests/semconv/model/test_correct_parse.py @@ -19,11 +19,8 @@ from opentelemetry.semconv.model.constraints import AnyOf, Include from opentelemetry.semconv.model.semantic_attribute import StabilityLevel from opentelemetry.semconv.model.semantic_convention import ( - EventSemanticConvention, - MetricSemanticConvention, - SemanticConventionSet, - SpanSemanticConvention, -) + EventSemanticConvention, MetricSemanticConvention, SemanticConventionSet, + SpanSemanticConvention) class TestCorrectParse(unittest.TestCase): 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 7d53a3a6..efcd1c35 100644 --- a/semantic-conventions/src/tests/semconv/model/test_error_detection.py +++ b/semantic-conventions/src/tests/semconv/model/test_error_detection.py @@ -15,13 +15,10 @@ import os import unittest -from ruamel.yaml.constructor import DuplicateKeyError - from opentelemetry.semconv.model.exceptions import ValidationError from opentelemetry.semconv.model.semantic_convention import ( - SemanticConventionSet, - parse_semantic_convention_groups, -) + SemanticConventionSet, parse_semantic_convention_groups) +from ruamel.yaml.constructor import DuplicateKeyError class TestCorrectErrorDetection(unittest.TestCase): diff --git a/semantic-conventions/src/tests/semconv/model/test_semantic_convention.py b/semantic-conventions/src/tests/semconv/model/test_semantic_convention.py index df7a133f..7b7e05de 100644 --- a/semantic-conventions/src/tests/semconv/model/test_semantic_convention.py +++ b/semantic-conventions/src/tests/semconv/model/test_semantic_convention.py @@ -15,9 +15,7 @@ import os from opentelemetry.semconv.model.semantic_convention import ( - SpanKind, - parse_semantic_convention_groups, -) + SpanKind, parse_semantic_convention_groups) def test_parse_basic(open_test_file): diff --git a/semantic-conventions/src/tests/semconv/model/test_semantic_convention_units.py b/semantic-conventions/src/tests/semconv/model/test_semantic_convention_units.py index 5728a749..9cb62586 100644 --- a/semantic-conventions/src/tests/semconv/model/test_semantic_convention_units.py +++ b/semantic-conventions/src/tests/semconv/model/test_semantic_convention_units.py @@ -1,12 +1,9 @@ import os import pytest - from opentelemetry.semconv.model.exceptions import ValidationError from opentelemetry.semconv.model.semantic_convention import ( - UnitSemanticConvention, - parse_semantic_convention_groups, -) + UnitSemanticConvention, parse_semantic_convention_groups) def test_build_units(open_test_file): diff --git a/semantic-conventions/src/tests/semconv/model/test_utils.py b/semantic-conventions/src/tests/semconv/model/test_utils.py index 1bffee42..0438c5e4 100644 --- a/semantic-conventions/src/tests/semconv/model/test_utils.py +++ b/semantic-conventions/src/tests/semconv/model/test_utils.py @@ -12,7 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. import pytest - from opentelemetry.semconv.model.exceptions import ValidationError from opentelemetry.semconv.model.utils import validate_id, validate_values diff --git a/semantic-conventions/src/tests/semconv/templating/test_code.py b/semantic-conventions/src/tests/semconv/templating/test_code.py index a7dfd115..bd0e208f 100644 --- a/semantic-conventions/src/tests/semconv/templating/test_code.py +++ b/semantic-conventions/src/tests/semconv/templating/test_code.py @@ -1,8 +1,8 @@ -import io import os import tempfile -from opentelemetry.semconv.model.semantic_convention import SemanticConventionSet +from opentelemetry.semconv.model.semantic_convention import \ + SemanticConventionSet from opentelemetry.semconv.templating.code import CodeRenderer @@ -69,9 +69,7 @@ def test_codegen_attribute_templates(test_file_path, read_test_file): def test_codegen_attribute_root_ns(test_file_path, read_test_file): semconv = SemanticConventionSet(debug=False) - semconv.parse( - test_file_path("jinja", "attributes_root_ns", "attributes.yml") - ) + semconv.parse(test_file_path("jinja", "attributes_root_ns", "attributes.yml")) semconv.finish() template_path = test_file_path("jinja", "attributes_root_ns", "template_all") @@ -79,7 +77,12 @@ def test_codegen_attribute_root_ns(test_file_path, read_test_file): test_path = os.path.join("attributes_root_ns", "all") tmppath = tempfile.mkdtemp() - renderer.render(semconv, template_path, os.path.join(tmppath, "Attributes.java"), "root_namespace") + renderer.render( + semconv, + template_path, + os.path.join(tmppath, "Attributes.java"), + "root_namespace", + ) first = read_test_file("jinja", test_path, "FirstAttributes.java") check_file(tmppath, "FirstAttributes.java", first) @@ -87,15 +90,13 @@ def test_codegen_attribute_root_ns(test_file_path, read_test_file): second = read_test_file("jinja", test_path, "SecondAttributes.java") check_file(tmppath, "SecondAttributes.java", second) - third = read_test_file("jinja",test_path, "ThirdAttributes.java") + third = read_test_file("jinja", test_path, "ThirdAttributes.java") check_file(tmppath, "ThirdAttributes.java", third) def test_codegen_attribute_root_ns_stable(test_file_path, read_test_file): semconv = SemanticConventionSet(debug=False) - semconv.parse( - test_file_path("jinja", "attributes_root_ns", "attributes.yml") - ) + semconv.parse(test_file_path("jinja", "attributes_root_ns", "attributes.yml")) semconv.finish() test_path = os.path.join("attributes_root_ns", "stable") @@ -103,27 +104,36 @@ def test_codegen_attribute_root_ns_stable(test_file_path, read_test_file): renderer = CodeRenderer({}, trim_whitespace=True) tmppath = tempfile.mkdtemp() - renderer.render(semconv, template_path, os.path.join(tmppath, "Attributes.java"), "root_namespace") + renderer.render( + semconv, + template_path, + os.path.join(tmppath, "Attributes.java"), + "root_namespace", + ) thirdStable = read_test_file("jinja", test_path, "ThirdAttributesStable.java") check_file(tmppath, "ThirdAttributes.java", thirdStable) assert not os.path.isfile(os.path.join(tmppath, "FirstAttributes.java")) assert not os.path.isfile(os.path.join(tmppath, "SecondAttributes.java")) + def test_codegen_attribute_root_ns_no_group_prefix(test_file_path, read_test_file): semconv = SemanticConventionSet(debug=False) test_path = os.path.join("attributes_root_ns", "no_group_prefix") - semconv.parse( - test_file_path("jinja", test_path, "attributes_no_group_prefix.yml") - ) + semconv.parse(test_file_path("jinja", test_path, "attributes_no_group_prefix.yml")) semconv.finish() template_path = test_file_path("jinja", "attributes_root_ns", "template_all") renderer = CodeRenderer({}, trim_whitespace=True) tmppath = tempfile.mkdtemp() - renderer.render(semconv, template_path, os.path.join(tmppath, "Attributes.java"), "root_namespace") + renderer.render( + semconv, + template_path, + os.path.join(tmppath, "Attributes.java"), + "root_namespace", + ) foo = read_test_file("jinja", test_path, "FooAttributes.java") check_file(tmppath, "FooAttributes.java", foo) @@ -131,12 +141,11 @@ def test_codegen_attribute_root_ns_no_group_prefix(test_file_path, read_test_fil other = read_test_file("jinja", test_path, "OtherAttributes.java") check_file(tmppath, "OtherAttributes.java", other) + def test_codegen_attribute_root_ns_single_file(test_file_path, read_test_file): semconv = SemanticConventionSet(debug=False) - semconv.parse( - test_file_path("jinja", "attributes_root_ns", "attributes.yml") - ) + semconv.parse(test_file_path("jinja", "attributes_root_ns", "attributes.yml")) semconv.finish() test_path = os.path.join("attributes_root_ns", "single_file") @@ -144,13 +153,16 @@ def test_codegen_attribute_root_ns_single_file(test_file_path, read_test_file): renderer = CodeRenderer({}, trim_whitespace=True) tmppath = tempfile.mkdtemp() - renderer.render(semconv, template_path, os.path.join(tmppath, "AllAttributes.java"), None) + renderer.render( + semconv, template_path, os.path.join(tmppath, "AllAttributes.java"), None + ) all = read_test_file("jinja", test_path, "AllAttributes.java") check_file(tmppath, "AllAttributes.java", all) + def check_file(tmppath, actual_filename, expected_content): with open(os.path.join(tmppath, actual_filename)) as f: actual = f.read() print(actual) - assert actual == expected_content \ No newline at end of file + assert actual == expected_content diff --git a/semantic-conventions/src/tests/semconv/templating/test_markdown.py b/semantic-conventions/src/tests/semconv/templating/test_markdown.py index ed6adba4..12e34026 100644 --- a/semantic-conventions/src/tests/semconv/templating/test_markdown.py +++ b/semantic-conventions/src/tests/semconv/templating/test_markdown.py @@ -18,7 +18,8 @@ from pathlib import Path from typing import Optional, Sequence -from opentelemetry.semconv.model.semantic_convention import SemanticConventionSet +from opentelemetry.semconv.model.semantic_convention import \ + SemanticConventionSet from opentelemetry.semconv.templating.markdown import MarkdownRenderer from opentelemetry.semconv.templating.markdown.options import MarkdownOptions From 0aa3dc5c2c306ef55b61c2aab9ff0ceec96a1763 Mon Sep 17 00:00:00 2001 From: Liudmila Molkova Date: Sat, 2 Dec 2023 12:17:09 -0800 Subject: [PATCH 06/18] lint --- .../src/opentelemetry/semconv/main.py | 4 +++- .../semconv/model/semantic_attribute.py | 7 ++++-- .../semconv/model/semantic_convention.py | 9 ++++---- .../opentelemetry/semconv/templating/code.py | 15 +++++++------ .../semconv/templating/markdown/__init__.py | 22 ++++++++++++------- .../tests/semconv/model/test_correct_parse.py | 7 ++++-- .../semconv/model/test_error_detection.py | 4 +++- .../semconv/model/test_semantic_convention.py | 4 +++- .../model/test_semantic_convention_units.py | 4 +++- .../src/tests/semconv/templating/test_code.py | 3 +-- .../tests/semconv/templating/test_markdown.py | 3 +-- 11 files changed, 51 insertions(+), 31 deletions(-) diff --git a/semantic-conventions/src/opentelemetry/semconv/main.py b/semantic-conventions/src/opentelemetry/semconv/main.py index d9d6db84..9a967277 100644 --- a/semantic-conventions/src/opentelemetry/semconv/main.py +++ b/semantic-conventions/src/opentelemetry/semconv/main.py @@ -20,7 +20,9 @@ from typing import List from opentelemetry.semconv.model.semantic_convention import ( - CONVENTION_CLS_BY_GROUP_TYPE, SemanticConventionSet) + CONVENTION_CLS_BY_GROUP_TYPE, + SemanticConventionSet, +) from opentelemetry.semconv.templating.code import CodeRenderer from opentelemetry.semconv.templating.markdown import MarkdownRenderer from opentelemetry.semconv.templating.markdown.options import MarkdownOptions diff --git a/semantic-conventions/src/opentelemetry/semconv/model/semantic_attribute.py b/semantic-conventions/src/opentelemetry/semconv/model/semantic_attribute.py index 75a0c43d..682b0a0a 100644 --- a/semantic-conventions/src/opentelemetry/semconv/model/semantic_attribute.py +++ b/semantic-conventions/src/opentelemetry/semconv/model/semantic_attribute.py @@ -19,8 +19,11 @@ from typing import Dict, List, Optional, Union from opentelemetry.semconv.model.exceptions import ValidationError -from opentelemetry.semconv.model.utils import (check_no_missing_keys, - validate_id, validate_values) +from opentelemetry.semconv.model.utils import ( + check_no_missing_keys, + validate_id, + validate_values, +) from ruamel.yaml.comments import CommentedMap, CommentedSeq TEMPLATE_PREFIX = "template[" diff --git a/semantic-conventions/src/opentelemetry/semconv/model/semantic_convention.py b/semantic-conventions/src/opentelemetry/semconv/model/semantic_convention.py index adca8b35..ca269cfa 100644 --- a/semantic-conventions/src/opentelemetry/semconv/model/semantic_convention.py +++ b/semantic-conventions/src/opentelemetry/semconv/model/semantic_convention.py @@ -18,11 +18,12 @@ from enum import Enum from typing import Dict, Optional, Tuple, Union -from opentelemetry.semconv.model.constraints import (AnyOf, Include, - parse_constraints) +from opentelemetry.semconv.model.constraints import AnyOf, Include, parse_constraints from opentelemetry.semconv.model.exceptions import ValidationError -from opentelemetry.semconv.model.semantic_attribute import (AttributeType, - SemanticAttribute) +from opentelemetry.semconv.model.semantic_attribute import ( + AttributeType, + SemanticAttribute, +) from opentelemetry.semconv.model.unit_member import UnitMember from opentelemetry.semconv.model.utils import ValidatableYamlNode, validate_id from ruamel.yaml import YAML diff --git a/semantic-conventions/src/opentelemetry/semconv/templating/code.py b/semantic-conventions/src/opentelemetry/semconv/templating/code.py index 2a9b6c12..1f2bb84e 100644 --- a/semantic-conventions/src/opentelemetry/semconv/templating/code.py +++ b/semantic-conventions/src/opentelemetry/semconv/templating/code.py @@ -19,13 +19,14 @@ import mistune from jinja2 import Environment, FileSystemLoader, select_autoescape -from opentelemetry.semconv.model.semantic_attribute import (AttributeType, - RequirementLevel, - SemanticAttribute, - StabilityLevel, - TextWithLinks) -from opentelemetry.semconv.model.semantic_convention import \ - SemanticConventionSet +from opentelemetry.semconv.model.semantic_attribute import ( + AttributeType, + RequirementLevel, + SemanticAttribute, + StabilityLevel, + TextWithLinks, +) +from opentelemetry.semconv.model.semantic_convention import SemanticConventionSet from opentelemetry.semconv.model.utils import ID_RE diff --git a/semantic-conventions/src/opentelemetry/semconv/templating/markdown/__init__.py b/semantic-conventions/src/opentelemetry/semconv/templating/markdown/__init__.py index 63b6dd4a..64c8b744 100644 --- a/semantic-conventions/src/opentelemetry/semconv/templating/markdown/__init__.py +++ b/semantic-conventions/src/opentelemetry/semconv/templating/markdown/__init__.py @@ -21,15 +21,21 @@ from pathlib import PurePath from opentelemetry.semconv.model.constraints import AnyOf, Include -from opentelemetry.semconv.model.semantic_attribute import (AttributeType, - EnumAttributeType, - EnumMember, - RequirementLevel, - SemanticAttribute, - StabilityLevel) +from opentelemetry.semconv.model.semantic_attribute import ( + AttributeType, + EnumAttributeType, + EnumMember, + RequirementLevel, + SemanticAttribute, + StabilityLevel, +) from opentelemetry.semconv.model.semantic_convention import ( - BaseSemanticConvention, EventSemanticConvention, MetricSemanticConvention, - SemanticConventionSet, UnitSemanticConvention) + BaseSemanticConvention, + EventSemanticConvention, + MetricSemanticConvention, + SemanticConventionSet, + UnitSemanticConvention, +) from opentelemetry.semconv.model.utils import ID_RE from opentelemetry.semconv.templating.markdown.options import MarkdownOptions 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 9f7c7fff..11597797 100644 --- a/semantic-conventions/src/tests/semconv/model/test_correct_parse.py +++ b/semantic-conventions/src/tests/semconv/model/test_correct_parse.py @@ -19,8 +19,11 @@ from opentelemetry.semconv.model.constraints import AnyOf, Include from opentelemetry.semconv.model.semantic_attribute import StabilityLevel from opentelemetry.semconv.model.semantic_convention import ( - EventSemanticConvention, MetricSemanticConvention, SemanticConventionSet, - SpanSemanticConvention) + EventSemanticConvention, + MetricSemanticConvention, + SemanticConventionSet, + SpanSemanticConvention, +) class TestCorrectParse(unittest.TestCase): 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 efcd1c35..ea9b7127 100644 --- a/semantic-conventions/src/tests/semconv/model/test_error_detection.py +++ b/semantic-conventions/src/tests/semconv/model/test_error_detection.py @@ -17,7 +17,9 @@ from opentelemetry.semconv.model.exceptions import ValidationError from opentelemetry.semconv.model.semantic_convention import ( - SemanticConventionSet, parse_semantic_convention_groups) + SemanticConventionSet, + parse_semantic_convention_groups, +) from ruamel.yaml.constructor import DuplicateKeyError diff --git a/semantic-conventions/src/tests/semconv/model/test_semantic_convention.py b/semantic-conventions/src/tests/semconv/model/test_semantic_convention.py index 7b7e05de..df7a133f 100644 --- a/semantic-conventions/src/tests/semconv/model/test_semantic_convention.py +++ b/semantic-conventions/src/tests/semconv/model/test_semantic_convention.py @@ -15,7 +15,9 @@ import os from opentelemetry.semconv.model.semantic_convention import ( - SpanKind, parse_semantic_convention_groups) + SpanKind, + parse_semantic_convention_groups, +) def test_parse_basic(open_test_file): diff --git a/semantic-conventions/src/tests/semconv/model/test_semantic_convention_units.py b/semantic-conventions/src/tests/semconv/model/test_semantic_convention_units.py index 9cb62586..019248b3 100644 --- a/semantic-conventions/src/tests/semconv/model/test_semantic_convention_units.py +++ b/semantic-conventions/src/tests/semconv/model/test_semantic_convention_units.py @@ -3,7 +3,9 @@ import pytest from opentelemetry.semconv.model.exceptions import ValidationError from opentelemetry.semconv.model.semantic_convention import ( - UnitSemanticConvention, parse_semantic_convention_groups) + UnitSemanticConvention, + parse_semantic_convention_groups, +) def test_build_units(open_test_file): diff --git a/semantic-conventions/src/tests/semconv/templating/test_code.py b/semantic-conventions/src/tests/semconv/templating/test_code.py index bd0e208f..164581f9 100644 --- a/semantic-conventions/src/tests/semconv/templating/test_code.py +++ b/semantic-conventions/src/tests/semconv/templating/test_code.py @@ -1,8 +1,7 @@ import os import tempfile -from opentelemetry.semconv.model.semantic_convention import \ - SemanticConventionSet +from opentelemetry.semconv.model.semantic_convention import SemanticConventionSet from opentelemetry.semconv.templating.code import CodeRenderer diff --git a/semantic-conventions/src/tests/semconv/templating/test_markdown.py b/semantic-conventions/src/tests/semconv/templating/test_markdown.py index 12e34026..ed6adba4 100644 --- a/semantic-conventions/src/tests/semconv/templating/test_markdown.py +++ b/semantic-conventions/src/tests/semconv/templating/test_markdown.py @@ -18,8 +18,7 @@ from pathlib import Path from typing import Optional, Sequence -from opentelemetry.semconv.model.semantic_convention import \ - SemanticConventionSet +from opentelemetry.semconv.model.semantic_convention import SemanticConventionSet from opentelemetry.semconv.templating.markdown import MarkdownRenderer from opentelemetry.semconv.templating.markdown.options import MarkdownOptions From b384c808610c9a823c533cb65afbb64416a7127f Mon Sep 17 00:00:00 2001 From: Liudmila Molkova Date: Sat, 2 Dec 2023 12:19:17 -0800 Subject: [PATCH 07/18] lint --- semantic-conventions/src/opentelemetry/semconv/main.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/semantic-conventions/src/opentelemetry/semconv/main.py b/semantic-conventions/src/opentelemetry/semconv/main.py index 9a967277..e6331814 100644 --- a/semantic-conventions/src/opentelemetry/semconv/main.py +++ b/semantic-conventions/src/opentelemetry/semconv/main.py @@ -129,7 +129,8 @@ def add_code_parser(subparsers): parser.add_argument( "--output", "-o", - help="Specify the output file name for the code generation. See also --file-per-group on how to generate multiple files.", + help="Specify the output file name for the code generation. " + "See also `--file-per-group` on how to generate multiple files.", type=str, required=True, ) From 5fc8be5a88f3876cfd44e226a71bdb3768c43bcc Mon Sep 17 00:00:00 2001 From: Liudmila Molkova Date: Sat, 2 Dec 2023 12:41:28 -0800 Subject: [PATCH 08/18] Update semantic-conventions/CHANGELOG.md --- semantic-conventions/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/semantic-conventions/CHANGELOG.md b/semantic-conventions/CHANGELOG.md index a0633aa3..e608dace 100644 --- a/semantic-conventions/CHANGELOG.md +++ b/semantic-conventions/CHANGELOG.md @@ -4,7 +4,7 @@ Please update the changelog as part of any significant pull request. ## Unreleased -- Added code-generation mode that groups attributes by the root namespace and ability to wring each group into individual file. +- Added code-generation mode that groups attributes by the root namespace and ability to write each group into individual file. [BREAKING] The `--file-per-group ` that used to create multiple directories (like `output//file`) now generates multiple files (`output/file`) instead. ([#243](https://github.com/open-telemetry/build-tools/pull/243)) From a84bad610bafdf2be56c0a47fad449e571c36987 Mon Sep 17 00:00:00 2001 From: Liudmila Molkova Date: Mon, 4 Dec 2023 17:38:56 -0800 Subject: [PATCH 09/18] add metrics and fix docs --- semantic-conventions/README.md | 10 ++-- .../semconv/model/semantic_convention.py | 3 ++ .../opentelemetry/semconv/templating/code.py | 42 +++++++++++---- .../all/FirstAttributes.java | 0 .../all/SecondAttributes.java | 0 .../all/ThirdAttributes.java | 0 .../attributes.yml | 0 .../attributes_and_metrics/First.java | 15 ++++++ .../attributes_and_metrics/Second.java | 15 ++++++ .../attributes_and_metrics/semconv.yml | 29 +++++++++++ .../attributes_and_metrics/template | 50 ++++++++++++++++++ .../no_group_prefix/FooAttributes.java | 0 .../no_group_prefix/OtherAttributes.java | 0 .../attributes_no_group_prefix.yml | 0 .../single_file/All.java} | 15 ++---- .../single_file/semconv.yml | 51 +++++++++++++++++++ .../single_file/template_single_file | 15 ++++-- .../stable/ThirdAttributesStable.java | 0 .../stable/template_only_stable | 0 .../template_all | 0 .../src/tests/semconv/templating/test_code.py | 46 ++++++++++++----- 21 files changed, 251 insertions(+), 40 deletions(-) rename semantic-conventions/src/tests/data/jinja/{attributes_root_ns => group_by_root_namespace}/all/FirstAttributes.java (100%) rename semantic-conventions/src/tests/data/jinja/{attributes_root_ns => group_by_root_namespace}/all/SecondAttributes.java (100%) rename semantic-conventions/src/tests/data/jinja/{attributes_root_ns => group_by_root_namespace}/all/ThirdAttributes.java (100%) rename semantic-conventions/src/tests/data/jinja/{attributes_root_ns => group_by_root_namespace}/attributes.yml (100%) create mode 100644 semantic-conventions/src/tests/data/jinja/group_by_root_namespace/attributes_and_metrics/First.java create mode 100644 semantic-conventions/src/tests/data/jinja/group_by_root_namespace/attributes_and_metrics/Second.java create mode 100644 semantic-conventions/src/tests/data/jinja/group_by_root_namespace/attributes_and_metrics/semconv.yml create mode 100644 semantic-conventions/src/tests/data/jinja/group_by_root_namespace/attributes_and_metrics/template rename semantic-conventions/src/tests/data/jinja/{attributes_root_ns => group_by_root_namespace}/no_group_prefix/FooAttributes.java (100%) rename semantic-conventions/src/tests/data/jinja/{attributes_root_ns => group_by_root_namespace}/no_group_prefix/OtherAttributes.java (100%) rename semantic-conventions/src/tests/data/jinja/{attributes_root_ns => group_by_root_namespace}/no_group_prefix/attributes_no_group_prefix.yml (100%) rename semantic-conventions/src/tests/data/jinja/{attributes_root_ns/single_file/AllAttributes.java => group_by_root_namespace/single_file/All.java} (70%) create mode 100644 semantic-conventions/src/tests/data/jinja/group_by_root_namespace/single_file/semconv.yml rename semantic-conventions/src/tests/data/jinja/{attributes_root_ns => group_by_root_namespace}/single_file/template_single_file (86%) rename semantic-conventions/src/tests/data/jinja/{attributes_root_ns => group_by_root_namespace}/stable/ThirdAttributesStable.java (100%) rename semantic-conventions/src/tests/data/jinja/{attributes_root_ns => group_by_root_namespace}/stable/template_only_stable (100%) rename semantic-conventions/src/tests/data/jinja/{attributes_root_ns => group_by_root_namespace}/template_all (100%) diff --git a/semantic-conventions/README.md b/semantic-conventions/README.md index c3578607..6ce33916 100644 --- a/semantic-conventions/README.md +++ b/semantic-conventions/README.md @@ -165,19 +165,19 @@ Processes a single pattern value and is called for each distinct value. - `semconv` - the instance of parsed `BaseSemanticConvention` being processed. -### Filtering attributes +### Filtering and mapping Jinja templates has a notion of [filters](https://jinja.palletsprojects.com/en/2.11.x/templates/#list-of-builtin-filters) allowing to transform objects or filter lists. Semconvgen supports following additional filters to simplify common operations in templates. -#### Attribute filters +#### `SemanticAttribute` operations 1. `is_definition` - Checks if the attribute is the original definition of the attribute and not a reference. 2. `is_deprecated` - Checks if the attribute is deprecated. The same check can also be done with `(attribute.stability | string()) == "StabilityLevel.DEPRECATED"` 3. `is_experimental` - Checks if the attribute is experimental. The same check can also be done with `(attribute.stability | string()) == "StabilityLevel.EXPERIMENTAL"` 4. `is_stable` - Checks if the attribute is experimental. The same check can also be done with `(attribute.stability | string()) == "StabilityLevel.STABLE"` -5. `is_template` - Checks if the attribute is a template attribute. The same check can also be done with `(attribute.stability | string()) == "StabilityLevel.STABLE"` +5. `is_template` - Checks if the attribute is a template attribute. #### String operations @@ -188,3 +188,7 @@ Semconvgen supports following additional filters to simplify common operations i 4. `to_const_name` - Converts a string to Python or Java constant name (SNAKE_CASE) replacing `.` or `-` with `_`. E.g. `foo.bAR-baz` becomes `FOO_BAR_BAZ`. 5. `to_doc_brief` - Trims whitespace and removes dot at the end. E.g. ` Hello world.\t` becomes `Hello world` + +#### `BaseSemanticConvention` operations + +1. `is_metric` - Checks if semantic convention describes a metric. diff --git a/semantic-conventions/src/opentelemetry/semconv/model/semantic_convention.py b/semantic-conventions/src/opentelemetry/semconv/model/semantic_convention.py index ca269cfa..08bd49a7 100644 --- a/semantic-conventions/src/opentelemetry/semconv/model/semantic_convention.py +++ b/semantic-conventions/src/opentelemetry/semconv/model/semantic_convention.py @@ -263,6 +263,9 @@ def __init__(self, group): self.metric_name = group.get("metric_name") self.unit = group.get("unit") self.instrument = group.get("instrument") + + namespaces = self.metric_name.split(".") + self.root_namespace = namespaces[0] if len(namespaces) > 1 else "" self.validate() def validate(self): diff --git a/semantic-conventions/src/opentelemetry/semconv/templating/code.py b/semantic-conventions/src/opentelemetry/semconv/templating/code.py index 1f2bb84e..1a1dc87b 100644 --- a/semantic-conventions/src/opentelemetry/semconv/templating/code.py +++ b/semantic-conventions/src/opentelemetry/semconv/templating/code.py @@ -26,7 +26,7 @@ StabilityLevel, TextWithLinks, ) -from opentelemetry.semconv.model.semantic_convention import SemanticConventionSet +from opentelemetry.semconv.model.semantic_convention import BaseSemanticConvention, MetricSemanticConvention, SemanticConventionSet from opentelemetry.semconv.model.utils import ID_RE @@ -182,6 +182,10 @@ def is_template(attribute: SemanticAttribute) -> bool: return AttributeType.is_template_type(attribute.attr_type) +def is_metric(semconv: BaseSemanticConvention) -> bool: + return isinstance(semconv, MetricSemanticConvention) + + class CodeRenderer: pattern = f"{{{ID_RE.pattern}}}" @@ -242,11 +246,13 @@ def setup_environment(env: Environment, trim_whitespace: bool): env.filters["is_stable"] = is_stable env.filters["is_experimental"] = is_experimental env.filters["is_template"] = is_template + env.filters["is_metric"] = is_metric env.tests["is_stable"] = is_stable env.tests["is_experimental"] = is_experimental env.tests["is_deprecated"] = is_deprecated env.tests["is_definition"] = is_definition env.tests["is_template"] = is_template + env.tests["is_metric"] = is_metric env.trim_blocks = trim_whitespace env.lstrip_blocks = trim_whitespace @@ -307,14 +313,16 @@ def _render_group_by_root_namespace( output_file: str, env: Environment, ): - root_namespaces = self._grouped_attribute_definitions(semconvset) - for ns in root_namespaces: + attribute_and_templates = self._grouped_attribute_definitions(semconvset) + metrics = self._grouped_metric_definitions(semconvset) + for ns in attribute_and_templates.keys(): sanitized_ns = ns if ns != "" else "other" output_name = self.prefix_output_file(output_file, sanitized_ns) data = { "template": template_path, - "attributes_and_templates": root_namespaces[ns], + "attributes_and_templates": attribute_and_templates[ns], + "metrics": metrics.get(ns) or [], "root_namespace": sanitized_ns, } data.update(self.parameters) @@ -323,18 +331,30 @@ def _render_group_by_root_namespace( self._write_template_to_file(template, data, output_name) def _grouped_attribute_definitions(self, semconvset): - root_namespaces = {} + grouped_attributes = {} for semconv in semconvset.models.values(): for attr in filter( lambda a: is_definition(a), semconv.attributes_and_templates ): - if attr.root_namespace not in root_namespaces: - root_namespaces[attr.root_namespace] = [] - root_namespaces[attr.root_namespace].append(attr) + if attr.root_namespace not in grouped_attributes: + grouped_attributes[attr.root_namespace] = [] + grouped_attributes[attr.root_namespace].append(attr) + + for ns in grouped_attributes: + grouped_attributes[ns] = sorted(grouped_attributes[ns], key=lambda a: a.fqn) + return grouped_attributes + + def _grouped_metric_definitions(self, semconvset): + grouped_metrics = {} + for semconv in filter(lambda s: is_metric(s), semconvset.models.values()): + if semconv.root_namespace not in grouped_metrics: + grouped_metrics[semconv.root_namespace] = [] + + grouped_metrics[semconv.root_namespace].append(semconv) - for ns in root_namespaces: - root_namespaces[ns] = sorted(root_namespaces[ns], key=lambda a: a.fqn) - return root_namespaces + for ns in grouped_metrics: + grouped_metrics[ns] = sorted(grouped_metrics[ns], key=lambda a: a.metric_name) + return grouped_metrics def _write_template_to_file(self, template, data, output_name): template.globals["now"] = datetime.datetime.utcnow() diff --git a/semantic-conventions/src/tests/data/jinja/attributes_root_ns/all/FirstAttributes.java b/semantic-conventions/src/tests/data/jinja/group_by_root_namespace/all/FirstAttributes.java similarity index 100% rename from semantic-conventions/src/tests/data/jinja/attributes_root_ns/all/FirstAttributes.java rename to semantic-conventions/src/tests/data/jinja/group_by_root_namespace/all/FirstAttributes.java diff --git a/semantic-conventions/src/tests/data/jinja/attributes_root_ns/all/SecondAttributes.java b/semantic-conventions/src/tests/data/jinja/group_by_root_namespace/all/SecondAttributes.java similarity index 100% rename from semantic-conventions/src/tests/data/jinja/attributes_root_ns/all/SecondAttributes.java rename to semantic-conventions/src/tests/data/jinja/group_by_root_namespace/all/SecondAttributes.java diff --git a/semantic-conventions/src/tests/data/jinja/attributes_root_ns/all/ThirdAttributes.java b/semantic-conventions/src/tests/data/jinja/group_by_root_namespace/all/ThirdAttributes.java similarity index 100% rename from semantic-conventions/src/tests/data/jinja/attributes_root_ns/all/ThirdAttributes.java rename to semantic-conventions/src/tests/data/jinja/group_by_root_namespace/all/ThirdAttributes.java diff --git a/semantic-conventions/src/tests/data/jinja/attributes_root_ns/attributes.yml b/semantic-conventions/src/tests/data/jinja/group_by_root_namespace/attributes.yml similarity index 100% rename from semantic-conventions/src/tests/data/jinja/attributes_root_ns/attributes.yml rename to semantic-conventions/src/tests/data/jinja/group_by_root_namespace/attributes.yml diff --git a/semantic-conventions/src/tests/data/jinja/group_by_root_namespace/attributes_and_metrics/First.java b/semantic-conventions/src/tests/data/jinja/group_by_root_namespace/attributes_and_metrics/First.java new file mode 100644 index 00000000..19728a9a --- /dev/null +++ b/semantic-conventions/src/tests/data/jinja/group_by_root_namespace/attributes_and_metrics/First.java @@ -0,0 +1,15 @@ +package io.opentelemetry.instrumentation.api.semconv; + +class First { + /** + * short description of attr_one + */ + public static final AttributeKey FIRST_ATTR_ONE = booleanKey("first.attr_one"); + /** + * first metric description + * + * Instrument: counter + * Unit: {one} + */ + public static final String FIRST_METRIC_NAME = "first.metric.name"; +} \ No newline at end of file diff --git a/semantic-conventions/src/tests/data/jinja/group_by_root_namespace/attributes_and_metrics/Second.java b/semantic-conventions/src/tests/data/jinja/group_by_root_namespace/attributes_and_metrics/Second.java new file mode 100644 index 00000000..8511b7e3 --- /dev/null +++ b/semantic-conventions/src/tests/data/jinja/group_by_root_namespace/attributes_and_metrics/Second.java @@ -0,0 +1,15 @@ +package io.opentelemetry.instrumentation.api.semconv; + +class Second { + /** + * short description of attr_two + */ + public static final AttributeKey SECOND_ATTR_TWO = longKey("second.attr_two"); + /** + * second metric description + * + * Instrument: histogram + * Unit: s + */ + public static final String SECOND_METRIC_NAME = "second.metric.name"; +} \ No newline at end of file diff --git a/semantic-conventions/src/tests/data/jinja/group_by_root_namespace/attributes_and_metrics/semconv.yml b/semantic-conventions/src/tests/data/jinja/group_by_root_namespace/attributes_and_metrics/semconv.yml new file mode 100644 index 00000000..101916ac --- /dev/null +++ b/semantic-conventions/src/tests/data/jinja/group_by_root_namespace/attributes_and_metrics/semconv.yml @@ -0,0 +1,29 @@ +groups: + - id: first_group_id + type: attribute_group + brief: first description + prefix: first + attributes: + - id: attr_one + type: boolean + brief: short description of attr_one + + - id: first_metric_id + brief: first metric description + metric_name: first.metric.name + instrument: counter + type: metric + unit: "{one}" + extends: first_group_id + + - id: second_group_id + brief: second metric description + metric_name: second.metric.name + type: metric + instrument: histogram + unit: "s" + prefix: second + attributes: + - id: attr_two + type: int + brief: short description of attr_two diff --git a/semantic-conventions/src/tests/data/jinja/group_by_root_namespace/attributes_and_metrics/template b/semantic-conventions/src/tests/data/jinja/group_by_root_namespace/attributes_and_metrics/template new file mode 100644 index 00000000..c8080f55 --- /dev/null +++ b/semantic-conventions/src/tests/data/jinja/group_by_root_namespace/attributes_and_metrics/template @@ -0,0 +1,50 @@ +{%- macro to_java_return_type(type) -%} + {%- if type == "string" -%} + String + {%- elif type == "string[]" -%} + List + {%- elif type == "boolean" -%} + boolean + {%- elif type == "int" -%} + long + {%- elif type == "double" -%} + double + {%- else -%} + {{type}} + {%- endif -%} +{%- endmacro %} +{%- macro to_java_key_type(type) -%} + {%- if type == "string" -%} + stringKey + {%- elif type == "string[]" -%} + stringArrayKey + {%- elif type == "boolean" -%} + booleanKey + {%- elif type == "int" -%} + longKey + {%- elif type == "double" -%} + doubleKey + {%- else -%} + {{ type | to_camelcase(False)}}Key + {%- endif -%} +{%- endmacro %} +package io.opentelemetry.instrumentation.api.semconv; + +class {{ root_namespace | to_camelcase(True) }} { +{%- for attribute in attributes_and_templates %} + + /** + * {{attribute.brief | render_markdown(code="{{@code {0}}}", paragraph="{0}")}} + */ + public static final AttributeKey<{{ to_java_return_type(attribute.instantiated_type | string) | first_up }}> {{attribute.fqn | to_const_name}} = {{to_java_key_type(attribute.instantiated_type | string)}}("{{attribute.fqn}}"); +{% endfor %} +{%- for metric in metrics %} + /** + * {{metric.brief | to_doc_brief}} + * + * Instrument: {{metric.instrument }} + * Unit: {{metric.unit }} + */ + public static final String {{metric.metric_name | to_const_name}} = "{{metric.metric_name}}"; +{% endfor %} +} \ No newline at end of file diff --git a/semantic-conventions/src/tests/data/jinja/attributes_root_ns/no_group_prefix/FooAttributes.java b/semantic-conventions/src/tests/data/jinja/group_by_root_namespace/no_group_prefix/FooAttributes.java similarity index 100% rename from semantic-conventions/src/tests/data/jinja/attributes_root_ns/no_group_prefix/FooAttributes.java rename to semantic-conventions/src/tests/data/jinja/group_by_root_namespace/no_group_prefix/FooAttributes.java diff --git a/semantic-conventions/src/tests/data/jinja/attributes_root_ns/no_group_prefix/OtherAttributes.java b/semantic-conventions/src/tests/data/jinja/group_by_root_namespace/no_group_prefix/OtherAttributes.java similarity index 100% rename from semantic-conventions/src/tests/data/jinja/attributes_root_ns/no_group_prefix/OtherAttributes.java rename to semantic-conventions/src/tests/data/jinja/group_by_root_namespace/no_group_prefix/OtherAttributes.java diff --git a/semantic-conventions/src/tests/data/jinja/attributes_root_ns/no_group_prefix/attributes_no_group_prefix.yml b/semantic-conventions/src/tests/data/jinja/group_by_root_namespace/no_group_prefix/attributes_no_group_prefix.yml similarity index 100% rename from semantic-conventions/src/tests/data/jinja/attributes_root_ns/no_group_prefix/attributes_no_group_prefix.yml rename to semantic-conventions/src/tests/data/jinja/group_by_root_namespace/no_group_prefix/attributes_no_group_prefix.yml diff --git a/semantic-conventions/src/tests/data/jinja/attributes_root_ns/single_file/AllAttributes.java b/semantic-conventions/src/tests/data/jinja/group_by_root_namespace/single_file/All.java similarity index 70% rename from semantic-conventions/src/tests/data/jinja/attributes_root_ns/single_file/AllAttributes.java rename to semantic-conventions/src/tests/data/jinja/group_by_root_namespace/single_file/All.java index 1b7d3657..cd1cabeb 100644 --- a/semantic-conventions/src/tests/data/jinja/attributes_root_ns/single_file/AllAttributes.java +++ b/semantic-conventions/src/tests/data/jinja/group_by_root_namespace/single_file/All.java @@ -23,19 +23,12 @@ class SecondAttributes { */ public static final AttributeKey SECOND_ATTR_TWO = stringKey("second.attr_two"); } - class ThirdAttributes { - /** - * this is the description of attribute template - */ - public static final AttributeKeyTemplate THIRD_ATTR_TEMPLATE_THREE = stringKey("third.attr_template_three"); - - /** - * short description of attr_three - */ - public static final AttributeKey THIRD_ATTR_THREE = stringKey("third.attr_three"); - } /** * short description of attr_four */ public static final AttributeKey ATTR_FOUR = stringKey("attr_four"); + /** + * first metric description + */ + public static final String FIRST_METRIC_NAME = "first.metric.name"; } \ No newline at end of file diff --git a/semantic-conventions/src/tests/data/jinja/group_by_root_namespace/single_file/semconv.yml b/semantic-conventions/src/tests/data/jinja/group_by_root_namespace/single_file/semconv.yml new file mode 100644 index 00000000..37d33fc2 --- /dev/null +++ b/semantic-conventions/src/tests/data/jinja/group_by_root_namespace/single_file/semconv.yml @@ -0,0 +1,51 @@ +groups: + - id: first_group_id + type: attribute_group + brief: first description + prefix: first + attributes: + - id: attr_one + type: boolean + brief: short description of attr_one + - id: attr_template_one + type: template[string] + brief: > + this is the description of attribute template + examples: 'example' + + - id: second_group_id + brief: second description + prefix: second + span_kind: client + extends: first_group_id + attributes: + - id: attr_two + type: string + brief: short description of attr_two + examples: ['example_one', 'example_two'] + + - id: first_group_part_two + type: resource + brief: first_a description + prefix: first + attributes: + - id: attr_one_a + type: int + brief: short description of attr_one_a + - ref: second.attr_two + + - id: forth_group_id + brief: forth description + attributes: + - id: attr_four + type: string + brief: short description of attr_four + examples: "4" + + - id: first_metric_id + brief: first metric description + metric_name: first.metric.name + instrument: counter + type: metric + unit: "{one}" + extends: first_group_id \ No newline at end of file diff --git a/semantic-conventions/src/tests/data/jinja/attributes_root_ns/single_file/template_single_file b/semantic-conventions/src/tests/data/jinja/group_by_root_namespace/single_file/template_single_file similarity index 86% rename from semantic-conventions/src/tests/data/jinja/attributes_root_ns/single_file/template_single_file rename to semantic-conventions/src/tests/data/jinja/group_by_root_namespace/single_file/template_single_file index 168c8323..986ec3d2 100644 --- a/semantic-conventions/src/tests/data/jinja/attributes_root_ns/single_file/template_single_file +++ b/semantic-conventions/src/tests/data/jinja/group_by_root_namespace/single_file/template_single_file @@ -50,8 +50,8 @@ class AllAttributes { } {%- endif %} {%- endfor %} - {# non-namespaced attributes #} - {%- for attribute in attributes_and_templates[""] %} +{# non-namespaced attributes #} +{%- for attribute in attributes_and_templates[""] %} /** * {{attribute.brief | render_markdown(code="{{@code {0}}}", paragraph="{0}")}} */ @@ -61,5 +61,14 @@ class AllAttributes { public static final AttributeKey<{{ to_java_return_type(attribute.instantiated_type | string) | first_up }}> {{attribute.fqn | to_const_name}} = {{to_java_key_type(attribute.instantiated_type | string)}}("{{attribute.fqn}}"); {% endif %} - {%- endfor %} +{%- endfor %} +{%- for id in semconvs %} +{%- if semconvs[id] | is_metric %} +{% set metric = semconvs[id] %} + /** + * {{metric.brief | to_doc_brief}} + */ + public static final String {{metric.metric_name | to_const_name}} = "{{metric.metric_name}}"; +{% endif %} +{% endfor %} } \ No newline at end of file diff --git a/semantic-conventions/src/tests/data/jinja/attributes_root_ns/stable/ThirdAttributesStable.java b/semantic-conventions/src/tests/data/jinja/group_by_root_namespace/stable/ThirdAttributesStable.java similarity index 100% rename from semantic-conventions/src/tests/data/jinja/attributes_root_ns/stable/ThirdAttributesStable.java rename to semantic-conventions/src/tests/data/jinja/group_by_root_namespace/stable/ThirdAttributesStable.java diff --git a/semantic-conventions/src/tests/data/jinja/attributes_root_ns/stable/template_only_stable b/semantic-conventions/src/tests/data/jinja/group_by_root_namespace/stable/template_only_stable similarity index 100% rename from semantic-conventions/src/tests/data/jinja/attributes_root_ns/stable/template_only_stable rename to semantic-conventions/src/tests/data/jinja/group_by_root_namespace/stable/template_only_stable diff --git a/semantic-conventions/src/tests/data/jinja/attributes_root_ns/template_all b/semantic-conventions/src/tests/data/jinja/group_by_root_namespace/template_all similarity index 100% rename from semantic-conventions/src/tests/data/jinja/attributes_root_ns/template_all rename to semantic-conventions/src/tests/data/jinja/group_by_root_namespace/template_all diff --git a/semantic-conventions/src/tests/semconv/templating/test_code.py b/semantic-conventions/src/tests/semconv/templating/test_code.py index 164581f9..70ed4e9a 100644 --- a/semantic-conventions/src/tests/semconv/templating/test_code.py +++ b/semantic-conventions/src/tests/semconv/templating/test_code.py @@ -68,13 +68,13 @@ def test_codegen_attribute_templates(test_file_path, read_test_file): def test_codegen_attribute_root_ns(test_file_path, read_test_file): semconv = SemanticConventionSet(debug=False) - semconv.parse(test_file_path("jinja", "attributes_root_ns", "attributes.yml")) + semconv.parse(test_file_path("jinja", "group_by_root_namespace", "attributes.yml")) semconv.finish() - template_path = test_file_path("jinja", "attributes_root_ns", "template_all") + template_path = test_file_path("jinja", "group_by_root_namespace", "template_all") renderer = CodeRenderer({}, trim_whitespace=True) - test_path = os.path.join("attributes_root_ns", "all") + test_path = os.path.join("group_by_root_namespace", "all") tmppath = tempfile.mkdtemp() renderer.render( semconv, @@ -95,10 +95,10 @@ def test_codegen_attribute_root_ns(test_file_path, read_test_file): def test_codegen_attribute_root_ns_stable(test_file_path, read_test_file): semconv = SemanticConventionSet(debug=False) - semconv.parse(test_file_path("jinja", "attributes_root_ns", "attributes.yml")) + semconv.parse(test_file_path("jinja", "group_by_root_namespace", "attributes.yml")) semconv.finish() - test_path = os.path.join("attributes_root_ns", "stable") + test_path = os.path.join("group_by_root_namespace", "stable") template_path = test_file_path("jinja", test_path, "template_only_stable") renderer = CodeRenderer({}, trim_whitespace=True) @@ -119,11 +119,11 @@ def test_codegen_attribute_root_ns_stable(test_file_path, read_test_file): def test_codegen_attribute_root_ns_no_group_prefix(test_file_path, read_test_file): semconv = SemanticConventionSet(debug=False) - test_path = os.path.join("attributes_root_ns", "no_group_prefix") + test_path = os.path.join("group_by_root_namespace", "no_group_prefix") semconv.parse(test_file_path("jinja", test_path, "attributes_no_group_prefix.yml")) semconv.finish() - template_path = test_file_path("jinja", "attributes_root_ns", "template_all") + template_path = test_file_path("jinja", "group_by_root_namespace", "template_all") renderer = CodeRenderer({}, trim_whitespace=True) tmppath = tempfile.mkdtemp() @@ -144,20 +144,42 @@ def test_codegen_attribute_root_ns_no_group_prefix(test_file_path, read_test_fil def test_codegen_attribute_root_ns_single_file(test_file_path, read_test_file): semconv = SemanticConventionSet(debug=False) - semconv.parse(test_file_path("jinja", "attributes_root_ns", "attributes.yml")) + test_path = os.path.join("group_by_root_namespace", "single_file") + semconv.parse(test_file_path("jinja", test_path, "semconv.yml")) semconv.finish() - test_path = os.path.join("attributes_root_ns", "single_file") template_path = test_file_path("jinja", test_path, "template_single_file") renderer = CodeRenderer({}, trim_whitespace=True) tmppath = tempfile.mkdtemp() renderer.render( - semconv, template_path, os.path.join(tmppath, "AllAttributes.java"), None + semconv, template_path, os.path.join(tmppath, "All.java"), None ) - all = read_test_file("jinja", test_path, "AllAttributes.java") - check_file(tmppath, "AllAttributes.java", all) + all = read_test_file("jinja", test_path, "All.java") + check_file(tmppath, "All.java", all) + + +def test_codegen_attribute_root_ns_metrics(test_file_path, read_test_file): + semconv = SemanticConventionSet(debug=False) + + test_path = os.path.join("group_by_root_namespace", "attributes_and_metrics") + semconv.parse(test_file_path("jinja", test_path, "semconv.yml")) + semconv.finish() + + template_path = test_file_path("jinja", test_path, "template") + renderer = CodeRenderer({}, trim_whitespace=True) + + tmppath = tempfile.mkdtemp() + renderer.render( + semconv, template_path, os.path.join(tmppath, ".java"), "root_namespace" + ) + + first = read_test_file("jinja", test_path, "First.java") + check_file(tmppath, "First.java", first) + + second = read_test_file("jinja", test_path, "Second.java") + check_file(tmppath, "Second.java", second) def check_file(tmppath, actual_filename, expected_content): From 446eb27d73ea78921f2b00288209b487346296ff Mon Sep 17 00:00:00 2001 From: Liudmila Molkova Date: Mon, 4 Dec 2023 18:05:55 -0800 Subject: [PATCH 10/18] up --- .../src/opentelemetry/semconv/model/constraints.py | 3 ++- .../opentelemetry/semconv/model/semantic_attribute.py | 3 ++- .../semconv/model/semantic_convention.py | 3 ++- .../src/opentelemetry/semconv/templating/code.py | 11 +++++++++-- .../src/tests/semconv/model/test_error_detection.py | 3 ++- .../semconv/model/test_semantic_convention_units.py | 1 + .../src/tests/semconv/model/test_utils.py | 1 + .../src/tests/semconv/templating/test_code.py | 4 +--- 8 files changed, 20 insertions(+), 9 deletions(-) diff --git a/semantic-conventions/src/opentelemetry/semconv/model/constraints.py b/semantic-conventions/src/opentelemetry/semconv/model/constraints.py index 77af3682..2e44119c 100644 --- a/semantic-conventions/src/opentelemetry/semconv/model/constraints.py +++ b/semantic-conventions/src/opentelemetry/semconv/model/constraints.py @@ -15,10 +15,11 @@ from dataclasses import dataclass, replace from typing import List, Tuple +from ruamel.yaml.comments import CommentedSeq + from opentelemetry.semconv.model.exceptions import ValidationError from opentelemetry.semconv.model.semantic_attribute import SemanticAttribute from opentelemetry.semconv.model.utils import validate_values -from ruamel.yaml.comments import CommentedSeq # We cannot frozen due to later evaluation of the attributes diff --git a/semantic-conventions/src/opentelemetry/semconv/model/semantic_attribute.py b/semantic-conventions/src/opentelemetry/semconv/model/semantic_attribute.py index 682b0a0a..b8a25ac7 100644 --- a/semantic-conventions/src/opentelemetry/semconv/model/semantic_attribute.py +++ b/semantic-conventions/src/opentelemetry/semconv/model/semantic_attribute.py @@ -18,13 +18,14 @@ from enum import Enum from typing import Dict, List, Optional, Union +from ruamel.yaml.comments import CommentedMap, CommentedSeq + from opentelemetry.semconv.model.exceptions import ValidationError from opentelemetry.semconv.model.utils import ( check_no_missing_keys, validate_id, validate_values, ) -from ruamel.yaml.comments import CommentedMap, CommentedSeq TEMPLATE_PREFIX = "template[" TEMPLATE_SUFFIX = "]" diff --git a/semantic-conventions/src/opentelemetry/semconv/model/semantic_convention.py b/semantic-conventions/src/opentelemetry/semconv/model/semantic_convention.py index 08bd49a7..9aafb1c8 100644 --- a/semantic-conventions/src/opentelemetry/semconv/model/semantic_convention.py +++ b/semantic-conventions/src/opentelemetry/semconv/model/semantic_convention.py @@ -18,6 +18,8 @@ from enum import Enum from typing import Dict, Optional, Tuple, Union +from ruamel.yaml import YAML + from opentelemetry.semconv.model.constraints import AnyOf, Include, parse_constraints from opentelemetry.semconv.model.exceptions import ValidationError from opentelemetry.semconv.model.semantic_attribute import ( @@ -26,7 +28,6 @@ ) from opentelemetry.semconv.model.unit_member import UnitMember from opentelemetry.semconv.model.utils import ValidatableYamlNode, validate_id -from ruamel.yaml import YAML class SpanKind(Enum): diff --git a/semantic-conventions/src/opentelemetry/semconv/templating/code.py b/semantic-conventions/src/opentelemetry/semconv/templating/code.py index 1a1dc87b..0ed63d30 100644 --- a/semantic-conventions/src/opentelemetry/semconv/templating/code.py +++ b/semantic-conventions/src/opentelemetry/semconv/templating/code.py @@ -19,6 +19,7 @@ import mistune from jinja2 import Environment, FileSystemLoader, select_autoescape + from opentelemetry.semconv.model.semantic_attribute import ( AttributeType, RequirementLevel, @@ -26,7 +27,11 @@ StabilityLevel, TextWithLinks, ) -from opentelemetry.semconv.model.semantic_convention import BaseSemanticConvention, MetricSemanticConvention, SemanticConventionSet +from opentelemetry.semconv.model.semantic_convention import ( + BaseSemanticConvention, + MetricSemanticConvention, + SemanticConventionSet, +) from opentelemetry.semconv.model.utils import ID_RE @@ -353,7 +358,9 @@ def _grouped_metric_definitions(self, semconvset): grouped_metrics[semconv.root_namespace].append(semconv) for ns in grouped_metrics: - grouped_metrics[ns] = sorted(grouped_metrics[ns], key=lambda a: a.metric_name) + grouped_metrics[ns] = sorted( + grouped_metrics[ns], key=lambda a: a.metric_name + ) return grouped_metrics def _write_template_to_file(self, template, data, output_name): 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 ea9b7127..7d53a3a6 100644 --- a/semantic-conventions/src/tests/semconv/model/test_error_detection.py +++ b/semantic-conventions/src/tests/semconv/model/test_error_detection.py @@ -15,12 +15,13 @@ import os import unittest +from ruamel.yaml.constructor import DuplicateKeyError + from opentelemetry.semconv.model.exceptions import ValidationError from opentelemetry.semconv.model.semantic_convention import ( SemanticConventionSet, parse_semantic_convention_groups, ) -from ruamel.yaml.constructor import DuplicateKeyError class TestCorrectErrorDetection(unittest.TestCase): diff --git a/semantic-conventions/src/tests/semconv/model/test_semantic_convention_units.py b/semantic-conventions/src/tests/semconv/model/test_semantic_convention_units.py index 019248b3..5728a749 100644 --- a/semantic-conventions/src/tests/semconv/model/test_semantic_convention_units.py +++ b/semantic-conventions/src/tests/semconv/model/test_semantic_convention_units.py @@ -1,6 +1,7 @@ import os import pytest + from opentelemetry.semconv.model.exceptions import ValidationError from opentelemetry.semconv.model.semantic_convention import ( UnitSemanticConvention, diff --git a/semantic-conventions/src/tests/semconv/model/test_utils.py b/semantic-conventions/src/tests/semconv/model/test_utils.py index 0438c5e4..1bffee42 100644 --- a/semantic-conventions/src/tests/semconv/model/test_utils.py +++ b/semantic-conventions/src/tests/semconv/model/test_utils.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. import pytest + from opentelemetry.semconv.model.exceptions import ValidationError from opentelemetry.semconv.model.utils import validate_id, validate_values diff --git a/semantic-conventions/src/tests/semconv/templating/test_code.py b/semantic-conventions/src/tests/semconv/templating/test_code.py index 70ed4e9a..43866bd5 100644 --- a/semantic-conventions/src/tests/semconv/templating/test_code.py +++ b/semantic-conventions/src/tests/semconv/templating/test_code.py @@ -152,9 +152,7 @@ def test_codegen_attribute_root_ns_single_file(test_file_path, read_test_file): renderer = CodeRenderer({}, trim_whitespace=True) tmppath = tempfile.mkdtemp() - renderer.render( - semconv, template_path, os.path.join(tmppath, "All.java"), None - ) + renderer.render(semconv, template_path, os.path.join(tmppath, "All.java"), None) all = read_test_file("jinja", test_path, "All.java") check_file(tmppath, "All.java", all) From 805ebd469131d8abeab366f7ab31a409cca749a3 Mon Sep 17 00:00:00 2001 From: Liudmila Molkova Date: Mon, 4 Dec 2023 18:23:06 -0800 Subject: [PATCH 11/18] lint --- .../opentelemetry/semconv/templating/code.py | 19 +++++++++++-------- .../src/tests/semconv/templating/test_code.py | 17 ++++++++--------- 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/semantic-conventions/src/opentelemetry/semconv/templating/code.py b/semantic-conventions/src/opentelemetry/semconv/templating/code.py index 0ed63d30..3d435dcf 100644 --- a/semantic-conventions/src/opentelemetry/semconv/templating/code.py +++ b/semantic-conventions/src/opentelemetry/semconv/templating/code.py @@ -184,7 +184,7 @@ def is_definition(attribute: SemanticAttribute) -> bool: def is_template(attribute: SemanticAttribute) -> bool: - return AttributeType.is_template_type(attribute.attr_type) + return AttributeType.is_template_type(str(attribute.attr_type)) def is_metric(semconv: BaseSemanticConvention) -> bool: @@ -320,13 +320,13 @@ def _render_group_by_root_namespace( ): attribute_and_templates = self._grouped_attribute_definitions(semconvset) metrics = self._grouped_metric_definitions(semconvset) - for ns in attribute_and_templates.keys(): + for ns, attribute_and_templates in attribute_and_templates.items(): sanitized_ns = ns if ns != "" else "other" output_name = self.prefix_output_file(output_file, sanitized_ns) data = { "template": template_path, - "attributes_and_templates": attribute_and_templates[ns], + "attributes_and_templates": attribute_and_templates, "metrics": metrics.get(ns) or [], "root_namespace": sanitized_ns, } @@ -338,9 +338,9 @@ def _render_group_by_root_namespace( def _grouped_attribute_definitions(self, semconvset): grouped_attributes = {} for semconv in semconvset.models.values(): - for attr in filter( - lambda a: is_definition(a), semconv.attributes_and_templates - ): + for attr in semconv.attributes_and_templates: + if not is_definition(attr): # skip references + continue if attr.root_namespace not in grouped_attributes: grouped_attributes[attr.root_namespace] = [] grouped_attributes[attr.root_namespace].append(attr) @@ -351,7 +351,10 @@ def _grouped_attribute_definitions(self, semconvset): def _grouped_metric_definitions(self, semconvset): grouped_metrics = {} - for semconv in filter(lambda s: is_metric(s), semconvset.models.values()): + for semconv in semconvset.models.values(): + if not is_metric(semconv): + continue + if semconv.root_namespace not in grouped_metrics: grouped_metrics[semconv.root_namespace] = [] @@ -370,5 +373,5 @@ def _write_template_to_file(self, template, data, output_name): content = template.render(data) if content != "": - with open(output_name, "w") as f: + with open(output_name, "w", encoding="utf-8") as f: f.write(content) diff --git a/semantic-conventions/src/tests/semconv/templating/test_code.py b/semantic-conventions/src/tests/semconv/templating/test_code.py index 43866bd5..52a1e679 100644 --- a/semantic-conventions/src/tests/semconv/templating/test_code.py +++ b/semantic-conventions/src/tests/semconv/templating/test_code.py @@ -15,7 +15,7 @@ def test_codegen_units(test_file_path, read_test_file): filename = os.path.join(tempfile.mkdtemp(), "Attributes.java") renderer.render(semconv, template_path, filename, None) - with open(filename) as f: + with open(filename, "r", encoding="utf-8") as f: result = f.read() expected = read_test_file("jinja", "metrics", "expected.java") @@ -36,7 +36,7 @@ def test_strip_blocks_enabled(test_file_path, read_test_file): filename = os.path.join(tempfile.mkdtemp(), "Attributes.java") renderer.render(semconv, template_path, filename, None) - with open(filename) as f: + with open(filename, "r", encoding="utf-8") as f: result = f.read() expected = read_test_file( @@ -58,7 +58,7 @@ def test_codegen_attribute_templates(test_file_path, read_test_file): filename = os.path.join(tempfile.mkdtemp(), "Attributes.java") renderer.render(semconv, template_path, filename, None) - with open(filename) as f: + with open(filename, "r", encoding="utf-8") as f: result = f.read() expected = read_test_file("jinja", "attribute_templates", "expected.java") @@ -134,8 +134,8 @@ def test_codegen_attribute_root_ns_no_group_prefix(test_file_path, read_test_fil "root_namespace", ) - foo = read_test_file("jinja", test_path, "FooAttributes.java") - check_file(tmppath, "FooAttributes.java", foo) + res = read_test_file("jinja", test_path, "FooAttributes.java") + check_file(tmppath, "FooAttributes.java", res) other = read_test_file("jinja", test_path, "OtherAttributes.java") check_file(tmppath, "OtherAttributes.java", other) @@ -154,8 +154,8 @@ def test_codegen_attribute_root_ns_single_file(test_file_path, read_test_file): tmppath = tempfile.mkdtemp() renderer.render(semconv, template_path, os.path.join(tmppath, "All.java"), None) - all = read_test_file("jinja", test_path, "All.java") - check_file(tmppath, "All.java", all) + result = read_test_file("jinja", test_path, "All.java") + check_file(tmppath, "All.java", result) def test_codegen_attribute_root_ns_metrics(test_file_path, read_test_file): @@ -181,7 +181,6 @@ def test_codegen_attribute_root_ns_metrics(test_file_path, read_test_file): def check_file(tmppath, actual_filename, expected_content): - with open(os.path.join(tmppath, actual_filename)) as f: + with open(os.path.join(tmppath, actual_filename), "r", encoding="utf-8") as f: actual = f.read() - print(actual) assert actual == expected_content From 971dbd26a099e370faafd92f6c2d45e6bcdd338a Mon Sep 17 00:00:00 2001 From: Liudmila Molkova Date: Wed, 6 Dec 2023 19:10:54 -0800 Subject: [PATCH 12/18] fix default metric stability and add better metrics tests --- .../semconv/model/semantic_attribute.py | 3 +- .../src/opentelemetry/semconv/model/utils.py | 2 +- .../opentelemetry/semconv/templating/code.py | 14 ++++---- .../attributes_and_metrics/First.java | 12 ++++--- .../attributes_and_metrics/Second.java | 15 -------- .../attributes_and_metrics/SecondGroup.java | 19 +++++++++++ .../attributes_and_metrics/semconv.yml | 7 ++-- .../attributes_and_metrics/template | 34 ++++++++++++++++--- .../src/tests/semconv/templating/test_code.py | 4 +-- 9 files changed, 73 insertions(+), 37 deletions(-) delete mode 100644 semantic-conventions/src/tests/data/jinja/group_by_root_namespace/attributes_and_metrics/Second.java create mode 100644 semantic-conventions/src/tests/data/jinja/group_by_root_namespace/attributes_and_metrics/SecondGroup.java diff --git a/semantic-conventions/src/opentelemetry/semconv/model/semantic_attribute.py b/semantic-conventions/src/opentelemetry/semconv/model/semantic_attribute.py index b8a25ac7..fbd4629f 100644 --- a/semantic-conventions/src/opentelemetry/semconv/model/semantic_attribute.py +++ b/semantic-conventions/src/opentelemetry/semconv/model/semantic_attribute.py @@ -320,11 +320,10 @@ def parse_stability_deprecated(stability, deprecated, position_data): if "stability" in position_data else position_data["deprecated"], ) - return stability, deprecated + return stability or StabilityLevel.EXPERIMENTAL, deprecated @staticmethod def check_stability(stability_value, position): - stability_value_map = { "deprecated": StabilityLevel.DEPRECATED, "experimental": StabilityLevel.EXPERIMENTAL, diff --git a/semantic-conventions/src/opentelemetry/semconv/model/utils.py b/semantic-conventions/src/opentelemetry/semconv/model/utils.py index 291d28f5..8f114c3c 100644 --- a/semantic-conventions/src/opentelemetry/semconv/model/utils.py +++ b/semantic-conventions/src/opentelemetry/semconv/model/utils.py @@ -13,7 +13,7 @@ # limitations under the License. import re -from typing import Tuple # noqa: F401 +from typing import Tuple from opentelemetry.semconv.model.exceptions import ValidationError diff --git a/semantic-conventions/src/opentelemetry/semconv/templating/code.py b/semantic-conventions/src/opentelemetry/semconv/templating/code.py index 3d435dcf..9bd5892b 100644 --- a/semantic-conventions/src/opentelemetry/semconv/templating/code.py +++ b/semantic-conventions/src/opentelemetry/semconv/templating/code.py @@ -167,16 +167,18 @@ def first_up(name: str) -> str: return name[0].upper() + name[1:] -def is_stable(attribute: SemanticAttribute) -> bool: - return attribute.stability == StabilityLevel.STABLE +def is_stable(obj: typing.Union[SemanticAttribute, BaseSemanticConvention]) -> bool: + return obj.stability == StabilityLevel.STABLE -def is_deprecated(attribute: SemanticAttribute) -> bool: - return attribute.stability == StabilityLevel.DEPRECATED +def is_deprecated(obj: typing.Union[SemanticAttribute, BaseSemanticConvention]) -> bool: + return obj.stability == StabilityLevel.DEPRECATED -def is_experimental(attribute: SemanticAttribute) -> bool: - return attribute.stability == StabilityLevel.EXPERIMENTAL +def is_experimental( + obj: typing.Union[SemanticAttribute, BaseSemanticConvention] +) -> bool: + return obj.stability == StabilityLevel.EXPERIMENTAL def is_definition(attribute: SemanticAttribute) -> bool: diff --git a/semantic-conventions/src/tests/data/jinja/group_by_root_namespace/attributes_and_metrics/First.java b/semantic-conventions/src/tests/data/jinja/group_by_root_namespace/attributes_and_metrics/First.java index 19728a9a..7ec15b57 100644 --- a/semantic-conventions/src/tests/data/jinja/group_by_root_namespace/attributes_and_metrics/First.java +++ b/semantic-conventions/src/tests/data/jinja/group_by_root_namespace/attributes_and_metrics/First.java @@ -1,5 +1,7 @@ package io.opentelemetry.instrumentation.api.semconv; +import io.opentelemetry.api.metrics.Meter; + class First { /** * short description of attr_one @@ -7,9 +9,11 @@ class First { public static final AttributeKey FIRST_ATTR_ONE = booleanKey("first.attr_one"); /** * first metric description - * - * Instrument: counter - * Unit: {one} + * Experimental: False */ - public static final String FIRST_METRIC_NAME = "first.metric.name"; + public static final LongCounterBuilder createFirstMetric(Meter meter) { + return meter.counterBuilder("first.metric") + .setDescription("first metric description") + .setUnit("{one}"); + } } \ No newline at end of file diff --git a/semantic-conventions/src/tests/data/jinja/group_by_root_namespace/attributes_and_metrics/Second.java b/semantic-conventions/src/tests/data/jinja/group_by_root_namespace/attributes_and_metrics/Second.java deleted file mode 100644 index 8511b7e3..00000000 --- a/semantic-conventions/src/tests/data/jinja/group_by_root_namespace/attributes_and_metrics/Second.java +++ /dev/null @@ -1,15 +0,0 @@ -package io.opentelemetry.instrumentation.api.semconv; - -class Second { - /** - * short description of attr_two - */ - public static final AttributeKey SECOND_ATTR_TWO = longKey("second.attr_two"); - /** - * second metric description - * - * Instrument: histogram - * Unit: s - */ - public static final String SECOND_METRIC_NAME = "second.metric.name"; -} \ No newline at end of file diff --git a/semantic-conventions/src/tests/data/jinja/group_by_root_namespace/attributes_and_metrics/SecondGroup.java b/semantic-conventions/src/tests/data/jinja/group_by_root_namespace/attributes_and_metrics/SecondGroup.java new file mode 100644 index 00000000..20fbc0ed --- /dev/null +++ b/semantic-conventions/src/tests/data/jinja/group_by_root_namespace/attributes_and_metrics/SecondGroup.java @@ -0,0 +1,19 @@ +package io.opentelemetry.instrumentation.api.semconv; + +import io.opentelemetry.api.metrics.Meter; + +class SecondGroup { + /** + * short description of attr_two + */ + public static final AttributeKey SECOND_GROUP_ATTR_TWO = longKey("second_group.attr_two"); + /** + * second metric description + * Experimental: True + */ + public static final DoubleHistogramBuilder createSecondGroupMetric(Meter meter) { + return meter.histogramBuilder("second_group.metric") + .setDescription("second metric description") + .setUnit("s"); + } +} \ No newline at end of file diff --git a/semantic-conventions/src/tests/data/jinja/group_by_root_namespace/attributes_and_metrics/semconv.yml b/semantic-conventions/src/tests/data/jinja/group_by_root_namespace/attributes_and_metrics/semconv.yml index 101916ac..3d2c670b 100644 --- a/semantic-conventions/src/tests/data/jinja/group_by_root_namespace/attributes_and_metrics/semconv.yml +++ b/semantic-conventions/src/tests/data/jinja/group_by_root_namespace/attributes_and_metrics/semconv.yml @@ -10,19 +10,20 @@ groups: - id: first_metric_id brief: first metric description - metric_name: first.metric.name + metric_name: first.metric instrument: counter type: metric unit: "{one}" + stability: stable extends: first_group_id - id: second_group_id brief: second metric description - metric_name: second.metric.name + metric_name: second_group.metric type: metric instrument: histogram unit: "s" - prefix: second + prefix: second_group attributes: - id: attr_two type: int diff --git a/semantic-conventions/src/tests/data/jinja/group_by_root_namespace/attributes_and_metrics/template b/semantic-conventions/src/tests/data/jinja/group_by_root_namespace/attributes_and_metrics/template index c8080f55..f1cacd96 100644 --- a/semantic-conventions/src/tests/data/jinja/group_by_root_namespace/attributes_and_metrics/template +++ b/semantic-conventions/src/tests/data/jinja/group_by_root_namespace/attributes_and_metrics/template @@ -28,8 +28,32 @@ {{ type | to_camelcase(False)}}Key {%- endif -%} {%- endmacro %} +{%- macro to_java_instrument_builder_factory(instrument) -%} + {%- if instrument == "counter" -%} + counterBuilder + {%- elif instrument == "histogram" -%} + histogramBuilder + {%- elif instrument == "updowncounter" -%} + upDownCounterBuilder + {%- elif instrument == "gauge" -%} + gaugeBuilder + {%- endif -%} +{%- endmacro %} +{%- macro to_java_instrument_builder_type(instrument) -%} + {%- if instrument == "counter" -%} + LongCounterBuilder + {%- elif instrument == "histogram" -%} + DoubleHistogramBuilder + {%- elif instrument == "updowncounter" -%} + LongUpDownCounterBuilder + {%- elif instrument == "gauge" -%} + DoubleGaugeBuilder + {%- endif -%} +{%- endmacro %} package io.opentelemetry.instrumentation.api.semconv; +import io.opentelemetry.api.metrics.Meter; + class {{ root_namespace | to_camelcase(True) }} { {%- for attribute in attributes_and_templates %} @@ -41,10 +65,12 @@ class {{ root_namespace | to_camelcase(True) }} { {%- for metric in metrics %} /** * {{metric.brief | to_doc_brief}} - * - * Instrument: {{metric.instrument }} - * Unit: {{metric.unit }} + * Experimental: {{ metric | is_experimental }} */ - public static final String {{metric.metric_name | to_const_name}} = "{{metric.metric_name}}"; + public static final {{ to_java_instrument_builder_type(metric.instrument) }} create{{metric.metric_name | to_camelcase(True)}}(Meter meter) { + return meter.{{to_java_instrument_builder_factory(metric.instrument)}}("{{ metric.metric_name }}") + .setDescription("{{ metric.brief }}") + .setUnit("{{ metric.unit }}"); + } {% endfor %} } \ No newline at end of file diff --git a/semantic-conventions/src/tests/semconv/templating/test_code.py b/semantic-conventions/src/tests/semconv/templating/test_code.py index 52a1e679..e60eb927 100644 --- a/semantic-conventions/src/tests/semconv/templating/test_code.py +++ b/semantic-conventions/src/tests/semconv/templating/test_code.py @@ -176,8 +176,8 @@ def test_codegen_attribute_root_ns_metrics(test_file_path, read_test_file): first = read_test_file("jinja", test_path, "First.java") check_file(tmppath, "First.java", first) - second = read_test_file("jinja", test_path, "Second.java") - check_file(tmppath, "Second.java", second) + second = read_test_file("jinja", test_path, "SecondGroup.java") + check_file(tmppath, "SecondGroup.java", second) def check_file(tmppath, actual_filename, expected_content): From 82e2a42a1bf0a94be6b9d11bee5177c4134aa0ac Mon Sep 17 00:00:00 2001 From: Liudmila Molkova Date: Sun, 10 Dec 2023 16:08:34 -0800 Subject: [PATCH 13/18] lint --- semantic-conventions/src/opentelemetry/semconv/model/utils.py | 1 - 1 file changed, 1 deletion(-) diff --git a/semantic-conventions/src/opentelemetry/semconv/model/utils.py b/semantic-conventions/src/opentelemetry/semconv/model/utils.py index 8f114c3c..c33e55ed 100644 --- a/semantic-conventions/src/opentelemetry/semconv/model/utils.py +++ b/semantic-conventions/src/opentelemetry/semconv/model/utils.py @@ -13,7 +13,6 @@ # limitations under the License. import re -from typing import Tuple from opentelemetry.semconv.model.exceptions import ValidationError From 870178f60553e2e5462200ccd365b759d93971ff Mon Sep 17 00:00:00 2001 From: Liudmila Molkova Date: Mon, 11 Dec 2023 07:58:36 -0800 Subject: [PATCH 14/18] tests --- .../src/opentelemetry/semconv/model/semantic_attribute.py | 2 +- .../src/opentelemetry/semconv/templating/code.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/semantic-conventions/src/opentelemetry/semconv/model/semantic_attribute.py b/semantic-conventions/src/opentelemetry/semconv/model/semantic_attribute.py index fbd4629f..f22a9bbd 100644 --- a/semantic-conventions/src/opentelemetry/semconv/model/semantic_attribute.py +++ b/semantic-conventions/src/opentelemetry/semconv/model/semantic_attribute.py @@ -320,7 +320,7 @@ def parse_stability_deprecated(stability, deprecated, position_data): if "stability" in position_data else position_data["deprecated"], ) - return stability or StabilityLevel.EXPERIMENTAL, deprecated + return stability, deprecated @staticmethod def check_stability(stability_value, position): diff --git a/semantic-conventions/src/opentelemetry/semconv/templating/code.py b/semantic-conventions/src/opentelemetry/semconv/templating/code.py index 9bd5892b..1685d967 100644 --- a/semantic-conventions/src/opentelemetry/semconv/templating/code.py +++ b/semantic-conventions/src/opentelemetry/semconv/templating/code.py @@ -178,7 +178,7 @@ def is_deprecated(obj: typing.Union[SemanticAttribute, BaseSemanticConvention]) def is_experimental( obj: typing.Union[SemanticAttribute, BaseSemanticConvention] ) -> bool: - return obj.stability == StabilityLevel.EXPERIMENTAL + return obj.stability is None or obj.stability == StabilityLevel.EXPERIMENTAL def is_definition(attribute: SemanticAttribute) -> bool: From 4d25ff10cc7c8faad88dbd74acd2ba86d4be6926 Mon Sep 17 00:00:00 2001 From: Liudmila Molkova Date: Mon, 11 Dec 2023 08:40:07 -0800 Subject: [PATCH 15/18] nits --- semantic-conventions/src/opentelemetry/semconv/model/utils.py | 1 + 1 file changed, 1 insertion(+) diff --git a/semantic-conventions/src/opentelemetry/semconv/model/utils.py b/semantic-conventions/src/opentelemetry/semconv/model/utils.py index c33e55ed..291d28f5 100644 --- a/semantic-conventions/src/opentelemetry/semconv/model/utils.py +++ b/semantic-conventions/src/opentelemetry/semconv/model/utils.py @@ -13,6 +13,7 @@ # limitations under the License. import re +from typing import Tuple # noqa: F401 from opentelemetry.semconv.model.exceptions import ValidationError From 59e6bc45bcc27cd36a7c1f14278b3f4cbfe703ce Mon Sep 17 00:00:00 2001 From: Liudmila Molkova Date: Mon, 11 Dec 2023 14:44:01 -0800 Subject: [PATCH 16/18] hack pushing for PR --- .github/workflows/semconvgen.yml | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/.github/workflows/semconvgen.yml b/.github/workflows/semconvgen.yml index 28a42a8f..d8ed3a49 100644 --- a/.github/workflows/semconvgen.yml +++ b/.github/workflows/semconvgen.yml @@ -13,7 +13,7 @@ jobs: tests: runs-on: ubuntu-latest defaults: - run: + run: working-directory: semantic-conventions/ steps: - uses: actions/checkout@v3 @@ -39,7 +39,7 @@ jobs: run: pylint *.py src/ - name: Type checking (mypy) run: mypy src/ - + build-and-publish-docker: runs-on: ubuntu-latest @@ -68,3 +68,11 @@ jobs: else tag_and_push "${GITHUB_REF#"refs/tags/"}" fi + - name: Push the Dev Docker image + if: startsWith(github.ref, 'refs/pull/243') + run: | + echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin + function tag_and_push { + docker tag semconvgen "otel/semconvgen:${1}" && docker push "otel/semconvgen:${1}" + } + tag_and_push "0.24.0-dev-pr-243" From dacda3cc86638a428a31af8e17bcd1a80787693d Mon Sep 17 00:00:00 2001 From: Liudmila Molkova Date: Tue, 12 Dec 2023 09:08:47 -0800 Subject: [PATCH 17/18] Apply suggestions from code review Co-authored-by: Armin Ruech <7052238+arminru@users.noreply.github.com> --- .github/workflows/semconvgen.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/semconvgen.yml b/.github/workflows/semconvgen.yml index d8ed3a49..16ede1c5 100644 --- a/.github/workflows/semconvgen.yml +++ b/.github/workflows/semconvgen.yml @@ -69,10 +69,12 @@ jobs: tag_and_push "${GITHUB_REF#"refs/tags/"}" fi - name: Push the Dev Docker image - if: startsWith(github.ref, 'refs/pull/243') + if: startsWith(github.ref, 'refs/heads/feature/') run: | echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin function tag_and_push { docker tag semconvgen "otel/semconvgen:${1}" && docker push "otel/semconvgen:${1}" } - tag_and_push "0.24.0-dev-pr-243" + TAG="${GITHUB_REF#"refs/heads/"}" + TAG="${TAG/"/"/"-"}" + tag_and_push "${TAG}" From 66ebf2c34179844bc58f13398d42aa916cb63afc Mon Sep 17 00:00:00 2001 From: Alexander Wert Date: Tue, 19 Dec 2023 14:32:21 +0100 Subject: [PATCH 18/18] Update semantic-conventions/README.md --- semantic-conventions/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/semantic-conventions/README.md b/semantic-conventions/README.md index 6ce33916..5ebdba88 100644 --- a/semantic-conventions/README.md +++ b/semantic-conventions/README.md @@ -136,7 +136,7 @@ The image also supports customizing [Whitespace Control in Jinja templates](https://jinja.palletsprojects.com/en/3.1.x/templates/#whitespace-control) via the additional flag `--trim-whitespace`. Providing the flag will enable both `lstrip_blocks` and `trim_blocks`. -### Accessing Semantic Conventions in the templated +### Accessing Semantic Conventions in the template When template is processed, it has access to a set of variables that depends on the `--file-per-group` value (or lack of it). You can access properties of these variables and call Jinja or Python functions defined on them.