You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
When flake8 is invoked in stdin mode, the flake8-annotations plugin fails with the error message [Errno 2] No such file or directory: 'stdin'. This is because in stdin mode, the filename argument flake8 passes to the checker class does not refer to an actual file, but contains the display name flake8 uses in the output to indicate where the code is coming from. (By default, this display name is 'stdin', hence why it's mentioned in the error message above; this display name is configurable with the CL argument --stdin-display-name.)
Since flake8-annotations does not use the input passed by flake8 but rather parses the file directly from disk itself, it tries to use filename to open a file and that fails.
To Reproduce
An easy way to reproduce the error is by piping something to flake8 when the plugin is enabled. I've added the -v verbosity option :
$ printf'print("Hello, world!")'| flake8 -v -
(...) snipped to reduce output
flake8.checker MainProcess 285 CRITICAL Plugin TYP raised an unexpected exception
Traceback (most recent call last):
File "/home/sebastiaan/.local/share/virtualenvs/aoc-2019-1zqaskJS/lib/python3.8/site-packages/flake8/checker.py", line 435, in run_check
return plugin["plugin"](**arguments)
File "/home/sebastiaan/.local/share/virtualenvs/aoc-2019-1zqaskJS/lib/python3.8/site-packages/flake8_annotations/checker.py", line 27, in __init__
self.tree, self.lines = self.load_file(Path(filename))
File "/home/sebastiaan/.local/share/virtualenvs/aoc-2019-1zqaskJS/lib/python3.8/site-packages/flake8_annotations/checker.py", line 73, in load_file
with src_filepath.open("r", encoding="utf-8") as f:
File "/usr/local/lib/python3.8/pathlib.py", line 1200, in open
return io.open(self, mode, buffering, encoding, errors, newline,
File "/usr/local/lib/python3.8/pathlib.py", line 1054, in _opener
return self._accessor.open(self, flags, mode)
FileNotFoundError: [Errno 2] No such file or directory: 'stdin'"flake8-annotations" failed during execution due to "[Errno 2] No such file or directory: 'stdin'"
Run flake8 with greater verbosity to see more details
(...) snipped to reduce output
Version Information
[master=] -> $ pipenv run flake8 --version
3.7.9 (flake8-annotations: 1.1.1, flake8-tidy-imports: 3.1.0, import-order: 0.18.1, mccabe: 0.6.1, pycodestyle: 2.5.0, pyflakes: 2.1.1) CPython 3.8.0 on Linux
[master=] -> $ pipenv run python -V
Python 3.8.0
I have also tested this with Python 3.7.4 and the same bug occurs.
Possible solution
As far as I'm aware, there's no guaranteed way to find out whether or not flake8 was called to lint input from stdin or from a file on disk using the arguments that are (potentially) passed to the checker class. The reason is that flake8/pycodestyle takes care of processing the input (file or stdin) and then passes that parsed input to the plugin checker class. This means that there is normally no need for a plugin to parse the file contents again.
Unfortunately, since the ast tree provided by flake8 is untyped, we do need to parse the file again and the least expensive way of doing that is by reading the file again. (We could also join together the lines list for that, but that's more expensive.) However, there's another option: If you add a parse_options method to the plugin's checker class, flake8 will pass the command line arguments to it. This means we can check for the - indicating stdin mode:
# Quick mock-up; I've set a default value of `False` for `read_from_stdin`@classmethoddefparse_options(cls, option_manager: OptionManager, options: Values, args: List[str]) ->None:
"""Parse the command line options provided to `flake8`."""if"-"inargs:
cls.read_from_stdin=True
Obviously, we still need to actually get the value from stdin that we're supposed to lint. Since stdin has already been exhausted by pycodestyle, we can do that by adding the lines parameter to the checker's __init__:
def__init__(self, tree: ast.Module, filename: str, lines: List[str]):
# Unfortunately no way that I can find around requesting the ast-parsed tree from flake8# Removing tree unregisters the plugin, and per the documentation the alternative is# requesting per-line informationself.lines=lines# <- this also means we don't have to manually split it laterself.tree=self.load_file(filename)
and use self.lines in read_file if we're in stdin mode:
defload_file(self, path: str) ->ast.Module:
"""Parse the provided Python file and return an (typed AST, source) tuple."""ifself.read_from_stdin:
# Reconstruct source as a single string from `self.lines`src="".join(self.lines)
else:
# Read source as single string from file on disksrc_filepath=Path(path)
withsrc_filepath.open("r", encoding="utf-8") asf:
src=f.read()
ifPY_GTE_38:
# Built-in ast requires a flag to parse type commentstree=ast.parse(src, type_comments=True)
else:
# typed-ast will implicitly parse type commentstree=ast.parse(src)
returntree
I've confirmed that this works (and, with some small adjustments for the changed method signatures, the tests also pass), but there may be a better way.
The text was updated successfully, but these errors were encountered:
Previously, when the checker instance is created it requested the filename from flake8 in order to load the source and parse to obtain a typed AST, as the flake8 provided AST is not guaranteed to contain type information. While sufficient for parsing files, this falls apart when attempting to parse code piped to stdin & causes our plugin to error out.
To mitigate these issues, the source is requested from flake8 as lines (list of strings), which is joined & passed into the typed AST generation rather than farming to a file IO task. This also allows us to drop the request for the AST from flake8 that we don't use, as requesting the lines will ensure that the plugin is registered.
The related test helpers have been updated to accommodate the change in instantiation.
Closes#52
After experimenting between the various proposed alternatives, it ends up being a pretty significant simplification to just request the list of lines from flake8 and join them when we need the source to obtain a typed AST for the rest of the checker. This obviates any need for this plugin to separately consider stdin vs. file inputs since flake8 is already doing this for us.
This fix will be included in v1.1.2, to be merged by #53
When flake8 is invoked in
stdin
mode, the flake8-annotations plugin fails with the error message[Errno 2] No such file or directory: 'stdin'
. This is because instdin
mode, thefilename
argument flake8 passes to the checker class does not refer to an actual file, but contains the display name flake8 uses in the output to indicate where the code is coming from. (By default, this display name is'stdin'
, hence why it's mentioned in the error message above; this display name is configurable with the CL argument--stdin-display-name
.)Since
flake8-annotations
does not use the input passed byflake8
but rather parses the file directly from disk itself, it tries to usefilename
to open a file and that fails.To Reproduce
An easy way to reproduce the error is by piping something to
flake8
when the plugin is enabled. I've added the-v
verbosity option :Version Information
I have also tested this with Python 3.7.4 and the same bug occurs.
Possible solution
As far as I'm aware, there's no guaranteed way to find out whether or not
flake8
was called to lint input fromstdin
or from a file on disk using the arguments that are (potentially) passed to the checker class. The reason is that flake8/pycodestyle takes care of processing the input (file or stdin) and then passes that parsed input to the plugin checker class. This means that there is normally no need for a plugin to parse the file contents again.Unfortunately, since the ast tree provided by flake8 is untyped, we do need to parse the file again and the least expensive way of doing that is by reading the file again. (We could also join together the
lines
list for that, but that's more expensive.) However, there's another option: If you add aparse_options
method to the plugin's checker class, flake8 will pass the command line arguments to it. This means we can check for the-
indicatingstdin
mode:Obviously, we still need to actually get the value from
stdin
that we're supposed to lint. Sincestdin
has already been exhausted bypycodestyle
, we can do that by adding thelines
parameter to the checker's__init__
:and use
self.lines
inread_file
if we're instdin
mode:I've confirmed that this works (and, with some small adjustments for the changed method signatures, the tests also pass), but there may be a better way.
The text was updated successfully, but these errors were encountered: