diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ca512681..81536479 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,12 +12,12 @@ jobs: name: "Python ${{ matrix.python-version }}" runs-on: "ubuntu-latest" env: - USING_COVERAGE: '3.10,3.11,3.12,3.13' + USING_COVERAGE: '3.10,3.11,3.12,3.13,3.14' strategy: fail-fast: false matrix: - python-version: ["3.10", "3.11", "3.12", "3.13"] + python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"] steps: - uses: "actions/checkout@v6" diff --git a/papermill/tests/test_execute.py b/papermill/tests/test_execute.py index 09600d64..07db7876 100644 --- a/papermill/tests/test_execute.py +++ b/papermill/tests/test_execute.py @@ -159,7 +159,9 @@ def test(self): self.assertRegex(nb.cells[5].source, '') self.assertEqual(nb.cells[5].metadata["tags"], ["papermill-error-cell-tag"]) self.assertEqual(nb.cells[6].execution_count, 2) - self.assertEqual(nb.cells[6].outputs[0].output_type, 'error') + # Python 3.14+ emits a stderr stream before the error output for assertion errors + output_types = [o.output_type for o in nb.cells[6].outputs] + self.assertIn('error', output_types) self.assertEqual(nb.cells[7].execution_count, None) @@ -187,8 +189,10 @@ def test(self): self.assertEqual(nb.cells[2].cell_type, "markdown") self.assertRegex(nb.cells[2].source, '') self.assertEqual(nb.cells[3].execution_count, 2) - self.assertEqual(nb.cells[3].outputs[0].output_type, 'display_data') - self.assertEqual(nb.cells[3].outputs[1].output_type, 'error') + # Python 3.14+ may insert a stderr stream between display_data and error outputs + output_types = [o.output_type for o in nb.cells[3].outputs] + self.assertIn('display_data', output_types) + self.assertIn('error', output_types) self.assertEqual(nb.cells[4].execution_count, None) @@ -279,9 +283,12 @@ def test_sys_exit(self): self.assertEqual(nb.cells[0].cell_type, "code") self.assertEqual(nb.cells[0].execution_count, 1) self.assertEqual(nb.cells[1].execution_count, 2) - self.assertEqual(nb.cells[1].outputs[0].output_type, 'error') - self.assertEqual(nb.cells[1].outputs[0].ename, 'SystemExit') - self.assertEqual(nb.cells[1].outputs[0].evalue, '') + # Python 3.14+ may emit a stderr stream before the error output + output_types = [o.output_type for o in nb.cells[1].outputs] + self.assertIn('error', output_types) + error_output = next(o for o in nb.cells[1].outputs if o.output_type == 'error') + self.assertEqual(error_output.ename, 'SystemExit') + self.assertEqual(error_output.evalue, '') self.assertEqual(nb.cells[2].execution_count, None) def test_sys_exit0(self): @@ -292,9 +299,12 @@ def test_sys_exit0(self): self.assertEqual(nb.cells[0].cell_type, "code") self.assertEqual(nb.cells[0].execution_count, 1) self.assertEqual(nb.cells[1].execution_count, 2) - self.assertEqual(nb.cells[1].outputs[0].output_type, 'error') - self.assertEqual(nb.cells[1].outputs[0].ename, 'SystemExit') - self.assertEqual(nb.cells[1].outputs[0].evalue, '0') + # Python 3.14+ may emit a stderr stream before the error output + output_types = [o.output_type for o in nb.cells[1].outputs] + self.assertIn('error', output_types) + error_output = next(o for o in nb.cells[1].outputs if o.output_type == 'error') + self.assertEqual(error_output.ename, 'SystemExit') + self.assertEqual(error_output.evalue, '0') self.assertEqual(nb.cells[2].execution_count, None) def test_sys_exit1(self): @@ -310,7 +320,9 @@ def test_sys_exit1(self): self.assertEqual(nb.cells[2].cell_type, "markdown") self.assertRegex(nb.cells[2].source, '') self.assertEqual(nb.cells[3].execution_count, 2) - self.assertEqual(nb.cells[3].outputs[0].output_type, 'error') + # Python 3.14+ may emit a stderr stream before the error output + output_types = [o.output_type for o in nb.cells[3].outputs] + self.assertIn('error', output_types) self.assertEqual(nb.cells[4].execution_count, None) @@ -322,9 +334,12 @@ def test_system_exit(self): self.assertEqual(nb.cells[0].cell_type, "code") self.assertEqual(nb.cells[0].execution_count, 1) self.assertEqual(nb.cells[1].execution_count, 2) - self.assertEqual(nb.cells[1].outputs[0].output_type, 'error') - self.assertEqual(nb.cells[1].outputs[0].ename, 'SystemExit') - self.assertEqual(nb.cells[1].outputs[0].evalue, '') + # Python 3.14+ may emit a stderr stream before the error output + output_types = [o.output_type for o in nb.cells[1].outputs] + self.assertIn('error', output_types) + error_output = next(o for o in nb.cells[1].outputs if o.output_type == 'error') + self.assertEqual(error_output.ename, 'SystemExit') + self.assertEqual(error_output.evalue, '') self.assertEqual(nb.cells[2].execution_count, None) def test_line_magic_error(self): @@ -456,6 +471,8 @@ def test_output_formatting(self): self.assertEqual(nb.cells[2].cell_type, "markdown") self.assertRegex(nb.cells[2].source, '') self.assertEqual(nb.cells[3].execution_count, 2) - self.assertEqual(nb.cells[3].outputs[0].output_type, 'error') + # Python 3.14+ may emit a stderr stream before the error output + output_types = [o.output_type for o in nb.cells[3].outputs] + self.assertIn('error', output_types) self.assertEqual(nb.cells[4].execution_count, None) diff --git a/pyproject.toml b/pyproject.toml index 99336a0a..c485f8e5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -225,7 +225,7 @@ omit = [ legacy_tox_ini = """ [tox] skipsdist = true -envlist = py{310,311,312,313}, dist, manifest, docs, binder +envlist = py{310,311,312,313,314}, dist, manifest, docs, binder [gh-actions] python = @@ -233,6 +233,7 @@ python = 3.11: py311 3.12: py312 3.13: py313, docs, dist + 3.14: py314 # Manifest [testenv:manifest] @@ -280,6 +281,7 @@ basepython = py311: python3.11 py312: python3.12 py313: python3.13 + py314: python3.14 manifest: python3.13 dist: python3.13 docs: python3.13