Skip to content

Commit b3d2427

Browse files
gh-58032: Do not use argparse.FileType in module CLIs and scripts (GH-113649)
Open and close files manually. It prevents from leaking files, preliminary creation of output files, and accidental closing of stdin and stdout.
1 parent a862981 commit b3d2427

File tree

7 files changed

+56
-41
lines changed

7 files changed

+56
-41
lines changed

Lib/ast.py

+9-5
Original file line numberDiff line numberDiff line change
@@ -1812,8 +1812,7 @@ def main():
18121812
import argparse
18131813

18141814
parser = argparse.ArgumentParser(prog='python -m ast')
1815-
parser.add_argument('infile', type=argparse.FileType(mode='rb'), nargs='?',
1816-
default='-',
1815+
parser.add_argument('infile', nargs='?', default='-',
18171816
help='the file to parse; defaults to stdin')
18181817
parser.add_argument('-m', '--mode', default='exec',
18191818
choices=('exec', 'single', 'eval', 'func_type'),
@@ -1827,9 +1826,14 @@ def main():
18271826
help='indentation of nodes (number of spaces)')
18281827
args = parser.parse_args()
18291828

1830-
with args.infile as infile:
1831-
source = infile.read()
1832-
tree = parse(source, args.infile.name, args.mode, type_comments=args.no_type_comments)
1829+
if args.infile == '-':
1830+
name = '<stdin>'
1831+
source = sys.stdin.buffer.read()
1832+
else:
1833+
name = args.infile
1834+
with open(args.infile, 'rb') as infile:
1835+
source = infile.read()
1836+
tree = parse(source, name, args.mode, type_comments=args.no_type_comments)
18331837
print(dump(tree, include_attributes=args.include_attributes, indent=args.indent))
18341838

18351839
if __name__ == '__main__':

Lib/dis.py

+9-4
Original file line numberDiff line numberDiff line change
@@ -1032,11 +1032,16 @@ def main():
10321032
help='show inline caches')
10331033
parser.add_argument('-O', '--show-offsets', action='store_true',
10341034
help='show instruction offsets')
1035-
parser.add_argument('infile', type=argparse.FileType('rb'), nargs='?', default='-')
1035+
parser.add_argument('infile', nargs='?', default='-')
10361036
args = parser.parse_args()
1037-
with args.infile as infile:
1038-
source = infile.read()
1039-
code = compile(source, args.infile.name, "exec")
1037+
if args.infile == '-':
1038+
name = '<stdin>'
1039+
source = sys.stdin.buffer.read()
1040+
else:
1041+
name = args.infile
1042+
with open(args.infile, 'rb') as infile:
1043+
source = infile.read()
1044+
code = compile(source, name, "exec")
10401045
dis(code, show_caches=args.show_caches, show_offsets=args.show_offsets)
10411046

10421047
if __name__ == "__main__":

Lib/json/tool.py

+19-15
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
import argparse
1414
import json
1515
import sys
16-
from pathlib import Path
1716

1817

1918
def main():
@@ -22,11 +21,9 @@ def main():
2221
'to validate and pretty-print JSON objects.')
2322
parser = argparse.ArgumentParser(prog=prog, description=description)
2423
parser.add_argument('infile', nargs='?',
25-
type=argparse.FileType(encoding="utf-8"),
2624
help='a JSON file to be validated or pretty-printed',
27-
default=sys.stdin)
25+
default='-')
2826
parser.add_argument('outfile', nargs='?',
29-
type=Path,
3027
help='write the output of infile to outfile',
3128
default=None)
3229
parser.add_argument('--sort-keys', action='store_true', default=False,
@@ -59,23 +56,30 @@ def main():
5956
dump_args['indent'] = None
6057
dump_args['separators'] = ',', ':'
6158

62-
with options.infile as infile:
59+
try:
60+
if options.infile == '-':
61+
infile = sys.stdin
62+
else:
63+
infile = open(options.infile, encoding='utf-8')
6364
try:
6465
if options.json_lines:
6566
objs = (json.loads(line) for line in infile)
6667
else:
6768
objs = (json.load(infile),)
69+
finally:
70+
if infile is not sys.stdin:
71+
infile.close()
6872

69-
if options.outfile is None:
70-
out = sys.stdout
71-
else:
72-
out = options.outfile.open('w', encoding='utf-8')
73-
with out as outfile:
74-
for obj in objs:
75-
json.dump(obj, outfile, **dump_args)
76-
outfile.write('\n')
77-
except ValueError as e:
78-
raise SystemExit(e)
73+
if options.outfile is None:
74+
outfile = sys.stdout
75+
else:
76+
outfile = open(options.outfile, 'w', encoding='utf-8')
77+
with outfile:
78+
for obj in objs:
79+
json.dump(obj, outfile, **dump_args)
80+
outfile.write('\n')
81+
except ValueError as e:
82+
raise SystemExit(e)
7983

8084

8185
if __name__ == '__main__':

Tools/importbench/importbench.py

+4-6
Original file line numberDiff line numberDiff line change
@@ -165,8 +165,8 @@ def using_bytecode_benchmark(seconds, repeat):
165165

166166
def main(import_, options):
167167
if options.source_file:
168-
with options.source_file:
169-
prev_results = json.load(options.source_file)
168+
with open(options.source_file, 'r', encoding='utf-8') as source_file:
169+
prev_results = json.load(source_file)
170170
else:
171171
prev_results = {}
172172
__builtins__.__import__ = import_
@@ -218,8 +218,8 @@ def main(import_, options):
218218
new_result/old_result)
219219
print(benchmark_name, ':', result)
220220
if options.dest_file:
221-
with options.dest_file:
222-
json.dump(new_results, options.dest_file, indent=2)
221+
with open(options.dest_file, 'w', encoding='utf-8') as dest_file:
222+
json.dump(new_results, dest_file, indent=2)
223223

224224

225225
if __name__ == '__main__':
@@ -229,11 +229,9 @@ def main(import_, options):
229229
parser.add_argument('-b', '--builtin', dest='builtin', action='store_true',
230230
default=False, help="use the built-in __import__")
231231
parser.add_argument('-r', '--read', dest='source_file',
232-
type=argparse.FileType('r'),
233232
help='file to read benchmark data from to compare '
234233
'against')
235234
parser.add_argument('-w', '--write', dest='dest_file',
236-
type=argparse.FileType('w'),
237235
help='file to write benchmark data to')
238236
parser.add_argument('--benchmark', dest='benchmark',
239237
help='specific benchmark to run')

Tools/peg_generator/pegen/keywordgen.py

+4-5
Original file line numberDiff line numberDiff line change
@@ -41,25 +41,24 @@ def main() -> None:
4141
description="Generate the Lib/keywords.py file from the grammar."
4242
)
4343
parser.add_argument(
44-
"grammar", type=str, help="The file with the grammar definition in PEG format"
44+
"grammar", help="The file with the grammar definition in PEG format"
4545
)
4646
parser.add_argument(
47-
"tokens_file", type=argparse.FileType("r"), help="The file with the token definitions"
47+
"tokens_file", help="The file with the token definitions"
4848
)
4949
parser.add_argument(
5050
"keyword_file",
51-
type=argparse.FileType("w"),
5251
help="The path to write the keyword definitions",
5352
)
5453
args = parser.parse_args()
5554

5655
grammar, _, _ = build_parser(args.grammar)
57-
with args.tokens_file as tok_file:
56+
with open(args.tokens_file) as tok_file:
5857
all_tokens, exact_tok, non_exact_tok = generate_token_definitions(tok_file)
5958
gen = CParserGenerator(grammar, all_tokens, exact_tok, non_exact_tok, file=None)
6059
gen.collect_rules()
6160

62-
with args.keyword_file as thefile:
61+
with open(args.keyword_file, 'w') as thefile:
6362
all_keywords = sorted(list(gen.keywords.keys()))
6463
all_soft_keywords = sorted(gen.soft_keywords)
6564

Tools/scripts/summarize_stats.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -1154,12 +1154,13 @@ def to_markdown(x):
11541154
print("Stats gathered on:", date.today(), file=out)
11551155

11561156

1157-
def output_stats(inputs: list[Path], json_output=TextIO | None):
1157+
def output_stats(inputs: list[Path], json_output=str | None):
11581158
match len(inputs):
11591159
case 1:
11601160
data = load_raw_data(Path(inputs[0]))
11611161
if json_output is not None:
1162-
save_raw_data(data, json_output) # type: ignore
1162+
with open(json_output, 'w', encoding='utf-8') as f:
1163+
save_raw_data(data, f) # type: ignore
11631164
stats = Stats(data)
11641165
output_markdown(sys.stdout, LAYOUT, stats)
11651166
case 2:
@@ -1195,7 +1196,6 @@ def main():
11951196
parser.add_argument(
11961197
"--json-output",
11971198
nargs="?",
1198-
type=argparse.FileType("w"),
11991199
help="Output complete raw results to the given JSON file.",
12001200
)
12011201

Tools/ssl/make_ssl_data.py

+8-3
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
)
2424
parser.add_argument("srcdir", help="OpenSSL source directory")
2525
parser.add_argument(
26-
"output", nargs="?", type=argparse.FileType("w"), default=sys.stdout
26+
"output", nargs="?", default=None
2727
)
2828

2929

@@ -126,8 +126,13 @@ def main():
126126
lines.append("")
127127
lines.extend(gen_error_codes(args))
128128

129-
for line in lines:
130-
args.output.write(line + "\n")
129+
if args.output is None:
130+
for line in lines:
131+
print(line)
132+
else:
133+
with open(args.output, 'w') as output:
134+
for line in lines:
135+
print(line, file=output)
131136

132137

133138
if __name__ == "__main__":

0 commit comments

Comments
 (0)