ucon 0.5.2__py3-none-any.whl → 0.6.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.
- ucon/__init__.py +4 -1
- ucon/core.py +7 -2
- ucon/mcp/__init__.py +8 -0
- ucon/mcp/server.py +250 -0
- ucon/pydantic.py +199 -0
- ucon/units.py +259 -11
- {ucon-0.5.2.dist-info → ucon-0.6.0.dist-info}/METADATA +73 -97
- ucon-0.6.0.dist-info/RECORD +17 -0
- ucon-0.6.0.dist-info/entry_points.txt +2 -0
- ucon-0.6.0.dist-info/top_level.txt +1 -0
- tests/ucon/__init__.py +0 -3
- tests/ucon/conversion/__init__.py +0 -0
- tests/ucon/conversion/test_graph.py +0 -409
- tests/ucon/conversion/test_map.py +0 -409
- tests/ucon/test_algebra.py +0 -239
- tests/ucon/test_basis_transform.py +0 -521
- tests/ucon/test_core.py +0 -827
- tests/ucon/test_default_graph_conversions.py +0 -443
- tests/ucon/test_dimensionless_units.py +0 -248
- tests/ucon/test_graph_basis_transform.py +0 -263
- tests/ucon/test_quantity.py +0 -615
- tests/ucon/test_rebased_unit.py +0 -184
- tests/ucon/test_uncertainty.py +0 -264
- tests/ucon/test_unit_system.py +0 -174
- tests/ucon/test_units.py +0 -25
- tests/ucon/test_vector_fraction.py +0 -185
- ucon-0.5.2.dist-info/RECORD +0 -29
- ucon-0.5.2.dist-info/top_level.txt +0 -2
- {ucon-0.5.2.dist-info → ucon-0.6.0.dist-info}/WHEEL +0 -0
- {ucon-0.5.2.dist-info → ucon-0.6.0.dist-info}/licenses/LICENSE +0 -0
- {ucon-0.5.2.dist-info → ucon-0.6.0.dist-info}/licenses/NOTICE +0 -0
ucon/__init__.py
CHANGED
|
@@ -53,6 +53,7 @@ from ucon.core import (
|
|
|
53
53
|
Ratio,
|
|
54
54
|
)
|
|
55
55
|
from ucon.graph import get_default_graph, using_graph
|
|
56
|
+
from ucon.units import UnknownUnitError, get_unit_by_name
|
|
56
57
|
|
|
57
58
|
|
|
58
59
|
__all__ = [
|
|
@@ -69,7 +70,9 @@ __all__ = [
|
|
|
69
70
|
'UnitFactor',
|
|
70
71
|
'UnitProduct',
|
|
71
72
|
'UnitSystem',
|
|
73
|
+
'UnknownUnitError',
|
|
72
74
|
'get_default_graph',
|
|
73
|
-
'
|
|
75
|
+
'get_unit_by_name',
|
|
74
76
|
'units',
|
|
77
|
+
'using_graph',
|
|
75
78
|
]
|
ucon/core.py
CHANGED
|
@@ -1108,16 +1108,21 @@ class UnitProduct:
|
|
|
1108
1108
|
part = getattr(unit, "shorthand", "") or getattr(unit, "name", "") or ""
|
|
1109
1109
|
if not part:
|
|
1110
1110
|
return
|
|
1111
|
+
|
|
1112
|
+
def fmt_exp(p: float) -> str:
|
|
1113
|
+
"""Format exponent, using int when possible to avoid '2.0' → '²·⁰'."""
|
|
1114
|
+
return str(int(p) if p == int(p) else p).translate(cls._SUPERSCRIPTS)
|
|
1115
|
+
|
|
1111
1116
|
if power > 0:
|
|
1112
1117
|
if power == 1:
|
|
1113
1118
|
num.append(part)
|
|
1114
1119
|
else:
|
|
1115
|
-
num.append(part +
|
|
1120
|
+
num.append(part + fmt_exp(power))
|
|
1116
1121
|
elif power < 0:
|
|
1117
1122
|
if power == -1:
|
|
1118
1123
|
den.append(part)
|
|
1119
1124
|
else:
|
|
1120
|
-
den.append(part +
|
|
1125
|
+
den.append(part + fmt_exp(-power))
|
|
1121
1126
|
|
|
1122
1127
|
@property
|
|
1123
1128
|
def shorthand(self) -> str:
|
ucon/mcp/__init__.py
ADDED
ucon/mcp/server.py
ADDED
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
# ucon MCP Server
|
|
2
|
+
#
|
|
3
|
+
# Provides unit conversion and dimensional analysis tools for AI agents.
|
|
4
|
+
#
|
|
5
|
+
# Usage:
|
|
6
|
+
# ucon-mcp # Run via entry point
|
|
7
|
+
# python -m ucon.mcp # Run as module
|
|
8
|
+
|
|
9
|
+
from mcp.server.fastmcp import FastMCP
|
|
10
|
+
from pydantic import BaseModel
|
|
11
|
+
|
|
12
|
+
from ucon import Dimension, get_unit_by_name
|
|
13
|
+
from ucon.core import Number, Scale, Unit, UnitProduct
|
|
14
|
+
from ucon.units import UnknownUnitError
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
mcp = FastMCP("ucon")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# -----------------------------------------------------------------------------
|
|
21
|
+
# Response Models
|
|
22
|
+
# -----------------------------------------------------------------------------
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class ConversionResult(BaseModel):
|
|
26
|
+
"""Result of a unit conversion."""
|
|
27
|
+
|
|
28
|
+
quantity: float
|
|
29
|
+
unit: str | None
|
|
30
|
+
dimension: str
|
|
31
|
+
uncertainty: float | None = None
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class UnitInfo(BaseModel):
|
|
35
|
+
"""Information about an available unit."""
|
|
36
|
+
|
|
37
|
+
name: str
|
|
38
|
+
shorthand: str
|
|
39
|
+
aliases: list[str]
|
|
40
|
+
dimension: str
|
|
41
|
+
scalable: bool
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class ScaleInfo(BaseModel):
|
|
45
|
+
"""Information about a scale prefix."""
|
|
46
|
+
|
|
47
|
+
name: str
|
|
48
|
+
prefix: str
|
|
49
|
+
factor: float
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class DimensionCheck(BaseModel):
|
|
53
|
+
"""Result of a dimensional compatibility check."""
|
|
54
|
+
|
|
55
|
+
compatible: bool
|
|
56
|
+
dimension_a: str
|
|
57
|
+
dimension_b: str
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
# -----------------------------------------------------------------------------
|
|
61
|
+
# Tools
|
|
62
|
+
# -----------------------------------------------------------------------------
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@mcp.tool()
|
|
66
|
+
def convert(value: float, from_unit: str, to_unit: str) -> ConversionResult:
|
|
67
|
+
"""
|
|
68
|
+
Convert a numeric value from one unit to another.
|
|
69
|
+
|
|
70
|
+
Units can be specified as:
|
|
71
|
+
- Base units: "meter", "m", "second", "s", "gram", "g"
|
|
72
|
+
- Scaled units: "km", "mL", "kg", "MHz" (use list_scales for prefixes)
|
|
73
|
+
- Composite units: "m/s", "kg*m/s^2", "N*m"
|
|
74
|
+
- Exponents: "m^2", "s^-1" (ASCII) or "m²", "s⁻¹" (Unicode)
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
value: The numeric quantity to convert.
|
|
78
|
+
from_unit: Source unit string.
|
|
79
|
+
to_unit: Target unit string.
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
ConversionResult with converted quantity, unit, and dimension.
|
|
83
|
+
|
|
84
|
+
Raises:
|
|
85
|
+
UnknownUnitError: If a unit string cannot be parsed.
|
|
86
|
+
DimensionMismatch: If units have incompatible dimensions.
|
|
87
|
+
"""
|
|
88
|
+
src = get_unit_by_name(from_unit)
|
|
89
|
+
dst = get_unit_by_name(to_unit)
|
|
90
|
+
|
|
91
|
+
num = Number(quantity=value, unit=src)
|
|
92
|
+
result = num.to(dst)
|
|
93
|
+
|
|
94
|
+
unit_str = None
|
|
95
|
+
dim_name = "none"
|
|
96
|
+
|
|
97
|
+
if result.unit:
|
|
98
|
+
if isinstance(result.unit, UnitProduct):
|
|
99
|
+
unit_str = result.unit.shorthand
|
|
100
|
+
dim_name = result.unit.dimension.name
|
|
101
|
+
elif isinstance(result.unit, Unit):
|
|
102
|
+
unit_str = result.unit.shorthand
|
|
103
|
+
dim_name = result.unit.dimension.name
|
|
104
|
+
|
|
105
|
+
return ConversionResult(
|
|
106
|
+
quantity=result.quantity,
|
|
107
|
+
unit=unit_str,
|
|
108
|
+
dimension=dim_name,
|
|
109
|
+
uncertainty=result.uncertainty,
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
@mcp.tool()
|
|
114
|
+
def list_units(dimension: str | None = None) -> list[UnitInfo]:
|
|
115
|
+
"""
|
|
116
|
+
List available units, optionally filtered by dimension.
|
|
117
|
+
|
|
118
|
+
Returns base units only. Use scale prefixes (from list_scales) to form
|
|
119
|
+
scaled variants. For example, "meter" with prefix "k" becomes "km".
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
dimension: Optional filter by dimension name (e.g., "length", "mass", "time").
|
|
123
|
+
Use list_dimensions() to see available dimensions.
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
List of UnitInfo objects describing available units.
|
|
127
|
+
"""
|
|
128
|
+
import ucon.units as units_module
|
|
129
|
+
|
|
130
|
+
# Units that accept SI scale prefixes
|
|
131
|
+
SCALABLE_UNITS = {
|
|
132
|
+
"meter", "gram", "second", "ampere", "kelvin", "mole", "candela",
|
|
133
|
+
"hertz", "newton", "pascal", "joule", "watt", "coulomb", "volt",
|
|
134
|
+
"farad", "ohm", "siemens", "weber", "tesla", "henry", "lumen",
|
|
135
|
+
"lux", "becquerel", "gray", "sievert", "katal",
|
|
136
|
+
"liter", "byte",
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
result = []
|
|
140
|
+
seen_names = set()
|
|
141
|
+
|
|
142
|
+
for name in dir(units_module):
|
|
143
|
+
obj = getattr(units_module, name)
|
|
144
|
+
if isinstance(obj, Unit) and obj.name and obj.name not in seen_names:
|
|
145
|
+
seen_names.add(obj.name)
|
|
146
|
+
|
|
147
|
+
if dimension and obj.dimension.name != dimension:
|
|
148
|
+
continue
|
|
149
|
+
|
|
150
|
+
result.append(
|
|
151
|
+
UnitInfo(
|
|
152
|
+
name=obj.name,
|
|
153
|
+
shorthand=obj.shorthand,
|
|
154
|
+
aliases=list(obj.aliases) if obj.aliases else [],
|
|
155
|
+
dimension=obj.dimension.name,
|
|
156
|
+
scalable=obj.name in SCALABLE_UNITS,
|
|
157
|
+
)
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
return sorted(result, key=lambda u: (u.dimension, u.name))
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
@mcp.tool()
|
|
164
|
+
def list_scales() -> list[ScaleInfo]:
|
|
165
|
+
"""
|
|
166
|
+
List available scale prefixes for units.
|
|
167
|
+
|
|
168
|
+
These prefixes can be combined with scalable units (see list_units).
|
|
169
|
+
For example, prefix "k" (kilo) with unit "m" (meter) forms "km".
|
|
170
|
+
|
|
171
|
+
Includes both SI decimal prefixes (kilo, mega, milli, micro, etc.)
|
|
172
|
+
and binary prefixes (kibi, mebi, gibi) for information units.
|
|
173
|
+
|
|
174
|
+
Note on bytes:
|
|
175
|
+
- SI prefixes: kB = 1000 B, MB = 1,000,000 B (decimal)
|
|
176
|
+
- Binary prefixes: KiB = 1024 B, MiB = 1,048,576 B (powers of 2)
|
|
177
|
+
|
|
178
|
+
Returns:
|
|
179
|
+
List of ScaleInfo objects with name, prefix symbol, and numeric factor.
|
|
180
|
+
"""
|
|
181
|
+
result = []
|
|
182
|
+
for scale in Scale:
|
|
183
|
+
if scale == Scale.one:
|
|
184
|
+
continue # Skip the identity scale
|
|
185
|
+
result.append(
|
|
186
|
+
ScaleInfo(
|
|
187
|
+
name=scale.name,
|
|
188
|
+
prefix=scale.shorthand,
|
|
189
|
+
factor=scale.descriptor.evaluated,
|
|
190
|
+
)
|
|
191
|
+
)
|
|
192
|
+
return sorted(result, key=lambda s: -s.factor)
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
@mcp.tool()
|
|
196
|
+
def check_dimensions(unit_a: str, unit_b: str) -> DimensionCheck:
|
|
197
|
+
"""
|
|
198
|
+
Check if two units have compatible dimensions.
|
|
199
|
+
|
|
200
|
+
Units with the same dimension can be converted between each other.
|
|
201
|
+
Units with different dimensions cannot be added or directly compared.
|
|
202
|
+
|
|
203
|
+
Args:
|
|
204
|
+
unit_a: First unit string.
|
|
205
|
+
unit_b: Second unit string.
|
|
206
|
+
|
|
207
|
+
Returns:
|
|
208
|
+
DimensionCheck indicating compatibility and the dimension of each unit.
|
|
209
|
+
"""
|
|
210
|
+
a = get_unit_by_name(unit_a)
|
|
211
|
+
b = get_unit_by_name(unit_b)
|
|
212
|
+
|
|
213
|
+
dim_a = a.dimension if isinstance(a, Unit) else a.dimension
|
|
214
|
+
dim_b = b.dimension if isinstance(b, Unit) else b.dimension
|
|
215
|
+
|
|
216
|
+
return DimensionCheck(
|
|
217
|
+
compatible=(dim_a == dim_b),
|
|
218
|
+
dimension_a=dim_a.name,
|
|
219
|
+
dimension_b=dim_b.name,
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
@mcp.tool()
|
|
224
|
+
def list_dimensions() -> list[str]:
|
|
225
|
+
"""
|
|
226
|
+
List available physical dimensions.
|
|
227
|
+
|
|
228
|
+
Dimensions represent fundamental physical quantities (length, mass, time, etc.)
|
|
229
|
+
and derived quantities (velocity, force, energy, etc.).
|
|
230
|
+
|
|
231
|
+
Use these dimension names to filter list_units().
|
|
232
|
+
|
|
233
|
+
Returns:
|
|
234
|
+
List of dimension names.
|
|
235
|
+
"""
|
|
236
|
+
return sorted([d.name for d in Dimension])
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
# -----------------------------------------------------------------------------
|
|
240
|
+
# Entry Point
|
|
241
|
+
# -----------------------------------------------------------------------------
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def main():
|
|
245
|
+
"""Run the ucon MCP server."""
|
|
246
|
+
mcp.run()
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
if __name__ == "__main__":
|
|
250
|
+
main()
|
ucon/pydantic.py
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
# © 2025 The Radiativity Company
|
|
2
|
+
# Licensed under the Apache License, Version 2.0
|
|
3
|
+
# See the LICENSE file for details.
|
|
4
|
+
|
|
5
|
+
"""
|
|
6
|
+
ucon.pydantic
|
|
7
|
+
=============
|
|
8
|
+
|
|
9
|
+
Pydantic v2 integration for ucon.
|
|
10
|
+
|
|
11
|
+
Provides type-annotated wrappers for use in Pydantic models with full
|
|
12
|
+
JSON serialization support.
|
|
13
|
+
|
|
14
|
+
Usage
|
|
15
|
+
-----
|
|
16
|
+
>>> from pydantic import BaseModel
|
|
17
|
+
>>> from ucon.pydantic import Number
|
|
18
|
+
>>>
|
|
19
|
+
>>> class Measurement(BaseModel):
|
|
20
|
+
... value: Number
|
|
21
|
+
...
|
|
22
|
+
>>> m = Measurement(value={"quantity": 5, "unit": "km"})
|
|
23
|
+
>>> print(m.value)
|
|
24
|
+
<5 km>
|
|
25
|
+
>>> print(m.model_dump_json())
|
|
26
|
+
{"value": {"quantity": 5.0, "unit": "km", "uncertainty": null}}
|
|
27
|
+
|
|
28
|
+
Installation
|
|
29
|
+
------------
|
|
30
|
+
Requires Pydantic v2. Install with::
|
|
31
|
+
|
|
32
|
+
pip install ucon[pydantic]
|
|
33
|
+
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
from typing import Annotated, Any
|
|
37
|
+
|
|
38
|
+
try:
|
|
39
|
+
from pydantic import GetCoreSchemaHandler, GetJsonSchemaHandler
|
|
40
|
+
from pydantic.json_schema import JsonSchemaValue
|
|
41
|
+
from pydantic_core import CoreSchema, core_schema
|
|
42
|
+
except ImportError as e:
|
|
43
|
+
raise ImportError(
|
|
44
|
+
"Pydantic v2 is required for ucon.pydantic. "
|
|
45
|
+
"Install with: pip install ucon[pydantic]"
|
|
46
|
+
) from e
|
|
47
|
+
|
|
48
|
+
from ucon.core import Number as _Number
|
|
49
|
+
from ucon.units import UnknownUnitError, get_unit_by_name
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _validate_number(v: Any) -> _Number:
|
|
53
|
+
"""
|
|
54
|
+
Validate and convert input to Number.
|
|
55
|
+
|
|
56
|
+
Accepts:
|
|
57
|
+
- Number instance (passthrough)
|
|
58
|
+
- dict with 'quantity' and optional 'unit', 'uncertainty'
|
|
59
|
+
|
|
60
|
+
Raises:
|
|
61
|
+
ValueError: If input cannot be converted to Number.
|
|
62
|
+
"""
|
|
63
|
+
if isinstance(v, _Number):
|
|
64
|
+
return v
|
|
65
|
+
|
|
66
|
+
if isinstance(v, dict):
|
|
67
|
+
quantity = v.get("quantity")
|
|
68
|
+
if quantity is None:
|
|
69
|
+
raise ValueError("Number dict must have 'quantity' field")
|
|
70
|
+
|
|
71
|
+
unit_str = v.get("unit")
|
|
72
|
+
uncertainty = v.get("uncertainty")
|
|
73
|
+
|
|
74
|
+
# Parse unit if provided
|
|
75
|
+
if unit_str:
|
|
76
|
+
try:
|
|
77
|
+
unit = get_unit_by_name(unit_str)
|
|
78
|
+
except UnknownUnitError as e:
|
|
79
|
+
raise ValueError(f"Unknown unit: {unit_str!r}") from e
|
|
80
|
+
else:
|
|
81
|
+
unit = None
|
|
82
|
+
|
|
83
|
+
return _Number(
|
|
84
|
+
quantity=quantity,
|
|
85
|
+
unit=unit,
|
|
86
|
+
uncertainty=uncertainty,
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
raise ValueError(
|
|
90
|
+
f"Cannot parse Number from {type(v).__name__}. "
|
|
91
|
+
"Expected Number instance or dict with 'quantity' field."
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _serialize_number(n: _Number) -> dict:
|
|
96
|
+
"""
|
|
97
|
+
Serialize Number to JSON-compatible dict.
|
|
98
|
+
|
|
99
|
+
Output format::
|
|
100
|
+
|
|
101
|
+
{
|
|
102
|
+
"quantity": <float>,
|
|
103
|
+
"unit": <str | null>,
|
|
104
|
+
"uncertainty": <float | null>
|
|
105
|
+
}
|
|
106
|
+
"""
|
|
107
|
+
# Get unit shorthand
|
|
108
|
+
if n.unit is None:
|
|
109
|
+
unit_str = None
|
|
110
|
+
elif hasattr(n.unit, 'shorthand'):
|
|
111
|
+
unit_str = n.unit.shorthand
|
|
112
|
+
# Empty shorthand means dimensionless
|
|
113
|
+
if unit_str == "":
|
|
114
|
+
unit_str = None
|
|
115
|
+
else:
|
|
116
|
+
unit_str = None
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
"quantity": float(n.quantity),
|
|
120
|
+
"unit": unit_str,
|
|
121
|
+
"uncertainty": float(n.uncertainty) if n.uncertainty is not None else None,
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
class _NumberPydanticAnnotation:
|
|
126
|
+
"""
|
|
127
|
+
Pydantic annotation helper for ucon Number type.
|
|
128
|
+
|
|
129
|
+
This class provides the schema generation hooks that Pydantic v2 needs
|
|
130
|
+
to properly validate and serialize Number instances without introspecting
|
|
131
|
+
the internal Unit/UnitProduct types.
|
|
132
|
+
"""
|
|
133
|
+
|
|
134
|
+
@classmethod
|
|
135
|
+
def __get_pydantic_core_schema__(
|
|
136
|
+
cls,
|
|
137
|
+
_source_type: Any,
|
|
138
|
+
_handler: GetCoreSchemaHandler,
|
|
139
|
+
) -> CoreSchema:
|
|
140
|
+
"""
|
|
141
|
+
Generate Pydantic core schema for Number validation/serialization.
|
|
142
|
+
|
|
143
|
+
Uses no_info_plain_validator_function to bypass Pydantic's default
|
|
144
|
+
introspection of the Number class fields.
|
|
145
|
+
"""
|
|
146
|
+
return core_schema.no_info_plain_validator_function(
|
|
147
|
+
_validate_number,
|
|
148
|
+
serialization=core_schema.plain_serializer_function_ser_schema(
|
|
149
|
+
_serialize_number,
|
|
150
|
+
info_arg=False,
|
|
151
|
+
return_schema=core_schema.dict_schema(),
|
|
152
|
+
),
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
@classmethod
|
|
156
|
+
def __get_pydantic_json_schema__(
|
|
157
|
+
cls,
|
|
158
|
+
_core_schema: CoreSchema,
|
|
159
|
+
handler: GetJsonSchemaHandler,
|
|
160
|
+
) -> JsonSchemaValue:
|
|
161
|
+
"""Generate JSON schema for OpenAPI documentation."""
|
|
162
|
+
return {
|
|
163
|
+
"type": "object",
|
|
164
|
+
"properties": {
|
|
165
|
+
"quantity": {"type": "number"},
|
|
166
|
+
"unit": {"type": "string", "nullable": True},
|
|
167
|
+
"uncertainty": {"type": "number", "nullable": True},
|
|
168
|
+
},
|
|
169
|
+
"required": ["quantity"],
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
Number = Annotated[_Number, _NumberPydanticAnnotation]
|
|
174
|
+
"""
|
|
175
|
+
Pydantic-compatible Number type.
|
|
176
|
+
|
|
177
|
+
Use this as a type hint in Pydantic models to enable automatic validation
|
|
178
|
+
and JSON serialization of ucon Number instances.
|
|
179
|
+
|
|
180
|
+
Example::
|
|
181
|
+
|
|
182
|
+
from pydantic import BaseModel
|
|
183
|
+
from ucon.pydantic import Number
|
|
184
|
+
|
|
185
|
+
class Measurement(BaseModel):
|
|
186
|
+
value: Number
|
|
187
|
+
|
|
188
|
+
# From dict
|
|
189
|
+
m = Measurement(value={"quantity": 5, "unit": "m"})
|
|
190
|
+
|
|
191
|
+
# From Number instance
|
|
192
|
+
from ucon import units
|
|
193
|
+
m2 = Measurement(value=units.meter(10))
|
|
194
|
+
|
|
195
|
+
# Serialize to JSON
|
|
196
|
+
print(m.model_dump_json())
|
|
197
|
+
"""
|
|
198
|
+
|
|
199
|
+
__all__ = ["Number"]
|