From 9bd0ad6e56928a3ef300911a67441da6b2fee16f Mon Sep 17 00:00:00 2001 From: Chris Wesseling Date: Thu, 28 Oct 2021 14:47:44 +0200 Subject: [PATCH 1/4] Make json.tool soak up infile before writing to outfile So that $ python -m json.tool foo.json foo.json doens't result in an empty foo.json. --- Lib/json/tool.py | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/Lib/json/tool.py b/Lib/json/tool.py index 5dee0a744b2a99e..5859a2d3b04ca4f 100644 --- a/Lib/json/tool.py +++ b/Lib/json/tool.py @@ -13,6 +13,18 @@ import argparse import json import sys +from pathlib import Path + + +def pathtype(path): + class wrapped_fd: + 'Make stdin and stdout have open() like a `pathlib.Path`' + def __init__(self, file_descriptor): + self.fd = file_descriptor + + def open(self, *args, **kwargs): + return self.fd + return wrapped_fd(path) if path in (sys.stdin, sys.stderr) else Path(path) def main(): @@ -21,11 +33,11 @@ def main(): 'to validate and pretty-print JSON objects.') parser = argparse.ArgumentParser(prog=prog, description=description) parser.add_argument('infile', nargs='?', - type=argparse.FileType(encoding="utf-8"), + type=pathtype, help='a JSON file to be validated or pretty-printed', default=sys.stdin) parser.add_argument('outfile', nargs='?', - type=argparse.FileType('w', encoding="utf-8"), + type=pathtype, help='write the output of infile to outfile', default=sys.stdout) parser.add_argument('--sort-keys', action='store_true', default=False, @@ -58,12 +70,17 @@ def main(): dump_args['indent'] = None dump_args['separators'] = ',', ':' - with options.infile as infile, options.outfile as outfile: + with options.infile.open(encoding='utf-8') as infile: try: if options.json_lines: objs = (json.loads(line) for line in infile) else: - objs = (json.load(infile), ) + objs = (json.load(infile),) + except ValueError as e: + raise SystemExit(e) + + with options.outfile.open('w', encoding='utf-8') as outfile: + try: for obj in objs: json.dump(obj, outfile, **dump_args) outfile.write('\n') From 35ea369964d4f5e16d68654117b6eb326fc74eac Mon Sep 17 00:00:00 2001 From: Chris Wesseling Date: Thu, 28 Oct 2021 16:27:15 +0200 Subject: [PATCH 2/4] Add test for in place writing case Fix failing tests. --- Lib/json/tool.py | 34 ++++++++++++++------------------- Lib/test/test_json/test_tool.py | 9 +++++++++ 2 files changed, 23 insertions(+), 20 deletions(-) diff --git a/Lib/json/tool.py b/Lib/json/tool.py index 5859a2d3b04ca4f..345ab1f8519e463 100644 --- a/Lib/json/tool.py +++ b/Lib/json/tool.py @@ -16,15 +16,13 @@ from pathlib import Path -def pathtype(path): - class wrapped_fd: - 'Make stdin and stdout have open() like a `pathlib.Path`' - def __init__(self, file_descriptor): - self.fd = file_descriptor +class wrapped_fd: + 'Make stdin and stdout have open() like a `pathlib.Path`' + def __init__(self, file_descriptor): + self.fd = file_descriptor - def open(self, *args, **kwargs): - return self.fd - return wrapped_fd(path) if path in (sys.stdin, sys.stderr) else Path(path) + def open(self, *args, **kwargs): + return self.fd def main(): @@ -33,13 +31,13 @@ def main(): 'to validate and pretty-print JSON objects.') parser = argparse.ArgumentParser(prog=prog, description=description) parser.add_argument('infile', nargs='?', - type=pathtype, + type=Path, help='a JSON file to be validated or pretty-printed', - default=sys.stdin) + default=wrapped_fd(sys.stdin)) parser.add_argument('outfile', nargs='?', - type=pathtype, + type=Path, help='write the output of infile to outfile', - default=sys.stdout) + default=wrapped_fd(sys.stdout)) parser.add_argument('--sort-keys', action='store_true', default=False, help='sort the output of dictionaries alphabetically by key') parser.add_argument('--no-ensure-ascii', dest='ensure_ascii', action='store_false', @@ -76,14 +74,10 @@ def main(): objs = (json.loads(line) for line in infile) else: objs = (json.load(infile),) - except ValueError as e: - raise SystemExit(e) - - with options.outfile.open('w', encoding='utf-8') as outfile: - try: - for obj in objs: - json.dump(obj, outfile, **dump_args) - outfile.write('\n') + with options.outfile.open('w', encoding='utf-8') as outfile: + for obj in objs: + json.dump(obj, outfile, **dump_args) + outfile.write('\n') except ValueError as e: raise SystemExit(e) diff --git a/Lib/test/test_json/test_tool.py b/Lib/test/test_json/test_tool.py index 0386690cad95beb..1d7fca6efb1cc72 100644 --- a/Lib/test/test_json/test_tool.py +++ b/Lib/test/test_json/test_tool.py @@ -131,6 +131,15 @@ def test_infile_outfile(self): self.assertEqual(out, b'') self.assertEqual(err, b'') + def test_writing_in_place(self): + infile = self._create_infile() + rc, out, err = assert_python_ok('-m', 'json.tool', 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' process = subprocess.run(args, input=self.jsonlines_raw, capture_output=True, text=True, check=True) From 81088a1f9f9f04c596c68c415b8246e75dc545e5 Mon Sep 17 00:00:00 2001 From: Chris Wesseling Date: Fri, 29 Oct 2021 13:21:21 +0200 Subject: [PATCH 3/4] Process review remarks from @methane https://github.com/python/cpython/pull/29273#pullrequestreview-792594115 --- Lib/json/tool.py | 30 +++++++++++++----------------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/Lib/json/tool.py b/Lib/json/tool.py index 345ab1f8519e463..0490b8c0be11dfe 100644 --- a/Lib/json/tool.py +++ b/Lib/json/tool.py @@ -16,28 +16,19 @@ from pathlib import Path -class wrapped_fd: - 'Make stdin and stdout have open() like a `pathlib.Path`' - def __init__(self, file_descriptor): - self.fd = file_descriptor - - def open(self, *args, **kwargs): - return self.fd - - def main(): prog = 'python -m json.tool' description = ('A simple command line interface for json module ' 'to validate and pretty-print JSON objects.') parser = argparse.ArgumentParser(prog=prog, description=description) parser.add_argument('infile', nargs='?', - type=Path, + type=argparse.FileType(encoding="utf-8"), help='a JSON file to be validated or pretty-printed', - default=wrapped_fd(sys.stdin)) + default=sys.stdin) parser.add_argument('outfile', nargs='?', type=Path, help='write the output of infile to outfile', - default=wrapped_fd(sys.stdout)) + default=None) parser.add_argument('--sort-keys', action='store_true', default=False, help='sort the output of dictionaries alphabetically by key') parser.add_argument('--no-ensure-ascii', dest='ensure_ascii', action='store_false', @@ -68,16 +59,21 @@ def main(): dump_args['indent'] = None dump_args['separators'] = ',', ':' - with options.infile.open(encoding='utf-8') as infile: + with options.infile as infile: try: if options.json_lines: objs = (json.loads(line) for line in infile) else: objs = (json.load(infile),) - with options.outfile.open('w', encoding='utf-8') as outfile: - for obj in objs: - json.dump(obj, outfile, **dump_args) - outfile.write('\n') + + if options.outfile is None: + out = sys.stdout + else: + out = options.outfile.open('w', encoding='utf-8') + with out as outfile: + for obj in objs: + json.dump(obj, outfile, **dump_args) + outfile.write('\n') except ValueError as e: raise SystemExit(e) From 1fdba545ba98616cd83f21ef1fac17089e76876c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Langa?= Date: Sat, 6 Nov 2021 17:47:51 +0100 Subject: [PATCH 4/4] Add Blurb --- .../next/Library/2021-11-06-17-47-46.bpo-45644.ZMqHD_.rst | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2021-11-06-17-47-46.bpo-45644.ZMqHD_.rst diff --git a/Misc/NEWS.d/next/Library/2021-11-06-17-47-46.bpo-45644.ZMqHD_.rst b/Misc/NEWS.d/next/Library/2021-11-06-17-47-46.bpo-45644.ZMqHD_.rst new file mode 100644 index 000000000000000..2cf4eae02c44982 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2021-11-06-17-47-46.bpo-45644.ZMqHD_.rst @@ -0,0 +1,3 @@ +In-place JSON file formatting using ``python3 -m json.tool infile infile`` +now works correctly, previously it left the file empty. Patch by Chris +Wesseling.