athena-kit 1.0.0__py3-none-any.whl

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 (164) hide show
  1. athena_kit/__init__.py +5 -0
  2. athena_kit/_import_utils.py +41 -0
  3. athena_kit/bosun/__init__.py +32 -0
  4. athena_kit/bosun/ast/__init__.py +71 -0
  5. athena_kit/bosun/ast/nodes.py +115 -0
  6. athena_kit/bosun/ast/queries.py +130 -0
  7. athena_kit/bosun/opentsdb/__init__.py +95 -0
  8. athena_kit/bosun/opentsdb/enums.py +38 -0
  9. athena_kit/bosun/opentsdb/exceptions.py +2 -0
  10. athena_kit/bosun/opentsdb/intervals.py +45 -0
  11. athena_kit/bosun/opentsdb/models.py +205 -0
  12. athena_kit/bosun/opentsdb/parser.py +303 -0
  13. athena_kit/bosun/opentsdb/serializer.py +87 -0
  14. athena_kit/bosun/parser/__init__.py +43 -0
  15. athena_kit/bosun/parser/exceptions.py +7 -0
  16. athena_kit/bosun/parser/lexer.py +237 -0
  17. athena_kit/bosun/parser/parselets.py +173 -0
  18. athena_kit/bosun/parser/parser.py +46 -0
  19. athena_kit/bosun/parser/preprocess.py +399 -0
  20. athena_kit/bosun/parser/tokens.py +108 -0
  21. athena_kit/core/__init__.py +211 -0
  22. athena_kit/core/models/__init__.py +30 -0
  23. athena_kit/core/models/base.py +11 -0
  24. athena_kit/core/models/enums.py +114 -0
  25. athena_kit/core/tabular/__init__.py +55 -0
  26. athena_kit/core/tabular/backend.py +120 -0
  27. athena_kit/core/tabular/cell.py +22 -0
  28. athena_kit/core/tabular/pandas.py +139 -0
  29. athena_kit/core/tabular/repository.py +134 -0
  30. athena_kit/core/tabular/row.py +90 -0
  31. athena_kit/core/tabular/serialization.py +204 -0
  32. athena_kit/core/tabular/source.py +26 -0
  33. athena_kit/core/temporal/__init__.py +78 -0
  34. athena_kit/core/temporal/codec/__init__.py +90 -0
  35. athena_kit/core/temporal/codec/date.py +179 -0
  36. athena_kit/core/temporal/codec/datetime.py +204 -0
  37. athena_kit/core/temporal/codec/options.py +95 -0
  38. athena_kit/core/temporal/codec/temporal.py +141 -0
  39. athena_kit/core/temporal/codec/time.py +140 -0
  40. athena_kit/core/temporal/normalize.py +53 -0
  41. athena_kit/core/temporal/predicates.py +14 -0
  42. athena_kit/core/temporal/timezone.py +87 -0
  43. athena_kit/core/temporal/types.py +64 -0
  44. athena_kit/core/values/__init__.py +53 -0
  45. athena_kit/core/values/fallbacks.py +60 -0
  46. athena_kit/core/values/optional.py +81 -0
  47. athena_kit/core/values/predicates.py +0 -0
  48. athena_kit/http/__init__.py +76 -0
  49. athena_kit/http/aclient.py +38 -0
  50. athena_kit/http/client.py +38 -0
  51. athena_kit/http/hooks/__init__.py +79 -0
  52. athena_kit/http/hooks/event_hooks.py +105 -0
  53. athena_kit/http/hooks/logging.py +106 -0
  54. athena_kit/http/hooks/request_id.py +48 -0
  55. athena_kit/http/hooks/response_status.py +58 -0
  56. athena_kit/http/hooks/types.py +19 -0
  57. athena_kit/http/response_json.py +257 -0
  58. athena_kit/http/retrying.py +169 -0
  59. athena_kit/lark/__init__.py +28 -0
  60. athena_kit/lark/aclient.py +65 -0
  61. athena_kit/lark/auth.py +91 -0
  62. athena_kit/lark/sheets/__init__.py +30 -0
  63. athena_kit/lark/sheets/a1_notation.py +66 -0
  64. athena_kit/lark/sheets/aclient.py +343 -0
  65. athena_kit/lark/sheets/backend.py +64 -0
  66. athena_kit/lark/sheets/requests.py +63 -0
  67. athena_kit/matplotlib/__init__.py +2 -0
  68. athena_kit/matplotlib/adapters/__init__.py +35 -0
  69. athena_kit/matplotlib/adapters/styles.py +129 -0
  70. athena_kit/matplotlib/datas/__init__.py +34 -0
  71. athena_kit/matplotlib/datas/categorical.py +56 -0
  72. athena_kit/matplotlib/datas/xy.py +63 -0
  73. athena_kit/matplotlib/decorations/__init__.py +39 -0
  74. athena_kit/matplotlib/decorations/axis.py +34 -0
  75. athena_kit/matplotlib/decorations/cartesian.py +126 -0
  76. athena_kit/matplotlib/decorations/chart.py +26 -0
  77. athena_kit/matplotlib/decorations/grid.py +24 -0
  78. athena_kit/matplotlib/decorations/tick.py +58 -0
  79. athena_kit/matplotlib/options/__init__.py +38 -0
  80. athena_kit/matplotlib/options/_base.py +11 -0
  81. athena_kit/matplotlib/options/chart.py +38 -0
  82. athena_kit/matplotlib/options/coords/__init__.py +47 -0
  83. athena_kit/matplotlib/options/coords/axis.py +95 -0
  84. athena_kit/matplotlib/options/coords/cartesian.py +48 -0
  85. athena_kit/matplotlib/options/coords/grid.py +52 -0
  86. athena_kit/matplotlib/options/coords/legend.py +37 -0
  87. athena_kit/matplotlib/options/coords/tick.py +104 -0
  88. athena_kit/matplotlib/options/figure.py +28 -0
  89. athena_kit/matplotlib/options/plots/__init__.py +62 -0
  90. athena_kit/matplotlib/options/plots/bar.py +105 -0
  91. athena_kit/matplotlib/options/plots/data_label.py +89 -0
  92. athena_kit/matplotlib/options/plots/line.py +42 -0
  93. athena_kit/matplotlib/options/plots/marker.py +50 -0
  94. athena_kit/matplotlib/options/renderfig.py +57 -0
  95. athena_kit/matplotlib/options/rules/__init__.py +42 -0
  96. athena_kit/matplotlib/options/rules/conditions.py +73 -0
  97. athena_kit/matplotlib/options/rules/data_context.py +43 -0
  98. athena_kit/matplotlib/options/rules/matches.py +53 -0
  99. athena_kit/matplotlib/options/savefig.py +80 -0
  100. athena_kit/matplotlib/rendering/__init__.py +30 -0
  101. athena_kit/matplotlib/rendering/chart.py +21 -0
  102. athena_kit/matplotlib/rendering/color_cycle.py +17 -0
  103. athena_kit/matplotlib/rendering/coords/__init__.py +25 -0
  104. athena_kit/matplotlib/rendering/coords/_axes_runtime.py +80 -0
  105. athena_kit/matplotlib/rendering/coords/_render_plan.py +122 -0
  106. athena_kit/matplotlib/rendering/coords/cartesian.py +59 -0
  107. athena_kit/matplotlib/rendering/coords/legend.py +39 -0
  108. athena_kit/matplotlib/rendering/coords/tick.py +119 -0
  109. athena_kit/matplotlib/rendering/plots/__init__.py +25 -0
  110. athena_kit/matplotlib/rendering/plots/bar_plot.py +169 -0
  111. athena_kit/matplotlib/rendering/plots/layers/__init__.py +33 -0
  112. athena_kit/matplotlib/rendering/plots/layers/bar_layer.py +56 -0
  113. athena_kit/matplotlib/rendering/plots/layers/datalabel_layer.py +120 -0
  114. athena_kit/matplotlib/rendering/plots/layers/line_layer.py +52 -0
  115. athena_kit/matplotlib/rendering/plots/layers/marker_layer.py +119 -0
  116. athena_kit/matplotlib/rendering/plots/line_plot.py +63 -0
  117. athena_kit/matplotlib/runtime/__init__.py +64 -0
  118. athena_kit/matplotlib/runtime/artifact.py +62 -0
  119. athena_kit/matplotlib/runtime/context.py +220 -0
  120. athena_kit/matplotlib/runtime/pipeline.py +40 -0
  121. athena_kit/matplotlib/runtime/renderer.py +107 -0
  122. athena_kit/matplotlib/runtime/writers.py +99 -0
  123. athena_kit/matplotlib/specs/__init__.py +34 -0
  124. athena_kit/matplotlib/specs/_base.py +12 -0
  125. athena_kit/matplotlib/specs/chart.py +51 -0
  126. athena_kit/matplotlib/specs/coords/__init__.py +49 -0
  127. athena_kit/matplotlib/specs/coords/base.py +27 -0
  128. athena_kit/matplotlib/specs/coords/cartesian.py +121 -0
  129. athena_kit/matplotlib/specs/coords/polar.py +62 -0
  130. athena_kit/matplotlib/specs/coords/tick.py +94 -0
  131. athena_kit/matplotlib/specs/coords/unions.py +44 -0
  132. athena_kit/matplotlib/specs/figure.py +104 -0
  133. athena_kit/matplotlib/specs/plots/__init__.py +41 -0
  134. athena_kit/matplotlib/specs/plots/bar.py +53 -0
  135. athena_kit/matplotlib/specs/plots/base.py +16 -0
  136. athena_kit/matplotlib/specs/plots/line.py +51 -0
  137. athena_kit/matplotlib/specs/plots/pie.py +27 -0
  138. athena_kit/matplotlib/specs/plots/unions.py +31 -0
  139. athena_kit/matplotlib/styles/__init__.py +57 -0
  140. athena_kit/matplotlib/styles/base.py +23 -0
  141. athena_kit/matplotlib/styles/chart.py +28 -0
  142. athena_kit/matplotlib/styles/coord.py +31 -0
  143. athena_kit/matplotlib/styles/figure.py +34 -0
  144. athena_kit/matplotlib/styles/grid.py +38 -0
  145. athena_kit/matplotlib/styles/legend.py +75 -0
  146. athena_kit/matplotlib/styles/plot.py +31 -0
  147. athena_kit/matplotlib/styles/presets/__init__.py +30 -0
  148. athena_kit/matplotlib/styles/presets/fonts.py +15 -0
  149. athena_kit/matplotlib/styles/presets/palettes.py +16 -0
  150. athena_kit/matplotlib/styles/theme.py +58 -0
  151. athena_kit/matplotlib/styles/tick.py +69 -0
  152. athena_kit/matplotlib/transforms/__init__.py +0 -0
  153. athena_kit/matplotlib/transforms/alignment_data.py +120 -0
  154. athena_kit/matplotlib/transforms/normalize_data.py +131 -0
  155. athena_kit/matplotlib/transforms/tick_labels.py +101 -0
  156. athena_kit/matplotlib/types/__init__.py +92 -0
  157. athena_kit/matplotlib/types/options.py +37 -0
  158. athena_kit/matplotlib/types/specs.py +124 -0
  159. athena_kit/matplotlib/types/styles.py +201 -0
  160. athena_kit/py.typed +1 -0
  161. athena_kit-1.0.0.dist-info/METADATA +245 -0
  162. athena_kit-1.0.0.dist-info/RECORD +164 -0
  163. athena_kit-1.0.0.dist-info/WHEEL +4 -0
  164. athena_kit-1.0.0.dist-info/licenses/LICENSE +21 -0
