python-units 0.2.0__py3-none-any.whl → 0.3.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.
- api/public.py +1 -0
- core/deprecations.py +62 -0
- core/quantity.py +45 -6
- core/unit_definitions.py +97 -5
- models/dimension.py +27 -1
- {python_units-0.2.0.dist-info → python_units-0.3.0.dist-info}/METADATA +16 -6
- {python_units-0.2.0.dist-info → python_units-0.3.0.dist-info}/RECORD +10 -9
- {python_units-0.2.0.dist-info → python_units-0.3.0.dist-info}/WHEEL +0 -0
- {python_units-0.2.0.dist-info → python_units-0.3.0.dist-info}/licenses/LICENSE +0 -0
- {python_units-0.2.0.dist-info → python_units-0.3.0.dist-info}/top_level.txt +0 -0
api/public.py
CHANGED
core/deprecations.py
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""Deprecation helpers for compatibility APIs."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import warnings
|
|
7
|
+
from collections.abc import Callable
|
|
8
|
+
from typing import TypeVar
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
_T = TypeVar("_T")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def warn_legacy_api(name: str, replacement: str, removal_version: str) -> None:
|
|
15
|
+
"""
|
|
16
|
+
Emit a deprecation warning for a legacy public API.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
name: Deprecated API name.
|
|
20
|
+
replacement: Preferred API name or usage pattern.
|
|
21
|
+
removal_version: Planned removal release.
|
|
22
|
+
|
|
23
|
+
Returns:
|
|
24
|
+
None.
|
|
25
|
+
|
|
26
|
+
Raises:
|
|
27
|
+
None.
|
|
28
|
+
"""
|
|
29
|
+
warnings.warn(
|
|
30
|
+
"{} is deprecated; use {} instead. It is scheduled for removal in {}.".format(
|
|
31
|
+
name,
|
|
32
|
+
replacement,
|
|
33
|
+
removal_version,
|
|
34
|
+
),
|
|
35
|
+
DeprecationWarning,
|
|
36
|
+
stacklevel=4,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def deprecated_call(
|
|
41
|
+
name: str,
|
|
42
|
+
replacement: str,
|
|
43
|
+
removal_version: str,
|
|
44
|
+
callback: Callable[[], _T],
|
|
45
|
+
) -> _T:
|
|
46
|
+
"""
|
|
47
|
+
Warn for a deprecated API call and return the callback result.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
name: Deprecated API name.
|
|
51
|
+
replacement: Preferred API name or usage pattern.
|
|
52
|
+
removal_version: Planned removal release.
|
|
53
|
+
callback: Zero-argument callable that performs the actual work.
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
The callback result.
|
|
57
|
+
|
|
58
|
+
Raises:
|
|
59
|
+
Any exception raised by ``callback``.
|
|
60
|
+
"""
|
|
61
|
+
warn_legacy_api(name, replacement, removal_version)
|
|
62
|
+
return callback()
|
core/quantity.py
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
from __future__ import annotations
|
|
5
5
|
|
|
6
|
+
from core.deprecations import deprecated_call
|
|
6
7
|
from core.errors import InvalidValueError, UnitCompatibilityError, UnitOperandError
|
|
7
8
|
from core.unit_definitions import BaseUnit, SIUnit, clone_unit
|
|
8
9
|
from models.dimension import SI_DIMENSION_SYSTEM
|
|
@@ -200,8 +201,12 @@ def float_quantity(quantity: Quantity) -> Quantity:
|
|
|
200
201
|
|
|
201
202
|
def long_quantity(quantity: Quantity) -> Quantity:
|
|
202
203
|
"""Legacy compatibility helper equivalent to ``int_quantity``."""
|
|
203
|
-
|
|
204
|
-
|
|
204
|
+
return deprecated_call(
|
|
205
|
+
"long_quantity",
|
|
206
|
+
"int_quantity",
|
|
207
|
+
"1.0.0",
|
|
208
|
+
lambda: int_quantity(quantity),
|
|
209
|
+
)
|
|
205
210
|
|
|
206
211
|
|
|
207
212
|
def complex_quantity(quantity: Quantity) -> Quantity:
|
|
@@ -210,7 +215,41 @@ def complex_quantity(quantity: Quantity) -> Quantity:
|
|
|
210
215
|
return Quantity(complex(quantity.value), quantity.unit)
|
|
211
216
|
|
|
212
217
|
|
|
213
|
-
int_unit
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
218
|
+
def int_unit(quantity: Quantity) -> Quantity:
|
|
219
|
+
"""Legacy compatibility helper equivalent to ``int_quantity``."""
|
|
220
|
+
return deprecated_call(
|
|
221
|
+
"int_unit",
|
|
222
|
+
"int_quantity",
|
|
223
|
+
"1.0.0",
|
|
224
|
+
lambda: int_quantity(quantity),
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def float_unit(quantity: Quantity) -> Quantity:
|
|
229
|
+
"""Legacy compatibility helper equivalent to ``float_quantity``."""
|
|
230
|
+
return deprecated_call(
|
|
231
|
+
"float_unit",
|
|
232
|
+
"float_quantity",
|
|
233
|
+
"1.0.0",
|
|
234
|
+
lambda: float_quantity(quantity),
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def long_unit(quantity: Quantity) -> Quantity:
|
|
239
|
+
"""Legacy compatibility helper equivalent to ``int_quantity``."""
|
|
240
|
+
return deprecated_call(
|
|
241
|
+
"long_unit",
|
|
242
|
+
"int_quantity",
|
|
243
|
+
"1.0.0",
|
|
244
|
+
lambda: int_quantity(quantity),
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def complex_unit(quantity: Quantity) -> Quantity:
|
|
249
|
+
"""Legacy compatibility helper equivalent to ``complex_quantity``."""
|
|
250
|
+
return deprecated_call(
|
|
251
|
+
"complex_unit",
|
|
252
|
+
"complex_quantity",
|
|
253
|
+
"1.0.0",
|
|
254
|
+
lambda: complex_quantity(quantity),
|
|
255
|
+
)
|
core/unit_definitions.py
CHANGED
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
from __future__ import annotations
|
|
5
5
|
|
|
6
6
|
from numbers import Number
|
|
7
|
-
from
|
|
7
|
+
from types import MappingProxyType
|
|
8
|
+
from typing import Dict, Mapping
|
|
8
9
|
|
|
9
10
|
from core.errors import (
|
|
10
11
|
InvalidUnitError,
|
|
@@ -14,12 +15,17 @@ from core.errors import (
|
|
|
14
15
|
)
|
|
15
16
|
from models.dimension import Dimension, DimensionSystem, SI_DIMENSION_SYSTEM
|
|
16
17
|
|
|
17
|
-
_CANONICAL_UNITS:
|
|
18
|
+
_CANONICAL_UNITS: Mapping[Dimension, "BaseUnit"] = MappingProxyType({})
|
|
19
|
+
_REGISTERED_CANONICAL_UNITS: tuple["BaseUnit", ...] = ()
|
|
18
20
|
|
|
19
21
|
|
|
20
22
|
def register_canonical_unit(unit: "BaseUnit") -> None:
|
|
21
|
-
"""
|
|
22
|
-
|
|
23
|
+
"""Validate a preferred unit against the static canonical SI registry."""
|
|
24
|
+
require_unit_instance(unit)
|
|
25
|
+
if not any(registered_unit == unit for registered_unit in _REGISTERED_CANONICAL_UNITS):
|
|
26
|
+
raise InvalidUnitError(
|
|
27
|
+
"canonical units are statically defined and cannot be registered"
|
|
28
|
+
)
|
|
23
29
|
|
|
24
30
|
|
|
25
31
|
def require_unit_instance(unit: object) -> None:
|
|
@@ -80,7 +86,17 @@ class BaseUnit:
|
|
|
80
86
|
self._dimension = dimension
|
|
81
87
|
|
|
82
88
|
def __eq__(self, unit2: object) -> bool:
|
|
83
|
-
|
|
89
|
+
if not isinstance(unit2, BaseUnit):
|
|
90
|
+
return False
|
|
91
|
+
if self.dimension != unit2.dimension:
|
|
92
|
+
return False
|
|
93
|
+
if isinstance(self, DerivedUnit) or isinstance(unit2, DerivedUnit):
|
|
94
|
+
return (
|
|
95
|
+
isinstance(self, DerivedUnit)
|
|
96
|
+
and isinstance(unit2, DerivedUnit)
|
|
97
|
+
and self.name == unit2.name
|
|
98
|
+
)
|
|
99
|
+
return True
|
|
84
100
|
|
|
85
101
|
def _combine(self, unit2: "BaseUnit", operator_name: str) -> "BaseUnit":
|
|
86
102
|
require_unit_instance(unit2)
|
|
@@ -213,3 +229,79 @@ class DerivedUnit(BaseUnit):
|
|
|
213
229
|
if self.name:
|
|
214
230
|
return self.name
|
|
215
231
|
return self.full_units
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def _build_canonical_units() -> tuple[Mapping[Dimension, BaseUnit], tuple[BaseUnit, ...]]:
|
|
235
|
+
ampere = SIUnit.define("A")
|
|
236
|
+
candela = SIUnit.define("cd")
|
|
237
|
+
kelvin = SIUnit.define("K")
|
|
238
|
+
kilogram = SIUnit.define("kg")
|
|
239
|
+
metre = SIUnit.define("m")
|
|
240
|
+
mole = SIUnit.define("mol")
|
|
241
|
+
second = SIUnit.define("s")
|
|
242
|
+
|
|
243
|
+
newton = DerivedUnit.define("N", kilogram * metre / second / second)
|
|
244
|
+
pascal = DerivedUnit.define("Pa", newton / metre / metre)
|
|
245
|
+
joule = DerivedUnit.define("J", newton * metre)
|
|
246
|
+
watt = DerivedUnit.define("W", joule / second)
|
|
247
|
+
coulomb = DerivedUnit.define("C", second * ampere)
|
|
248
|
+
volt = DerivedUnit.define("V", watt / ampere)
|
|
249
|
+
farad = DerivedUnit.define("F", coulomb / volt)
|
|
250
|
+
ohm = DerivedUnit.define("Ω", volt / ampere)
|
|
251
|
+
siemens = DerivedUnit.define("S", ampere / volt)
|
|
252
|
+
weber = DerivedUnit.define("Wb", volt * second)
|
|
253
|
+
tesla = DerivedUnit.define("T", weber / metre / metre)
|
|
254
|
+
henry = DerivedUnit.define("H", weber / ampere)
|
|
255
|
+
steradian = DerivedUnit.define("sr", metre * metre / metre / metre)
|
|
256
|
+
lumen = DerivedUnit.define("lm", candela * steradian)
|
|
257
|
+
lux = DerivedUnit.define("lx", lumen / metre / metre)
|
|
258
|
+
|
|
259
|
+
registered_units = (
|
|
260
|
+
ampere,
|
|
261
|
+
candela,
|
|
262
|
+
kelvin,
|
|
263
|
+
kilogram,
|
|
264
|
+
metre,
|
|
265
|
+
mole,
|
|
266
|
+
second,
|
|
267
|
+
newton,
|
|
268
|
+
pascal,
|
|
269
|
+
joule,
|
|
270
|
+
watt,
|
|
271
|
+
coulomb,
|
|
272
|
+
volt,
|
|
273
|
+
farad,
|
|
274
|
+
ohm,
|
|
275
|
+
siemens,
|
|
276
|
+
weber,
|
|
277
|
+
tesla,
|
|
278
|
+
henry,
|
|
279
|
+
lumen,
|
|
280
|
+
lux,
|
|
281
|
+
)
|
|
282
|
+
preferred_units = (
|
|
283
|
+
ampere,
|
|
284
|
+
kelvin,
|
|
285
|
+
kilogram,
|
|
286
|
+
metre,
|
|
287
|
+
mole,
|
|
288
|
+
second,
|
|
289
|
+
newton,
|
|
290
|
+
pascal,
|
|
291
|
+
joule,
|
|
292
|
+
watt,
|
|
293
|
+
coulomb,
|
|
294
|
+
volt,
|
|
295
|
+
farad,
|
|
296
|
+
ohm,
|
|
297
|
+
siemens,
|
|
298
|
+
weber,
|
|
299
|
+
tesla,
|
|
300
|
+
henry,
|
|
301
|
+
lumen,
|
|
302
|
+
lux,
|
|
303
|
+
)
|
|
304
|
+
return MappingProxyType({unit.dimension: unit for unit in preferred_units}), registered_units
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
_CANONICAL_UNITS, _REGISTERED_CANONICAL_UNITS = _build_canonical_units()
|
models/dimension.py
CHANGED
|
@@ -31,6 +31,13 @@ class Dimension:
|
|
|
31
31
|
raise ValueError(
|
|
32
32
|
"dimension must define {} exponents".format(len(self.system.symbols))
|
|
33
33
|
)
|
|
34
|
+
for exponent in self.exponents:
|
|
35
|
+
if not isinstance(exponent, int) or isinstance(exponent, bool):
|
|
36
|
+
raise ValueError(
|
|
37
|
+
"dimension exponents must be integers, got {}".format(
|
|
38
|
+
type(exponent).__name__
|
|
39
|
+
)
|
|
40
|
+
)
|
|
34
41
|
|
|
35
42
|
@classmethod
|
|
36
43
|
def from_mapping(
|
|
@@ -40,11 +47,30 @@ class Dimension:
|
|
|
40
47
|
) -> "Dimension":
|
|
41
48
|
"""Construct a dimension from a base-symbol mapping."""
|
|
42
49
|
dimension_system = system or cls.default_system
|
|
50
|
+
unknown_symbols = set(mapping) - set(dimension_system.symbols)
|
|
51
|
+
if unknown_symbols:
|
|
52
|
+
raise ValueError(
|
|
53
|
+
"unknown dimension symbols: {}".format(", ".join(sorted(unknown_symbols)))
|
|
54
|
+
)
|
|
43
55
|
return cls(
|
|
44
56
|
system=dimension_system,
|
|
45
|
-
exponents=tuple(
|
|
57
|
+
exponents=tuple(
|
|
58
|
+
cls._validate_exponent(mapping.get(symbol, 0))
|
|
59
|
+
for symbol in dimension_system.symbols
|
|
60
|
+
),
|
|
46
61
|
)
|
|
47
62
|
|
|
63
|
+
@staticmethod
|
|
64
|
+
def _validate_exponent(exponent: object) -> int:
|
|
65
|
+
"""Return a valid exponent or raise for invalid exponent input."""
|
|
66
|
+
if not isinstance(exponent, int) or isinstance(exponent, bool):
|
|
67
|
+
raise ValueError(
|
|
68
|
+
"dimension exponents must be integers, got {}".format(
|
|
69
|
+
type(exponent).__name__
|
|
70
|
+
)
|
|
71
|
+
)
|
|
72
|
+
return exponent
|
|
73
|
+
|
|
48
74
|
def to_mapping(self) -> dict[str, int]:
|
|
49
75
|
"""Return a base-symbol mapping for compatibility with public APIs."""
|
|
50
76
|
return dict(zip(self.system.symbols, self.exponents))
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-units
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Python library to represent numbers with units
|
|
5
5
|
Author-email: "Paul K. Korir, PhD" <paul.korir@gmail.com>
|
|
6
6
|
License-Expression: GPL-3.0-or-later
|
|
@@ -92,8 +92,10 @@ print(u.Unit(1, u.metre))
|
|
|
92
92
|
```
|
|
93
93
|
|
|
94
94
|
The legacy `Unit` constructor remains available as a compatibility alias for
|
|
95
|
-
`Quantity` during the migration period
|
|
96
|
-
`
|
|
95
|
+
`Quantity` during the migration period. It is deprecated and scheduled for
|
|
96
|
+
removal in `1.0.0`, but it remains a true alias until then so existing type
|
|
97
|
+
checks keep working. New code should prefer `from units import Quantity` and
|
|
98
|
+
`from units.si import ...`.
|
|
97
99
|
|
|
98
100
|
The package is Python 3-only. Python 2 compatibility behavior is not part of the
|
|
99
101
|
supported interface.
|
|
@@ -146,11 +148,19 @@ Canonical unit imports:
|
|
|
146
148
|
|
|
147
149
|
Legacy compatibility helpers:
|
|
148
150
|
|
|
151
|
+
* `Unit`
|
|
149
152
|
* `long_quantity`
|
|
153
|
+
* `int_unit`
|
|
154
|
+
* `float_unit`
|
|
150
155
|
* `long_unit`
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
156
|
+
* `complex_unit`
|
|
157
|
+
|
|
158
|
+
These names remain available during the migration period and emit
|
|
159
|
+
`DeprecationWarning` when called. `Unit` remains a true alias for `Quantity` and
|
|
160
|
+
does not emit a call-time warning, because preserving `Unit is Quantity` is part
|
|
161
|
+
of the pre-`1.0.0` compatibility contract. New code should prefer `Quantity`,
|
|
162
|
+
scalar-by-unit construction, and the `*_quantity` conversion helpers. The
|
|
163
|
+
deprecated compatibility paths are scheduled for removal in `1.0.0`.
|
|
154
164
|
|
|
155
165
|
# Notes on semantics
|
|
156
166
|
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
adapters/__init__.py,sha256=CSemt7d9mX9of0YQijrkQJ9FXwzXqN0TcnCeWGhXXC4,64
|
|
2
2
|
api/__init__.py,sha256=EVoYM25GfHSWZnMigUDRsAnnyMnMUoeVZnlxlgQRfUI,72
|
|
3
|
-
api/public.py,sha256=
|
|
3
|
+
api/public.py,sha256=9iHQ3hy6S2NBAQYF9_vqLUrXlaVhSN_ibFVEZKmXjls,1759
|
|
4
4
|
api/si.py,sha256=Qx-WMSia7wxd6hnuhadTtpB8jyeVH7eK-4s98rMnlyM,2242
|
|
5
5
|
core/__init__.py,sha256=OjXKNa-EMmUZbVVyG4rLhoKfDl6ifZihCSmv27_XIMQ,811
|
|
6
|
+
core/deprecations.py,sha256=SQf13eA5dJt78CvIKuXi3vI1bBknwEPHT3FkDWh0Xmc,1436
|
|
6
7
|
core/errors.py,sha256=iwA73z53DPMPmdoGg-T_PYpyfETqbxwGpxcDYOrsxLY,609
|
|
7
|
-
core/quantity.py,sha256=
|
|
8
|
-
core/unit_definitions.py,sha256=
|
|
8
|
+
core/quantity.py,sha256=KbZowTitSwjudPDgx3cw-nnu4hnnLECxDG6T7vTjE9s,10058
|
|
9
|
+
core/unit_definitions.py,sha256=c3vbjM5-bm-wRNG6z9qJY3b54W2U5JsQM-Vpt2QnaW4,10312
|
|
9
10
|
models/__init__.py,sha256=8-39jReNyDmbbih6K5IHeqwoWfagjXzjy7nIfJBpPMI,189
|
|
10
|
-
models/dimension.py,sha256=
|
|
11
|
-
python_units-0.
|
|
11
|
+
models/dimension.py,sha256=UNlb234AaUim7iXWzc_2RZ5aZCUefZTvOGTNizoQiPc,3789
|
|
12
|
+
python_units-0.3.0.dist-info/licenses/LICENSE,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
12
13
|
services/__init__.py,sha256=tSot9S3F7fHezy3Lj4DNRMQK1ATptRaN0efKRJSgrKs,65
|
|
13
14
|
units/__init__.py,sha256=Tyj9-XgWDco8J7s_wx2jaEP3fylc2Tc6VZIbsZ1ron4,251
|
|
14
15
|
units/dimension.py,sha256=v-aiG3bDV7tJUV_xuxZSAw8UHQe0GnbybI2UkiEwuQM,221
|
|
@@ -18,7 +19,7 @@ units/si.py,sha256=2wqvff8wxQP1w-fWPYolWuIeBGl942jyX8N6dQyGwDU,118
|
|
|
18
19
|
units/unit.py,sha256=5g_azobEX4LPiO7W3n5RmpYCmZuVpZDX77Ipy9ydILk,450
|
|
19
20
|
utils/__init__.py,sha256=zTkrwGlYAf6IRcS8e_9nRyACmwJO5Ni-Lwoy55K3qCw,191
|
|
20
21
|
utils/numbers.py,sha256=_wzMOCoU2hOybqVcT-x__7eY6WkwHa97Ahhr9L5reQE,884
|
|
21
|
-
python_units-0.
|
|
22
|
-
python_units-0.
|
|
23
|
-
python_units-0.
|
|
24
|
-
python_units-0.
|
|
22
|
+
python_units-0.3.0.dist-info/METADATA,sha256=2brF79U2LZZXfM_7o-YhOdH7i8RQmqNA4LGMHpBHEJM,7377
|
|
23
|
+
python_units-0.3.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
24
|
+
python_units-0.3.0.dist-info/top_level.txt,sha256=xVEgUUcetmpTHsYk3A-xD9qAxU5S2yD0n8YW1r08tn0,46
|
|
25
|
+
python_units-0.3.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|