Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

pytest fails with python 3.12b4 possibly due to Fraction formatting change #1818

Closed
mtasaka opened this issue Jul 16, 2023 · 3 comments · Fixed by #1821
Closed

pytest fails with python 3.12b4 possibly due to Fraction formatting change #1818

mtasaka opened this issue Jul 16, 2023 · 3 comments · Fixed by #1821

Comments

@mtasaka
Copy link
Contributor

mtasaka commented Jul 16, 2023

With python 3.12b4 (actually on Fedora 39, python3-3.12.0~b4-1.fc39.x86_64), pytest for pint git head ( 2d649ea ) fails as below:

$ pytest --benchmark-skip
============================= test session starts ==============================
platform linux -- Python 3.12.0b4, pytest-7.3.2, pluggy-1.0.0
benchmark: 4.0.0 (defaults: timer=time.perf_counter disable_gc=False min_rounds=5 min_time=0.000005 max_time=1.0 calibration_precision=10 warmup=False warmup_iterations=100000)
Matplotlib: 3.7.2
Freetype: 2.13.1
rootdir: /builddir/build/BUILD/GIT/pint
plugins: cov-4.0.0, benchmark-4.0.0, subtests-0.10.0, mpl-0.13
collected 2536 items / 3 skipped
....
....
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ============================
FAILED pint/testsuite/test_non_int.py::TestNonIntTypeQuantityFraction::test_to_base_units
FAILED pint/testsuite/test_non_int.py::TestNonIntTypeOffsetUnitMathFraction::test_multiplication[input_tuple0-expected_output0]
FAILED pint/testsuite/test_non_int.py::TestNonIntTypeOffsetUnitMathFraction::test_multiplication[input_tuple21-expected_output21]
FAILED pint/testsuite/test_non_int.py::TestNonIntTypeOffsetUnitMathFraction::test_multiplication[input_tuple28-expected_output28]
FAILED pint/testsuite/test_non_int.py::TestNonIntTypeOffsetUnitMathFraction::test_multiplication[input_tuple35-expected_output35]
FAILED pint/testsuite/test_non_int.py::TestNonIntTypeOffsetUnitMathFraction::test_multiplication_with_autoconvert[input_tuple0-expected_output0]
FAILED pint/testsuite/test_non_int.py::TestNonIntTypeOffsetUnitMathFraction::test_multiplication_with_autoconvert[input_tuple1-expected_output1]
FAILED pint/testsuite/test_non_int.py::TestNonIntTypeOffsetUnitMathFraction::test_multiplication_with_autoconvert[input_tuple2-expected_output2]
FAILED pint/testsuite/test_non_int.py::TestNonIntTypeOffsetUnitMathFraction::test_multiplication_with_autoconvert[input_tuple3-expected_output3]
FAILED pint/testsuite/test_non_int.py::TestNonIntTypeOffsetUnitMathFraction::test_multiplication_with_autoconvert[input_tuple4-expected_output4]
FAILED pint/testsuite/test_non_int.py::TestNonIntTypeOffsetUnitMathFraction::test_multiplication_with_autoconvert[input_tuple8-expected_output8]
FAILED pint/testsuite/test_non_int.py::TestNonIntTypeOffsetUnitMathFraction::test_multiplication_with_autoconvert[input_tuple9-expected_output9]
FAILED pint/testsuite/test_non_int.py::TestNonIntTypeOffsetUnitMathFraction::test_multiplication_with_autoconvert[input_tuple10-expected_output10]
FAILED pint/testsuite/test_non_int.py::TestNonIntTypeOffsetUnitMathFraction::test_multiplication_with_scalar[input_tuple1-expected_output1]
FAILED pint/testsuite/test_non_int.py::TestNonIntTypeOffsetUnitMathFraction::test_division_with_scalar[input_tuple1-expected_output1]
FAILED pint/testsuite/test_non_int.py::TestNonIntTypeOffsetUnitMathFraction::test_exponentiation[input_tuple3-expected_output3]
= 16 failed, 2009 passed, 503 skipped, 11 xfailed, 6 warnings, 298 subtests passed in 182.14s (0:03:02) =

... and all of the above failures are like:

______________ TestNonIntTypeQuantityFraction.test_to_base_units _______________
self = <pint.testsuite.test_non_int.TestNonIntTypeQuantityFraction object at 0x7f1635287b00>

    def test_to_base_units(self):
        x = self.Q_("1*inch")
        self.assert_quantity_almost_equal(
            x.to_base_units(), self.QP_("0.0254", "meter")
        )
        x = self.Q_("1*inch*inch")
