Skip to main content

types-bits

u1..u10 and i1..i10 as fully materialized Literal unions. Generated .pyi, zero runtime cost, exhaustively checked.

from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from types_bits import u4

x: u4 = 15  # ok
y: u4 = 16  # error, on every checker

camas runs the gate; camas --list enumerates it. CI fans the same tree out over a runner axis, with both matrix axes emitted from tasks.py. camas bench and camas bench_shapes regenerate everything below into bench/results/.

Runtime cost

Zero. The stub is a PEP 484 .pyi behind TYPE_CHECKING. import types_bits loads two modules and no stdlib (tests/test_runtime.py). Every number below is type-checker wall clock, per check run.

Check cost by width

Seconds, cold, best of one, Python 3.14.1 / WSL2. Encoding flat (uN: TypeAlias = Literal[0, ..., 2**N-1]), declaring u1..uN (so a probe at N bits holds 2^(N+1)-2 literals) and widening each into the next.

bits mypy pyright basedpyright ty pyrefly zuban
8 1.54 0.60 0.72 0.07 0.27 0.10
10 1.53 0.62 0.65 0.07 0.26 0.11
12 1.58 0.59 0.70 0.11 0.32 0.28
14 1.63 0.54 0.63 0.20 0.26 3.25
16 2.58 0.55 0.60 0.34 0.39 48.73

Shipped library (u1..u10 + i1..i10): mypy 1.51, basedpyright 1.28, pyright 1.08, pyrefly 0.23, zuban 0.08, ty 0.06 — matching the uN: TypeAlias = int control.

xychart-beta
    title "16 bits"
    x-axis [zuban, mypy, basedpyright, pyright, pyrefly, ty]
    y-axis "seconds" 0 --> 55
    bar [48.73, 2.58, 0.60, 0.55, 0.39, 0.34]

zuban, same sweep — 12x then 15x per extra 2 bits past 12:

xychart-beta
    title "zuban vs width"
    x-axis "bits" [8, 10, 12, 14, 16]
    y-axis "seconds" 0 --> 55
    line [0.10, 0.11, 0.28, 3.25, 48.73]

The other five, 0–3s axis. Rising line is mypy; flat cluster is pyright, basedpyright, pyrefly, ty:

xychart-beta
    title "mypy, pyright, basedpyright, pyrefly, ty"
    x-axis "bits" [8, 10, 12, 14, 16]
    y-axis "seconds" 0 --> 3
    line [1.54, 1.53, 1.58, 1.63, 2.58]
    line [0.60, 0.62, 0.59, 0.54, 0.55]
    line [0.72, 0.65, 0.70, 0.63, 0.60]
    line [0.27, 0.26, 0.32, 0.26, 0.39]
    line [0.07, 0.07, 0.11, 0.20, 0.34]

Fixed vs marginal cost

u(N-1) and u(N) declared in every row; only the use count varies. Italic rows are the uN: TypeAlias = int control.

10 bits — indistinguishable from int:

uses mypy pyright basedpyright ty pyrefly zuban
declared, unused 1.23 0.48 0.64 0.03 0.22 0.07
control 1.39 0.48 0.65 0.06 0.22 0.08
1 widening 1.24 0.51 0.60 0.05 0.22 0.10
10 widenings 1.20 0.53 0.59 0.07 0.25 0.16

16 bits — 98,304 literals:

uses mypy pyright basedpyright ty pyrefly zuban
declared, unused 2.01 0.53 0.57 0.22 0.29 0.20
control 1.19 0.47 0.55 0.05 0.21 0.08
1 assignment 1.96 0.56 0.61 0.21 0.33 0.21
1 widening 1.94 0.52 0.60 0.21 0.28 34.90
10 widenings 2.19 0.56 0.65 0.26 0.29 timeout (>180s)
  • Declaration: fixed per check run, ~linear in literals (~8 µs/literal on mypy). Not per importing file, not per use.
  • Assignment: free. Enumerated membership is a hash lookup.
  • Widening: free except zuban at width.

Widening

Cost tracks the narrow operand, not the wide one. Wide side fixed at u16, one widening:

narrow zuban mypy ty
u1 0.18 2.54 0.20
u4 0.18 2.46 0.15
u8 0.16 2.67 0.21
u12 0.76 2.50 0.17
u15 35.21 2.80 0.18

Measured under the PEP 695 spelling; no task reproduces this sweep.

Both operands must be large. At the 10-bit ceiling the worst case (u9 → u10) is 0.10s. Annotating a boundary at one width, so callers assign literals rather than widen between adjacent wide aliases, avoids the shape entirely.

Encodings

