Skip to content

gh-122873: Allow "python3 -m json" to work #122884

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 24 commits into from
Aug 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
27e449d
Rename json.tool to json.__main__
treyhunner Aug 1, 2024
423c971
Make json.tool delegate to json.__main__
treyhunner Aug 1, 2024
1c1a8fa
Note json.tool deprecation
treyhunner Aug 1, 2024
f8f6931
Move main function from json.__main__ to json.tool
treyhunner Aug 2, 2024
834fbd7
Remove deprecation warning
treyhunner Aug 9, 2024
62afcf1
Fix module name as shown at command-line
treyhunner Aug 10, 2024
f58e60a
Update docs and what's new for json.tool to json
treyhunner Aug 10, 2024
168082a
Remove duplicate json module description from docs
treyhunner Aug 10, 2024
52039ee
Give up on marking `-m` as an option
treyhunner Aug 10, 2024
2e37475
Remove unnecessary newline in changed note
treyhunner Aug 10, 2024
efd0503
Remove now-invalid json.tool module reference
treyhunner Aug 10, 2024
c6bb211
Add news entry with blurb
treyhunner Aug 10, 2024
e768e39
Remove older raw string usage in docstring
treyhunner Aug 10, 2024
a6b5c0d
Use sentence case in section title
treyhunner Aug 10, 2024
3105bc2
Add CLI invocation for easier copy-pasting
treyhunner Aug 10, 2024
238fc57
Update json.__init__ docstring for python -m json
treyhunner Aug 10, 2024
7f704bf
Note `python -m json` in What's New
treyhunner Aug 10, 2024
fba68ac
Use SystemExit consistently in json package
treyhunner Aug 10, 2024
8662c81
Improve grammar in news
treyhunner Aug 12, 2024
52129e6
Add json.tool submodule documentation reference back in.
treyhunner Aug 12, 2024
016206d
Note that json.tool still works for compatibility reasons
treyhunner Aug 12, 2024
9b22285
Note that json.tool implements the CLI
treyhunner Aug 12, 2024
9fd0c1c
Reference json.__main__ in json.tool docstring
treyhunner Aug 12, 2024
734fbf9
Switch json.tool to json on listing of Python CLIs
treyhunner Aug 13, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Doc/library/cmdline.rst
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ The following modules have a command-line interface.
* :ref:`http.server <http-server-cli>`
* :mod:`!idlelib`
* :ref:`inspect <inspect-module-cli>`
* :ref:`json.tool <json-commandline>`
* :ref:`json <json-commandline>`
* :mod:`mimetypes`
* :mod:`pdb`
* :mod:`pickle`
Expand Down
30 changes: 18 additions & 12 deletions Doc/library/json.rst
Original file line number Diff line number Diff line change
Expand Up @@ -116,15 +116,15 @@ Extending :class:`JSONEncoder`::
['[2.0', ', 1.0', ']']


Using :mod:`json.tool` from the shell to validate and pretty-print:
Using :mod:`json` from the shell to validate and pretty-print:

.. code-block:: shell-session

$ echo '{"json":"obj"}' | python -m json.tool
$ echo '{"json":"obj"}' | python -m json
{
"json": "obj"
}
$ echo '{1.2:3.4}' | python -m json.tool
$ echo '{1.2:3.4}' | python -m json
Expecting property name enclosed in double quotes: line 1 column 2 (char 1)

See :ref:`json-commandline` for detailed documentation.
Expand Down Expand Up @@ -678,40 +678,46 @@ when serializing instances of "exotic" numerical types such as


.. _json-commandline:
.. program:: json.tool
.. program:: json

Command Line Interface
Command-line interface
----------------------

.. module:: json.tool
:synopsis: A command line to validate and pretty-print JSON.
:synopsis: A command-line interface to validate and pretty-print JSON.

**Source code:** :source:`Lib/json/tool.py`

--------------

The :mod:`json.tool` module provides a simple command line interface to validate
and pretty-print JSON objects.
The :mod:`json` module can be invoked as a script via ``python -m json``
to validate and pretty-print JSON objects. The :mod:`json.tool` submodule
implements this interface.

If the optional ``infile`` and ``outfile`` arguments are not
specified, :data:`sys.stdin` and :data:`sys.stdout` will be used respectively:

.. code-block:: shell-session

$ echo '{"json": "obj"}' | python -m json.tool
$ echo '{"json": "obj"}' | python -m json
{
"json": "obj"
}
$ echo '{1.2:3.4}' | python -m json.tool
$ echo '{1.2:3.4}' | python -m json
Expecting property name enclosed in double quotes: line 1 column 2 (char 1)

.. versionchanged:: 3.5
The output is now in the same order as the input. Use the
:option:`--sort-keys` option to sort the output of dictionaries
alphabetically by key.

.. versionchanged:: 3.14
The :mod:`json` module may now be directly executed as
``python -m json``. For backwards compatibility, invoking
the CLI as ``python -m json.tool`` remains supported.

Command line options

Command-line options
^^^^^^^^^^^^^^^^^^^^

.. option:: infile
Expand All @@ -720,7 +726,7 @@ Command line options

.. code-block:: shell-session

$ python -m json.tool mp_films.json
$ python -m json mp_films.json
[
{
"title": "And Now for Something Completely Different",
Expand Down
4 changes: 4 additions & 0 deletions Doc/whatsnew/3.14.rst
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,10 @@ Add notes for JSON serialization errors that allow to identify the source
of the error.
(Contributed by Serhiy Storchaka in :gh:`122163`.)

Enable :mod:`json` module to work as a script using the :option:`-m` switch: ``python -m json``.
See the :ref:`JSON command-line interface <json-commandline>` documentation.
(Contributed by Trey Hunner in :gh:`122873`.)

operator
--------

Expand Down
6 changes: 3 additions & 3 deletions Lib/json/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,13 +86,13 @@
'[2.0, 1.0]'


Using json.tool from the shell to validate and pretty-print::
Using json from the shell to validate and pretty-print::

$ echo '{"json":"obj"}' | python -m json.tool
$ echo '{"json":"obj"}' | python -m json
{
"json": "obj"
}
$ echo '{ 1.2:3.4}' | python -m json.tool
$ echo '{ 1.2:3.4}' | python -m json
Expecting property name enclosed in double quotes: line 1 column 3 (char 2)
"""
__version__ = '2.0.9'
Expand Down
20 changes: 20 additions & 0 deletions Lib/json/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
"""Command-line tool to validate and pretty-print JSON

Usage::

$ echo '{"json":"obj"}' | python -m json
{
"json": "obj"
}
$ echo '{ 1.2:3.4}' | python -m json
Expecting property name enclosed in double quotes: line 1 column 3 (char 2)

"""
import json.tool


if __name__ == '__main__':
try:
json.tool.main()
except BrokenPipeError as exc:
raise SystemExit(exc.errno)
17 changes: 5 additions & 12 deletions Lib/json/tool.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,15 @@
r"""Command-line tool to validate and pretty-print JSON

Usage::

$ echo '{"json":"obj"}' | python -m json.tool
{
"json": "obj"
}
$ echo '{ 1.2:3.4}' | python -m json.tool
Expecting property name enclosed in double quotes: line 1 column 3 (char 2)
"""Command-line tool to validate and pretty-print JSON

See `json.__main__` for a usage example (invocation as
`python -m json.tool` is supported for backwards compatibility).
"""
import argparse
import json
import sys


def main():
prog = 'python -m json.tool'
prog = 'python -m json'
description = ('A simple command line interface for json module '
'to validate and pretty-print JSON objects.')
parser = argparse.ArgumentParser(prog=prog, description=description)
Expand Down Expand Up @@ -86,4 +79,4 @@ def main():
try:
main()
except BrokenPipeError as exc:
sys.exit(exc.errno)
raise SystemExit(exc.errno)
40 changes: 23 additions & 17 deletions Lib/test/test_json/test_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,15 @@


@support.requires_subprocess()
class TestTool(unittest.TestCase):
class TestMain(unittest.TestCase):
data = """

[["blorpie"],[ "whoops" ] , [
],\t"d-shtaeou",\r"d-nthiouh",
"i-vhbjkhnth", {"nifty":87}, {"morefield" :\tfalse,"field"
:"yes"} ]
"""
module = 'json'

expect_without_sort_keys = textwrap.dedent("""\
[
Expand Down Expand Up @@ -87,7 +88,7 @@ class TestTool(unittest.TestCase):
""")

def test_stdin_stdout(self):
args = sys.executable, '-m', 'json.tool'
args = sys.executable, '-m', self.module
process = subprocess.run(args, input=self.data, capture_output=True, text=True, check=True)
self.assertEqual(process.stdout, self.expect)
self.assertEqual(process.stderr, '')
Expand All @@ -101,7 +102,7 @@ def _create_infile(self, data=None):

def test_infile_stdout(self):
infile = self._create_infile()
rc, out, err = assert_python_ok('-m', 'json.tool', infile)
rc, out, err = assert_python_ok('-m', self.module, infile)
self.assertEqual(rc, 0)
self.assertEqual(out.splitlines(), self.expect.encode().splitlines())
self.assertEqual(err, b'')
Expand All @@ -115,7 +116,7 @@ def test_non_ascii_infile(self):
''').encode()

infile = self._create_infile(data)
rc, out, err = assert_python_ok('-m', 'json.tool', infile)
rc, out, err = assert_python_ok('-m', self.module, infile)

self.assertEqual(rc, 0)
self.assertEqual(out.splitlines(), expect.splitlines())
Expand All @@ -124,7 +125,7 @@ def test_non_ascii_infile(self):
def test_infile_outfile(self):
infile = self._create_infile()
outfile = os_helper.TESTFN + '.out'
rc, out, err = assert_python_ok('-m', 'json.tool', infile, outfile)
rc, out, err = assert_python_ok('-m', self.module, infile, outfile)
self.addCleanup(os.remove, outfile)
with open(outfile, "r", encoding="utf-8") as fp:
self.assertEqual(fp.read(), self.expect)
Expand All @@ -134,28 +135,28 @@ def test_infile_outfile(self):

def test_writing_in_place(self):
infile = self._create_infile()
rc, out, err = assert_python_ok('-m', 'json.tool', infile, infile)
rc, out, err = assert_python_ok('-m', self.module, infile, infile)
with open(infile, "r", encoding="utf-8") as fp:
self.assertEqual(fp.read(), self.expect)
self.assertEqual(rc, 0)
self.assertEqual(out, b'')
self.assertEqual(err, b'')

def test_jsonlines(self):
args = sys.executable, '-m', 'json.tool', '--json-lines'
args = sys.executable, '-m', self.module, '--json-lines'
process = subprocess.run(args, input=self.jsonlines_raw, capture_output=True, text=True, check=True)
self.assertEqual(process.stdout, self.jsonlines_expect)
self.assertEqual(process.stderr, '')

def test_help_flag(self):
rc, out, err = assert_python_ok('-m', 'json.tool', '-h')
rc, out, err = assert_python_ok('-m', self.module, '-h')
self.assertEqual(rc, 0)
self.assertTrue(out.startswith(b'usage: '))
self.assertEqual(err, b'')

def test_sort_keys_flag(self):
infile = self._create_infile()
rc, out, err = assert_python_ok('-m', 'json.tool', '--sort-keys', infile)
rc, out, err = assert_python_ok('-m', self.module, '--sort-keys', infile)
self.assertEqual(rc, 0)
self.assertEqual(out.splitlines(),
self.expect_without_sort_keys.encode().splitlines())
Expand All @@ -169,31 +170,31 @@ def test_indent(self):
2
]
''')
args = sys.executable, '-m', 'json.tool', '--indent', '2'
args = sys.executable, '-m', self.module, '--indent', '2'
process = subprocess.run(args, input=input_, capture_output=True, text=True, check=True)
self.assertEqual(process.stdout, expect)
self.assertEqual(process.stderr, '')

def test_no_indent(self):
input_ = '[1,\n2]'
expect = '[1, 2]\n'
args = sys.executable, '-m', 'json.tool', '--no-indent'
args = sys.executable, '-m', self.module, '--no-indent'
process = subprocess.run(args, input=input_, capture_output=True, text=True, check=True)
self.assertEqual(process.stdout, expect)
self.assertEqual(process.stderr, '')

def test_tab(self):
input_ = '[1, 2]'
expect = '[\n\t1,\n\t2\n]\n'
args = sys.executable, '-m', 'json.tool', '--tab'
args = sys.executable, '-m', self.module, '--tab'
process = subprocess.run(args, input=input_, capture_output=True, text=True, check=True)
self.assertEqual(process.stdout, expect)
self.assertEqual(process.stderr, '')

def test_compact(self):
input_ = '[ 1 ,\n 2]'
expect = '[1,2]\n'
args = sys.executable, '-m', 'json.tool', '--compact'
args = sys.executable, '-m', self.module, '--compact'
process = subprocess.run(args, input=input_, capture_output=True, text=True, check=True)
self.assertEqual(process.stdout, expect)
self.assertEqual(process.stderr, '')
Expand All @@ -202,7 +203,7 @@ def test_no_ensure_ascii_flag(self):
infile = self._create_infile('{"key":"💩"}')
outfile = os_helper.TESTFN + '.out'
self.addCleanup(os.remove, outfile)
assert_python_ok('-m', 'json.tool', '--no-ensure-ascii', infile, outfile)
assert_python_ok('-m', self.module, '--no-ensure-ascii', infile, outfile)
with open(outfile, "rb") as f:
lines = f.read().splitlines()
# asserting utf-8 encoded output file
Expand All @@ -213,7 +214,7 @@ def test_ensure_ascii_default(self):
infile = self._create_infile('{"key":"💩"}')
outfile = os_helper.TESTFN + '.out'
self.addCleanup(os.remove, outfile)
assert_python_ok('-m', 'json.tool', infile, outfile)
assert_python_ok('-m', self.module, infile, outfile)
with open(outfile, "rb") as f:
lines = f.read().splitlines()
# asserting an ascii encoded output file
Expand All @@ -222,11 +223,16 @@ def test_ensure_ascii_default(self):

@unittest.skipIf(sys.platform =="win32", "The test is failed with ValueError on Windows")
def test_broken_pipe_error(self):
cmd = [sys.executable, '-m', 'json.tool']
cmd = [sys.executable, '-m', self.module]
proc = subprocess.Popen(cmd,
stdout=subprocess.PIPE,
stdin=subprocess.PIPE)
# bpo-39828: Closing before json.tool attempts to write into stdout.
# bpo-39828: Closing before json attempts to write into stdout.
proc.stdout.close()
proc.communicate(b'"{}"')
self.assertEqual(proc.returncode, errno.EPIPE)


@support.requires_subprocess()
class TestTool(TestMain):
module = 'json.tool'
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Enable :mod:`json` module to work as a script using the :option:`-m` switch: ``python -m json``.
See the :ref:`JSON command-line interface <json-commandline>` documentation.
Patch by Trey Hunner.
Loading