Skip to content
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

bpo-29688: document and test pathlib.Path.absolute(). #26153

Merged
19 changes: 16 additions & 3 deletions Doc/library/pathlib.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1052,6 +1052,18 @@ call fails (for example because the path doesn't exist).
Added return value, return the new Path instance.


.. method:: Path.absolute()

Make the path absolute, without normalization or resolving symlinks.
Returns a new path object::

>>> p = Path('tests')
>>> p
PosixPath('tests')
>>> p.absolute()
PosixPath('/home/antoine/pathlib/tests')


.. method:: Path.resolve(strict=False)

Make the path absolute, resolving any symlinks. A new path object is
Expand Down Expand Up @@ -1239,13 +1251,14 @@ Below is a table mapping various :mod:`os` functions to their corresponding

Not all pairs of functions/methods below are equivalent. Some of them,
despite having some overlapping use-cases, have different semantics. They
include :func:`os.path.abspath` and :meth:`Path.resolve`,
include :func:`os.path.abspath` and :meth:`Path.absolute`,
:func:`os.path.relpath` and :meth:`PurePath.relative_to`.

==================================== ==============================
:mod:`os` and :mod:`os.path` :mod:`pathlib`
==================================== ==============================
:func:`os.path.abspath` :meth:`Path.resolve` [#]_
:func:`os.path.abspath` :meth:`Path.absolute` [#]_
:func:`os.path.realpath` :meth:`Path.resolve`
:func:`os.chmod` :meth:`Path.chmod`
:func:`os.mkdir` :meth:`Path.mkdir`
:func:`os.makedirs` :meth:`Path.mkdir`
Expand Down Expand Up @@ -1278,5 +1291,5 @@ Below is a table mapping various :mod:`os` functions to their corresponding

.. rubric:: Footnotes

.. [#] :func:`os.path.abspath` does not resolve symbolic links while :meth:`Path.resolve` does.
.. [#] :func:`os.path.abspath` normalizes the resulting path, which may change its meaning in the presence of symlinks, while :meth:`Path.absolute` does not.
.. [#] :meth:`Path.relative_to` requires ``self`` to be the subpath of the argument, but :func:`os.path.relpath` does not.
11 changes: 3 additions & 8 deletions Lib/pathlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -1046,24 +1046,19 @@ def rglob(self, pattern):
yield p

def absolute(self):
"""Return an absolute version of this path. This function works
even if the path doesn't point to anything.
"""Return an absolute version of this path by prepending the current
working directory. No normalization or symlink resolution is performed.

No normalization is done, i.e. all '.' and '..' will be kept along.
Use resolve() to get the canonical path to a file.
"""
# XXX untested yet!
if self.is_absolute():
return self
# FIXME this must defer to the specific flavour (and, under Windows,
# use nt._getfullpathname())
return self._from_parts([self._accessor.getcwd()] + self._parts)

def resolve(self, strict=False):
"""
Make the path absolute, resolving all symlinks on the way and also
normalizing it (for example turning slashes into backslashes under
Windows).
normalizing it.
"""

def check_eloop(e):
Expand Down
58 changes: 58 additions & 0 deletions Lib/test/test_pathlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -1456,6 +1456,28 @@ def test_cwd(self):
p = self.cls.cwd()
self._test_cwd(p)

def test_absolute_common(self):
P = self.cls

with mock.patch("pathlib._normal_accessor.getcwd") as getcwd:
getcwd.return_value = BASE

# Simple relative paths.
self.assertEqual(str(P().absolute()), BASE)
self.assertEqual(str(P('.').absolute()), BASE)
self.assertEqual(str(P('a').absolute()), os.path.join(BASE, 'a'))
self.assertEqual(str(P('a', 'b', 'c').absolute()), os.path.join(BASE, 'a', 'b', 'c'))

# Symlinks should not be resolved.
self.assertEqual(str(P('linkB', 'fileB').absolute()), os.path.join(BASE, 'linkB', 'fileB'))
self.assertEqual(str(P('brokenLink').absolute()), os.path.join(BASE, 'brokenLink'))
self.assertEqual(str(P('brokenLinkLoop').absolute()), os.path.join(BASE, 'brokenLinkLoop'))

# '..' entries should be preserved and not normalised.
self.assertEqual(str(P('..').absolute()), os.path.join(BASE, '..'))
self.assertEqual(str(P('a', '..').absolute()), os.path.join(BASE, 'a', '..'))
self.assertEqual(str(P('..', 'b').absolute()), os.path.join(BASE, '..', 'b'))

def _test_home(self, p):
q = self.cls(os.path.expanduser('~'))
self.assertEqual(p, q)
Expand Down Expand Up @@ -2457,6 +2479,17 @@ def test_glob_empty_pattern(self):
class PosixPathTest(_BasePathTest, unittest.TestCase):
cls = pathlib.PosixPath

def test_absolute(self):
P = self.cls
self.assertEqual(str(P('/').absolute()), '/')
self.assertEqual(str(P('/a').absolute()), '/a')
self.assertEqual(str(P('/a/b').absolute()), '/a/b')

# '//'-prefixed absolute path (supported by POSIX).
self.assertEqual(str(P('//').absolute()), '//')
self.assertEqual(str(P('//a').absolute()), '//a')
self.assertEqual(str(P('//a/b').absolute()), '//a/b')

def _check_symlink_loop(self, *args, strict=True):
path = self.cls(*args)
with self.assertRaises(RuntimeError):
Expand Down Expand Up @@ -2622,6 +2655,31 @@ def test_handling_bad_descriptor(self):
class WindowsPathTest(_BasePathTest, unittest.TestCase):
cls = pathlib.WindowsPath

def test_absolute(self):
P = self.cls

# Simple absolute paths.
self.assertEqual(str(P('c:\\').absolute()), 'c:\\')
self.assertEqual(str(P('c:\\a').absolute()), 'c:\\a')
self.assertEqual(str(P('c:\\a\\b').absolute()), 'c:\\a\\b')

# UNC absolute paths.
share = '\\\\server\\share\\'
self.assertEqual(str(P(share).absolute()), share)
self.assertEqual(str(P(share + 'a').absolute()), share + 'a')
self.assertEqual(str(P(share + 'a\\b').absolute()), share + 'a\\b')
Comment on lines +2662 to +2670
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
self.assertEqual(str(P('c:\\').absolute()), 'c:\\')
self.assertEqual(str(P('c:\\a').absolute()), 'c:\\a')
self.assertEqual(str(P('c:\\a\\b').absolute()), 'c:\\a\\b')
# UNC absolute paths.
share = '\\\\server\\share\\'
self.assertEqual(str(P(share).absolute()), share)
self.assertEqual(str(P(share + 'a').absolute()), share + 'a')
self.assertEqual(str(P(share + 'a\\b').absolute()), share + 'a\\b')
self.assertEqual(str(P(r'c:\').absolute()), r'c:\')
self.assertEqual(str(P(r'c:\a').absolute()), r'c:\a')
self.assertEqual(str(P(r'c:\a\b').absolute()), r'c:\a\b')
# UNC absolute paths.
share = '\\\\server\\share\\'
self.assertEqual(str(P(share).absolute()), share)
self.assertEqual(str(P(share + 'a').absolute()), share + 'a')
self.assertEqual(str(P(share + r'a\b').absolute()), share + r'a\b')


# UNC relative paths.
with mock.patch("pathlib._normal_accessor.getcwd") as getcwd:
getcwd.return_value = share

self.assertEqual(str(P().absolute()), share)
self.assertEqual(str(P('.').absolute()), share)
self.assertEqual(str(P('a').absolute()), os.path.join(share, 'a'))
self.assertEqual(str(P('a', 'b', 'c').absolute()),
os.path.join(share, 'a', 'b', 'c'))


def test_glob(self):
P = self.cls
p = P(BASE)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Document :meth:`pathlib.Path.absolute` (which has always existed).