Metadata-Version: 2.5
Name: dataclass-values
Version: 0.1.0
Summary: Value(1.2, "m", low=0.6, high=1.85, source="..."): a float, int, str or tuple that remembers where it came from, plus a Space derived from any dataclass of them.
Project-URL: Source, https://github.com/zacharyyamaoka/dataclass_values
Author: Zach Yamaoka
License-Expression: MIT
License-File: LICENSE
Keywords: configuration,dataclasses,provenance,sampling,units
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Typing :: Typed
Requires-Python: >=3.11
Provides-Extra: test
Requires-Dist: pytest>=8; extra == 'test'
Description-Content-Type: text/markdown

# dataclass-values

A number that remembers where it came from, and still is the number. Stdlib only.

```python
from dataclasses import dataclass
from dataclass_values import Basis, Normal, Space, Value, plain

@dataclass(frozen=True)
class Belt:
    width_m: float = Value(1.2, "m", low=0.6, high=1.85, basis=Basis.CHOSEN, source="site survey")
    speed_mps: float = Value(0.5, "m/s", dist=Normal(0.5, 0.05), low=0.2, high=0.8)

belt = Belt()
belt.width_m * 2                       # 2.4, a plain float
Space(belt).sample()                   # a Belt, every ranged field redrawn, basis=SAMPLED
Space(belt).violations(Belt(9.0, 0.5)) # ["width_m=9.0 outside [0.6, 1.85]"]
plain(belt)                            # the same Belt with bare floats, for ROS / JSON
```

## Why

The syntax applies to any domain, so it is its own package: robot_msgs is one vocabulary built on it,
py_dataclass_viewer is one app built on it (Zach, 2026-11-10: "the pydataclass syntax can technically
apply to any domain! robot_msgs is just a particular domain.").

Zach's rule (Oct 5 2026, do not re-litigate):

- `Value(1.2, "m", low=0.6, high=1.85, basis=Basis.CHOSEN, source="...")` IS the builtin: a
  float/int/str/tuple subclass. "I definitely don't want to have to do `width_m.value`."
- Spaces are derived: `Space(obj).sample()` returns the same class, every ranged value marked
  `basis=SAMPLED`. No second `PoseSpace` class to drift from the one it shadows.
- `plain()` strips the notes at a runtime boundary. "We can use Value wherever we use a raw value
  normally and we can always strip it back to the plain value."

## API

- `Value(raw, unit=None, *, low, high, step, choices, dist, basis, source, measured_by, notes, date_accessed)`
  returns `FloatValue | IntValue | StrValue | TupleValue` (also exported). Refused at construction when
  outside `[low, high]` (tuples elementwise) or not in `choices`. `v` is a short alias.
- `meta(x)` the evidence dict or `None`; `plain(obj)` strips every `Value`, through dataclasses,
  lists and tuples.
- `Basis`: `CHOSEN DATASHEET MARKET ASSUMED MEASURED SAMPLED` (a `StrEnum`).
- `Uniform`, `Normal`, `LogUniform`, `Choice`: per-value distributions with `.draw(rng)`.
- `Space(instance)`: `.sample(rng=None)`, `.contains(other)`, `.violations(other)` (dotted field
  paths). Subclasses add bounds by overriding the hooks `extra_draw(drawn, rng)` and
  `extra_violations(other)`; robot_msgs uses them for numpy array bounds.

## Tests

```bash
env -u VIRTUAL_ENV uv run --isolated --extra test python -m pytest -q
```
