Skip to content

Fix Argument.__repr__ crash when _action is not initialized#14429

Open
EternalRights wants to merge 5 commits intopytest-dev:mainfrom
EternalRights:fix-argparse-attrerror
Open

Fix Argument.__repr__ crash when _action is not initialized#14429
EternalRights wants to merge 5 commits intopytest-dev:mainfrom
EternalRights:fix-argparse-attrerror

Conversation

@EternalRights
Copy link
Copy Markdown
Contributor

@EternalRights EternalRights commented Apr 29, 2026

Ran into this while looking at #13817. When Argument.__init__ doesn't complete (e.g. if argparse.Action raises during construction), calling repr() on the partially-initialized Argument triggers a secondary AttributeError because self._action doesn't exist yet. This masks the original error message.

The fix uses getattr(self, "_action", None) in __repr__ and returns "Argument(<uninitialized>)" when _action is missing, as RonnyPfannschmidt suggested. This way the original exception message gets through instead of being buried under a secondary crash.

@psf-chronographer psf-chronographer Bot added the bot:chronographer:provided (automation) changelog entry is part of PR label Apr 29, 2026
@EternalRights EternalRights force-pushed the fix-argparse-attrerror branch from 4f863ed to 4cd4ca7 Compare April 29, 2026 17:25
Comment thread testing/test_parseopt.py Outdated
Comment on lines +353 to +368
def test_argument_repr_initialized(parser: parseopt.Parser) -> None:
"""Argument.__repr__ works normally when properly initialized."""
parser.addoption("--myflag", dest="myflag", help="test flag")
option = parser._anonymous.options[-1]
result = repr(option)
assert "opts:" in result
assert "dest:" in result


def test_argument_repr_with_type(parser: parseopt.Parser) -> None:
"""Argument.__repr__ includes type when set."""
parser.addoption("--count", type=int, dest="count", help="count")
option = parser._anonymous.options[-1]
result = repr(option)
assert "type:" in result
assert "int" in result
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why two separate tests for this? Wouldn't make sense to also test the type in test_argument_repr_initialized? For that matter, test_argument_repr_initialized could check the entire string, instead of relying on in checks for a few keywords.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the review! You're right on all points.

  1. I kept test_argument_repr_uninitialized separate because it tests a fundamentally different construction path (bare __new__ vs proper initialization through parser.addoption). This follows the same pattern as test_saferepr.py where error paths are separate from normal ones. However, I agree the initialized and with_type cases should be merged as they test the same construction path.

  2. Agreed — the type branch should be tested within test_argument_repr_initialized, not as a separate test. I'll merge them.

  3. Agreed — I'll switch to exact string matching with ==. I see test_saferepr.py consistently uses exact comparison.

I'll push the updates shortly.

EternalRights and others added 3 commits May 7, 2026 23:03
… string matching

Per nicoddemus review feedback:
- Merge test_argument_repr_with_type into test_argument_repr_initialized
  as they test the same construction path
- Switch from 'in' checks to exact == string matching
- Cover both 'without type' and 'with type' branches in one test
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bot:chronographer:provided (automation) changelog entry is part of PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants