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 <[email protected]>
+
+- 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)
+
+
[email protected]
+async def test_run_hook_sync():
+ some_sync_function = MagicMock()
+ await run_hook(some_sync_function)
+ assert some_sync_function.call_count == 1
+
+
[email protected]
+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