Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-joblib for openSUSE:Factory checked in at 2026-02-26 18:49:58 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-joblib (Old) and /work/SRC/openSUSE:Factory/.python-joblib.new.29461 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-joblib" Thu Feb 26 18:49:58 2026 rev:32 rq:1334686 version:1.5.3 Changes: -------- --- /work/SRC/openSUSE:Factory/python-joblib/python-joblib.changes 2025-09-11 14:39:29.712189070 +0200 +++ /work/SRC/openSUSE:Factory/.python-joblib.new.29461/python-joblib.changes 2026-02-26 18:50:00.428909034 +0100 @@ -1,0 +2,16 @@ +Tue Feb 24 09:57:31 UTC 2026 - Daniel Garcia <[email protected]> + +- Update to 1.5.3 + * The ``Memory`` object won't overwrite an already existing + ``.gitignore`` file in its cache directory anymore. + * Harden the safety checks in ``eval_expr(pre_dispatch)`` to prevent + excessive memory allocation and potential crashes by limiting the + allowed length of the expression and the maximum numeric value of + sub-expressions and not evaluating expressions with non-numeric + literals. + * Vendor cloudpickle 3.1.2 to fix a pickling problem with + interactively defined abstract base classes and type annotations + in Python 3.14+. +- Add fix-tests-numpy-2.4.patch upstream patch gh#joblib/joblib#1770 + +------------------------------------------------------------------- Old: ---- joblib-1.5.2.tar.gz New: ---- fix-tests-numpy-2.4.patch joblib-1.5.3.tar.gz ----------(New B)---------- New: in Python 3.14+. - Add fix-tests-numpy-2.4.patch upstream patch gh#joblib/joblib#1770 ----------(New E)---------- ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-joblib.spec ++++++ --- /var/tmp/diff_new_pack.WhiE56/_old 2026-02-26 18:50:01.756964417 +0100 +++ /var/tmp/diff_new_pack.WhiE56/_new 2026-02-26 18:50:01.760964584 +0100 @@ -1,7 +1,7 @@ # # spec file for package python-joblib # -# Copyright (c) 2025 SUSE LLC +# Copyright (c) 2026 SUSE LLC and contributors # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -18,7 +18,7 @@ %{?sle15_python_module_pythons} Name: python-joblib -Version: 1.5.2 +Version: 1.5.3 Release: 0 Summary: Module for using Python functions as pipeline jobs License: BSD-3-Clause @@ -27,6 +27,8 @@ # PATCH-FIX-OPENSUSE Also avoid a DeprecationWarning when using fork() under # multiprocessing Patch1: also-filter-new-fork-warning.patch +# PATCH-FIX-UPSTREAM fix-tests-numpy-2.4.patch gh#joblib/joblib#1770 +Patch2: fix-tests-numpy-2.4.patch BuildRequires: %{python_module base >= 3.7} BuildRequires: %{python_module lz4} BuildRequires: %{python_module numpy} ++++++ fix-tests-numpy-2.4.patch ++++++ >From c559b8feace27e24767f4b2cf84025bf1acdc2bd Mon Sep 17 00:00:00 2001 From: Nanored4498 <[email protected]> Date: Thu, 8 Jan 2026 17:21:11 +0100 Subject: [PATCH 1/8] repare CI --- joblib/test/test_numpy_pickle.py | 54 ++++++++++++++++++++++---------- 1 file changed, 37 insertions(+), 17 deletions(-) diff --git a/joblib/test/test_numpy_pickle.py b/joblib/test/test_numpy_pickle.py index ed320497b..b648f53c3 100644 --- a/joblib/test/test_numpy_pickle.py +++ b/joblib/test/test_numpy_pickle.py @@ -416,6 +416,22 @@ def _check_pickle(filename, expected_list, mmap_mode=None): if (re.search("_0.1.+.pkl$", filename_base) and mmap_mode is not None) else 0 ) + + if np.__version__.split(".") >= "2.4.0".split("."): + prefix = "joblib_" + version_index = filename_base.find(prefix) + len(prefix) + joblib_version = filename_base[version_index:] + + def check_version(v): + return joblib_version.startswith(v) + + if check_version("0.9."): + expected_nb_user_warnings += 1 + if "compressed" in filename_base: + expected_nb_user_warnings += 2 + elif check_version("0.10.") or check_version("0.11."): + expected_nb_user_warnings += 4 + expected_nb_warnings = ( expected_nb_deprecation_warnings + expected_nb_user_warnings ) @@ -425,23 +441,27 @@ def _check_pickle(filename, expected_list, mmap_mode=None): f"{[w.message for w in warninfo]}" ) - deprecation_warnings = [ - w for w in warninfo if issubclass(w.category, DeprecationWarning) - ] - user_warnings = [w for w in warninfo if issubclass(w.category, UserWarning)] - for w in deprecation_warnings: - assert ( - str(w.message) - == "The file '{0}' has been generated with a joblib " - "version less than 0.10. Please regenerate this " - "pickle file.".format(filename) - ) - - for w in user_warnings: - escaped_filename = re.escape(filename) - assert re.search( - f"memmapped.+{escaped_filename}.+segmentation fault", str(w.message) - ) + for w in warninfo: + if issubclass(w.category, DeprecationWarning): + assert ( + str(w.message) + == "The file '{0}' has been generated with a joblib " + "version less than 0.10. Please regenerate this " + "pickle file.".format(filename) + ) + elif issubclass(w.category, np.exceptions.VisibleDeprecationWarning): + assert ( + str(w.message) + == "dtype(): align should be passed as Python or NumPy " + "boolean but got `align=0`. Did you mean to pass a tuple " + "to create a subarray type? (Deprecated NumPy 2.4)" + ) + elif issubclass(w.category, UserWarning): + escaped_filename = re.escape(filename) + assert re.search( + f"memmapped.+{escaped_filename}.+segmentation fault", + str(w.message), + ) for result, expected in zip(result_list, expected_list): if isinstance(expected, np.ndarray): >From 05f9f53475514af65bc181529624712bb8163502 Mon Sep 17 00:00:00 2001 From: Nanored4498 <[email protected]> Date: Fri, 9 Jan 2026 10:20:42 +0100 Subject: [PATCH 2/8] Better version check --- joblib/test/test_numpy_pickle.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/joblib/test/test_numpy_pickle.py b/joblib/test/test_numpy_pickle.py index b648f53c3..1f84e6f7b 100644 --- a/joblib/test/test_numpy_pickle.py +++ b/joblib/test/test_numpy_pickle.py @@ -16,6 +16,8 @@ from contextlib import closing from pathlib import Path +from packaging.version import Version + try: import lzma except ImportError: @@ -417,7 +419,11 @@ def _check_pickle(filename, expected_list, mmap_mode=None): else 0 ) - if np.__version__.split(".") >= "2.4.0".split("."): + # Account for the VisibleDeprecationWarning raised by + # numpy 2.4+ with align been of the wrong type + numpyDepreciationWarning = False + if Version(np.__version__) >= Version("2.4.0"): + numpyDepreciationWarning = True prefix = "joblib_" version_index = filename_base.find(prefix) + len(prefix) joblib_version = filename_base[version_index:] @@ -429,7 +435,7 @@ def check_version(v): expected_nb_user_warnings += 1 if "compressed" in filename_base: expected_nb_user_warnings += 2 - elif check_version("0.10.") or check_version("0.11."): + else: expected_nb_user_warnings += 4 expected_nb_warnings = ( @@ -449,7 +455,9 @@ def check_version(v): "version less than 0.10. Please regenerate this " "pickle file.".format(filename) ) - elif issubclass(w.category, np.exceptions.VisibleDeprecationWarning): + elif numpyDepreciationWarning and issubclass( + w.category, np.exceptions.VisibleDeprecationWarning + ): assert ( str(w.message) == "dtype(): align should be passed as Python or NumPy " @@ -462,6 +470,8 @@ def check_version(v): f"memmapped.+{escaped_filename}.+segmentation fault", str(w.message), ) + else: + raise Exception(f"No warning of type {w.category} is expected") for result, expected in zip(result_list, expected_list): if isinstance(expected, np.ndarray): >From e1817d9d9b4ad88d98ad1b3c83fb48db9136eb63 Mon Sep 17 00:00:00 2001 From: Thomas Moreau <[email protected]> Date: Fri, 9 Jan 2026 11:09:48 +0100 Subject: [PATCH 3/8] CLN clarify numpy deprecation warning handling --- joblib/test/test_numpy_pickle.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/joblib/test/test_numpy_pickle.py b/joblib/test/test_numpy_pickle.py index 1f84e6f7b..bce9e08c6 100644 --- a/joblib/test/test_numpy_pickle.py +++ b/joblib/test/test_numpy_pickle.py @@ -421,9 +421,9 @@ def _check_pickle(filename, expected_list, mmap_mode=None): # Account for the VisibleDeprecationWarning raised by # numpy 2.4+ with align been of the wrong type - numpyDepreciationWarning = False + check_numpy_depreciation_warning = False if Version(np.__version__) >= Version("2.4.0"): - numpyDepreciationWarning = True + check_numpy_depreciation_warning = True prefix = "joblib_" version_index = filename_base.find(prefix) + len(prefix) joblib_version = filename_base[version_index:] @@ -455,7 +455,13 @@ def check_version(v): "version less than 0.10. Please regenerate this " "pickle file.".format(filename) ) - elif numpyDepreciationWarning and issubclass( + elif issubclass(w.category, UserWarning): + escaped_filename = re.escape(filename) + assert re.search( + f"memmapped.+{escaped_filename}.+segmentation fault", + str(w.message), + ) + elif check_numpy_depreciation_warning and issubclass( w.category, np.exceptions.VisibleDeprecationWarning ): assert ( @@ -464,12 +470,6 @@ def check_version(v): "boolean but got `align=0`. Did you mean to pass a tuple " "to create a subarray type? (Deprecated NumPy 2.4)" ) - elif issubclass(w.category, UserWarning): - escaped_filename = re.escape(filename) - assert re.search( - f"memmapped.+{escaped_filename}.+segmentation fault", - str(w.message), - ) else: raise Exception(f"No warning of type {w.category} is expected") >From 2c513ace44928b05c264a5be0d9e809b5c35a501 Mon Sep 17 00:00:00 2001 From: Thomas Moreau <[email protected]> Date: Fri, 9 Jan 2026 11:11:13 +0100 Subject: [PATCH 4/8] Update joblib/test/test_numpy_pickle.py --- joblib/test/test_numpy_pickle.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/joblib/test/test_numpy_pickle.py b/joblib/test/test_numpy_pickle.py index bce9e08c6..7e68b9942 100644 --- a/joblib/test/test_numpy_pickle.py +++ b/joblib/test/test_numpy_pickle.py @@ -428,10 +428,7 @@ def _check_pickle(filename, expected_list, mmap_mode=None): version_index = filename_base.find(prefix) + len(prefix) joblib_version = filename_base[version_index:] - def check_version(v): - return joblib_version.startswith(v) - - if check_version("0.9."): + if joblib_version.startswith("0.9."): expected_nb_user_warnings += 1 if "compressed" in filename_base: expected_nb_user_warnings += 2 >From 65d1da5c582bf99032481a4d0b65ce7ce0640fba Mon Sep 17 00:00:00 2001 From: Thomas Moreau <[email protected]> Date: Fri, 9 Jan 2026 11:16:12 +0100 Subject: [PATCH 5/8] Restore UserWarning check in test_numpy_pickle Reintroduce UserWarning check for segmentation fault. --- joblib/test/test_numpy_pickle.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/joblib/test/test_numpy_pickle.py b/joblib/test/test_numpy_pickle.py index 7e68b9942..3970fe535 100644 --- a/joblib/test/test_numpy_pickle.py +++ b/joblib/test/test_numpy_pickle.py @@ -452,12 +452,6 @@ def _check_pickle(filename, expected_list, mmap_mode=None): "version less than 0.10. Please regenerate this " "pickle file.".format(filename) ) - elif issubclass(w.category, UserWarning): - escaped_filename = re.escape(filename) - assert re.search( - f"memmapped.+{escaped_filename}.+segmentation fault", - str(w.message), - ) elif check_numpy_depreciation_warning and issubclass( w.category, np.exceptions.VisibleDeprecationWarning ): @@ -467,6 +461,12 @@ def _check_pickle(filename, expected_list, mmap_mode=None): "boolean but got `align=0`. Did you mean to pass a tuple " "to create a subarray type? (Deprecated NumPy 2.4)" ) + elif issubclass(w.category, UserWarning): + escaped_filename = re.escape(filename) + assert re.search( + f"memmapped.+{escaped_filename}.+segmentation fault", + str(w.message), + ) else: raise Exception(f"No warning of type {w.category} is expected") >From 0979956337eb5f76c08ee4aeafd0597c6c83aefa Mon Sep 17 00:00:00 2001 From: Nanored4498 <[email protected]> Date: Fri, 9 Jan 2026 16:02:06 +0100 Subject: [PATCH 6/8] now ignore VisibleDeprecationWarning --- joblib/test/test_numpy_pickle.py | 62 +++++++++++++------------------- 1 file changed, 24 insertions(+), 38 deletions(-) diff --git a/joblib/test/test_numpy_pickle.py b/joblib/test/test_numpy_pickle.py index 3970fe535..cb6a7d878 100644 --- a/joblib/test/test_numpy_pickle.py +++ b/joblib/test/test_numpy_pickle.py @@ -16,8 +16,6 @@ from contextlib import closing from pathlib import Path -from packaging.version import Version - try: import lzma except ImportError: @@ -407,68 +405,56 @@ def _check_pickle(filename, expected_list, mmap_mode=None): try: with warnings.catch_warnings(record=True) as warninfo: warnings.simplefilter("always") + # Ignore VisibleDeprecationWarning raised by + # numpy 2.4+ with align been of the wrong type + if hasattr(np, "exceptions") and hasattr( + np.exceptions, "VisibleDeprecationWarning" + ): + warnings.filterwarnings( + "ignore", category=np.exceptions.VisibleDeprecationWarning + ) result_list = numpy_pickle.load(filename, mmap_mode=mmap_mode) + filename_base = os.path.basename(filename) expected_nb_deprecation_warnings = ( 1 if ("_0.9" in filename_base or "_0.8.4" in filename_base) else 0 ) + deprecation_warnings = [] expected_nb_user_warnings = ( 3 if (re.search("_0.1.+.pkl$", filename_base) and mmap_mode is not None) else 0 ) - - # Account for the VisibleDeprecationWarning raised by - # numpy 2.4+ with align been of the wrong type - check_numpy_depreciation_warning = False - if Version(np.__version__) >= Version("2.4.0"): - check_numpy_depreciation_warning = True - prefix = "joblib_" - version_index = filename_base.find(prefix) + len(prefix) - joblib_version = filename_base[version_index:] - - if joblib_version.startswith("0.9."): - expected_nb_user_warnings += 1 - if "compressed" in filename_base: - expected_nb_user_warnings += 2 - else: - expected_nb_user_warnings += 4 - - expected_nb_warnings = ( - expected_nb_deprecation_warnings + expected_nb_user_warnings - ) - assert len(warninfo) == expected_nb_warnings, ( - "Did not get the expected number of warnings. Expected " - f"{expected_nb_warnings} but got warnings: " - f"{[w.message for w in warninfo]}" - ) + user_warnings = [] for w in warninfo: if issubclass(w.category, DeprecationWarning): + deprecation_warnings.append(w.message) assert ( str(w.message) == "The file '{0}' has been generated with a joblib " "version less than 0.10. Please regenerate this " "pickle file.".format(filename) ) - elif check_numpy_depreciation_warning and issubclass( - w.category, np.exceptions.VisibleDeprecationWarning - ): - assert ( - str(w.message) - == "dtype(): align should be passed as Python or NumPy " - "boolean but got `align=0`. Did you mean to pass a tuple " - "to create a subarray type? (Deprecated NumPy 2.4)" - ) elif issubclass(w.category, UserWarning): + user_warnings.append(w.message) escaped_filename = re.escape(filename) assert re.search( f"memmapped.+{escaped_filename}.+segmentation fault", str(w.message), ) - else: - raise Exception(f"No warning of type {w.category} is expected") + + assert len(deprecation_warnings) == expected_nb_deprecation_warnings, ( + "Did not get the expected number of deprecation warnings. " + f"Expected {expected_nb_deprecation_warnings} " + "but got warnings: {deprecation_warnings}" + ) + assert len(user_warnings) == expected_nb_user_warnings, ( + "Did not get the expected number of user warnings. " + f"Expected {expected_nb_user_warnings} " + "but got warnings: {user_warnings}" + ) for result, expected in zip(result_list, expected_list): if isinstance(expected, np.ndarray): >From eb4dbaab32e07053eaf4f17055c232992d0e62ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Est=C3=A8ve?= <[email protected]> Date: Mon, 12 Jan 2026 07:07:49 +0100 Subject: [PATCH 7/8] Simpler strategy --- joblib/test/test_numpy_pickle.py | 66 ++++++++++++++------------------ 1 file changed, 29 insertions(+), 37 deletions(-) diff --git a/joblib/test/test_numpy_pickle.py b/joblib/test/test_numpy_pickle.py index cb6a7d878..a6920b035 100644 --- a/joblib/test/test_numpy_pickle.py +++ b/joblib/test/test_numpy_pickle.py @@ -405,57 +405,49 @@ def _check_pickle(filename, expected_list, mmap_mode=None): try: with warnings.catch_warnings(record=True) as warninfo: warnings.simplefilter("always") - # Ignore VisibleDeprecationWarning raised by - # numpy 2.4+ with align been of the wrong type - if hasattr(np, "exceptions") and hasattr( - np.exceptions, "VisibleDeprecationWarning" - ): - warnings.filterwarnings( - "ignore", category=np.exceptions.VisibleDeprecationWarning - ) + # Ignore numpy >= 2.4 warning when load old pickle where + # align=0 but it should be a Python or Numpy boolean + warnings.filterwarnings( + "ignore", message=".+align should be passed.+boolean.+align=0" + ) result_list = numpy_pickle.load(filename, mmap_mode=mmap_mode) - filename_base = os.path.basename(filename) expected_nb_deprecation_warnings = ( 1 if ("_0.9" in filename_base or "_0.8.4" in filename_base) else 0 ) - deprecation_warnings = [] expected_nb_user_warnings = ( 3 if (re.search("_0.1.+.pkl$", filename_base) and mmap_mode is not None) else 0 ) - user_warnings = [] - - for w in warninfo: - if issubclass(w.category, DeprecationWarning): - deprecation_warnings.append(w.message) - assert ( - str(w.message) - == "The file '{0}' has been generated with a joblib " - "version less than 0.10. Please regenerate this " - "pickle file.".format(filename) - ) - elif issubclass(w.category, UserWarning): - user_warnings.append(w.message) - escaped_filename = re.escape(filename) - assert re.search( - f"memmapped.+{escaped_filename}.+segmentation fault", - str(w.message), - ) - - assert len(deprecation_warnings) == expected_nb_deprecation_warnings, ( - "Did not get the expected number of deprecation warnings. " - f"Expected {expected_nb_deprecation_warnings} " - "but got warnings: {deprecation_warnings}" + expected_nb_warnings = ( + expected_nb_deprecation_warnings + expected_nb_user_warnings ) - assert len(user_warnings) == expected_nb_user_warnings, ( - "Did not get the expected number of user warnings. " - f"Expected {expected_nb_user_warnings} " - "but got warnings: {user_warnings}" + assert len(warninfo) == expected_nb_warnings, ( + "Did not get the expected number of warnings. Expected " + f"{expected_nb_warnings} but got warnings: " + f"{[w.message for w in warninfo]}" ) + deprecation_warnings = [ + w for w in warninfo if issubclass(w.category, DeprecationWarning) + ] + user_warnings = [w for w in warninfo if issubclass(w.category, UserWarning)] + for w in deprecation_warnings: + assert ( + str(w.message) + == "The file '{0}' has been generated with a joblib " + "version less than 0.10. Please regenerate this " + "pickle file.".format(filename) + ) + + for w in user_warnings: + escaped_filename = re.escape(filename) + assert re.search( + f"memmapped.+{escaped_filename}.+segmentation fault", str(w.message) + ) + for result, expected in zip(result_list, expected_list): if isinstance(expected, np.ndarray): expected = _ensure_native_byte_order(expected) >From afc24f82511b8f536e0344c98a722c73081fedf0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Est=C3=A8ve?= <[email protected]> Date: Mon, 12 Jan 2026 07:08:42 +0100 Subject: [PATCH 8/8] Tweak wording --- joblib/test/test_numpy_pickle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/joblib/test/test_numpy_pickle.py b/joblib/test/test_numpy_pickle.py index a6920b035..af4214edc 100644 --- a/joblib/test/test_numpy_pickle.py +++ b/joblib/test/test_numpy_pickle.py @@ -405,7 +405,7 @@ def _check_pickle(filename, expected_list, mmap_mode=None): try: with warnings.catch_warnings(record=True) as warninfo: warnings.simplefilter("always") - # Ignore numpy >= 2.4 warning when load old pickle where + # Ignore numpy >= 2.4 warning when loading old pickles, where # align=0 but it should be a Python or Numpy boolean warnings.filterwarnings( "ignore", message=".+align should be passed.+boolean.+align=0" ++++++ joblib-1.5.2.tar.gz -> joblib-1.5.3.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/joblib-1.5.2/PKG-INFO new/joblib-1.5.3/PKG-INFO --- old/joblib-1.5.2/PKG-INFO 2025-08-27 14:15:21.971439800 +0200 +++ new/joblib-1.5.3/PKG-INFO 2025-12-15 09:41:26.185438900 +0100 @@ -1,9 +1,9 @@ Metadata-Version: 2.4 Name: joblib -Version: 1.5.2 +Version: 1.5.3 Summary: Lightweight pipelining with Python functions Author-email: Gael Varoquaux <[email protected]> -License: BSD 3-Clause +License-Expression: BSD-3-Clause Project-URL: Homepage, https://joblib.readthedocs.io Project-URL: Source, https://github.com/joblib/joblib Platform: any @@ -12,7 +12,6 @@ Classifier: Intended Audience :: Developers Classifier: Intended Audience :: Science/Research Classifier: Intended Audience :: Education -Classifier: License :: OSI Approved :: BSD License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.9 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/joblib-1.5.2/doc/conftest.py new/joblib-1.5.3/doc/conftest.py --- old/joblib-1.5.2/doc/conftest.py 2025-08-27 14:15:17.000000000 +0200 +++ new/joblib-1.5.3/doc/conftest.py 2025-12-15 09:41:21.000000000 +0100 @@ -1,16 +1,20 @@ import faulthandler +import pytest + from joblib.parallel import mp from joblib.test.common import np -from joblib.testing import fixture, skipif +from joblib.testing import fixture @fixture(scope="module") -@skipif(np is None or mp is None, "Numpy or Multiprocessing not available") def parallel_numpy_fixture(request): """Fixture to skip memmapping test if numpy or multiprocessing is not installed""" + if np is None or mp is None: + pytest.skip("Numpy or Multiprocessing not available") + def setup(module): faulthandler.dump_traceback_later(timeout=300, exit=True) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/joblib-1.5.2/joblib/__init__.py new/joblib-1.5.3/joblib/__init__.py --- old/joblib-1.5.2/joblib/__init__.py 2025-08-27 14:15:17.000000000 +0200 +++ new/joblib-1.5.3/joblib/__init__.py 2025-12-15 09:41:21.000000000 +0100 @@ -106,7 +106,7 @@ # Dev branch marker is: 'X.Y.dev' or 'X.Y.devN' where N is an integer. # 'X.Y.dev0' is the canonical version of 'X.Y.dev' # -__version__ = "1.5.2" +__version__ = "1.5.3" import os diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/joblib-1.5.2/joblib/_store_backends.py new/joblib-1.5.3/joblib/_store_backends.py --- old/joblib-1.5.2/joblib/_store_backends.py 2025-08-27 14:15:17.000000000 +0200 +++ new/joblib-1.5.3/joblib/_store_backends.py 2025-12-15 09:41:21.000000000 +0100 @@ -474,9 +474,14 @@ if os.path.dirname(location) and os.path.basename(location) == "joblib" else location ) - with open(os.path.join(cache_directory, ".gitignore"), "w") as file: - file.write("# Created by joblib automatically.\n") - file.write("*\n") + gitignore = os.path.join(cache_directory, ".gitignore") + if not os.path.exists(gitignore): + try: + with open(gitignore, "w") as file: + file.write("# Created by joblib automatically.\n") + file.write("*\n") + except OSError as e: + warnings.warn(f"Unable to write {gitignore}. Exception: {e}.") # item can be stored compressed for faster I/O self.compress = backend_options.get("compress", False) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/joblib-1.5.2/joblib/_utils.py new/joblib-1.5.3/joblib/_utils.py --- old/joblib-1.5.2/joblib/_utils.py 2025-08-27 14:15:17.000000000 +0200 +++ new/joblib-1.5.3/joblib/_utils.py 2025-12-15 09:41:21.000000000 +0100 @@ -1,6 +1,7 @@ # Adapted from https://stackoverflow.com/a/9558001/2536294 import ast +import functools import operator as op from dataclasses import dataclass @@ -24,24 +25,60 @@ def eval_expr(expr): - """ + """Somewhat safely evaluate an arithmetic expression. + >>> eval_expr('2*6') 12 >>> eval_expr('2**6') 64 >>> eval_expr('1 + 2*3**(4) / (6 + -7)') -161.0 + + Raises ValueError if the expression is invalid, too long + or its computation involves too large values. """ + # Restrict the length of the expression to avoid potential Python crashes + # as per the documentation of ast.parse. + max_length = 30 + if len(expr) > max_length: + raise ValueError( + f"Expression {expr[:max_length]!r}... is too long. " + f"Max length is {max_length}, got {len(expr)}." + ) try: return eval_(ast.parse(expr, mode="eval").body) - except (TypeError, SyntaxError, KeyError) as e: + except (TypeError, SyntaxError, OverflowError, KeyError) as e: raise ValueError( f"{expr!r} is not a valid or supported arithmetic expression." ) from e +def limit(max_=None): + """Return decorator that limits allowed returned values.""" + + def decorator(func): + @functools.wraps(func) + def wrapper(*args, **kwargs): + ret = func(*args, **kwargs) + try: + mag = abs(ret) + except TypeError: + pass # not applicable + else: + if mag > max_: + raise ValueError( + f"Numeric literal {ret} is too large, max is {max_}." + ) + return ret + + return wrapper + + return decorator + + +@limit(max_=10**6) def eval_(node): - if isinstance(node, ast.Constant): # <constant> + if isinstance(node, ast.Constant) and isinstance(node.value, (int, float)): return node.value elif isinstance(node, ast.BinOp): # <left> <operator> <right> return operators[type(node.op)](eval_(node.left), eval_(node.right)) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/joblib-1.5.2/joblib/externals/cloudpickle/__init__.py new/joblib-1.5.3/joblib/externals/cloudpickle/__init__.py --- old/joblib-1.5.2/joblib/externals/cloudpickle/__init__.py 2025-08-27 14:15:17.000000000 +0200 +++ new/joblib-1.5.3/joblib/externals/cloudpickle/__init__.py 2025-12-15 09:41:21.000000000 +0100 @@ -3,7 +3,7 @@ __doc__ = cloudpickle.__doc__ -__version__ = "3.1.1" +__version__ = "3.1.2" __all__ = [ # noqa "__version__", diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/joblib-1.5.2/joblib/externals/cloudpickle/cloudpickle.py new/joblib-1.5.3/joblib/externals/cloudpickle/cloudpickle.py --- old/joblib-1.5.2/joblib/externals/cloudpickle/cloudpickle.py 2025-08-27 14:15:17.000000000 +0200 +++ new/joblib-1.5.3/joblib/externals/cloudpickle/cloudpickle.py 2025-12-15 09:41:21.000000000 +0100 @@ -783,6 +783,12 @@ clsdict.pop("__dict__", None) # unpicklable property object + if sys.version_info >= (3, 14): + # PEP-649/749: __annotate_func__ contains a closure that references the class + # dict. We need to exclude it from pickling. Python will recreate it when + # __annotations__ is accessed at unpickling time. + clsdict.pop("__annotate_func__", None) + return (clsdict, {}) @@ -1190,6 +1196,10 @@ for subclass in registry: obj.register(subclass) + # PEP-649/749: During pickling, we excluded the __annotate_func__ attribute but it + # will be created by Python. Subsequently, annotations will be recreated when + # __annotations__ is accessed. + return obj @@ -1301,12 +1311,9 @@ def dump(self, obj): try: return super().dump(obj) - except RuntimeError as e: - if len(e.args) > 0 and "recursion" in e.args[0]: - msg = "Could not pickle object as excessively deep recursion required." - raise pickle.PicklingError(msg) from e - else: - raise + except RecursionError as e: + msg = "Could not pickle object as excessively deep recursion required." + raise pickle.PicklingError(msg) from e def __init__(self, file, protocol=None, buffer_callback=None): if protocol is None: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/joblib-1.5.2/joblib/test/_openmp_test_helper/setup.py new/joblib-1.5.3/joblib/test/_openmp_test_helper/setup.py --- old/joblib-1.5.2/joblib/test/_openmp_test_helper/setup.py 2025-08-27 14:15:17.000000000 +0200 +++ new/joblib-1.5.3/joblib/test/_openmp_test_helper/setup.py 2025-12-15 09:41:21.000000000 +0100 @@ -2,10 +2,10 @@ import os import sys -from distutils.core import setup -from distutils.extension import Extension from Cython.Build import cythonize +from setuptools import setup +from setuptools.extension import Extension if sys.platform == "darwin": os.environ["CC"] = "gcc-4.9" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/joblib-1.5.2/joblib/test/test_memory.py new/joblib-1.5.3/joblib/test/test_memory.py --- old/joblib-1.5.2/joblib/test/test_memory.py 2025-08-27 14:15:17.000000000 +0200 +++ new/joblib-1.5.3/joblib/test/test_memory.py 2025-12-15 09:41:21.000000000 +0100 @@ -12,12 +12,12 @@ import logging import os import os.path -import pathlib import pickle import shutil import sys import textwrap import time +from pathlib import Path import pytest @@ -1264,7 +1264,7 @@ def test_instanciate_store_backend_with_pathlib_path(): # Instantiate a FileSystemStoreBackend using a pathlib.Path object - path = pathlib.Path("some_folder") + path = Path("some_folder") backend_obj = _store_backend_factory("local", path) try: assert backend_obj.location == "some_folder" @@ -1547,31 +1547,36 @@ ) -@with_numpy -@parametrize( - "location", - [ - "test_cache_dir", - pathlib.Path("test_cache_dir"), - pathlib.Path("test_cache_dir").resolve(), - ], -) -def test_memory_creates_gitignore(location): - """Test that using the memory object automatically creates a `.gitignore` file - within the new cache directory.""" - - mem = Memory(location) - arr = np.asarray([[1, 2, 3], [4, 5, 6]]) - costly_operation = mem.cache(np.square) - costly_operation(arr) +class TestAutoGitignore: + "Tests for the MemorizedFunc and NotMemorizedFunc classes" - location = pathlib.Path(location) + def test_memory_creates_gitignore(self, tmpdir): + """Test that using the memory object automatically creates a `.gitignore` file + within the new cache directory.""" - try: - path_to_gitignore_file = os.path.join(location, ".gitignore") - gitignore_file_content = "# Created by joblib automatically.\n*\n" - with open(path_to_gitignore_file) as f: - assert gitignore_file_content == f.read() + location = Path(tmpdir.mkdir("test_cache_dir")) - finally: # remove cache folder after test - shutil.rmtree(location, ignore_errors=True) + mem = Memory(location) + costly_operation = mem.cache(id) + costly_operation(0) + + gitignore_file = location / ".gitignore" + assert gitignore_file.exists() + assert gitignore_file.read_text() == "# Created by joblib automatically.\n*\n" + + def test_memory_does_not_overwrite_existing_gitignore(self, tmpdir): + """Test that using the memory object does not overwrite an existing + `.gitignore` file within the cache directory.""" + + location = Path(tmpdir.mkdir("test_cache_dir")) + gitignore_file = location / ".gitignore" + + existing_content = "# Existing .gitignore file!" + gitignore_file.write_text(existing_content) + + # Cache a function and call it. + mem = Memory(location) + mem.cache(id)(0) + + assert gitignore_file.exists() + assert gitignore_file.read_text() == existing_content diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/joblib-1.5.2/joblib/test/test_utils.py new/joblib-1.5.3/joblib/test/test_utils.py --- old/joblib-1.5.2/joblib/test/test_utils.py 2025-08-27 14:15:17.000000000 +0200 +++ new/joblib-1.5.3/joblib/test/test_utils.py 2025-12-15 09:41:21.000000000 +0100 @@ -5,13 +5,33 @@ @pytest.mark.parametrize( "expr", - ["exec('import os')", "print(1)", "import os", "1+1; import os", "1^1"], + [ + "exec('import os')", + "print(1)", + "import os", + "1+1; import os", + "1^1", + "' ' * 10**10", + "9. ** 10000.", + ], ) def test_eval_expr_invalid(expr): with pytest.raises(ValueError, match="is not a valid or supported arithmetic"): eval_expr(expr) +def test_eval_expr_too_long(): + expr = "1" + "+1" * 50 + with pytest.raises(ValueError, match="is too long"): + eval_expr(expr) + + [email protected]("expr", ["1e7", "10**7", "9**9**9"]) +def test_eval_expr_too_large_literal(expr): + with pytest.raises(ValueError, match="Numeric literal .* is too large"): + eval_expr(expr) + + @pytest.mark.parametrize( "expr, result", [ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/joblib-1.5.2/joblib.egg-info/PKG-INFO new/joblib-1.5.3/joblib.egg-info/PKG-INFO --- old/joblib-1.5.2/joblib.egg-info/PKG-INFO 2025-08-27 14:15:21.000000000 +0200 +++ new/joblib-1.5.3/joblib.egg-info/PKG-INFO 2025-12-15 09:41:26.000000000 +0100 @@ -1,9 +1,9 @@ Metadata-Version: 2.4 Name: joblib -Version: 1.5.2 +Version: 1.5.3 Summary: Lightweight pipelining with Python functions Author-email: Gael Varoquaux <[email protected]> -License: BSD 3-Clause +License-Expression: BSD-3-Clause Project-URL: Homepage, https://joblib.readthedocs.io Project-URL: Source, https://github.com/joblib/joblib Platform: any @@ -12,7 +12,6 @@ Classifier: Intended Audience :: Developers Classifier: Intended Audience :: Science/Research Classifier: Intended Audience :: Education -Classifier: License :: OSI Approved :: BSD License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.9 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/joblib-1.5.2/pyproject.toml new/joblib-1.5.3/pyproject.toml --- old/joblib-1.5.2/pyproject.toml 2025-08-27 14:15:17.000000000 +0200 +++ new/joblib-1.5.3/pyproject.toml 2025-12-15 09:41:21.000000000 +0100 @@ -1,11 +1,11 @@ [build-system] -requires = ["setuptools>=61.2"] +requires = ["setuptools>=77.0.3"] build-backend = "setuptools.build_meta" [project] name = "joblib" authors = [{name = "Gael Varoquaux", email = "[email protected]"}] -license = {text = "BSD 3-Clause"} +license = "BSD-3-Clause" description = "Lightweight pipelining with Python functions" classifiers = [ "Development Status :: 5 - Production/Stable", @@ -13,7 +13,6 @@ "Intended Audience :: Developers", "Intended Audience :: Science/Research", "Intended Audience :: Education", - "License :: OSI Approved :: BSD License", "Operating System :: OS Independent", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.9", @@ -48,6 +47,7 @@ ] platforms = ["any"] include-package-data = false +license-files = ["LICENSE.txt"] [tool.setuptools.package-data] "joblib.test" = [