encoding form result
flat Literal[0, ..., 2**N-1] shipped; fastest, portable
nested Literal[u9, 512, ...] legal per PEP 586; pyrefly rejects past ~10 levels (Invalid type inside literal, int, 1 error at 10 bits → 7 at 16), ty at 16
union u9 | Literal[512, ...] ty 44.24s at 14 bits vs 0.17s flat
annotated Annotated[Literal[...], Ge, Le] tracks flat within noise to 14 bits
opaque int control

Measured under the PEP 695 spelling, before the PEP 613 switch; camas bench_full regenerates.

xychart-beta
    title "ty at 14 bits by encoding"
    x-axis [union, nested, annotated, flat]
    y-axis "seconds" 0 --> 46
    bar [44.24, 0.23, 0.22, 0.17]

Runtime tier

PEP 562 module __getattr__ resolves the same names to Annotated[int, Ge(lo), Le(hi)] (PEP 593), the shape annotated-types consumers read. tests/test_runtime.py pins it against the static bounds for all 20 widths. Needs the rt extra; annotated_types imports on first attribute access.

from pydantic import TypeAdapter
from types_bits import u8  # Annotated[int, Ge(0), Le(255)] at runtime

TypeAdapter(u8).validate_python(256)  # ValidationError

Prior art

range-typed-integers defines u8 = NewType('u8', Annotated[int, ValueRange(0, 255)]) for the byte widths u8..u64 / i8..i64.

range-typed-integers types-bits
carrier NewType over Annotated[int, ValueRange] Literal enumeration
bound enforced by runtime u8_checked() / check_int(), raising IntegerBoundError the type checker
a: u8 = 12 mypy error — int is not u8; requires u8(12) ok
a: u8 = 900 mypy error, identical to the line above error, and distinguished
u8(900) accepted statically n/a
widths u8..u64 u1..u10

Verified against every checker in the gate: mypy reports the same Incompatible types in assignment (expression has type "int", variable has type "u8") on the in-range and out-of-range lines alike. The range is metadata no checker reads (PEP 746 would not change this). ValueRange is O(1) per type, so it reaches u64; enumeration is O(2^N), so it stops at u10.

PEPs

No PEP provides bounded integers.

PEP Status Relevance
586 – Literal Types Final The mechanism. Calls Literal insufficient for numpy-style numeric code and defers integer generics. Permits the nested form pyrefly rejects.
593 – Annotated Final Metadata channel for the runtime tier.
613 – Explicit Type Aliases Final uN: TypeAlias = ... in the stub; why the floor is 3.10.
695 – Type Parameter Syntax Final type uN = ... reads better, but mypy rejects a type statement under --python-version 3.11 — fatally, errors prevented further checking — so it would pin the floor at 3.12. The other five accept it at a 3.10 target.
561 – Packaging Type Information Final py.typed; why the stub ships in the wheel.
562 – Module __getattr__ Final One name, two tiers.
649 / 749 – Deferred Annotations Final (3.14) Guarded import gets cheaper.
746 – Type checking Annotated metadata Draft, targets 3.15 Lets a checker verify metadata suits its type. Does not make any checker enforce Ge/Le.

fixtures/reject.py covers the arithmetic case: every checker rejects u4 + u4 where a u4 is required. mypy widens the sum to int; ty and zuban widen it to Literal[0, ..., 30].

Metadata

Release files for types-bits 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for types-bits 0.2.0
File Size Uploaded Provenance Actions
types_bits-0.2.0.tar.gz 119.3 kB Without provenance Details for types_bits-0.2.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for types-bits 0.2.0
File Interpreter ABI Platform Actions
types_bits-0.2.0-py3-none-any.whl Python 3 none any Details for types_bits-0.2.0-py3-none-any.whl

Total release size: 133.1 kB

Release files / types_bits-0.2.0.tar.gz

Download URL types_bits-0.2.0.tar.gz
Size 119.3 kB
Tags Source
SHA-256 checksum
How to use checksums
0e9a45d6614551043880e5f47c70981b39d78bc1c2a018d1ad4324bccf60079c
BLAKE2b-256 checksum
How to use checksums
b3f62a6f942fd4b9324fa2cead74f7b85a21ccbdd0ddddce6de87bf00ac528e7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.7 {"installer":{"name":"uv","version":"0.12.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / types_bits-0.2.0-py3-none-any.whl

Download URL types_bits-0.2.0-py3-none-any.whl
Size 13.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
810b8e773b0ffd80fb589185abc21641fddba234a8ebbb7ce55f3f5d6dc8cf6d
BLAKE2b-256 checksum
How to use checksums
a541cf41a0415d18b66378abc956034e417acccef3462888c173c8d53ed4ff87
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.7 {"installer":{"name":"uv","version":"0.12.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Security for types-bits 0.2.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page