Skip to content

Preserve argparse.FileType by making it possible to context manage files as they're opened #150125

@MojoVampire

Description

@MojoVampire

Feature or enhancement

Proposal:

gh-58032 proposed, and eventually put a PendingDeprecationWarning on argparse.FileType, for the reasonable reason that it's, as written, impossible to properly close files when a failure occurs during parsing after a file has been opened (leading to ResourceWarnings), and even when parsing completes successfully, it's difficult for the caller to properly manage what it returns; with args.infile, args.outfile: works well enough up until the caller passes - as an argument, and now you're closing your own stdin/stdout unexpectedly.

My proposal is:

  1. Add an argument (keyword-only to avoid interfering with potential changes to FileType to match open) to FileType, named stack or manager, that, if passed, will be treated as API compatible with contextlib.ExitStack (I can't imagine needing anything else, but any type providing a suitable .enter_context and .callback is adequate); when the file is opened, it will be registered on the ExitStack, via enter_context for the common case, and by registering a callback to flush it for the - case.
  2. Only issue the PendingDeprecationWarning for FileTypes created without providing an ExitStack to manage them; FileType as a whole would no longer be deprecated, only using it in antipatterns
  3. Document a basic recipe for using FileType so correct use would be obvious, along the lines of:
    with contextlib.ExitStack() as stack:
        parser = argparse.ArgumentParser(description="FileType example")
        parser.add_argument('infile', type=argparse.FileType(stack=stack), help="File to read")
        parser.add_argument('outfile', type=argparse.FileType("w", stack=stack), help="File to write")
        args = parser.parse_args()
    
        # Do stuff with args.infile and args.outfile
        # Or use stack.pop_all() to transfer resource ownership elsewhere
    
    # Stack is closed here, all newly opened files are closed, any standard handles are flushed but not closed

The changes required are tiny, it continues to allow a race-free way to verify the legality of file arguments to the program (by opening them, the only truly race-free way) without requiring uglier patterns like:

try:
    infile = open(args.infile)
except OSError as e:
    parser.print_usage(file=sys.stderr)
    sys.exit(f"Couldn't open {args.infile!r}: {e}")

with infile:
    # Repeat for outfile

and keeping it relatively easy to write simple argparse-based programs.

Has this already been discussed elsewhere?

No response given

Links to previous discussion of this feature:

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    stdlibStandard Library Python modules in the Lib/ directorytype-featureA feature request or enhancement

    Projects

    Status

    No status

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions