Skip to content

Commit

Permalink
Merge pull request #2135 from hgrecco/_testing_improve
Browse files Browse the repository at this point in the history
Fix small annoyances across different test to clean up the log.
  • Loading branch information
hgrecco authored Feb 15, 2025
2 parents 6e91d5c + d7c69f7 commit 645158a
Show file tree
Hide file tree
Showing 9 changed files with 94 additions and 61 deletions.
62 changes: 39 additions & 23 deletions pint/delegates/formatter/_spec_helpers.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
"""
pint.delegates.formatter._spec_helpers
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
pint.delegates.formatter._spec_helpers
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Convenient functions to deal with format specifications.
Convenient functions to deal with format specifications.
:copyright: 2022 by Pint Authors, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
:copyright: 2022 by Pint Authors, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""

from __future__ import annotations
Expand Down Expand Up @@ -87,45 +87,61 @@ def remove_custom_flags(spec: str) -> str:
return spec


##########
# This weird way of defining split format
# is the only reasonable way I foudn to use
# lru_cache in a function that might emit warning
# and do it every time.
# TODO: simplify it when there are no warnings.


@functools.lru_cache
def split_format(
def _split_format(
spec: str, default: str, separate_format_defaults: bool = True
) -> tuple[str, str]:
) -> tuple[str, str, list[str]]:
"""Split format specification into magnitude and unit format."""
mspec = remove_custom_flags(spec)
uspec = extract_custom_flags(spec)

default_mspec = remove_custom_flags(default)
default_uspec = extract_custom_flags(default)

warns = []
if separate_format_defaults in (False, None):
# should we warn always or only if there was no explicit choice?
# Given that we want to eventually remove the flag again, I'd say yes?
if spec and separate_format_defaults is None:
if not uspec and default_uspec:
warnings.warn(
(
"The given format spec does not contain a unit formatter."
" Falling back to the builtin defaults, but in the future"
" the unit formatter specified in the `default_format`"
" attribute will be used instead."
),
DeprecationWarning,
warns.append(
"The given format spec does not contain a unit formatter."
" Falling back to the builtin defaults, but in the future"
" the unit formatter specified in the `default_format`"
" attribute will be used instead."
)
if not mspec and default_mspec:
warnings.warn(
(
"The given format spec does not contain a magnitude formatter."
" Falling back to the builtin defaults, but in the future"
" the magnitude formatter specified in the `default_format`"
" attribute will be used instead."
),
DeprecationWarning,
warns.append(
"The given format spec does not contain a magnitude formatter."
" Falling back to the builtin defaults, but in the future"
" the magnitude formatter specified in the `default_format`"
" attribute will be used instead."
)
elif not spec:
mspec, uspec = default_mspec, default_uspec
else:
mspec = mspec or default_mspec
uspec = uspec or default_uspec

return mspec, uspec, warns


def split_format(
spec: str, default: str, separate_format_defaults: bool = True
) -> tuple[str, str]:
"""Split format specification into magnitude and unit format."""

mspec, uspec, warns = _split_format(spec, default, separate_format_defaults)

for warn_msg in warns:
warnings.warn(warn_msg, DeprecationWarning)

return mspec, uspec
20 changes: 16 additions & 4 deletions pint/testing.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,10 +73,16 @@ def assert_equal(first, second, msg: str | None = None) -> None:
if isinstance(m1, ndarray) or isinstance(m2, ndarray):
np.testing.assert_array_equal(m1, m2, err_msg=msg)
elif not isinstance(m1, Number):
warnings.warn("In assert_equal, m1 is not a number ", UserWarning)
warnings.warn(
f"In assert_equal, m1 is not a number {first} ({m1}) vs. {second} ({m2}) ",
UserWarning,
)
return
elif not isinstance(m2, Number):
warnings.warn("In assert_equal, m2 is not a number ", UserWarning)
warnings.warn(
f"In assert_equal, m2 is not a number {first} ({m1}) vs. {second} ({m2}) ",
UserWarning,
)
return
elif math.isnan(m1):
assert math.isnan(m2), msg
Expand Down Expand Up @@ -131,10 +137,16 @@ def assert_allclose(
if isinstance(m1, ndarray) or isinstance(m2, ndarray):
np.testing.assert_allclose(m1, m2, rtol=rtol, atol=atol, err_msg=msg)
elif not isinstance(m1, Number):
warnings.warn("In assert_equal, m1 is not a number ", UserWarning)
warnings.warn(
f"In assert_equal, m1 is not a number {first} ({m1}) vs. {second} ({m2}) ",
UserWarning,
)
return
elif not isinstance(m2, Number):
warnings.warn("In assert_equal, m2 is not a number ", UserWarning)
warnings.warn(
f"In assert_equal, m1 is not a number {first} ({m1}) vs. {second} ({m2}) ",
UserWarning,
)
return
elif math.isnan(m1):
assert math.isnan(m2), msg
Expand Down
2 changes: 1 addition & 1 deletion pint/testsuite/test_babel.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ def test_no_babel(func_registry):
ureg = func_registry
distance = 24.0 * ureg.meter
with pytest.raises(Exception):
distance.format_babel(locale="fr_FR", length="long")
ureg.formatter.format_unit_babel(distance, locale="fr_FR", length="long")


@helpers.requires_babel(["fr_FR", "ro_RO"])
Expand Down
30 changes: 3 additions & 27 deletions pint/testsuite/test_issues.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,29 +70,6 @@ def test_issue37(self, module_registry):
np.testing.assert_array_equal(qq.magnitude, x * m)
assert qq.units == module_registry.meter.units

@pytest.mark.xfail
@helpers.requires_numpy
def test_issue39(self, module_registry):
x = np.matrix([[1, 2, 3], [1, 2, 3], [1, 2, 3]])
q = module_registry.meter * x
assert isinstance(q, module_registry.Quantity)
np.testing.assert_array_equal(q.magnitude, x)
assert q.units == module_registry.meter.units
q = x * module_registry.meter
assert isinstance(q, module_registry.Quantity)
np.testing.assert_array_equal(q.magnitude, x)
assert q.units == module_registry.meter.units

m = np.matrix(2 * np.ones(3, 3))
qq = q * m
assert isinstance(qq, module_registry.Quantity)
np.testing.assert_array_equal(qq.magnitude, x * m)
assert qq.units == module_registry.meter.units
qq = m * q
assert isinstance(qq, module_registry.Quantity)
np.testing.assert_array_equal(qq.magnitude, x * m)
assert qq.units == module_registry.meter.units

@helpers.requires_numpy
def test_issue44(self, module_registry):
x = 4.0 * module_registry.dimensionless
Expand Down Expand Up @@ -909,7 +886,7 @@ def test_issue1963(self, module_registry):
assert_equal(1e2 * b, a)
assert_equal(c, 50 * a)

assert_equal((1 * ureg.milligram) / (1 * ureg.gram), ureg.permille)
assert_equal((1 * ureg.milligram) / (1 * ureg.gram), 1 * ureg.permille)

@pytest.mark.xfail
@helpers.requires_uncertainties()
Expand Down Expand Up @@ -1230,7 +1207,7 @@ def test_issue_1845():
def test_issues_1841(func_registry, units, spec, expected):
ur = func_registry
ur.formatter.default_sort_func = sort_by_dimensionality
ur.default_format = spec
ur.formatter.default_format = spec
value = ur.Unit(UnitsContainer(**units))
assert f"{value}" == expected

Expand All @@ -1242,7 +1219,7 @@ def test_issues_1841_xfail():

# sets compact display mode by default
ur = UnitRegistry()
ur.default_format = "~P"
ur.formatter.default_format = "~P"
ur.formatter.default_sort_func = sort_by_dimensionality

q = ur.Quantity("2*pi radian * hour")
Expand Down Expand Up @@ -1297,7 +1274,6 @@ def test_issue2017():

@fmt.register_unit_format("test")
def _test_format(unit, registry, **options):
print("format called")
proc = {u.replace("µ", "u"): e for u, e in unit.items()}
return fmt.formatter(
proc.items(),
Expand Down
2 changes: 1 addition & 1 deletion pint/testsuite/test_measurement.py
Original file line number Diff line number Diff line change
Expand Up @@ -296,5 +296,5 @@ def test_tokenization(self):

pint_eval.tokenizer = pint_eval.uncertainty_tokenizer
for p in pint_eval.tokenizer("8 + / - 4"):
print(p)
str(p)
assert True
7 changes: 5 additions & 2 deletions pint/testsuite/test_numpy.py
Original file line number Diff line number Diff line change
Expand Up @@ -440,6 +440,7 @@ def test_cross(self):

# NP2: Remove this when we only support np>=2.0
@helpers.requires_array_function_protocol()
@helpers.requires_numpy_previous_than("2.0")
def test_trapz(self):
helpers.assert_quantity_equal(
np.trapz([1.0, 2.0, 3.0, 4.0] * self.ureg.J, dx=1 * self.ureg.m),
Expand Down Expand Up @@ -1227,8 +1228,10 @@ def test_copyto(self):
helpers.assert_quantity_equal(q, self.Q_([[2, 2], [6, 4]], "m"))
np.copyto(q, 0, where=[[False, False], [True, False]])
helpers.assert_quantity_equal(q, self.Q_([[2, 2], [0, 4]], "m"))
np.copyto(a, q)
self.assertNDArrayEqual(a, np.array([[2, 2], [0, 4]]))
with pytest.warns(UnitStrippedWarning):
# as a is not quantity, the unit is stripped.
np.copyto(a, q)
self.assertNDArrayEqual(a, np.array([[2, 2], [0, 4]]))

@helpers.requires_array_function_protocol()
def test_tile(self):
Expand Down
25 changes: 25 additions & 0 deletions pint/testsuite/test_numpy_func.py
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,7 @@ def test_numpy_wrap(self):
# TODO (#905 follow-up): test that NotImplemented is returned when upcast types
# present

@helpers.requires_numpy_previous_than("2.0")
def test_trapz(self):
with ExitStack() as stack:
stack.callback(
Expand All @@ -210,12 +211,36 @@ def test_trapz(self):
np.trapz(t, x=z), self.Q_(1108.6, "kelvin meter")
)

@helpers.requires_numpy_at_least("2.0")
def test_trapezoid(self):
with ExitStack() as stack:
stack.callback(
setattr,
self.ureg,
"autoconvert_offset_to_baseunit",
self.ureg.autoconvert_offset_to_baseunit,
)
self.ureg.autoconvert_offset_to_baseunit = True
t = self.Q_(np.array([0.0, 4.0, 8.0]), "degC")
z = self.Q_(np.array([0.0, 2.0, 4.0]), "m")
helpers.assert_quantity_equal(
np.trapezoid(t, x=z), self.Q_(1108.6, "kelvin meter")
)

@helpers.requires_numpy_previous_than("2.0")
def test_trapz_no_autoconvert(self):
t = self.Q_(np.array([0.0, 4.0, 8.0]), "degC")
z = self.Q_(np.array([0.0, 2.0, 4.0]), "m")
with pytest.raises(OffsetUnitCalculusError):
np.trapz(t, x=z)

@helpers.requires_numpy_at_least("2.0")
def test_trapezoid_no_autoconvert(self):
t = self.Q_(np.array([0.0, 4.0, 8.0]), "degC")
z = self.Q_(np.array([0.0, 2.0, 4.0]), "m")
with pytest.raises(OffsetUnitCalculusError):
np.trapezoid(t, x=z)

def test_correlate(self):
a = self.Q_(np.array([1, 2, 3]), "m")
v = self.Q_(np.array([0, 1, 0.5]), "s")
Expand Down
6 changes: 3 additions & 3 deletions pint/testsuite/test_quantity.py
Original file line number Diff line number Diff line change
Expand Up @@ -273,27 +273,27 @@ def test_default_formatting(self, subtests):
ureg.formatter.default_format = spec
assert f"{x}" == result

@pytest.mark.xfail(reason="Still not clear how default formatting will work.")
def test_formatting_override_default_units(self):
ureg = UnitRegistry()
ureg.formatter.default_format = "~"
x = ureg.Quantity(4, "m ** 2")

assert f"{x:dP}" == "4 meter²"
ureg.separate_format_defaults = None
with pytest.warns(DeprecationWarning):
assert f"{x:d}" == "4 meter ** 2"

ureg.separate_format_defaults = True
with assert_no_warnings():
assert f"{x:d}" == "4 m ** 2"

@pytest.mark.xfail(reason="Still not clear how default formatting will work.")
def test_formatting_override_default_magnitude(self):
ureg = UnitRegistry()
ureg.formatter.default_format = ".2f"
x = ureg.Quantity(4, "m ** 2")

assert f"{x:dP}" == "4 meter²"
ureg.separate_format_defaults = None
with pytest.warns(DeprecationWarning):
assert f"{x:D}" == "4 meter ** 2"

Expand Down Expand Up @@ -842,7 +842,7 @@ def test_nonnumeric_magnitudes(self):
ureg = self.ureg
x = "some string" * ureg.m
with pytest.warns(UndefinedBehavior):
self.compare_quantity_compact(x, x)
x.to_compact()

def test_very_large_to_compact(self):
# This should not raise an IndexError
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ cache-keys = [{ file = "pyproject.toml" }, { git = true }]

[tool.pytest.ini_options]
addopts = "--import-mode=importlib"
xfail_strict = true
pythonpath = "."

[tool.ruff.format]
Expand Down

0 comments on commit 645158a

Please sign in to comment.