Skip to content
Draft
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
10 changes: 10 additions & 0 deletions Include/internal/pycore_pyerrors.h
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,16 @@ PyAPI_FUNC(void) _PyErr_SetString(
PyObject *exception,
const char *string);

/*
* Raise an OSError subclass with an explicit errno value, so that the
* resulting exception has a meaningful errno attribute. msg is used as
* strerror. Prefer PyErr_SetFromErrno() when the C errno is already set.
*/
PyAPI_FUNC(void) _PyErr_SetOSErrorWithMessage(
PyObject *exception,
int err,
const char *msg);

/*
* Set an exception with the error message decoded from the current locale
* encoding (LC_CTYPE).
Expand Down
4 changes: 2 additions & 2 deletions Lib/asyncio/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -480,7 +480,7 @@ async def wait_for(fut, timeout):
try:
return fut.result()
except exceptions.CancelledError as exc:
raise TimeoutError from exc
raise TimeoutError('timed out') from exc

async with timeouts.timeout(timeout):
return await fut
Expand Down Expand Up @@ -613,7 +613,7 @@ async def _wait_for_one(self, resolve=False):
f = await self._done.get()
if f is None:
# Dummy value from _handle_timeout().
raise exceptions.TimeoutError
raise exceptions.TimeoutError('timed out')
return f.result() if resolve else f


Expand Down
4 changes: 2 additions & 2 deletions Lib/asyncio/timeouts.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ async def __aexit__(
# Since there are no new cancel requests, we're
# handling this.
if issubclass(exc_type, exceptions.CancelledError):
raise TimeoutError from exc_val
raise TimeoutError('timed out') from exc_val
elif exc_val is not None:
self._insert_timeout_error(exc_val)
if isinstance(exc_val, ExceptionGroup):
Expand All @@ -134,7 +134,7 @@ def _on_timeout(self) -> None:
def _insert_timeout_error(exc_val: BaseException) -> None:
while exc_val.__context__ is not None:
if isinstance(exc_val.__context__, exceptions.CancelledError):
te = TimeoutError()
te = TimeoutError('timed out')
te.__context__ = te.__cause__ = exc_val.__context__
exc_val.__context__ = te
break
Expand Down
4 changes: 2 additions & 2 deletions Lib/concurrent/futures/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -460,7 +460,7 @@ def result(self, timeout=None):
elif self._state == FINISHED:
return self.__get_result()
else:
raise TimeoutError()
raise TimeoutError('timed out')
finally:
# Break a reference cycle with the exception in self._exception
self = None
Expand Down Expand Up @@ -496,7 +496,7 @@ def exception(self, timeout=None):
elif self._state == FINISHED:
return self._exception
else:
raise TimeoutError()
raise TimeoutError('timed out')

# The following methods should only be used by Executors and in tests.
def set_running_or_notify_cancel(self):
Expand Down
10 changes: 5 additions & 5 deletions Lib/importlib/resources/abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ def open_resource(self, resource: Text) -> BinaryIO:
# This deliberately raises FileNotFoundError instead of
# NotImplementedError so that if this method is accidentally called,
# it'll still do the right thing.
raise FileNotFoundError
raise FileNotFoundError('No such resource')

@abc.abstractmethod
def resource_path(self, resource: Text) -> Text:
Expand All @@ -47,20 +47,20 @@ def resource_path(self, resource: Text) -> Text:
# This deliberately raises FileNotFoundError instead of
# NotImplementedError so that if this method is accidentally called,
# it'll still do the right thing.
raise FileNotFoundError
raise FileNotFoundError('No such resource')

@abc.abstractmethod
def is_resource(self, path: Text) -> bool:
"""Return True if the named 'path' is a resource.

Files are resources, directories are not.
"""
raise FileNotFoundError
raise FileNotFoundError('No such resource')

@abc.abstractmethod
def contents(self) -> Iterable[str]:
"""Return an iterable of entries in `package`."""
raise FileNotFoundError
raise FileNotFoundError('No such resource')


class TraversalError(Exception):
Expand Down Expand Up @@ -180,7 +180,7 @@ def open_resource(self, resource: StrPath) -> BinaryIO:
return self.files().joinpath(resource).open('rb')

def resource_path(self, resource: Any) -> NoReturn:
raise FileNotFoundError(resource)
raise FileNotFoundError('No such resource', filename=resource)

def is_resource(self, path: StrPath) -> bool:
return self.files().joinpath(path).is_file()
Expand Down
11 changes: 8 additions & 3 deletions Lib/importlib/resources/readers.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,10 @@ def open_resource(self, resource):
try:
return super().open_resource(resource)
except KeyError as exc:
raise FileNotFoundError(exc.args[0])
if resource == exc.args[0]:
raise FileNotFoundError('No such resource', filename=resource)
else:
raise FileNotFoundError(exc.args[0])

def is_resource(self, path):
"""
Expand All @@ -73,8 +76,10 @@ def __init__(self, *paths):
if not self._paths:
message = 'MultiplexedPath must contain at least one path'
raise FileNotFoundError(message)
if not all(path.is_dir() for path in self._paths):
raise NotADirectoryError('MultiplexedPath only supports directories')
for path in self._paths:
if not path.is_dir():
message = 'MultiplexedPath only supports directories'
raise NotADirectoryError(message, filename=path)

def iterdir(self):
children = (child for path in self._paths for child in path.iterdir())
Expand Down
2 changes: 1 addition & 1 deletion Lib/logging/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ def fileConfig(fname, defaults=None, disable_existing_loggers=True, encoding=Non

if isinstance(fname, str):
if not os.path.exists(fname):
raise FileNotFoundError(f"{fname} doesn't exist")
raise FileNotFoundError('No such file', filename=fname)
elif not os.path.getsize(fname):
raise RuntimeError(f'{fname} is an empty file')

Expand Down
4 changes: 2 additions & 2 deletions Lib/multiprocessing/pool.py
Original file line number Diff line number Diff line change
Expand Up @@ -792,7 +792,7 @@ def wait(self, timeout=None):
def get(self, timeout=None):
self.wait(timeout)
if not self.ready():
raise TimeoutError
raise TimeoutError('timed out')
if self._success:
return self._value
else:
Expand Down Expand Up @@ -893,7 +893,7 @@ def next(self, timeout=None):
except IndexError:
if self._index == self._length:
self._stop_iterator()
raise TimeoutError from None
raise TimeoutError('timed out') from None

if self._buffersize_sema is not None:
self._buffersize_sema.release()
Expand Down
8 changes: 4 additions & 4 deletions Lib/shutil.py
Original file line number Diff line number Diff line change
Expand Up @@ -362,7 +362,8 @@ def copyfile(src, dst, *, follow_symlinks=True):
# Issue 43219, raise a less confusing exception
except IsADirectoryError as e:
if not os.path.exists(dst):
raise FileNotFoundError(f'Directory does not exist: {dst}') from e
raise FileNotFoundError('Directory does not exist',
filename=dst) from e
else:
raise

Expand Down Expand Up @@ -948,9 +949,8 @@ def move(src, dst, copy_function=copy2):
if (_is_immutable(src)
or (not os.access(src, os.W_OK) and os.listdir(src)
and sys.platform == 'darwin')):
raise PermissionError("Cannot move the non-empty directory "
"'%s': Lacking write permission to '%s'."
% (src, src))
raise PermissionError("Cannot move the non-empty directory: "
"Lacking write permission", filename=src)
copytree(src, real_dst, copy_function=copy_function,
symlinks=True)
rmtree(src)
Expand Down
8 changes: 5 additions & 3 deletions Lib/test/test_turtle.py
Original file line number Diff line number Diff line change
Expand Up @@ -510,7 +510,8 @@ def test_save_raises_if_parent_not_found(self) -> None:

with tempfile.TemporaryDirectory() as tmpdir:
parent = os.path.join(tmpdir, "unknown_parent")
msg = f"The directory '{parent}' does not exist. Cannot save to it"
msg = ("The directory does not exist. Cannot save to it: "
f"'{parent}'")

with self.assertRaisesRegex(FileNotFoundError, re.escape(msg)):
turtle.TurtleScreen.save(screen, os.path.join(parent, "a.ps"))
Expand All @@ -524,8 +525,9 @@ def test_save_raises_if_file_found(self) -> None:
f.write("some text")

msg = (
f"The file '{file_path}' already exists. To overwrite it use"
" the 'overwrite=True' argument of the save function."
"The file already exists. To overwrite it use"
" the 'overwrite=True' argument of the save function: "
f"'{file_path}'"
)
with self.assertRaisesRegex(FileExistsError, re.escape(msg)):
turtle.TurtleScreen.save(screen, file_path)
Expand Down
9 changes: 5 additions & 4 deletions Lib/turtle.py
Original file line number Diff line number Diff line change
Expand Up @@ -1558,13 +1558,14 @@ def save(self, filename, *, overwrite=False):
filename = Path(filename)
if not filename.parent.exists():
raise FileNotFoundError(
f"The directory '{filename.parent}' does not exist."
" Cannot save to it."
"The directory does not exist. Cannot save to it",
filename=str(filename.parent),
)
if not overwrite and filename.exists():
raise FileExistsError(
f"The file '{filename}' already exists. To overwrite it use"
" the 'overwrite=True' argument of the save function."
"The file already exists. To overwrite it use"
" the 'overwrite=True' argument of the save function",
filename=str(filename),
)
if (ext := filename.suffix) not in {".ps", ".eps"}:
raise ValueError(
Expand Down
4 changes: 2 additions & 2 deletions Lib/zipfile/_path/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -341,10 +341,10 @@ def open(self, mode='r', *args, pwd=None, **kwargs):
to io.TextIOWrapper().
"""
if self.is_dir():
raise IsADirectoryError(self)
raise IsADirectoryError(filename=self)
zip_mode = mode[0]
if zip_mode == 'r' and not self.exists():
raise FileNotFoundError(self)
raise FileNotFoundError('No such file', filename=self)
stream = self.root.open(self.at, zip_mode, pwd=pwd)
if 'b' in mode:
if args or kwargs:
Expand Down
11 changes: 11 additions & 0 deletions Python/errors.c
Original file line number Diff line number Diff line change
Expand Up @@ -933,6 +933,17 @@ PyErr_SetFromErrno(PyObject *exc)
return PyErr_SetFromErrnoWithFilenameObjects(exc, NULL, NULL);
}

void
_PyErr_SetOSErrorWithMessage(PyObject *exc, int err, const char *msg)
{
PyObject *args = Py_BuildValue("(is)", err, msg);
if (args == NULL) {
return;
}
PyErr_SetObject(exc, args);
Py_DECREF(args);
}

#ifdef MS_WINDOWS
/* Windows specific error code handling */
PyObject *PyErr_SetExcFromWindowsErrWithFilenameObject(
Expand Down
4 changes: 3 additions & 1 deletion Python/remote_debug.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ extern "C" {

#include "pyconfig.h"
#include "internal/pycore_ceval.h"
#include "internal/pycore_pyerrors.h"

#ifdef __linux__
# include <elf.h>
Expand Down Expand Up @@ -1577,7 +1578,8 @@ _Py_RemoteDebug_WriteRemoteMemory(proc_handle_t *handle, uintptr_t remote_addres
PyErr_SetString(PyExc_PermissionError, "Not enough permissions to write memory");
break;
case KERN_INVALID_ARGUMENT:
PyErr_SetString(PyExc_PermissionError, "Invalid argument to mach_vm_write");
_PyErr_SetOSErrorWithMessage(PyExc_PermissionError, EINVAL,
"Invalid argument to mach_vm_write");
break;
default:
PyErr_Format(PyExc_RuntimeError, "Unknown error writing memory: %d", (int)kr);
Expand Down
4 changes: 3 additions & 1 deletion Tools/build/generate-build-details.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import argparse
import collections
import errno
import importlib.machinery
import json
import os
Expand Down Expand Up @@ -93,7 +94,8 @@ def generate_data(schema_version: str) -> collections.defaultdict[str, Any]:
has_dynamic_library = hasattr(sys, 'dllhandle')
has_static_library = not has_dynamic_library
else:
raise NotADirectoryError(f'Unknown platform: {os.name}')
raise NotADirectoryError(errno.ENOTDIR,
f'Unknown platform: {os.name}')

# On POSIX, EXT_SUFFIX is set regardless if extension modules are supported
# or not, and on Windows older versions of CPython only set EXT_SUFFIX when
Expand Down
3 changes: 2 additions & 1 deletion Tools/c-analyzer/c_parser/source.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import contextlib
import errno
import os.path


Expand Down Expand Up @@ -28,7 +29,7 @@ def good_file(filename, alt=None):
yield filename
except Exception:
if not os.path.exists(filename):
raise FileNotFoundError(f'file not found: {filename}')
raise FileNotFoundError(errno.ENOENT, 'file not found', filename)
raise # re-raise


Expand Down
4 changes: 3 additions & 1 deletion Tools/clinic/libclinic/utils.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import collections
import dataclasses as dc
import errno
import enum
import hashlib
import os
Expand Down Expand Up @@ -64,7 +65,8 @@ def makedirs(self, dirname: str) -> None:
elif os.path.exists(dirname):
# Create nothing, but fail as os.makedirs() does, so that
# the caller can report an existing non-directory.
raise FileExistsError(dirname)
raise FileExistsError(errno.EEXIST, os.strerror(errno.EEXIST),
dirname)

def write(self, filename: str, new_contents: str) -> None:
if not self.dry_run:
Expand Down
Loading