Skip to content

gh-47169: Adding errno Symbolic Values to OSError.__str__()#150041

Open
sadrasabouri wants to merge 19 commits into
python:mainfrom
sadrasabouri:bpo-2920
Open

gh-47169: Adding errno Symbolic Values to OSError.__str__()#150041
sadrasabouri wants to merge 19 commits into
python:mainfrom
sadrasabouri:bpo-2920

Conversation

@sadrasabouri
Copy link
Copy Markdown

@sadrasabouri sadrasabouri commented May 18, 2026

OSError.__str__ now includes the symbolic errno name alongside the number (something like below):

[Errno 2 (ENOENT)] No such file or directory: 'foo'

The errno constants list (previously inlined in Modules/errnomodule.c) is extracted into Objects/errnonames.h as a macro header. Both errnomodule.c (for module registration) and Objects/exceptions.c (for name lookup) include it with their customly defined add_errcode definition, avoiding duplication of the errno. list. If the errno value is not in the list (e.g. a platform-specific code not covered by the header), the output falls back to the plain number, same as before.. A new helper OSError_format_errno walks the list to find the symbolic name for a given errno value. OSError_str calls this helper in all paths.

Tests are adapted from #14988.

Note: _errno_list is searched linearly on every OSError.__str__() call. A hash table or a flat array indexed by errno value would give O(1) lookup, but each has its own trade-off. I leave the review open on that, so we can discuss to find the best solution.

Question: The tests in test_tabnanny wrap the helper with self.assertRaises(SystemExit), tabnanny.errprint() writes to stderr and immediately calls sys.exit, which raises SystemExit before verify_tabnanny_check() reaches the assertEqual on stderr.getvalue.

@sadrasabouri sadrasabouri requested a review from iritkatriel as a code owner May 18, 2026 23:37
@python-cla-bot
Copy link
Copy Markdown

python-cla-bot Bot commented May 18, 2026

All commit authors signed the Contributor License Agreement.

CLA signed

@cmaloney
Copy link
Copy Markdown
Contributor

The "Check if generated files are up to date" failure is real / needs to be addressed:

too many lines, try to increase MAX_SIZES[MAXLINES] in cpython/_parser.py
/home/runner/work/cpython/cpython/Objects/errnonames.h starting at line 10 to 214

(Should be able to run that locally as make check-c-globals)

@@ -0,0 +1,7 @@
OSError.__str__() now includes the symbolic name of the error code (e.g.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

  1. Be more concise in this. In particular the high level change is just "Include error name in OSError". The particular implementation details aren't critical to describe / important to someone just trying to figure out what changed in CPython.
  2. Should use rST syntax, in particular when referring to specific classes, methods, or values link to them when appropriate

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

fixed in 156cd0c

Comment thread Lib/test/test_exceptions.py Outdated
open(filename)
self.assertEqual(
str(cm.exception),
f"[Errno {errno.ENOENT} ({errname})] {errmsg}: {filename!r}",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit: I'd prefer being a constant string for the test here, that it gets the integer + ENOENT string is more important to me than "it matches this other piece we can look up"

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

fixed in 6d0ca87

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Just kept the {errno.ENOENT} due to c8a4a31

Comment thread Lib/test/test_signal.py
not in err):
raise AssertionError(err)
if ('OSError: [Errno %d]' % errno.EBADF) not in err:
if ('OSError: [Errno %d (%s)]' %
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This exists in the CPython codebase; that makes me worry this same sort of string matching may exist in many other codebases and will be broken by this change...

Is there some way we could measure/check for that? Keeping the symbolic name outside the [] would limit breakage some.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Good point. It's a valid concern indeed. I ran rg -n '\[Errno [^]]*\]|OSError: \[Errno' Lib Tools Objects Modules Python and found cases treating the int after Error as a format.
The two other alternatives are as follows, let me know which one looks better:

  1. [Errno 2] (ENOENT) No such file or directory
  2. [Errno 2] No such file or directory (ENOENT)

I like 1 bettter, to avoid confusion.

@sadrasabouri
Copy link
Copy Markdown
Author

Hey @cmaloney, I applied the requested changes and raised a question. Once you send me your feedback I will continue on this PR.

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

Projects

Status: Todo

Development

Successfully merging this pull request may close these issues.

3 participants