TypedUnit 0.2.18__tar.gz → 0.3.0__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.
- {typedunit-0.2.18 → typedunit-0.3.0}/PKG-INFO +7 -5
- {typedunit-0.2.18 → typedunit-0.3.0}/TypedUnit/__init__.py +1 -0
- {typedunit-0.2.18 → typedunit-0.3.0}/TypedUnit/_version.py +1 -1
- typedunit-0.3.0/TypedUnit/validation.py +73 -0
- {typedunit-0.2.18 → typedunit-0.3.0}/TypedUnit.egg-info/PKG-INFO +7 -5
- {typedunit-0.2.18 → typedunit-0.3.0}/TypedUnit.egg-info/SOURCES.txt +3 -1
- {typedunit-0.2.18 → typedunit-0.3.0}/TypedUnit.egg-info/requires.txt +7 -4
- {typedunit-0.2.18 → typedunit-0.3.0}/pyproject.toml +4 -9
- typedunit-0.3.0/tests/test_validate_quantity.py +94 -0
- {typedunit-0.2.18 → typedunit-0.3.0}/LICENSE +0 -0
- {typedunit-0.2.18 → typedunit-0.3.0}/README.rst +0 -0
- {typedunit-0.2.18 → typedunit-0.3.0}/TypedUnit/directories.py +0 -0
- {typedunit-0.2.18 → typedunit-0.3.0}/TypedUnit/helper.py +0 -0
- {typedunit-0.2.18 → typedunit-0.3.0}/TypedUnit/units.py +0 -0
- {typedunit-0.2.18 → typedunit-0.3.0}/TypedUnit/utils.py +0 -0
- {typedunit-0.2.18 → typedunit-0.3.0}/TypedUnit.egg-info/dependency_links.txt +0 -0
- {typedunit-0.2.18 → typedunit-0.3.0}/TypedUnit.egg-info/top_level.txt +0 -0
- {typedunit-0.2.18 → typedunit-0.3.0}/setup.cfg +0 -0
- {typedunit-0.2.18 → typedunit-0.3.0}/tests/test_0.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: TypedUnit
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: A package wrapping pint for handling typed units in Python.
|
|
5
5
|
Author-email: Martin Poinsinet de Sivry-Houle <martin.poinsinet.de.sivry@gmail.com>
|
|
6
6
|
License: MIT License
|
|
@@ -47,15 +47,17 @@ Requires-Dist: numpy
|
|
|
47
47
|
Requires-Dist: matplotlib
|
|
48
48
|
Requires-Dist: pint_pandas
|
|
49
49
|
Provides-Extra: release
|
|
50
|
-
Requires-Dist: martinpdes-maintenance==0.2.
|
|
50
|
+
Requires-Dist: martinpdes-maintenance==0.2.3; extra == "release"
|
|
51
51
|
Provides-Extra: testing
|
|
52
|
-
Requires-Dist:
|
|
53
|
-
Requires-Dist: pytest-cov<7,>=2; extra == "testing"
|
|
52
|
+
Requires-Dist: martinpdes-maintenance[testing]==0.2.3; extra == "testing"
|
|
54
53
|
Requires-Dist: pytest-json-report~=1.5; extra == "testing"
|
|
55
|
-
Requires-Dist:
|
|
54
|
+
Requires-Dist: pytest-cov<7; extra == "testing"
|
|
56
55
|
Requires-Dist: coverage~=7.6; extra == "testing"
|
|
57
56
|
Provides-Extra: documentation
|
|
58
57
|
Requires-Dist: MartinPdeS-docs<0.5,>=0.4.0; extra == "documentation"
|
|
58
|
+
Provides-Extra: dev
|
|
59
|
+
Requires-Dist: martinpdes-maintenance[quality]==0.2.3; extra == "dev"
|
|
60
|
+
Requires-Dist: flake8==7.3.0; extra == "dev"
|
|
59
61
|
Dynamic: license-file
|
|
60
62
|
|
|
61
63
|
TypedUnit
|
|
@@ -8,3 +8,4 @@ from TypedUnit.helper import validate_units as validate_units
|
|
|
8
8
|
from TypedUnit.units import * # noqa: F403 -- Re-export the unit module's public types.
|
|
9
9
|
from TypedUnit.units import Quantity as Quantity
|
|
10
10
|
from TypedUnit.units import ureg as ureg
|
|
11
|
+
from TypedUnit.validation import validate_quantity # noqa: F401 -- public API
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"""Direct validation of Pint quantities without modifying their values."""
|
|
2
|
+
|
|
3
|
+
# ruff: noqa: I001
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
import numpy as np
|
|
6
|
+
from pint import Quantity, UnitRegistry
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def validate_quantity(
|
|
10
|
+
*,
|
|
11
|
+
value: Quantity,
|
|
12
|
+
unit: str,
|
|
13
|
+
name: str,
|
|
14
|
+
registry: UnitRegistry | None = None,
|
|
15
|
+
shape: tuple[int, ...] | None = None,
|
|
16
|
+
positive: bool = False,
|
|
17
|
+
nonnegative: bool = False,
|
|
18
|
+
) -> None:
|
|
19
|
+
"""Validate a quantity's registry, dimensions, shape, and magnitudes.
|
|
20
|
+
|
|
21
|
+
Parameters
|
|
22
|
+
----------
|
|
23
|
+
value : pint.Quantity
|
|
24
|
+
Quantity to validate; it is not changed or replaced.
|
|
25
|
+
unit : str
|
|
26
|
+
Unit compatible with the required dimensionality. Magnitudes are
|
|
27
|
+
checked after conversion to this unit.
|
|
28
|
+
name : str
|
|
29
|
+
Parameter name included in error messages.
|
|
30
|
+
registry : pint.UnitRegistry or None, optional
|
|
31
|
+
If supplied, require a quantity created by this exact registry. None
|
|
32
|
+
accepts quantities from any Pint registry. Default is None.
|
|
33
|
+
shape : tuple of int or None, optional
|
|
34
|
+
Required magnitude shape. An empty tuple requires a scalar; None leaves
|
|
35
|
+
the shape unrestricted. Default is None.
|
|
36
|
+
positive : bool, optional
|
|
37
|
+
Require every magnitude to be strictly positive. Default is False.
|
|
38
|
+
nonnegative : bool, optional
|
|
39
|
+
Require every magnitude to be nonnegative. Default is False.
|
|
40
|
+
|
|
41
|
+
Returns
|
|
42
|
+
-------
|
|
43
|
+
None
|
|
44
|
+
The supplied quantity is preserved and no converted value is returned.
|
|
45
|
+
|
|
46
|
+
Raises
|
|
47
|
+
------
|
|
48
|
+
TypeError
|
|
49
|
+
If value is not a Pint quantity or belongs to a different registry.
|
|
50
|
+
pint.DimensionalityError
|
|
51
|
+
If the supplied units are incompatible with unit.
|
|
52
|
+
ValueError
|
|
53
|
+
If magnitudes are nonfinite or violate shape or sign requirements.
|
|
54
|
+
"""
|
|
55
|
+
if not isinstance(value, Quantity):
|
|
56
|
+
raise TypeError(f"{name} must be a Pint quantity.")
|
|
57
|
+
|
|
58
|
+
if registry is not None and not isinstance(value, registry.Quantity):
|
|
59
|
+
raise TypeError(f"{name} must belong to the specified unit registry.")
|
|
60
|
+
|
|
61
|
+
magnitude = np.asarray(value.to(unit).magnitude, dtype=float)
|
|
62
|
+
|
|
63
|
+
if shape is not None and magnitude.shape != shape:
|
|
64
|
+
raise ValueError(f"{name} must have shape {shape}.")
|
|
65
|
+
|
|
66
|
+
if not np.all(np.isfinite(magnitude)):
|
|
67
|
+
raise ValueError(f"{name} must be finite.")
|
|
68
|
+
|
|
69
|
+
if positive and np.any(magnitude <= 0):
|
|
70
|
+
raise ValueError(f"{name} must be positive.")
|
|
71
|
+
|
|
72
|
+
if nonnegative and np.any(magnitude < 0):
|
|
73
|
+
raise ValueError(f"{name} must be nonnegative.")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: TypedUnit
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: A package wrapping pint for handling typed units in Python.
|
|
5
5
|
Author-email: Martin Poinsinet de Sivry-Houle <martin.poinsinet.de.sivry@gmail.com>
|
|
6
6
|
License: MIT License
|
|
@@ -47,15 +47,17 @@ Requires-Dist: numpy
|
|
|
47
47
|
Requires-Dist: matplotlib
|
|
48
48
|
Requires-Dist: pint_pandas
|
|
49
49
|
Provides-Extra: release
|
|
50
|
-
Requires-Dist: martinpdes-maintenance==0.2.
|
|
50
|
+
Requires-Dist: martinpdes-maintenance==0.2.3; extra == "release"
|
|
51
51
|
Provides-Extra: testing
|
|
52
|
-
Requires-Dist:
|
|
53
|
-
Requires-Dist: pytest-cov<7,>=2; extra == "testing"
|
|
52
|
+
Requires-Dist: martinpdes-maintenance[testing]==0.2.3; extra == "testing"
|
|
54
53
|
Requires-Dist: pytest-json-report~=1.5; extra == "testing"
|
|
55
|
-
Requires-Dist:
|
|
54
|
+
Requires-Dist: pytest-cov<7; extra == "testing"
|
|
56
55
|
Requires-Dist: coverage~=7.6; extra == "testing"
|
|
57
56
|
Provides-Extra: documentation
|
|
58
57
|
Requires-Dist: MartinPdeS-docs<0.5,>=0.4.0; extra == "documentation"
|
|
58
|
+
Provides-Extra: dev
|
|
59
|
+
Requires-Dist: martinpdes-maintenance[quality]==0.2.3; extra == "dev"
|
|
60
|
+
Requires-Dist: flake8==7.3.0; extra == "dev"
|
|
59
61
|
Dynamic: license-file
|
|
60
62
|
|
|
61
63
|
TypedUnit
|
|
@@ -7,9 +7,11 @@ TypedUnit/directories.py
|
|
|
7
7
|
TypedUnit/helper.py
|
|
8
8
|
TypedUnit/units.py
|
|
9
9
|
TypedUnit/utils.py
|
|
10
|
+
TypedUnit/validation.py
|
|
10
11
|
TypedUnit.egg-info/PKG-INFO
|
|
11
12
|
TypedUnit.egg-info/SOURCES.txt
|
|
12
13
|
TypedUnit.egg-info/dependency_links.txt
|
|
13
14
|
TypedUnit.egg-info/requires.txt
|
|
14
15
|
TypedUnit.egg-info/top_level.txt
|
|
15
|
-
tests/test_0.py
|
|
16
|
+
tests/test_0.py
|
|
17
|
+
tests/test_validate_quantity.py
|
|
@@ -3,15 +3,18 @@ numpy
|
|
|
3
3
|
matplotlib
|
|
4
4
|
pint_pandas
|
|
5
5
|
|
|
6
|
+
[dev]
|
|
7
|
+
martinpdes-maintenance[quality]==0.2.3
|
|
8
|
+
flake8==7.3.0
|
|
9
|
+
|
|
6
10
|
[documentation]
|
|
7
11
|
MartinPdeS-docs<0.5,>=0.4.0
|
|
8
12
|
|
|
9
13
|
[release]
|
|
10
|
-
martinpdes-maintenance==0.2.
|
|
14
|
+
martinpdes-maintenance==0.2.3
|
|
11
15
|
|
|
12
16
|
[testing]
|
|
13
|
-
|
|
14
|
-
pytest-cov<7,>=2
|
|
17
|
+
martinpdes-maintenance[testing]==0.2.3
|
|
15
18
|
pytest-json-report~=1.5
|
|
16
|
-
|
|
19
|
+
pytest-cov<7
|
|
17
20
|
coverage~=7.6
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "TypedUnit"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.3.0"
|
|
8
8
|
description = "A package wrapping pint for handling typed units in Python."
|
|
9
9
|
license = {file = "LICENSE"}
|
|
10
10
|
readme = "README.rst"
|
|
@@ -42,18 +42,13 @@ Documentation = "https://martinpdes.github.io/TypedUnit/"
|
|
|
42
42
|
Repository = "https://github.com/MartinPdeS/TypedUnit.git"
|
|
43
43
|
|
|
44
44
|
[project.optional-dependencies]
|
|
45
|
-
release = ["martinpdes-maintenance==0.2.
|
|
46
|
-
testing = [
|
|
47
|
-
"pytest >=7.4,<10.0",
|
|
48
|
-
"pytest-cov >=2,<7",
|
|
49
|
-
"pytest-json-report ~=1.5",
|
|
50
|
-
"flake8 ==7.3.0",
|
|
51
|
-
"coverage ~=7.6"
|
|
52
|
-
]
|
|
45
|
+
release = ["martinpdes-maintenance==0.2.3"]
|
|
46
|
+
testing = ["martinpdes-maintenance[testing]==0.2.3", "pytest-json-report ~=1.5", "pytest-cov<7", "coverage ~=7.6"]
|
|
53
47
|
|
|
54
48
|
documentation = [
|
|
55
49
|
"MartinPdeS-docs>=0.4.0,<0.5",
|
|
56
50
|
]
|
|
51
|
+
dev = ["martinpdes-maintenance[quality]==0.2.3", "flake8 ==7.3.0"]
|
|
57
52
|
|
|
58
53
|
[tool.ruff]
|
|
59
54
|
target-version = "py310"
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"""Direct quantity validation across registries and array shapes."""
|
|
2
|
+
|
|
3
|
+
# ruff: noqa: I001
|
|
4
|
+
import numpy as np
|
|
5
|
+
import pint
|
|
6
|
+
import pytest
|
|
7
|
+
from TypedUnit import Length, validate_quantity, validate_units
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def test_valid_values_are_not_replaced_or_mutated() -> None:
|
|
11
|
+
"""Compatible alternate units and array shapes preserve the input."""
|
|
12
|
+
registry = pint.UnitRegistry()
|
|
13
|
+
value = np.array([1.0, 2.0]) * registry.mm
|
|
14
|
+
assert (
|
|
15
|
+
validate_quantity(
|
|
16
|
+
value=value,
|
|
17
|
+
unit="m",
|
|
18
|
+
name="lengths",
|
|
19
|
+
registry=registry,
|
|
20
|
+
shape=(2,),
|
|
21
|
+
positive=True,
|
|
22
|
+
)
|
|
23
|
+
is None
|
|
24
|
+
)
|
|
25
|
+
np.testing.assert_array_equal(value.magnitude, [1.0, 2.0])
|
|
26
|
+
assert value.units == registry.mm
|
|
27
|
+
validate_quantity(value=2 * registry.cm, unit="m", name="length", shape=())
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def test_registry_is_optional_but_explicit_registry_is_enforced() -> None:
|
|
31
|
+
"""Foreign registries are accepted by default and rejected when specified."""
|
|
32
|
+
first = pint.UnitRegistry()
|
|
33
|
+
second = pint.UnitRegistry()
|
|
34
|
+
validate_quantity(value=1 * first.mm, unit="m", name="length")
|
|
35
|
+
with pytest.raises(TypeError, match="specified unit registry"):
|
|
36
|
+
validate_quantity(
|
|
37
|
+
value=1 * first.mm,
|
|
38
|
+
unit="m",
|
|
39
|
+
name="length",
|
|
40
|
+
registry=second,
|
|
41
|
+
)
|
|
42
|
+
with pytest.raises(TypeError, match="length"):
|
|
43
|
+
validate_quantity(value=1, unit="m", name="length")
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def test_units_and_shape_are_checked() -> None:
|
|
47
|
+
"""Wrong dimensions propagate Pint errors and scalar shape is explicit."""
|
|
48
|
+
registry = pint.UnitRegistry()
|
|
49
|
+
with pytest.raises(pint.DimensionalityError):
|
|
50
|
+
validate_quantity(value=1 * registry.second, unit="m", name="length")
|
|
51
|
+
with pytest.raises(ValueError, match="shape"):
|
|
52
|
+
validate_quantity(
|
|
53
|
+
value=np.array([1]) * registry.mm,
|
|
54
|
+
unit="m",
|
|
55
|
+
name="length",
|
|
56
|
+
shape=(),
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@pytest.mark.parametrize("value", [np.nan, np.inf, -np.inf])
|
|
61
|
+
def test_nonfinite_values_rejected(value: float) -> None:
|
|
62
|
+
"""Nonfinite values are rejected independently of sign requirements."""
|
|
63
|
+
registry = pint.UnitRegistry()
|
|
64
|
+
with pytest.raises(ValueError, match="finite"):
|
|
65
|
+
validate_quantity(value=value * registry.mm, unit="m", name="length")
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def test_sign_requirements_apply_to_every_element() -> None:
|
|
69
|
+
"""Strict positivity rejects zero while nonnegativity permits it."""
|
|
70
|
+
registry = pint.UnitRegistry()
|
|
71
|
+
zero = np.array([0, 1]) * registry.mm
|
|
72
|
+
validate_quantity(value=zero, unit="m", name="length", nonnegative=True)
|
|
73
|
+
with pytest.raises(ValueError, match="positive"):
|
|
74
|
+
validate_quantity(value=zero, unit="m", name="length", positive=True)
|
|
75
|
+
with pytest.raises(ValueError, match="nonnegative"):
|
|
76
|
+
validate_quantity(
|
|
77
|
+
value=np.array([1, -1]) * registry.mm,
|
|
78
|
+
unit="m",
|
|
79
|
+
name="length",
|
|
80
|
+
nonnegative=True,
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def test_existing_validate_units_decorator_remains_available() -> None:
|
|
85
|
+
"""The separate direct validator does not replace the decorator API."""
|
|
86
|
+
registry = pint.UnitRegistry()
|
|
87
|
+
|
|
88
|
+
@validate_units
|
|
89
|
+
def identity(length: Length) -> Length:
|
|
90
|
+
return length
|
|
91
|
+
|
|
92
|
+
assert identity(2 * registry.mm) == 2 * registry.mm
|
|
93
|
+
with pytest.raises(ValueError):
|
|
94
|
+
identity(2 * registry.second)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|