Skip to content

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

Closed as not planned

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

Activity

  1. picnixz commented on May 19, 2026

    @picnixz
    Member

    For me, passing the stack is already an antipattern. Argparse should not be responsible for that. I am -1 on this one and would rather decouple responsibilities here

  2. added
    stdlibStandard Library Python modules in the Lib/ directory
    pendingThe issue will be closed if no feedback is provided
    on May 19, 2026
  3. MojoVampire commented on May 19, 2026

    @MojoVampire
    ContributorAuthor

    @picnixz: Argparse isn't directly responsible; the only part of it that ties to the ExitStack is argparse.FileType itself (the entirety of the rest of the argparse machinery is oblivious to it and remains unconnected; if FileType was on the io module instead as a sort of partial application of open, it would work equally well; it's part of argparse for namespacing reasons, there's no actual entanglement between it and the rest of the module).

    I'd considered making ArgumentParser itself a context manager to manage any context managers it parses, but, discounted it due to it requiring the whole of argparse to become more context manager aware (needing to check each type-parsed argument for __enter__/__exit__ and/or requiring an argument to tell it to context manage things individually, requiring reimplementing concepts like handing off ownership, etc.). contextlib.ExitStack already does everything we want though, it's far more composable/reusable, and it's more explicit (you explicitly create and manage one with with, you explicitly pass it to FileType, or any other managed type a user invents following FileType as a model; it was created at least in part to be a model for user parsers).

    I'm open to other approaches, but I think deprecating FileType, rather than fixing the problem, is a mistake; especially for short scripts, the incremental work to manually manage opening the files (especially if you're not aware of contextlib.ExitStack; a side-benefit of my approach is cross-linking to make it more discoverable) while keeping your try blocks minimal is verbose and ugly (see original post). Just this past weekend we had a talk at PyCon US 2026 by @drcraig, "How to Write a Great Command Line Application" that basically began with "I was dismayed to discover my entire presentation was recommending a deprecated tool", because any reasonable command line application taking files was using argparse.FileType to open them and benefit from all the automatic checks.

    I personally teach a class that drills students on using argparse.FileType in their final project, because the final project is intended to be a "production grade tool" used by non-programmers, and we want them doing fine-grained error handling to report minimal, actionable information to the user when something goes wrong, rather than dumping stack traces to the screen. argparse.FileType means literally 80%+ of all the possible exceptions their program can raise (most of them boil down to "can't open a file", with the rest being "file with specific format isn't in correct format or otherwise contains the wrong stuff") can be handled seamlessly by argparse (without it, doing "the right thing", the terrible pattern I showed above, opening a file without with to narrow the scope for catching exceptions so they can output a more tailored message, rather than catching all OSErrors and guessing, bloats their code dramatically, and reduces the practice they get using with statements in idiomatic ways).

    My point is, without FileType, the alternatives are ugly, verbose, and generally unidiomatic ("Manage your own files" ends up being "We reinvent wheels, badly"). With FileType as is, there are flaws, and while I don't think they were worth killing FileType given its utility for simple scripts, fixing them is a reasonable goal, and this is the least intrusive means of doing so.

  4. removed
    pendingThe issue will be closed if no feedback is provided
    on May 19, 2026
  5. cmaloney commented on May 20, 2026

    @cmaloney
    Contributor

    I like FileType but the API, and having stack need to be passed seems error prone to me. Why not do something like type=pathlib.Path?

    That makes it directly usable with open. Gives straightforward ways to resolve path arguments to absolute paths if needed (ex. tools which change cwd). A lot more attributes become easily accessible and there is not as much structural complexity.

  6. picnixz commented on May 20, 2026

    @picnixz
    Member

    Since we are in a strong disagreement, please open a discussion on discuss.python.org. This is the way to reach a consensus.

  7. picnixz commented on May 20, 2026

    @picnixz
    Member

    In addition, other core devs already decided to deprecate the feature 2y ago. I do not like when we change our decisions on a whim either (hence the needs of a DPO thread to revert that decision)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

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

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions