Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-nbclient for openSUSE:Factory checked in at 2022-02-18 23:03:02 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-nbclient (Old) and /work/SRC/openSUSE:Factory/.python-nbclient.new.1958 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-nbclient" Fri Feb 18 23:03:02 2022 rev:17 rq:955826 version:0.5.11 Changes: -------- --- /work/SRC/openSUSE:Factory/python-nbclient/python-nbclient.changes 2022-01-17 22:35:29.086300901 +0100 +++ /work/SRC/openSUSE:Factory/.python-nbclient.new.1958/python-nbclient.changes 2022-02-18 23:03:37.889409616 +0100 @@ -1,0 +2,10 @@ +Thu Feb 17 16:33:39 UTC 2022 - Arun Persaud <a...@gmx.de> + +- update to version 0.5.11: + * Merged PRs + + Pin ipython<8 in tests #198 (@davidbrochart) + + Clear execution metadata, prefer msg header date when recording + times #195 (@kevin-bates) + + Client hooks #188 (@devintang3) + +------------------------------------------------------------------- Old: ---- nbclient-0.5.10.tar.gz New: ---- nbclient-0.5.11.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-nbclient.spec ++++++ --- /var/tmp/diff_new_pack.BcpbF7/_old 2022-02-18 23:03:38.445409572 +0100 +++ /var/tmp/diff_new_pack.BcpbF7/_new 2022-02-18 23:03:38.453409572 +0100 @@ -24,17 +24,15 @@ %define psuffix %{nil} %bcond_with test %endif - %if 0%{?suse_version} > 1500 %bcond_without libalternatives %else %bcond_with libalternatives %endif - %{?!python_module:%define python_module() python3-%{**}} %define skip_python2 1 Name: python-nbclient%{psuffix} -Version: 0.5.10 +Version: 0.5.11 Release: 0 Summary: A client library for executing notebooks License: BSD-3-Clause @@ -48,14 +46,14 @@ Requires: python-nbformat >= 5.0 Requires: python-nest-asyncio Requires: python-traitlets >= 4.2 +BuildArch: noarch %if %{with libalternatives} -Requires: alts BuildRequires: alts +Requires: alts %else Requires(post): update-alternatives Requires(postun):update-alternatives %endif -BuildArch: noarch %if %{with test} BuildRequires: %{python_module ipykernel} BuildRequires: %{python_module ipython} ++++++ nbclient-0.5.10.tar.gz -> nbclient-0.5.11.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nbclient-0.5.10/CHANGELOG.md new/nbclient-0.5.11/CHANGELOG.md --- old/nbclient-0.5.10/CHANGELOG.md 2022-01-13 21:34:31.000000000 +0100 +++ new/nbclient-0.5.11/CHANGELOG.md 2022-02-14 20:49:07.000000000 +0100 @@ -2,6 +2,24 @@ <!-- <START NEW CHANGELOG ENTRY> --> +## 0.5.11 + +([Full Changelog](https://github.com/jupyter/nbclient/compare/v0.5.10...050c7da89a98159e6361b1ad0dbefd215db5f816)) + +### Merged PRs + +- Pin ipython<8 in tests [#198](https://github.com/jupyter/nbclient/pull/198) ([@davidbrochart](https://github.com/davidbrochart)) +- Clear execution metadata, prefer msg header date when recording times [#195](https://github.com/jupyter/nbclient/pull/195) ([@kevin-bates](https://github.com/kevin-bates)) +- Client hooks [#188](https://github.com/jupyter/nbclient/pull/188) ([@devintang3](https://github.com/devintang3)) + +### Contributors to this release + +([GitHub contributors page for this release](https://github.com/jupyter/nbclient/graphs/contributors?from=2022-01-13&to=2022-02-14&type=c)) + +[@davidbrochart](https://github.com/search?q=repo%3Ajupyter%2Fnbclient+involves%3Adavidbrochart+updated%3A2022-01-13..2022-02-14&type=Issues) | [@devintang3](https://github.com/search?q=repo%3Ajupyter%2Fnbclient+involves%3Adevintang3+updated%3A2022-01-13..2022-02-14&type=Issues) | [@kevin-bates](https://github.com/search?q=repo%3Ajupyter%2Fnbclient+involves%3Akevin-bates+updated%3A2022-01-13..2022-02-14&type=Issues) + +<!-- <END NEW CHANGELOG ENTRY> --> + ## 0.5.10 ([Full Changelog](https://github.com/jupyter/nbclient/compare/v0.5.9...e82c5d8d064ac1097f4e12f387b4c47ea5c576ff)) @@ -22,8 +40,6 @@ [@davidbrochart](https://github.com/search?q=repo%3Ajupyter%2Fnbclient+involves%3Adavidbrochart+updated%3A2021-11-19..2022-01-13&type=Issues) | [@frenzymadness](https://github.com/search?q=repo%3Ajupyter%2Fnbclient+involves%3Afrenzymadness+updated%3A2021-11-19..2022-01-13&type=Issues) | [@kianmeng](https://github.com/search?q=repo%3Ajupyter%2Fnbclient+involves%3Akianmeng+updated%3A2021-11-19..2022-01-13&type=Issues) | [@martinRenou](https://github.com/search?q=repo%3Ajupyter%2Fnbclient+involves%3AmartinRenou+updated%3A2021-11-19..2022-01-13&type=Issues) | [@takluyver](https://github.com/search?q=repo%3Ajupyter%2Fnbclient+involves%3Atakluyver+updated%3A2021-11-19..2022-01-13&type=Issues) -<!-- <END NEW CHANGELOG ENTRY> --> - ## 0.5.9 ([Full Changelog](https://github.com/jupyter/nbclient/compare/v0.5.8...0146681d7ffd62cbc675c8d1463a2b016a3d3008)) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nbclient-0.5.10/PKG-INFO new/nbclient-0.5.11/PKG-INFO --- old/nbclient-0.5.10/PKG-INFO 2022-01-13 21:35:03.114851200 +0100 +++ new/nbclient-0.5.11/PKG-INFO 2022-02-14 20:49:44.258455500 +0100 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: nbclient -Version: 0.5.10 +Version: 0.5.11 Summary: A client library for executing notebooks. Formerly nbconvert's ExecutePreprocessor. Home-page: https://jupyter.org Author: Jupyter Development Team diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nbclient-0.5.10/docs/client.rst new/nbclient-0.5.11/docs/client.rst --- old/nbclient-0.5.10/docs/client.rst 2022-01-13 21:34:31.000000000 +0100 +++ new/nbclient-0.5.11/docs/client.rst 2022-02-14 20:49:07.000000000 +0100 @@ -96,6 +96,36 @@ maintain consistency: we can just run a notebook twice, specifying first "python2" and then "python3" as the kernel name. +Hooks before and after notebook or cell execution +------------------------------------------------- +There are several configurable hooks that allow the user to execute code before and +after a notebook or a cell is executed. Each one is configured with a function that will be called in its +respective place in the execution pipeline. +Each is described below: + +**Notebook-level hooks**: These hooks are called with a single extra parameter: + +- ``notebook=NotebookNode``: the current notebook being executed. + +Here is the available hooks: + +- ``on_notebook_start`` will run when the notebook client is initialized, before any execution has happened. +- ``on_notebook_complete`` will run when the notebook client has finished executing, after kernel cleanup. +- ``on_notebook_error`` will run when the notebook client has encountered an exception before kernel cleanup. + +**Cell-level hooks**: These hooks are called with two parameters: + +- ``cell=NotebookNode``: a reference to the current cell. +- ``cell_index=int``: the index of the cell in the current notebook's list of cells. + +Here are the available hooks: + +- ``on_cell_start`` will run for all cell types before the cell is executed. +- ``on_cell_execute`` will run right before the code cell is executed. +- ``on_cell_complete`` will run after execution, if the cell is executed with no errors. +- ``on_cell_error`` will run if there is an error during cell execution. + + Handling errors and exceptions ------------------------------ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nbclient-0.5.10/nbclient/_version.py new/nbclient-0.5.11/nbclient/_version.py --- old/nbclient-0.5.10/nbclient/_version.py 2022-01-13 21:34:51.000000000 +0100 +++ new/nbclient-0.5.11/nbclient/_version.py 2022-02-14 20:49:29.000000000 +0100 @@ -1,7 +1,7 @@ import re from typing import List, Union -__version__ = "0.5.10" +__version__ = "0.5.11" # Build up version_info tuple for backwards compatibility pattern = r'(?P<major>\d+).(?P<minor>\d+).(?P<patch>\d+)(?P<rest>.*)' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nbclient-0.5.10/nbclient/client.py new/nbclient-0.5.11/nbclient/client.py --- old/nbclient-0.5.10/nbclient/client.py 2022-01-13 21:34:31.000000000 +0100 +++ new/nbclient-0.5.11/nbclient/client.py 2022-02-14 20:49:07.000000000 +0100 @@ -9,12 +9,24 @@ from queue import Empty from textwrap import dedent from time import monotonic +from typing import Optional from jupyter_client import KernelManager from jupyter_client.client import KernelClient from nbformat import NotebookNode from nbformat.v4 import output_from_msg -from traitlets import Any, Bool, Dict, Enum, Integer, List, Type, Unicode, default +from traitlets import ( + Any, + Bool, + Callable, + Dict, + Enum, + Integer, + List, + Type, + Unicode, + default, +) from traitlets.config.configurable import LoggingConfigurable from .exceptions import ( @@ -25,10 +37,25 @@ DeadKernelError, ) from .output_widget import OutputWidget -from .util import ensure_async, run_sync +from .util import ensure_async, run_hook, run_sync -def timestamp() -> str: +def timestamp(msg: Optional[Dict] = None) -> str: + if msg and 'header' in msg: # The test mocks don't provide a header, so tolerate that + msg_header = msg['header'] + if 'date' in msg_header and isinstance(msg_header['date'], datetime.datetime): + try: + # reformat datetime into expected format + formatted_time = datetime.datetime.strftime( + msg_header['date'], '%Y-%m-%dT%H:%M:%S.%fZ' + ) + if ( + formatted_time + ): # docs indicate strftime may return empty string, so let's catch that too + return formatted_time + except Exception: + pass # fallback to a local time + return datetime.datetime.utcnow().isoformat() + 'Z' @@ -245,6 +272,87 @@ kernel_manager_class: KernelManager = Type(config=True, help='The kernel manager class to use.') + on_notebook_start: t.Optional[t.Callable] = Callable( + default_value=None, + allow_none=True, + help=dedent( + """ + A callable which executes after the kernel manager and kernel client are setup, and + cells are about to execute. + Called with kwargs `notebook`. + """ + ), + ).tag(config=True) + + on_notebook_complete: t.Optional[t.Callable] = Callable( + default_value=None, + allow_none=True, + help=dedent( + """ + A callable which executes after the kernel is cleaned up. + Called with kwargs `notebook`. + """ + ), + ).tag(config=True) + + on_notebook_error: t.Optional[t.Callable] = Callable( + default_value=None, + allow_none=True, + help=dedent( + """ + A callable which executes when the notebook encounters an error. + Called with kwargs `notebook`. + """ + ), + ).tag(config=True) + + on_cell_start: t.Optional[t.Callable] = Callable( + default_value=None, + allow_none=True, + help=dedent( + """ + A callable which executes before a cell is executed and before non-executing cells + are skipped. + Called with kwargs `cell` and `cell_index`. + """ + ), + ).tag(config=True) + + on_cell_execute: t.Optional[t.Callable] = Callable( + default_value=None, + allow_none=True, + help=dedent( + """ + A callable which executes just before a code cell is executed. + Called with kwargs `cell` and `cell_index`. + """ + ), + ).tag(config=True) + + on_cell_complete: t.Optional[t.Callable] = Callable( + default_value=None, + allow_none=True, + help=dedent( + """ + A callable which executes after a cell execution is complete. It is + called even when a cell results in a failure. + Called with kwargs `cell` and `cell_index`. + """ + ), + ).tag(config=True) + + on_cell_error: t.Optional[t.Callable] = Callable( + default_value=None, + allow_none=True, + help=dedent( + """ + A callable which executes when a cell execution results in an error. + This is executed even if errors are suppressed with `cell_allows_errors`. + Called with kwargs `cell` and `cell_index`. + """ + ), + ).tag(config=True) + @default('kernel_manager_class') def _kernel_manager_class_default(self) -> KernelManager: """Use a dynamic default to avoid importing jupyter_client at startup""" @@ -426,6 +534,7 @@ await self._async_cleanup_kernel() raise self.kc.allow_stdin = False + await run_hook(self.on_notebook_start, notebook=self.nb) return self.kc start_new_kernel_client = run_sync(async_start_new_kernel_client) @@ -497,10 +606,13 @@ await self.async_start_new_kernel_client() try: yield + except RuntimeError as e: + await run_hook(self.on_notebook_error, notebook=self.nb) + raise e finally: if cleanup_kc: await self._async_cleanup_kernel() - + await run_hook(self.on_notebook_complete, notebook=self.nb) atexit.unregister(self._cleanup_kernel) try: loop.remove_signal_handler(signal.SIGINT) @@ -618,7 +730,7 @@ msg = await ensure_async(self.kc.shell_channel.get_msg(timeout=new_timeout)) if msg['parent_header'].get('msg_id') == msg_id: if self.record_timing: - cell['metadata']['execution']['shell.execute_reply'] = timestamp() + cell['metadata']['execution']['shell.execute_reply'] = timestamp(msg) try: await asyncio.wait_for(task_poll_output_msg, self.iopub_timeout) except (asyncio.TimeoutError, Empty): @@ -729,7 +841,9 @@ return True return False - def _check_raise_for_error(self, cell: NotebookNode, exec_reply: t.Optional[t.Dict]) -> None: + async def _check_raise_for_error( + self, cell: NotebookNode, cell_index: int, exec_reply: t.Optional[t.Dict] + ) -> None: if exec_reply is None: return None @@ -743,7 +857,7 @@ or exec_reply_content.get('ename') in self.allow_error_names or "raises-exception" in cell.metadata.get("tags", []) ) - + await run_hook(self.on_cell_error, cell=cell, cell_index=cell_index) if not cell_allows_errors: raise CellExecutionError.from_cell_and_msg(cell, exec_reply_content) @@ -788,6 +902,9 @@ The cell which was just processed. """ assert self.kc is not None + + await run_hook(self.on_cell_start, cell=cell, cell_index=cell_index) + if cell.cell_type != 'code' or not cell.source.strip(): self.log.debug("Skipping non-executing cell %s", cell_index) return cell @@ -796,7 +913,7 @@ self.log.debug("Skipping tagged cell %s", cell_index) return cell - if self.record_timing and 'execution' not in cell['metadata']: + if self.record_timing: # clear execution metadata prior to execution cell['metadata']['execution'] = {} self.log.debug("Executing cell:\n%s", cell.source) @@ -805,11 +922,13 @@ self.allow_errors or "raises-exception" in cell.metadata.get("tags", []) ) + await run_hook(self.on_cell_execute, cell=cell, cell_index=cell_index) parent_msg_id = await ensure_async( self.kc.execute( cell.source, store_history=store_history, stop_on_error=not cell_allows_errors ) ) + await run_hook(self.on_cell_complete, cell=cell, cell_index=cell_index) # We launched a code cell to execute self.code_cells_executed += 1 exec_timeout = self._get_timeout(cell) @@ -843,7 +962,7 @@ if execution_count: cell['execution_count'] = execution_count - self._check_raise_for_error(cell, exec_reply) + await self._check_raise_for_error(cell, cell_index, exec_reply) self.nb['cells'][cell_index] = cell return cell @@ -894,11 +1013,11 @@ if self.record_timing: if msg_type == 'status': if content['execution_state'] == 'idle': - cell['metadata']['execution']['iopub.status.idle'] = timestamp() + cell['metadata']['execution']['iopub.status.idle'] = timestamp(msg) elif content['execution_state'] == 'busy': - cell['metadata']['execution']['iopub.status.busy'] = timestamp() + cell['metadata']['execution']['iopub.status.busy'] = timestamp(msg) elif msg_type == 'execute_input': - cell['metadata']['execution']['iopub.execute_input'] = timestamp() + cell['metadata']['execution']['iopub.execute_input'] = timestamp(msg) if msg_type == 'status': if content['execution_state'] == 'idle': diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nbclient-0.5.10/nbclient/tests/test_client.py new/nbclient-0.5.11/nbclient/tests/test_client.py --- old/nbclient-0.5.10/nbclient/tests/test_client.py 2022-01-13 21:34:31.000000000 +0100 +++ new/nbclient-0.5.11/nbclient/tests/test_client.py 2022-02-14 20:49:07.000000000 +0100 @@ -33,6 +33,16 @@ # see: https://github.com/ipython/ipython/blob/master/docs/source/whatsnew/version8.rst#traceback-improvements # noqa ipython8_input_pat = re.compile(r'Input In \[\d+\],') +hook_methods = [ + "on_cell_start", + "on_cell_execute", + "on_cell_complete", + "on_cell_error", + "on_notebook_start", + "on_notebook_complete", + "on_notebook_error", +] + class AsyncMock(Mock): pass @@ -741,6 +751,82 @@ assert 'version_major' in wdata assert 'version_minor' in wdata + def test_execution_hook(self): + filename = os.path.join(current_dir, 'files', 'HelloWorld.ipynb') + with open(filename) as f: + input_nb = nbformat.read(f, 4) + hooks = [MagicMock() for i in range(7)] + executor = NotebookClient(input_nb) + for executor_hook, hook in zip(hook_methods, hooks): + setattr(executor, executor_hook, hook) + executor.execute() + for hook in hooks[:3]: + hook.assert_called_once() + hooks[3].assert_not_called() + for hook in hooks[4:6]: + hook.assert_called_once() + hooks[6].assert_not_called() + + def test_error_execution_hook_error(self): + filename = os.path.join(current_dir, 'files', 'Error.ipynb') + with open(filename) as f: + input_nb = nbformat.read(f, 4) + hooks = [MagicMock() for i in range(7)] + executor = NotebookClient(input_nb) + for executor_hook, hook in zip(hook_methods, hooks): + setattr(executor, executor_hook, hook) + with pytest.raises(CellExecutionError): + executor.execute() + for hook in hooks[:5]: + hook.assert_called_once() + hooks[6].assert_not_called() + + def test_error_notebook_hook(self): + filename = os.path.join(current_dir, 'files', 'Autokill.ipynb') + with open(filename) as f: + input_nb = nbformat.read(f, 4) + hooks = [MagicMock() for i in range(7)] + executor = NotebookClient(input_nb) + for executor_hook, hook in zip(hook_methods, hooks): + setattr(executor, executor_hook, hook) + with pytest.raises(RuntimeError): + executor.execute() + for hook in hooks[:3]: + hook.assert_called_once() + hooks[3].assert_not_called() + for hook in hooks[4:]: + hook.assert_called_once() + + def test_async_execution_hook(self): + filename = os.path.join(current_dir, 'files', 'HelloWorld.ipynb') + with open(filename) as f: + input_nb = nbformat.read(f, 4) + hooks = [AsyncMock() for i in range(7)] + executor = NotebookClient(input_nb) + for executor_hook, hook in zip(hook_methods, hooks): + setattr(executor, executor_hook, hook) + executor.execute() + for hook in hooks[:3]: + hook.assert_called_once() + hooks[3].assert_not_called() + for hook in hooks[4:6]: + hook.assert_called_once() + hooks[6].assert_not_called() + + def test_error_async_execution_hook(self): + filename = os.path.join(current_dir, 'files', 'Error.ipynb') + with open(filename) as f: + input_nb = nbformat.read(f, 4) + hooks = [AsyncMock() for i in range(7)] + executor = NotebookClient(input_nb) + for executor_hook, hook in zip(hook_methods, hooks): + setattr(executor, executor_hook, hook) + with pytest.raises(CellExecutionError): + executor.execute().execute() + for hook in hooks[:5]: + hook.assert_called_once() + hooks[6].assert_not_called() + class TestRunCell(NBClientTestsBase): """Contains test functions for NotebookClient.execute_cell""" @@ -1524,3 +1610,92 @@ assert message_mock.call_count == 0 # Should also consume the message stream assert cell_mock.outputs == [] + + @prepare_cell_mocks() + def test_cell_hooks(self, executor, cell_mock, message_mock): + hooks = [MagicMock() for i in range(7)] + for executor_hook, hook in zip(hook_methods, hooks): + setattr(executor, executor_hook, hook) + executor.execute_cell(cell_mock, 0) + for hook in hooks[:3]: + hook.assert_called_once_with(cell=cell_mock, cell_index=0) + for hook in hooks[4:]: + hook.assert_not_called() + + @prepare_cell_mocks( + { + 'msg_type': 'error', + 'header': {'msg_type': 'error'}, + 'content': {'ename': 'foo', 'evalue': 'bar', 'traceback': ['Boom']}, + }, + reply_msg={ + 'msg_type': 'execute_reply', + 'header': {'msg_type': 'execute_reply'}, + # ERROR + 'content': {'status': 'error'}, + }, + ) + def test_error_cell_hooks(self, executor, cell_mock, message_mock): + hooks = [MagicMock() for i in range(7)] + for executor_hook, hook in zip(hook_methods, hooks): + setattr(executor, executor_hook, hook) + with self.assertRaises(CellExecutionError): + executor.execute_cell(cell_mock, 0) + for hook in hooks[:4]: + hook.assert_called_once_with(cell=cell_mock, cell_index=0) + for hook in hooks[5:]: + hook.assert_not_called() + + @prepare_cell_mocks( + reply_msg={ + 'msg_type': 'execute_reply', + 'header': {'msg_type': 'execute_reply'}, + # ERROR + 'content': {'status': 'error'}, + } + ) + def test_non_code_cell_hooks(self, executor, cell_mock, message_mock): + cell_mock = NotebookNode(source='"foo" = "bar"', metadata={}, cell_type='raw', outputs=[]) + hooks = [MagicMock() for i in range(7)] + for executor_hook, hook in zip(hook_methods, hooks): + setattr(executor, executor_hook, hook) + executor.execute_cell(cell_mock, 0) + for hook in hooks[:1]: + hook.assert_called_once_with(cell=cell_mock, cell_index=0) + for hook in hooks[1:]: + hook.assert_not_called() + + @prepare_cell_mocks() + def test_async_cell_hooks(self, executor, cell_mock, message_mock): + hooks = [AsyncMock() for i in range(7)] + for executor_hook, hook in zip(hook_methods, hooks): + setattr(executor, executor_hook, hook) + executor.execute_cell(cell_mock, 0) + for hook in hooks[:3]: + hook.assert_called_once_with(cell=cell_mock, cell_index=0) + for hook in hooks[4:]: + hook.assert_not_called() + + @prepare_cell_mocks( + { + 'msg_type': 'error', + 'header': {'msg_type': 'error'}, + 'content': {'ename': 'foo', 'evalue': 'bar', 'traceback': ['Boom']}, + }, + reply_msg={ + 'msg_type': 'execute_reply', + 'header': {'msg_type': 'execute_reply'}, + # ERROR + 'content': {'status': 'error'}, + }, + ) + def test_error_async_cell_hooks(self, executor, cell_mock, message_mock): + hooks = [AsyncMock() for i in range(7)] + for executor_hook, hook in zip(hook_methods, hooks): + setattr(executor, executor_hook, hook) + with self.assertRaises(CellExecutionError): + executor.execute_cell(cell_mock, 0) + for hook in hooks[:4]: + hook.assert_called_once_with(cell=cell_mock, cell_index=0) + for hook in hooks[4:]: + hook.assert_not_called() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nbclient-0.5.10/nbclient/tests/test_util.py new/nbclient-0.5.11/nbclient/tests/test_util.py --- old/nbclient-0.5.10/nbclient/tests/test_util.py 1970-01-01 01:00:00.000000000 +0100 +++ new/nbclient-0.5.11/nbclient/tests/test_util.py 2022-02-14 20:49:07.000000000 +0100 @@ -0,0 +1,73 @@ +import asyncio +from unittest.mock import MagicMock + +import pytest +import tornado + +from nbclient.util import run_hook, run_sync + + +@run_sync +async def some_async_function(): + await asyncio.sleep(0.01) + return 42 + + +def test_nested_asyncio_with_existing_ioloop(): + ioloop = asyncio.new_event_loop() + try: + asyncio.set_event_loop(ioloop) + assert some_async_function() == 42 + assert asyncio.get_event_loop() is ioloop + finally: + asyncio._set_running_loop(None) # it seems nest_asyncio doesn't reset this + + +def test_nested_asyncio_with_no_ioloop(): + asyncio.set_event_loop(None) + try: + assert some_async_function() == 42 + finally: + asyncio._set_running_loop(None) # it seems nest_asyncio doesn't reset this + + +def test_nested_asyncio_with_tornado(): + # This tests if tornado accepts the pure-Python Futures, see + # https://github.com/tornadoweb/tornado/issues/2753 + # https://github.com/erdewit/nest_asyncio/issues/23 + asyncio.set_event_loop(asyncio.new_event_loop()) + ioloop = tornado.ioloop.IOLoop.current() + + async def some_async_function(): + future = asyncio.ensure_future(asyncio.sleep(0.1)) + # this future is a different future after nested-asyncio has patched + # the asyncio module, check if tornado likes it: + ioloop.add_future(future, lambda f: f.result()) + await future + return 42 + + def some_sync_function(): + return run_sync(some_async_function)() + + async def run(): + # calling some_async_function directly should work + assert await some_async_function() == 42 + # but via a sync function (using nested-asyncio) can lead to issues: + # https://github.com/tornadoweb/tornado/issues/2753 + assert some_sync_function() == 42 + + ioloop.run_sync(run) + + +@pytest.mark.asyncio +async def test_run_hook_sync(): + some_sync_function = MagicMock() + await run_hook(some_sync_function) + assert some_sync_function.call_count == 1 + + +@pytest.mark.asyncio +async def test_run_hook_async(): + hook = MagicMock(return_value=some_async_function()) + await run_hook(hook) + assert hook.call_count == 1 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nbclient-0.5.10/nbclient/tests/util.py new/nbclient-0.5.11/nbclient/tests/util.py --- old/nbclient-0.5.10/nbclient/tests/util.py 2022-01-13 21:34:31.000000000 +0100 +++ new/nbclient-0.5.11/nbclient/tests/util.py 1970-01-01 01:00:00.000000000 +0100 @@ -1,57 +0,0 @@ -import asyncio - -import tornado - -from nbclient.util import run_sync - - -@run_sync -async def some_async_function(): - await asyncio.sleep(0.01) - return 42 - - -def test_nested_asyncio_with_existing_ioloop(): - ioloop = asyncio.new_event_loop() - try: - asyncio.set_event_loop(ioloop) - assert some_async_function() == 42 - assert asyncio.get_event_loop() is ioloop - finally: - asyncio._set_running_loop(None) # it seems nest_asyncio doesn't reset this - - -def test_nested_asyncio_with_no_ioloop(): - asyncio.set_event_loop(None) - try: - assert some_async_function() == 42 - finally: - asyncio._set_running_loop(None) # it seems nest_asyncio doesn't reset this - - -def test_nested_asyncio_with_tornado(): - # This tests if tornado accepts the pure-Python Futures, see - # https://github.com/tornadoweb/tornado/issues/2753 - # https://github.com/erdewit/nest_asyncio/issues/23 - asyncio.set_event_loop(asyncio.new_event_loop()) - ioloop = tornado.ioloop.IOLoop.current() - - async def some_async_function(): - future = asyncio.ensure_future(asyncio.sleep(0.1)) - # this future is a different future after nested-asyncio has patched - # the asyncio module, check if tornado likes it: - ioloop.add_future(future, lambda f: f.result()) - await future - return 42 - - def some_sync_function(): - return run_sync(some_async_function)() - - async def run(): - # calling some_async_function directly should work - assert await some_async_function() == 42 - # but via a sync function (using nested-asyncio) can lead to issues: - # https://github.com/tornadoweb/tornado/issues/2753 - assert some_sync_function() == 42 - - ioloop.run_sync(run) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nbclient-0.5.10/nbclient/util.py new/nbclient-0.5.11/nbclient/util.py --- old/nbclient-0.5.10/nbclient/util.py 2022-01-13 21:34:31.000000000 +0100 +++ new/nbclient-0.5.11/nbclient/util.py 2022-02-14 20:49:07.000000000 +0100 @@ -6,7 +6,7 @@ import asyncio import inspect import sys -from typing import Any, Awaitable, Callable, Union +from typing import Any, Awaitable, Callable, Optional, Union def check_ipython() -> None: @@ -102,3 +102,11 @@ return result # obj doesn't need to be awaited return obj + + +async def run_hook(hook: Optional[Callable], **kwargs) -> None: + if hook is None: + return + res = hook(**kwargs) + if inspect.isawaitable(res): + await res diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nbclient-0.5.10/nbclient.egg-info/PKG-INFO new/nbclient-0.5.11/nbclient.egg-info/PKG-INFO --- old/nbclient-0.5.10/nbclient.egg-info/PKG-INFO 2022-01-13 21:35:03.000000000 +0100 +++ new/nbclient-0.5.11/nbclient.egg-info/PKG-INFO 2022-02-14 20:49:43.000000000 +0100 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: nbclient -Version: 0.5.10 +Version: 0.5.11 Summary: A client library for executing notebooks. Formerly nbconvert's ExecutePreprocessor. Home-page: https://jupyter.org Author: Jupyter Development Team diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nbclient-0.5.10/nbclient.egg-info/SOURCES.txt new/nbclient-0.5.11/nbclient.egg-info/SOURCES.txt --- old/nbclient-0.5.10/nbclient.egg-info/SOURCES.txt 2022-01-13 21:35:03.000000000 +0100 +++ new/nbclient-0.5.11/nbclient.egg-info/SOURCES.txt 2022-02-14 20:49:44.000000000 +0100 @@ -42,7 +42,7 @@ nbclient/tests/conftest.py nbclient/tests/fake_kernelmanager.py nbclient/tests/test_client.py -nbclient/tests/util.py +nbclient/tests/test_util.py nbclient/tests/files/Autokill.ipynb nbclient/tests/files/Check History in Memory.ipynb nbclient/tests/files/Clear Output.ipynb diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nbclient-0.5.10/nbclient.egg-info/entry_points.txt new/nbclient-0.5.11/nbclient.egg-info/entry_points.txt --- old/nbclient-0.5.10/nbclient.egg-info/entry_points.txt 2022-01-13 21:35:03.000000000 +0100 +++ new/nbclient-0.5.11/nbclient.egg-info/entry_points.txt 2022-02-14 20:49:44.000000000 +0100 @@ -1,3 +1,2 @@ [console_scripts] jupyter-execute = nbclient.cli:main - diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nbclient-0.5.10/nbclient.egg-info/requires.txt new/nbclient-0.5.11/nbclient.egg-info/requires.txt --- old/nbclient-0.5.10/nbclient.egg-info/requires.txt 2022-01-13 21:35:03.000000000 +0100 +++ new/nbclient-0.5.11/nbclient.egg-info/requires.txt 2022-02-14 20:49:44.000000000 +0100 @@ -11,10 +11,11 @@ myst-parser [test] -ipython +ipython<8.0.0 ipykernel ipywidgets<8.0.0 pytest>=4.1 +pytest-asyncio pytest-cov>=2.6.1 check-manifest flake8 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nbclient-0.5.10/pyproject.toml new/nbclient-0.5.11/pyproject.toml --- old/nbclient-0.5.10/pyproject.toml 2022-01-13 21:34:51.000000000 +0100 +++ new/nbclient-0.5.11/pyproject.toml 2022-02-14 20:49:29.000000000 +0100 @@ -53,7 +53,7 @@ ignore = [".mailmap", "*.yml", "*.yaml"] [tool.tbump.version] -current = "0.5.10" +current = "0.5.11" regex = ''' (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+) ((?P<channel>a|b|rc|.dev)(?P<release>\d+))? diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nbclient-0.5.10/requirements-dev.txt new/nbclient-0.5.11/requirements-dev.txt --- old/nbclient-0.5.10/requirements-dev.txt 2022-01-13 21:34:31.000000000 +0100 +++ new/nbclient-0.5.11/requirements-dev.txt 2022-02-14 20:49:07.000000000 +0100 @@ -1,7 +1,8 @@ -ipython +ipython<8.0.0 ipykernel ipywidgets<8.0.0 pytest>=4.1 +pytest-asyncio pytest-cov>=2.6.1 check-manifest flake8