glean-parser 17.1.0__tar.gz → 17.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (181) hide show
  1. glean_parser-17.1.0/docs/readme.md → glean_parser-17.2.0/PKG-INFO +30 -11
  2. {glean_parser-17.1.0 → glean_parser-17.2.0}/README.md +1 -11
  3. {glean_parser-17.1.0 → glean_parser-17.2.0}/glean_parser/lint.py +28 -0
  4. {glean_parser-17.1.0 → glean_parser-17.2.0}/glean_parser/metrics.py +56 -0
  5. {glean_parser-17.1.0 → glean_parser-17.2.0}/glean_parser/parser.py +21 -4
  6. {glean_parser-17.1.0 → glean_parser-17.2.0}/glean_parser/rust.py +2 -0
  7. {glean_parser-17.1.0 → glean_parser-17.2.0}/glean_parser/schemas/metrics.2-0-0.schema.yaml +64 -10
  8. {glean_parser-17.1.0 → glean_parser-17.2.0}/glean_parser/templates/rust.jinja2 +15 -2
  9. {glean_parser-17.1.0 → glean_parser-17.2.0}/glean_parser/util.py +9 -1
  10. glean_parser-17.2.0/pyproject.toml +76 -0
  11. {glean_parser-17.1.0 → glean_parser-17.2.0}/tests/data/all_metrics.yaml +15 -0
  12. glean_parser-17.2.0/tests/data/dual_labeled.yaml +96 -0
  13. glean_parser-17.2.0/tests/data/dual_labeled_invalid.yaml +72 -0
  14. glean_parser-17.2.0/tests/data/redefined_category.yamlx +32 -0
  15. glean_parser-17.2.0/tests/data/redefined_metric.yamlx +31 -0
  16. glean_parser-17.2.0/tests/data/redefined_ping.yamlx +30 -0
  17. glean_parser-17.2.0/tests/data/same_name_different_category.yaml +32 -0
  18. {glean_parser-17.1.0 → glean_parser-17.2.0}/tests/test_lint.py +48 -1
  19. {glean_parser-17.1.0 → glean_parser-17.2.0}/tests/test_parser.py +74 -1
  20. {glean_parser-17.1.0 → glean_parser-17.2.0}/tests/test_rust.py +48 -0
  21. glean_parser-17.1.0/.circleci/config.yml +0 -271
  22. glean_parser-17.1.0/.editorconfig +0 -21
  23. glean_parser-17.1.0/.github/CODEOWNERS +0 -12
  24. glean_parser-17.1.0/.github/ISSUE_TEMPLATE.md +0 -15
  25. glean_parser-17.1.0/.github/dependabot.yml +0 -6
  26. glean_parser-17.1.0/.github/pull_request_template.md +0 -8
  27. glean_parser-17.1.0/.swiftlint.yml +0 -6
  28. glean_parser-17.1.0/CHANGELOG.md +0 -820
  29. glean_parser-17.1.0/CODE_OF_CONDUCT.md +0 -15
  30. glean_parser-17.1.0/CONTRIBUTING.md +0 -189
  31. glean_parser-17.1.0/MANIFEST.in +0 -14
  32. glean_parser-17.1.0/Makefile +0 -79
  33. glean_parser-17.1.0/PKG-INFO +0 -897
  34. glean_parser-17.1.0/docs/Makefile +0 -20
  35. glean_parser-17.1.0/docs/_static/glean.jpeg +0 -0
  36. glean_parser-17.1.0/docs/authors.md +0 -17
  37. glean_parser-17.1.0/docs/conf.py +0 -160
  38. glean_parser-17.1.0/docs/contributing.md +0 -189
  39. glean_parser-17.1.0/docs/history.md +0 -820
  40. glean_parser-17.1.0/docs/index.rst +0 -27
  41. glean_parser-17.1.0/docs/installation.md +0 -41
  42. glean_parser-17.1.0/docs/make.bat +0 -36
  43. glean_parser-17.1.0/docs/metrics-yaml.rst +0 -13
  44. glean_parser-17.1.0/docs/pings-yaml.rst +0 -13
  45. glean_parser-17.1.0/docs/tags-yaml.rst +0 -13
  46. glean_parser-17.1.0/glean_parser.egg-info/PKG-INFO +0 -897
  47. glean_parser-17.1.0/glean_parser.egg-info/SOURCES.txt +0 -173
  48. glean_parser-17.1.0/glean_parser.egg-info/dependency_links.txt +0 -1
  49. glean_parser-17.1.0/glean_parser.egg-info/entry_points.txt +0 -2
  50. glean_parser-17.1.0/glean_parser.egg-info/not-zip-safe +0 -1
  51. glean_parser-17.1.0/glean_parser.egg-info/requires.txt +0 -6
  52. glean_parser-17.1.0/glean_parser.egg-info/top_level.txt +0 -1
  53. glean_parser-17.1.0/pytest.ini +0 -7
  54. glean_parser-17.1.0/requirements_dev.txt +0 -13
  55. glean_parser-17.1.0/setup.cfg +0 -10
  56. glean_parser-17.1.0/setup.py +0 -77
  57. glean_parser-17.1.0/tools/extract_data_categories.py +0 -176
  58. {glean_parser-17.1.0 → glean_parser-17.2.0}/.gitignore +0 -0
  59. {glean_parser-17.1.0 → glean_parser-17.2.0}/AUTHORS.md +0 -0
  60. {glean_parser-17.1.0 → glean_parser-17.2.0}/LICENSE +0 -0
  61. {glean_parser-17.1.0 → glean_parser-17.2.0}/glean_parser/__init__.py +0 -0
  62. {glean_parser-17.1.0 → glean_parser-17.2.0}/glean_parser/__main__.py +0 -0
  63. {glean_parser-17.1.0 → glean_parser-17.2.0}/glean_parser/coverage.py +0 -0
  64. {glean_parser-17.1.0 → glean_parser-17.2.0}/glean_parser/data_review.py +0 -0
  65. {glean_parser-17.1.0 → glean_parser-17.2.0}/glean_parser/go_server.py +0 -0
  66. {glean_parser-17.1.0 → glean_parser-17.2.0}/glean_parser/javascript.py +0 -0
  67. {glean_parser-17.1.0 → glean_parser-17.2.0}/glean_parser/javascript_server.py +0 -0
  68. {glean_parser-17.1.0 → glean_parser-17.2.0}/glean_parser/kotlin.py +0 -0
  69. {glean_parser-17.1.0 → glean_parser-17.2.0}/glean_parser/markdown.py +0 -0
  70. {glean_parser-17.1.0 → glean_parser-17.2.0}/glean_parser/pings.py +0 -0
  71. {glean_parser-17.1.0 → glean_parser-17.2.0}/glean_parser/python_server.py +0 -0
  72. {glean_parser-17.1.0 → glean_parser-17.2.0}/glean_parser/ruby_server.py +0 -0
  73. {glean_parser-17.1.0 → glean_parser-17.2.0}/glean_parser/rust_server.py +0 -0
  74. {glean_parser-17.1.0 → glean_parser-17.2.0}/glean_parser/schemas/metrics.1-0-0.schema.yaml +0 -0
  75. {glean_parser-17.1.0 → glean_parser-17.2.0}/glean_parser/schemas/pings.1-0-0.schema.yaml +0 -0
  76. {glean_parser-17.1.0 → glean_parser-17.2.0}/glean_parser/schemas/pings.2-0-0.schema.yaml +0 -0
  77. {glean_parser-17.1.0 → glean_parser-17.2.0}/glean_parser/schemas/tags.1-0-0.schema.yaml +0 -0
  78. {glean_parser-17.1.0 → glean_parser-17.2.0}/glean_parser/swift.py +0 -0
  79. {glean_parser-17.1.0 → glean_parser-17.2.0}/glean_parser/tags.py +0 -0
  80. {glean_parser-17.1.0 → glean_parser-17.2.0}/glean_parser/templates/data_review.jinja2 +0 -0
  81. {glean_parser-17.1.0 → glean_parser-17.2.0}/glean_parser/templates/go_server.jinja2 +0 -0
  82. {glean_parser-17.1.0 → glean_parser-17.2.0}/glean_parser/templates/javascript.buildinfo.jinja2 +0 -0
  83. {glean_parser-17.1.0 → glean_parser-17.2.0}/glean_parser/templates/javascript.jinja2 +0 -0
  84. {glean_parser-17.1.0 → glean_parser-17.2.0}/glean_parser/templates/javascript_server.jinja2 +0 -0
  85. {glean_parser-17.1.0 → glean_parser-17.2.0}/glean_parser/templates/kotlin.buildinfo.jinja2 +0 -0
  86. {glean_parser-17.1.0 → glean_parser-17.2.0}/glean_parser/templates/kotlin.jinja2 +0 -0
  87. {glean_parser-17.1.0 → glean_parser-17.2.0}/glean_parser/templates/markdown.jinja2 +0 -0
  88. {glean_parser-17.1.0 → glean_parser-17.2.0}/glean_parser/templates/python_server.jinja2 +0 -0
  89. {glean_parser-17.1.0 → glean_parser-17.2.0}/glean_parser/templates/qmldir.jinja2 +0 -0
  90. {glean_parser-17.1.0 → glean_parser-17.2.0}/glean_parser/templates/ruby_server.jinja2 +0 -0
  91. {glean_parser-17.1.0 → glean_parser-17.2.0}/glean_parser/templates/rust_server.jinja2 +0 -0
  92. {glean_parser-17.1.0 → glean_parser-17.2.0}/glean_parser/templates/swift.jinja2 +0 -0
  93. {glean_parser-17.1.0 → glean_parser-17.2.0}/glean_parser/translate.py +0 -0
  94. {glean_parser-17.1.0 → glean_parser-17.2.0}/glean_parser/translation_options.py +0 -0
  95. {glean_parser-17.1.0 → glean_parser-17.2.0}/glean_parser/validate_ping.py +0 -0
  96. {glean_parser-17.1.0 → glean_parser-17.2.0}/server_telemetry/sdk-metrics-compat.yaml +0 -0
  97. {glean_parser-17.1.0 → glean_parser-17.2.0}/server_telemetry/server-side-pings.yaml +0 -0
  98. {glean_parser-17.1.0 → glean_parser-17.2.0}/tests/conftest.py +0 -0
  99. {glean_parser-17.1.0 → glean_parser-17.2.0}/tests/data/all_pings.yaml +0 -0
  100. {glean_parser-17.1.0 → glean_parser-17.2.0}/tests/data/attribution.yaml +0 -0
  101. {glean_parser-17.1.0 → glean_parser-17.2.0}/tests/data/bad_attribution.yamlx +0 -0
  102. {glean_parser-17.1.0 → glean_parser-17.2.0}/tests/data/bad_ping.yamlx +0 -0
  103. {glean_parser-17.1.0 → glean_parser-17.2.0}/tests/data/core.yaml +0 -0
  104. {glean_parser-17.1.0 → glean_parser-17.2.0}/tests/data/duplicate_labeled.yaml +0 -0
  105. {glean_parser-17.1.0 → glean_parser-17.2.0}/tests/data/duplicate_send_in_ping.yaml +0 -0
  106. {glean_parser-17.1.0 → glean_parser-17.2.0}/tests/data/empty.yaml +0 -0
  107. {glean_parser-17.1.0 → glean_parser-17.2.0}/tests/data/event_key_ordering.yaml +0 -0
  108. {glean_parser-17.1.0 → glean_parser-17.2.0}/tests/data/events_with_types.yaml +0 -0
  109. {glean_parser-17.1.0 → glean_parser-17.2.0}/tests/data/fxa-server-metrics.yaml +0 -0
  110. {glean_parser-17.1.0 → glean_parser-17.2.0}/tests/data/fxa-server-pings.yaml +0 -0
  111. {glean_parser-17.1.0 → glean_parser-17.2.0}/tests/data/go_server_custom_ping_only_metrics.yaml +0 -0
  112. {glean_parser-17.1.0 → glean_parser-17.2.0}/tests/data/go_server_custom_ping_only_pings.yaml +0 -0
  113. {glean_parser-17.1.0 → glean_parser-17.2.0}/tests/data/go_server_events_and_custom_ping_metrics.yaml +0 -0
  114. {glean_parser-17.1.0 → glean_parser-17.2.0}/tests/data/go_server_events_and_custom_ping_pings.yaml +0 -0
  115. {glean_parser-17.1.0 → glean_parser-17.2.0}/tests/data/go_server_events_only_metrics.yaml +0 -0
  116. {glean_parser-17.1.0 → glean_parser-17.2.0}/tests/data/go_server_metrics_unsupported.yaml +0 -0
  117. {glean_parser-17.1.0 → glean_parser-17.2.0}/tests/data/invalid-ping-names.yaml +0 -0
  118. {glean_parser-17.1.0 → glean_parser-17.2.0}/tests/data/invalid.yamlx +0 -0
  119. {glean_parser-17.1.0 → glean_parser-17.2.0}/tests/data/jwe.yaml +0 -0
  120. {glean_parser-17.1.0 → glean_parser-17.2.0}/tests/data/metric-with-tags.yaml +0 -0
  121. {glean_parser-17.1.0 → glean_parser-17.2.0}/tests/data/mixed-expirations.yaml +0 -0
  122. {glean_parser-17.1.0 → glean_parser-17.2.0}/tests/data/name_too_similar.yaml +0 -0
  123. {glean_parser-17.1.0 → glean_parser-17.2.0}/tests/data/object.yaml +0 -0
  124. {glean_parser-17.1.0 → glean_parser-17.2.0}/tests/data/old_event_api.yamlx +0 -0
  125. {glean_parser-17.1.0 → glean_parser-17.2.0}/tests/data/ordering.yaml +0 -0
  126. {glean_parser-17.1.0 → glean_parser-17.2.0}/tests/data/pings.yaml +0 -0
  127. {glean_parser-17.1.0 → glean_parser-17.2.0}/tests/data/rate.yaml +0 -0
  128. {glean_parser-17.1.0 → glean_parser-17.2.0}/tests/data/reserved_categories.yamlx +0 -0
  129. {glean_parser-17.1.0 → glean_parser-17.2.0}/tests/data/ruby_server_metrics_unsupported.yaml +0 -0
  130. {glean_parser-17.1.0 → glean_parser-17.2.0}/tests/data/ruby_server_pings_unsupported.yaml +0 -0
  131. {glean_parser-17.1.0 → glean_parser-17.2.0}/tests/data/rust_server_custom_ping_only_metrics.yaml +0 -0
  132. {glean_parser-17.1.0 → glean_parser-17.2.0}/tests/data/rust_server_custom_ping_only_pings.yaml +0 -0
  133. {glean_parser-17.1.0 → glean_parser-17.2.0}/tests/data/rust_server_events_and_custom_ping_metrics.yaml +0 -0
  134. {glean_parser-17.1.0 → glean_parser-17.2.0}/tests/data/rust_server_events_and_custom_ping_pings.yaml +0 -0
  135. {glean_parser-17.1.0 → glean_parser-17.2.0}/tests/data/rust_server_events_only_metrics.yaml +0 -0
  136. {glean_parser-17.1.0 → glean_parser-17.2.0}/tests/data/rust_server_metrics_unsupported.yaml +0 -0
  137. {glean_parser-17.1.0 → glean_parser-17.2.0}/tests/data/schema-violation.yaml +0 -0
  138. {glean_parser-17.1.0 → glean_parser-17.2.0}/tests/data/send_if_empty_with_metrics.yaml +0 -0
  139. {glean_parser-17.1.0 → glean_parser-17.2.0}/tests/data/server_custom_ping_only_compare.go +0 -0
  140. {glean_parser-17.1.0 → glean_parser-17.2.0}/tests/data/server_custom_ping_only_compare.rs +0 -0
  141. {glean_parser-17.1.0 → glean_parser-17.2.0}/tests/data/server_events_and_custom_ping_compare.go +0 -0
  142. {glean_parser-17.1.0 → glean_parser-17.2.0}/tests/data/server_events_and_custom_ping_compare.rs +0 -0
  143. {glean_parser-17.1.0 → glean_parser-17.2.0}/tests/data/server_events_compare.rb +0 -0
  144. {glean_parser-17.1.0 → glean_parser-17.2.0}/tests/data/server_events_only_compare.go +0 -0
  145. {glean_parser-17.1.0 → glean_parser-17.2.0}/tests/data/server_events_only_compare.rs +0 -0
  146. {glean_parser-17.1.0 → glean_parser-17.2.0}/tests/data/server_metrics_no_events_no_pings.yaml +0 -0
  147. {glean_parser-17.1.0 → glean_parser-17.2.0}/tests/data/server_metrics_with_event.yaml +0 -0
  148. {glean_parser-17.1.0 → glean_parser-17.2.0}/tests/data/server_pings.yaml +0 -0
  149. {glean_parser-17.1.0 → glean_parser-17.2.0}/tests/data/single_labeled.yaml +0 -0
  150. {glean_parser-17.1.0 → glean_parser-17.2.0}/tests/data/smaller.yaml +0 -0
  151. {glean_parser-17.1.0 → glean_parser-17.2.0}/tests/data/tags.yaml +0 -0
  152. {glean_parser-17.1.0 → glean_parser-17.2.0}/tests/data/telemetry_mirror.yaml +0 -0
  153. {glean_parser-17.1.0 → glean_parser-17.2.0}/tests/data/text.yaml +0 -0
  154. {glean_parser-17.1.0 → glean_parser-17.2.0}/tests/data/text_invalid.yaml +0 -0
  155. {glean_parser-17.1.0 → glean_parser-17.2.0}/tests/data/unknown_ping_used.yaml +0 -0
  156. {glean_parser-17.1.0 → glean_parser-17.2.0}/tests/data/wrong_key.yamlx +0 -0
  157. {glean_parser-17.1.0 → glean_parser-17.2.0}/tests/data/yaml_nits.yamlx +0 -0
  158. {glean_parser-17.1.0 → glean_parser-17.2.0}/tests/detekt.yml +0 -0
  159. {glean_parser-17.1.0 → glean_parser-17.2.0}/tests/test-go/test.go.tmpl +0 -0
  160. {glean_parser-17.1.0 → glean_parser-17.2.0}/tests/test-js/package.json +0 -0
  161. {glean_parser-17.1.0 → glean_parser-17.2.0}/tests/test-js/test.js.tmpl +0 -0
  162. {glean_parser-17.1.0 → glean_parser-17.2.0}/tests/test-py/test.py +0 -0
  163. {glean_parser-17.1.0 → glean_parser-17.2.0}/tests/test-rb/test.rb.tmpl +0 -0
  164. {glean_parser-17.1.0 → glean_parser-17.2.0}/tests/test-rs/test.rs.tmpl +0 -0
  165. {glean_parser-17.1.0 → glean_parser-17.2.0}/tests/test_cli.py +0 -0
  166. {glean_parser-17.1.0 → glean_parser-17.2.0}/tests/test_go_server.py +0 -0
  167. {glean_parser-17.1.0 → glean_parser-17.2.0}/tests/test_javascript.py +0 -0
  168. {glean_parser-17.1.0 → glean_parser-17.2.0}/tests/test_javascript_server.py +0 -0
  169. {glean_parser-17.1.0 → glean_parser-17.2.0}/tests/test_kotlin.py +0 -0
  170. {glean_parser-17.1.0 → glean_parser-17.2.0}/tests/test_markdown.py +0 -0
  171. {glean_parser-17.1.0 → glean_parser-17.2.0}/tests/test_metrics.py +0 -0
  172. {glean_parser-17.1.0 → glean_parser-17.2.0}/tests/test_pings.py +0 -0
  173. {glean_parser-17.1.0 → glean_parser-17.2.0}/tests/test_python_server.py +0 -0
  174. {glean_parser-17.1.0 → glean_parser-17.2.0}/tests/test_ruby_server.py +0 -0
  175. {glean_parser-17.1.0 → glean_parser-17.2.0}/tests/test_rust_server.py +0 -0
  176. {glean_parser-17.1.0 → glean_parser-17.2.0}/tests/test_swift.py +0 -0
  177. {glean_parser-17.1.0 → glean_parser-17.2.0}/tests/test_tags.py +0 -0
  178. {glean_parser-17.1.0 → glean_parser-17.2.0}/tests/test_translate.py +0 -0
  179. {glean_parser-17.1.0 → glean_parser-17.2.0}/tests/test_utils.py +0 -0
  180. {glean_parser-17.1.0 → glean_parser-17.2.0}/tests/test_validate_ping.py +0 -0
  181. {glean_parser-17.1.0 → glean_parser-17.2.0}/tests/util.py +0 -0
@@ -1,3 +1,32 @@
1
+ Metadata-Version: 2.4
2
+ Name: glean-parser
3
+ Version: 17.2.0
4
+ Summary: Parser tools for Mozilla's Glean telemetry
5
+ Project-URL: Homepage, https://mozilla.github.io/glean
6
+ Project-URL: Repository, https://github.com/mozilla/glean_parser
7
+ Project-URL: Changelog, https://github.com/mozilla/glean_parser/blob/main/CHANGELOG.md
8
+ Author-email: The Glean Team <glean-team@mozilla.com>
9
+ License-File: AUTHORS.md
10
+ License-File: LICENSE
11
+ Classifier: Development Status :: 5 - Production/Stable
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Natural Language :: English
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.8
16
+ Classifier: Programming Language :: Python :: 3.9
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Requires-Python: >=3.8
22
+ Requires-Dist: click>=7
23
+ Requires-Dist: diskcache>=4
24
+ Requires-Dist: jinja2>=2.10.1
25
+ Requires-Dist: jsonschema>=3.0.2
26
+ Requires-Dist: platformdirs>=2.4.0
27
+ Requires-Dist: pyyaml>=5.3.1
28
+ Description-Content-Type: text/markdown
29
+
1
30
  # Glean Parser
2
31
 
3
32
  Parser tools for Mozilla's Glean telemetry.
@@ -16,17 +45,7 @@ code for various integrations, linting and coverage testing.
16
45
 
17
46
  ## Requirements
18
47
 
19
- - Python 3.8 (or later)
20
-
21
- The following library requirements are installed automatically when
22
- `glean_parser` is installed by `pip`.
23
-
24
- - Click
25
- - diskcache
26
- - Jinja2
27
- - jsonschema
28
- - platformdirs
29
- - PyYAML
48
+ - Python 3.8 (or later)
30
49
 
31
50
  ## Usage
32
51
 
@@ -16,17 +16,7 @@ code for various integrations, linting and coverage testing.
16
16
 
17
17
  ## Requirements
18
18
 
19
- - Python 3.8 (or later)
20
-
21
- The following library requirements are installed automatically when
22
- `glean_parser` is installed by `pip`.
23
-
24
- - Click
25
- - diskcache
26
- - Jinja2
27
- - jsonschema
28
- - platformdirs
29
- - PyYAML
19
+ - Python 3.8 (or later)
30
20
 
31
21
  ## Usage
32
22
 
@@ -572,6 +572,20 @@ def lint_metrics(
572
572
 
573
573
  nits.extend(_lint_all_objects(objs, parser_config))
574
574
 
575
+ # The information for whether there's duplicate categories found within the
576
+ # same YAML document is on the objs value, which is not presently linted
577
+ # (we pull out its metrics, pings, and tags and lint those).
578
+ # So we perform that custom work here.
579
+ if getattr(objs, "duplicate", None):
580
+ nits.append(
581
+ GlinterNit(
582
+ "REDEFINED_CATEGORY",
583
+ getattr(objs, "duplicate", ""),
584
+ f"Category redefined {objs.duplicate}", # type: ignore[attr-defined]
585
+ CheckType.error,
586
+ )
587
+ )
588
+
575
589
  for category_name, category in sorted(list(objs.items())):
576
590
  if category_name == "pings":
577
591
  nits.extend(_lint_pings(category, parser_config, valid_tag_names))
@@ -581,6 +595,20 @@ def lint_metrics(
581
595
  # currently we have no linting for tags
582
596
  continue
583
597
 
598
+ # The information for whether there's duplicate metrics found within the
599
+ # same YAML document is on the category value, which is not presently linted
600
+ # (we lint only its metrics).
601
+ # So we perform that custom work here.
602
+ if getattr(objs[category_name], "duplicate", None):
603
+ nits.append(
604
+ GlinterNit(
605
+ "REDEFINED_METRIC",
606
+ category_name,
607
+ f"Metric redefined {getattr(objs[category_name], 'duplicate', '')}",
608
+ CheckType.error,
609
+ )
610
+ )
611
+
584
612
  # Make sure the category has only Metrics, not Pings or Tags
585
613
  category_metrics = dict(
586
614
  (name, metric)
@@ -416,18 +416,23 @@ class LabeledString(Labeled, String):
416
416
  class LabeledCounter(Labeled, Counter):
417
417
  typename = "labeled_counter"
418
418
 
419
+
419
420
  class LabeledCustomDistribution(Labeled, CustomDistribution):
420
421
  typename = "labeled_custom_distribution"
421
422
 
423
+
422
424
  class LabeledMemoryDistribution(Labeled, MemoryDistribution):
423
425
  typename = "labeled_memory_distribution"
424
426
 
427
+
425
428
  class LabeledTimingDistribution(Labeled, TimingDistribution):
426
429
  typename = "labeled_timing_distribution"
427
430
 
431
+
428
432
  class LabeledQuantity(Labeled, Quantity):
429
433
  typename = "labeled_quantity"
430
434
 
435
+
431
436
  class Rate(Metric):
432
437
  typename = "rate"
433
438
 
@@ -527,4 +532,55 @@ class Object(Metric):
527
532
  return structure
528
533
 
529
534
 
535
+ class DualLabeledCounter(Metric):
536
+ typename = "dual_labeled_counter"
537
+ dual_labeled = True
538
+
539
+ def __init__(self, *args, **kwargs):
540
+ dual_labels = kwargs.pop("dual_labels", None)
541
+ if not dual_labels:
542
+ raise ValueError(
543
+ "`dual_labeled_counter` is missing required parameter `dual_labels`"
544
+ )
545
+ k = dual_labels.get("key", None)
546
+ if not k:
547
+ raise ValueError("`dual_labels` is missing required parameter `key`")
548
+ c = dual_labels.get("category", None)
549
+ if not c:
550
+ raise ValueError("`dual_labels` is missing required parameter `categories`")
551
+ keys = k.get("labels", None)
552
+ if keys is not None:
553
+ if not isinstance(keys, list) or not all(isinstance(k, str) for k in keys):
554
+ raise ValueError("key `labels` must be a list of strings")
555
+ self.ordered_keys = keys
556
+ self.keys = set([CowString(key) for key in keys])
557
+ else:
558
+ self.ordered_keys = None
559
+ self.keys = None
560
+ categories = c.get("labels", None)
561
+ if categories is not None:
562
+ if not isinstance(categories, list) or not all(
563
+ isinstance(c, str) for c in categories
564
+ ):
565
+ raise ValueError("category `labels` must be a list of strings")
566
+ self.ordered_categories = categories
567
+ self.categories = set([CowString(category) for category in categories])
568
+ else:
569
+ self.ordered_categories = None
570
+ self.categories = None
571
+ super().__init__(*args, **kwargs)
572
+
573
+ def serialize(self) -> Dict[str, util.JSONType]:
574
+ """
575
+ Serialize the metric back to JSON object model.
576
+ """
577
+ d = super().serialize()
578
+ d["keys"] = self.ordered_keys
579
+ d["categories"] = self.ordered_categories
580
+ del d["ordered_keys"]
581
+ del d["ordered_categories"]
582
+ del d["dual_labeled"]
583
+ return d
584
+
585
+
530
586
  ObjectTree = Dict[str, Dict[str, Union[Metric, pings.Ping, tags.Tag]]]
@@ -182,6 +182,10 @@ def _instantiate_metrics(
182
182
  global_tags = content.get("$tags", [])
183
183
  assert isinstance(global_tags, list)
184
184
 
185
+ # Duplicate category names end up on the root content.
186
+ if not hasattr(all_objects, "duplicate"):
187
+ setattr(all_objects, "duplicate", getattr(content, "duplicate", None))
188
+
185
189
  for category_key, category_val in sorted(content.items()):
186
190
  if category_key.startswith("$"):
187
191
  continue
@@ -273,6 +277,12 @@ def _instantiate_metrics(
273
277
  metric_obj.defined_in["line"],
274
278
  )
275
279
  else:
280
+ if not hasattr(all_objects[category_key], "duplicate"):
281
+ setattr(
282
+ all_objects[category_key],
283
+ "duplicate",
284
+ getattr(content[category_key], "duplicate", None),
285
+ )
276
286
  all_objects[category_key][metric_key] = metric_obj
277
287
  sources[(category_key, metric_key)] = filepath
278
288
 
@@ -292,6 +302,15 @@ def _instantiate_pings(
292
302
  assert isinstance(global_no_lint, list)
293
303
  ping_schedule_reverse_map: Dict[str, Set[str]] = dict()
294
304
 
305
+ # Duplicate ping names end up on the root content.
306
+ duplicate_ping = getattr(content, "duplicate", None)
307
+ if duplicate_ping:
308
+ yield util.format_error(
309
+ filepath,
310
+ "",
311
+ f"Duplicate ping named '{duplicate_ping}'.",
312
+ )
313
+
295
314
  for ping_key, ping_val in sorted(content.items()):
296
315
  if ping_key.startswith("$"):
297
316
  continue
@@ -344,8 +363,7 @@ def _instantiate_pings(
344
363
  yield util.format_error(
345
364
  filepath,
346
365
  "",
347
- f"Duplicate ping name '{ping_key}' "
348
- f"already defined in '{already_seen}'",
366
+ f"Duplicate ping name '{ping_key}' already defined in '{already_seen}'",
349
367
  )
350
368
  else:
351
369
  all_objects.setdefault("pings", {})[ping_key] = ping_obj
@@ -403,8 +421,7 @@ def _instantiate_tags(
403
421
  yield util.format_error(
404
422
  filepath,
405
423
  "",
406
- f"Duplicate tag name '{tag_key}' "
407
- f"already defined in '{already_seen}'",
424
+ f"Duplicate tag name '{tag_key}' already defined in '{already_seen}'",
408
425
  )
409
426
  else:
410
427
  all_objects.setdefault("tags", {})[tag_key] = tag_obj
@@ -101,6 +101,8 @@ def type_name(obj):
101
101
 
102
102
  if getattr(obj, "labeled", False):
103
103
  return "LabeledMetric<{}>".format(class_name(obj.type))
104
+ if getattr(obj, "dual_labeled_counter", False):
105
+ return "DualLabeledCounterMetric"
104
106
  generate_enums = getattr(obj, "_generate_enums", []) # Extra Keys? Reasons?
105
107
  if len(generate_enums):
106
108
  generic = None
@@ -52,7 +52,16 @@ definitions:
52
52
  labeled_metric_id:
53
53
  type: string
54
54
  pattern: "^[ -~]+$"
55
- maxLength: 71 # Note: this should be category + metric + 1
55
+ maxLength: 111 # Note: this should be category + metric + 1
56
+
57
+ optional_labels:
58
+ anyOf:
59
+ - type: array
60
+ uniqueItems: true
61
+ items:
62
+ $ref: "#/definitions/labeled_metric_id"
63
+ maxItems: 4096
64
+ - type: "null"
56
65
 
57
66
  metric:
58
67
  description: |
@@ -121,14 +130,20 @@ definitions:
121
130
  metric types include:
122
131
 
123
132
  `labeled_boolean`, `labeled_string`, `labeled_counter`,
124
- `labeled_custom_distribution`, `labeled_memory_distribution`,
125
- `labeled_timing_distribution`, `labeled_quantity`.
133
+ `dual_labeled_counter`, `labeled_custom_distribution`,
134
+ `labeled_memory_distribution`, `labeled_timing_distribution`,
135
+ `labeled_quantity`.
126
136
 
127
137
  - `text`: Record long text data.
128
138
 
129
139
  - `object`: Record structured data based on a pre-defined schema
130
140
  Additional properties: `structure`.
131
141
 
142
+ - `dual_labeled_counter`: A counter with two label dimensions.
143
+ This metric type is used to record a counter with two different
144
+ label axes, such as a key and a category. Additional properties:
145
+ `dual_labels`.
146
+
132
147
  type: string
133
148
  enum:
134
149
  - event
@@ -155,6 +170,7 @@ definitions:
155
170
  - rate
156
171
  - text
157
172
  - object
173
+ - dual_labeled_counter
158
174
 
159
175
  description:
160
176
  title: Description
@@ -370,13 +386,51 @@ definitions:
370
386
  labels in the special label `__other__`.
371
387
 
372
388
  Valid with any of the labeled metric types.
373
- anyOf:
374
- - type: array
375
- uniqueItems: true
376
- items:
377
- $ref: "#/definitions/labeled_metric_id"
378
- maxItems: 4096
379
- - type: "null"
389
+ $ref: "#/definitions/optional_labels"
390
+
391
+ dual_labels:
392
+ type: object
393
+ description: Defines the two label dimensions for a dual-labeled metric.
394
+ properties:
395
+ key:
396
+ type: object
397
+ description: The primary label dimension.
398
+ properties:
399
+ description:
400
+ type: string
401
+ description: |
402
+ Human-readable description of the key dimension (first label
403
+ axis).
404
+ labels:
405
+ description: |
406
+ Optional list of statically defined label values for the key
407
+ dimension.
408
+ $ref: "#/definitions/optional_labels"
409
+ required:
410
+ - description
411
+ additionalProperties: false
412
+ category:
413
+ type: object
414
+ description: The secondary label dimension.
415
+ properties:
416
+ description:
417
+ type: string
418
+ description: |
419
+ Human-readable description of the category dimension (second
420
+ label axis).
421
+ labels:
422
+ description: |
423
+ Optional list of statically defined label values for the
424
+ category dimension.
425
+ $ref: "#/definitions/optional_labels"
426
+ required:
427
+ - description
428
+ additionalProperties: false
429
+ required:
430
+ - key
431
+ - category
432
+ additionalProperties: false
433
+
380
434
 
381
435
  extra_keys:
382
436
  title: Extra keys
@@ -89,7 +89,7 @@ CommonMetricData {
89
89
  disabled: {{ obj.is_disabled()|rust }},
90
90
  ..Default::default()
91
91
  }
92
- {% endmacro %}
92
+ {%- endmacro -%}
93
93
  {% for category in categories %}
94
94
  {% if category.contains_pings %}
95
95
  {% for obj in category.objs.values() %}
@@ -150,7 +150,7 @@ pub mod {{ category.name|snake_case }} {
150
150
  {%- for arg_name in extra_metric_args if not obj.labeled and obj[arg_name] is defined and arg_name != 'allowed_extra_keys' -%}
151
151
  , {{ obj[arg_name]|rust }}
152
152
  {%- endfor -%}
153
- {{ ", " if obj.labeled else ")\n" }}
153
+ {{ ", " if obj.labeled or obj.dual_labeled else ")\n" }}
154
154
  {%- if obj.labeled -%}
155
155
  {%- if obj.labels -%}
156
156
  Some({{ obj.labels|rust }})
@@ -158,6 +158,19 @@ pub mod {{ category.name|snake_case }} {
158
158
  None
159
159
  {%- endif -%})
160
160
  {% endif %}
161
+ {%- if obj.dual_labeled -%}
162
+ {%- if obj.keys -%}
163
+ Some({{ obj.keys|rust }})
164
+ {%- else -%}
165
+ None
166
+ {%- endif -%}
167
+ {{ ", " }}
168
+ {%- if obj.categories -%}
169
+ Some({{ obj.categories|rust }})
170
+ {%- else -%}
171
+ None
172
+ {%- endif -%})
173
+ {% endif %}
161
174
  });
162
175
  {% endfor %}
163
176
  }
@@ -88,7 +88,15 @@ def yaml_load(stream):
88
88
 
89
89
  def _construct_mapping_adding_line(loader, node):
90
90
  loader.flatten_mapping(node)
91
- mapping = DictWrapper(loader.construct_pairs(node))
91
+ pairs = loader.construct_pairs(node)
92
+
93
+ # Redefinition of a key might be a mistake if that key is a metric name.
94
+ mapping = DictWrapper()
95
+ for pair in pairs:
96
+ if pair[0] in mapping:
97
+ mapping.duplicate = pair[0]
98
+ mapping[pair[0]] = pair[1]
99
+
92
100
  mapping.defined_in = {"line": node.start_mark.line}
93
101
  return mapping
94
102
 
@@ -0,0 +1,76 @@
1
+ [project]
2
+ name = "glean-parser"
3
+ dynamic = ["version"]
4
+ authors = [
5
+ { name = "The Glean Team", email = "glean-team@mozilla.com" }
6
+ ]
7
+ description = "Parser tools for Mozilla's Glean telemetry"
8
+ readme = "README.md"
9
+ classifiers = [
10
+ "Development Status :: 5 - Production/Stable",
11
+ "Intended Audience :: Developers",
12
+ "Natural Language :: English",
13
+ "Programming Language :: Python :: 3",
14
+ "Programming Language :: Python :: 3.8",
15
+ "Programming Language :: Python :: 3.9",
16
+ "Programming Language :: Python :: 3.10",
17
+ "Programming Language :: Python :: 3.11",
18
+ "Programming Language :: Python :: 3.12",
19
+ "Programming Language :: Python :: 3.13",
20
+ ]
21
+ requires-python = ">=3.8"
22
+ dependencies = [
23
+ "Click>=7",
24
+ "diskcache>=4",
25
+ "Jinja2>=2.10.1",
26
+ "jsonschema>=3.0.2",
27
+ "platformdirs>=2.4.0",
28
+ "PyYAML>=5.3.1",
29
+ ]
30
+
31
+ [project.urls]
32
+ Homepage = "https://mozilla.github.io/glean"
33
+ Repository = "https://github.com/mozilla/glean_parser"
34
+ Changelog = "https://github.com/mozilla/glean_parser/blob/main/CHANGELOG.md"
35
+
36
+ [project.scripts]
37
+ glean_parser = "glean_parser.__main__:main_wrapper"
38
+
39
+ [build-system]
40
+ requires = ["hatchling", "hatch-vcs"]
41
+ build-backend = "hatchling.build"
42
+
43
+ [dependency-groups]
44
+ dev = [
45
+ "coverage>=7.6.1",
46
+ "mypy>=1.14.1",
47
+ "pip-licenses>=4.5.1",
48
+ "pytest>=8.3.4",
49
+ "recommonmark>=0.7.1",
50
+ "ruff>=0.9.3",
51
+ "sphinx>=7.1.2",
52
+ "types-pyyaml>=6.0.12.20241230",
53
+ "yamllint>=1.35.1",
54
+ ]
55
+
56
+ [tool.hatch.build.targets.sdist]
57
+ include = [
58
+ "/glean_parser",
59
+ "/server_telemetry",
60
+ "/tests",
61
+ ]
62
+
63
+ [tool.hatch.version]
64
+ source = "vcs"
65
+
66
+ [tool.hatch.version.raw-options]
67
+ version_scheme = "no-guess-dev"
68
+
69
+ [tool.pytest.ini_options]
70
+ markers = [
71
+ "web_dependency: mark a test that requires a web connection.",
72
+ "node_dependency: mark a test that requires node.",
73
+ "ruby_dependency: mark a test that requires ruby.",
74
+ "go_dependency: mark a test that requires go.",
75
+ "rust_dependency: mark a test that requires rust.",
76
+ ]
@@ -27,6 +27,21 @@ all_metrics:
27
27
  - label_a
28
28
  - label_b
29
29
 
30
+ dual_labeled_counter:
31
+ <<: *defaults
32
+ type: dual_labeled_counter
33
+ dual_labels:
34
+ key:
35
+ description: The key for the dual labeled counter
36
+ labels:
37
+ - key1
38
+ - key2
39
+ category:
40
+ description: The category for the dual labeled counter
41
+ labels:
42
+ - category1
43
+ - category2
44
+
30
45
  bool:
31
46
  <<: *defaults
32
47
  type: boolean
@@ -0,0 +1,96 @@
1
+ # Any copyright is dedicated to the Public Domain.
2
+ # https://creativecommons.org/publicdomain/zero/1.0/
3
+
4
+ ---
5
+ $schema: moz://mozilla.org/schemas/glean/metrics/2-0-0
6
+
7
+ no_lint: [EXPIRATION_DATE_TOO_FAR]
8
+
9
+ test.dual_labeled:
10
+ static_static:
11
+ type: dual_labeled_counter
12
+ description: >
13
+ A dual labeled counter with static keys and categories
14
+ dual_labels:
15
+ key:
16
+ description: >
17
+ The key for the dual labeled counter
18
+ labels:
19
+ - key1
20
+ - key2
21
+ category:
22
+ description: >
23
+ The category for the dual labeled counter
24
+ labels:
25
+ - category1
26
+ - category2
27
+ bugs:
28
+ - https://bugzilla.mozilla.org/11137353
29
+ data_reviews:
30
+ - http://example.com/reviews
31
+ notification_emails:
32
+ - CHANGE-ME@example.com
33
+ expires: 2100-01-01
34
+
35
+ static_dynamic:
36
+ type: dual_labeled_counter
37
+ description: >
38
+ A dual labeled counter with static keys and dynamic categories
39
+ dual_labels:
40
+ key:
41
+ description: >
42
+ The key for the dual labeled counter
43
+ labels:
44
+ - key1
45
+ - key2
46
+ category:
47
+ description: >
48
+ The category for the dual labeled counter
49
+ bugs:
50
+ - https://bugzilla.mozilla.org/11137353
51
+ data_reviews:
52
+ - http://example.com/reviews
53
+ notification_emails:
54
+ - CHANGE-ME@example.com
55
+ expires: 2100-01-01
56
+
57
+ dynamic_static:
58
+ type: dual_labeled_counter
59
+ description: >
60
+ A dual labeled counter with dynamic keys and static categories
61
+ dual_labels:
62
+ key:
63
+ description: >
64
+ The key for the dual labeled counter
65
+ category:
66
+ description: >
67
+ The category for the dual labeled counter
68
+ labels:
69
+ - category1
70
+ - category2
71
+ bugs:
72
+ - https://bugzilla.mozilla.org/11137353
73
+ data_reviews:
74
+ - http://example.com/reviews
75
+ notification_emails:
76
+ - CHANGE-ME@example.com
77
+ expires: 2100-01-01
78
+
79
+ dynamic_dynamic:
80
+ type: dual_labeled_counter
81
+ description: >
82
+ A dual labeled counter with dynamic keys and dynamic categories
83
+ dual_labels:
84
+ key:
85
+ description: >
86
+ The key for the dual labeled counter
87
+ category:
88
+ description: >
89
+ The category for the dual labeled counter
90
+ bugs:
91
+ - https://bugzilla.mozilla.org/11137353
92
+ data_reviews:
93
+ - http://example.com/reviews
94
+ notification_emails:
95
+ - CHANGE-ME@example.com
96
+ expires: 2100-01-01
@@ -0,0 +1,72 @@
1
+ # Any copyright is dedicated to the Public Domain.
2
+ # https://creativecommons.org/publicdomain/zero/1.0/
3
+
4
+ ---
5
+ $schema: moz://mozilla.org/schemas/glean/metrics/2-0-0
6
+
7
+ no_lint: [EXPIRATION_DATE_TOO_FAR]
8
+
9
+ test.dual_labeled:
10
+ missing_key:
11
+ type: dual_labeled_counter
12
+ description: >
13
+ A dual labeled counter with missing `key` property of `dual_labels`
14
+ dual_labels:
15
+ category:
16
+ description: >
17
+ The category for the dual labeled counter
18
+ labels:
19
+ - category1
20
+ - category2
21
+ bugs:
22
+ - https://bugzilla.mozilla.org/11137353
23
+ data_reviews:
24
+ - http://example.com/reviews
25
+ notification_emails:
26
+ - CHANGE-ME@example.com
27
+ expires: 2100-01-01
28
+
29
+ missing_category:
30
+ type: dual_labeled_counter
31
+ description: >
32
+ A dual labeled counter with missing `category` property of `dual_labels`
33
+ dual_labels:
34
+ key:
35
+ description: >
36
+ The key for the dual labeled counter
37
+ labels:
38
+ - key1
39
+ - key2
40
+ bugs:
41
+ - https://bugzilla.mozilla.org/11137353
42
+ data_reviews:
43
+ - http://example.com/reviews
44
+ notification_emails:
45
+ - CHANGE-ME@example.com
46
+ expires: 2100-01-01
47
+
48
+ missing_dual_labels:
49
+ type: dual_labeled_counter
50
+ description: >
51
+ A dual labeled counter with missing `dual_labels` property
52
+ bugs:
53
+ - https://bugzilla.mozilla.org/11137353
54
+ data_reviews:
55
+ - http://example.com/reviews
56
+ notification_emails:
57
+ - CHANGE-ME@example.com
58
+ expires: 2100-01-01
59
+
60
+ missing_key_and_category:
61
+ type: dual_labeled_counter
62
+ description: >
63
+ A dual labeled counter with missing both `key` and `category` properties
64
+ dual_labels:
65
+ foo: bar
66
+ bugs:
67
+ - https://bugzilla.mozilla.org/11137353
68
+ data_reviews:
69
+ - http://example.com/reviews
70
+ notification_emails:
71
+ - CHANGE-ME@example.com
72
+ expires: 2100-01-01