Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 47 additions & 8 deletions Doc/library/exceptions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -357,20 +357,32 @@ The following exceptions are the exceptions that are usually raised.
the built-in constant.


.. exception:: OSError([arg])
OSError(errno, strerror[, filename[, winerror[, filename2]]])
.. exception:: OSError([[errno,] strerror,] /, filename=None, winerror=None, filename2=None)

.. index:: pair: module; errno

This exception is raised when a system function returns a system-related
error, including I/O failures such as "file not found" or "disk full"
(not for illegal argument types or other incidental errors).

The second form of the constructor sets the corresponding attributes,
described below. The attributes default to :const:`None` if not
specified. For backwards compatibility, if three arguments are passed,
the :attr:`~BaseException.args` attribute contains only a 2-tuple
of the first two constructor arguments.
The constructor arguments set the corresponding attributes, described below.
If *errno* is omitted,
it defaults to the error code which corresponds to the exception class,
for the subclasses listed in `OS exceptions`_ below,
and to ``None`` for :exc:`OSError` itself.
If *strerror* is omitted,
it is derived from *winerror* on Windows when that was given,
and from the resulting :attr:`.errno` otherwise.
The remaining attributes default to ``None``.

If *filename*, *winerror* or *filename2* is given,
the :attr:`~BaseException.args` attribute is set to
``(errno, strerror, filename, winerror, filename2)``,
truncated after the last of the three which was given,
with omitted values replaced by ``None``.
Otherwise it contains the arguments as passed.
Either way ``type(exc)(*exc.args)`` reproduces the exception,
which is how it is pickled.

The constructor often actually returns a subclass of :exc:`OSError`, as
described in `OS exceptions`_ below. The particular subclass depends on
Expand All @@ -380,7 +392,9 @@ The following exceptions are the exceptions that are usually raised.

.. attribute:: errno

A numeric error code from the C variable :c:data:`errno`.
A numeric error code from the C variable :c:data:`errno`,
or the :attr:`default_errno` of the exception class
when the constructor was called without one.

.. attribute:: winerror

Expand Down Expand Up @@ -423,6 +437,11 @@ The following exceptions are the exceptions that are usually raised.
:term:`filesystem encoding and error handler`. Also, the *filename2*
constructor argument and attribute was added.

.. versionchanged:: next
*errno* and *strerror* can now be omitted.
*filename*, *winerror* and *filename2* can be passed by keyword.
The :attr:`~BaseException.args` attribute is no longer truncated.


.. exception:: OverflowError

Expand Down Expand Up @@ -737,6 +756,20 @@ OS exceptions

The following exceptions are subclasses of :exc:`OSError`, they get raised
depending on the system error code.
Each of them corresponds to one or more :mod:`errno` values,
and defines the first of them as a class attribute:

.. attribute:: OSError.default_errno

The error code which corresponds to the exception class,
used as :attr:`~OSError.errno` when the *errno* argument is omitted.
It is not defined by :exc:`OSError` itself,
nor by :exc:`ConnectionError`,
which correspond to no single error code.
A user-defined subclass may define it
to give its instances a default :attr:`~OSError.errno` too.

.. versionadded:: next

.. exception:: BlockingIOError

Expand All @@ -754,6 +787,12 @@ depending on the system error code.
before it blocked. This attribute is available when using the
buffered I/O classes from the :mod:`io` module.

It is set by passing the *characters_written* keyword argument
or the third positional argument.

.. versionchanged:: next
Added the *characters_written* keyword argument.

.. exception:: ChildProcessError

Raised when an operation on a child process failed.
Expand Down
26 changes: 26 additions & 0 deletions Doc/whatsnew/3.16.rst
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,23 @@ Other language changes
libraries.
(Contributed by Serhiy Storchaka in :gh:`78959`.)

* The :exc:`OSError` constructor no longer requires the *errno* and
*strerror* arguments.
If *errno* is omitted, it defaults to the error code which corresponds
to the exception class, so that ``FileNotFoundError()`` works both with
code which tests the :attr:`~OSError.errno` attribute and with code
which uses :func:`isinstance`.
If *strerror* is omitted, it is derived from the resulting
:attr:`~OSError.errno`, or on Windows from :attr:`~OSError.winerror`
if that was given.
The *filename*, *winerror* and *filename2* arguments can now be passed
by keyword, as can *characters_written* for :exc:`BlockingIOError`::

>>> str(FileNotFoundError(filename='cfg.ini'))
"[Errno 2] No such file or directory: 'cfg.ini'"

(Contributed by Serhiy Storchaka in :gh:`109714`.)

* :ref:`Frame objects <frame-objects>` now support :mod:`weak references
<weakref>`. This allows associating extra data with active frames,
for example in debuggers, without keeping the frames (and everything
Expand Down Expand Up @@ -845,6 +862,15 @@ that may require changes to your code.
:exc:`TypeError`.
(Contributed by Serhiy Storchaka in :gh:`152587`.)

* The :attr:`~BaseException.args` attribute of :exc:`OSError` is no longer
truncated to two items when a file name is given, so that
``OSError(2, 'No such file or directory', 'cfg.ini').args`` is now the
whole 3-tuple. This makes an exception which carries a file name
survive pickling.
Passing more than five positional arguments to the constructor now
raises :exc:`TypeError` instead of being silently ignored.
(Contributed by Serhiy Storchaka in :gh:`109714`.)

* On Windows, seeking a pipe now fails instead of silently appearing to
succeed: :func:`os.lseek` and :meth:`~io.IOBase.seek` raise :exc:`OSError`,
and :meth:`~io.IOBase.seekable` returns ``False``. As a consequence,
Expand Down
6 changes: 6 additions & 0 deletions Include/internal/pycore_pyerrors.h
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,12 @@ PyAPI_FUNC(void) _PyErr_SetString(
PyObject *exception,
const char *string);

#ifdef MS_WINDOWS
/* Return the message for a Windows error code as a new reference,
or NULL with an exception set. */
extern PyObject* _PyErr_WindowsErrorMessage(unsigned long err);
#endif

/*
* Set an exception with the error message decoded from the current locale
* encoding (LC_CTYPE).
Expand Down
6 changes: 3 additions & 3 deletions Lib/test/test_capi/test_exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ def test_set_object(self):
# is superclass, so does not wrap
with self.assertRaises(PermissionError) as e:
_testcapi.exc_set_object(OSError, PermissionError(24))
self.assertEqual(e.exception.args, (24,))
self.assertEqual(e.exception.args, (errno.EACCES, 24))

class Meta(type):
def __subclasscheck__(cls, sub):
Expand Down Expand Up @@ -305,7 +305,7 @@ def test_setfromerrnowithfilename(self):
with self.assertRaises(FileNotFoundError) as e:
setfromerrnowithfilename(ENOENT, OSError, b'file')
self.assertEqual(e.exception.args,
(ENOENT, 'No such file or directory'))
(ENOENT, 'No such file or directory', 'file'))
self.assertEqual(e.exception.errno, ENOENT)
self.assertEqual(e.exception.filename, 'file')

Expand All @@ -325,7 +325,7 @@ def test_setfromerrnowithfilename(self):

with self.assertRaises(OSError) as e:
setfromerrnowithfilename(0, OSError, b'file')
self.assertEqual(e.exception.args, (0, 'Error'))
self.assertEqual(e.exception.args, (0, 'Error', 'file'))
self.assertEqual(e.exception.errno, 0)
self.assertEqual(e.exception.filename, 'file')

Expand Down
2 changes: 1 addition & 1 deletion Lib/test/test_concurrent_futures/test_as_completed.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ def test_correct_timeout_exception_msg(self):
with self.assertRaises(futures.TimeoutError) as cm:
list(futures.as_completed(futures_list, timeout=0))

self.assertEqual(str(cm.exception), '2 (of 4) futures unfinished')
self.assertIn('2 (of 4) futures unfinished', str(cm.exception))


create_executor_tests(globals(), AsCompletedTests)
Expand Down
117 changes: 117 additions & 0 deletions Lib/test/test_exception_hierarchy.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,22 @@ def test_errno_mapping(self):
e = OSError(errcode, "Some message")
self.assertIs(type(e), OSError, repr(e))

def _defaults(self):
# The first errno listed for a class is the one it defaults to.
defaults = {}
for errcode, exc in self._map.items():
defaults.setdefault(exc, errcode)
return defaults

def test_default_errno(self):
for exc, errcode in self._defaults().items():
with self.subTest(exc=exc.__name__):
self.assertEqual(exc.default_errno, errcode)
e = exc()
self.assertEqual(e.errno, errcode)
self.assertEqual(e.strerror, os.strerror(errcode))
self.assertEqual(e.args, (errcode, os.strerror(errcode)))

def test_try_except(self):
filename = "some_hopefully_non_existing_file"

Expand Down Expand Up @@ -136,6 +152,80 @@ def test_posix_error(self):
if os.name == "nt":
self.assertEqual(e.winerror, None)

def test_strerror_derived_from_errno(self):
e = FileNotFoundError()
self.assertEqual(e.errno, errno.ENOENT)
self.assertEqual(e.strerror, os.strerror(errno.ENOENT))
# an explicit strerror wins over the one derived from errno
e = FileNotFoundError('not found')
self.assertEqual(e.errno, errno.ENOENT)
self.assertEqual(e.strerror, 'not found')
self.assertEqual(e.args, (errno.ENOENT, 'not found'))
# ... including an explicit None
e = FileNotFoundError(errno.ENOENT, None)
self.assertIsNone(e.strerror)

@unittest.skipUnless(os.name == "nt", "Windows-specific test")
def test_strerror_derived_from_winerror(self):
# the message of the Windows error code is more specific than the one
# of the errno it is translated to
import ctypes
e = OSError(filename="foo.txt", winerror=183)
self.assertEqual(e.errno, EEXIST)
expected = ctypes.WinError(183).strerror.rstrip(" .")
self.assertEqual(e.strerror, expected)
# a code which the system has no message for
e = OSError(winerror=99999)
self.assertEqual(e.errno, errno.EINVAL)
self.assertEqual(e.strerror, "Windows Error 0x%x" % 99999)

def test_strerror_not_derived_from_bogus_errno(self):
for code in 2**31, -2**31-1, 2**1000, -2**1000, 'x':
with self.subTest(default_errno=code):
cls = type('E', (OSError,), {'default_errno': code})
e = cls()
self.assertEqual(e.errno, code)
self.assertIsNone(e.strerror)

@unittest.skipUnless(os.name == "nt", "Windows-specific test")
def test_strerror_not_derived_from_bogus_winerror(self):
# a winerror out of the C long range is rejected
for code in 2**31, -2**31-1, 2**1000, -2**1000:
with self.subTest(winerror=code):
self.assertRaises(OverflowError, OSError, winerror=code)
# a winerror which is not an integer is not translated at all
e = OSError(winerror='x')
self.assertIsNone(e.errno)
self.assertIsNone(e.strerror)
self.assertEqual(e.winerror, 'x')

def test_keyword_arguments(self):
e = FileNotFoundError(filename='foo.txt')
self.assertEqual(e.errno, errno.ENOENT)
self.assertEqual(e.strerror, os.strerror(errno.ENOENT))
self.assertEqual(e.filename, 'foo.txt')
self.assertIsNone(e.filename2)

e = OSError('cannot open', filename='foo.txt', filename2='bar.txt')
self.assertIsNone(e.errno)
self.assertEqual(e.strerror, 'cannot open')
self.assertEqual(e.filename, 'foo.txt')
self.assertEqual(e.filename2, 'bar.txt')

e = OSError(EEXIST, 'exists', filename='foo.txt')
self.assertEqual(e.errno, EEXIST)
self.assertEqual(e.filename, 'foo.txt')

def test_keyword_argument_errors(self):
# errno and strerror are positional-only
self.assertRaises(TypeError, OSError, errno=EEXIST)
self.assertRaises(TypeError, OSError, strerror='exists')
# and cannot be given twice
self.assertRaises(TypeError, OSError,
EEXIST, 'exists', 'foo.txt', filename='bar.txt')
# more than five arguments
self.assertRaises(TypeError, OSError, 1, 2, 3, 4, 5, 6)

@unittest.skipUnless(os.name == "nt", "Windows-specific test")
def test_errno_translation(self):
# ERROR_ALREADY_EXISTS (183) -> EEXIST
Expand All @@ -145,6 +235,13 @@ def test_errno_translation(self):
self.assertEqual(e.args[0], EEXIST)
self.assertEqual(e.strerror, "File already exists")
self.assertEqual(e.filename, "foo.txt")
# winerror can also be given by keyword
e = OSError("File already exists", filename="foo.txt", winerror=183)
self.assertEqual(e.winerror, 183)
self.assertEqual(e.errno, EEXIST)
self.assertEqual(e.args, (EEXIST, "File already exists", "foo.txt", 183))
self.assertEqual(e.strerror, "File already exists")
self.assertEqual(e.filename, "foo.txt")

def test_blockingioerror(self):
args = ("a", "b", "c", "d", "e")
Expand All @@ -162,6 +259,26 @@ def test_blockingioerror(self):
with self.assertRaises(AttributeError):
e.characters_written

# characters_written can also be given by keyword
e = BlockingIOError("would block", characters_written=3)
self.assertEqual(e.strerror, "would block")
self.assertEqual(e.characters_written, 3)
# including when the class is chosen by errno
e = OSError(errno.EAGAIN, "would block", characters_written=3)
self.assertIs(type(e), BlockingIOError)
self.assertEqual(e.characters_written, 3)
# but only for BlockingIOError
for cls in OSError, FileNotFoundError:
with self.subTest(cls=cls.__name__):
self.assertRaises(TypeError, cls, characters_written=3)
# and not together with a file name, which it is an alternative to
self.assertRaises(TypeError, BlockingIOError,
filename="foo.txt", characters_written=3)
self.assertRaises(TypeError, BlockingIOError,
errno.EAGAIN, "would block", 3, characters_written=3)
# and it is keyword-only
self.assertRaises(TypeError, BlockingIOError, 1, 2, 3, 4, 5, 6)


class ExplicitSubclassingTest(unittest.TestCase):

Expand Down
Loading
Loading