diff --git a/Lib/json/tool.py b/Lib/json/tool.py index 0490b8c0be11dfe..b9155535adbc804 100644 --- a/Lib/json/tool.py +++ b/Lib/json/tool.py @@ -11,9 +11,28 @@ """ import argparse +import contextlib import json +import os.path import sys -from pathlib import Path +import tempfile + + +@contextlib.contextmanager +def _staged_outfile(path): + # Use tempfile to support outfile == infile case. + # See https://discuss.python.org/t/adding-atomicwrite-in-stdlib/11899 + dir, base = os.path.split(path) + fd, tempname = tempfile.mkstemp(prefix=base + '.', dir=dir) + file = os.fdopen(fd, "w", encoding="utf-8") + try: + with file: + yield file + except: + os.remove(tempname) + raise + else: + os.replace(tempname, path) def main(): @@ -26,7 +45,7 @@ def main(): help='a JSON file to be validated or pretty-printed', default=sys.stdin) parser.add_argument('outfile', nargs='?', - type=Path, + type=str, help='write the output of infile to outfile', default=None) parser.add_argument('--sort-keys', action='store_true', default=False, @@ -66,10 +85,12 @@ def main(): else: objs = (json.load(infile),) - if options.outfile is None: + if options.outfile is None or options.outfile == '-': + # infile (argparse.FileType) supports '-' so outfile + # supports '-' for consistency. out = sys.stdout else: - out = options.outfile.open('w', encoding='utf-8') + out = _staged_outfile(options.outfile) with out as outfile: for obj in objs: json.dump(obj, outfile, **dump_args) diff --git a/Misc/NEWS.d/next/Library/2022-01-18-18-32-49.bpo-33927.2dO0-u.rst b/Misc/NEWS.d/next/Library/2022-01-18-18-32-49.bpo-33927.2dO0-u.rst new file mode 100644 index 000000000000000..e3bd89785d07cfc --- /dev/null +++ b/Misc/NEWS.d/next/Library/2022-01-18-18-32-49.bpo-33927.2dO0-u.rst @@ -0,0 +1,3 @@ +``python -m json.tool infile outfile`` uses staged write idiom for +``outfile``. This allows that outfile is same to infile even when +``--json-lines`` option is used.