Skip to content

Commit

Permalink
bpo-29688: document and test pathlib.Path.absolute() (GH-26153)
Browse files Browse the repository at this point in the history
Co-authored-by: Brett Cannon <brett@python.org>
Co-authored-by: Brian Helba <brian.helba@kitware.com>
  • Loading branch information
3 people committed Jan 28, 2022
1 parent 1f036ed commit 18cb2ef
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 11 deletions.
19 changes: 16 additions & 3 deletions Doc/library/pathlib.rst
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
Expand Up @@ -1043,24 +1043,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
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 @@ -2463,6 +2485,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 @@ -2628,6 +2661,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')

# 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
@@ -0,0 +1 @@
Document :meth:`pathlib.Path.absolute` (which has always existed).

0 comments on commit 18cb2ef

Please sign in to comment.