nbQA-dev / nbQA

Run ruff, isort, pyupgrade, mypy, pylint, flake8, and more on Jupyter Notebooks
https://nbqa.readthedocs.io/en/latest/index.html
MIT License
1.04k stars 41 forks source link

Flake8 async parsing issue #702

Closed chrisk314 closed 2 years ago

chrisk314 commented 2 years ago

When attempting to run nbqa flake8 on a Jupyter Notebook which contains await statements on python coroutines, a parsing error results. I have attached the json content for a simple notebook with two code cells: the first defines an awaitable function, the second calls the awaitable. The notebook can be executed without issue.

{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Example notebook"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import asyncio\n",
    "\n",
    "async def example(x: float) -> float:\n",
    "    await asyncio.sleep(1)\n",
    "    return x"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "x: float = 1.0\n",
    "r: float = await example(x)\n",
    "assert r == x"
   ]
  }
 ],
 "metadata": {
  "language_info": {
   "name": "python"
  },
  "orig_nbformat": 4
 },
 "nbformat": 4,
 "nbformat_minor": 2
}

When running this notebook with

nbqa flake8 example.ipynb

The following error message results

Traceback (most recent call last):
  File "/media/csk/d/src/org/anglo-american/voxel.analytics.twinning.pmf/.venv-dev/lib/python3.7/site-packages/pydocstyle/parser.py", line 415, in parse
    compile(src, filename, 'exec')
  File "example.ipynb", line 11
    r: float = await example(x)
              ^
SyntaxError: 'await' outside function

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/usr/lib/python3.7/runpy.py", line 193, in _run_module_as_main
    "__main__", mod_spec)
  File "/usr/lib/python3.7/runpy.py", line 85, in _run_code
    exec(code, run_globals)
  File "/media/csk/d/src/org/anglo-american/voxel.analytics.twinning.pmf/.venv-dev/lib/python3.7/site-packages/flake8/__main__.py", line 4, in <module>
    cli.main()
  File "/media/csk/d/src/org/anglo-american/voxel.analytics.twinning.pmf/.venv-dev/lib/python3.7/site-packages/flake8/main/cli.py", line 22, in main
    app.run(argv)
  File "/media/csk/d/src/org/anglo-american/voxel.analytics.twinning.pmf/.venv-dev/lib/python3.7/site-packages/flake8/main/application.py", line 375, in run
    self._run(argv)
  File "/media/csk/d/src/org/anglo-american/voxel.analytics.twinning.pmf/.venv-dev/lib/python3.7/site-packages/flake8/main/application.py", line 364, in _run
    self.run_checks()
  File "/media/csk/d/src/org/anglo-american/voxel.analytics.twinning.pmf/.venv-dev/lib/python3.7/site-packages/flake8/main/application.py", line 271, in run_checks
    self.file_checker_manager.run()
  File "/media/csk/d/src/org/anglo-american/voxel.analytics.twinning.pmf/.venv-dev/lib/python3.7/site-packages/flake8/checker.py", line 311, in run
    self.run_serial()
  File "/media/csk/d/src/org/anglo-american/voxel.analytics.twinning.pmf/.venv-dev/lib/python3.7/site-packages/flake8/checker.py", line 295, in run_serial
    checker.run_checks()
  File "/media/csk/d/src/org/anglo-american/voxel.analytics.twinning.pmf/.venv-dev/lib/python3.7/site-packages/flake8/checker.py", line 597, in run_checks
    self.run_ast_checks()
  File "/media/csk/d/src/org/anglo-american/voxel.analytics.twinning.pmf/.venv-dev/lib/python3.7/site-packages/flake8/checker.py", line 500, in run_ast_checks
    for (line_number, offset, text, _) in runner:
  File "/media/csk/d/src/org/anglo-american/voxel.analytics.twinning.pmf/.venv-dev/lib/python3.7/site-packages/flake8_docstrings.py", line 142, in run
    for error in self._check_source():
  File "/media/csk/d/src/org/anglo-american/voxel.analytics.twinning.pmf/.venv-dev/lib/python3.7/site-packages/flake8_docstrings.py", line 127, in _check_source
    for err in self._call_check_source():
  File "/media/csk/d/src/org/anglo-american/voxel.analytics.twinning.pmf/.venv-dev/lib/python3.7/site-packages/pydocstyle/checker.py", line 136, in check_source
    module = parse(StringIO(source), filename)
  File "/media/csk/d/src/org/anglo-american/voxel.analytics.twinning.pmf/.venv-dev/lib/python3.7/site-packages/pydocstyle/parser.py", line 429, in __call__
    return self.parse(*args, **kwargs)
  File "/media/csk/d/src/org/anglo-american/voxel.analytics.twinning.pmf/.venv-dev/lib/python3.7/site-packages/pydocstyle/parser.py", line 417, in parse
    raise ParseError() from error
