onebudgetspec-cli 0.1.0__py3-none-macosx_11_0_arm64.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.
@@ -0,0 +1,222 @@
1
+ Metadata-Version: 2.4
2
+ Name: onebudgetspec-cli
3
+ Version: 0.1.0
4
+ License-File: LICENSE
5
+ Summary: The onebudgetspec command line: check, validate and list the budgets registered in budgets.yaml files.
6
+ Author: Nick DeRobertis
7
+ License-Expression: MIT
8
+ Requires-Python: >=3.9
9
+ Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
10
+ Project-URL: Repository, https://github.com/nickderobertis/onebudgetspec
11
+
12
+ # onebudgetspec
13
+
14
+ Register measurable budgets in YAML and gate on them. Each budget names a command that
15
+ performs the measurement, a unit, a direction and a threshold; `onebudgetspec check` runs
16
+ every selected command once and reports the actual value, the budget and the headroom.
17
+
18
+ It ships as a Rust SDK (`onebudgetspec-core`) and a command line (`onebudgetspec`), with
19
+ Python and TypeScript SDKs released beside them at the same version (see [SDKs](#sdks)).
20
+
21
+ ## Principles
22
+
23
+ - **Measure once.** Each selected budget's command runs exactly once per check. There is
24
+ no retry, repeat or sampling.
25
+ - **Report actual, budget and headroom.** Every result carries all three, so even a result
26
+ within budget shows how close it came.
27
+ - **Record host conditions beside each result.** The load average, CPU count, available
28
+ memory and any conditions the file declares are recorded with every measurement, so a
29
+ person can judge an extreme circumstance from the result itself.
30
+ - **Over budget is a defect, never re-sampled.** An over-budget result fails the check on
31
+ any host. It is never re-run until it passes.
32
+ - **No baselines.** A budget is the threshold written in the file, not a comparison with an
33
+ earlier run.
34
+
35
+ ## Install
36
+
37
+ ```sh
38
+ pip install onebudgetspec-cli # or: uvx --from onebudgetspec-cli onebudgetspec
39
+ npm install --save-dev @onebudgetspec/cli
40
+ cargo install onebudgetspec
41
+ ```
42
+
43
+ ## The budgets file
44
+
45
+ A file named `budgets.yaml`:
46
+
47
+ ```yaml
48
+ schema_version: 1
49
+ conditions: # optional; recorded beside every result measured from this file
50
+ - name: dispatches # ^[a-z][a-z0-9_]*$, unique, never load1, cpus or mem_available_mib
51
+ command: ["scripts/count-dispatches.sh"] # run once per file per check; trimmed stdout recorded
52
+ budgets:
53
+ - id: gate-time # ^[a-z][a-z0-9-]*$, unique across every file one discovery finds
54
+ description: "Wall clock of the full gate" # optional
55
+ labels: [ci] # optional free tags, for selecting a subset
56
+ measure: reported # elapsed | reported
57
+ command: ["scripts/budget-gate-time.sh"] # argv, no shell, run from this file's directory
58
+ unit: seconds # a free label; `elapsed` requires seconds
59
+ direction: max # max: within when actual <= threshold; min: within when actual >= threshold
60
+ threshold: 1800 # finite and non-negative
61
+ timeout_seconds: 3600 # optional; past it the measurement is an error
62
+ ```
63
+
64
+ Unknown keys are refused at every level, naming the key and the file.
65
+
66
+ **`elapsed`** times the command's wall clock; a non-zero exit is an error. **`reported`**
67
+ sets `ONEBUDGETSPEC_RESULT` to the path of a fresh, empty file, and the command writes one
68
+ JSON object there and exits 0:
69
+
70
+ ```json
71
+ {"value": 1395.0, "detail": "optional text", "conditions": {"runner": "large"}}
72
+ ```
73
+
74
+ `detail` and `conditions` are optional. Returned conditions are recorded in that result's
75
+ host conditions only; a returned name equal to a declared condition, `load1`, `cpus` or
76
+ `mem_available_mib` makes the measurement an error rather than replacing a sampled value.
77
+ A command's own stdout and stderr go to stderr, never into a report.
78
+
79
+ ## The command line
80
+
81
+ ```sh
82
+ onebudgetspec check [PATH]... [--id ID]... [--label LABEL]... [--exclude-label LABEL]... [--recursive]
83
+ onebudgetspec validate [PATH]... [--recursive]
84
+ onebudgetspec list [PATH]... [--id ID]... [--label LABEL]... [--exclude-label LABEL]... [--recursive]
85
+ onebudgetspec schema
86
+ ```
87
+
88
+ With no PATH, `./budgets.yaml` is read. `--recursive` searches a directory for files named
89
+ `budgets.yaml`, honouring `.gitignore`. Every verb takes `--output text|json` (`--json` for
90
+ short). `schema` prints the JSON Schema bundle for the budgets file and both reports.
91
+
92
+ A text result reads:
93
+
94
+ ```text
95
+ budget gate-time: actual 1395 seconds, budget 1800 seconds, headroom 405 seconds (22.5%) — within; host: load=2.1/8 mem_available=5120MiB dispatches=3
96
+ ```
97
+
98
+ | Exit | Meaning |
99
+ | ---- | ------- |
100
+ | 0 | every selected budget is within |
101
+ | 1 | at least one is over, and none errored |
102
+ | 2 | the invocation or a file is invalid; nothing was run (the reason is on stderr) |
103
+ | 3 | at least one measurement errored (its reason is that result's `error`, in the report), or the report could not be written (that reason is on stderr) |
104
+
105
+ The npm launcher adds three of its own, each with the reason on stderr: `64` on a platform
106
+ no carrier is published for, `69` when the carrier package is missing or its binary
107
+ cannot run, and `70` when the binary was ended by a signal.
108
+
109
+ ## SDKs
110
+
111
+ Three SDKs, released at the binary's version. The Rust SDK is the engine itself; the Python
112
+ and TypeScript SDKs run the `onebudgetspec` binary once per call, parse the JSON it prints,
113
+ and return the reports as types generated from `onebudgetspec schema`, so they never drift
114
+ from it.
115
+
116
+ | | Rust | Python | TypeScript |
117
+ | --- | --- | --- | --- |
118
+ | Package | `onebudgetspec-core` (crates.io) | `onebudgetspec-sdk` (PyPI), imported as `onebudgetspec_sdk` | `@onebudgetspec/sdk` (npm) |
119
+ | Install | `cargo add onebudgetspec-core` | `pip install onebudgetspec-sdk`, which installs `onebudgetspec-cli` at the same version | `npm install @onebudgetspec/sdk`, which installs its optional dependency `@onebudgetspec/cli` at the same version |
120
+ | Calls | `load(paths, recursive)`, then `.select(&Selection)` and `.check()` or `.list_report()`, or `.all().list_report()` to validate; `schema_bundle()` | `check(paths=None, ids=None, labels=None, exclude_labels=None, recursive=False, cwd=None) -> CheckReport`, `validate(paths=None, recursive=False, cwd=None) -> ListReport`, `list_budgets(...)` with `check`'s arguments `-> ListReport`, `schema() -> dict` | `check({paths, ids, labels, excludeLabels, recursive, cwd})`, `validate({paths, recursive, cwd})`, `listBudgets({paths, ids, labels, excludeLabels, recursive, cwd})`, `schema()`, each a promise of the generated type |
121
+ | Binary | none: it measures in process | the `binary=` argument, then `ONEBUDGETSPEC_BIN`, then the `onebudgetspec` the `onebudgetspec-cli` wheel installed, then `onebudgetspec` on `PATH` | the `binary` option, then `ONEBUDGETSPEC_BIN`, then the launcher of the resolved `@onebudgetspec/cli` package |
122
+ | Tests and journeys it owes | `crates/onebudgetspec-core/tests/api.rs`, `crates/onebudgetspec-core/tests/schema.rs`, and every CLI journey and conformance case, which drive it through the binary | `sdks/python/tests/test_conformance.py` (every conformance case through `check`, `list_budgets` and `validate`), `sdks/python/tests/test_binary.py` (resolution order and refusals), `sdks/python/tests/test_generate.py` (the model generator), and the packaging journey `crates/onebudgetspec-packaging-e2e/tests/packaging/sdk_python.rs` | `sdks/typescript/tests/conformance.test.ts`, `sdks/typescript/tests/binary.test.ts`, `sdks/typescript/tests/errors.test.ts` and `sdks/typescript/tests/generator.test.ts` (the same concerns), and the packaging journey `crates/onebudgetspec-packaging-e2e/tests/packaging/sdk_typescript.rs` |
123
+
124
+ In Python and TypeScript, exit statuses `0`, `1` and `3` return the report, since the
125
+ verdicts are in it; status `2` (an invalid invocation or budgets file) raises
126
+ `OnebudgetspecError` (Python) or rejects with it (TypeScript), carrying the CLI's own
127
+ message and `exit_code`/`exitCode` `2`.
128
+
129
+ ```python
130
+ from onebudgetspec_sdk import check
131
+
132
+ report = check(labels=["api"], exclude_labels=["slow"], cwd="services")
133
+ for result in report.results:
134
+ print(result.id, result.verdict, result.actual, result.headroom, result.host.conditions)
135
+ ```
136
+
137
+ ```ts
138
+ import { check } from "@onebudgetspec/sdk";
139
+
140
+ const report = await check({ labels: ["api"], excludeLabels: ["slow"], cwd: "services" });
141
+ for (const result of report.results) {
142
+ console.log(result.id, result.verdict, result.actual, result.headroom, result.host.conditions);
143
+ }
144
+ ```
145
+
146
+ ## Nesting budgets files
147
+
148
+ Each project registers its own budgets beside its code, and only the budgets a change can
149
+ affect run; the budgets that must always be checked go in the root file. With Nx as the
150
+ worked example, a repository lays them out as:
151
+
152
+ ```text
153
+ budgets.yaml # always checked
154
+ services/api/budgets.yaml # checked when services/api is affected
155
+ services/api/project.json
156
+ ```
157
+
158
+ The project's own file:
159
+
160
+ ```yaml title="services/api/budgets.yaml"
161
+ schema_version: 1
162
+ budgets:
163
+ - id: api-cold-start
164
+ description: "Seconds from process start to the first healthy response"
165
+ measure: elapsed
166
+ command: ["scripts/start-and-probe.sh"]
167
+ unit: seconds
168
+ direction: max
169
+ threshold: 2
170
+ timeout_seconds: 30
171
+ - id: api-requests-per-second
172
+ measure: reported
173
+ command: ["scripts/load-test.sh"]
174
+ unit: requests/s
175
+ direction: min
176
+ threshold: 500
177
+ ```
178
+
179
+ checked by that project's `budgets` target in `services/api/project.json`:
180
+
181
+ ```json
182
+ {
183
+ "targets": {
184
+ "budgets": {
185
+ "command": "onebudgetspec check services/api/budgets.yaml",
186
+ "inputs": ["{projectRoot}/**/*"]
187
+ }
188
+ }
189
+ }
190
+ ```
191
+
192
+ and run for the affected projects only:
193
+
194
+ ```sh
195
+ nx affected -t budgets
196
+ ```
197
+
198
+ The root file holds what every change must stay within:
199
+
200
+ ```yaml title="budgets.yaml"
201
+ schema_version: 1
202
+ conditions:
203
+ - name: runner
204
+ command: ["uname", "-m"]
205
+ budgets:
206
+ - id: gate-time
207
+ description: "Wall clock of the full gate"
208
+ measure: elapsed
209
+ command: ["just", "check"]
210
+ unit: seconds
211
+ direction: max
212
+ threshold: 1800
213
+ timeout_seconds: 3600
214
+ ```
215
+
216
+ checked on every change with `onebudgetspec check budgets.yaml`. To check every file at
217
+ once, `onebudgetspec check --recursive .` discovers both.
218
+
219
+ ## Licence
220
+
221
+ MIT
222
+
@@ -0,0 +1,6 @@
1
+ onebudgetspec_cli-0.1.0.data/scripts/onebudgetspec,sha256=T98pUBLltRMhCisOTZq77Ln92pWtLM0_NlKQbRkjte4,3147392
2
+ onebudgetspec_cli-0.1.0.dist-info/METADATA,sha256=XcKgYM8VIagmlpvoYLNBBmpY4ZrSfFQwcCq0Z5rF91o,9980
3
+ onebudgetspec_cli-0.1.0.dist-info/WHEEL,sha256=U927pSLvWHh5eaqcN5v72CdMm-HUWF344fKLe8HhSOU,102
4
+ onebudgetspec_cli-0.1.0.dist-info/licenses/LICENSE,sha256=JeBGdcXIUkoTZEHhkKd-FmKgdgumkH509Ob1QWJomVE,1072
5
+ onebudgetspec_cli-0.1.0.dist-info/sboms/onebudgetspec.cyclonedx.json,sha256=tOpdSXu66Yu3dcxH4KxDwT8JpEMnIg7iQuiDtWjqV7o,78266
6
+ onebudgetspec_cli-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: maturin (1.15.0)
3
+ Root-Is-Purelib: false
4
+ Tag: py3-none-macosx_11_0_arm64
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Nick DeRobertis
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.