From cfb56813029c05fe3fdd7699c25d52ad9dd72978 Mon Sep 17 00:00:00 2001 From: tommoral Date: Fri, 21 Mar 2025 10:19:03 +0100 Subject: [PATCH 01/22] MTN deterministic co_filename for dynamic code pickling --- cloudpickle/cloudpickle.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/cloudpickle/cloudpickle.py b/cloudpickle/cloudpickle.py index 4d532e5d..1345f85e 100644 --- a/cloudpickle/cloudpickle.py +++ b/cloudpickle/cloudpickle.py @@ -829,6 +829,11 @@ def _code_reduce(obj): # See the inline comment in _class_setstate for details. co_name = "".join(obj.co_name) + # co_filename is not used in the constructor of code objects, so we can + # safely set it to indicate that this is dynamic code. This also makes + # the payload deterministic, independent of where the function is defined. + co_filename = "".join("") + # Create shallow copies of these tuple to make cloudpickle payload deterministic. # When creating a code object during load, copies of these four tuples are # created, while in the main process, these tuples can be shared. @@ -851,7 +856,7 @@ def _code_reduce(obj): obj.co_consts, co_names, co_varnames, - obj.co_filename, + co_filename, co_name, obj.co_qualname, obj.co_firstlineno, @@ -874,7 +879,7 @@ def _code_reduce(obj): obj.co_consts, co_names, co_varnames, - obj.co_filename, + co_filename, co_name, obj.co_firstlineno, obj.co_linetable, @@ -895,7 +900,7 @@ def _code_reduce(obj): obj.co_code, obj.co_consts, co_varnames, - obj.co_filename, + co_filename, co_name, obj.co_firstlineno, obj.co_lnotab, @@ -919,7 +924,7 @@ def _code_reduce(obj): obj.co_consts, co_names, co_varnames, - obj.co_filename, + co_filename, co_name, obj.co_firstlineno, obj.co_lnotab, From 0104e42eb4d7d53c188eeef5be194236a14806b8 Mon Sep 17 00:00:00 2001 From: tommoral Date: Fri, 21 Mar 2025 10:36:49 +0100 Subject: [PATCH 02/22] CI trigger From 36307533d795058a0bebb2ef1ce5d79564cc12ff Mon Sep 17 00:00:00 2001 From: tommoral Date: Tue, 25 Mar 2025 22:07:21 +0100 Subject: [PATCH 03/22] CI trigger From df24e317e917e24e0af05aa190efb08aa43c6c80 Mon Sep 17 00:00:00 2001 From: tommoral Date: Tue, 25 Mar 2025 23:59:14 +0100 Subject: [PATCH 04/22] TST add test in ipykernel --- dev-requirements.txt | 2 + tests/cloudpickle_ipykernel_test.py | 64 +++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+) create mode 100644 tests/cloudpickle_ipykernel_test.py diff --git a/dev-requirements.txt b/dev-requirements.txt index 35c529fe..478c2f0f 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -7,6 +7,8 @@ pytest-cov psutil # To be able to test tornado coroutines tornado +# To be able to test behavior in jupyter-notbooks +ipykernel # To be able to test numpy specific things # but do not build numpy from source on Python nightly numpy >=1.18.5; python_version <= '3.12' diff --git a/tests/cloudpickle_ipykernel_test.py b/tests/cloudpickle_ipykernel_test.py new file mode 100644 index 00000000..50f43880 --- /dev/null +++ b/tests/cloudpickle_ipykernel_test.py @@ -0,0 +1,64 @@ +import pytest +import textwrap + +from .testutils import check_deterministic_pickle + +try: + import ipykernel +except ImportError: + pytest.skip("ipykernel is not installed", allow_module_level=True) + + +def run_in_notebook(code): + km = ipykernel.connect.jupyter_client.KernelManager() + km.start_kernel() + kc = km.client() + kc.start_channels() + status, output, err = "run", None, None + try: + assert km.is_alive() and kc.is_alive() + kc.wait_for_ready() + idx = kc.execute(code) + running = True + while running: + res = kc.iopub_channel.get_msg(timeout=None) + if res['parent_header'].get('msg_id') != idx: + continue + content = res['content'] + if content.get("name", "state") == "stdout": + output = content['text'] + if "traceback" in content: + err = "\n".join(content['traceback']) + status = "error" + running = res['content'].get('execution_state', None) != "idle" + finally: + kc.shutdown() + kc.stop_channels() + km.shutdown_kernel(now=True, restart=False) + assert not km.is_alive() + if status != "error": + status = "ok" if not running else "exec_error" + return status, output, err + + +def test_deterministic_payload_for_dynamic_func_in_notebook(): + code = textwrap.dedent(""" + import cloudpickle + + MY_PI = 3.1415 + + def get_pi(): + return MY_PI + + print(cloudpickle.dumps(get_pi)) + """) + + status, output, err = run_in_notebook(code) + assert status == "ok" + payload = eval(output.strip(), {}) + + status, output, err = run_in_notebook(code) + assert status == "ok" + payload2 = eval(output.strip(), {}) + + check_deterministic_pickle(payload, payload2) From 738827e2175595d023bd3ab287ff8bd273880877 Mon Sep 17 00:00:00 2001 From: tommoral Date: Wed, 26 Mar 2025 10:00:36 +0100 Subject: [PATCH 05/22] CI trigger From 28101062f6eb65c8f02fa308c922119a6eccb87c Mon Sep 17 00:00:00 2001 From: tommoral Date: Wed, 26 Mar 2025 11:22:34 +0100 Subject: [PATCH 06/22] CI trigger From 58ac7274a8b68decfdd7114888842ea485f68b2a Mon Sep 17 00:00:00 2001 From: Thomas Moreau Date: Mon, 31 Mar 2025 13:55:48 +0200 Subject: [PATCH 07/22] Apply suggestions from code review Co-authored-by: Olivier Grisel --- dev-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev-requirements.txt b/dev-requirements.txt index 478c2f0f..7735fd92 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -7,7 +7,7 @@ pytest-cov psutil # To be able to test tornado coroutines tornado -# To be able to test behavior in jupyter-notbooks +# To be able to test behavior in jupyter-notebooks ipykernel # To be able to test numpy specific things # but do not build numpy from source on Python nightly From bb600815a1cabeac22568fc77cc320ea9bcac64c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20du=20Boisberranger?= Date: Fri, 14 Mar 2025 17:29:04 +0100 Subject: [PATCH 08/22] skip workflow on forks (#559) --- .github/workflows/publish_to_pypi.yml | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/.github/workflows/publish_to_pypi.yml b/.github/workflows/publish_to_pypi.yml index 4b6e2775..bbefdfa1 100644 --- a/.github/workflows/publish_to_pypi.yml +++ b/.github/workflows/publish_to_pypi.yml @@ -1,4 +1,4 @@ -name: Publish Python 🐍 distribution 📦 to PyPI and TestPyPI +name: Publish cloudpickle 🥒 distribution 📦 to PyPI and TestPyPI # Taken from: # https://packaging.python.org/en/latest/guides/publishing-package-distribution-releases-using-github-actions-ci-cd-workflows/ @@ -7,6 +7,8 @@ on: push jobs: build: name: Build distribution 📦 + # Don't run on forked repositories + if: github.event.repository.fork != true runs-on: ubuntu-latest steps: @@ -30,10 +32,11 @@ jobs: with: name: python-package-distributions path: dist/ + retention-days: 1 publish-to-pypi: name: >- - Publish Python 🐍 distribution 📦 to PyPI + Publish cloudpickle 🥒 distribution 📦 to PyPI if: startsWith(github.ref, 'refs/tags/') # only publish to PyPI on tag pushes needs: - build @@ -55,7 +58,7 @@ jobs: github-release: name: >- - Sign the Python 🐍 distribution 📦 with Sigstore + Sign the cloudpickle 🥒 distribution 📦 with Sigstore and upload them to GitHub Release needs: - publish-to-pypi @@ -97,7 +100,7 @@ jobs: --repo "$GITHUB_REPOSITORY" publish-to-testpypi: - name: Publish Python 🐍 distribution 📦 to TestPyPI + name: Publish cloudpickle 🥒 distribution 📦 to TestPyPI needs: - build runs-on: ubuntu-latest From 543ab5928282958bf9206f9ba2c51e928e932570 Mon Sep 17 00:00:00 2001 From: Thomas Moreau Date: Tue, 25 Mar 2025 10:32:53 +0100 Subject: [PATCH 09/22] MTN skip test with recursion limit on python3.14 due to segfault on OSX (#561) --- tests/cloudpickle_test.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/tests/cloudpickle_test.py b/tests/cloudpickle_test.py index 00467e35..72aa132f 100644 --- a/tests/cloudpickle_test.py +++ b/tests/cloudpickle_test.py @@ -2507,12 +2507,18 @@ def inner_function(): inner_func = depickled_factory() assert inner_func() == _TEST_GLOBAL_VARIABLE + # TODO: remove this xfail when we drop support for Python 3.8. We don't + # plan to fix it because Python 3.8 is EOL. @pytest.mark.skipif( sys.version_info < (3, 9), reason="Can cause CPython 3.8 to segfault", ) - # TODO: remove this xfail when we drop support for Python 3.8. We don't - # plan to fix it because Python 3.8 is EOL. + @pytest.mark.skipif( + sys.version_info > (3, 14), + reason="Can cause CPython 3.14 interpreter to crash", + # This interpreter crash is reported upstream in + # https://github.com/python/cpython/issues/131543 + ) def test_recursion_during_pickling(self): class A: def __getattribute__(self, name): From 2aff8f28bd41b7cdc2c973c51dba07bcfffb2f18 Mon Sep 17 00:00:00 2001 From: Thomas Moreau Date: Mon, 25 Aug 2025 10:44:28 +0200 Subject: [PATCH 10/22] MTN unskip 3.14 --- tests/cloudpickle_test.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/tests/cloudpickle_test.py b/tests/cloudpickle_test.py index 72aa132f..7e1e09e1 100644 --- a/tests/cloudpickle_test.py +++ b/tests/cloudpickle_test.py @@ -2513,12 +2513,6 @@ def inner_function(): sys.version_info < (3, 9), reason="Can cause CPython 3.8 to segfault", ) - @pytest.mark.skipif( - sys.version_info > (3, 14), - reason="Can cause CPython 3.14 interpreter to crash", - # This interpreter crash is reported upstream in - # https://github.com/python/cpython/issues/131543 - ) def test_recursion_during_pickling(self): class A: def __getattribute__(self, name): From c435e1448b637e3b89a9d1cb4716cfa3b52ba58a Mon Sep 17 00:00:00 2001 From: tommoral Date: Mon, 25 Aug 2025 11:42:31 +0200 Subject: [PATCH 11/22] Revert "MTN unskip 3.14" This reverts commit 2aff8f28bd41b7cdc2c973c51dba07bcfffb2f18. --- tests/cloudpickle_test.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/cloudpickle_test.py b/tests/cloudpickle_test.py index d5f8f8fb..3647617d 100644 --- a/tests/cloudpickle_test.py +++ b/tests/cloudpickle_test.py @@ -2513,6 +2513,12 @@ def inner_function(): sys.version_info < (3, 9), reason="Can cause CPython 3.8 to segfault", ) + @pytest.mark.skipif( + sys.version_info > (3, 14), + reason="Can cause CPython 3.14 interpreter to crash", + # This interpreter crash is reported upstream in + # https://github.com/python/cpython/issues/131543 + ) def test_recursion_during_pickling(self): class A: def __getattribute__(self, name): From 7ffc74fd02c807eeaa31b694434e8b0b3cf8918a Mon Sep 17 00:00:00 2001 From: tommoral Date: Mon, 25 Aug 2025 11:43:30 +0200 Subject: [PATCH 12/22] DBG add more logs --- .github/workflows/testing.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index a7becfb4..f42348cd 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -69,7 +69,7 @@ jobs: shell: bash run: | COVERAGE_PROCESS_START=$GITHUB_WORKSPACE/.coveragerc \ - PYTHONPATH='.:tests' python -m pytest -r s + PYTHONPATH='.:tests' python -m pytest -r s -vs coverage combine --append coverage xml -i - name: Publish coverage results From cbc4a4c8697d403727859b20d31befab6aefc9b4 Mon Sep 17 00:00:00 2001 From: tommoral Date: Mon, 25 Aug 2025 13:52:00 +0200 Subject: [PATCH 13/22] DBG remove ipykernel tests --- tests/cloudpickle_ipykernel_test.py | 64 ----------------------------- 1 file changed, 64 deletions(-) delete mode 100644 tests/cloudpickle_ipykernel_test.py diff --git a/tests/cloudpickle_ipykernel_test.py b/tests/cloudpickle_ipykernel_test.py deleted file mode 100644 index 50f43880..00000000 --- a/tests/cloudpickle_ipykernel_test.py +++ /dev/null @@ -1,64 +0,0 @@ -import pytest -import textwrap - -from .testutils import check_deterministic_pickle - -try: - import ipykernel -except ImportError: - pytest.skip("ipykernel is not installed", allow_module_level=True) - - -def run_in_notebook(code): - km = ipykernel.connect.jupyter_client.KernelManager() - km.start_kernel() - kc = km.client() - kc.start_channels() - status, output, err = "run", None, None - try: - assert km.is_alive() and kc.is_alive() - kc.wait_for_ready() - idx = kc.execute(code) - running = True - while running: - res = kc.iopub_channel.get_msg(timeout=None) - if res['parent_header'].get('msg_id') != idx: - continue - content = res['content'] - if content.get("name", "state") == "stdout": - output = content['text'] - if "traceback" in content: - err = "\n".join(content['traceback']) - status = "error" - running = res['content'].get('execution_state', None) != "idle" - finally: - kc.shutdown() - kc.stop_channels() - km.shutdown_kernel(now=True, restart=False) - assert not km.is_alive() - if status != "error": - status = "ok" if not running else "exec_error" - return status, output, err - - -def test_deterministic_payload_for_dynamic_func_in_notebook(): - code = textwrap.dedent(""" - import cloudpickle - - MY_PI = 3.1415 - - def get_pi(): - return MY_PI - - print(cloudpickle.dumps(get_pi)) - """) - - status, output, err = run_in_notebook(code) - assert status == "ok" - payload = eval(output.strip(), {}) - - status, output, err = run_in_notebook(code) - assert status == "ok" - payload2 = eval(output.strip(), {}) - - check_deterministic_pickle(payload, payload2) From ec76c5f08cbffbbac925752eb729ae08a45a7433 Mon Sep 17 00:00:00 2001 From: tommoral Date: Mon, 25 Aug 2025 13:57:44 +0200 Subject: [PATCH 14/22] DBG skip ipykernel for win+py3.11- --- tests/cloudpickle_ipykernel_test.py | 68 +++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 tests/cloudpickle_ipykernel_test.py diff --git a/tests/cloudpickle_ipykernel_test.py b/tests/cloudpickle_ipykernel_test.py new file mode 100644 index 00000000..63340766 --- /dev/null +++ b/tests/cloudpickle_ipykernel_test.py @@ -0,0 +1,68 @@ +import sys +import pytest +import textwrap + +from .testutils import check_deterministic_pickle + +if sys.platform == "win32": + if sys.version_info < (3, 11): + pytest.skip( + "ipykernel requires Python 3.11 or later", + allow_module_level=True + ) +ipykernel = pytest.importorskip("ipykernel") + + +def run_in_notebook(code): + km = ipykernel.connect.jupyter_client.KernelManager() + km.start_kernel() + kc = km.client() + kc.start_channels() + status, output, err = "run", None, None + try: + assert km.is_alive() and kc.is_alive() + kc.wait_for_ready() + idx = kc.execute(code) + running = True + while running: + res = kc.iopub_channel.get_msg(timeout=None) + if res['parent_header'].get('msg_id') != idx: + continue + content = res['content'] + if content.get("name", "state") == "stdout": + output = content['text'] + if "traceback" in content: + err = "\n".join(content['traceback']) + status = "error" + running = res['content'].get('execution_state', None) != "idle" + finally: + kc.shutdown() + kc.stop_channels() + km.shutdown_kernel(now=True, restart=False) + assert not km.is_alive() + if status != "error": + status = "ok" if not running else "exec_error" + return status, output, err + + +def test_deterministic_payload_for_dynamic_func_in_notebook(): + code = textwrap.dedent(""" + import cloudpickle + + MY_PI = 3.1415 + + def get_pi(): + return MY_PI + + print(cloudpickle.dumps(get_pi)) + """) + + status, output, err = run_in_notebook(code) + assert status == "ok" + payload = eval(output.strip(), {}) + + status, output, err = run_in_notebook(code) + assert status == "ok" + payload2 = eval(output.strip(), {}) + + check_deterministic_pickle(payload, payload2) From 5ea9b2cf8392f1d989402105a1b07effd93dfacc Mon Sep 17 00:00:00 2001 From: Olivier Grisel Date: Mon, 3 Nov 2025 11:01:47 +0100 Subject: [PATCH 15/22] Trigger CI From 84f42399a4a0021eda68731d683d8b013b54347c Mon Sep 17 00:00:00 2001 From: Thomas Moreau Date: Tue, 4 Nov 2025 14:18:47 +0100 Subject: [PATCH 16/22] Apply suggestion from @ogrisel Co-authored-by: Olivier Grisel --- tests/cloudpickle_ipykernel_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/cloudpickle_ipykernel_test.py b/tests/cloudpickle_ipykernel_test.py index 63340766..80af87f7 100644 --- a/tests/cloudpickle_ipykernel_test.py +++ b/tests/cloudpickle_ipykernel_test.py @@ -18,7 +18,7 @@ def run_in_notebook(code): km.start_kernel() kc = km.client() kc.start_channels() - status, output, err = "run", None, None + status, output, err = "kernel_started", None, None try: assert km.is_alive() and kc.is_alive() kc.wait_for_ready() From db60564fbdd22e60ff6317e2e6d6a35bd2016ba3 Mon Sep 17 00:00:00 2001 From: tommoral Date: Tue, 4 Nov 2025 14:45:45 +0100 Subject: [PATCH 17/22] TST run_in_notebook --- tests/cloudpickle_ipykernel_test.py | 32 ++++++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/tests/cloudpickle_ipykernel_test.py b/tests/cloudpickle_ipykernel_test.py index 80af87f7..0527e828 100644 --- a/tests/cloudpickle_ipykernel_test.py +++ b/tests/cloudpickle_ipykernel_test.py @@ -1,6 +1,8 @@ import sys +import time import pytest import textwrap +from _queue import Empty from .testutils import check_deterministic_pickle @@ -13,7 +15,7 @@ ipykernel = pytest.importorskip("ipykernel") -def run_in_notebook(code): +def run_in_notebook(code, timeout=10): km = ipykernel.connect.jupyter_client.KernelManager() km.start_kernel() kc = km.client() @@ -25,7 +27,11 @@ def run_in_notebook(code): idx = kc.execute(code) running = True while running: - res = kc.iopub_channel.get_msg(timeout=None) + try: + res = kc.iopub_channel.get_msg(timeout=timeout) + except Empty: + status = "timeout" + break if res['parent_header'].get('msg_id') != idx: continue content = res['content'] @@ -40,11 +46,31 @@ def run_in_notebook(code): kc.stop_channels() km.shutdown_kernel(now=True, restart=False) assert not km.is_alive() - if status != "error": + if status not in ["error", "timeout"]: status = "ok" if not running else "exec_error" return status, output, err +@pytest.mark.parametrize("code, expected", [ + ("1 + 1", "ok"), + ("raise ValueError('This is a test error')", "error"), + ("import time; time.sleep(100)", "timeout") + +]) +def test_run_in_notebook(code, expected): + code = textwrap.dedent(code) + + t_start = time.time() + status, output, err = run_in_notebook(code, timeout=1) + duration = time.time() - t_start + assert status == expected, ( + f"Unexpected status: {status}, output: {output}, err: {err}, duration: {duration}" + ) + assert duration < 10, "Timeout not enforced properly" + if expected == "error": + assert "This is a test error" in err + + def test_deterministic_payload_for_dynamic_func_in_notebook(): code = textwrap.dedent(""" import cloudpickle From 1f28156c5c2222232f32ea2648adfb765bc93d52 Mon Sep 17 00:00:00 2001 From: tommoral Date: Tue, 4 Nov 2025 14:48:35 +0100 Subject: [PATCH 18/22] FIX import queue in run_in_notebook --- tests/cloudpickle_ipykernel_test.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/cloudpickle_ipykernel_test.py b/tests/cloudpickle_ipykernel_test.py index 0527e828..3fbaa9e6 100644 --- a/tests/cloudpickle_ipykernel_test.py +++ b/tests/cloudpickle_ipykernel_test.py @@ -2,7 +2,6 @@ import time import pytest import textwrap -from _queue import Empty from .testutils import check_deterministic_pickle @@ -16,6 +15,9 @@ def run_in_notebook(code, timeout=10): + + from _queue import Empty + km = ipykernel.connect.jupyter_client.KernelManager() km.start_kernel() kc = km.client() From 17d6ffb7171e7763ae588c3198c326cda4bdaf87 Mon Sep 17 00:00:00 2001 From: tommoral Date: Tue, 4 Nov 2025 14:50:28 +0100 Subject: [PATCH 19/22] DOC add change log entry --- CHANGES.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 517c5db5..a6b0b443 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,9 @@ In development ============== +- Make pickling of functions depending on globals in notebook more + deterministic. ([PR#560](https://github.com/cloudpipe/cloudpickle/pull/560)) + 3.1.2 ===== From b96c3e99ab844100ac667784cae4d51ecededabc Mon Sep 17 00:00:00 2001 From: tommoral Date: Tue, 4 Nov 2025 14:54:06 +0100 Subject: [PATCH 20/22] FIX correct import --- tests/cloudpickle_ipykernel_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/cloudpickle_ipykernel_test.py b/tests/cloudpickle_ipykernel_test.py index 3fbaa9e6..502d6848 100644 --- a/tests/cloudpickle_ipykernel_test.py +++ b/tests/cloudpickle_ipykernel_test.py @@ -2,6 +2,7 @@ import time import pytest import textwrap +from queue import Empty from .testutils import check_deterministic_pickle @@ -16,7 +17,6 @@ def run_in_notebook(code, timeout=10): - from _queue import Empty km = ipykernel.connect.jupyter_client.KernelManager() km.start_kernel() From 42caaae0099ad60be9f19841017e5d393202054a Mon Sep 17 00:00:00 2001 From: tommoral Date: Wed, 5 Nov 2025 06:51:32 +0100 Subject: [PATCH 21/22] CLN skip timeout tests on Pypy --- tests/cloudpickle_ipykernel_test.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/cloudpickle_ipykernel_test.py b/tests/cloudpickle_ipykernel_test.py index 502d6848..8a5cbfd7 100644 --- a/tests/cloudpickle_ipykernel_test.py +++ b/tests/cloudpickle_ipykernel_test.py @@ -1,6 +1,7 @@ import sys import time import pytest +import platform import textwrap from queue import Empty @@ -17,7 +18,6 @@ def run_in_notebook(code, timeout=10): - km = ipykernel.connect.jupyter_client.KernelManager() km.start_kernel() kc = km.client() @@ -53,6 +53,10 @@ def run_in_notebook(code, timeout=10): return status, output, err +@pytest.mark.skipif( + platform.python_implementation() == "PyPy", + reason="Skip PyPy because tests are too slow", +) @pytest.mark.parametrize("code, expected", [ ("1 + 1", "ok"), ("raise ValueError('This is a test error')", "error"), From 66db929a4b75ec0fb712f3d0d6f1ea78fbe28307 Mon Sep 17 00:00:00 2001 From: Olivier Grisel Date: Wed, 5 Nov 2025 11:43:14 +0100 Subject: [PATCH 22/22] Expand inline comment. --- cloudpickle/cloudpickle.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cloudpickle/cloudpickle.py b/cloudpickle/cloudpickle.py index 6932e23c..9f9f7971 100644 --- a/cloudpickle/cloudpickle.py +++ b/cloudpickle/cloudpickle.py @@ -837,7 +837,9 @@ def _code_reduce(obj): # co_filename is not used in the constructor of code objects, so we can # safely set it to indicate that this is dynamic code. This also makes - # the payload deterministic, independent of where the function is defined. + # the payload deterministic, independent of where the function is defined + # which is especially useful when defining classes in jupyter/ipython + # cells which do not have a deterministic filename. co_filename = "".join("") # Create shallow copies of these tuple to make cloudpickle payload deterministic.