Skip to content

GH-127807: pathlib ABCs: remove PathBase._unsupported_msg() #127855

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Dec 12, 2024
Merged
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
4 changes: 1 addition & 3 deletions Lib/pathlib/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@
operating systems.
"""

from pathlib._abc import *
from pathlib._local import *

__all__ = (_abc.__all__ +
_local.__all__)
__all__ = _local.__all__
35 changes: 11 additions & 24 deletions Lib/pathlib/_abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,6 @@
from pathlib._os import copyfileobj


__all__ = ["UnsupportedOperation"]


class UnsupportedOperation(NotImplementedError):
"""An exception that is raised when an unsupported operation is attempted.
"""
pass


@functools.cache
def _is_case_sensitive(parser):
return parser.normcase('Aa') == 'Aa'
Expand Down Expand Up @@ -353,8 +344,8 @@ class PathBase(PurePathBase):

This class provides dummy implementations for many methods that derived
classes can override selectively; the default implementations raise
UnsupportedOperation. The most basic methods, such as stat() and open(),
directly raise UnsupportedOperation; these basic methods are called by
NotImplementedError. The most basic methods, such as stat() and open(),
directly raise NotImplementedError; these basic methods are called by
other methods such as is_dir() and read_text().

The Path class derives this class to implement local filesystem paths.
Expand All @@ -363,16 +354,12 @@ class PathBase(PurePathBase):
"""
__slots__ = ()

@classmethod
def _unsupported_msg(cls, attribute):
return f"{cls.__name__}.{attribute} is unsupported"

def stat(self, *, follow_symlinks=True):
"""
Return the result of the stat() system call on this path, like
os.stat() does.
"""
raise UnsupportedOperation(self._unsupported_msg('stat()'))
raise NotImplementedError

# Convenience functions for querying the stat results

Expand Down Expand Up @@ -448,7 +435,7 @@ def open(self, mode='r', buffering=-1, encoding=None,
Open the file pointed to by this path and return a file object, as
the built-in open() function does.
"""
raise UnsupportedOperation(self._unsupported_msg('open()'))
raise NotImplementedError

def read_bytes(self):
"""
Expand Down Expand Up @@ -498,7 +485,7 @@ def iterdir(self):
The children are yielded in arbitrary order, and the
special entries '.' and '..' are not included.
"""
raise UnsupportedOperation(self._unsupported_msg('iterdir()'))
raise NotImplementedError

def _glob_selector(self, parts, case_sensitive, recurse_symlinks):
if case_sensitive is None:
Expand Down Expand Up @@ -575,14 +562,14 @@ def readlink(self):
"""
Return the path to which the symbolic link points.
"""
raise UnsupportedOperation(self._unsupported_msg('readlink()'))
raise NotImplementedError

def symlink_to(self, target, target_is_directory=False):
"""
Make this path a symlink pointing to the target path.
Note the order of arguments (link, target) is the reverse of os.symlink.
"""
raise UnsupportedOperation(self._unsupported_msg('symlink_to()'))
raise NotImplementedError

def _symlink_to_target_of(self, link):
"""
Expand All @@ -595,7 +582,7 @@ def mkdir(self, mode=0o777, parents=False, exist_ok=False):
"""
Create a new directory at this given path.
"""
raise UnsupportedOperation(self._unsupported_msg('mkdir()'))
raise NotImplementedError

# Metadata keys supported by this path type.
_readable_metadata = _writable_metadata = frozenset()
Expand All @@ -604,13 +591,13 @@ def _read_metadata(self, keys=None, *, follow_symlinks=True):
"""
Returns path metadata as a dict with string keys.
"""
raise UnsupportedOperation(self._unsupported_msg('_read_metadata()'))
raise NotImplementedError

def _write_metadata(self, metadata, *, follow_symlinks=True):
"""
Sets path metadata from the given dict with string keys.
"""
raise UnsupportedOperation(self._unsupported_msg('_write_metadata()'))
raise NotImplementedError

def _copy_metadata(self, target, *, follow_symlinks=True):
"""
Expand Down Expand Up @@ -687,7 +674,7 @@ def _delete(self):
"""
Delete this file or directory (including all sub-directories).
"""
raise UnsupportedOperation(self._unsupported_msg('_delete()'))
raise NotImplementedError

def move(self, target):
"""
Expand Down
37 changes: 29 additions & 8 deletions Lib/pathlib/_local.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,22 @@

from pathlib._os import (copyfile, file_metadata_keys, read_file_metadata,
write_file_metadata)
from pathlib._abc import UnsupportedOperation, PurePathBase, PathBase
from pathlib._abc import PurePathBase, PathBase


__all__ = [
"UnsupportedOperation",
"PurePath", "PurePosixPath", "PureWindowsPath",
"Path", "PosixPath", "WindowsPath",
]


class UnsupportedOperation(NotImplementedError):
"""An exception that is raised when an unsupported operation is attempted.
"""
pass


class _PathParents(Sequence):
"""This object provides sequence-like access to the logical ancestors
of a path. Don't try to construct it yourself."""
Expand Down Expand Up @@ -527,10 +534,6 @@ class Path(PathBase, PurePath):
"""
__slots__ = ()

@classmethod
def _unsupported_msg(cls, attribute):
return f"{cls.__name__}.{attribute} is unsupported on this system"

def __new__(cls, *args, **kwargs):
if cls is Path:
cls = WindowsPath if os.name == 'nt' else PosixPath
Expand Down Expand Up @@ -817,7 +820,8 @@ def owner(self, *, follow_symlinks=True):
"""
Return the login name of the file owner.
"""
raise UnsupportedOperation(self._unsupported_msg('owner()'))
f = f"{type(self).__name__}.owner()"
raise UnsupportedOperation(f"{f} is unsupported on this system")

if grp:
def group(self, *, follow_symlinks=True):
Expand All @@ -831,14 +835,22 @@ def group(self, *, follow_symlinks=True):
"""
Return the group name of the file gid.
"""
raise UnsupportedOperation(self._unsupported_msg('group()'))
f = f"{type(self).__name__}.group()"
raise UnsupportedOperation(f"{f} is unsupported on this system")

if hasattr(os, "readlink"):
def readlink(self):
"""
Return the path to which the symbolic link points.
"""
return self.with_segments(os.readlink(self))
else:
def readlink(self):
"""
Return the path to which the symbolic link points.
"""
f = f"{type(self).__name__}.readlink()"
raise UnsupportedOperation(f"{f} is unsupported on this system")

def touch(self, mode=0o666, exist_ok=True):
"""
Expand Down Expand Up @@ -989,6 +1001,14 @@ def symlink_to(self, target, target_is_directory=False):
Note the order of arguments (link, target) is the reverse of os.symlink.
"""
os.symlink(target, self, target_is_directory)
else:
def symlink_to(self, target, target_is_directory=False):
"""
Make this path a symlink pointing to the target path.
Note the order of arguments (link, target) is the reverse of os.symlink.
"""
f = f"{type(self).__name__}.symlink_to()"
raise UnsupportedOperation(f"{f} is unsupported on this system")

if os.name == 'nt':
def _symlink_to_target_of(self, link):
Expand All @@ -1013,7 +1033,8 @@ def hardlink_to(self, target):

Note the order of arguments (self, target) is the reverse of os.link's.
"""
raise UnsupportedOperation(self._unsupported_msg('hardlink_to()'))
f = f"{type(self).__name__}.hardlink_to()"
raise UnsupportedOperation(f"{f} is unsupported on this system")

def expanduser(self):
""" Return a new path with expanded ~ and ~user constructs
Expand Down
8 changes: 8 additions & 0 deletions Lib/test/test_pathlib/test_pathlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,14 @@ def needs_symlinks(fn):
_tests_needing_symlinks.add(fn.__name__)
return fn



class UnsupportedOperationTest(unittest.TestCase):
def test_is_notimplemented(self):
self.assertTrue(issubclass(pathlib.UnsupportedOperation, NotImplementedError))
self.assertTrue(isinstance(pathlib.UnsupportedOperation(), NotImplementedError))


#
# Tests for the pure classes.
#
Expand Down
12 changes: 3 additions & 9 deletions Lib/test/test_pathlib/test_pathlib_abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import stat
import unittest

from pathlib._abc import UnsupportedOperation, PurePathBase, PathBase
from pathlib._abc import PurePathBase, PathBase
from pathlib._types import Parser
import posixpath

Expand All @@ -27,11 +27,6 @@ def needs_windows(fn):
return fn


class UnsupportedOperationTest(unittest.TestCase):
def test_is_notimplemented(self):
self.assertTrue(issubclass(UnsupportedOperation, NotImplementedError))
self.assertTrue(isinstance(UnsupportedOperation(), NotImplementedError))

#
# Tests for the pure classes.
#
Expand Down Expand Up @@ -1294,10 +1289,9 @@ def test_is_absolute_windows(self):
class PathBaseTest(PurePathBaseTest):
cls = PathBase

def test_unsupported_operation(self):
P = self.cls
def test_not_implemented_error(self):
p = self.cls('')
e = UnsupportedOperation
e = NotImplementedError
self.assertRaises(e, p.stat)
self.assertRaises(e, p.exists)
self.assertRaises(e, p.is_dir)
Expand Down
Loading