nteract / testbook

🧪 📗 Unit test your Jupyter Notebooks the right way
https://testbook.readthedocs.io
BSD 3-Clause "New" or "Revised" License
417 stars 37 forks source link

ModuleNotFoundError while using @testbook('....ipynb', execute=True) #152

Open sanfeu opened 1 year ago

sanfeu commented 1 year ago

Hello

In my notebook, I import a variable in a py file. While the notebook is running while executed manually, it failed to be executed while using @testbook

image

#mini_config.py
schema_name = 'ds_handson_sanfeu'

image

#test_mini_loader.py
from testbook import testbook
@testbook('./src/mini_loader.ipynb', execute=True)
def test_check_country_spit(tb):
    assert True

The test fails with this error log:

./tests/test_mini_loader.py::test_check_module Failed: [undefined]nbclient.exceptions.CellExecutionError: An error occurred while executing the following cell:
------------------
from mini_config import schema_name
------------------

#x1B[1;31m---------------------------------------------------------------------------#x1B[0m
#x1B[1;31mModuleNotFoundError#x1B[0m                       Traceback (most recent call last)
Cell #x1B[1;32mIn[1], line 1#x1B[0m
#x1B[1;32m----> 1#x1B[0m #x1B[38;5;28;01mfrom#x1B[39;00m #x1B[38;5;21;01mmini_config#x1B[39;00m #x1B[38;5;28;01mimport#x1B[39;00m schema_name

#x1B[1;31mModuleNotFoundError#x1B[0m: No module named 'mini_config'
ModuleNotFoundError: No module named 'mini_config'
args = (), kwargs = {}

    @functools.wraps(func)
    def wrapper(*args, **kwargs):  # pragma: no cover
        with self.client.setup_kernel():
>           self._prepare()

.venv\lib\site-packages\testbook\testbook.py:62: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
.venv\lib\site-packages\testbook\testbook.py:46: in _prepare
    self.client.execute()
.venv\lib\site-packages\testbook\client.py:147: in execute
    super().execute_cell(cell, index)
.venv\lib\site-packages\jupyter_core\utils\__init__.py:166: in wrapped
    return loop.run_until_complete(inner)
C:\Python39_64\lib\asyncio\base_events.py:642: in run_until_complete
    return future.result()
.venv\lib\site-packages\nbclient\client.py:1021: in async_execute_cell
    await self._check_raise_for_error(cell, cell_index, exec_reply)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <testbook.client.TestbookNotebookClient object at 0x000001D10D95A3A0>