>       self.assert_quantity_almost_equal(
            x.to_base_units(),
            self.Q_(
                self.kwargs["non_int_type"]("0.0254")
                ** self.kwargs["non_int_type"]("2.0"),
                "meter*meter",
            ),
        )

pint/testsuite/test_non_int.py:159: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
pint/testsuite/test_non_int.py:29: in assert_quantity_almost_equal
    helpers.assert_quantity_almost_equal(
pint/testing.py:66: in assert_allclose
    msg = f"Comparing {first!r} and {second!r}. "
pint/facets/plain/quantity.py:262: in __repr__
    return f"<Quantity({self._magnitude}, '{self._units}')>"
pint/util.py:608: in __format__
    return format_unit(self, spec)
pint/formatting.py:452: in format_unit
    return fmt(unit, registry=registry, **options)
pint/formatting.py:268: in format_default
    return formatter(
pint/formatting.py:386: in formatter
    pos_terms.append(power_fmt.format(key, fun(value)))
pint/formatting.py:349: in <lambda>
    fun = lambda x: exp_call(abs(x))
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = Fraction(2, 1), format_spec = 'n'

    def __format__(self, format_spec, /):
        """Format this fraction according to the given format specification."""
    
        # Backwards compatiblility with existing formatting.
        if not format_spec:
            return str(self)
    
        # Validate and parse the format specifier.
        match = _FLOAT_FORMAT_SPECIFICATION_MATCHER(format_spec)
        if match is None:
>           raise ValueError(
                f"Invalid format specifier {format_spec!r} "
                f"for object of type {type(self).__name__!r}"
E               ValueError: Invalid format specifier 'n' for object of type 'Fraction'

/usr/lib64/python3.12/fractions.py:427: ValueError
@mtasaka
Copy link
Contributor Author

mtasaka commented Jul 16, 2023

I believe this is due to python3.12 formatting change for Fraction.

With python 3.11:

$ python3
Python 3.11.4 (main, Jun  7 2023, 00:00:00) [GCC 13.1.1 20230511 (Red Hat 13.1.1-2)] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from fractions import Fraction
>>> frac = Fraction(4,5)
>>> "{:n}".format(4.0 / 5)
'0.8'
>>> "{:n}".format(frac)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unsupported format string passed to Fraction.__format__

With python 3.12b4:

$ python3
Python 3.12.0b4 (main, Jul 12 2023, 00:00:00) [GCC 13.1.1 20230614 (Red Hat 13.1.1-4)] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from fractions import Fraction
>>> frac = Fraction(4,5)
>>> "{:n}".format(4.0 / 5)
'0.8'
>>> "{:n}".format(frac)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib64/python3.12/fractions.py", line 427, in __format__
    raise ValueError(
ValueError: Invalid format specifier 'n' for object of type 'Fraction'

So python 3.11 raised TypeError, now python3.12b4 raises ValueError, possibly due to python/cpython@3e09f31#diff-f561eb7eb97f832b2698837f52c2c2cf23bdb0b31c69cf1f6aaa560280993316R417 .
Looking at the backtrace, it seems that pint is expected to catch this error at:

except TypeError:
(actually with python 3.11 the above TypeError was caught here), however with python 3.12b4 this fails.

@mtasaka
Copy link
Contributor Author

mtasaka commented Jul 16, 2023

The above errors are all resolved by the following (not knowing this is desired, although)

diff --git a/pint/testing.py b/pint/testing.py
index 126a39f..f2a570a 100644
--- a/pint/testing.py
+++ b/pint/testing.py
@@ -64,7 +64,7 @@ def assert_allclose(
     if msg is None:
         try:
             msg = f"Comparing {first!r} and {second!r}. "
-        except TypeError:
+        except (TypeError, ValueError):
             try:
                 msg = f"Comparing {first} and {second}. "
             except Exception:

@hgrecco
Copy link
Owner

hgrecco commented Jul 17, 2023

Thanks for catching this. Would you mind issuing a PR?

mtasaka added a commit to mtasaka/pint that referenced this issue Jul 17, 2023
python 3.12 supports float-style formatting for Fraction by
python/cpython#100161 .
With this change, when ":n" format specifier is used in format() for
Fraction type, this now raises ValueError instead of previous
TypeError.

To make pytest succeed with python 3.12, make
pint.testing.assert_allclose also rescue ValueError .

Fixes hgrecco#1818 .
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants