Skip to content

Commit daff390

Browse files
authored
bpo-38731: Add --quiet option to py_compile CLI (GH-17134)
1 parent af08db7 commit daff390

6 files changed

Lines changed: 137 additions & 53 deletions

File tree

Doc/library/py_compile.rst

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -125,21 +125,33 @@ byte-code cache files in the directory containing the source code.
125125
system external to Python like a build system.
126126

127127

128-
.. function:: main(args=None)
128+
Command-Line Interface
129+
----------------------
129130

130-
Compile several source files. The files named in *args* (or on the command
131-
line, if *args* is ``None``) are compiled and the resulting byte-code is
132-
cached in the normal manner. This function does not search a directory
133-
structure to locate source files; it only compiles files named explicitly.
134-
If ``'-'`` is the only parameter in args, the list of files is taken from
135-
standard input.
131+
This module can be invoked as a script to compile several source
132+
files. The files named in *filenames* are compiled and the resulting
133+
bytecode is cached in the normal manner. This program does not search
134+
a directory structure to locate source files; it only compiles files
135+
named explicitly. The exit status is nonzero if one of the files could
136+
not be compiled.
136137

137-
.. versionchanged:: 3.2
138-
Added support for ``'-'``.
138+
.. program:: python -m py_compile
139+
140+
.. cmdoption:: <file> ... <fileN>
141+
-
142+
143+
Positional arguments are files to compile. If ``-`` is the only
144+
parameter, the list of files is taken from standard input.
145+
146+
.. cmdoption:: -q, --quiet
147+
148+
Suppress errors output.
149+
150+
.. versionchanged:: 3.2
151+
Added support for ``-``.
139152

140-
When this module is run as a script, the :func:`main` is used to compile all the
141-
files named on the command line. The exit status is nonzero if one of the files
142-
could not be compiled.
153+
.. versionchanged:: 3.10
154+
Added support for :option:`-q`.
143155

144156

145157
.. seealso::

Doc/whatsnew/3.10.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,12 @@ Added the *root_dir* and *dir_fd* parameters in :func:`~glob.glob` and
110110
:func:`~glob.iglob` which allow to specify the root directory for searching.
111111
(Contributed by Serhiy Storchaka in :issue:`38144`.)
112112

113+
py_compile
114+
----------
115+
116+
Added ``--quiet`` option to command-line interface of :mod:`py_compile`.
117+
(Contributed by Gregory Schevchenko in :issue:`38731`.)
118+
113119
sys
114120
---
115121

Lib/py_compile.py

