unitx 0.13.2__tar.gz → 0.13.3__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.
- {unitx-0.13.2 → unitx-0.13.3}/CHANGELOG.md +13 -0
- {unitx-0.13.2 → unitx-0.13.3}/PKG-INFO +2 -2
- {unitx-0.13.2 → unitx-0.13.3}/README.md +1 -1
- {unitx-0.13.2 → unitx-0.13.3}/docs/README.md +1 -0
- unitx-0.13.3/docs/serialization.md +225 -0
- {unitx-0.13.2 → unitx-0.13.3}/docs/typing.md +5 -0
- {unitx-0.13.2 → unitx-0.13.3}/pyproject.toml +1 -1
- {unitx-0.13.2 → unitx-0.13.3}/pyrightconfig.json +2 -0
- {unitx-0.13.2 → unitx-0.13.3}/src/unitx/exceptions.py +10 -0
- unitx-0.13.3/src/unitx/serialization.py +354 -0
- unitx-0.13.3/tests/test_serialization.py +548 -0
- unitx-0.13.3/tests/typing/check_serialization.py +32 -0
- {unitx-0.13.2 → unitx-0.13.3}/.github/workflows/ci.yml +0 -0
- {unitx-0.13.2 → unitx-0.13.3}/.github/workflows/release.yml +0 -0
- {unitx-0.13.2 → unitx-0.13.3}/.gitignore +0 -0
- {unitx-0.13.2 → unitx-0.13.3}/CONTRIBUTING.md +0 -0
- {unitx-0.13.2 → unitx-0.13.3}/LICENSE +0 -0
- {unitx-0.13.2 → unitx-0.13.3}/benchmarks/numpy_writes.py +0 -0
- {unitx-0.13.2 → unitx-0.13.3}/conftest.py +0 -0
- {unitx-0.13.2 → unitx-0.13.3}/docs/affine.md +0 -0
- {unitx-0.13.2 → unitx-0.13.3}/docs/catalogue.md +0 -0
- {unitx-0.13.2 → unitx-0.13.3}/docs/custom-units.md +0 -0
- {unitx-0.13.2 → unitx-0.13.3}/docs/display.md +0 -0
- {unitx-0.13.2 → unitx-0.13.3}/docs/images/blackbody.svg +0 -0
- {unitx-0.13.2 → unitx-0.13.3}/docs/migration.md +0 -0
- {unitx-0.13.2 → unitx-0.13.3}/docs/numpy.md +0 -0
- {unitx-0.13.2 → unitx-0.13.3}/docs/scalars.md +0 -0
- {unitx-0.13.2 → unitx-0.13.3}/docs/stdlib.md +0 -0
- {unitx-0.13.2 → unitx-0.13.3}/docs/units.md +0 -0
- {unitx-0.13.2 → unitx-0.13.3}/examples/README.md +0 -0
- {unitx-0.13.2 → unitx-0.13.3}/examples/blackbody.ipynb +0 -0
- {unitx-0.13.2 → unitx-0.13.3}/examples/damped_oscillator.ipynb +0 -0
- {unitx-0.13.2 → unitx-0.13.3}/examples/gaussian_wavepacket.ipynb +0 -0
- {unitx-0.13.2 → unitx-0.13.3}/examples/optical_attenuation.ipynb +0 -0
- {unitx-0.13.2 → unitx-0.13.3}/examples/pendulum_uncertainty.ipynb +0 -0
- {unitx-0.13.2 → unitx-0.13.3}/src/unitx/__init__.py +0 -0
- {unitx-0.13.2 → unitx-0.13.3}/src/unitx/_constnamespace.py +0 -0
- {unitx-0.13.2 → unitx-0.13.3}/src/unitx/_conversion.py +0 -0
- {unitx-0.13.2 → unitx-0.13.3}/src/unitx/_display.py +0 -0
- {unitx-0.13.2 → unitx-0.13.3}/src/unitx/_immutable.py +0 -0
- {unitx-0.13.2 → unitx-0.13.3}/src/unitx/_numerics.py +0 -0
- {unitx-0.13.2 → unitx-0.13.3}/src/unitx/_numpy.py +0 -0
- {unitx-0.13.2 → unitx-0.13.3}/src/unitx/_numpy_typing.py +0 -0
- {unitx-0.13.2 → unitx-0.13.3}/src/unitx/_unicode.py +0 -0
- {unitx-0.13.2 → unitx-0.13.3}/src/unitx/affinequantity.py +0 -0
- {unitx-0.13.2 → unitx-0.13.3}/src/unitx/affineunit.py +0 -0
- {unitx-0.13.2 → unitx-0.13.3}/src/unitx/dimension.py +0 -0
- {unitx-0.13.2 → unitx-0.13.3}/src/unitx/factor.py +0 -0
- {unitx-0.13.2 → unitx-0.13.3}/src/unitx/math.py +0 -0
- {unitx-0.13.2 → unitx-0.13.3}/src/unitx/monomial.py +0 -0
- {unitx-0.13.2 → unitx-0.13.3}/src/unitx/py.typed +0 -0
- {unitx-0.13.2 → unitx-0.13.3}/src/unitx/quantity.py +0 -0
- {unitx-0.13.2 → unitx-0.13.3}/src/unitx/si/__init__.py +0 -0
- {unitx-0.13.2 → unitx-0.13.3}/src/unitx/si/bases.py +0 -0
- {unitx-0.13.2 → unitx-0.13.3}/src/unitx/si/constants.py +0 -0
- {unitx-0.13.2 → unitx-0.13.3}/src/unitx/si/units.py +0 -0
- {unitx-0.13.2 → unitx-0.13.3}/src/unitx/singleunit.py +0 -0
- {unitx-0.13.2 → unitx-0.13.3}/src/unitx/time.py +0 -0
- {unitx-0.13.2 → unitx-0.13.3}/src/unitx/unit.py +0 -0
- {unitx-0.13.2 → unitx-0.13.3}/src/unitx/unitparser.py +0 -0
- {unitx-0.13.2 → unitx-0.13.3}/src/unitx/unitregistry.py +0 -0
- {unitx-0.13.2 → unitx-0.13.3}/tests/__init__.py +0 -0
- {unitx-0.13.2 → unitx-0.13.3}/tests/_notebooks.py +0 -0
- {unitx-0.13.2 → unitx-0.13.3}/tests/example_typing/check_blackbody.py +0 -0
- {unitx-0.13.2 → unitx-0.13.3}/tests/numpy_typing/check_math.py +0 -0
- {unitx-0.13.2 → unitx-0.13.3}/tests/numpy_typing/check_numpy.py +0 -0
- {unitx-0.13.2 → unitx-0.13.3}/tests/test_affine.py +0 -0
- {unitx-0.13.2 → unitx-0.13.3}/tests/test_affinequantity.py +0 -0
- {unitx-0.13.2 → unitx-0.13.3}/tests/test_builtin_definitions.py +0 -0
- {unitx-0.13.2 → unitx-0.13.3}/tests/test_dimension.py +0 -0
- {unitx-0.13.2 → unitx-0.13.3}/tests/test_display.py +0 -0
- {unitx-0.13.2 → unitx-0.13.3}/tests/test_examples.py +0 -0
- {unitx-0.13.2 → unitx-0.13.3}/tests/test_factor.py +0 -0
- {unitx-0.13.2 → unitx-0.13.3}/tests/test_immutable.py +0 -0
- {unitx-0.13.2 → unitx-0.13.3}/tests/test_import.py +0 -0
- {unitx-0.13.2 → unitx-0.13.3}/tests/test_math.py +0 -0
- {unitx-0.13.2 → unitx-0.13.3}/tests/test_math_numpy.py +0 -0
- {unitx-0.13.2 → unitx-0.13.3}/tests/test_monomial.py +0 -0
- {unitx-0.13.2 → unitx-0.13.3}/tests/test_numeric.py +0 -0
- {unitx-0.13.2 → unitx-0.13.3}/tests/test_numpy.py +0 -0
- {unitx-0.13.2 → unitx-0.13.3}/tests/test_numpy_affine.py +0 -0
- {unitx-0.13.2 → unitx-0.13.3}/tests/test_numpy_differentiation.py +0 -0
- {unitx-0.13.2 → unitx-0.13.3}/tests/test_numpy_integration.py +0 -0
- {unitx-0.13.2 → unitx-0.13.3}/tests/test_numpy_multi_outputs.py +0 -0
- {unitx-0.13.2 → unitx-0.13.3}/tests/test_numpy_mutation.py +0 -0
- {unitx-0.13.2 → unitx-0.13.3}/tests/test_numpy_outputs.py +0 -0
- {unitx-0.13.2 → unitx-0.13.3}/tests/test_numpy_products.py +0 -0
- {unitx-0.13.2 → unitx-0.13.3}/tests/test_numpy_statistics.py +0 -0
- {unitx-0.13.2 → unitx-0.13.3}/tests/test_quantity.py +0 -0
- {unitx-0.13.2 → unitx-0.13.3}/tests/test_si.py +0 -0
- {unitx-0.13.2 → unitx-0.13.3}/tests/test_singleunit.py +0 -0
- {unitx-0.13.2 → unitx-0.13.3}/tests/test_siunitx_display.py +0 -0
- {unitx-0.13.2 → unitx-0.13.3}/tests/test_time.py +0 -0
- {unitx-0.13.2 → unitx-0.13.3}/tests/test_typing.py +0 -0
- {unitx-0.13.2 → unitx-0.13.3}/tests/test_unicode.py +0 -0
- {unitx-0.13.2 → unitx-0.13.3}/tests/test_unicode_units.py +0 -0
- {unitx-0.13.2 → unitx-0.13.3}/tests/test_unit.py +0 -0
- {unitx-0.13.2 → unitx-0.13.3}/tests/test_unit_definition.py +0 -0
- {unitx-0.13.2 → unitx-0.13.3}/tests/test_unitparser.py +0 -0
- {unitx-0.13.2 → unitx-0.13.3}/tests/test_unitregistry.py +0 -0
- {unitx-0.13.2 → unitx-0.13.3}/tests/test_warnings.py +0 -0
- {unitx-0.13.2 → unitx-0.13.3}/tests/typing/check_affine.py +0 -0
- {unitx-0.13.2 → unitx-0.13.3}/tests/typing/check_math_time.py +0 -0
- {unitx-0.13.2 → unitx-0.13.3}/tests/typing/check_quantity.py +0 -0
- {unitx-0.13.2 → unitx-0.13.3}/tests/typing/check_si.py +0 -0
|
@@ -2,6 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.13.3] - 2026-10-22
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- `unitx.serialization.to_dict()` / `from_dict()` exchange scalar Quantities,
|
|
10
|
+
affine points and their units as versioned JSON-compatible records. Preserve
|
|
11
|
+
int/float/Fraction/Decimal magnitudes, exact extra scales, formal unit identity
|
|
12
|
+
and coordinate references; validate existing registry meanings without
|
|
13
|
+
registering units. NumPy, complex magnitudes and subclasses are not supported.
|
|
14
|
+
- Add `SerializationError` for invalid records and failed restoration, and an
|
|
15
|
+
independent `VersionError` for unsupported explicit versions. Both derive
|
|
16
|
+
from `UnitxError` and `ValueError`; decoding preserves underlying causes.
|
|
17
|
+
|
|
5
18
|
## [0.13.2] - 2026-10-22
|
|
6
19
|
|
|
7
20
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: unitx
|
|
3
|
-
Version: 0.13.
|
|
3
|
+
Version: 0.13.3
|
|
4
4
|
Summary: Physical units, quantities, and unit conversion with exact unit algebra, type hints, and NumPy integration.
|
|
5
5
|
Project-URL: Homepage, https://github.com/DaitTHU/unitx
|
|
6
6
|
Project-URL: Documentation, https://github.com/DaitTHU/unitx/blob/main/docs/README.md
|
|
@@ -213,7 +213,7 @@ from matplotlib import pyplot as plt
|
|
|
213
213
|
from unitx.si import SI, si
|
|
214
214
|
|
|
215
215
|
wavelength = np.linspace(0.01, 3, 100)[:, None] * si.um
|
|
216
|
-
temperature = np.
|
|
216
|
+
temperature = np.asarray([3000, 4000, 5000]) * si.K
|
|
217
217
|
exponent = SI.h * SI.c / (wavelength * SI.kB * temperature)
|
|
218
218
|
radiance = 2 * SI.h * SI.c**2 / (wavelength**5 * np.expm1(exponent))
|
|
219
219
|
radiance = radiance.to('kW/sr/m2/nm')
|
|
@@ -185,7 +185,7 @@ from matplotlib import pyplot as plt
|
|
|
185
185
|
from unitx.si import SI, si
|
|
186
186
|
|
|
187
187
|
wavelength = np.linspace(0.01, 3, 100)[:, None] * si.um
|
|
188
|
-
temperature = np.
|
|
188
|
+
temperature = np.asarray([3000, 4000, 5000]) * si.K
|
|
189
189
|
exponent = SI.h * SI.c / (wavelength * SI.kB * temperature)
|
|
190
190
|
radiance = 2 * SI.h * SI.c**2 / (wavelength**5 * np.expm1(exponent))
|
|
191
191
|
radiance = radiance.to('kW/sr/m2/nm')
|
|
@@ -16,6 +16,7 @@ reading about an older version.
|
|
|
16
16
|
| Call unit-aware mathematical functions or read a clock | [Standard-library enhancements](stdlib.md) |
|
|
17
17
|
| Calculate with arrays, masks, reductions and output buffers | [NumPy support](numpy.md) |
|
|
18
18
|
| Format text or export LaTeX, HTML and siunitx | [Display and formatting](display.md) |
|
|
19
|
+
| Save and restore scalar quantities as JSON-compatible records | [Scalar data exchange](serialization.md) |
|
|
19
20
|
| Understand annotations and NumPy inference limits | [Typing](typing.md) |
|
|
20
21
|
| Update code written for an earlier release | [Migration notes](migration.md) |
|
|
21
22
|
| Run scientific examples | [Physics notebooks](../examples/README.md) |
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
# Scalar data exchange
|
|
2
|
+
|
|
3
|
+
`unitx.serialization.to_dict()` returns a versioned record made of dictionaries,
|
|
4
|
+
lists, strings, null and an integer format version. `from_dict()` restores it.
|
|
5
|
+
Use Python's JSON functions, or another transport for the same data structure:
|
|
6
|
+
|
|
7
|
+
```pycon
|
|
8
|
+
>>> import json
|
|
9
|
+
>>> from decimal import Decimal
|
|
10
|
+
>>> from fractions import Fraction
|
|
11
|
+
>>> from unitx import AffineQuantity, AffineUnit, Quantity, Unit
|
|
12
|
+
>>> from unitx.serialization import from_dict, to_dict
|
|
13
|
+
>>> original = Quantity(Fraction(1, 3), 'cm')
|
|
14
|
+
>>> text = json.dumps(to_dict(original), allow_nan=False)
|
|
15
|
+
>>> restored = from_dict(json.loads(text))
|
|
16
|
+
>>> type(restored.value) is Fraction
|
|
17
|
+
True
|
|
18
|
+
>>> restored.value, restored.unit
|
|
19
|
+
(Fraction(1, 3), Unit('cm'))
|
|
20
|
+
>>> restored == original
|
|
21
|
+
True
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
The module supports `Unit`, `ScaledUnit`, `AffineUnit`, scalar `Quantity` and
|
|
26
|
+
scalar `AffineQuantity`. Import the two functions from `unitx.serialization`;
|
|
27
|
+
they are not package-root exports. No optional dependency is required.
|
|
28
|
+
|
|
29
|
+
## Stored numbers
|
|
30
|
+
|
|
31
|
+
Magnitudes must be exact instances of Python `int`, `float`, `Fraction` or
|
|
32
|
+
`Decimal`. Booleans, complex numbers, numeric subclasses, custom magnitudes,
|
|
33
|
+
NumPy scalars and arrays are rejected with TypeError. A round trip preserves
|
|
34
|
+
the stored magnitude's type and value without converting its unit.
|
|
35
|
+
|
|
36
|
+
| Type tag | `value` field | Meaning |
|
|
37
|
+
| --- | --- | --- |
|
|
38
|
+
| `int` | A canonical decimal integer string | Retains integers beyond the exact range of a JSON consumer's floating-point numbers |
|
|
39
|
+
| `fraction` | Two integer strings, `[numerator, denominator]` | Exact rational, with positive denominator |
|
|
40
|
+
| `float` | `float.hex()` text | Exact finite binary value, including subnormals and signed zero; `inf`, `-inf` and `nan` are also supported |
|
|
41
|
+
| `decimal` | Decimal text | Retains sign, coefficient, trailing zeros and exponent; infinities and NaNs are supported |
|
|
42
|
+
|
|
43
|
+
Float NaNs preserve their category; NaN payload bits and sign are not part of
|
|
44
|
+
the format. Decimal NaN payloads and signaling status are retained. Export
|
|
45
|
+
and import do not round a Decimal magnitude in the active context. Invalid
|
|
46
|
+
Decimal strings are rejected even when the caller disables InvalidOperation
|
|
47
|
+
traps, without setting flags in the caller's context.
|
|
48
|
+
|
|
49
|
+
All numerical data except the format version use strings. Consequently strict
|
|
50
|
+
JSON encoding with `allow_nan=False` works even for nonfinite magnitudes.
|
|
51
|
+
Python's normal integer-string conversion and Decimal representation limits
|
|
52
|
+
still apply.
|
|
53
|
+
|
|
54
|
+
## Units and points
|
|
55
|
+
|
|
56
|
+
The format preserves the formal product, including prefixes and rational
|
|
57
|
+
exponents. Equal physical scale does not merge distinct units such as Hz and
|
|
58
|
+
Bq, and `km/m` retains its nontrivial dimensionless scale. A ScaledUnit stores
|
|
59
|
+
its additional Factor separately, including when that Factor is one:
|
|
60
|
+
|
|
61
|
+
```pycon
|
|
62
|
+
>>> from unitx import pi
|
|
63
|
+
>>> from unitx.unit import ScaledUnit
|
|
64
|
+
>>> scaled = ScaledUnit('m', pi ** Fraction(1, 3))
|
|
65
|
+
>>> rebuilt = from_dict(json.loads(json.dumps(to_dict(scaled))))
|
|
66
|
+
>>> type(rebuilt) is ScaledUnit and rebuilt.scale == scaled.scale
|
|
67
|
+
True
|
|
68
|
+
>>> rebuilt**3 == scaled**3
|
|
69
|
+
True
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
An AffineUnit stores its physical origin, difference unit and logarithmic kind.
|
|
74
|
+
Registered coordinates also retain their canonical registered symbol;
|
|
75
|
+
anonymous coordinates remain anonymous. Points stay AffineQuantity objects:
|
|
76
|
+
|
|
77
|
+
```pycon
|
|
78
|
+
>>> point = AffineQuantity(Decimal('20.00'), 'degC')
|
|
79
|
+
>>> rebuilt = from_dict(json.loads(json.dumps(to_dict(point))))
|
|
80
|
+
>>> type(rebuilt) is AffineQuantity and rebuilt.unit == point.unit
|
|
81
|
+
True
|
|
82
|
+
>>> rebuilt.value.as_tuple() == point.value.as_tuple()
|
|
83
|
+
True
|
|
84
|
+
>>> epoch = AffineUnit(Quantity(946684800, 's'), delta_unit='s')
|
|
85
|
+
>>> custom = AffineQuantity(Fraction(1, 2), epoch)
|
|
86
|
+
>>> from_dict(to_dict(custom)).unit == epoch
|
|
87
|
+
True
|
|
88
|
+
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
This preserves the existing model's information. It does not add clock identity,
|
|
92
|
+
timezone or source labels to time points. Point arithmetic and the existing
|
|
93
|
+
hashability rules are unchanged.
|
|
94
|
+
|
|
95
|
+
## Registry requirements
|
|
96
|
+
|
|
97
|
+
Import resolves spellings against the receiving process's existing registry.
|
|
98
|
+
Register custom units explicitly during application initialization before
|
|
99
|
+
decoding records that refer to them. Unknown spellings raise SerializationError
|
|
100
|
+
with the original UnitSymbolError retained as `__cause__`.
|
|
101
|
+
Decoding never registers or replaces definitions, and neither function changes
|
|
102
|
+
the input object or record.
|
|
103
|
+
|
|
104
|
+
A linear unit record contains its canonical formal identity and its combined
|
|
105
|
+
dimension, SI Factor and logarithmic degree. Import checks these against the
|
|
106
|
+
resolved unit. A registered coordinate must also match the saved physical
|
|
107
|
+
origin, its formal reference and difference units, and logarithmic kind.
|
|
108
|
+
Conflicts raise SerializationError rather than silently changing the result.
|
|
109
|
+
|
|
110
|
+
These checks describe the resulting unit, not a complete registry snapshot.
|
|
111
|
+
They do not compare names, aliases, prefix policies or each individual unit's
|
|
112
|
+
conversion metadata when changes cancel within a product. The receiving
|
|
113
|
+
application remains responsible for compatible unit definitions. Factor checks
|
|
114
|
+
compare exact formal state: a changed symbolic approximation or a different
|
|
115
|
+
unresolved-integer decomposition can cause rejection even if physical values
|
|
116
|
+
are approximately equal. No floating-point tolerance is used to infer equality.
|
|
117
|
+
|
|
118
|
+
## Version 1 record format
|
|
119
|
+
|
|
120
|
+
Only the outermost object has `"version": 1`; every object record has a `type`.
|
|
121
|
+
For example, an integer length is represented as:
|
|
122
|
+
|
|
123
|
+
```json
|
|
124
|
+
{
|
|
125
|
+
"version": 1,
|
|
126
|
+
"type": "quantity",
|
|
127
|
+
"value": {"type": "int", "value": "3"},
|
|
128
|
+
"unit": {
|
|
129
|
+
"type": "unit",
|
|
130
|
+
"expression": "m",
|
|
131
|
+
"identity": [["", "m", "1", "1"]],
|
|
132
|
+
"dimension": [["0", "1"], ["1", "1"], ["0", "1"], ["0", "1"], ["0", "1"], ["0", "1"], ["0", "1"]],
|
|
133
|
+
"factor": [],
|
|
134
|
+
"log_degree": ["0", "1"]
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
The object records have these exact fields:
|
|
140
|
+
|
|
141
|
+
| `type` | Fields in addition to `type` |
|
|
142
|
+
| --- | --- |
|
|
143
|
+
| `quantity`, `affine_quantity` | `value` scalar record, `unit` linear/affine unit record respectively |
|
|
144
|
+
| `unit` | `expression`, `identity`, `dimension`, `factor`, `log_degree` |
|
|
145
|
+
| `scaled_unit` | The unit fields plus `scale`, a Factor component list |
|
|
146
|
+
| `affine_unit` | `origin` linear Quantity record, `delta_unit` linear unit record, `kind` null/`power`/`amplitude`, `symbol` canonical registered string or null |
|
|
147
|
+
|
|
148
|
+
`identity` is a sorted list of `[prefix_symbol, unprefixed_symbol, numerator,
|
|
149
|
+
denominator]` terms; the last two strings encode the nonzero exponent.
|
|
150
|
+
`dimension` lists seven rational pairs in T, L, M, I, Theta, N, J order.
|
|
151
|
+
`log_degree` is another rational pair.
|
|
152
|
+
|
|
153
|
+
Factor component lists preserve the formal integer/symbolic bases and rational
|
|
154
|
+
exponents. The empty list represents one. Each component has an `exponent`
|
|
155
|
+
pair and one of these base descriptions:
|
|
156
|
+
|
|
157
|
+
| Component `type` | Other fields |
|
|
158
|
+
| --- | --- |
|
|
159
|
+
| `prime` | `value`: prime integer string |
|
|
160
|
+
| `symbolic` | `symbol`: literal label, `value`: positive finite float.hex() approximation |
|
|
161
|
+
| `unfactored` | `value`: unresolved integer string, `limit`: stored factorization-limit integer string |
|
|
162
|
+
|
|
163
|
+
Symbolic labels are reconstructed as SymbolicConstant objects, never evaluated
|
|
164
|
+
as code. Their mathematical construction requirements remain the caller's
|
|
165
|
+
responsibility. Unresolved bases keep the existing incomplete-factorization
|
|
166
|
+
limitations and emit IncompleteFactorWarning on reconstruction.
|
|
167
|
+
|
|
168
|
+
Unknown versions, tags, missing or extra fields and invalid nested records are
|
|
169
|
+
rejected. The version is independent of the package release number; future
|
|
170
|
+
incompatible record changes require a new format version. This is a data
|
|
171
|
+
format, not a Python class/subclass reconstruction protocol. No `eval`, dynamic
|
|
172
|
+
class imports, JSON encoder subclasses, file I/O or configurable codec registry
|
|
173
|
+
are involved. Applications parsing external input should apply their own size
|
|
174
|
+
and resource limits.
|
|
175
|
+
|
|
176
|
+
## Errors
|
|
177
|
+
|
|
178
|
+
Import the error classes from `unitx.exceptions`. The public boundaries are:
|
|
179
|
+
|
|
180
|
+
| Operation and failure | Exception |
|
|
181
|
+
| --- | --- |
|
|
182
|
+
| `to_dict()` receives an unsupported object or magnitude | TypeError |
|
|
183
|
+
| `from_dict()` receives a non-dictionary top-level input | TypeError |
|
|
184
|
+
| A record specifies an unsupported integer format version | VersionError |
|
|
185
|
+
| A record has no version or the version is not an integer | SerializationError |
|
|
186
|
+
| Invalid fields, missing definitions, conflicting unit meanings or failed reconstruction | SerializationError |
|
|
187
|
+
|
|
188
|
+
VersionError and SerializationError both inherit UnitxError and ValueError.
|
|
189
|
+
They are independent classes: VersionError is available for explicit version
|
|
190
|
+
checks elsewhere in unitx as well. It currently describes the record format
|
|
191
|
+
version, not the installed package version. Catch the two classes separately
|
|
192
|
+
for different recovery actions, or catch UnitxError for both:
|
|
193
|
+
|
|
194
|
+
```pycon
|
|
195
|
+
>>> from unitx.exceptions import SerializationError, UnitxError, VersionError
|
|
196
|
+
>>> try:
|
|
197
|
+
... from_dict({'version': 2})
|
|
198
|
+
... except VersionError:
|
|
199
|
+
... print('Unsupported record version')
|
|
200
|
+
Unsupported record version
|
|
201
|
+
>>> try:
|
|
202
|
+
... from_dict({'version': 1, 'type': 'unknown'})
|
|
203
|
+
... except SerializationError:
|
|
204
|
+
... print('Invalid record')
|
|
205
|
+
Invalid record
|
|
206
|
+
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
Underlying numeric, parsing and coordinate exceptions, including
|
|
210
|
+
UnitSymbolError, UnitSyntaxError and DimensionError, are retained as
|
|
211
|
+
`error.__cause__` when wrapped. Explicit record-validation errors use
|
|
212
|
+
SerializationError directly. Exceptions outside the expected validation
|
|
213
|
+
families, such as RuntimeError, are not wrapped.
|
|
214
|
+
|
|
215
|
+
The return type of `from_dict()` is a union of the supported object classes;
|
|
216
|
+
use `isinstance` when code requires a specific class. Its scalar magnitude type
|
|
217
|
+
is the supported union, since arbitrary input data cannot statically determine
|
|
218
|
+
the original numerical type.
|
|
219
|
+
|
|
220
|
+
Numerical encodings follow the Python documentation for
|
|
221
|
+
[float hexadecimal conversion](https://docs.python.org/3/library/stdtypes.html#float.hex),
|
|
222
|
+
[Decimal construction](https://docs.python.org/3/library/decimal.html#decimal.Decimal)
|
|
223
|
+
and [JSON data exchange](https://docs.python.org/3/library/json.html).
|
|
224
|
+
|
|
225
|
+
[Documentation index](README.md) · [Scalar behavior](scalars.md) · [Custom units](custom-units.md)
|
|
@@ -26,6 +26,11 @@ for its array overloads. `unitx.time` clocks return `AffineQuantity[int]`;
|
|
|
26
26
|
duration adapters use ordinary Quantity objects. `time.from_datetime()` returns
|
|
27
27
|
`AffineQuantity[int]`; `time.to_datetime()` returns an aware datetime.
|
|
28
28
|
|
|
29
|
+
`unitx.serialization.from_dict()` returns a union of the supported unit and
|
|
30
|
+
scalar quantity classes. Use `isinstance` to narrow it. The magnitude type is
|
|
31
|
+
`int | float | Fraction | Decimal`; the input record determines its runtime type.
|
|
32
|
+
See [scalar data exchange](serialization.md).
|
|
33
|
+
|
|
29
34
|
A signature such as `Quantity[float]` does not require metres rather than
|
|
30
35
|
seconds. An `Annotated` label alone does not teach a type checker dimensional
|
|
31
36
|
algebra. unitx does not currently supply dimension-parameterized quantities
|
|
@@ -4,7 +4,7 @@ build-backend = 'hatchling.build'
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = 'unitx'
|
|
7
|
-
version = '0.13.
|
|
7
|
+
version = '0.13.3'
|
|
8
8
|
description = 'Physical units, quantities, and unit conversion with exact unit algebra, type hints, and NumPy integration.'
|
|
9
9
|
readme = 'README.md'
|
|
10
10
|
requires-python = '>=3.12'
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
"src/unitx/_display.py",
|
|
4
4
|
"src/unitx/math.py",
|
|
5
5
|
"src/unitx/time.py",
|
|
6
|
+
"src/unitx/serialization.py",
|
|
6
7
|
"src/unitx/affineunit.py",
|
|
7
8
|
"src/unitx/affinequantity.py",
|
|
8
9
|
"src/unitx/_conversion.py",
|
|
@@ -18,6 +19,7 @@
|
|
|
18
19
|
"tests/typing"
|
|
19
20
|
],
|
|
20
21
|
"strict": [
|
|
22
|
+
"src/unitx/serialization.py",
|
|
21
23
|
"src/unitx/quantity.py",
|
|
22
24
|
"tests/typing",
|
|
23
25
|
"tests/numpy_typing",
|
|
@@ -7,12 +7,14 @@ __all__ = [
|
|
|
7
7
|
'DimensionError',
|
|
8
8
|
'IncompleteFactorWarning',
|
|
9
9
|
'InexactFloatWarning',
|
|
10
|
+
'SerializationError',
|
|
10
11
|
'UnitRegistryError',
|
|
11
12
|
'UnitSymbolError',
|
|
12
13
|
'UnitSymbolWarning',
|
|
13
14
|
'UnitSyntaxError',
|
|
14
15
|
'UnitxError',
|
|
15
16
|
'UnitxWarning',
|
|
17
|
+
'VersionError',
|
|
16
18
|
]
|
|
17
19
|
|
|
18
20
|
|
|
@@ -36,6 +38,14 @@ class UnitRegistryError(UnitxError):
|
|
|
36
38
|
"""Exception raised for errors in the unit registry."""
|
|
37
39
|
|
|
38
40
|
|
|
41
|
+
class SerializationError(UnitxError, ValueError):
|
|
42
|
+
"""A serialized record has an invalid format or cannot be restored."""
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class VersionError(UnitxError, ValueError):
|
|
46
|
+
"""An explicitly specified version is unsupported by the operation."""
|
|
47
|
+
|
|
48
|
+
|
|
39
49
|
# Python 3.12 warnings.warn() new API
|
|
40
50
|
_WARNING_SKIP_PREFIXES: Final = (os.path.dirname(__file__) + os.sep,)
|
|
41
51
|
|