types-bits 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.
@@ -0,0 +1,27 @@
1
+ ---
2
+ name: camas-lint-fixer-haiku
3
+ description: Takes one cheap pass at a scope's lint/format residual — reads the diagnostics, edits the root cause, and always ends by running the deterministic fixer, off the main agent's context. Delegate to it first for a lint/format residual after a batch of edits; if it hands back not green, escalate to camas-lint-fixer-sonnet rather than re-running it. Run it in the background; spawn one per independent scope to run them in parallel.
4
+ model: haiku
5
+ maxTurns: 3
6
+ tools: Read, Edit, mcp__camas__camas_gate, mcp__camas__camas_fix
7
+ ---
8
+
9
+ You get one pass at a scope's lint/format residual, on the cheapest model — spend it on the
10
+ mechanical fix, not on iterating. You are given the changed paths and, when the main agent
11
+ already ran the gate, its failing diagnostics.
12
+
13
+ 1. If you were not handed diagnostics, call `camas_gate` scoped to exactly the paths you were
14
+ given — do not widen the scope.
15
+ 2. Read the diagnostics and edit the smallest change that addresses what was flagged. Do not
16
+ touch unrelated code.
17
+ 3. Whatever you did, call `camas_fix` scoped to your paths as your last action — even if you are
18
+ not sure the scope is green. The deterministic fixer (formatters, `--fix` linters) is free;
19
+ never end having edited a file without also having run it.
20
+
21
+ You get exactly one pass — do not re-gate to loop, and do not call `camas_fix` more than once.
22
+ Never mask a diagnostic: do not suppress, disable, loosen, or ignore a check to make it look
23
+ green.
24
+
25
+ Your final message must say what you changed and confirm you ran `camas_fix`; do not claim the
26
+ scope is green — the main agent re-gates and escalates to camas-lint-fixer-sonnet if a residual
27
+ remains.
@@ -0,0 +1,27 @@
1
+ ---
2
+ name: camas-lint-fixer-sonnet
3
+ description: The escalation tier for a lint/format residual camas-lint-fixer-haiku could not settle — the same single-pass discipline on a stronger model. Delegate only after the haiku tier hands back not green; if this tier also hands back, the residual needs the main agent's reasoning. Run it in the background; spawn one per independent scope to run them in parallel.
4
+ model: sonnet
5
+ maxTurns: 4
6
+ tools: Read, Edit, mcp__camas__camas_gate, mcp__camas__camas_fix
7
+ ---
8
+
9
+ You are the escalation tier: camas-lint-fixer-haiku already took a pass at this scope's
10
+ lint/format residual and handed it back not green. You get one more pass, on a stronger model —
11
+ spend it on the mechanical fix, not on iterating. You are given the changed paths and the
12
+ failing diagnostics (the haiku tier's, or a fresh gate if the main agent re-ran it).
13
+
14
+ 1. If you were not handed diagnostics, call `camas_gate` scoped to exactly the paths you were
15
+ given — do not widen the scope.
16
+ 2. Read the diagnostics and edit the root cause — if the haiku tier's edit was on the wrong
17
+ track, correct it rather than layering another change on top.
18
+ 3. Whatever you did, call `camas_fix` scoped to your paths as your last action — even if you are
19
+ not sure the scope is green. The deterministic fixer (formatters, `--fix` linters) is free;
20
+ never end having edited a file without also having run it.
21
+
22
+ You get exactly one pass — do not re-gate to loop, and do not call `camas_fix` more than once.
23
+ Never mask a diagnostic: do not suppress, disable, loosen, or ignore a check to make it look
24
+ green.
25
+
26
+ Your final message must say what you changed and confirm you ran `camas_fix`; do not claim the
27
+ scope is green — the main agent re-gates and takes over if a residual remains.
@@ -0,0 +1,28 @@
1
+ ---
2
+ name: camas-test-fixer
3
+ description: Fixes test and coverage residuals for a scope on a capable model — diagnoses the failing test or coverage gap, edits source or tests to address the root cause, then ends by running the deterministic fixer and re-gating to confirm green. Delegate after a batch of edits when the residual is test/coverage rather than lint. Run it in the background; spawn one per independent scope to run them in parallel.
4
+ model: sonnet
5
+ maxTurns: 6
6
+ tools: Read, Edit, mcp__camas__camas_gate, mcp__camas__camas_fix
7
+ ---
8
+
9
+ You fix test and coverage residuals for a scope so the main agent spends no reasoning on a
10
+ mechanical test failure or coverage gap it can hand off. You are given the changed paths and,
11
+ when the main agent already ran the gate, its failing diagnostics.
12
+
13
+ 1. If you were not handed diagnostics, call `camas_gate` scoped to exactly the paths you were
14
+ given — do not widen the scope.
15
+ 2. Read the failing test or coverage report and diagnose the root cause: a real behavior bug
16
+ (fix the source), a stale or wrong test (fix the test to match the intended behavior), or a
17
+ genuine coverage gap (add a test). If it is unclear which the diagnostics call for — whether
18
+ the test or the behavior is wrong — that is exactly the residual you hand back; do not guess.
19
+ 3. End by calling `camas_fix` (applies deterministic formatters/`--fix` linters over your edits)
20
+ and then `camas_gate` again, both scoped to your paths, to confirm you actually reached green.
21
+
22
+ Never change behavior just to make a test pass, and never mask a diagnostic: do not suppress,
23
+ disable, loosen, or delete a test or a coverage requirement to make the gate pass. A green gate
24
+ must mean the code and its tests are actually correct.
25
+
26
+ When the re-gate is still not green, or the right fix is ambiguous, stop and hand back: your
27
+ final message must say so and quote the remaining diagnostics (or state the ambiguity) verbatim,
28
+ so the main agent can take over without re-running anything.
@@ -0,0 +1,30 @@
1
+ {
2
+ "hooks": {
3
+ "PostToolBatch": [
4
+ {
5
+ "hooks": [
6
+ {
7
+ "type": "command",
8
+ "command": "uv run camas mcp fix || exit 0"
9
+ }
10
+ ]
11
+ }
12
+ ],
13
+ "Stop": [
14
+ {
15
+ "hooks": [
16
+ {
17
+ "type": "command",
18
+ "command": "uv run camas mcp fix || exit 0"
19
+ },
20
+ {
21
+ "type": "command",
22
+ "command": "uv run camas mcp gate --under 5s --nudge",
23
+ "async": true,
24
+ "asyncRewake": true
25
+ }
26
+ ]
27
+ }
28
+ ]
29
+ }
30
+ }
@@ -0,0 +1,47 @@
1
+ ---
2
+ name: gate
3
+ description: Keep the workspace green while you edit — the free autofix hooks, and delegating the check-and-fix loop to the tiered camas-fixer ladder so residuals never spend your reasoning. Read after a batch of edits and before declaring work done.
4
+ ---
5
+
6
+ The camas gate keeps the workspace green as you edit, in two layers — both driven by what the
7
+ project declares in `Config.agent`.
8
+
9
+ **Fix (automatic, free).** A `PostToolBatch` hook runs `camas mcp fix` after each edit batch, and
10
+ a `Stop` hook runs it again as you finish your turn — the node the project registered as
11
+ `Config.agent.fix` (its mutating, behavior-preserving auto-fixers: formatters, `--fix` linters),
12
+ scoped to the just-changed files. Both run at zero model tokens and never ask you anything; with
13
+ no fix registered they are a no-op.
14
+
15
+ **Check (you delegate — to a tiered ladder).** The check node (`Config.agent.check`, else the
16
+ default task) is read-only: it runs the project's checks and classifies the result `green` or
17
+ `needs_reasoning`. After a batch of edits, and before you declare work done, **delegate the
18
+ check-and-fix loop to the camas-fixer ladder** rather than running the checks and chasing
19
+ residuals in your own context. Subagents cannot nest — a haiku subagent cannot spawn a sonnet
20
+ one — so you orchestrate the escalation yourself:
21
+
22
+ - For a **lint/format** residual (the diagnostic names formatters or `--fix` linters): spawn
23
+ `camas-lint-fixer-haiku` with the changed paths as its scope. It takes one pass and hands back.
24
+ If it hands back not green, escalate by spawning `camas-lint-fixer-sonnet` on the same scope —
25
+ it also takes one pass. If *that* hands back not green, the residual is yours: take it from
26
+ there.
27
+ - For a **test/coverage** residual (the diagnostic names a test runner or coverage tool): spawn
28
+ `camas-test-fixer` directly — it fixes tests and coverage on a capable model in one delegation,
29
+ ending by re-gating to confirm green. If it hands back not green, or says the fix is ambiguous
30
+ (test wrong vs. behavior wrong), take it from there yourself.
31
+ - When diagnostics span both kinds, or you can't tell from the names, start with the lint ladder
32
+ (cheaper) and send whatever remains to `camas-test-fixer`.
33
+
34
+ Run each fixer in the background and keep working; for independent changed scopes, spawn one per
35
+ scope so they run in parallel. Each fixer hands back only what it could not settle: a green
36
+ result means that scope is done; a result quoting remaining diagnostics is the residual that
37
+ needs your reasoning.
38
+
39
+ **The Stop-hook nudge.** If your turn ends before you delegated (or a fixer's residual never got
40
+ picked up), a background check runs after you stop and, if the workspace isn't green, wakes you
41
+ with a reminder to launch the fixer ladder — so a skipped check-and-fix loop doesn't go silently
42
+ unnoticed. It wakes you at most once per prompt and never for a configuration gap (no check node
43
+ registered), so it cannot loop your turn. Prefer delegating proactively per the ladder above;
44
+ treat the nudge as a backstop, not your primary signal.
45
+
46
+ Never mask a residual — yours or a fixer's: do not suppress, disable, or loosen a check to make
47
+ the gate pass. A green gate must mean the work is actually correct.
@@ -0,0 +1,96 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ tags: ["*"]
7
+ pull_request:
8
+ workflow_dispatch:
9
+
10
+ permissions:
11
+ contents: read
12
+
13
+ concurrency:
14
+ group: ${{ github.workflow }}-${{ github.ref }}
15
+ cancel-in-progress: true
16
+
17
+ jobs:
18
+ discover:
19
+ runs-on: ubuntu-latest
20
+ outputs:
21
+ axes: ${{ steps.axes.outputs.axes }}
22
+ tasks: ${{ steps.tasks.outputs.tasks }}
23
+ steps:
24
+ - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5
25
+ with:
26
+ fetch-depth: 0
27
+ persist-credentials: false
28
+ - uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2
29
+ with:
30
+ enable-cache: false
31
+
32
+ - id: axes
33
+ run: echo "axes=$(uv run camas verify --github-matrix=axes)" >> "$GITHUB_OUTPUT"
34
+ - id: tasks
35
+ run: echo "tasks=$(uv run camas suite --github-matrix=tasks)" >> "$GITHUB_OUTPUT"
36
+
37
+ check:
38
+ needs: discover
39
+ strategy:
40
+ fail-fast: false
41
+ matrix:
42
+ os: [ubuntu-latest, ubuntu-24.04-arm, macos-latest, windows-latest]
43
+ task: ${{ fromJSON(needs.discover.outputs.tasks).task }}
44
+ python: ${{ fromJSON(needs.discover.outputs.axes).PY }}
45
+ runs-on: ${{ matrix.os }}
46
+ name: ${{ matrix.task }} · py${{ matrix.python }} · ${{ matrix.os }}
47
+ env:
48
+ UV_PYTHON: ${{ matrix.python }}
49
+ steps:
50
+ - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5
51
+ with:
52
+ fetch-depth: 0
53
+ persist-credentials: false
54
+ - uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2
55
+ with:
56
+ enable-cache: false
57
+ - run: uv run camas "$TASK"
58
+ shell: bash
59
+ env:
60
+ TASK: ${{ matrix.task }}
61
+
62
+ build:
63
+ runs-on: ubuntu-latest
64
+ steps:
65
+ - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5
66
+ with:
67
+ fetch-depth: 0
68
+ persist-credentials: false
69
+ - uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2
70
+ with:
71
+ enable-cache: false
72
+ - run: uv run camas build
73
+ - run: unzip -l dist/*.whl | grep -q types_bits/__init__.pyi
74
+ - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
75
+ with:
76
+ name: dist
77
+ path: dist/
78
+
79
+ release:
80
+ if: startsWith(github.ref, 'refs/tags/')
81
+ needs: [check, build]
82
+ runs-on: ubuntu-latest
83
+ environment:
84
+ name: pypi
85
+ url: https://pypi.org/p/types-bits
86
+ permissions:
87
+ id-token: write
88
+ steps:
89
+ - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
90
+ with:
91
+ name: dist
92
+ path: dist/
93
+ - uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2
94
+ with:
95
+ enable-cache: false
96
+ - run: uv publish --trusted-publishing always
@@ -0,0 +1,21 @@
1
+ # Materialized type stubs — regenerate with `camas gen`
2
+ /src/types_bits/*.pyi
3
+
4
+ # Task runner cache
5
+ /.camas/
6
+
7
+ # Benchmark output
8
+ /bench/results/
9
+
10
+ .venv/
11
+ __pycache__/
12
+ *.py[cod]
13
+ dist/
14
+ .pytest_cache/
15
+ .mypy_cache/
16
+ .ruff_cache/
17
+ .pyrefly_cache/
18
+ .zuban_cache/
19
+
20
+ /bench/.work/
21
+ *.tmp
@@ -0,0 +1,13 @@
1
+ {
2
+ "mcpServers": {
3
+ "camas": {
4
+ "type": "stdio",
5
+ "command": "uv",
6
+ "args": [
7
+ "run",
8
+ "camas",
9
+ "mcp"
10
+ ]
11
+ }
12
+ }
13
+ }
@@ -0,0 +1,3 @@
1
+ 3.14
2
+ 3.13
3
+ 3.12
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 JP Hutchins
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.
@@ -0,0 +1,222 @@
1
+ Metadata-Version: 2.4
2
+ Name: types-bits
3
+ Version: 0.1.0
4
+ Summary: Sized integer types: exhaustive Literal unions statically, Annotated bounds at runtime
5
+ Author-email: JP Hutchins <jphutchins@gmail.com>
6
+ License-Expression: MIT
7
+ License-File: LICENSE
8
+ Requires-Python: >=3.12
9
+ Provides-Extra: rt
10
+ Requires-Dist: annotated-types>=0.7; extra == 'rt'
11
+ Description-Content-Type: text/markdown
12
+
13
+ # types-bits
14
+
15
+ `u1`..`u10` and `i1`..`i10` as fully materialized `Literal` unions. Generated `.pyi`, zero
16
+ runtime cost, exhaustively checked.
17
+
18
+ ```python
19
+ from typing import TYPE_CHECKING
20
+
21
+ if TYPE_CHECKING:
22
+ from types_bits import u4
23
+
24
+ x: u4 = 15 # ok
25
+ y: u4 = 16 # error, on every checker
26
+ ```
27
+
28
+ `camas` runs the gate; `camas --list` enumerates it. CI fans the same tree out over a
29
+ runner axis, with both matrix axes emitted from `tasks.py`. `camas bench` and
30
+ `camas bench_shapes` regenerate everything below into `bench/results/`.
31
+
32
+ ## Runtime cost
33
+
34
+ Zero. The stub is a [PEP 484][pep484] `.pyi` behind `TYPE_CHECKING`. `import types_bits`
35
+ loads two modules and no stdlib (`tests/test_runtime.py`). Every number below is
36
+ type-checker wall clock, per check run.
37
+
38
+ ## Check cost by width
39
+
40
+ Seconds, cold, best of one, Python 3.14.1 / WSL2. Encoding `flat`
41
+ (`type uN = Literal[0, ..., 2**N-1]`), declaring `u1..uN` (so a probe at N bits holds 2^(N+1)-2 literals) and widening each
42
+ into the next.
43
+
44
+ | bits | mypy | pyright | basedpyright | ty | pyrefly | zuban |
45
+ |---:|---:|---:|---:|---:|---:|---:|
46
+ | 8 | 2.30 | 0.68 | 0.71 | 0.08 | 0.32 | 0.10 |
47
+ | 10 | 1.98 | 0.75 | 0.83 | 0.09 | 0.38 | 0.12 |
48
+ | 12 | 2.22 | 0.79 | 0.85 | 0.11 | 0.31 | 0.33 |
49
+ | 14 | 3.10 | 0.59 | 0.64 | 0.17 | 0.27 | 3.23 |
50
+ | 16 | 3.85 | 0.70 | 0.79 | 0.41 | 0.46 | 51.26 |
51
+
52
+ Shipped library (`u1..u10` + `i1..i10`): mypy 2.05, basedpyright 1.43,
53
+ pyright 1.13, pyrefly 0.24, zuban 0.08, ty 0.06 — matching the `type uN = int` control.
54
+
55
+ ```mermaid
56
+ xychart-beta
57
+ title "16 bits"
58
+ x-axis [zuban, mypy, basedpyright, pyright, pyrefly, ty]
59
+ y-axis "seconds" 0 --> 55
60
+ bar [51.26, 3.85, 0.79, 0.70, 0.46, 0.41]
61
+ ```
62
+
63
+ zuban, same sweep — roughly 16x per extra 2 bits past 12:
64
+
65
+ ```mermaid
66
+ xychart-beta
67
+ title "zuban vs width"
68
+ x-axis "bits" [8, 10, 12, 14, 16]
69
+ y-axis "seconds" 0 --> 55
70
+ line [0.10, 0.12, 0.33, 3.23, 51.26]
71
+ ```
72
+
73
+ The other five, 0–4s axis. Rising line is mypy; flat cluster is pyright, basedpyright,
74
+ pyrefly, ty:
75
+
76
+ ```mermaid
77
+ xychart-beta
78
+ title "mypy, pyright, basedpyright, pyrefly, ty"
79
+ x-axis "bits" [8, 10, 12, 14, 16]
80
+ y-axis "seconds" 0 --> 4
81
+ line [2.30, 1.98, 2.22, 3.10, 3.85]
82
+ line [0.68, 0.75, 0.79, 0.59, 0.70]
83
+ line [0.71, 0.83, 0.85, 0.64, 0.79]
84
+ line [0.32, 0.38, 0.31, 0.27, 0.46]
85
+ line [0.08, 0.09, 0.11, 0.17, 0.41]
86
+ ```
87
+
88
+ ## Fixed vs marginal cost
89
+
90
+ `u(N-1)` and `u(N)` declared in every row; only the use count varies. Italic rows are the
91
+ `type uN = int` control.
92
+
93
+ 10 bits — indistinguishable from `int`:
94
+
95
+ | uses | mypy | pyright | basedpyright | ty | pyrefly | zuban |
96
+ |---|---:|---:|---:|---:|---:|---:|
97
+ | declared, unused | 1.64 | 0.55 | 0.60 | 0.05 | 0.23 | 0.10 |
98
+ | *control* | *1.64* | *0.60* | *0.59* | *0.06* | *0.23* | *0.08* |
99
+ | 1 widening | 1.50 | 0.56 | 0.64 | 0.06 | 0.26 | 0.09 |
100
+ | 10 widenings | 1.75 | 0.65 | 0.90 | 0.08 | 0.29 | 0.20 |
101
+
102
+ 16 bits — 98,304 literals:
103
+
104
+ | uses | mypy | pyright | basedpyright | ty | pyrefly | zuban |
105
+ |---|---:|---:|---:|---:|---:|---:|
106
+ | declared, unused | 2.79 | 0.66 | 0.64 | 0.18 | 0.29 | 0.20 |
107
+ | *control* | *1.57* | *0.62* | *0.59* | *0.05* | *0.28* | *0.08* |
108
+ | 1 assignment | 2.77 | 0.62 | 0.65 | 0.19 | 0.33 | 0.21 |
109
+ | 1 widening | 2.81 | 0.64 | 0.65 | 0.22 | 0.37 | **35.66** |
110
+ | 10 widenings | 3.31 | 0.56 | 0.58 | 0.20 | 0.31 | **timeout (>180s)** |
111
+
112
+ - Declaration: fixed per check run, ~linear in literals (~12 µs/literal on mypy). Not per
113
+ importing file, not per use.
114
+ - Assignment: free. Enumerated membership is a hash lookup.
115
+ - Widening: free except zuban at width.
116
+
117
+ ## Widening
118
+
119
+ Cost tracks the *narrow* operand, not the wide one. Wide side fixed at `u16`, one widening:
120
+
121
+ | narrow | zuban | mypy | ty |
122
+ |---|---:|---:|---:|
123
+ | u1 | 0.18 | 2.54 | 0.20 |
124
+ | u4 | 0.18 | 2.46 | 0.15 |
125
+ | u8 | 0.16 | 2.67 | 0.21 |
126
+ | u12 | 0.76 | 2.50 | 0.17 |
127
+ | u15 | 35.21 | 2.80 | 0.18 |
128
+
129
+ Both operands must be large. At the 10-bit ceiling the worst case (`u9` → `u10`) is 0.09s.
130
+ Annotating a boundary at one width, so callers assign literals rather than widen between
131
+ adjacent wide aliases, avoids the shape entirely.
132
+
133
+ ## Encodings
134
+
135
+ | encoding | form | result |
136
+ |---|---|---|
137
+ | `flat` | `Literal[0, ..., 2**N-1]` | shipped; fastest, portable |
138
+ | `nested` | `Literal[u9, 512, ...]` | legal per [PEP 586][pep586]; pyrefly rejects past ~10 levels (`Invalid type inside literal, int`, 1 error at 10 bits → 7 at 16), ty at 16 |
139
+ | `union` | `u9 \| Literal[512, ...]` | ty 44.24s at 14 bits vs 0.17s flat |
140
+ | `annotated` | `Annotated[Literal[...], Ge, Le]` | tracks `flat` within noise to 14 bits |
141
+ | `opaque` | `int` | control |
142
+
143
+ ```mermaid
144
+ xychart-beta
145
+ title "ty at 14 bits by encoding"
146
+ x-axis [union, nested, annotated, flat]
147
+ y-axis "seconds" 0 --> 46
148
+ bar [44.24, 0.23, 0.22, 0.17]
149
+ ```
150
+
151
+ ## Runtime tier
152
+
153
+ [PEP 562][pep562] module `__getattr__` resolves the same names to
154
+ `Annotated[int, Ge(lo), Le(hi)]` ([PEP 593][pep593]), the shape
155
+ [`annotated-types`][at] consumers read. `tests/test_runtime.py` pins it against the static
156
+ bounds for all 20 widths. Needs the `rt` extra; `annotated_types` imports on first
157
+ attribute access.
158
+
159
+ ```python
160
+ from pydantic import TypeAdapter
161
+ from types_bits import u8 # Annotated[int, Ge(0), Le(255)] at runtime
162
+
163
+ TypeAdapter(u8).validate_python(256) # ValidationError
164
+ ```
165
+
166
+ ## Prior art
167
+
168
+ [`range-typed-integers`][rti] defines `u8 = NewType('u8', Annotated[int, ValueRange(0, 255)])`
169
+ for the byte widths `u8`..`u64` / `i8`..`i64`.
170
+
171
+ | | range-typed-integers | types-bits |
172
+ |---|---|---|
173
+ | carrier | `NewType` over `Annotated[int, ValueRange]` | `Literal` enumeration |
174
+ | bound enforced by | runtime `u8_checked()` / `check_int()`, raising `IntegerBoundError` | the type checker |
175
+ | `a: u8 = 12` | mypy error — `int` is not `u8`; requires `u8(12)` | ok |
176
+ | `a: u8 = 900` | mypy error, *identical* to the line above | error, and distinguished |
177
+ | `u8(900)` | accepted statically | n/a |
178
+ | widths | u8..u64 | u1..u10 |
179
+
180
+ Verified against every checker in the gate: mypy reports the same `Incompatible types in assignment
181
+ (expression has type "int", variable has type "u8")` on the in-range and out-of-range
182
+ lines alike. The range is metadata no checker reads ([PEP 746][pep746] would not change
183
+ this). `ValueRange` is O(1) per type, so it reaches u64; enumeration is O(2^N), so it
184
+ stops at u10.
185
+
186
+ ## PEPs
187
+
188
+ No PEP provides bounded integers.
189
+
190
+ | PEP | Status | Relevance |
191
+ |---|---|---|
192
+ | [586 – Literal Types][pep586] | Final | The mechanism. Calls `Literal` insufficient for numpy-style numeric code and defers integer generics. Permits the `nested` form pyrefly rejects. |
193
+ | [593 – `Annotated`][pep593] | Final | Metadata channel for the runtime tier. |
194
+ | [695 – Type Parameter Syntax][pep695] | Final | `type uN = ...` in the stub. |
195
+ | [561 – Packaging Type Information][pep561] | Final | `py.typed`; why the stub ships in the wheel. |
196
+ | [562 – Module `__getattr__`][pep562] | Final | One name, two tiers. |
197
+ | [649][pep649] / [749][pep749] – Deferred Annotations | Final (3.14) | Guarded import gets cheaper. |
198
+ | [746 – Type checking `Annotated` metadata][pep746] | Draft, targets 3.15 | Lets a checker verify metadata suits its type. Does not make any checker enforce `Ge`/`Le`. |
199
+
200
+ - [python/typing#554, "Support for Range Types?"][i554] — closed. Requested an Ada-style
201
+ `RangeType`.
202
+ - [discuss.python.org, "Use type hinting with bound constraints, e.g. `int[0:15]`"][thread]
203
+ — no resolution. Objections: most constraints are not statically validatable; ranges need
204
+ new type-system machinery; arithmetic is not closed. Landed on `Annotated` +
205
+ `annotated-types` with runtime validation.
206
+
207
+ `fixtures/reject.py` covers the arithmetic case: every checker rejects `u4 + u4` where a
208
+ `u4` is required. mypy widens the sum to `int`; ty and zuban widen it to `Literal[0, ..., 30]`.
209
+
210
+ [pep484]: https://peps.python.org/pep-0484/
211
+ [pep561]: https://peps.python.org/pep-0561/
212
+ [pep562]: https://peps.python.org/pep-0562/
213
+ [pep586]: https://peps.python.org/pep-0586/
214
+ [pep593]: https://peps.python.org/pep-0593/
215
+ [pep649]: https://peps.python.org/pep-0649/
216
+ [pep695]: https://peps.python.org/pep-0695/
217
+ [pep746]: https://peps.python.org/pep-0746/
218
+ [pep749]: https://peps.python.org/pep-0749/
219
+ [at]: https://github.com/annotated-types/annotated-types
220
+ [i554]: https://github.com/python/typing/issues/554
221
+ [thread]: https://discuss.python.org/t/use-type-hinting-with-bound-constraints-e-g-int-0-15/38820
222
+ [rti]: https://github.com/theCapypara/range-typed-integers