Skip to content

gh-144812: Add public argparse group typing protocols#150597

Open
gaborbernat wants to merge 1 commit into
python:mainfrom
gaborbernat:gh-144812-argparse-group-protocols
Open

gh-144812: Add public argparse group typing protocols#150597
gaborbernat wants to merge 1 commit into
python:mainfrom
gaborbernat:gh-144812-argparse-group-protocols

Conversation

@gaborbernat
Copy link
Copy Markdown
Contributor

ArgumentParser.add_argument_group() and add_mutually_exclusive_group() return objects whose only name is the private _ArgumentGroup and _MutuallyExclusiveGroup. Code that stores or passes these objects around has no public type to annotate against, so callers reach for the underscore-prefixed implementation classes. A GitHub code search turns up roughly 140 projects doing exactly that. 📦

This exposes two public names, argparse.ArgumentGroup and argparse.MutuallyExclusiveGroup, as structural typing.Protocols that guarantee the add_argument() method callers rely on. Modelling them as protocols rather than aliasing the concrete classes keeps the implementation private and free to evolve, which addresses the concern raised on the issue that typing needs should not turn inner classes into frozen public API. ✨ The protocols are built on first attribute access through the module __getattr__, so import argparse stays off the typing import path — relevant because argparse sits on the start-up path of most CLIs and does not otherwise import typing.

Type checkers read typeshed rather than Lib/argparse.py, so these names take effect for end users once stdlib/argparse.pyi declares the protocols and points the two return types at them. I confirmed that change makes mypy, pyright, ty, and pyrefly all resolve the public names; a matching python/typeshed PR will follow.

@read-the-docs-community
Copy link
Copy Markdown

read-the-docs-community Bot commented May 30, 2026

Documentation build overview

📚 cpython-previews | 🛠️ Build #32912641 | 📁 Comparing 6ab9015 against main (1c7011d)

  🔍 Preview build  

3 files changed
± library/argparse.html
± whatsnew/3.16.html
± whatsnew/changelog.html

@gaborbernat gaborbernat changed the title ✨ feat(argparse): add public group typing protocols gh-144812: ✨ Add public argparse group typing protocols May 30, 2026
ArgumentParser.add_argument_group() and add_mutually_exclusive_group()
return objects whose only public name was the private _ArgumentGroup and
_MutuallyExclusiveGroup. Code that stores or passes these objects had no
public type to annotate against, forcing callers to reference the
underscore-prefixed implementation classes.

Expose them as structural protocols rather than aliasing the concrete
classes, so the implementation stays private and free to evolve while
callers get a stable contract to annotate against. The protocols are
built on first attribute access through the module __getattr__, keeping
typing off the import path: argparse sits on the start-up path of most
CLIs and does not otherwise import typing.
@gaborbernat gaborbernat force-pushed the gh-144812-argparse-group-protocols branch from 22dfcb6 to 6ab9015 Compare May 30, 2026 03:06
@picnixz picnixz changed the title gh-144812: ✨ Add public argparse group typing protocols gh-144812: Add public argparse group typing protocols May 30, 2026
@picnixz
Copy link
Copy Markdown
Member

picnixz commented May 30, 2026

Please, the issue has not reached a consensus so a PR is still premature.

My concern is that once we expose the protocol publicly, we also can't change the implementation without deprecations (probably?)

I think a 3rd party library would be better in that case. In the same idea as typing_extensions.

Or alternatively, we should follow pathlib.types's idea though I'm not sure it's really the best for all modules in general.

@gaborbernat
Copy link
Copy Markdown
Contributor Author

gaborbernat commented May 30, 2026

I'm a bit puzzled about the push back here. The only thing we are exposing here is the API that is already documented through the documentation. Should we decide to change that? The typing information through the protocols would be subject to the same deprecation policy as we have for our officially documented public interface on these classes. While the classes themselves are private, the methods to call on this object are documented as public.

@picnixz
Copy link
Copy Markdown
Member

picnixz commented May 30, 2026

The stdlib has a history of not exposing lots of protocols, except with some exceptions in typing.py and collections.abc. Exposing a protocol is okay but as I said, it will create a friction with any changes we would do on the private interface and would also need to be synchronized with typeshed.

OTOH, if we make argparse a package and add argparse.types as a module with unstable types, then it would be ok I think. Much like pathlib.types. That way, we could avoid friction with the main module and we would not need to have _build_group_protocols and eagerly import typing.

But again, this would still add a maintenance cost. So I would prefer that we discuss this on the issue first. I'm ok with the protocol but _build_group_protocols is overly complex for that IMO.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants