Commit 1e3c3e90 authored by Brett Cannon's avatar Brett Cannon

Issue #25768: Make compileall functions return booleans and document

the return values as well as test them.

Thanks to Nicholas Chammas for the bug report and initial patch.
parent 4a4ca7c1
...@@ -103,7 +103,8 @@ Public functions ...@@ -103,7 +103,8 @@ Public functions
.. function:: compile_dir(dir, maxlevels=10, ddir=None, force=False, rx=None, quiet=0, legacy=False, optimize=-1, workers=1) .. function:: compile_dir(dir, maxlevels=10, ddir=None, force=False, rx=None, quiet=0, legacy=False, optimize=-1, workers=1)
Recursively descend the directory tree named by *dir*, compiling all :file:`.py` Recursively descend the directory tree named by *dir*, compiling all :file:`.py`
files along the way. files along the way. Return a true value if all the files compiled successfully,
and a false value otherwise.
The *maxlevels* parameter is used to limit the depth of the recursion; it The *maxlevels* parameter is used to limit the depth of the recursion; it
defaults to ``10``. defaults to ``10``.
...@@ -155,7 +156,8 @@ Public functions ...@@ -155,7 +156,8 @@ Public functions
.. function:: compile_file(fullname, ddir=None, force=False, rx=None, quiet=0, legacy=False, optimize=-1) .. function:: compile_file(fullname, ddir=None, force=False, rx=None, quiet=0, legacy=False, optimize=-1)
Compile the file with path *fullname*. Compile the file with path *fullname*. Return a true value if the file
compiled successfully, and a false value otherwise.
If *ddir* is given, it is prepended to the path to the file being compiled If *ddir* is given, it is prepended to the path to the file being compiled
for use in compilation time tracebacks, and is also compiled in to the for use in compilation time tracebacks, and is also compiled in to the
...@@ -191,8 +193,10 @@ Public functions ...@@ -191,8 +193,10 @@ Public functions
.. function:: compile_path(skip_curdir=True, maxlevels=0, force=False, quiet=0, legacy=False, optimize=-1) .. function:: compile_path(skip_curdir=True, maxlevels=0, force=False, quiet=0, legacy=False, optimize=-1)
Byte-compile all the :file:`.py` files found along ``sys.path``. If Byte-compile all the :file:`.py` files found along ``sys.path``. Return a
*skip_curdir* is true (the default), the current directory is not included true value if all the files compiled successfully, and a false value otherwise.
If *skip_curdir* is true (the default), the current directory is not included
in the search. All other parameters are passed to the :func:`compile_dir` in the search. All other parameters are passed to the :func:`compile_dir`
function. Note that unlike the other compile functions, ``maxlevels`` function. Note that unlike the other compile functions, ``maxlevels``
defaults to ``0``. defaults to ``0``.
......
...@@ -230,6 +230,11 @@ that may require changes to your code. ...@@ -230,6 +230,11 @@ that may require changes to your code.
Changes in the Python API Changes in the Python API
------------------------- -------------------------
* The functions in the :mod:`compileall` module now return booleans instead
of ``1`` or ``0`` to represent success or failure, respectively. Thanks to
booleans being a subclass of integers, this should only be an issue if you
were doing identity checks for ``1`` or ``0``. See :issue:`25768`.
* Reading the :attr:`~urllib.parse.SplitResult.port` attribute of * Reading the :attr:`~urllib.parse.SplitResult.port` attribute of
:func:`urllib.parse.urlsplit` and :func:`~urllib.parse.urlparse` results :func:`urllib.parse.urlsplit` and :func:`~urllib.parse.urlparse` results
now raises :exc:`ValueError` for out-of-range values, rather than now raises :exc:`ValueError` for out-of-range values, rather than
......
...@@ -68,7 +68,7 @@ def compile_dir(dir, maxlevels=10, ddir=None, force=False, rx=None, ...@@ -68,7 +68,7 @@ def compile_dir(dir, maxlevels=10, ddir=None, force=False, rx=None,
""" """
files = _walk_dir(dir, quiet=quiet, maxlevels=maxlevels, files = _walk_dir(dir, quiet=quiet, maxlevels=maxlevels,
ddir=ddir) ddir=ddir)
success = 1 success = True
if workers is not None and workers != 1 and ProcessPoolExecutor is not None: if workers is not None and workers != 1 and ProcessPoolExecutor is not None:
if workers < 0: if workers < 0:
raise ValueError('workers must be greater or equal to 0') raise ValueError('workers must be greater or equal to 0')
...@@ -81,12 +81,12 @@ def compile_dir(dir, maxlevels=10, ddir=None, force=False, rx=None, ...@@ -81,12 +81,12 @@ def compile_dir(dir, maxlevels=10, ddir=None, force=False, rx=None,
legacy=legacy, legacy=legacy,
optimize=optimize), optimize=optimize),
files) files)
success = min(results, default=1) success = min(results, default=True)
else: else:
for file in files: for file in files:
if not compile_file(file, ddir, force, rx, quiet, if not compile_file(file, ddir, force, rx, quiet,
legacy, optimize): legacy, optimize):
success = 0 success = False
return success return success
def compile_file(fullname, ddir=None, force=False, rx=None, quiet=0, def compile_file(fullname, ddir=None, force=False, rx=None, quiet=0,
...@@ -104,7 +104,7 @@ def compile_file(fullname, ddir=None, force=False, rx=None, quiet=0, ...@@ -104,7 +104,7 @@ def compile_file(fullname, ddir=None, force=False, rx=None, quiet=0,
legacy: if True, produce legacy pyc paths instead of PEP 3147 paths legacy: if True, produce legacy pyc paths instead of PEP 3147 paths
optimize: optimization level or -1 for level of the interpreter optimize: optimization level or -1 for level of the interpreter
""" """
success = 1 success = True
name = os.path.basename(fullname) name = os.path.basename(fullname)
if ddir is not None: if ddir is not None:
dfile = os.path.join(ddir, name) dfile = os.path.join(ddir, name)
...@@ -144,7 +144,7 @@ def compile_file(fullname, ddir=None, force=False, rx=None, quiet=0, ...@@ -144,7 +144,7 @@ def compile_file(fullname, ddir=None, force=False, rx=None, quiet=0,
ok = py_compile.compile(fullname, cfile, dfile, True, ok = py_compile.compile(fullname, cfile, dfile, True,
optimize=optimize) optimize=optimize)
except py_compile.PyCompileError as err: except py_compile.PyCompileError as err:
success = 0 success = False
if quiet >= 2: if quiet >= 2:
return success return success
elif quiet: elif quiet:
...@@ -157,7 +157,7 @@ def compile_file(fullname, ddir=None, force=False, rx=None, quiet=0, ...@@ -157,7 +157,7 @@ def compile_file(fullname, ddir=None, force=False, rx=None, quiet=0,
msg = msg.decode(sys.stdout.encoding) msg = msg.decode(sys.stdout.encoding)
print(msg) print(msg)
except (SyntaxError, UnicodeError, OSError) as e: except (SyntaxError, UnicodeError, OSError) as e:
success = 0 success = False
if quiet >= 2: if quiet >= 2:
return success return success
elif quiet: elif quiet:
...@@ -167,7 +167,7 @@ def compile_file(fullname, ddir=None, force=False, rx=None, quiet=0, ...@@ -167,7 +167,7 @@ def compile_file(fullname, ddir=None, force=False, rx=None, quiet=0,
print(e.__class__.__name__ + ':', e) print(e.__class__.__name__ + ':', e)
else: else:
if ok == 0: if ok == 0:
success = 0 success = False
return success return success
def compile_path(skip_curdir=1, maxlevels=0, force=False, quiet=0, def compile_path(skip_curdir=1, maxlevels=0, force=False, quiet=0,
...@@ -183,7 +183,7 @@ def compile_path(skip_curdir=1, maxlevels=0, force=False, quiet=0, ...@@ -183,7 +183,7 @@ def compile_path(skip_curdir=1, maxlevels=0, force=False, quiet=0,
legacy: as for compile_dir() (default False) legacy: as for compile_dir() (default False)
optimize: as for compile_dir() (default -1) optimize: as for compile_dir() (default -1)
""" """
success = 1 success = True
for dir in sys.path: for dir in sys.path:
if (not dir or dir == os.curdir) and skip_curdir: if (not dir or dir == os.curdir) and skip_curdir:
if quiet < 2: if quiet < 2:
......
import sys import sys
import compileall import compileall
import importlib.util import importlib.util
import test.test_importlib.util
import os import os
import pathlib import pathlib
import py_compile import py_compile
...@@ -40,6 +41,11 @@ class CompileallTests(unittest.TestCase): ...@@ -40,6 +41,11 @@ class CompileallTests(unittest.TestCase):
def tearDown(self): def tearDown(self):
shutil.rmtree(self.directory) shutil.rmtree(self.directory)
def add_bad_source_file(self):
self.bad_source_path = os.path.join(self.directory, '_test_bad.py')
with open(self.bad_source_path, 'w') as file:
file.write('x (\n')
def data(self): def data(self):
with open(self.bc_path, 'rb') as file: with open(self.bc_path, 'rb') as file:
data = file.read(8) data = file.read(8)
...@@ -78,15 +84,31 @@ class CompileallTests(unittest.TestCase): ...@@ -78,15 +84,31 @@ class CompileallTests(unittest.TestCase):
os.unlink(fn) os.unlink(fn)
except: except:
pass pass
compileall.compile_file(self.source_path, force=False, quiet=True) self.assertTrue(compileall.compile_file(self.source_path,
force=False, quiet=True))
self.assertTrue(os.path.isfile(self.bc_path) and self.assertTrue(os.path.isfile(self.bc_path) and
not os.path.isfile(self.bc_path2)) not os.path.isfile(self.bc_path2))
os.unlink(self.bc_path) os.unlink(self.bc_path)
compileall.compile_dir(self.directory, force=False, quiet=True) self.assertTrue(compileall.compile_dir(self.directory, force=False,
quiet=True))
self.assertTrue(os.path.isfile(self.bc_path) and self.assertTrue(os.path.isfile(self.bc_path) and
os.path.isfile(self.bc_path2)) os.path.isfile(self.bc_path2))
os.unlink(self.bc_path) os.unlink(self.bc_path)
os.unlink(self.bc_path2) os.unlink(self.bc_path2)
# Test against bad files
self.add_bad_source_file()
self.assertFalse(compileall.compile_file(self.bad_source_path,
force=False, quiet=2))
self.assertFalse(compileall.compile_dir(self.directory,
force=False, quiet=2))
def test_compile_path(self):
self.assertTrue(compileall.compile_path(quiet=2))
with test.test_importlib.util.import_state(path=[self.directory]):
self.add_bad_source_file()
self.assertFalse(compileall.compile_path(skip_curdir=False,
force=True, quiet=2))
def test_no_pycache_in_non_package(self): def test_no_pycache_in_non_package(self):
# Bug 8563 reported that __pycache__ directories got created by # Bug 8563 reported that __pycache__ directories got created by
......
...@@ -235,6 +235,7 @@ Octavian Cerna ...@@ -235,6 +235,7 @@ Octavian Cerna
Michael Cetrulo Michael Cetrulo
Dave Chambers Dave Chambers
Pascal Chambon Pascal Chambon
Nicholas Chammas
John Chandler John Chandler
Hye-Shik Chang Hye-Shik Chang
Jeffrey Chang Jeffrey Chang
......
...@@ -123,6 +123,9 @@ Core and Builtins ...@@ -123,6 +123,9 @@ Core and Builtins
Library Library
------- -------
- Issue #25768: Have the functions in compileall return booleans instead of
ints and add proper documentation and tests for the return values.
- Issue #24103: Fixed possible use after free in ElementTree.XMLPullParser. - Issue #24103: Fixed possible use after free in ElementTree.XMLPullParser.
- Issue #25860: os.fwalk() no longer skips remaining directories when error - Issue #25860: os.fwalk() no longer skips remaining directories when error
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment