promtext-cli 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.
- promtext_cli-0.1.0/.github/workflows/lint.yaml +14 -0
- promtext_cli-0.1.0/.github/workflows/publish.yaml +87 -0
- promtext_cli-0.1.0/.gitignore +3 -0
- promtext_cli-0.1.0/PKG-INFO +106 -0
- promtext_cli-0.1.0/README.md +93 -0
- promtext_cli-0.1.0/promtext_cli/__init__.py +0 -0
- promtext_cli-0.1.0/promtext_cli/main.py +120 -0
- promtext_cli-0.1.0/pyproject.toml +41 -0
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
name: Publish Python 🐍 distribution 📦 to PyPI and TestPyPI
|
|
2
|
+
# based on https://packaging.python.org/en/latest/guides/publishing-package-distribution-releases-using-github-actions-ci-cd-workflows
|
|
3
|
+
|
|
4
|
+
on: push
|
|
5
|
+
|
|
6
|
+
jobs:
|
|
7
|
+
build:
|
|
8
|
+
name: Build distribution 📦
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
|
|
11
|
+
steps:
|
|
12
|
+
- uses: actions/checkout@v4
|
|
13
|
+
with:
|
|
14
|
+
fetch-depth: 0 # fetch all history for tags, allowing to build proper dev version
|
|
15
|
+
- run: pipx install hatch
|
|
16
|
+
- run: hatch build
|
|
17
|
+
- name: Store the distribution packages
|
|
18
|
+
uses: actions/upload-artifact@v3
|
|
19
|
+
with:
|
|
20
|
+
name: python-package-distributions
|
|
21
|
+
path: dist/
|
|
22
|
+
|
|
23
|
+
publish-to-pypi:
|
|
24
|
+
name: >-
|
|
25
|
+
Publish Python 🐍 distribution 📦 to PyPI
|
|
26
|
+
if: startsWith(github.ref, 'refs/tags/') || github.ref == 'refs/heads/main' # only publish to PyPI on tag pushes or dev version for main
|
|
27
|
+
needs:
|
|
28
|
+
- build
|
|
29
|
+
runs-on: ubuntu-latest
|
|
30
|
+
environment:
|
|
31
|
+
name: pypi
|
|
32
|
+
url: https://pypi.org/p/promtext-cli
|
|
33
|
+
permissions:
|
|
34
|
+
id-token: write # IMPORTANT: mandatory for trusted publishing
|
|
35
|
+
|
|
36
|
+
steps:
|
|
37
|
+
- name: Download all the dists
|
|
38
|
+
uses: actions/download-artifact@v3
|
|
39
|
+
with:
|
|
40
|
+
name: python-package-distributions
|
|
41
|
+
path: dist/
|
|
42
|
+
- name: Publish distribution 📦 to PyPI
|
|
43
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
44
|
+
|
|
45
|
+
github-release:
|
|
46
|
+
name: >-
|
|
47
|
+
Sign the Python 🐍 distribution 📦 with Sigstore
|
|
48
|
+
and upload them to GitHub Release
|
|
49
|
+
if: startsWith(github.ref, 'refs/tags/') # only publish to PyPI on tag pushes
|
|
50
|
+
needs:
|
|
51
|
+
- publish-to-pypi
|
|
52
|
+
runs-on: ubuntu-latest
|
|
53
|
+
|
|
54
|
+
permissions:
|
|
55
|
+
contents: write # IMPORTANT: mandatory for making GitHub Releases
|
|
56
|
+
id-token: write # IMPORTANT: mandatory for sigstore
|
|
57
|
+
|
|
58
|
+
steps:
|
|
59
|
+
- name: Download all the dists
|
|
60
|
+
uses: actions/download-artifact@v3
|
|
61
|
+
with:
|
|
62
|
+
name: python-package-distributions
|
|
63
|
+
path: dist/
|
|
64
|
+
- name: Sign the dists with Sigstore
|
|
65
|
+
uses: sigstore/gh-action-sigstore-python@v1.2.3
|
|
66
|
+
with:
|
|
67
|
+
inputs: >-
|
|
68
|
+
./dist/*.tar.gz
|
|
69
|
+
./dist/*.whl
|
|
70
|
+
- name: Create GitHub Release
|
|
71
|
+
env:
|
|
72
|
+
GITHUB_TOKEN: ${{ github.token }}
|
|
73
|
+
run: >-
|
|
74
|
+
gh release create
|
|
75
|
+
'${{ github.ref_name }}'
|
|
76
|
+
--repo '${{ github.repository }}'
|
|
77
|
+
--notes ""
|
|
78
|
+
- name: Upload artifact signatures to GitHub Release
|
|
79
|
+
env:
|
|
80
|
+
GITHUB_TOKEN: ${{ github.token }}
|
|
81
|
+
# Upload to GitHub Release using the `gh` CLI.
|
|
82
|
+
# `dist/` contains the built packages, and the
|
|
83
|
+
# sigstore-produced signatures and certificates.
|
|
84
|
+
run: >-
|
|
85
|
+
gh release upload
|
|
86
|
+
'${{ github.ref_name }}' dist/**
|
|
87
|
+
--repo '${{ github.repository }}'
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: promtext-cli
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Prometheus Textfile Tooling
|
|
5
|
+
Project-URL: Issues, https://github.com/margau/promtext-cli/issues
|
|
6
|
+
Project-URL: Source, https://github.com/margau/promtext-cli
|
|
7
|
+
Author-email: margau <dev@marvingaube.de>
|
|
8
|
+
License-Expression: GPL-3.0
|
|
9
|
+
Keywords: Prometheus
|
|
10
|
+
Requires-Python: >=3.7
|
|
11
|
+
Requires-Dist: prometheus-client
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
|
|
14
|
+
# promtext-cli
|
|
15
|
+
|
|
16
|
+
promtext-cli is a tool for creating prometheus text files from a simple cli command.
|
|
17
|
+
|
|
18
|
+
It is intended for use with cronjob scripts (e.g. backups).
|
|
19
|
+
|
|
20
|
+
Features:
|
|
21
|
+
- supports merging new metrics into existing files
|
|
22
|
+
- metrics will be updated (same labelset or no labels given), or appended to existing metrics as new timeseries
|
|
23
|
+
- currently only supports gauge metrics
|
|
24
|
+
|
|
25
|
+
## Usage
|
|
26
|
+
```
|
|
27
|
+
promtext -h
|
|
28
|
+
usage: main.py [-h] [--docs DOCS] [--label KEY=VALUE] [-v] filename metric value
|
|
29
|
+
|
|
30
|
+
Prometheus textfile helper
|
|
31
|
+
|
|
32
|
+
positional arguments:
|
|
33
|
+
filename Path to existing or new prometheus textfile, will be updated
|
|
34
|
+
metric metric name (new or updated)
|
|
35
|
+
value metric value
|
|
36
|
+
|
|
37
|
+
options:
|
|
38
|
+
-h, --help show this help message and exit
|
|
39
|
+
--docs DOCS metric documentation
|
|
40
|
+
--label KEY=VALUE label key=value pairs
|
|
41
|
+
-v, --verbose
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Examples
|
|
45
|
+
`tmp/backup.prom` before:
|
|
46
|
+
```
|
|
47
|
+
# HELP backup_last_start
|
|
48
|
+
# TYPE backup_last_start gauge
|
|
49
|
+
backup_last_start{backup="example_1"} 1.721923501e+09
|
|
50
|
+
# HELP backup_last_end
|
|
51
|
+
# TYPE backup_last_end gauge
|
|
52
|
+
backup_last_end{backup="example_1"} 1.721989156e+09
|
|
53
|
+
# HELP backup_last_exit
|
|
54
|
+
# TYPE backup_last_exit gauge
|
|
55
|
+
backup_last_exit{backup="example_1"} 2.0
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Updating existing timeseries: `promtext tmp/backup.prom backup_last_start 0 --label backup=example_1`:
|
|
59
|
+
```
|
|
60
|
+
# HELP backup_last_start
|
|
61
|
+
# TYPE backup_last_start gauge
|
|
62
|
+
backup_last_start{backup="example_1"} 0.0
|
|
63
|
+
# HELP backup_last_end
|
|
64
|
+
# TYPE backup_last_end gauge
|
|
65
|
+
backup_last_end{backup="example_1"} 1.721989156e+09
|
|
66
|
+
# HELP backup_last_exit
|
|
67
|
+
# TYPE backup_last_exit gauge
|
|
68
|
+
backup_last_exit{backup="example_1"} 2.0
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Adding a new label: `promtext tmp/backup.prom backup_last_start 0 --label backup=example_2`
|
|
72
|
+
```
|
|
73
|
+
# HELP backup_last_start
|
|
74
|
+
# TYPE backup_last_start gauge
|
|
75
|
+
backup_last_start{backup="example_1"} 0.0
|
|
76
|
+
backup_last_start{backup="example_2"} 0.0
|
|
77
|
+
# HELP backup_last_end
|
|
78
|
+
# TYPE backup_last_end gauge
|
|
79
|
+
backup_last_end{backup="example_1"} 1.721989156e+09
|
|
80
|
+
# HELP backup_last_exit
|
|
81
|
+
# TYPE backup_last_exit gauge
|
|
82
|
+
backup_last_exit{backup="example_1"} 2.0
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Adding a new metric: `promtext tmp/backup.prom some_other_state 0 --label new_label=foo_bar`
|
|
86
|
+
```
|
|
87
|
+
# HELP backup_last_start
|
|
88
|
+
# TYPE backup_last_start gauge
|
|
89
|
+
backup_last_start{backup="example_1"} 0.0
|
|
90
|
+
backup_last_start{backup="example_2"} 0.0
|
|
91
|
+
# HELP backup_last_end
|
|
92
|
+
# TYPE backup_last_end gauge
|
|
93
|
+
backup_last_end{backup="example_1"} 1.721989156e+09
|
|
94
|
+
# HELP backup_last_exit
|
|
95
|
+
# TYPE backup_last_exit gauge
|
|
96
|
+
backup_last_exit{backup="example_1"} 2.0
|
|
97
|
+
# HELP some_other_state metric appended by promtext-cli
|
|
98
|
+
# TYPE some_other_state gauge
|
|
99
|
+
some_other_state{new_label="foo_bar"} 0.0
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
However, changing the label keys does not work:
|
|
103
|
+
```
|
|
104
|
+
promtext tmp/backup.prom some_other_state 0 --label foo_bar=foo_bar
|
|
105
|
+
ERROR:promtext_cli.main:labelnames for metric some_other_state not compatible, cannot update! Old: ['new_label'], New: ['foo_bar']
|
|
106
|
+
```
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# promtext-cli
|
|
2
|
+
|
|
3
|
+
promtext-cli is a tool for creating prometheus text files from a simple cli command.
|
|
4
|
+
|
|
5
|
+
It is intended for use with cronjob scripts (e.g. backups).
|
|
6
|
+
|
|
7
|
+
Features:
|
|
8
|
+
- supports merging new metrics into existing files
|
|
9
|
+
- metrics will be updated (same labelset or no labels given), or appended to existing metrics as new timeseries
|
|
10
|
+
- currently only supports gauge metrics
|
|
11
|
+
|
|
12
|
+
## Usage
|
|
13
|
+
```
|
|
14
|
+
promtext -h
|
|
15
|
+
usage: main.py [-h] [--docs DOCS] [--label KEY=VALUE] [-v] filename metric value
|
|
16
|
+
|
|
17
|
+
Prometheus textfile helper
|
|
18
|
+
|
|
19
|
+
positional arguments:
|
|
20
|
+
filename Path to existing or new prometheus textfile, will be updated
|
|
21
|
+
metric metric name (new or updated)
|
|
22
|
+
value metric value
|
|
23
|
+
|
|
24
|
+
options:
|
|
25
|
+
-h, --help show this help message and exit
|
|
26
|
+
--docs DOCS metric documentation
|
|
27
|
+
--label KEY=VALUE label key=value pairs
|
|
28
|
+
-v, --verbose
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Examples
|
|
32
|
+
`tmp/backup.prom` before:
|
|
33
|
+
```
|
|
34
|
+
# HELP backup_last_start
|
|
35
|
+
# TYPE backup_last_start gauge
|
|
36
|
+
backup_last_start{backup="example_1"} 1.721923501e+09
|
|
37
|
+
# HELP backup_last_end
|
|
38
|
+
# TYPE backup_last_end gauge
|
|
39
|
+
backup_last_end{backup="example_1"} 1.721989156e+09
|
|
40
|
+
# HELP backup_last_exit
|
|
41
|
+
# TYPE backup_last_exit gauge
|
|
42
|
+
backup_last_exit{backup="example_1"} 2.0
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Updating existing timeseries: `promtext tmp/backup.prom backup_last_start 0 --label backup=example_1`:
|
|
46
|
+
```
|
|
47
|
+
# HELP backup_last_start
|
|
48
|
+
# TYPE backup_last_start gauge
|
|
49
|
+
backup_last_start{backup="example_1"} 0.0
|
|
50
|
+
# HELP backup_last_end
|
|
51
|
+
# TYPE backup_last_end gauge
|
|
52
|
+
backup_last_end{backup="example_1"} 1.721989156e+09
|
|
53
|
+
# HELP backup_last_exit
|
|
54
|
+
# TYPE backup_last_exit gauge
|
|
55
|
+
backup_last_exit{backup="example_1"} 2.0
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Adding a new label: `promtext tmp/backup.prom backup_last_start 0 --label backup=example_2`
|
|
59
|
+
```
|
|
60
|
+
# HELP backup_last_start
|
|
61
|
+
# TYPE backup_last_start gauge
|
|
62
|
+
backup_last_start{backup="example_1"} 0.0
|
|
63
|
+
backup_last_start{backup="example_2"} 0.0
|
|
64
|
+
# HELP backup_last_end
|
|
65
|
+
# TYPE backup_last_end gauge
|
|
66
|
+
backup_last_end{backup="example_1"} 1.721989156e+09
|
|
67
|
+
# HELP backup_last_exit
|
|
68
|
+
# TYPE backup_last_exit gauge
|
|
69
|
+
backup_last_exit{backup="example_1"} 2.0
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Adding a new metric: `promtext tmp/backup.prom some_other_state 0 --label new_label=foo_bar`
|
|
73
|
+
```
|
|
74
|
+
# HELP backup_last_start
|
|
75
|
+
# TYPE backup_last_start gauge
|
|
76
|
+
backup_last_start{backup="example_1"} 0.0
|
|
77
|
+
backup_last_start{backup="example_2"} 0.0
|
|
78
|
+
# HELP backup_last_end
|
|
79
|
+
# TYPE backup_last_end gauge
|
|
80
|
+
backup_last_end{backup="example_1"} 1.721989156e+09
|
|
81
|
+
# HELP backup_last_exit
|
|
82
|
+
# TYPE backup_last_exit gauge
|
|
83
|
+
backup_last_exit{backup="example_1"} 2.0
|
|
84
|
+
# HELP some_other_state metric appended by promtext-cli
|
|
85
|
+
# TYPE some_other_state gauge
|
|
86
|
+
some_other_state{new_label="foo_bar"} 0.0
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
However, changing the label keys does not work:
|
|
90
|
+
```
|
|
91
|
+
promtext tmp/backup.prom some_other_state 0 --label foo_bar=foo_bar
|
|
92
|
+
ERROR:promtext_cli.main:labelnames for metric some_other_state not compatible, cannot update! Old: ['new_label'], New: ['foo_bar']
|
|
93
|
+
```
|
|
File without changes
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"""promtext_cli is providing a CLI to cleanly update prometheus textfiles from scripts"""
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
import logging
|
|
6
|
+
import sys
|
|
7
|
+
|
|
8
|
+
from prometheus_client.parser import text_string_to_metric_families
|
|
9
|
+
from prometheus_client import CollectorRegistry, Gauge, write_to_textfile
|
|
10
|
+
|
|
11
|
+
def promtext():
|
|
12
|
+
# setup argpars
|
|
13
|
+
# required file first
|
|
14
|
+
parser = argparse.ArgumentParser(description='Prometheus textfile helper')
|
|
15
|
+
parser.add_argument(
|
|
16
|
+
'filename',
|
|
17
|
+
type=str,
|
|
18
|
+
help='Path to existing or new prometheus textfile, will be updated'
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
# metric name, required
|
|
22
|
+
parser.add_argument(
|
|
23
|
+
'metric',
|
|
24
|
+
type=str,
|
|
25
|
+
help='metric name (new or updated)')
|
|
26
|
+
|
|
27
|
+
# metric value as int/float, required
|
|
28
|
+
parser.add_argument('value', type=float, help='metric value')
|
|
29
|
+
|
|
30
|
+
# metric documentation as optional argument
|
|
31
|
+
parser.add_argument(
|
|
32
|
+
'--docs', type=str,
|
|
33
|
+
help='metric documentation', default="metric appended by promtext-cli")
|
|
34
|
+
|
|
35
|
+
# labels, key-value, minimum 0, repeatable
|
|
36
|
+
parser.add_argument("--label", metavar="KEY=VALUE", help='label key=value pairs', action='append')
|
|
37
|
+
|
|
38
|
+
# log level from argparse
|
|
39
|
+
parser.add_argument(
|
|
40
|
+
'-v', '--verbose',
|
|
41
|
+
action="store_const", dest="loglevel", const=logging.INFO,
|
|
42
|
+
)
|
|
43
|
+
args = parser.parse_args()
|
|
44
|
+
logging.basicConfig(level=args.loglevel)
|
|
45
|
+
logger = logging.getLogger(__name__)
|
|
46
|
+
|
|
47
|
+
# processing: check if file is available, if yes, parse
|
|
48
|
+
textfile = Path(args.filename)
|
|
49
|
+
|
|
50
|
+
registry = CollectorRegistry()
|
|
51
|
+
metrics = {}
|
|
52
|
+
|
|
53
|
+
# check if args.filename exists with pathlib
|
|
54
|
+
if textfile.is_file():
|
|
55
|
+
for f in text_string_to_metric_families(textfile.read_text()):
|
|
56
|
+
# per metric: iterate over samples, create metric in registry
|
|
57
|
+
m = False
|
|
58
|
+
samples = []
|
|
59
|
+
for s in f.samples:
|
|
60
|
+
samples.append(s)
|
|
61
|
+
if len(samples) > 0:
|
|
62
|
+
# if we have samples, use the labelnames from them
|
|
63
|
+
labelnames = list(samples[0].labels.keys())
|
|
64
|
+
# metric-type specific init
|
|
65
|
+
if f.type == "gauge":
|
|
66
|
+
m = Gauge(f.name, f.documentation,
|
|
67
|
+
unit=f.unit, labelnames=labelnames, registry=registry)
|
|
68
|
+
else:
|
|
69
|
+
# we don't support other types yet, continue in these cases
|
|
70
|
+
logger.warning("unsupported metric type %s, dropping", f.type)
|
|
71
|
+
continue
|
|
72
|
+
for s in samples:
|
|
73
|
+
if len(labelnames) > 0:
|
|
74
|
+
labelvalues = list(s.labels.values())
|
|
75
|
+
m.labels(*labelvalues).set(s.value)
|
|
76
|
+
else:
|
|
77
|
+
m.set(s.value)
|
|
78
|
+
metrics[f.name] = m
|
|
79
|
+
logger.info("copy gauge metric %s with labels %s from old file",
|
|
80
|
+
f.name, ', '.join(labelnames))
|
|
81
|
+
else:
|
|
82
|
+
logger.warning("got empty metric %s from old file, dropping", f.name)
|
|
83
|
+
|
|
84
|
+
# add new metric from commandline
|
|
85
|
+
m = False
|
|
86
|
+
|
|
87
|
+
# figure out labelkey- and values
|
|
88
|
+
labelnames = []
|
|
89
|
+
labelvalues = []
|
|
90
|
+
if args.label:
|
|
91
|
+
for lpair in args.label:
|
|
92
|
+
k, v = lpair.split("=")
|
|
93
|
+
labelnames.append(k)
|
|
94
|
+
labelvalues.append(v)
|
|
95
|
+
|
|
96
|
+
# here, we use a new metric
|
|
97
|
+
if args.metric not in metrics:
|
|
98
|
+
logger.info("adding new metric %s", args.metric)
|
|
99
|
+
m = Gauge(args.metric, args.docs, registry=registry, labelnames=labelnames)
|
|
100
|
+
else:
|
|
101
|
+
m = metrics[args.metric]
|
|
102
|
+
old_labelnames = list(m._labelnames)
|
|
103
|
+
if old_labelnames != labelnames:
|
|
104
|
+
logger.error("labelnames for metric %s not compatible, cannot update! Old: %s, New: %s",
|
|
105
|
+
args.metric, old_labelnames, labelnames)
|
|
106
|
+
sys.exit(1)
|
|
107
|
+
logger.info("updating metric %s", args.metric)
|
|
108
|
+
|
|
109
|
+
# actually set the value
|
|
110
|
+
if len(labelnames) > 0:
|
|
111
|
+
m.labels(*labelvalues).set(args.value)
|
|
112
|
+
else:
|
|
113
|
+
m.set(args.value)
|
|
114
|
+
|
|
115
|
+
# write to file
|
|
116
|
+
write_to_textfile(args.filename, registry)
|
|
117
|
+
logger.info("wrote to %s", args.filename)
|
|
118
|
+
|
|
119
|
+
if __name__ == '__main__':
|
|
120
|
+
promtext()
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling", "hatch-vcs"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "promtext-cli"
|
|
7
|
+
description = 'Prometheus Textfile Tooling'
|
|
8
|
+
readme = "README.md"
|
|
9
|
+
requires-python = ">=3.7"
|
|
10
|
+
license = "GPL-3.0"
|
|
11
|
+
keywords = [
|
|
12
|
+
"Prometheus"
|
|
13
|
+
]
|
|
14
|
+
authors = [
|
|
15
|
+
{ name = "margau", email = "dev@marvingaube.de" }
|
|
16
|
+
]
|
|
17
|
+
classifiers = []
|
|
18
|
+
dependencies = [
|
|
19
|
+
"prometheus_client"
|
|
20
|
+
]
|
|
21
|
+
dynamic = ["version"]
|
|
22
|
+
|
|
23
|
+
[project.urls]
|
|
24
|
+
Issues = "https://github.com/margau/promtext-cli/issues"
|
|
25
|
+
Source = "https://github.com/margau/promtext-cli"
|
|
26
|
+
|
|
27
|
+
[project.scripts]
|
|
28
|
+
promtext = "promtext_cli.main:promtext"
|
|
29
|
+
|
|
30
|
+
[tool.hatch.version]
|
|
31
|
+
source = "vcs"
|
|
32
|
+
|
|
33
|
+
[tool.hatch.envs.ci]
|
|
34
|
+
dependencies = [
|
|
35
|
+
"pylint",
|
|
36
|
+
"ruff",
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
[tool.hatch.envs.ci.scripts]
|
|
40
|
+
check = "ruff check promtext_cli"
|
|
41
|
+
lint = "pylint promtext_cli || exit 0"
|