Skip to content

Incompatibility with Python 3.15 alpha 7 #31429

Closed

Description

@frenzymadness

We are already building RPM packages with Python 3.15 alpha 7 in Fedora Linux and I've found some issues and solutions for them.

call_count in Mock

Since python/cpython@850f95f, call_count is now calculated property, so setting call_count = 0 in tests does not work as before and does not actually reset the counter.

My solution for test_norma_callback:

--- a/lib/matplotlib/tests/test_colors.py
+++ b/lib/matplotlib/tests/test_colors.py
@@ -1590,7 +1590,9 @@ def test_norm_callback():
     assert increment.call_count == 2
 
     # We only want autoscale() calls to send out one update signal
-    increment.call_count = 0
+    # Python 3.15: Mock.call_count is now a computed property based on
+    # len(call_args_list), so setting call_count=0 no longer works.
+    increment.reset_mock()
     norm.autoscale([0, 1, 2])
     assert increment.call_count == 1

tolerances

I have to increase tolerances in test_pcolornearestunits and test_rcupdate – I don't know whether this is realted to Python 3.15 or not though.

--- a/lib/matplotlib/tests/test_axes.py
+++ b/lib/matplotlib/tests/test_axes.py
@@ -1668,7 +1668,7 @@ def test_pcolornearest(fig_test, fig_ref):
     ax.pcolormesh(x2, y2, Z, shading='nearest')
 
 
-@check_figures_equal(extensions=["png"])
+@check_figures_equal(extensions=["png"], tol=5.0)
 def test_pcolornearestunits(fig_test, fig_ref):
     ax = fig_test.subplots()
     x = [datetime.datetime.fromtimestamp(x * 3600) for x in range(10)]

and

--- a/lib/matplotlib/tests/test_backend_pgf.py
+++ b/lib/matplotlib/tests/test_backend_pgf.py
@@ -131,7 +131,7 @@ def test_rcupdate():
                 'pgf.preamble': ('\\usepackage[utf8x]{inputenc}'
                                  '\\usepackage[T1]{fontenc}'
                                  '\\usepackage{sfmath}')}]
-    tol = [0, 13.2] if _old_gs_version else [0, 0]
+    tol = [0, 13.2] if _old_gs_version else [0, 5.0]  # Increased tolerance for Python 3.15
     for i, rc_set in enumerate(rc_sets):
         with mpl.rc_context(rc_set):
             for substring, pkg in [('sfmath', 'sfmath'), ('utf8x', 'ucs')]:

Silenced deprecation warnings

I have to silence gi.PyGIDeprecationWarning: GLib.unix_signal_add_full is deprecated; use GLibUnix.signal_add_full instead and DeprecationWarning: This process (pid=13234) is multi-threaded, use of fork() may lead to deadlocks in the child.. Only the second one is coming from Python itself but both might be important for new releases of matplotlib.

--- a/lib/matplotlib/tests/test_backend_gtk3.py
+++ b/lib/matplotlib/tests/test_backend_gtk3.py
@@ -6,6 +6,8 @@ from unittest import mock
 
 
 @pytest.mark.backend("gtk3agg", skip_on_importerror=True)
+@pytest.mark.filterwarnings("ignore:.*unix_signal_add_full.*:gi.PyGIDeprecationWarning")
+@pytest.mark.filterwarnings("ignore:.*unix_signal_add_full.*:DeprecationWarning")
 def test_correct_key():
     pytest.xfail("test_widget_send_event is not triggering key_press_event")
 

and

--- a/lib/matplotlib/tests/test_font_manager.py
+++ b/lib/matplotlib/tests/test_font_manager.py
@@ -230,6 +230,9 @@ def _model_handler(_):
 
 @pytest.mark.skipif(not hasattr(os, "register_at_fork"),
                     reason="Cannot register at_fork handlers")
+# Python 3.15+ raises DeprecationWarning for fork in multi-threaded process
+@pytest.mark.filterwarnings("ignore:.*multi-threaded.*fork.*:DeprecationWarning")
+@pytest.mark.filterwarnings("ignore:.*multi-threaded.*fork.*:RuntimeWarning")
 def test_fork():
     _model_handler(0)  # Make sure the font cache is filled.
     ctx = multiprocessing.get_context("fork")

