Skip to content

Instantly share code, notes, and snippets.

@Gribouillis
Last active October 20, 2024 16:19
Show Gist options
  • Save Gribouillis/3fc674d8629921650cc72c4c180a0572 to your computer and use it in GitHub Desktop.
Save Gribouillis/3fc674d8629921650cc72c4c180a0572 to your computer and use it in GitHub Desktop.
CLI program to validate the syntax of Python source files by compiling them
#!/usr/bin/env python
# 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"]
__version__ = "2024.10.20"
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("<check_syntax>", 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.
Exit status is 0 if Python syntax is correct.
"""
parser = argparse.ArgumentParser(description=description)
parser.add_argument(
"-q", "--quiet", action="store_true", help="do not print error messages"
)
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": 2 if args.quiet else 0}
for filename in filenames:
filename = filename.rstrip("\n")
result = has_valid_syntax(filename, **kwargs) and result
else:
parser.exit(0 if result else 1)
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment