99import warnings
1010from base64 import b64decode , b64encode
1111from queue import Empty
12+ from unittest .mock import AsyncMock as AMock
1213from unittest .mock import MagicMock , Mock
1314
1415import nbformat
@@ -345,11 +346,7 @@ def test_async_parallel_notebooks(capfd, tmpdir):
345346 res = notebook_resources ()
346347
347348 with modified_env ({"NBEXECUTE_TEST_PARALLEL_TMPDIR" : str (tmpdir )}):
348- tasks = [
349- async_run_notebook (input_file .format (label = label ), opts , res ) for label in ("A" , "B" )
350- ]
351- loop = asyncio .get_event_loop ()
352- loop .run_until_complete (asyncio .gather (* tasks ))
349+ [async_run_notebook (input_file .format (label = label ), opts , res ) for label in ("A" , "B" )]
353350
354351 captured = capfd .readouterr ()
355352 assert filter_messages_on_error_output (captured .err ) == ""
@@ -370,9 +367,7 @@ def test_many_async_parallel_notebooks(capfd):
370367 # run once, to trigger creating the original context
371368 run_notebook (input_file , opts , res )
372369
373- tasks = [async_run_notebook (input_file , opts , res ) for i in range (4 )]
374- loop = asyncio .get_event_loop ()
375- loop .run_until_complete (asyncio .gather (* tasks ))
370+ [async_run_notebook (input_file , opts , res ) for i in range (4 )]
376371
377372 captured = capfd .readouterr ()
378373 assert filter_messages_on_error_output (captured .err ) == ""
@@ -741,6 +736,80 @@ def test_widgets(self):
741736 assert 'version_major' in wdata
742737 assert 'version_minor' in wdata
743738
739+ def test_execution_hook (self ):
740+ filename = os .path .join (current_dir , 'files' , 'HelloWorld.ipynb' )
741+ with open (filename ) as f :
742+ input_nb = nbformat .read (f , 4 )
743+ hook1 , hook2 , hook3 , hook4 = MagicMock (), MagicMock (), MagicMock (), MagicMock ()
744+ executor = NotebookClient (
745+ input_nb ,
746+ on_cell_start = hook1 ,
747+ on_cell_complete = hook2 ,
748+ on_cell_error = hook3 ,
749+ on_execution_start = hook4 ,
750+ )
751+ executor .execute ()
752+ hook1 .assert_called_once ()
753+ hook2 .assert_called_once ()
754+ hook3 .assert_not_called ()
755+ hook4 .assert_called_once ()
756+
757+ def test_error_execution_hook_error (self ):
758+ filename = os .path .join (current_dir , 'files' , 'Error.ipynb' )
759+ with open (filename ) as f :
760+ input_nb = nbformat .read (f , 4 )
761+ hook1 , hook2 , hook3 , hook4 = MagicMock (), MagicMock (), MagicMock (), MagicMock ()
762+ executor = NotebookClient (
763+ input_nb ,
764+ on_cell_start = hook1 ,
765+ on_cell_complete = hook2 ,
766+ on_cell_error = hook3 ,
767+ on_execution_start = hook4 ,
768+ )
769+ with pytest .raises (CellExecutionError ):
770+ executor .execute ()
771+ hook1 .assert_called_once ()
772+ hook2 .assert_called_once ()
773+ hook3 .assert_called_once ()
774+ hook4 .assert_called_once ()
775+
776+ def test_async_execution_hook (self ):
777+ filename = os .path .join (current_dir , 'files' , 'HelloWorld.ipynb' )
778+ with open (filename ) as f :
779+ input_nb = nbformat .read (f , 4 )
780+ hook1 , hook2 , hook3 , hook4 = AMock (), AMock (), AMock (), AMock ()
781+ executor = NotebookClient (
782+ input_nb ,
783+ on_cell_start = hook1 ,
784+ on_cell_complete = hook2 ,
785+ on_cell_error = hook3 ,
786+ on_execution_start = hook4 ,
787+ )
788+ executor .execute ()
789+ hook1 .assert_called_once ()
790+ hook2 .assert_called_once ()
791+ hook3 .assert_not_called ()
792+ hook4 .assert_called_once ()
793+
794+ def test_error_async_execution_hook (self ):
795+ filename = os .path .join (current_dir , 'files' , 'Error.ipynb' )
796+ with open (filename ) as f :
797+ input_nb = nbformat .read (f , 4 )
798+ hook1 , hook2 , hook3 , hook4 = AMock (), AMock (), AMock (), AMock ()
799+ executor = NotebookClient (
800+ input_nb ,
801+ on_cell_start = hook1 ,
802+ on_cell_complete = hook2 ,
803+ on_cell_error = hook3 ,
804+ on_execution_start = hook4 ,
805+ )
806+ with pytest .raises (CellExecutionError ):
807+ executor .execute ().execute ()
808+ hook1 .assert_called_once ()
809+ hook2 .assert_called_once ()
810+ hook3 .assert_called_once ()
811+ hook4 .assert_called_once ()
812+
744813
745814class TestRunCell (NBClientTestsBase ):
746815 """Contains test functions for NotebookClient.execute_cell"""
@@ -1524,3 +1593,81 @@ def test_no_source(self, executor, cell_mock, message_mock):
15241593 assert message_mock .call_count == 0
15251594 # Should also consume the message stream
15261595 assert cell_mock .outputs == []
1596+
1597+ @prepare_cell_mocks ()
1598+ def test_cell_hooks (self , executor , cell_mock , message_mock ):
1599+ hook1 , hook2 , hook3 , hook4 = MagicMock (), MagicMock (), MagicMock (), MagicMock ()
1600+ executor .on_cell_start = hook1
1601+ executor .on_cell_complete = hook2
1602+ executor .on_cell_error = hook3
1603+ executor .on_execution_start = hook4
1604+ executor .execute_cell (cell_mock , 0 )
1605+ hook1 .assert_called_once_with (cell = cell_mock , cell_index = 0 )
1606+ hook2 .assert_called_once_with (cell = cell_mock , cell_index = 0 )
1607+ hook3 .assert_not_called ()
1608+ hook4 .assert_not_called ()
1609+
1610+ @prepare_cell_mocks (
1611+ {
1612+ 'msg_type' : 'error' ,
1613+ 'header' : {'msg_type' : 'error' },
1614+ 'content' : {'ename' : 'foo' , 'evalue' : 'bar' , 'traceback' : ['Boom' ]},
1615+ },
1616+ reply_msg = {
1617+ 'msg_type' : 'execute_reply' ,
1618+ 'header' : {'msg_type' : 'execute_reply' },
1619+ # ERROR
1620+ 'content' : {'status' : 'error' },
1621+ },
1622+ )
1623+ def test_error_cell_hooks (self , executor , cell_mock , message_mock ):
1624+ hook1 , hook2 , hook3 , hook4 = MagicMock (), MagicMock (), MagicMock (), MagicMock ()
1625+ executor .on_cell_start = hook1
1626+ executor .on_cell_complete = hook2
1627+ executor .on_cell_error = hook3
1628+ executor .on_execution_start = hook4
1629+ with self .assertRaises (CellExecutionError ):
1630+ executor .execute_cell (cell_mock , 0 )
1631+ hook1 .assert_called_once_with (cell = cell_mock , cell_index = 0 )
1632+ hook2 .assert_called_once_with (cell = cell_mock , cell_index = 0 )
1633+ hook3 .assert_called_once_with (cell = cell_mock , cell_index = 0 )
1634+ hook4 .assert_not_called ()
1635+
1636+ @prepare_cell_mocks ()
1637+ def test_async_cell_hooks (self , executor , cell_mock , message_mock ):
1638+ hook1 , hook2 , hook3 , hook4 = AMock (), AMock (), AMock (), AMock ()
1639+ executor .on_cell_start = hook1
1640+ executor .on_cell_complete = hook2
1641+ executor .on_cell_error = hook3
1642+ executor .on_execution_start = hook4
1643+ executor .execute_cell (cell_mock , 0 )
1644+ hook1 .assert_called_once_with (cell = cell_mock , cell_index = 0 )
1645+ hook2 .assert_called_once_with (cell = cell_mock , cell_index = 0 )
1646+ hook3 .assert_not_called ()
1647+ hook4 .assert_not_called ()
1648+
1649+ @prepare_cell_mocks (
1650+ {
1651+ 'msg_type' : 'error' ,
1652+ 'header' : {'msg_type' : 'error' },
1653+ 'content' : {'ename' : 'foo' , 'evalue' : 'bar' , 'traceback' : ['Boom' ]},
1654+ },
1655+ reply_msg = {
1656+ 'msg_type' : 'execute_reply' ,
1657+ 'header' : {'msg_type' : 'execute_reply' },
1658+ # ERROR
1659+ 'content' : {'status' : 'error' },
1660+ },
1661+ )
1662+ def test_error_async_cell_hooks (self , executor , cell_mock , message_mock ):
1663+ hook1 , hook2 , hook3 , hook4 = AMock (), AMock (), AMock (), AMock ()
1664+ executor .on_cell_start = hook1
1665+ executor .on_cell_complete = hook2
1666+ executor .on_cell_error = hook3
1667+ executor .on_execution_start = hook4
1668+ with self .assertRaises (CellExecutionError ):
1669+ executor .execute_cell (cell_mock , 0 )
1670+ hook1 .assert_called_once_with (cell = cell_mock , cell_index = 0 )
1671+ hook2 .assert_called_once_with (cell = cell_mock , cell_index = 0 )
1672+ hook3 .assert_called_once_with (cell = cell_mock , cell_index = 0 )
1673+ hook4 .assert_not_called ()
0 commit comments