0

I am trying to make a basic linter script which I can run on Python files in the current directory. So far my script looks like this:

import subprocess
from os import listdir
from os.path import isfile, join

if __name__ == "__main__":
    subprocess.check_output(["black", "-l", "100", "./"])

    files = [f for f in listdir("./") if isfile(join("./", f))]
    for file in files:
        if file.endswith(".py"):
            subprocess.check_output(["flake8", file])

I am wanting to run the code via the command line with a call such as main.py. Black performs fine and finds the .py files in the current directory and formats them without a problem. However, when trying to run a similar command with flake8, it also runs on children of the directory such as the venv folder which I am not interested in.

Therefore, the script includes a check to get the files in the current directory and then find the .py files. However, once I get those files, I cannot seem to use my flake8 command with subprocess.check_output. The error I get is as follows:

Traceback (most recent call last):
  File "linter.py", line 18, in <module>
    subprocess.check_output(["flake8", file], shell=False)
  File "C:\Users\calum\AppData\Local\Programs\Python\Python38\lib\subprocess.py", line 411, in check_output
    return run(*popenargs, stdout=PIPE, timeout=timeout, check=True,
  File "C:\Users\calum\AppData\Local\Programs\Python\Python38\lib\subprocess.py", line 512, in run
    raise CalledProcessError(retcode, process.args,
subprocess.CalledProcessError: Command '['flake8', 'flask_server_controller.py']' returned non-zero exit status 1.

Could someone please explain the error and/or provide a solution to my problem. I would also like to add other linting tools to the script such as pylint, however, I am worried I will run into the same problem without understanding it properly.

Thanks in advance.

  • Run `'flake8 flask_server_controller.py'` yourself in console, to see the error. I assume you need to pass the full path to flake and make sure flake is in your env/path variable – Maurice Meyer Dec 25 '19 at 20:17
  • Is there a reason you don't use a tool that is actually suitable for this task, like a shell script? – tripleee Dec 25 '19 at 20:20
  • @MauriceMeyer Thanks for your reply. If I run the command above as suggested flake8 works on the file. There is no error, therefore, I don't think its a flake or env issue. – Calum Templeton Dec 25 '19 at 20:20
  • @tripleee No major reason. I have seen on a few other posts saying that shell script is not a good idea but as my experience is little, please feel free to provide an answer which uses it. I have tried to add the flag `shell=True` into `subprocess.check_output(["flake8", file], shell=True)`, however, this made no difference. – Calum Templeton Dec 25 '19 at 20:24
  • 1
    Check the path to the `flask_server_controller.py` or have a look at `stderr` as mentioned there: https://stackoverflow.com/questions/16198546/get-exit-code-and-stderr-from-subprocess-call. `exit status 1` is definitely a 'response' from flake8. – Maurice Meyer Dec 25 '19 at 20:27
  • `join("./",…)` doesn’t do anything. `shell=True` does mostly bad things. (Using a shell script as the entire program might or might not be an improvement.) – Davis Herring Dec 25 '19 at 20:42
  • You can't pass a list with `shell=True` but I would advise against that here. Either replace the Python script with a shell script, or go all Python. See also https://stackoverflow.com/questions/3172470/actual-meaning-of-shell-true-in-subprocess – tripleee Dec 25 '19 at 21:01
  • @MauriceMeyer I looked at the link you suggested and adapted one of those answers. It works a treat. Thank you everyone for the help! – Calum Templeton Dec 27 '19 at 10:13

1 Answers1

1

subprocess.check_output is giving you that because of the check_ aspect

this means that when the executable you're running returns nonzero (for example, flake8 returns nonzero when it detects lint failures) an exception will be raised

To avoid this behaviour, I'd suggest using subprocess.run instead, and forwarding along the return code. Something like this:

import os
import subprocess
import sys


def main():
    ret = 0
    output = b''
    for filename in os.listdir('.'):
        if filename.endswith('.py'):
            proc_ret = subprocess.run(
                ('flake8', filename),
                stdout=subprocess.PIPE,
            )
            ret |= proc_ret.returncode
            output += proc_ret.stdout
    sys.stdout.buffer.write(output)
    return ret


if __name__ == '__main__':
    exit(main())

Note that this is going to be prohibitively slow, you have to incur the startup cost of flake8 for every file.

One way you can improve this is by passing all the filenames to flake8 at once:

import os
import subprocess
import sys


def main():
    filenames = (fname for fname in os.listdir('.') if fname.endswith('.py'))
    proc_ret = subprocess.run(('flake8', *filenames), stdout=subprocess.PIPE)
    sys.stdout.buffer.write(proc_ret.stdout)
    return proc_ret.returncode


if __name__ == '__main__':
    exit(main())

but also this brings up another interesting point, why are you collecting the output at all? if you let the output go to stdout it will be printed automatically:

import os
import subprocess


def main():
    filenames = (fname for fname in os.listdir('.') if fname.endswith('.py'))
    return subprocess.call(('flake8', *filenames))


if __name__ == '__main__':
    exit(main())

and hmmm, you probably don't need to do this at all since flake8 has its own inclusion / exclusion code -- you probably just want to configure exclude properly

# setup.cfg / tox.ini / .flake8
[flake8]
# for example, exclude recursing into the venv
exclude = venv

and then you can use flake8 . as normal

(disclaimer: I am the current maintainer of flake8)

anthony sottile
  • 61,815
  • 15
  • 148
  • 207
  • Thanks for the reply. I managed to solve the problem looking at a different post but some of the points you have made I will change and implement. Thanks for your help :) – Calum Templeton Dec 27 '19 at 10:16