pydocstyle.parser.ParseError: Cannot parse file.
MarcoGorelli commented 2 years ago

Thanks for the report, but this doesn't look like valid Python:

(venv) marcogorelli@OVMG025 nbqa-dev % cat t.py 
import asyncio

async def example(x: float) -> float:
    await asyncio.sleep(1)
    return x

x: float = 1.0
r: float = await example(x)
assert r == x
(venv) marcogorelli@OVMG025 nbqa-dev % python t.py 
  File "t.py", line 8
    r: float = await example(x)
               ^
SyntaxError: 'await' outside function
MarcoGorelli commented 2 years ago

besides, can't reproduce:

(.venv37) marcogorelli@OVMG025 nbqa-dev % python --version
Python 3.7.10
(.venv37) marcogorelli@OVMG025 nbqa-dev % cat t.ipynb 
{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Example notebook"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import asyncio\n",
    "\n",
    "async def example(x: float) -> float:\n",
    "    await asyncio.sleep(1)\n",
    "    return x"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "x: float = 1.0\n",
    "r: float = await example(x)\n",
    "assert r == x"
   ]
  }
 ],
 "metadata": {
  "language_info": {
   "name": "python"
  },
  "orig_nbformat": 4
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
(.venv37) marcogorelli@OVMG025 nbqa-dev % nbqa flake8 t.ipynb
t.ipynb:cell_1:3:1: E302 expected 2 blank lines, found 1
t.ipynb:cell_2:2:12: F704 'yield' outside function

What version of nbQA are you on?

chrisk314 commented 2 years ago

@MarcoGorelli I don't agree that this should be considered closed. You are quite correct that this is not valid syntax in a regular python script; however, this has been supported in Jupyter and IPython since 2018. See this blog post.

Clearly this marks a difference between what is accepted/valid syntax in a regular python script vs in a Jupyter environment. One of the features of your package is to extend flake8 support to notebooks. In the case of this syntax, it is not currently working for me.

My version info: nbqa 1.2.3 flake8 4.0.1 python 3.7.12

If you look at the stacktrace it appears that the issue comes from flake8_docstrings which uses pydocstyle. Is there a way to configure flake8 when running through nbqa to disable problematic extensions? Or do you have another suggestion?

Note: believe me, I am not on python3.7 for the affected project of my own choice. We're currently negotiating migrating to 3.9.

MarcoGorelli commented 2 years ago

nbQA just runs your tool (in this case, flake8)

https://github.com/nbQA-dev/nbQA/blob/f2c53d45507058189cda0dab8958d50bd5401aa8/nbqa/__main__.py#L264

If flake8 doesn't support certain syntax, then neither will nbqa flake8, unless there's a way to preprocess the cell beforehand. nbQA currently does that for magics.

Do you have an idea for how it could preprocess the cell to support this syntax? If not, it'll just have to be a known limitation

If you look at the stacktrace it appears that the issue comes from flake8_docstrings which uses pydocstyle. Is there a way to configure flake8 when running through nbqa to disable problematic extensions?

You could run it via pre-commit, then it'll get its own virtual environment in which you can put whichever extensions you want

chrisk314 commented 2 years ago

OK, understood. I have confirmed that flake8_docstrings causes the problem. Removing this from the venv with the affected installation of flake8 allows me to run flake8 on the notebook and see the linting issues as expected.

This step is to be run from tox. I'd like to avoid needing different installations of flake8 with different confiurations or plugins just to work around this problem. I will look into approaches and report back in case it helps others with similar requirements.