Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
081cbc2
bpo-41109: separate object creation by Path and PurePath member varia…
websurfer5 Aug 16, 2020
0e4d53e
bpo-41109: Windows PurePath tests
websurfer5 Aug 17, 2020
53643f4
bpo-41109: use correct class in PureWindowsSubclassNewAndInitTest
websurfer5 Aug 17, 2020
c418958
bpo-41109: fix comment
websurfer5 Aug 17, 2020
34683c4
bpo-41109: fix class names and comments
websurfer5 Aug 17, 2020
19e0a40
bpo-41109: WindowsPath test suite
websurfer5 Aug 17, 2020
1853c6d
bpo-41109: add __init__() to Path and PurePath
websurfer5 Aug 17, 2020
17c141c
bpo-41109: remove extraneous **kwargs parameter from Path.__init__()
websurfer5 Aug 17, 2020
505cab0
bpo-41109: remove extraneous whitespace
websurfer5 Aug 17, 2020
c47bf06
bpo-41109: remove double-initialization in Path.__new__()
websurfer5 Aug 18, 2020
6607e77
bpo-41109: no longer need to call _init() from PurePath.__init__()
websurfer5 Aug 18, 2020
cf6afb0
bpo-41109: reorganize tests using a common base class for common tests
websurfer5 Aug 18, 2020
d25fdd7
bpo-41109: fix readlink test for Windows
websurfer5 Aug 18, 2020
4be7b7a
bpo-41109: cleanup test_resolve
websurfer5 Aug 18, 2020
1660fa6
bpo-41109: cleanup test_glob
websurfer5 Aug 18, 2020
2a27016
bpo-41109: test all initialization cases
websurfer5 Aug 18, 2020
4014a16
bpo-41109: news blurb
websurfer5 Aug 19, 2020
31b449e
bpo-41109: What's New entry and better news blurb
websurfer5 Aug 19, 2020
ebc8371
bpo-4119: Merge branch 'master' of github.com:python/cpython into fix…
websurfer5 Aug 19, 2020
86bb25b
bpo-41109: improve What's New and News entries
websurfer5 Aug 19, 2020
b2c62a6
bpo-41109: simplify _new_from_parts() and _new_from_parsed_parts() in…
websurfer5 Aug 19, 2020
6f363b9
Merge branch 'master' of github.com:python/cpython into fix-issue-41109
websurfer5 Aug 23, 2020
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
Next Next commit
bpo-41109: separate object creation by Path and PurePath member varia…
…bles and properties from __new__() object creation
  • Loading branch information
websurfer5 committed Aug 16, 2020
commit 081cbc25153e54ecbc260ab8b5c218564b7ec585
50 changes: 36 additions & 14 deletions Lib/pathlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -629,7 +629,7 @@ def __len__(self):
def __getitem__(self, idx):
if idx < 0 or idx >= len(self):
raise IndexError(idx)
return self._pathcls._from_parsed_parts(self._drv, self._root,
return self._pathcls()._new_from_parsed_parts(self._drv, self._root,
self._parts[:-idx - 1])

def __repr__(self):
Expand Down Expand Up @@ -700,6 +700,9 @@ def _from_parts(cls, args, init=True):

@classmethod
def _from_parsed_parts(cls, drv, root, parts, init=True):
import warnings
warnings.warn('_from_parsed_parts() is deprecated, use _new_from_parsed_parts() instead',
DeprecationWarning, 2)
self = object.__new__(cls)
self._drv = drv
self._root = root
Expand All @@ -708,6 +711,25 @@ def _from_parsed_parts(cls, drv, root, parts, init=True):
self._init()
return self

def _new_from_parts(self, args, init=True):
obj = type(self)()
drv, root, parts = obj._parse_args(args)
obj._drv = drv
obj._root = root
obj._parts = parts
if init:
obj._init()
return obj

def _new_from_parsed_parts(self, drv, root, parts, init=True):
obj = type(self)()
obj._drv = drv
obj._root = root
obj._parts = parts
if init:
obj._init()
return obj

@classmethod
def _format_parsed_parts(cls, drv, root, parts):
if drv or root:
Expand All @@ -723,7 +745,7 @@ def _make_child(self, args):
drv, root, parts = self._parse_args(args)
drv, root, parts = self._flavour.join_parsed_parts(
self._drv, self._root, self._parts, drv, root, parts)
return self._from_parsed_parts(drv, root, parts)
return self._new_from_parsed_parts(drv, root, parts)

def __str__(self):
"""Return the string representation of the path, suitable for
Expand Down Expand Up @@ -867,7 +889,7 @@ def with_name(self, name):
if (not name or name[-1] in [self._flavour.sep, self._flavour.altsep]
or drv or root or len(parts) != 1):
raise ValueError("Invalid name %r" % (name))
return self._from_parsed_parts(self._drv, self._root,
return self._new_from_parsed_parts(self._drv, self._root,
self._parts[:-1] + [name])

def with_stem(self, stem):
Expand All @@ -892,8 +914,8 @@ def with_suffix(self, suffix):
name = name + suffix
else:
name = name[:-len(old_suffix)] + suffix
return self._from_parsed_parts(self._drv, self._root,
self._parts[:-1] + [name])
return self._new_from_parsed_parts(self._drv, self._root,
self._parts[:-1] + [name])

def relative_to(self, *other):
"""Return the relative path to another path identified by the passed
Expand Down Expand Up @@ -925,8 +947,8 @@ def relative_to(self, *other):
raise ValueError("{!r} is not in the subpath of {!r}"
" OR one path is relative and the other is absolute."
.format(str(self), str(formatted)))
return self._from_parsed_parts('', root if n == 1 else '',
abs_parts[n:])
return self._new_from_parsed_parts('', root if n == 1 else '',
abs_parts[n:])

def is_relative_to(self, *other):
"""Return True if the path is relative to another path or False.
Expand Down Expand Up @@ -965,7 +987,7 @@ def __truediv__(self, key):

def __rtruediv__(self, key):
try:
return self._from_parts([key] + self._parts)
return self._new_from_parts([key] + self._parts)
except TypeError:
return NotImplemented

Expand All @@ -977,7 +999,7 @@ def parent(self):
parts = self._parts
if len(parts) == 1 and (drv or root):
return self
return self._from_parsed_parts(drv, root, parts[:-1])
return self._new_from_parsed_parts(drv, root, parts[:-1])

@property
def parents(self):
Expand Down Expand Up @@ -1085,7 +1107,7 @@ def _make_child_relpath(self, part):
# This is an optimization used for dir walking. `part` must be
# a single part relative to this path.
parts = self._parts + [part]
return self._from_parsed_parts(self._drv, self._root, parts)
return self._new_from_parsed_parts(self._drv, self._root, parts)

def __enter__(self):
return self
Expand Down Expand Up @@ -1188,7 +1210,7 @@ def absolute(self):
return self
# FIXME this must defer to the specific flavour (and, under Windows,
# use nt._getfullpathname())
obj = self._from_parts([os.getcwd()] + self._parts, init=False)
obj = self._new_from_parts([os.getcwd()] + self._parts, init=False)
obj._init(template=self)
return obj

Expand All @@ -1206,7 +1228,7 @@ def resolve(self, strict=False):
s = str(self.absolute())
# Now we have no symlinks in the path, it's safe to normalize it.
normed = self._flavour.pathmod.normpath(s)
obj = self._from_parts((normed,), init=False)
obj = self._new_from_parts((normed,), init=False)
obj._init(template=self)
return obj

Expand Down Expand Up @@ -1276,7 +1298,7 @@ def readlink(self):
Return the path to which the symbolic link points.
"""
path = self._accessor.readlink(self)
obj = self._from_parts((path,), init=False)
obj = self._new_from_parts((path,), init=False)
obj._init(template=self)
return obj

Expand Down Expand Up @@ -1541,7 +1563,7 @@ def expanduser(self):
if (not (self._drv or self._root) and
self._parts and self._parts[0][:1] == '~'):
homedir = self._flavour.gethomedir(self._parts[0][1:])
return self._from_parts([homedir] + self._parts[1:])
return self._new_from_parts([homedir] + self._parts[1:])

return self

Expand Down
151 changes: 151 additions & 0 deletions Lib/test/test_pathlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -2585,5 +2585,156 @@ def test_rtruediv(self):
10 / pathlib.PurePath("test")


class PurePosixSubclassNewAndInitTest(unittest.TestCase):
"""
Test that the __init__() and __new__() functions of subclasses
of PurePosixPath get called when PosixPath functions
and properties instantiate new objects of the subclass.
"""
cls = pathlib.PurePosixPath

class ASubclass(cls):
new_called = False
init_called = False

def __new__(cls, *args, **kwargs):
cls.new_called = True
return super().__new__(cls, *args, **kwargs)

def __init__(self, *args, **kwargs):
self.init_called = True
super().__init__()

def validate_object(self, path, value):
self.assertIs(type(path), self.ASubclass)
self.assertTrue(path.new_called)
self.assertTrue(path.init_called)
self.assertEqual(str(path), value)

def test_class_initialization(self):
self.validate_object(self.ASubclass('a/b/c.foo'), 'a/b/c.foo')

def test_joinpath(self):
self.validate_object(self.ASubclass('a/b/c.foo').parent.joinpath('d/e'),
'a/b/d/e')

def test_parent(self):
self.validate_object(self.ASubclass('a/b/c.foo').parent, 'a/b')

def test_parents(self):
path = self.ASubclass('a/b/c.foo')
self.validate_object(path.parents[0], 'a/b')
self.validate_object(path.parents[1], 'a')

def test_relative_to(self):
self.validate_object(self.ASubclass('a/b/c.foo').relative_to('a'),
'b/c.foo')

def test_rtruediv(self):
self.validate_object('left' / self.ASubclass('test'), 'left/test')

def test_truediv(self):
path = self.ASubclass('a/b/c.foo')
self.validate_object(path.parent / 'd' / path, 'a/b/d/a/b/c.foo')

def test_with_name(self):
self.validate_object(self.ASubclass('a/b/c.foo').with_name('bar'),
'a/b/bar')

def test_with_suffix(self):
self.validate_object(self.ASubclass('a/b/c.foo').with_suffix('.bar'),
'a/b/c.bar')


@only_posix
class PosixPathSubclassNewAndInitTest(unittest.TestCase):
"""
Test that the __init__() and __new__() functions of subclasses
of PosixPath get called when PosixPath functions
and properties instantiate new objects of the subclass.
"""
cls = pathlib.PosixPath

class ASubclass(cls):
new_called = False
init_called = False

def __new__(cls, *args, **kwargs):
cls.new_called = True
return super().__new__(cls, *args, **kwargs)

def __init__(self, *args, **kwargs):
self.init_called = True
super().__init__()

def validate_object(self, path, value):
self.assertIs(type(path), self.ASubclass)
self.assertTrue(path.new_called)
self.assertTrue(path.init_called)
self.assertEqual(str(path), value)

def test_class_initialization(self):
self.validate_object(self.ASubclass('a/b/c.foo'), 'a/b/c.foo')

def test_absolute(self):
relpath = 'a/b/c.foo'
self.validate_object(self.ASubclass(relpath).absolute(),
join(os.getcwd(), relpath))

def test_expanduser(self):
import_helper.import_module('pwd')
import pwd
pwdent = pwd.getpwuid(os.getuid())
userhome = pwdent.pw_dir.rstrip('/') or '/'
path = self.ASubclass('~/Documents')
with os_helper.EnvironmentVarGuard() as env:
env.pop('HOME', None)
self.validate_object(path.expanduser(), join(userhome, 'Documents'))

def test_glob_rglob_iterdir(self):
# create a file in a temp directory
d = tempfile.mkdtemp()
filename = 'fileA'
with open(join(d, filename), 'wb') as f:
f.write(b"this is file A\n")

# create an Asubclass object for testing
path = self.ASubclass(d)
self.validate_object(path, d)

# verify __new__ and __init__ are called
# in the subclass for each created instance
# and the subclass value is correct
cases = [(path.glob, 'f*', join(d, filename)), # _WildcardSelector
(path.glob, 'fileA', join(d, filename)), # _PreciseSelector
(path.glob, '**', d), # _RecursiveWildcardSelector
(path.rglob, 'f*', join(d, filename)),
(path.iterdir, None, join(d, filename))]

for func, param, value in cases:
n = 0
for name in func() if param is None else func(param):
self.validate_object(name, value)
n += 1
self.assertEqual(n, 1)

def test_resolve(self):
relpath = 'a/b/c.foo'
self.validate_object(self.ASubclass(relpath).resolve(),
join(os.getcwd(), relpath))

@os_helper.skip_unless_symlink
def test_readlink(self):
# create a file in a temp directory
d = tempfile.mkdtemp()
with open(join(d, 'fileA'), 'wb') as f:
f.write(b"this is file A\n")
# create a symlink to the file
os.symlink(join(d, 'fileA'), join(d, 'linkA'))

path = (self.ASubclass(d) / 'linkA').readlink()
self.validate_object(path, join(d, 'fileA'))


if __name__ == "__main__":
unittest.main()