Python Forum

Full Version: Script to check the syntax of Python files
You're currently viewing a stripped down version of our content. View the full version with proper formatting.
The following code is a CLI script to check the syntax of python code. Here is the usage string
Output:
usage: check_syntax.py [-h] FILENAME [FILENAME ...] Check Python source file syntax by attempting compilation. positional arguments: FILENAME Files to compile options: -h, --help show this help message and exit
Further updates of this code will happen in this gist.
# check_syntax.py - script to check the syntax of Python files
# inspired by the standard library's 'py_compile' module.

import importlib.machinery
import sys
import traceback

__all__ = ["has_valid_syntax", "main", "CheckSyntaxError"]


class CheckSyntaxError(Exception):
    """Exception raised when an error occurs while attempting to
    compile the file.

    To raise this exception, use

        raise PyCompileError(exc_type,exc_value,file[,msg])

    where

        exc_type:   exception type to be used in error message
                    type name can be accesses as class variable
                    'exc_type_name'

        exc_value:  exception value to be used in error message
                    can be accesses as class variable 'exc_value'

        file:       name of file being compiled to be used in error message
                    can be accesses as class variable 'file'

        msg:        string message to be written as error message
                    If no value is given, a default exception message will be
                    given, consistent with 'standard' py_compile output.
                    message (or default) can be accesses as class variable
                    'msg'

    """

    def __init__(self, exc_type, exc_value, file, msg=""):
        exc_type_name = exc_type.__name__
        if exc_type is SyntaxError:
            tbtext = "".join(traceback.format_exception_only(exc_type, exc_value))
            errmsg = tbtext.replace('File "<string>"', 'File "%s"' % file)
        else:
            errmsg = "Sorry: %s: %s" % (exc_type_name, exc_value)

        Exception.__init__(self, msg or errmsg, exc_type_name, exc_value, file)

        self.exc_type_name = exc_type_name
        self.exc_value = exc_value
        self.file = file
        self.msg = msg or errmsg

    def __str__(self):
        return self.msg


def has_valid_syntax(file, dfile=None, doraise=False, optimize=-1, quiet=0):
    """Check the syntax of a Python source file.

    :param file: The source file name.
    :param dfile: Purported file name, i.e. the file name that shows up in
        error messages.  Defaults to the source file name.
    :param doraise: Flag indicating whether or not an exception should be
        raised when a compile error is found.  If an exception occurs and this
        flag is set to False, a string indicating the nature of the exception
        will be printed, and the function will return to the caller. If an
        exception occurs and this flag is set to True, a CheckSyntaxError
        exception will be raised.
    :param optimize: The optimization level for the compiler.  Valid values
        are -1, 0, 1 and 2.  A value of -1 means to use the optimization
        level of the current interpreter, as given by -O command line options.
    :param quiet: Return full output with False or 0, errors only with 1,
        and no output with 2.

    :return: None

    """
    loader = importlib.machinery.SourceFileLoader("<py_compile>", file)
    source_bytes = loader.get_data(file)
    try:
        compile(source_bytes, dfile or file, "exec", optimize=optimize)
    except Exception as err:
        py_exc = CheckSyntaxError(err.__class__, err, dfile or file)
        if quiet < 2:
            if doraise:
                raise py_exc
            else:
                sys.stderr.write(py_exc.msg + "\n")
        return False
    else:
        return True


def main():
    import argparse

    description = "Check Python source file syntax by attempting compilation."
    parser = argparse.ArgumentParser(description=description)
    parser.add_argument(
        "filenames",
        nargs="+",
        help="Files to compile",
        metavar="FILENAME",
    )
    args = parser.parse_args()
    if args.filenames == ["-"]:
        filenames = [filename.rstrip("\n") for filename in sys.stdin.readlines()]
    else:
        filenames = args.filenames
    result = True
    kwargs = {"doraise": False, "quiet": 0}
    for filename in filenames:
        filename = filename.rstrip("\n")
        result = has_valid_syntax(filename, **kwargs) and result
    else:
        return parser.exit(0 if result else 1)


if __name__ == "__main__":
    main()
The script doesn't show anything even though I added extra errors.

Python 3.11.2 on LMDE6 (Debian 12)

my script to test

#!/usr/bin/env python3

pint("hello world")
the output in my IDE

Output:
Traceback (most recent call last): File "/tmp/./tmp.py", line 3, in <module> pint("hello world") ^^^^ NameError: name 'pint' is not defined. Did you mean: 'print'?
(Oct-19-2024, 01:16 PM)Axel_Erfurt Wrote: [ -> ]The script doesn't show anything even though I added extra errors.
pint('hello world') is not a syntax error! The script only detects syntax errors.
(Oct-19-2024, 01:16 PM)Axel_Erfurt Wrote: [ -> ]pint("hello world")
how could the Python compiler know that was bad without running it? it is valid code ... for a context that has a pint() function. the compiler can only catch bad code, which i understand to be syntax errors. maybe some funny Unicode values can cause a compile failure. but you'd want to catch those, too.