promcraft 0.1.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.
promcraft/__init__.py ADDED
@@ -0,0 +1,251 @@
1
+ from promcraft.aggregation import (
2
+ AggregationOperator,
3
+ Grouping,
4
+ avg,
5
+ bottomk,
6
+ count,
7
+ count_values,
8
+ group,
9
+ limit_ratio,
10
+ limitk,
11
+ max,
12
+ min,
13
+ quantile,
14
+ stddev,
15
+ stdvar,
16
+ sum,
17
+ topk,
18
+ )
19
+ from promcraft.base import Query
20
+ from promcraft.operator import (
21
+ BinaryOprator,
22
+ Group,
23
+ Match,
24
+ add,
25
+ and_,
26
+ atan2,
27
+ div,
28
+ eq,
29
+ gt,
30
+ gte,
31
+ lt,
32
+ lte,
33
+ mod,
34
+ mul,
35
+ neq,
36
+ or_,
37
+ pow,
38
+ sub,
39
+ unless,
40
+ )
41
+ from promcraft.scalar import Duration, Float, Hex, Scalar
42
+ from promcraft.string import String
43
+ from promcraft.vector import InstantVector, Label, RangeVector
44
+
45
+ # isort: split
46
+ # promcraft.functions must be imported AFTER promcraft.scalar and promcraft.vector so that
47
+ # our 'scalar' and 'vector' function names overwrite the submodule bindings that Python
48
+ # injects automatically when a submodule is imported.
49
+ from promcraft.functions import (
50
+ Function,
51
+ abs,
52
+ absent,
53
+ absent_over_time,
54
+ acos,
55
+ acosh,
56
+ asin,
57
+ asinh,
58
+ atan,
59
+ atanh,
60
+ avg_over_time,
61
+ ceil,
62
+ changes,
63
+ clamp,
64
+ clamp_max,
65
+ clamp_min,
66
+ cos,
67
+ cosh,
68
+ count_over_time,
69
+ day_of_month,
70
+ day_of_week,
71
+ day_of_year,
72
+ days_in_month,
73
+ deg,
74
+ delta,
75
+ deriv,
76
+ double_exponential_smoothing,
77
+ exp,
78
+ floor,
79
+ histogram_avg,
80
+ histogram_count,
81
+ histogram_fraction,
82
+ histogram_quantile,
83
+ histogram_stddev,
84
+ histogram_stdvar,
85
+ histogram_sum,
86
+ hour,
87
+ idelta,
88
+ increase,
89
+ info,
90
+ irate,
91
+ label_join,
92
+ label_replace,
93
+ last_over_time,
94
+ ln,
95
+ log2,
96
+ log10,
97
+ max_over_time,
98
+ min_over_time,
99
+ minute,
100
+ month,
101
+ pi,
102
+ predict_linear,
103
+ present_over_time,
104
+ quantile_over_time,
105
+ rad,
106
+ rate,
107
+ resets,
108
+ round,
109
+ scalar,
110
+ sgn,
111
+ sin,
112
+ sinh,
113
+ sort,
114
+ sort_by_label,
115
+ sort_by_label_desc,
116
+ sort_desc,
117
+ sqrt,
118
+ stddev_over_time,
119
+ stdvar_over_time,
120
+ sum_over_time,
121
+ tan,
122
+ tanh,
123
+ time,
124
+ timestamp,
125
+ vector,
126
+ year,
127
+ )
128
+
129
+ __all__ = [
130
+ "AggregationOperator",
131
+ "BinaryOprator",
132
+ "Duration",
133
+ "Float",
134
+ "Function",
135
+ "Group",
136
+ "Grouping",
137
+ "Hex",
138
+ "InstantVector",
139
+ "Label",
140
+ "Match",
141
+ "Query",
142
+ "RangeVector",
143
+ "Scalar",
144
+ "String",
145
+ "abs",
146
+ "absent",
147
+ "absent_over_time",
148
+ "acos",
149
+ "acosh",
150
+ "add",
151
+ "and_",
152
+ "asin",
153
+ "asinh",
154
+ "atan",
155
+ "atan2",
156
+ "atanh",
157
+ "avg",
158
+ "avg_over_time",
159
+ "bottomk",
160
+ "ceil",
161
+ "changes",
162
+ "clamp",
163
+ "clamp_max",
164
+ "clamp_min",
165
+ "cos",
166
+ "cosh",
167
+ "count",
168
+ "count_over_time",
169
+ "count_values",
170
+ "day_of_month",
171
+ "day_of_week",
172
+ "day_of_year",
173
+ "days_in_month",
174
+ "deg",
175
+ "delta",
176
+ "deriv",
177
+ "div",
178
+ "double_exponential_smoothing",
179
+ "eq",
180
+ "exp",
181
+ "floor",
182
+ "group",
183
+ "gt",
184
+ "gte",
185
+ "histogram_avg",
186
+ "histogram_count",
187
+ "histogram_fraction",
188
+ "histogram_quantile",
189
+ "histogram_stddev",
190
+ "histogram_stdvar",
191
+ "histogram_sum",
192
+ "hour",
193
+ "idelta",
194
+ "increase",
195
+ "info",
196
+ "irate",
197
+ "label_join",
198
+ "label_replace",
199
+ "last_over_time",
200
+ "limit_ratio",
201
+ "limitk",
202
+ "ln",
203
+ "log2",
204
+ "log10",
205
+ "lt",
206
+ "lte",
207
+ "max",
208
+ "max_over_time",
209
+ "min",
210
+ "min_over_time",
211
+ "minute",
212
+ "mod",
213
+ "month",
214
+ "mul",
215
+ "neq",
216
+ "or_",
217
+ "pi",
218
+ "pow",
219
+ "predict_linear",
220
+ "present_over_time",
221
+ "quantile",
222
+ "quantile_over_time",
223
+ "rad",
224
+ "rate",
225
+ "resets",
226
+ "round",
227
+ "scalar",
228
+ "sgn",
229
+ "sin",
230
+ "sinh",
231
+ "sort",
232
+ "sort_by_label",
233
+ "sort_by_label_desc",
234
+ "sort_desc",
235
+ "sqrt",
236
+ "stddev",
237
+ "stddev_over_time",
238
+ "stdvar",
239
+ "stdvar_over_time",
240
+ "sub",
241
+ "sum",
242
+ "sum_over_time",
243
+ "tan",
244
+ "tanh",
245
+ "time",
246
+ "timestamp",
247
+ "topk",
248
+ "unless",
249
+ "vector",
250
+ "year",
251
+ ]
@@ -0,0 +1,225 @@
1
+ import enum
2
+ from typing import Literal
3
+
4
+ from promcraft.base import Query
5
+ from promcraft.scalar import Scalar
6
+ from promcraft.string import String
7
+
8
+
9
+ class Grouping:
10
+ """Aggregation grouping clause that controls which label dimensions appear in results.
11
+
12
+ - ``by(labels)`` — keep only the listed labels in the output.
13
+ - ``without(labels)`` — drop the listed labels; preserve all others.
14
+
15
+ Example::
16
+
17
+ Grouping.by(["job", "env"]) # → 'by(job, env)'
18
+ Grouping.without(["instance"]) # → 'without(instance)'
19
+ """
20
+
21
+ def __init__(self, type: Literal["by", "without"], labels: list[str]) -> None:
22
+ self.type = type
23
+ self.labels = labels
24
+
25
+ @classmethod
26
+ def by(cls, labels: list[str]) -> "Grouping":
27
+ """Return a ``by(labels)`` grouping clause."""
28
+ return cls("by", labels)
29
+
30
+ @classmethod
31
+ def without(cls, labels: list[str]) -> "Grouping":
32
+ """Return a ``without(labels)`` grouping clause."""
33
+ return cls("without", labels)
34
+
35
+ def __str__(self) -> str:
36
+ labels_str = ", ".join(self.labels)
37
+ return f"{self.type}({labels_str})"
38
+
39
+
40
+ class AggregationOperator(Query):
41
+ """A PromQL aggregation expression that collapses multiple time series into fewer results.
42
+
43
+ Aggregation operators reduce a set of series by computing a single
44
+ output value per group. An optional :class:`Grouping` clause
45
+ (``by`` / ``without``) controls which label dimensions define the groups.
46
+ Some operators (``topk``, ``bottomk``, ``count_values``, ``quantile``,
47
+ ``limitk``, ``limit_ratio``) require an additional scalar or string
48
+ ``parameter``.
49
+
50
+ The fluent :meth:`by` and :meth:`without` methods return new immutable
51
+ instances with the grouping applied.
52
+
53
+ Example::
54
+
55
+ AggregationOperator(AggregationOperator.Operator.SUM, v).by(["job"])
56
+ # → 'sum(v) by(job)'
57
+
58
+ AggregationOperator(AggregationOperator.Operator.TOPK, v, parameter=Float(5))
59
+ # → 'topk(5.0, v)'
60
+ """
61
+
62
+ class Operator(enum.Enum):
63
+ """Enum of all PromQL aggregation operators."""
64
+
65
+ SUM = "sum"
66
+ AVG = "avg"
67
+ MIN = "min"
68
+ MAX = "max"
69
+ COUNT = "count"
70
+ GROUP = "group"
71
+ STDDEV = "stddev"
72
+ STDVAR = "stdvar"
73
+ TOPK = "topk"
74
+ BOTTOMK = "bottomk"
75
+ COUNT_VALUES = "count_values"
76
+ QUANTILE = "quantile"
77
+ LIMITK = "limitk"
78
+ LIMIT_RATIO = "limit_ratio"
79
+
80
+ def __str__(self) -> str:
81
+ return self.value
82
+
83
+ def __init__(
84
+ self,
85
+ op: Operator,
86
+ vector: Query,
87
+ parameter: Scalar | String | None = None,
88
+ grouping: Grouping | None = None,
89
+ ) -> None:
90
+ self.op = op
91
+ self.vector = vector
92
+ self.parameter = parameter
93
+ self.grouping = grouping
94
+
95
+ def __str__(self) -> str:
96
+ grouping_str = f" {self.grouping}" if self.grouping else ""
97
+ if self.parameter is not None:
98
+ args = f"{self.parameter}, {self.vector}"
99
+ else:
100
+ args = str(self.vector)
101
+ return f"{self.op}({args}){grouping_str}"
102
+
103
+ def by(self, labels: list[str]) -> "AggregationOperator":
104
+ """Return a copy of this aggregation with a ``by(labels)`` grouping clause."""
105
+ return AggregationOperator(
106
+ op=self.op,
107
+ vector=self.vector,
108
+ parameter=self.parameter,
109
+ grouping=Grouping.by(labels),
110
+ )
111
+
112
+ def without(self, labels: list[str]) -> "AggregationOperator":
113
+ """Return a copy of this aggregation with a ``without(labels)`` grouping clause."""
114
+ return AggregationOperator(
115
+ op=self.op,
116
+ vector=self.vector,
117
+ parameter=self.parameter,
118
+ grouping=Grouping.without(labels),
119
+ )
120
+
121
+
122
+ def sum(vector: Query, grouping: Grouping | None = None) -> AggregationOperator:
123
+ """Sum of all sample values across the aggregated dimensions (``sum(v)``)."""
124
+ return AggregationOperator(AggregationOperator.Operator.SUM, vector, grouping=grouping)
125
+
126
+
127
+ def avg(vector: Query, grouping: Grouping | None = None) -> AggregationOperator:
128
+ """Arithmetic mean of sample values across the aggregated dimensions (``avg(v)``)."""
129
+ return AggregationOperator(AggregationOperator.Operator.AVG, vector, grouping=grouping)
130
+
131
+
132
+ def min(vector: Query, grouping: Grouping | None = None) -> AggregationOperator:
133
+ """Smallest sample value across the aggregated dimensions (``min(v)``)."""
134
+ return AggregationOperator(AggregationOperator.Operator.MIN, vector, grouping=grouping)
135
+
136
+
137
+ def max(vector: Query, grouping: Grouping | None = None) -> AggregationOperator:
138
+ """Largest sample value across the aggregated dimensions (``max(v)``)."""
139
+ return AggregationOperator(AggregationOperator.Operator.MAX, vector, grouping=grouping)
140
+
141
+
142
+ def count(vector: Query, grouping: Grouping | None = None) -> AggregationOperator:
143
+ """Number of time series in the aggregated dimensions (``count(v)``)."""
144
+ return AggregationOperator(AggregationOperator.Operator.COUNT, vector, grouping=grouping)
145
+
146
+
147
+ def group(vector: Query, grouping: Grouping | None = None) -> AggregationOperator:
148
+ """Return 1 for each group that contains at least one element (``group(v)``)."""
149
+ return AggregationOperator(AggregationOperator.Operator.GROUP, vector, grouping=grouping)
150
+
151
+
152
+ def stddev(vector: Query, grouping: Grouping | None = None) -> AggregationOperator:
153
+ """Population standard deviation of sample values across the aggregated dimensions.
154
+
155
+ PromQL: ``stddev(v)``
156
+ """
157
+ return AggregationOperator(AggregationOperator.Operator.STDDEV, vector, grouping=grouping)
158
+
159
+
160
+ def stdvar(vector: Query, grouping: Grouping | None = None) -> AggregationOperator:
161
+ """Population standard variance of sample values across the aggregated dimensions.
162
+
163
+ PromQL: ``stdvar(v)``
164
+ """
165
+ return AggregationOperator(AggregationOperator.Operator.STDVAR, vector, grouping=grouping)
166
+
167
+
168
+ def topk(
169
+ parameter: Scalar, vector: Query, grouping: Grouping | None = None
170
+ ) -> AggregationOperator:
171
+ """Largest ``k`` sample values across the aggregated dimensions (``topk(k, v)``)."""
172
+ return AggregationOperator(
173
+ AggregationOperator.Operator.TOPK, vector, parameter=parameter, grouping=grouping
174
+ )
175
+
176
+
177
+ def bottomk(
178
+ parameter: Scalar, vector: Query, grouping: Grouping | None = None
179
+ ) -> AggregationOperator:
180
+ """Smallest ``k`` sample values across the aggregated dimensions (``bottomk(k, v)``)."""
181
+ return AggregationOperator(
182
+ AggregationOperator.Operator.BOTTOMK, vector, parameter=parameter, grouping=grouping
183
+ )
184
+
185
+
186
+ def count_values(
187
+ parameter: String, vector: Query, grouping: Grouping | None = None
188
+ ) -> AggregationOperator:
189
+ """Count the number of series per unique sample value, writing counts to ``parameter`` label.
190
+
191
+ PromQL: ``count_values(label, v)``
192
+ """
193
+ return AggregationOperator(
194
+ AggregationOperator.Operator.COUNT_VALUES, vector, parameter=parameter, grouping=grouping
195
+ )
196
+
197
+
198
+ def quantile(
199
+ parameter: Scalar, vector: Query, grouping: Grouping | None = None
200
+ ) -> AggregationOperator:
201
+ """φ-quantile (0 ≤ φ ≤ 1) of sample values across the aggregated dimensions.
202
+
203
+ PromQL: ``quantile(φ, v)``
204
+ """
205
+ return AggregationOperator(
206
+ AggregationOperator.Operator.QUANTILE, vector, parameter=parameter, grouping=grouping
207
+ )
208
+
209
+
210
+ def limitk(
211
+ parameter: Scalar, vector: Query, grouping: Grouping | None = None
212
+ ) -> AggregationOperator:
213
+ """Pseudo-randomly sample at most ``k`` series from the input (``limitk(k, v)``)."""
214
+ return AggregationOperator(
215
+ AggregationOperator.Operator.LIMITK, vector, parameter=parameter, grouping=grouping
216
+ )
217
+
218
+
219
+ def limit_ratio(
220
+ parameter: Scalar, vector: Query, grouping: Grouping | None = None
221
+ ) -> AggregationOperator:
222
+ """Pseudo-randomly sample fraction ``r`` of series from the input (``limit_ratio(r, v)``)."""
223
+ return AggregationOperator(
224
+ AggregationOperator.Operator.LIMIT_RATIO, vector, parameter=parameter, grouping=grouping
225
+ )
promcraft/base.py ADDED
@@ -0,0 +1,90 @@
1
+ from abc import ABC, abstractmethod
2
+ from typing import TYPE_CHECKING
3
+
4
+ if TYPE_CHECKING:
5
+ from promcraft.operator import BinaryOprator
6
+
7
+
8
+ class Query(ABC):
9
+ @abstractmethod
10
+ def __str__(self) -> str:
11
+ raise NotImplementedError(
12
+ "Subclasses must implement __str__ method",
13
+ )
14
+
15
+ def __add__(self, other: "Query") -> "BinaryOprator":
16
+ from promcraft.operator import add
17
+
18
+ return add(self, other)
19
+
20
+ def __sub__(self, other: "Query") -> "BinaryOprator":
21
+ from promcraft.operator import sub
22
+
23
+ return sub(self, other)
24
+
25
+ def __mul__(self, other: "Query") -> "BinaryOprator":
26
+ from promcraft.operator import mul
27
+
28
+ return mul(self, other)
29
+
30
+ def __truediv__(self, other: "Query") -> "BinaryOprator":
31
+ from promcraft.operator import div
32
+
33
+ return div(self, other)
34
+
35
+ def __mod__(self, other: "Query") -> "BinaryOprator":
36
+ from promcraft.operator import mod
37
+
38
+ return mod(self, other)
39
+
40
+ def __pow__(self, other: "Query") -> "BinaryOprator":
41
+ from promcraft.operator import pow
42
+
43
+ return pow(self, other)
44
+
45
+ def __eq__(self, other: object) -> "BinaryOprator": # type: ignore[override]
46
+ from promcraft.operator import eq
47
+
48
+ if not isinstance(other, Query):
49
+ raise NotImplementedError("Cannot compare Query with non-Query object")
50
+ return eq(self, other)
51
+
52
+ def __hash__(self) -> int:
53
+ return id(self)
54
+
55
+ def __ne__(self, other: object) -> "BinaryOprator": # type: ignore[override]
56
+ from promcraft.operator import neq
57
+
58
+ if not isinstance(other, Query):
59
+ raise NotImplementedError("Cannot compare Query with non-Query object")
60
+ return neq(self, other)
61
+
62
+ def __lt__(self, other: "Query") -> "BinaryOprator":
63
+ from promcraft.operator import lt
64
+
65
+ return lt(self, other)
66
+
67
+ def __le__(self, other: "Query") -> "BinaryOprator":
68
+ from promcraft.operator import lte
69
+
70
+ return lte(self, other)
71
+
72
+ def __gt__(self, other: "Query") -> "BinaryOprator":
73
+ from promcraft.operator import gt
74
+
75
+ return gt(self, other)
76
+
77
+ def __ge__(self, other: "Query") -> "BinaryOprator":
78
+ from promcraft.operator import gte
79
+
80
+ return gte(self, other)
81
+
82
+ def __and__(self, other: "Query") -> "BinaryOprator":
83
+ from promcraft.operator import and_
84
+
85
+ return and_(self, other)
86
+
87
+ def __or__(self, other: "Query") -> "BinaryOprator":
88
+ from promcraft.operator import or_
89
+
90
+ return or_(self, other)