Lines changed: 34 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -173,46 +173,40 @@ def compile(file, cfile=None, dfile=None, doraise=False, optimize=-1,
173173
return cfile
174174

175175

176-
def main(args=None):
177-
"""Compile several source files.
178-
179-
The files named in 'args' (or on the command line, if 'args' is
180-
not specified) are compiled and the resulting bytecode is cached
181-
in the normal manner. This function does not search a directory
182-
structure to locate source files; it only compiles files named
183-
explicitly. If '-' is the only parameter in args, the list of
184-
files is taken from standard input.
185-
186-
"""
187-
if args is None:
188-
args = sys.argv[1:]
189-
rv = 0
190-
if args == ['-']:
191-
while True:
192-
filename = sys.stdin.readline()
193-
if not filename:
194-
break
195-
filename = filename.rstrip('\n')
196-
try:
197-
compile(filename, doraise=True)
198-
except PyCompileError as error:
199-
rv = 1
200-
if quiet < 2:
201-
sys.stderr.write("%s\n" % error.msg)
202-
except OSError as error:
203-
rv = 1
204-
if quiet < 2:
205-
sys.stderr.write("%s\n" % error)
176+
def main():
177+
import argparse
178+
179+
description = 'A simple command-line interface for py_compile module.'
180+
parser = argparse.ArgumentParser(description=description)
181+
parser.add_argument(
182+
'-q', '--quiet',
183+
action='store_true',
184+
help='Suppress error output',
185+
)
186+
parser.add_argument(
187+
'filenames',
188+
nargs='+',
189+
help='Files to compile',
190+
)
191+
args = parser.parse_args()
192+
if args.filenames == ['-']:
193+
filenames = sys.stdin.readlines()
206194
else:
207-
for filename in args:
208-
try:
209-
compile(filename, doraise=True)
210-
except PyCompileError as error:
211-
# return value to indicate at least one failure
212-
rv = 1
213-
if quiet < 2:
214-
sys.stderr.write("%s\n" % error.msg)
215-
return rv
195+
filenames = args.filenames
196+
for filename in filenames:
197+
try:
198+
compile(filename, doraise=True)
199+
except PyCompileError as error:
200+
if args.quiet:
201+
parser.exit(1)
202+
else:
203+
parser.exit(1, error.msg)
204+
except OSError as error:
205+
if args.quiet:
206+
parser.exit(1)
207+
else:
208+
parser.exit(1, str(error))
209+
216210

217211
if __name__ == "__main__":
218-
sys.exit(main())
212+
main()

Lib/test/test_py_compile.py

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,13 @@
44
import py_compile
55
import shutil
66
import stat
7+
import subprocess
78
import sys
89
import tempfile
910
import unittest
1011

1112
from test import support
12-
from test.support import os_helper
13+
from test.support import os_helper, script_helper
1314

1415

1516
def without_source_date_epoch(fxn):
@@ -217,5 +218,73 @@ class PyCompileTestsWithoutSourceEpoch(PyCompileTestsBase,
217218
pass
218219

219220

221+
class PyCompileCLITestCase(unittest.TestCase):
222+
223+
def setUp(self):
224+
self.directory = tempfile.mkdtemp()
225+
self.source_path = os.path.join(self.directory, '_test.py')
226+
self.cache_path = importlib.util.cache_from_source(self.source_path)
227+
with open(self.source_path, 'w') as file:
228+
file.write('x = 123\n')
229+
230+
def tearDown(self):
231+
support.rmtree(self.directory)
232+
233+
def pycompilecmd(self, *args, **kwargs):
234+
# assert_python_* helpers don't return proc object. We'll just use
235+
# subprocess.run() instead of spawn_python() and its friends to test
236+
# stdin support of the CLI.
237+
if args and args[0] == '-' and 'input' in kwargs:
238+
return subprocess.run([sys.executable, '-m', 'py_compile', '-'],
239+
input=kwargs['input'].encode(),
240+
capture_output=True)
241+
return script_helper.assert_python_ok('-m', 'py_compile', *args, **kwargs)
242+
243+
def pycompilecmd_failure(self, *args):
244+
return script_helper.assert_python_failure('-m', 'py_compile', *args)
245+
246+
def test_stdin(self):
247+
result = self.pycompilecmd('-', input=self.source_path)
248+
self.assertEqual(result.returncode, 0)
249+
self.assertEqual(result.stdout, b'')
250+
self.assertEqual(result.stderr, b'')
251+
self.assertTrue(os.path.exists(self.cache_path))
252+
253+
def test_with_files(self):
254+
rc, stdout, stderr = self.pycompilecmd(self.source_path, self.source_path)
255+
self.assertEqual(rc, 0)
256+
self.assertEqual(stdout, b'')
257+
self.assertEqual(stderr, b'')
258+
self.assertTrue(os.path.exists(self.cache_path))
259+
260+
def test_bad_syntax(self):
261+
bad_syntax = os.path.join(os.path.dirname(__file__), 'badsyntax_3131.py')
262+
rc, stdout, stderr = self.pycompilecmd_failure(bad_syntax)
263+
self.assertEqual(rc, 1)
264+
self.assertEqual(stdout, b'')
265+
self.assertIn(b'SyntaxError', stderr)
266+
267+
def test_bad_syntax_with_quiet(self):
268+
bad_syntax = os.path.join(os.path.dirname(__file__), 'badsyntax_3131.py')
269+
rc, stdout, stderr = self.pycompilecmd_failure('-q', bad_syntax)
270+
self.assertEqual(rc, 1)
271+
self.assertEqual(stdout, b'')
272+
self.assertEqual(stderr, b'')
273+
274+
def test_file_not_exists(self):
275+
should_not_exists = os.path.join(os.path.dirname(__file__), 'should_not_exists.py')
276+
rc, stdout, stderr = self.pycompilecmd_failure(self.source_path, should_not_exists)
277+
self.assertEqual(rc, 1)
278+
self.assertEqual(stdout, b'')
279+
self.assertIn(b'No such file or directory', stderr)
280+
281+
def test_file_not_exists_with_quiet(self):
282+
should_not_exists = os.path.join(os.path.dirname(__file__), 'should_not_exists.py')
283+
rc, stdout, stderr = self.pycompilecmd_failure('-q', self.source_path, should_not_exists)
284+
self.assertEqual(rc, 1)
285+
self.assertEqual(stdout, b'')
286+
self.assertEqual(stderr, b'')
287+
288+
220289
if __name__ == "__main__":
221290
unittest.main()

Misc/ACKS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1565,6 +1565,7 @@ Justin Sheehy
15651565
Akash Shende
15661566
Charlie Shepherd
15671567
Bruce Sherwood
1568+
Gregory Shevchenko
15681569
Alexander Shigin
15691570
Pete Shinners
15701571
Michael Shiplett
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Add ``--quiet`` option to command-line interface of :mod:`py_compile`.
2+
Patch by Gregory Schevchenko.

0 commit comments

Comments
 (0)