athena_kit/__init__.py ADDED
@@ -0,0 +1,5 @@
1
+ """Athena Kit public namespace."""
2
+
3
+ __all__ = ("__version__",)
4
+
5
+ __version__ = "1.0.0"
@@ -0,0 +1,41 @@
1
+ from importlib import import_module
2
+
3
+
4
+ def import_attr(
5
+ attr_name: str,
6
+ module_name: str | None,
7
+ package: str | None,
8
+ ) -> object:
9
+ """Import an attribute from a module located in a package.
10
+
11
+ This utility function is used in custom `__getattr__` methods within `__init__.py`
12
+ files to dynamically import attributes.
13
+
14
+ Args:
15
+ attr_name: The name of the attribute to import.
16
+ module_name: The name of the module to import from.
17
+
18
+ If `None`, the attribute is imported from the package itself.
19
+ package: The name of the package where the module is located.
20
+
21
+ Returns:
22
+ The imported attribute.
23
+
24
+ Raises:
25
+ ImportError: If the module cannot be found.
26
+ AttributeError: If the attribute does not exist in the module or package.
27
+ """
28
+ if module_name == "__module__" or module_name is None:
29
+ try:
30
+ result = import_module(f".{attr_name}", package=package)
31
+ except ModuleNotFoundError:
32
+ msg = f"module '{package!r}' has no attribute {attr_name!r}"
33
+ raise AttributeError(msg) from None
34
+ else:
35
+ try:
36
+ module = import_module(f".{module_name}", package=package)
37
+ except ModuleNotFoundError as err:
38
+ msg = f"module '{package!r}.{module_name!r}' not found ({err})"
39
+ raise ImportError(msg) from None
40
+ result = getattr(module, attr_name)
41
+ return result
@@ -0,0 +1,32 @@
1
+ from typing import TYPE_CHECKING
2
+
3
+ from athena_kit._import_utils import import_attr
4
+
5
+ if TYPE_CHECKING:
6
+ from athena_kit.bosun.parser import Lexer, Parser
7
+ from athena_kit.bosun.parser.preprocess import preprocess
8
+
9
+ __all__ = (
10
+ "Lexer",
11
+ "Parser",
12
+ "preprocess",
13
+ )
14
+
15
+ _dynamic_imports = {
16
+ "Lexer": "parser",
17
+ "Parser": "parser",
18
+ "preprocess": "parser.preprocess",
19
+ }
20
+
21
+
22
+ def __getattr__(attr_name: str) -> object:
23
+ module_name = _dynamic_imports.get(attr_name)
24
+ if module_name is None:
25
+ raise AttributeError(f"module {__name__!r} has no attribute {attr_name!r}")
26
+ result = import_attr(attr_name, module_name, __spec__.parent)
27
+ globals()[attr_name] = result
28
+ return result
29
+
30
+
31
+ def __dir__() -> list[str]:
32
+ return list(__all__)
@@ -0,0 +1,71 @@
1
+ from typing import TYPE_CHECKING
2
+
3
+ from athena_kit._import_utils import import_attr
4
+
5
+ if TYPE_CHECKING:
6
+ from athena_kit.bosun.ast.nodes import (
7
+ BinaryOperatorExpression,
8
+ CallExpression,
9
+ Expression,
10
+ FloatLiteralExpression,
11
+ IntLiteralExpression,
12
+ LiteralExpression,
13
+ LiteralValue,
14
+ NameExpression,
15
+ Program,
16
+ StrLiteralExpression,
17
+ UnaryOperatorExpression,
18
+ )
19
+ from athena_kit.bosun.ast.queries import (
20
+ extract_all_queries,
21
+ extract_query,
22
+ render_calc_formula,
23
+ render_expression_formula,
24
+ )
25
+
26
+ __all__ = (
27
+ "BinaryOperatorExpression",
28
+ "CallExpression",
29
+ "Expression",
30
+ "FloatLiteralExpression",
31
+ "IntLiteralExpression",
32
+ "LiteralExpression",
33
+ "LiteralValue",
34
+ "NameExpression",
35
+ "Program",
36
+ "StrLiteralExpression",
37
+ "UnaryOperatorExpression",
38
+ "extract_all_queries",
39
+ "extract_query",
40
+ "render_calc_formula",
41
+ "render_expression_formula",
42
+ )
43
+
44
+ _dynamic_imports = {
45
+ "BinaryOperatorExpression": "nodes",
46
+ "CallExpression": "nodes",
47
+ "Expression": "nodes",
48
+ "FloatLiteralExpression": "nodes",
49
+ "IntLiteralExpression": "nodes",
50
+ "LiteralExpression": "nodes",
51
+ "LiteralValue": "nodes",
52
+ "NameExpression": "nodes",
53
+ "Program": "nodes",
54
+ "StrLiteralExpression": "nodes",
55
+ "UnaryOperatorExpression": "nodes",
56
+ "extract_all_queries": "queries",
57
+ "extract_query": "queries",
58
+ "render_calc_formula": "queries",
59
+ "render_expression_formula": "queries",
60
+ }
61
+
62
+
63
+ def __getattr__(attr_name: str) -> object:
64
+ module_name = _dynamic_imports.get(attr_name)
65
+ result = import_attr(attr_name, module_name, __spec__.parent)
66
+ globals()[attr_name] = result
67
+ return result
68
+
69
+
70
+ def __dir__() -> list[str]:
71
+ return list(__all__)
@@ -0,0 +1,115 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+
5
+ from athena_kit.bosun.parser.tokens import Token
6
+
7
+ type LiteralValue = int | float | str
8
+
9
+
10
+ @dataclass(frozen=True, slots=True)
11
+ class Program:
12
+ """Bosun 表达式 AST 根节点。
13
+
14
+ 不承载 query 提取、公式生成等业务逻辑,需要分析 AST 时,请使用 `athena_kit.bosun.ast.queries` 中的函数。
15
+ """
16
+
17
+ expression: Expression
18
+
19
+
20
+ class Expression:
21
+ """Bosun 表达式 AST 节点基类。
22
+
23
+ 该基类用于统一表达式节点类型,方便 parser 和后续分析函数做类型标注与模式匹配。
24
+ 它不定义行为,具体语义由子类表达。
25
+ """
26
+
27
+
28
+ @dataclass(frozen=True, slots=True)
29
+ class NameExpression(Expression):
30
+ """名称表达式。
31
+
32
+ 当前主要用于表示函数调用左侧的函数名,例如 `q`、`avg`、`nv`。
33
+ """
34
+
35
+ name: str
36
+
37
+ def __str__(self) -> str:
38
+ return self.name
39
+
40
+
41
+ @dataclass(frozen=True, slots=True)
42
+ class LiteralExpression(Expression):
43
+ """字面量表达式基类。
44
+
45
+ 保存 parser 从 token 中解析出的 Python 字面量值,具体子类用于区分整数、浮点数和字符串。
46
+ """
47
+
48
+ literal: LiteralValue
49
+
50
+ def __str__(self) -> str:
51
+ return str(self.literal)
52
+
53
+
54
+ class IntLiteralExpression(LiteralExpression): ...
55
+
56
+
57
+ class FloatLiteralExpression(LiteralExpression): ...
58
+
59
+
60
+ class StrLiteralExpression(LiteralExpression):
61
+ """字符串字面量表达式。
62
+
63
+ `literal` 保存去掉外层双引号后的字符串内容,`__str__` 会重新补回双引号,用于表达式调试输出。
64
+ """
65
+
66
+ literal: str
67
+
68
+ def __str__(self) -> str:
69
+ return f'"{self.literal}"'
70
+
71
+
72
+ @dataclass(frozen=True, slots=True)
73
+ class UnaryOperatorExpression(Expression):
74
+ """一元运算表达式,表示 `+x`、`-x`、`!x` 这类前缀运算。
75
+
76
+ - `operator` 保留原始运算符 token。
77
+ - `right` 保存一元运算的右操作数。
78
+ """
79
+
80
+ operator: Token
81
+ right: Expression
82
+
83
+ def __str__(self) -> str:
84
+ return f"({self.operator.text}{self.right})"
85
+
86
+
87
+ @dataclass(frozen=True, slots=True)
88
+ class BinaryOperatorExpression(Expression):
89
+ """二元运算表达式,表示算术、比较和逻辑二元运算。
90
+
91
+ - `operator` 保留原始运算符 token。
92
+ - `left` 与 `right` 保存左右操作数。
93
+ """
94
+
95
+ left: Expression
96
+ operator: Token
97
+ right: Expression
98
+
99
+ def __str__(self) -> str:
100
+ return f"({self.left} {self.operator.text} {self.right})"
101
+
102
+
103
+ @dataclass(frozen=True, slots=True)
104
+ class CallExpression(Expression):
105
+ """函数调用表达式,表示 `func(arg1, arg2, ...)`。
106
+
107
+ - `function` 保存函数名。
108
+ - `args` 保存按原始顺序解析出的参数表达式列表。
109
+ """
110
+
111
+ function: str
112
+ args: list[Expression]
113
+
114
+ def __str__(self) -> str:
115
+ return f"{self.function}({','.join(str(arg) for arg in self.args)})"
@@ -0,0 +1,130 @@
1
+ from athena_kit.bosun.ast.nodes import (
2
+ BinaryOperatorExpression,
3
+ CallExpression,
4
+ Expression,
5
+ LiteralExpression,
6
+ NameExpression,
7
+ Program,
8
+ StrLiteralExpression,
9
+ UnaryOperatorExpression,
10
+ )
11
+ from athena_kit.bosun.opentsdb import Query, parse_query
12
+
13
+ _QUERY_WRAPPER_FUNCTIONS = frozenset({
14
+ "shift",
15
+ "sum",
16
+ "avg",
17
+ "max",
18
+ "min",
19
+ "fv",
20
+ "nv",
21
+ "nanas",
22
+ })
23
+
24
+
25
+ def extract_query(expr: Expression, queries: list[Query]) -> None:
26
+ """从表达式树中收集所有 `q(...)` 调用中的 OpenTSDB `Query`。
27
+
28
+ 该函数会递归遍历表达式树,并把每个 `q(query_string, start, end)`
29
+ 的第一个字符串参数解析为 `Query` 后追加到 `queries`。
30
+ `sum`、`avg`、`shift`、`nv` 等包装函数会继续向第一个参数内部递归,暂不支持的函数会抛出 `NotImplementedError`。
31
+ """
32
+ match expr:
33
+ case NameExpression() | LiteralExpression():
34
+ return
35
+
36
+ case UnaryOperatorExpression(right=right):
37
+ extract_query(right, queries)
38
+
39
+ case BinaryOperatorExpression(left=left, right=right):
40
+ extract_query(left, queries)
41
+ extract_query(right, queries)
42
+
43
+ case CallExpression(function="q", args=args):
44
+ queries.append(parse_query(_require_string_literal(args, "q")))
45
+ case CallExpression(function=function, args=args) if function in _QUERY_WRAPPER_FUNCTIONS:
46
+ extract_query(args[0], queries)
47
+ case CallExpression(function=function):
48
+ raise NotImplementedError(f"[Extract Expr Queries] Not implemented call function: {function}.")
49
+
50
+ case _:
51
+ raise NotImplementedError(f"[Extract Expr Queries] Not implemented expression: {expr}.")
52
+
53
+
54
+ def extract_all_queries(program: Program) -> list[Query]:
55
+ """提取程序中出现的 OpenTSDB `Query`,并按首次出现顺序去重。
56
+
57
+ `Query` 的相等性由 `athena_kit.bosun.opentsdb.models.Query.__eq__` 决定,同一个查询即使出现在多个位置,
58
+ 也只会保留第一次出现的实例。返回顺序用于后续生成 `$kpi0`、`$kpi1` 等稳定命名。
59
+ """
60
+ queries: list[Query] = []
61
+ extract_query(program.expression, queries)
62
+
63
+ distinct_queries: list[Query] = []
64
+ for query in queries:
65
+ if query not in distinct_queries:
66
+ distinct_queries.append(query)
67
+
68
+ return distinct_queries
69
+
70
+
71
+ def render_expression_formula(expr: Expression, named_queries: dict[str, Query]) -> str:
72
+ """将表达式树中的 `q(...)` 调用替换为命名查询变量。
73
+
74
+ - `named_queries` 是变量名到 `Query` 的映射,例如 `{"$kpi0": query}`。
75
+ - 遍历过程中遇到 `q(...)` 时,会解析其查询字符串并查找等价 `Query`,然后返回对应变量名。
76
+ - 普通运算表达式会保留括号结构,包装函数会继续向第一个参数内部递归。
77
+ """
78
+ match expr:
79
+ case NameExpression(name=name):
80
+ return name
81
+
82
+ case LiteralExpression(literal=literal):
83
+ return str(literal)
84
+
85
+ case UnaryOperatorExpression(operator=operator, right=right):
86
+ return f"({operator.text}{render_expression_formula(right, named_queries)})"
87
+
88
+ case BinaryOperatorExpression(left=left, operator=operator, right=right):
89
+ left_formula = render_expression_formula(left, named_queries)
90
+ right_formula = render_expression_formula(right, named_queries)
91
+ return f"({left_formula}{operator.text}{right_formula})"
92
+
93
+ case CallExpression(function="q", args=args):
94
+ query: Query = parse_query(_require_string_literal(args, "q"))
95
+ for name, named_query in named_queries.items():
96
+ if query == named_query:
97
+ return name
98
+ raise ValueError(f"Query is not registered in named_queries: {query}.")
99
+ case CallExpression(function=function, args=args) if function in _QUERY_WRAPPER_FUNCTIONS:
100
+ return render_expression_formula(args[0], named_queries)
101
+ case CallExpression(function=function):
102
+ raise NotImplementedError(f"[Extract Expr Queries Formula] Not implemented call function: {function}.")
103
+
104
+ case _:
105
+ raise NotImplementedError(f"[Extract Expr Queries Formula] Not implemented expression: {expr}.")
106
+
107
+
108
+ def render_calc_formula(program: Program) -> str:
109
+ """生成命名查询定义和替换后的计算公式。
110
+
111
+ 输出由两部分组成:
112
+
113
+ - 前半部分是 `$kpiN=query` 的查询定义列表。
114
+ - 后半部分是把原始表达式中的 `q(...)` 替换为 `$kpiN` 后的计算公式。
115
+
116
+ 两部分之间使用空行分隔,便于直接展示或写入 Bosun 风格文本。
117
+ """
118
+ named_queries = {f"$kpi{index}": query for index, query in enumerate(extract_all_queries(program))}
119
+ formula = render_expression_formula(program.expression, named_queries)
120
+
121
+ query_definitions = "\n\n".join(f"{name}={query}" for name, query in named_queries.items())
122
+
123
+ return f"{query_definitions}\n\n{formula}"
124
+
125
+
126
+ def _require_string_literal(args: list[Expression], function: str) -> str:
127
+ if not args or not isinstance(args[0], StrLiteralExpression):
128
+ raise ValueError(f"`{function}` first argument must be a string literal.")
129
+
130
+ return args[0].literal
@@ -0,0 +1,95 @@
1
+ from typing import TYPE_CHECKING
2
+
3
+ from athena_kit._import_utils import import_attr
4
+
5
+ if TYPE_CHECKING:
6
+ from athena_kit.bosun.opentsdb.enums import Aggregator, FilterType, MultiFunction
7
+ from athena_kit.bosun.opentsdb.exceptions import OpenTSDBParseError
8
+ from athena_kit.bosun.opentsdb.intervals import calc_interval_seconds
9
+ from athena_kit.bosun.opentsdb.models import (
10
+ Downsampling,
11
+ MultiField,
12
+ Query,
13
+ Rate,
14
+ TagKv,
15
+ TopK,
16
+ )
17
+ from athena_kit.bosun.opentsdb.parser import (
18
+ parse_downsampling,
19
+ parse_multifield,
20
+ parse_query,
21
+ parse_rate,
22
+ parse_tagkv,
23
+ parse_topk,
24
+ )
25
+ from athena_kit.bosun.opentsdb.serializer import (
26
+ serialize_downsampling,
27
+ serialize_multifield,
28
+ serialize_query,
29
+ serialize_rate,
30
+ serialize_tagkv,
31
+ serialize_topk,
32
+ )
33
+
34
+ __all__ = (
35
+ "Aggregator",
36
+ "FilterType",
37
+ "MultiFunction",
38
+ "OpenTSDBParseError",
39
+ "calc_interval_seconds",
40
+ "Downsampling",
41
+ "MultiField",
42
+ "Query",
43
+ "Rate",
44
+ "TagKv",
45
+ "TopK",
46
+ "parse_downsampling",
47
+ "parse_multifield",
48
+ "parse_query",
49
+ "parse_rate",
50
+ "parse_tagkv",
51
+ "parse_topk",
52
+ "serialize_downsampling",
53
+ "serialize_multifield",
54
+ "serialize_query",
55
+ "serialize_rate",
56
+ "serialize_tagkv",
57
+ "serialize_topk",
58
+ )
59
+
60
+ _dynamic_imports = {
61
+ "Aggregator": "enums",
62
+ "FilterType": "enums",
63
+ "MultiFunction": "enums",
64
+ "OpenTSDBParseError": "exceptions",
65
+ "calc_interval_seconds": "intervals",
66
+ "Downsampling": "models",
67
+ "MultiField": "models",
68
+ "Query": "models",
69
+ "Rate": "models",
70
+ "TagKv": "models",
71
+ "TopK": "models",
72
+ "parse_downsampling": "parser",
73
+ "parse_multifield": "parser",
74
+ "parse_query": "parser",
75
+ "parse_rate": "parser",
76
+ "parse_tagkv": "parser",
77
+ "parse_topk": "parser",
78
+ "serialize_downsampling": "serializer",
79
+ "serialize_multifield": "serializer",
80
+ "serialize_query": "serializer",
81
+ "serialize_rate": "serializer",
82
+ "serialize_tagkv": "serializer",
83
+ "serialize_topk": "serializer",
84
+ }
85
+
86
+
87
+ def __getattr__(attr_name: str) -> object:
88
+ module_name = _dynamic_imports.get(attr_name)
89
+ result = import_attr(attr_name, module_name, __spec__.parent)
90
+ globals()[attr_name] = result
91
+ return result
92
+
93
+
94
+ def __dir__() -> list[str]:
95
+ return list(__all__)
@@ -0,0 +1,38 @@
1
+ from enum import StrEnum
2
+
3
+
4
+ class Aggregator(StrEnum):
5
+ SUM = "sum"
6
+ AVG = "avg"
7
+ MIN = "min"
8
+ MAX = "max"
9
+ COUNT = "count"
10
+ P50 = "p50"
11
+ P90 = "p90"
12
+ P99 = "p99"
13
+ P999 = "p999"
14
+ P9999 = "p9999"
15
+ P99999 = "p99999"
16
+ DEV = "dev"
17
+ ZIMSUM = "zimsum"
18
+ MINMIN = "minmin"
19
+ MINMAX = "minmax"
20
+
21
+
22
+ class FilterType(StrEnum):
23
+ LITERAL_OR = "literal_or"
24
+ ILITERAL_OR = "iliteral_or"
25
+ NOT_LITERAL_OR = "not_literal_or"
26
+ NOT_ILITERAL_OR = "not_iliteral_or"
27
+ WILDCARD = "wildcard"
28
+ IWILDCARD = "iwildcard"
29
+ REGEXP = "regexp"
30
+
31
+
32
+ class MultiFunction(StrEnum):
33
+ HIST_HEATMAP = "hist_heatmap"
34
+ HIST_SUM = "hist_sum"
35
+ HIST_COUNT = "hist_count"
36
+ HIST_AVG = "hist_avg"
37
+ HIST_PERCENTILE = "hist_percentile"
38
+ WEIGHTED_AVG = "weighted_avg"
@@ -0,0 +1,2 @@
1
+ class OpenTSDBParseError(ValueError):
2
+ """OpenTSDB 查询字符串解析失败。"""
@@ -0,0 +1,45 @@
1
+ import re
2
+
3
+
4
+ def calc_interval_seconds(interval: str) -> float:
5
+ """计算 OpenTSDB 时间间隔字符串对应的秒数。
6
+
7
+ 支持由多个 `数字 + 单位` 片段连续组成的 interval,例如 `1m`、`1d12h30m`、`1s500ms`。
8
+ 年份和月份使用固定近似值计算:`y` 按 365 天计算,`n` 按 30 天计算,`ms` 会转换为小数秒。
9
+
10
+ 输入格式非法时抛出 `ValueError`。
11
+ """
12
+ # Time unit: y=years, n=months, w=weeks, d=days, h=hours, m=minutes, s=seconds, ms=milliseconds
13
+ if not re.fullmatch(r"(\d+(?:ms|[smhdwny]))+", interval):
14
+ raise ValueError(f"Illegal interval format: `{interval}`.")
15
+
16
+ duration_and_units = [du for du in re.split(r"(ms|y|n|w|d|h|m|s)", interval) if du != ""]
17
+ if len(duration_and_units) % 2 != 0:
18
+ raise ValueError(f"Illegal interval format: `{interval}`, duration and unit must appear in pairs.")
19
+
20
+ seconds = 0
21
+ for index, duration_str in enumerate(duration_and_units):
22
+ if index % 2 == 0:
23
+ duration = int(duration_str)
24
+ unit = duration_and_units[index + 1]
25
+ match unit:
26
+ case "y":
27
+ base = 365 * 24 * 3600
28
+ case "n":
29
+ base = 30 * 24 * 3600
30
+ case "w":
31
+ base = 7 * 24 * 3600
32
+ case "d":
33
+ base = 1 * 24 * 3600
34
+ case "h":
35
+ base = 1 * 3600
36
+ case "m":
37
+ base = 60
38
+ case "s":
39
+ base = 1
40
+ case "ms":
41
+ base = 0.001
42
+ case _:
43
+ raise NotImplementedError(f"Unsupported time unit: {unit}.")
44
+ seconds += duration * base
45
+ return seconds