promcraft 0.1.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.
- promcraft-0.1.0/LICENSE +21 -0
- promcraft-0.1.0/PKG-INFO +670 -0
- promcraft-0.1.0/README.md +653 -0
- promcraft-0.1.0/pyproject.toml +66 -0
- promcraft-0.1.0/src/promcraft/__init__.py +251 -0
- promcraft-0.1.0/src/promcraft/aggregation.py +225 -0
- promcraft-0.1.0/src/promcraft/base.py +90 -0
- promcraft-0.1.0/src/promcraft/functions.py +521 -0
- promcraft-0.1.0/src/promcraft/operator.py +367 -0
- promcraft-0.1.0/src/promcraft/py.typed +0 -0
- promcraft-0.1.0/src/promcraft/scalar.py +88 -0
- promcraft-0.1.0/src/promcraft/string.py +37 -0
- promcraft-0.1.0/src/promcraft/vector.py +168 -0
promcraft-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Peter Babka
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
promcraft-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,670 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: promcraft
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary:
|
|
5
|
+
License-File: LICENSE
|
|
6
|
+
Author: Peter Babka
|
|
7
|
+
Author-email: 159peter951@gmail.com
|
|
8
|
+
Requires-Python: >=3.10,<4.0
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
|
|
17
|
+
# promcraft
|
|
18
|
+
|
|
19
|
+
[](https://pypi.org/project/promcraft/)
|
|
20
|
+
[](https://pypi.org/project/promcraft/)
|
|
21
|
+
[](https://github.com/xbabka01/promcraft/actions/workflows/python.yml)
|
|
22
|
+
|
|
23
|
+
A Python library for building [Prometheus QL](https://prometheus.io/docs/prometheus/latest/querying/basics/) queries programmatically. Instead of constructing raw query strings, use composable Python objects that serialize to valid PromQL syntax.
|
|
24
|
+
|
|
25
|
+
## Installation
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
pip install promcraft
|
|
29
|
+
# or with Poetry
|
|
30
|
+
poetry add promcraft
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Requires Python 3.10+.
|
|
34
|
+
|
|
35
|
+
## Quick Start
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
from promcraft import InstantVector, RangeVector, Label, String, Duration, BinaryOprator
|
|
39
|
+
|
|
40
|
+
# Simple metric selector
|
|
41
|
+
query = InstantVector("up", [])
|
|
42
|
+
str(query) # "up{}"
|
|
43
|
+
|
|
44
|
+
# With label filters
|
|
45
|
+
query = InstantVector("http_requests_total", [
|
|
46
|
+
Label.eq("job", String("api-server")),
|
|
47
|
+
Label.eq("env", String("production")),
|
|
48
|
+
])
|
|
49
|
+
str(query) # 'http_requests_total{job = "api-server", env = "production"}'
|
|
50
|
+
|
|
51
|
+
# Range vector with rate()
|
|
52
|
+
from promcraft import rate
|
|
53
|
+
query = rate(RangeVector("http_requests_total", [
|
|
54
|
+
Label.eq("job", String("api-server")),
|
|
55
|
+
], Duration(m=5)))
|
|
56
|
+
str(query) # 'rate(http_requests_total{job = "api-server"}[5m])'
|
|
57
|
+
|
|
58
|
+
# Binary operation
|
|
59
|
+
left = InstantVector("http_requests_total", [Label.eq("status", String("500"))])
|
|
60
|
+
right = InstantVector("http_requests_total", [])
|
|
61
|
+
ratio = left / right
|
|
62
|
+
str(ratio) # 'http_requests_total{status = "500"} / http_requests_total{}'
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## API Reference
|
|
66
|
+
|
|
67
|
+
### Scalars
|
|
68
|
+
|
|
69
|
+
#### `Float`
|
|
70
|
+
|
|
71
|
+
Wraps a Python float for use in queries.
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
from promcraft import Float
|
|
75
|
+
|
|
76
|
+
Float(3.14) # "3.14"
|
|
77
|
+
Float(0.0) # "0.0"
|
|
78
|
+
Float(-2.5) # "-2.5"
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
#### `Hex`
|
|
82
|
+
|
|
83
|
+
Represents a hexadecimal integer literal.
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
from promcraft import Hex
|
|
87
|
+
|
|
88
|
+
Hex(255) # "0xff"
|
|
89
|
+
Hex(0) # "0x0"
|
|
90
|
+
Hex(16) # "0x10"
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
#### `Duration`
|
|
94
|
+
|
|
95
|
+
Represents a Prometheus duration literal. All parameters are keyword-only.
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
from promcraft import Duration
|
|
99
|
+
|
|
100
|
+
Duration() # "0s" (default when no units given)
|
|
101
|
+
Duration(s=5) # "5s"
|
|
102
|
+
Duration(m=1, s=30) # "1m30s"
|
|
103
|
+
Duration(h=1, m=30) # "1h30m"
|
|
104
|
+
Duration(ms=500) # "500ms"
|
|
105
|
+
Duration(y=1, w=2, d=3, h=4, m=5, s=6, ms=7) # "1y2w3d4h5m6s7ms"
|
|
106
|
+
Duration(m=5, neg=True) # "-5m"
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
| Parameter | Unit |
|
|
110
|
+
|-----------|-------------|
|
|
111
|
+
| `y` | years |
|
|
112
|
+
| `w` | weeks |
|
|
113
|
+
| `d` | days |
|
|
114
|
+
| `h` | hours |
|
|
115
|
+
| `m` | minutes |
|
|
116
|
+
| `s` | seconds |
|
|
117
|
+
| `ms` | milliseconds|
|
|
118
|
+
| `neg` | negate the duration |
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
### `String`
|
|
123
|
+
|
|
124
|
+
Represents a PromQL string literal with configurable quoting.
|
|
125
|
+
|
|
126
|
+
```python
|
|
127
|
+
from promcraft import String
|
|
128
|
+
|
|
129
|
+
String("hello") # '"hello"' (double-quote default)
|
|
130
|
+
String("hello", '"') # '"hello"'
|
|
131
|
+
String("hello", "'") # "'hello'"
|
|
132
|
+
String("hello", "`") # "`hello`"
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Special characters are escaped automatically for `"` and `'` quotes. Backtick strings are not escaped.
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
### `Label`
|
|
140
|
+
|
|
141
|
+
Represents a label matcher used inside vector selectors. Use the factory methods for convenience.
|
|
142
|
+
|
|
143
|
+
```python
|
|
144
|
+
from promcraft import Label, String
|
|
145
|
+
|
|
146
|
+
Label.eq("job", String("prometheus")) # 'job = "prometheus"'
|
|
147
|
+
Label.neq("env", String("prod")) # 'env != "prod"'
|
|
148
|
+
Label.re("name", String("prom.*")) # 'name =~ "prom.*"'
|
|
149
|
+
Label.nre("name", String("test.*")) # 'name !~ "test.*"'
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
| Method | Operator | Meaning |
|
|
153
|
+
|---------------|----------|----------------------|
|
|
154
|
+
| `Label.eq` | `=` | Exact match |
|
|
155
|
+
| `Label.neq` | `!=` | Not equal |
|
|
156
|
+
| `Label.re` | `=~` | Regex match |
|
|
157
|
+
| `Label.nre` | `!~` | Regex does not match |
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
### `InstantVector`
|
|
162
|
+
|
|
163
|
+
Selects a set of time series at a single point in time.
|
|
164
|
+
|
|
165
|
+
```python
|
|
166
|
+
InstantVector(metric, labels, *, offset=None, at=None)
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
```python
|
|
170
|
+
from promcraft import InstantVector, Label, String, Duration, Float
|
|
171
|
+
|
|
172
|
+
# Basic
|
|
173
|
+
InstantVector("up", [])
|
|
174
|
+
# "up{}"
|
|
175
|
+
|
|
176
|
+
# With labels
|
|
177
|
+
InstantVector("http_requests_total", [
|
|
178
|
+
Label.eq("job", String("api")),
|
|
179
|
+
Label.re("status", String("5..")),
|
|
180
|
+
])
|
|
181
|
+
# 'http_requests_total{job = "api", status =~ "5.."}'
|
|
182
|
+
|
|
183
|
+
# With offset
|
|
184
|
+
InstantVector("up", [], offset=Duration(m=5))
|
|
185
|
+
# "up{} offset 5m"
|
|
186
|
+
|
|
187
|
+
# With @ modifier (Unix timestamp)
|
|
188
|
+
InstantVector("up", [], at=Float(1609746000.0))
|
|
189
|
+
# "up{} @ 1609746000.0"
|
|
190
|
+
|
|
191
|
+
# With @ modifier (range function references)
|
|
192
|
+
InstantVector("up", [], at="start()")
|
|
193
|
+
# "up{} @ start()"
|
|
194
|
+
|
|
195
|
+
InstantVector("up", [], at="end()")
|
|
196
|
+
# "up{} @ end()"
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
### `RangeVector`
|
|
202
|
+
|
|
203
|
+
Selects a range of samples over a time window. Required by functions like `rate()`, `increase()`, `avg_over_time()`.
|
|
204
|
+
|
|
205
|
+
```python
|
|
206
|
+
RangeVector(metric, labels, range, *, resolution=None, offset=None, at=None)
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
```python
|
|
210
|
+
from promcraft import RangeVector, Label, String, Duration
|
|
211
|
+
|
|
212
|
+
# Basic range
|
|
213
|
+
RangeVector("http_requests_total", [], Duration(m=5))
|
|
214
|
+
# "http_requests_total{}[5m]"
|
|
215
|
+
|
|
216
|
+
# With resolution (subquery step)
|
|
217
|
+
RangeVector("up", [], Duration(m=5), resolution=Duration(s=30))
|
|
218
|
+
# "up{}[5m :30s]"
|
|
219
|
+
|
|
220
|
+
# With offset
|
|
221
|
+
RangeVector("up", [], Duration(m=5), offset=Duration(m=1))
|
|
222
|
+
# "up{}[5m] offset 1m"
|
|
223
|
+
|
|
224
|
+
# Combined
|
|
225
|
+
RangeVector(
|
|
226
|
+
"http_requests_total",
|
|
227
|
+
[Label.eq("job", String("api"))],
|
|
228
|
+
Duration(m=5),
|
|
229
|
+
resolution=Duration(s=30),
|
|
230
|
+
offset=Duration(m=1),
|
|
231
|
+
at="end()",
|
|
232
|
+
)
|
|
233
|
+
# 'http_requests_total{job = "api"}[5m :30s] offset 1m @ end()'
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
---
|
|
237
|
+
|
|
238
|
+
### `BinaryOprator`
|
|
239
|
+
|
|
240
|
+
Combines two query expressions with a binary operator.
|
|
241
|
+
|
|
242
|
+
```python
|
|
243
|
+
BinaryOprator(op, left, right, *, match=None, group=None)
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
#### Operators
|
|
247
|
+
|
|
248
|
+
| Enum value | Symbol | Category |
|
|
249
|
+
|------------------------------|----------|----------------|
|
|
250
|
+
| `BinaryOprator.Operator.ADD` | `+` | Arithmetic |
|
|
251
|
+
| `BinaryOprator.Operator.SUB` | `-` | Arithmetic |
|
|
252
|
+
| `BinaryOprator.Operator.MUL` | `*` | Arithmetic |
|
|
253
|
+
| `BinaryOprator.Operator.DIV` | `/` | Arithmetic |
|
|
254
|
+
| `BinaryOprator.Operator.MOD` | `%` | Arithmetic |
|
|
255
|
+
| `BinaryOprator.Operator.POW` | `^` | Arithmetic |
|
|
256
|
+
| `BinaryOprator.Operator.EQ` | `==` | Comparison |
|
|
257
|
+
| `BinaryOprator.Operator.NEQ` | `!=` | Comparison |
|
|
258
|
+
| `BinaryOprator.Operator.LT` | `<` | Comparison |
|
|
259
|
+
| `BinaryOprator.Operator.LTE` | `<=` | Comparison |
|
|
260
|
+
| `BinaryOprator.Operator.GT` | `>` | Comparison |
|
|
261
|
+
| `BinaryOprator.Operator.GTE` | `>=` | Comparison |
|
|
262
|
+
| `BinaryOprator.Operator.AND` | `and` | Logical/Set |
|
|
263
|
+
| `BinaryOprator.Operator.OR` | `or` | Logical/Set |
|
|
264
|
+
| `BinaryOprator.Operator.UNLESS` | `unless` | Logical/Set |
|
|
265
|
+
| `BinaryOprator.Operator.ATAN2` | `atan2` | Trigonometric |
|
|
266
|
+
|
|
267
|
+
#### Helper functions
|
|
268
|
+
|
|
269
|
+
Each operator has a module-level helper so you don't need to import the enum:
|
|
270
|
+
|
|
271
|
+
```python
|
|
272
|
+
from promcraft import (
|
|
273
|
+
add, sub, mul, div, mod, pow,
|
|
274
|
+
eq, neq, lt, lte, gt, gte,
|
|
275
|
+
and_, or_, unless, atan2,
|
|
276
|
+
Float, InstantVector,
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
v1 = InstantVector("requests", [])
|
|
280
|
+
v2 = InstantVector("errors", [])
|
|
281
|
+
|
|
282
|
+
add(Float(1.0), Float(2.0)) # "1.0 + 2.0"
|
|
283
|
+
div(v2, v1) # "errors{} / requests{}"
|
|
284
|
+
gt(v1, Float(0.0)) # "requests{} > 0.0"
|
|
285
|
+
and_(v1, v2) # "requests{} and errors{}"
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
All helpers accept the same optional `match` and `group` keyword arguments as the constructor:
|
|
289
|
+
|
|
290
|
+
```python
|
|
291
|
+
div(v2, v1, match=Match.on(["job"]), group=Group.left([]))
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
#### Basic usage
|
|
295
|
+
|
|
296
|
+
```python
|
|
297
|
+
from promcraft import BinaryOprator, Float
|
|
298
|
+
|
|
299
|
+
Op = BinaryOprator.Operator
|
|
300
|
+
|
|
301
|
+
BinaryOprator(Op.ADD, Float(1.0), Float(2.0)) # "1.0 + 2.0"
|
|
302
|
+
BinaryOprator(Op.MUL, Float(2.0), Float(3.0)) # "2.0 * 3.0"
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
#### Label matching
|
|
306
|
+
|
|
307
|
+
Use `on()` or `ignoring()` to control which labels are used for matching when combining two vector selectors:
|
|
308
|
+
|
|
309
|
+
```python
|
|
310
|
+
from promcraft import BinaryOprator, InstantVector, Label, String
|
|
311
|
+
|
|
312
|
+
Op = BinaryOprator.Operator
|
|
313
|
+
|
|
314
|
+
requests = InstantVector("http_requests_total", [])
|
|
315
|
+
errors = InstantVector("http_errors_total", [])
|
|
316
|
+
|
|
317
|
+
# Match on specific labels
|
|
318
|
+
expr = BinaryOprator(Op.DIV, errors, requests).on(["job", "env"])
|
|
319
|
+
# 'http_errors_total{} / on(job, env) http_requests_total{}'
|
|
320
|
+
|
|
321
|
+
# Ignore specific labels
|
|
322
|
+
expr = BinaryOprator(Op.DIV, errors, requests).ignoring(["instance"])
|
|
323
|
+
# 'http_errors_total{} / ignoring(instance) http_requests_total{}'
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
#### Grouping
|
|
327
|
+
|
|
328
|
+
Use `group_left()` or `group_right()` for many-to-one or one-to-many matching:
|
|
329
|
+
|
|
330
|
+
```python
|
|
331
|
+
# group_left: result has labels from the left side
|
|
332
|
+
expr = (BinaryOprator(Op.MUL, requests, errors)
|
|
333
|
+
.on(["job"])
|
|
334
|
+
.group_left(["env"]))
|
|
335
|
+
# '... on(job) group_left(env) ...'
|
|
336
|
+
|
|
337
|
+
# group_right: result has labels from the right side
|
|
338
|
+
expr = (BinaryOprator(Op.MUL, requests, errors)
|
|
339
|
+
.group_right([]))
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
Matching and grouping can also be passed directly to the constructor:
|
|
343
|
+
|
|
344
|
+
```python
|
|
345
|
+
BinaryOprator(Op.DIV, left, right, match=Match.on(["job"]), group=Group.left([]))
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
#### Nested expressions
|
|
349
|
+
|
|
350
|
+
Nested `BinaryOprator` operands are automatically parenthesized:
|
|
351
|
+
|
|
352
|
+
```python
|
|
353
|
+
inner = BinaryOprator(Op.ADD, Float(1.0), Float(2.0))
|
|
354
|
+
outer = BinaryOprator(Op.MUL, inner, Float(3.0))
|
|
355
|
+
str(outer) # "(1.0 + 2.0) * 3.0"
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
---
|
|
359
|
+
|
|
360
|
+
### `Match`
|
|
361
|
+
|
|
362
|
+
Controls label matching for binary operations (used directly or via `BinaryOprator` chainable methods).
|
|
363
|
+
|
|
364
|
+
```python
|
|
365
|
+
from promcraft import Match
|
|
366
|
+
|
|
367
|
+
Match.on(["job", "env"]) # "on(job, env)"
|
|
368
|
+
Match.on([]) # "on()"
|
|
369
|
+
Match.ignoring(["instance"]) # "ignoring(instance)"
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
---
|
|
373
|
+
|
|
374
|
+
### `Group`
|
|
375
|
+
|
|
376
|
+
Controls grouping for many-to-one / one-to-many binary operations.
|
|
377
|
+
|
|
378
|
+
```python
|
|
379
|
+
from promcraft import Group
|
|
380
|
+
|
|
381
|
+
Group.left(["env"]) # "group_left(env)"
|
|
382
|
+
Group.left([]) # "group_left()"
|
|
383
|
+
Group.right(["job"]) # "group_right(job)"
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
---
|
|
387
|
+
|
|
388
|
+
### `AggregationOperator`
|
|
389
|
+
|
|
390
|
+
Applies a PromQL [aggregation operator](https://prometheus.io/docs/prometheus/latest/querying/operators/#aggregation-operators) to a vector.
|
|
391
|
+
|
|
392
|
+
```python
|
|
393
|
+
AggregationOperator(op, vector, *, parameter=None, grouping=None)
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
#### `Grouping`
|
|
397
|
+
|
|
398
|
+
Controls label grouping for aggregations. Use `by()` to keep specified labels, `without()` to drop them:
|
|
399
|
+
|
|
400
|
+
```python
|
|
401
|
+
from promcraft import Grouping
|
|
402
|
+
|
|
403
|
+
Grouping.by(["job", "env"]) # "by(job, env)"
|
|
404
|
+
Grouping.by([]) # "by()"
|
|
405
|
+
Grouping.without(["instance"]) # "without(instance)"
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
The `.by()` and `.without()` chainable methods on `AggregationOperator` return a new instance:
|
|
409
|
+
|
|
410
|
+
```python
|
|
411
|
+
from promcraft import AggregationOperator, InstantVector
|
|
412
|
+
|
|
413
|
+
v = InstantVector("http_requests_total", [])
|
|
414
|
+
|
|
415
|
+
AggregationOperator(AggregationOperator.Operator.SUM, v).by(["job"])
|
|
416
|
+
# "sum(http_requests_total{}) by(job)"
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
#### Helper functions
|
|
420
|
+
|
|
421
|
+
Each aggregation operator has a module-level helper. Helpers for operators that require a `parameter` take it as their first argument.
|
|
422
|
+
|
|
423
|
+
**No-parameter aggregations** — `sum`, `avg`, `min`, `max`, `count`, `group`, `stddev`, `stdvar`:
|
|
424
|
+
|
|
425
|
+
```python
|
|
426
|
+
from promcraft import (
|
|
427
|
+
sum, avg, min, max, count, group, stddev, stdvar,
|
|
428
|
+
InstantVector, Grouping,
|
|
429
|
+
)
|
|
430
|
+
|
|
431
|
+
v = InstantVector("http_requests_total", [])
|
|
432
|
+
|
|
433
|
+
sum(v) # "sum(http_requests_total{})"
|
|
434
|
+
avg(v, grouping=Grouping.by(["job"])) # "avg(http_requests_total{}) by(job)"
|
|
435
|
+
stddev(v, grouping=Grouping.without(["env"]))
|
|
436
|
+
# "stddev(http_requests_total{}) without(env)"
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
**Scalar-parameter aggregations** — `topk`, `bottomk`, `quantile`, `limitk`, `limit_ratio`:
|
|
440
|
+
|
|
441
|
+
```python
|
|
442
|
+
from promcraft import topk, bottomk, quantile, limitk, limit_ratio, Float
|
|
443
|
+
|
|
444
|
+
topk(Float(5.0), v) # "topk(5.0, http_requests_total{})"
|
|
445
|
+
bottomk(Float(3.0), v) # "bottomk(3.0, http_requests_total{})"
|
|
446
|
+
quantile(Float(0.95), v) # "quantile(0.95, http_requests_total{})"
|
|
447
|
+
limitk(Float(10.0), v) # "limitk(10.0, http_requests_total{})"
|
|
448
|
+
limit_ratio(Float(0.1), v) # "limit_ratio(0.1, http_requests_total{})"
|
|
449
|
+
topk(Float(5.0), v, grouping=Grouping.by(["job"]))
|
|
450
|
+
# "topk(5.0, http_requests_total{}) by(job)"
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
**String-parameter aggregation** — `count_values` (label name as a `String`):
|
|
454
|
+
|
|
455
|
+
```python
|
|
456
|
+
from promcraft import count_values, String
|
|
457
|
+
|
|
458
|
+
count_values(String("version"), v)
|
|
459
|
+
# 'count_values("version", http_requests_total{})'
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
---
|
|
463
|
+
|
|
464
|
+
### Functions
|
|
465
|
+
|
|
466
|
+
`Function` wraps any [Prometheus query function](https://prometheus.io/docs/prometheus/latest/querying/functions/). Each function is also available as a typed helper.
|
|
467
|
+
|
|
468
|
+
#### Rate / counter functions
|
|
469
|
+
|
|
470
|
+
```python
|
|
471
|
+
from promcraft import RangeVector, Duration, rate, irate, increase, delta, deriv, resets
|
|
472
|
+
|
|
473
|
+
rv = RangeVector("http_requests_total", [], Duration(m=5))
|
|
474
|
+
|
|
475
|
+
rate(rv) # "rate(http_requests_total{}[5m])"
|
|
476
|
+
irate(rv) # "irate(http_requests_total{}[5m])"
|
|
477
|
+
increase(rv) # "increase(http_requests_total{}[5m])"
|
|
478
|
+
delta(rv) # "delta(http_requests_total{}[5m])"
|
|
479
|
+
deriv(rv) # "deriv(http_requests_total{}[5m])"
|
|
480
|
+
resets(rv) # "resets(http_requests_total{}[5m])"
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
#### `*_over_time` aggregations
|
|
484
|
+
|
|
485
|
+
```python
|
|
486
|
+
from promcraft import (
|
|
487
|
+
avg_over_time, min_over_time, max_over_time, sum_over_time,
|
|
488
|
+
count_over_time, stddev_over_time, last_over_time,
|
|
489
|
+
present_over_time, absent_over_time, quantile_over_time, Float,
|
|
490
|
+
)
|
|
491
|
+
|
|
492
|
+
avg_over_time(rv) # "avg_over_time(http_requests_total{}[5m])"
|
|
493
|
+
quantile_over_time(Float(0.95), rv) # "quantile_over_time(0.95, http_requests_total{}[5m])"
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
#### Math & rounding
|
|
497
|
+
|
|
498
|
+
```python
|
|
499
|
+
from promcraft import InstantVector, abs, ceil, floor, sqrt, round, clamp, clamp_min, clamp_max, Float
|
|
500
|
+
|
|
501
|
+
v = InstantVector("cpu_usage", [])
|
|
502
|
+
|
|
503
|
+
abs(v) # "abs(cpu_usage{})"
|
|
504
|
+
ceil(v) # "ceil(cpu_usage{})"
|
|
505
|
+
sqrt(v) # "sqrt(cpu_usage{})"
|
|
506
|
+
round(v) # "round(cpu_usage{})"
|
|
507
|
+
round(v, Float(0.5)) # "round(cpu_usage{}, 0.5)"
|
|
508
|
+
clamp(v, Float(0.0), Float(1.0)) # "clamp(cpu_usage{}, 0.0, 1.0)"
|
|
509
|
+
clamp_min(v, Float(0.0)) # "clamp_min(cpu_usage{}, 0.0)"
|
|
510
|
+
clamp_max(v, Float(1.0)) # "clamp_max(cpu_usage{}, 1.0)"
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
#### Exponential & logarithm
|
|
514
|
+
|
|
515
|
+
```python
|
|
516
|
+
from promcraft import exp, ln, log2, log10
|
|
517
|
+
|
|
518
|
+
exp(v) # "exp(cpu_usage{})"
|
|
519
|
+
ln(v) # "ln(cpu_usage{})"
|
|
520
|
+
log2(v) # "log2(cpu_usage{})"
|
|
521
|
+
log10(v) # "log10(cpu_usage{})"
|
|
522
|
+
```
|
|
523
|
+
|
|
524
|
+
#### Trigonometry
|
|
525
|
+
|
|
526
|
+
```python
|
|
527
|
+
from promcraft import sin, cos, tan, asin, acos, atan, sinh, cosh, tanh, deg, rad, pi, sgn
|
|
528
|
+
|
|
529
|
+
sin(v) # "sin(cpu_usage{})"
|
|
530
|
+
deg(v) # "deg(cpu_usage{})"
|
|
531
|
+
rad(v) # "rad(cpu_usage{})"
|
|
532
|
+
pi() # "pi()"
|
|
533
|
+
sgn(v) # "sgn(cpu_usage{})"
|
|
534
|
+
```
|
|
535
|
+
|
|
536
|
+
#### Date / time
|
|
537
|
+
|
|
538
|
+
All accept an optional instant vector; when omitted they default to `vector(time())` in PromQL.
|
|
539
|
+
|
|
540
|
+
```python
|
|
541
|
+
from promcraft import hour, minute, day_of_week, day_of_month, month, year, time
|
|
542
|
+
|
|
543
|
+
hour() # "hour()"
|
|
544
|
+
hour(v) # "hour(cpu_usage{})"
|
|
545
|
+
time() # "time()"
|
|
546
|
+
```
|
|
547
|
+
|
|
548
|
+
#### Sorting
|
|
549
|
+
|
|
550
|
+
```python
|
|
551
|
+
from promcraft import sort, sort_desc, sort_by_label, sort_by_label_desc, String
|
|
552
|
+
|
|
553
|
+
sort(v) # "sort(cpu_usage{})"
|
|
554
|
+
sort_desc(v) # "sort_desc(cpu_usage{})"
|
|
555
|
+
sort_by_label(v, String("job")) # 'sort_by_label(cpu_usage{}, "job")'
|
|
556
|
+
sort_by_label_desc(v, String("job"), String("env"))
|
|
557
|
+
# 'sort_by_label_desc(cpu_usage{}, "job", "env")'
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
#### Label manipulation
|
|
561
|
+
|
|
562
|
+
```python
|
|
563
|
+
from promcraft import label_join, label_replace, String
|
|
564
|
+
|
|
565
|
+
label_join(v, String("addr"), String(":"), String("host"), String("port"))
|
|
566
|
+
# 'label_join(cpu_usage{}, "addr", ":", "host", "port")'
|
|
567
|
+
|
|
568
|
+
label_replace(v, String("job"), String("${1}"), String("job"), String("(.+)"))
|
|
569
|
+
# 'label_replace(cpu_usage{}, "job", "${1}", "job", "(.+)")'
|
|
570
|
+
```
|
|
571
|
+
|
|
572
|
+
#### Type conversion
|
|
573
|
+
|
|
574
|
+
```python
|
|
575
|
+
from promcraft import scalar, vector, Float
|
|
576
|
+
|
|
577
|
+
scalar(v) # "scalar(cpu_usage{})"
|
|
578
|
+
vector(Float(1.0)) # "vector(1.0)"
|
|
579
|
+
```
|
|
580
|
+
|
|
581
|
+
#### Histogram functions
|
|
582
|
+
|
|
583
|
+
```python
|
|
584
|
+
from promcraft import (
|
|
585
|
+
histogram_quantile, histogram_fraction,
|
|
586
|
+
histogram_count, histogram_sum, histogram_avg,
|
|
587
|
+
histogram_stddev, histogram_stdvar, Float,
|
|
588
|
+
)
|
|
589
|
+
|
|
590
|
+
histogram_quantile(Float(0.95), v) # "histogram_quantile(0.95, cpu_usage{})"
|
|
591
|
+
histogram_fraction(Float(0.0), Float(1.0), v) # "histogram_fraction(0.0, 1.0, cpu_usage{})"
|
|
592
|
+
histogram_count(v) # "histogram_count(cpu_usage{})"
|
|
593
|
+
```
|
|
594
|
+
|
|
595
|
+
#### Prediction & smoothing
|
|
596
|
+
|
|
597
|
+
```python
|
|
598
|
+
from promcraft import predict_linear, double_exponential_smoothing
|
|
599
|
+
|
|
600
|
+
predict_linear(rv, Float(3600.0))
|
|
601
|
+
# "predict_linear(http_requests_total{}[5m], 3600.0)"
|
|
602
|
+
|
|
603
|
+
double_exponential_smoothing(rv, Float(0.1), Float(0.5))
|
|
604
|
+
# "double_exponential_smoothing(http_requests_total{}[5m], 0.1, 0.5)"
|
|
605
|
+
```
|
|
606
|
+
|
|
607
|
+
#### Absence detection
|
|
608
|
+
|
|
609
|
+
```python
|
|
610
|
+
from promcraft import absent, absent_over_time
|
|
611
|
+
|
|
612
|
+
absent(v) # "absent(cpu_usage{})"
|
|
613
|
+
absent_over_time(rv) # "absent_over_time(http_requests_total{}[5m])"
|
|
614
|
+
```
|
|
615
|
+
|
|
616
|
+
#### Low-level: `Function`
|
|
617
|
+
|
|
618
|
+
The `Function` class underlies all helpers and can be used directly for any custom function:
|
|
619
|
+
|
|
620
|
+
```python
|
|
621
|
+
from promcraft import Function, InstantVector
|
|
622
|
+
|
|
623
|
+
Function("my_custom_func", [InstantVector("up", []), Float(42.0)])
|
|
624
|
+
# "my_custom_func(up{}, 42.0)"
|
|
625
|
+
```
|
|
626
|
+
|
|
627
|
+
---
|
|
628
|
+
|
|
629
|
+
## Composing Complex Queries
|
|
630
|
+
|
|
631
|
+
All objects implement `__str__`, so you can compose arbitrarily deep expressions:
|
|
632
|
+
|
|
633
|
+
```python
|
|
634
|
+
from promcraft import (
|
|
635
|
+
BinaryOprator, InstantVector, RangeVector,
|
|
636
|
+
Label, String, Duration, Float, rate, div,
|
|
637
|
+
)
|
|
638
|
+
|
|
639
|
+
Op = BinaryOprator.Operator
|
|
640
|
+
job_label = Label.eq("job", String("api"))
|
|
641
|
+
|
|
642
|
+
# rate of 5xx errors divided by rate of total requests
|
|
643
|
+
error_rate = rate(RangeVector("http_requests_total", [
|
|
644
|
+
job_label,
|
|
645
|
+
Label.re("status", String("5..")),
|
|
646
|
+
], Duration(m=5)))
|
|
647
|
+
|
|
648
|
+
total_rate = rate(RangeVector("http_requests_total", [job_label], Duration(m=5)))
|
|
649
|
+
|
|
650
|
+
ratio = div(error_rate, total_rate).on(["job"])
|
|
651
|
+
str(ratio)
|
|
652
|
+
# 'rate(http_requests_total{job = "api", status =~ "5.."}[5m])
|
|
653
|
+
# / on(job) rate(http_requests_total{job = "api"}[5m])'
|
|
654
|
+
```
|
|
655
|
+
|
|
656
|
+
## Development
|
|
657
|
+
|
|
658
|
+
```bash
|
|
659
|
+
# Install dependencies
|
|
660
|
+
poetry install
|
|
661
|
+
|
|
662
|
+
# Run tests (includes type checking and linting)
|
|
663
|
+
poetry run pytest
|
|
664
|
+
|
|
665
|
+
# Run only unit tests
|
|
666
|
+
poetry run pytest --no-header -p no:mypy -p no:ruff
|
|
667
|
+
```
|
|
668
|
+
|
|
669
|
+
The test suite uses `pytest` with `pytest-mypy` for static type checking and `pytest-ruff` for linting. All checks run together by default.
|
|
670
|
+
|