dataclass-values 0.1.0__py3-none-any.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,38 @@
1
+ """dataclass_values: Value(...) carries a number's evidence; Space(obj) samples and checks it.
2
+
3
+ Domain-free and stdlib-only. See README.md.
4
+ """
5
+
6
+ from .space import Space
7
+ from .values import (
8
+ Basis,
9
+ Choice,
10
+ FloatValue,
11
+ IntValue,
12
+ LogUniform,
13
+ Normal,
14
+ StrValue,
15
+ TupleValue,
16
+ Uniform,
17
+ Value,
18
+ meta,
19
+ plain,
20
+ v,
21
+ )
22
+
23
+ __all__ = [
24
+ "Basis",
25
+ "Choice",
26
+ "FloatValue",
27
+ "IntValue",
28
+ "LogUniform",
29
+ "Normal",
30
+ "Space",
31
+ "StrValue",
32
+ "TupleValue",
33
+ "Uniform",
34
+ "Value",
35
+ "meta",
36
+ "plain",
37
+ "v",
38
+ ]
File without changes
@@ -0,0 +1,126 @@
1
+ """Space: an instance seen as a space (gymnasium-style), derived from the instance itself.
2
+
3
+ from dataclass_values import Space, Value
4
+ pick = Pose(position=Point(x=Value(0.5, "m", low=0.4, high=0.6), y=0.0, z=0.3))
5
+ Space(pick).sample(rng) # -> a Pose, x drawn in [0.4, 0.6], marked basis=SAMPLED
6
+ Space(pick).contains(measured) # -> the acceptable range in a test
7
+ Space(pick).violations(measured) # -> ["position.x=0.7 outside [0.4, 0.6]"]
8
+
9
+ WHY the same class and a derived space (Zach, Oct 5 2026, "do not re-litigate"): a second
10
+ `PoseSpace` / `CheckedBelt` class "drifts" from the message it shadows. So a field carries its own
11
+ range as a `Value`, which IS the builtin float/int/str/tuple, and `Space(obj)` is a view over the
12
+ instance whose `sample()` returns the ordinary class. Nothing is discarded: the authored instance is
13
+ untouched, and each drawn value keeps its unit, range and source, marked basis=SAMPLED. Call
14
+ `plain()` only at a runtime boundary where you choose to drop the notes.
15
+
16
+ Subclasses add kinds of bound the scalar core knows nothing about (robot_msgs bounds numpy array
17
+ fields) by overriding the two hooks `extra_draw` and `extra_violations`.
18
+ """
19
+
20
+ from __future__ import annotations
21
+
22
+ import random
23
+ from dataclasses import fields, is_dataclass, replace
24
+ from typing import Any
25
+
26
+ from .values import META, Basis, TupleValue, Value, meta, plain, within
27
+
28
+
29
+ class Space:
30
+ """Every `Value` in `instance` with a dist, choices or [low, high] can be drawn and checked."""
31
+
32
+ def __init__(self, instance: Any) -> None:
33
+ self.instance = instance
34
+
35
+ # ── sampling ──────────────────────────────────────────────────────────────
36
+
37
+ def sample(self, rng: random.Random | None = None):
38
+ """The same class as the instance, every ranged Value redrawn (basis=SAMPLED)."""
39
+ rng = rng or random.Random()
40
+ return self.extra_draw(self._draw(self.instance, rng), rng)
41
+
42
+ def extra_draw(self, drawn, rng: random.Random):
43
+ """Hook: take the already-drawn instance and redraw whatever else this space bounds."""
44
+ return drawn
45
+
46
+ def _draw(self, x, rng):
47
+ if is_dataclass(x) and not isinstance(x, type):
48
+ return replace(
49
+ x, **{f.name: self._draw(getattr(x, f.name), rng) for f in fields(x) if f.init}
50
+ )
51
+ if isinstance(x, TupleValue) and x.dist is None and x.choices is None:
52
+ return x # a vector is one claim: drawn only through its own choices or dist
53
+ if isinstance(x, (list, tuple)) and not isinstance(x, TupleValue):
54
+ return type(x)(self._draw(i, rng) for i in x)
55
+ m = meta(x)
56
+ if not m or (
57
+ m["dist"] is None and m["choices"] is None and (m["low"] is None or m["high"] is None)
58
+ ):
59
+ return x
60
+ if m["dist"] is not None:
61
+ drawn = m["dist"].draw(rng)
62
+ elif m["choices"] is not None:
63
+ drawn = rng.choice(m["choices"])
64
+ else:
65
+ drawn = rng.uniform(m["low"], m["high"])
66
+ if isinstance(drawn, (int, float)) and not isinstance(drawn, bool):
67
+ if m["low"] is not None:
68
+ drawn = max(m["low"], drawn)
69
+ if m["high"] is not None:
70
+ drawn = min(m["high"], drawn)
71
+ if isinstance(x, int):
72
+ drawn = round(drawn)
73
+ if m["step"]:
74
+ base = m["low"] if m["low"] is not None else 0
75
+ drawn = base + round((drawn - base) / m["step"]) * m["step"]
76
+ drawn = round(drawn) if isinstance(x, int) else drawn
77
+ return Value(
78
+ drawn,
79
+ m["unit"],
80
+ **{
81
+ **{k: m[k] for k in META if k != "unit"},
82
+ "basis": Basis.SAMPLED,
83
+ "notes": f"sampled from {x!r}" + (f"; {m['notes']}" if m["notes"] else ""),
84
+ },
85
+ )
86
+
87
+ # ── checking ──────────────────────────────────────────────────────────────
88
+
89
+ def contains(self, other: Any) -> bool:
90
+ """True when every ranged field of `other` lies inside this space."""
91
+ return not self.violations(other)
92
+
93
+ def violations(self, other: Any) -> list[str]:
94
+ """One line per field of `other` outside its range: ``position.x=0.7 outside [0.4, 0.6]``"""
95
+ if type(other) is not type(self.instance):
96
+ raise TypeError(
97
+ f"a {type(self.instance).__name__} space cannot hold a {type(other).__name__}"
98
+ )
99
+ found: list[str] = []
100
+ self._check(self.instance, other, "", found)
101
+ found.extend(self.extra_violations(other))
102
+ return found
103
+
104
+ def extra_violations(self, other: Any) -> list[str]:
105
+ """Hook: one line per way `other` breaks a bound this space adds beyond the scalar ones."""
106
+ return []
107
+
108
+ def _check(self, ranged, other, path, found):
109
+ if is_dataclass(ranged) and not isinstance(ranged, type):
110
+ for f in fields(ranged):
111
+ self._check(
112
+ getattr(ranged, f.name), getattr(other, f.name), f"{path}{f.name}.", found
113
+ )
114
+ return
115
+ m = meta(ranged)
116
+ if not m:
117
+ return
118
+ name = path.rstrip(".")
119
+ if (
120
+ m["low"] is not None
121
+ and m["high"] is not None
122
+ and not within(other, m["low"], m["high"])
123
+ ):
124
+ found.append(f"{name}={plain(other)!r} outside [{m['low']}, {m['high']}]")
125
+ elif m["choices"] is not None and other not in m["choices"]:
126
+ found.append(f"{name}={plain(other)!r} not one of {m['choices']}")
@@ -0,0 +1,317 @@
1
+ """A number (or string) that remembers where it came from, and still is the number.
2
+
3
+ width_m = Value(1.2, "m", low=0.6, high=1.85, basis=Basis.CHOSEN, source="…")
4
+ v = Value # optional short alias; the real name is the readable one
5
+
6
+ WHY `Value(...)` reads like a type cast (Zach, Oct 5 2026): "v should be v = Value, v as Value, it's
7
+ like a type casting; the actual thing needs a readable name". `Value(1.2, "m")` returns a
8
+ FloatValue, so `isinstance(x, Value)` and `isinstance(x, float)` are both true.
9
+ WHY a float subclass and not a wrapper: "I definitely don't want to have to do width_m.value". The
10
+ class keeps `width_m: float`, `belt.width_m * 2` is a plain 2.4, ROS/numpy see a float, and the
11
+ evidence rides along.
12
+ WHY the value is first and unnamed, unit optionally second: both are obvious at a glance; everything
13
+ else is keyword-only, so the old positional puzzle (est(…, C, 0.6, 1.85)) cannot come back.
14
+ WHY arithmetic returns a plain number: a derived number is a new claim and must not inherit the
15
+ original's source.
16
+ WHY a tuple carrier: a vector (xyz, a limit pair) is one claim with one source;
17
+ `Value((0.0, 0.0, 0.3), "m")` is still a tuple, and `low`/`high` given as tuples are checked
18
+ elementwise.
19
+ WHY its own package (Zach, Nov 10 2026): "the pydataclass syntax can technically apply to any
20
+ domain! robot_msgs is just a particular domain." Domain-free, stdlib only.
21
+ """
22
+
23
+ from __future__ import annotations
24
+
25
+ import math
26
+ import random
27
+ from dataclasses import dataclass, fields, is_dataclass, replace
28
+ from enum import StrEnum
29
+ from typing import overload
30
+
31
+
32
+ class Basis(StrEnum):
33
+ """Where a number comes from (CHOSEN..ASSUMED match bam_facility_model.estimate.Basis)."""
34
+
35
+ CHOSEN = "chosen"
36
+ DATASHEET = "datasheet"
37
+ MARKET = "market"
38
+ ASSUMED = "assumed"
39
+ MEASURED = "measured"
40
+ SAMPLED = "sampled" # drawn by Space.sample() from this value's range, choices or distribution
41
+
42
+
43
+ # --- distributions: per-value, first class ---
44
+ @dataclass(frozen=True)
45
+ class Uniform:
46
+ low: float
47
+ high: float
48
+
49
+ def draw(self, rng: random.Random) -> float:
50
+ return rng.uniform(self.low, self.high)
51
+
52
+
53
+ @dataclass(frozen=True)
54
+ class Normal:
55
+ mean: float
56
+ std: float
57
+
58
+ def draw(self, rng: random.Random) -> float:
59
+ return rng.gauss(self.mean, self.std)
60
+
61
+
62
+ @dataclass(frozen=True)
63
+ class LogUniform:
64
+ low: float
65
+ high: float
66
+
67
+ def draw(self, rng: random.Random) -> float:
68
+ return math.exp(rng.uniform(math.log(self.low), math.log(self.high)))
69
+
70
+
71
+ @dataclass(frozen=True)
72
+ class Choice:
73
+ """A discrete distribution: options with optional weights."""
74
+
75
+ options: tuple
76
+ weights: tuple | None = None
77
+
78
+ def draw(self, rng: random.Random):
79
+ return rng.choices(self.options, weights=self.weights)[0]
80
+
81
+
82
+ Distribution = Uniform | Normal | LogUniform | Choice
83
+
84
+ META = (
85
+ "unit",
86
+ "low",
87
+ "high",
88
+ "step",
89
+ "choices",
90
+ "dist",
91
+ "basis",
92
+ "source",
93
+ "measured_by",
94
+ "notes",
95
+ "date_accessed",
96
+ )
97
+
98
+
99
+ def within(raw, low, high) -> bool:
100
+ """low <= raw <= high; for a tuple raw, elementwise (a scalar bound applies to each element)."""
101
+ if isinstance(raw, tuple):
102
+ lows = low if isinstance(low, tuple) else (low,) * len(raw)
103
+ highs = high if isinstance(high, tuple) else (high,) * len(raw)
104
+ if len(lows) != len(raw) or len(highs) != len(raw):
105
+ raise ValueError(f"{raw!r} has {len(raw)} elements but low={low!r}, high={high!r}")
106
+ return all(lo <= x <= hi for lo, x, hi in zip(lows, raw, highs, strict=True))
107
+ return low <= raw <= high
108
+
109
+
110
+ def _rebuild(cls, raw, meta):
111
+ meta = dict(meta)
112
+ unit = meta.pop("unit") # unit is positional-only in the constructor
113
+ return cls(raw, unit, **meta)
114
+
115
+
116
+ class Value:
117
+ """`Value(raw, unit=None, *, low, high, step, choices, dist, basis, source, ...)`.
118
+
119
+ Calling Value(...) picks the carrier for the raw type (FloatValue / IntValue / StrValue /
120
+ TupleValue), each of which subclasses both Value and the builtin. bool and None cannot carry
121
+ evidence: Python forbids subclassing them.
122
+ """
123
+
124
+ _base: type = object
125
+
126
+ @overload
127
+ def __new__(
128
+ cls,
129
+ raw: int,
130
+ unit: str | None = None,
131
+ /,
132
+ *,
133
+ low: float | None = None,
134
+ high: float | None = None,
135
+ step: float | None = None,
136
+ choices: tuple | None = None,
137
+ dist: Distribution | None = None,
138
+ basis: Basis | None = None,
139
+ source: str = "",
140
+ measured_by: str = "",
141
+ notes: str = "",
142
+ date_accessed: str = "",
143
+ ) -> IntValue: ...
144
+
145
+ @overload
146
+ def __new__(
147
+ cls,
148
+ raw: float,
149
+ unit: str | None = None,
150
+ /,
151
+ *,
152
+ low: float | None = None,
153
+ high: float | None = None,
154
+ step: float | None = None,
155
+ choices: tuple | None = None,
156
+ dist: Distribution | None = None,
157
+ basis: Basis | None = None,
158
+ source: str = "",
159
+ measured_by: str = "",
160
+ notes: str = "",
161
+ date_accessed: str = "",
162
+ ) -> FloatValue: ...
163
+
164
+ @overload
165
+ def __new__(
166
+ cls,
167
+ raw: str,
168
+ unit: str | None = None,
169
+ /,
170
+ *,
171
+ low: None = None,
172
+ high: None = None,
173
+ step: None = None,
174
+ choices: tuple | None = None,
175
+ dist: Distribution | None = None,
176
+ basis: Basis | None = None,
177
+ source: str = "",
178
+ measured_by: str = "",
179
+ notes: str = "",
180
+ date_accessed: str = "",
181
+ ) -> StrValue: ...
182
+
183
+ @overload
184
+ def __new__(
185
+ cls,
186
+ raw: tuple,
187
+ unit: str | None = None,
188
+ /,
189
+ *,
190
+ low: float | tuple | None = None,
191
+ high: float | tuple | None = None,
192
+ step: float | None = None,
193
+ choices: tuple | None = None,
194
+ dist: Distribution | None = None,
195
+ basis: Basis | None = None,
196
+ source: str = "",
197
+ measured_by: str = "",
198
+ notes: str = "",
199
+ date_accessed: str = "",
200
+ ) -> TupleValue: ...
201
+
202
+ def __new__(
203
+ cls,
204
+ raw=None,
205
+ unit=None,
206
+ /,
207
+ *,
208
+ low=None,
209
+ high=None,
210
+ step=None,
211
+ choices=None,
212
+ dist=None,
213
+ basis=None,
214
+ source="",
215
+ measured_by="",
216
+ notes="",
217
+ date_accessed="",
218
+ ):
219
+ if cls is Value:
220
+ if isinstance(raw, bool) or raw is None:
221
+ raise TypeError(
222
+ "bool and None cannot carry evidence (Python forbids subclassing them); "
223
+ "leave them plain"
224
+ )
225
+ for base, carrier in (
226
+ (float, FloatValue),
227
+ (int, IntValue),
228
+ (str, StrValue),
229
+ (tuple, TupleValue),
230
+ ):
231
+ if isinstance(raw, base):
232
+ cls = carrier
233
+ break
234
+ else:
235
+ raise TypeError(f"Value wraps float, int, str or tuple, not {type(raw).__name__}")
236
+ obj = cls._base.__new__(cls, raw)
237
+ meta = dict(
238
+ unit=unit,
239
+ low=low,
240
+ high=high,
241
+ step=step,
242
+ choices=tuple(choices) if choices is not None else None,
243
+ dist=dist,
244
+ basis=basis,
245
+ source=source,
246
+ measured_by=measured_by,
247
+ notes=notes,
248
+ date_accessed=date_accessed,
249
+ )
250
+ for k, val in meta.items():
251
+ object.__setattr__(obj, k, val)
252
+ if low is not None and high is not None and not within(raw, low, high):
253
+ raise ValueError(
254
+ f"{raw!r} outside [{low}, {high}] ({source or unit or ''})".rstrip(" ()")
255
+ )
256
+ if meta["choices"] is not None and raw not in meta["choices"]:
257
+ raise ValueError(f"{raw!r} not one of {meta['choices']}")
258
+ return obj
259
+
260
+ def __init__(self, *args, **kwargs): # all work happens in __new__
261
+ pass
262
+
263
+ def __setattr__(self, key, val):
264
+ raise AttributeError(
265
+ f"{type(self).__name__} is immutable; write a new Value(...) in the source"
266
+ )
267
+
268
+ def __reduce__(self): # copy, deepcopy, asdict and pickle rebuild through __new__
269
+ return (_rebuild, (type(self), self._base(self), self.meta()))
270
+
271
+ @property
272
+ def value(self):
273
+ """The plain builtin. New code just uses the value itself."""
274
+ return self._base(self)
275
+
276
+ def meta(self) -> dict:
277
+ return {k: getattr(self, k) for k in META}
278
+
279
+
280
+ class FloatValue(Value, float):
281
+ _base = float
282
+
283
+
284
+ class IntValue(Value, int):
285
+ _base = int
286
+
287
+
288
+ class StrValue(Value, str):
289
+ _base = str
290
+
291
+
292
+ class TupleValue(Value, tuple):
293
+ """A vector or pair that remembers where it came from.
294
+
295
+ `Value((0.0, 0.0, 0.3), "m", low=(-1, -1, 0), high=(1, 1, 2))`.
296
+ """
297
+
298
+ _base = tuple
299
+
300
+
301
+ v = Value # the optional short alias
302
+
303
+
304
+ def meta(x) -> dict | None:
305
+ """The evidence on a value, or None for a plain one."""
306
+ return x.meta() if isinstance(x, Value) else None
307
+
308
+
309
+ def plain(obj):
310
+ """The same object with every Value replaced by its builtin: for ROS, JSON and the like."""
311
+ if isinstance(obj, TupleValue):
312
+ return tuple(plain(x) for x in obj)
313
+ if is_dataclass(obj) and not isinstance(obj, type):
314
+ return replace(obj, **{f.name: plain(getattr(obj, f.name)) for f in fields(obj) if f.init})
315
+ if isinstance(obj, (list, tuple)):
316
+ return type(obj)(plain(x) for x in obj)
317
+ return obj.value if isinstance(obj, Value) else obj
@@ -0,0 +1,70 @@
1
+ Metadata-Version: 2.5
2
+ Name: dataclass-values
3
+ Version: 0.1.0
4
+ 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.
5
+ Project-URL: Source, https://github.com/zacharyyamaoka/dataclass_values
6
+ Author: Zach Yamaoka
7
+ License-Expression: MIT
8
+ License-File: LICENSE
9
+ Keywords: configuration,dataclasses,provenance,sampling,units
10
+ Classifier: Operating System :: OS Independent
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Typing :: Typed
13
+ Requires-Python: >=3.11
14
+ Provides-Extra: test
15
+ Requires-Dist: pytest>=8; extra == 'test'
16
+ Description-Content-Type: text/markdown
17
+
18
+ # dataclass-values
19
+
20
+ A number that remembers where it came from, and still is the number. Stdlib only.
21
+
22
+ ```python
23
+ from dataclasses import dataclass
24
+ from dataclass_values import Basis, Normal, Space, Value, plain
25
+
26
+ @dataclass(frozen=True)
27
+ class Belt:
28
+ width_m: float = Value(1.2, "m", low=0.6, high=1.85, basis=Basis.CHOSEN, source="site survey")
29
+ speed_mps: float = Value(0.5, "m/s", dist=Normal(0.5, 0.05), low=0.2, high=0.8)
30
+
31
+ belt = Belt()
32
+ belt.width_m * 2 # 2.4, a plain float
33
+ Space(belt).sample() # a Belt, every ranged field redrawn, basis=SAMPLED
34
+ Space(belt).violations(Belt(9.0, 0.5)) # ["width_m=9.0 outside [0.6, 1.85]"]
35
+ plain(belt) # the same Belt with bare floats, for ROS / JSON
36
+ ```
37
+
38
+ ## Why
39
+
40
+ The syntax applies to any domain, so it is its own package: robot_msgs is one vocabulary built on it,
41
+ py_dataclass_viewer is one app built on it (Zach, 2026-11-10: "the pydataclass syntax can technically
42
+ apply to any domain! robot_msgs is just a particular domain.").
43
+
44
+ Zach's rule (Oct 5 2026, do not re-litigate):
45
+
46
+ - `Value(1.2, "m", low=0.6, high=1.85, basis=Basis.CHOSEN, source="...")` IS the builtin: a
47
+ float/int/str/tuple subclass. "I definitely don't want to have to do `width_m.value`."
48
+ - Spaces are derived: `Space(obj).sample()` returns the same class, every ranged value marked
49
+ `basis=SAMPLED`. No second `PoseSpace` class to drift from the one it shadows.
50
+ - `plain()` strips the notes at a runtime boundary. "We can use Value wherever we use a raw value
51
+ normally and we can always strip it back to the plain value."
52
+
53
+ ## API
54
+
55
+ - `Value(raw, unit=None, *, low, high, step, choices, dist, basis, source, measured_by, notes, date_accessed)`
56
+ returns `FloatValue | IntValue | StrValue | TupleValue` (also exported). Refused at construction when
57
+ outside `[low, high]` (tuples elementwise) or not in `choices`. `v` is a short alias.
58
+ - `meta(x)` the evidence dict or `None`; `plain(obj)` strips every `Value`, through dataclasses,
59
+ lists and tuples.
60
+ - `Basis`: `CHOSEN DATASHEET MARKET ASSUMED MEASURED SAMPLED` (a `StrEnum`).
61
+ - `Uniform`, `Normal`, `LogUniform`, `Choice`: per-value distributions with `.draw(rng)`.
62
+ - `Space(instance)`: `.sample(rng=None)`, `.contains(other)`, `.violations(other)` (dotted field
63
+ paths). Subclasses add bounds by overriding the hooks `extra_draw(drawn, rng)` and
64
+ `extra_violations(other)`; robot_msgs uses them for numpy array bounds.
65
+
66
+ ## Tests
67
+
68
+ ```bash
69
+ env -u VIRTUAL_ENV uv run --isolated --extra test python -m pytest -q
70
+ ```
@@ -0,0 +1,8 @@
1
+ dataclass_values/__init__.py,sha256=O-Ci9D894Il6YnsWU4nUwiHMY7YhmxY7ydwHJEB1nu4,574
2
+ dataclass_values/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
+ dataclass_values/space.py,sha256=6Rm-CVlL8bmkY0XXiszCp6GMq4Kg7r1HKjnIs_j8hVM,5840
4
+ dataclass_values/values.py,sha256=NPPo2lNOPkM7iNBsqhVydIPGmD4YSAxYtbXbpiby2jE,9518
5
+ dataclass_values-0.1.0.dist-info/METADATA,sha256=3sVYy-dSeB8-oIvic6jzLE57WlPPc-wlc1PSU5IKNLs,3237
6
+ dataclass_values-0.1.0.dist-info/WHEEL,sha256=2iZrJkDIkeDh0k9SikKMqFuRP-ua6mb4Ir1AsER4BEw,87
7
+ dataclass_values-0.1.0.dist-info/licenses/LICENSE,sha256=6bCPolDAhPTDhDz1aKT0ErCY9wlydSa1utkKkDysbQE,1221
8
+ dataclass_values-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.33.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,26 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Zach Yamaoka
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.
22
+
23
+ ---
24
+
25
+ src/robot_msgs/ros/vision_msgs.py embeds message definitions from
26
+ https://github.com/ros-perception/vision_msgs (tag 4.1.1), licensed Apache-2.0.