cell = {'cell_type': 'code', 'execution_count': 1, 'id': '149206e7-45bc-46df-9799-a3cd321ccc7d', 'metadata': {'tags': [], 'ex...1b[1;31mModuleNotFoundError\x1b[0m: No module named 'mini_config'"]}], 'source': 'from mini_config import schema_name'}
cell_index = 0
exec_reply = {'buffers': [], 'content': {'ename': 'ModuleNotFoundError', 'engine_info': {'engine_id': -1, 'engine_uuid': '3eb7cbaf-...e, 'engine': '3eb7cbaf-c9fa-4c23-ab69-14121e5bb42c', 'started': '2023-01-06T17:40:58.354058Z', 'status': 'error'}, ...}

    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

        exec_reply_content = exec_reply['content']
        if exec_reply_content['status'] != 'error':
            return None

        cell_allows_errors = (not self.force_raise_errors) and (
            self.allow_errors
            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, execute_reply=exec_reply
        )
        if not cell_allows_errors:
>           raise CellExecutionError.from_cell_and_msg(cell, exec_reply_content)
E           nbclient.exceptions.CellExecutionError: An error occurred while executing the following cell:
E           ------------------
E           from mini_config import schema_name
E           ------------------
E           
E           #x1B[1;31m---------------------------------------------------------------------------#x1B[0m
E           #x1B[1;31mModuleNotFoundError#x1B[0m                       Traceback (most recent call last)
E           Cell #x1B[1;32mIn[1], line 1#x1B[0m
E           #x1B[1;32m----> 1#x1B[0m #x1B[38;5;28;01mfrom#x1B[39;00m #x1B[38;5;21;01mmini_config#x1B[39;00m #x1B[38;5;28;01mimport#x1B[39;00m schema_name
E           
E           #x1B[1;31mModuleNotFoundError#x1B[0m: No module named 'mini_config'
E           ModuleNotFoundError: No module named 'mini_config'

.venv\lib\site-packages\nbclient\client.py:915: CellExecutionError

I am working with python 3.9.6, in a venv built directly in the repertory. My environment is

anyio==3.6.2
argon2-cffi==21.3.0
argon2-cffi-bindings==21.2.0
arrow==1.2.3
asttokens==2.2.1
attrs==22.2.0
Babel==2.11.0
backcall==0.2.0
beautifulsoup4==4.11.1
bleach==5.0.1
certifi==2022.12.7
cffi==1.15.1
charset-normalizer==2.1.1
colorama==0.4.6
comm==0.1.2
debugpy==1.6.5
decorator==5.1.1
defusedxml==0.7.1
entrypoints==0.4
exceptiongroup==1.1.0
executing==1.2.0
fastjsonschema==2.16.2
fqdn==1.5.1
idna==3.4
importlib-metadata==6.0.0
iniconfig==1.1.1
ipykernel==6.19.4
ipython==8.8.0
ipython-genutils==0.2.0
ipywidgets==8.0.4
isoduration==20.11.0
jedi==0.18.2
Jinja2==3.1.2
json5==0.9.11
jsonpointer==2.3
jsonschema==4.17.3
jupyter==1.0.0
jupyter-console==6.4.4
jupyter-events==0.5.0
jupyter_client==7.4.8
jupyter_core==5.1.2
jupyter_server==2.0.6
jupyter_server_terminals==0.4.3
jupyterlab==3.5.2
jupyterlab-pygments==0.2.2
jupyterlab-widgets==3.0.5
jupyterlab_server==2.18.0
MarkupSafe==2.1.1
matplotlib-inline==0.1.6
mistune==2.0.4
nbclassic==0.4.8
nbclient==0.7.2
nbconvert==7.2.7
nbformat==5.7.1
nest-asyncio==1.5.6
notebook==6.5.2
notebook_shim==0.2.2
packaging==22.0
pandocfilters==1.5.0
parso==0.8.3
pickleshare==0.7.5
platformdirs==2.6.2
pluggy==1.0.0
prometheus-client==0.15.0
prompt-toolkit==3.0.36
psutil==5.9.4
pure-eval==0.2.2
pycparser==2.21
Pygments==2.14.0
pyrsistent==0.19.3
pytest==7.2.0
python-dateutil==2.8.2
python-json-logger==2.0.4
pytz==2022.7
pywin32==305
pywinpty==2.0.10
PyYAML==6.0
pyzmq==24.0.1
qtconsole==5.4.0
QtPy==2.3.0
requests==2.28.1
rfc3339-validator==0.1.4
rfc3986-validator==0.1.1
Send2Trash==1.8.0
six==1.16.0
sniffio==1.3.0
soupsieve==2.3.2.post1
stack-data==0.6.2
terminado==0.17.1
testbook==0.4.2
tinycss2==1.2.1
tomli==2.0.1
tornado==6.2
traitlets==5.8.0
uri-template==1.2.0
urllib3==1.26.13
wcwidth==0.2.5
webcolors==1.12
webencodings==0.5.1
websocket-client==1.4.2
widgetsnbextension==4.0.5
zipp==3.11.0

I tried init / play with sys.path but without success. Sorry if I missed something obvious. Can you help?

PS: Thanks a lot for this module, which will probably change the life of my coworkers soon.

sanfeu commented 1 year ago

I found the way around.

While executing from notebook, sys.path contains root/src . While running from testbook, sys.path contains root/.

I tried to change sys.path in my test.py file, but it has no effect on the notebook execution (I suppose independent kernel is launched).

So what worked for me is to inject a cell at the beginning of the notebook execution thta set my syspath as needed:

tb.inject("""
                import sys
                sys.path.append('./src')
                """
                , before=0, run=False)
WeakLemonDrink commented 1 month ago

I came across this error too but you can set the correct path by configuring the nbclient.NotebookClient which is a parent of TestbookNotebookClient. See also the nbclient docs.

You can update your decorator call to the following:

@testbook('./src/mini_loader.ipynb', execute=True, resources={"metadata": {"path": "src/"}})

This will set the execution path correctly without the need for the injected code. It may be useful to add this to the docs as its not intuitive that testbook should set a different path when compared to executing the notebook directly.