Activity

  1. timhoffm commented on Mar 31, 2026

    @timhoffm
    Member

    Thanks for the report.

    call_count - PR welcome
    tolerances - we need to investigate the image differences
    deprecation warnings - the first one is #31398; multi-threaded.fork needs separate investigation

  2. jklymak commented on Mar 31, 2026

    @jklymak
    Member

    tolerances - we need to investigate the image differences

    Yes, a tolerance of 5 is quite high - the largest in this file is 1.28, and even that seems suspicious. It would be helpful if you could generate the two images and post to make sure things are working. It's possible the test needs to be changed (perhaps due to different ticks being found), or that there is an actual bug.

  3. QuLogic commented on Mar 31, 2026

    @QuLogic
    Member

    The first issue is already fixed in #31153 and will be in the next release.

  4. frenzymadness commented on Apr 7, 2026

    @frenzymadness
    ContributorAuthor

    Ok, so for the test_pcolornearest, the 5 % tolerance is likely too much, as the images do not differ that much.

    Image
  5. frenzymadness commented on Apr 7, 2026

    @frenzymadness
    ContributorAuthor

    The same applies to rcupdate where the difference is not 0 but again very small:

    Image
  6. QuLogic commented on Apr 8, 2026

    @QuLogic
    Member

    Are you sure that latter one is Python 3.15 and not a new Ghostscript release? It seems odd that a Python update would make such a difference at all.

  7. frenzymadness commented on Apr 8, 2026

    @frenzymadness
    ContributorAuthor

    No, it does not seem to be related to Python 3.15.

  8. QuLogic commented on Apr 21, 2026

    @QuLogic
    Member

    I can only reproduce the callback and fork issue on Python 3.15 alpha in Fedora 43. The others are probably other dependencies, most likely Ghostscript.

  9. befeleme commented on May 21, 2026

    @befeleme

    I am testing matplotlib currently with Python 3.15.0b1 and see two more test failures not reported yet:

    There's an issue with timezones in test_auto_date_locator:

    ______________________ test_auto_date_locator_intmult_tz _______________________
    [gw3] linux -- Python 3.15.0 /usr/bin/python3
    
        def test_auto_date_locator_intmult_tz():
            def _create_auto_date_locator(date1, date2, tz):
                locator = mdates.AutoDateLocator(interval_multiples=True, tz=tz)
                locator.create_dummy_axis()
                locator.axis.set_view_interval(*mdates.date2num([date1, date2]))
                return locator
        
            results = ([datetime.timedelta(weeks=52*200),
                        ['1980-01-01 00:00:00-08:00', '2000-01-01 00:00:00-08:00',
                         '2020-01-01 00:00:00-08:00', '2040-01-01 00:00:00-08:00',
                         '2060-01-01 00:00:00-08:00', '2080-01-01 00:00:00-08:00',
                         '2100-01-01 00:00:00-08:00', '2120-01-01 00:00:00-08:00',
                         '2140-01-01 00:00:00-08:00', '2160-01-01 00:00:00-08:00',
                         '2180-01-01 00:00:00-08:00', '2200-01-01 00:00:00-08:00']
                        ],
                       [datetime.timedelta(weeks=52),
                        ['1997-01-01 00:00:00-08:00', '1997-02-01 00:00:00-08:00',
                         '1997-03-01 00:00:00-08:00', '1997-04-01 00:00:00-08:00',
                         '1997-05-01 00:00:00-07:00', '1997-06-01 00:00:00-07:00',
                         '1997-07-01 00:00:00-07:00', '1997-08-01 00:00:00-07:00',
                         '1997-09-01 00:00:00-07:00', '1997-10-01 00:00:00-07:00',
                         '1997-11-01 00:00:00-08:00', '1997-12-01 00:00:00-08:00']
                        ],
                       [datetime.timedelta(days=141),
                        ['1997-01-01 00:00:00-08:00', '1997-01-15 00:00:00-08:00',
                         '1997-02-01 00:00:00-08:00', '1997-02-15 00:00:00-08:00',
                         '1997-03-01 00:00:00-08:00', '1997-03-15 00:00:00-08:00',
                         '1997-04-01 00:00:00-08:00', '1997-04-15 00:00:00-07:00',
                         '1997-05-01 00:00:00-07:00', '1997-05-15 00:00:00-07:00']
                        ],
                       [datetime.timedelta(days=40),
                        ['1997-01-01 00:00:00-08:00', '1997-01-05 00:00:00-08:00',
                         '1997-01-09 00:00:00-08:00', '1997-01-13 00:00:00-08:00',
                         '1997-01-17 00:00:00-08:00', '1997-01-21 00:00:00-08:00',
                         '1997-01-25 00:00:00-08:00', '1997-01-29 00:00:00-08:00',
                         '1997-02-01 00:00:00-08:00', '1997-02-05 00:00:00-08:00',
                         '1997-02-09 00:00:00-08:00']
                        ],
                       [datetime.timedelta(hours=40),
                        ['1997-01-01 00:00:00-08:00', '1997-01-01 04:00:00-08:00',
                         '1997-01-01 08:00:00-08:00', '1997-01-01 12:00:00-08:00',
                         '1997-01-01 16:00:00-08:00', '1997-01-01 20:00:00-08:00',
                         '1997-01-02 00:00:00-08:00', '1997-01-02 04:00:00-08:00',
                         '1997-01-02 08:00:00-08:00', '1997-01-02 12:00:00-08:00',
                         '1997-01-02 16:00:00-08:00']
                        ],
                       [datetime.timedelta(minutes=20),
                        ['1997-01-01 00:00:00-08:00', '1997-01-01 00:05:00-08:00',
                         '1997-01-01 00:10:00-08:00', '1997-01-01 00:15:00-08:00',
                         '1997-01-01 00:20:00-08:00']
                        ],
                       [datetime.timedelta(seconds=40),
                        ['1997-01-01 00:00:00-08:00', '1997-01-01 00:00:05-08:00',
                         '1997-01-01 00:00:10-08:00', '1997-01-01 00:00:15-08:00',
                         '1997-01-01 00:00:20-08:00', '1997-01-01 00:00:25-08:00',
                         '1997-01-01 00:00:30-08:00', '1997-01-01 00:00:35-08:00',
                         '1997-01-01 00:00:40-08:00']
                        ]
                       )
        
            tz = dateutil.tz.gettz('Canada/Pacific')
            d1 = datetime.datetime(1997, 1, 1, tzinfo=tz)
            for t_delta, expected in results:
                with rc_context({'_internal.classic_mode': False}):
                    d2 = d1 + t_delta
                    locator = _create_auto_date_locator(d1, d2, tz)
                    st = list(map(str, mdates.num2date(locator(), tz=tz)))
    >               assert st == expected
    E               AssertionError: assert ['1980-01-01 ...0-07:00', ...] == ['1980-01-01 ...0-08:00', ...]
    E                 
    E                 At index 3 diff: '2040-01-01 00:00:00-07:00' != '2040-01-01 00:00:00-08:00'
    E                 Use -v to get more diff
    

    And an issue related to the default pprint output being changed:

    _____________________________ test_RcParams_class ______________________________
    [gw2] linux -- Python 3.15.0 /usr/bin/python3
    
        def test_RcParams_class():
            rc = mpl.RcParams({'font.cursive': ['Apple Chancery',
                                                'Textile',
                                                'Zapf Chancery',
                                                'cursive'],
                               'font.family': 'sans-serif',
                               'font.weight': 'normal',
                               'font.size': 12})
        
            expected_repr = """
        RcParams({'font.cursive': ['Apple Chancery',
                                   'Textile',
                                   'Zapf Chancery',
                                   'cursive'],
                  'font.family': ['sans-serif'],
                  'font.size': 12.0,
                  'font.weight': 'normal'})""".lstrip()
        
    >       assert expected_repr == repr(rc)
    E       assert "RcParams({'f...': 'normal'})" == "RcParams({\n...\n         })"
    E         
    E         - RcParams({
    E         -           'font.cursive': ['Apple Chancery', 'Textile', 'Zapf Chancery', 'cursive'],
    E         + RcParams({'font.cursive': ['Apple Chancery',
    E         +                            'Textile',
    E         +                            'Zapf Chancery',
    E         +                            'cursive'],...
    E         
    E         ...Full output truncated (7 lines hidden), use '-vv' to show
    
    ../BUILDROOT/usr/lib64/python3.15/site-packages/matplotlib/tests/test_rcparams.py:92: AssertionError
    
  10. QuLogic commented on May 29, 2026

    @QuLogic
    Member

    test_auto_date_locator_intmult_tz

    This one is #31780.

  11. added this to the v3.11.0 milestone on Jun 2, 2026
  12. befeleme commented on Jun 4, 2026

    @befeleme

    FTR, test_RcParams_class will probably not need changes in the end, as previous pprint defaults have been restored in beta2 (after community concerns voiced).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions