Skip to content

Commit

Permalink
Issue #19776: Add a expanduser() method on Path objects.
Browse files Browse the repository at this point in the history
Patch by Serhiy.
  • Loading branch information
pitrou committed Dec 30, 2014
1 parent 864d57c commit 8477ed6
Show file tree
Hide file tree
Showing 4 changed files with 180 additions and 3 deletions.
15 changes: 12 additions & 3 deletions Doc/library/pathlib.rst
Expand Up @@ -670,6 +670,18 @@ call fails (for example because the path doesn't exist):
symlink *points to* an existing file or directory.


.. method:: Path.expanduser()

Return a new path with expanded ``~`` and ``~user`` constructs,
as returned by :meth:`os.path.expanduser`::

>>> p = PosixPath('~/films/Monty Python')
>>> p.expanduser()
PosixPath('/home/eric/films/Monty Python')

.. versionadded:: 3.5


.. method:: Path.glob(pattern)

Glob the given *pattern* in the directory represented by this path,
Expand Down Expand Up @@ -1003,7 +1015,4 @@ call fails (for example because the path doesn't exist):
>>> p.read_text()
'Text file contents'

An existing file of the same name is overwritten. The optional parameters
have the same meaning as in :func:`open`.

.. versionadded:: 3.5
56 changes: 56 additions & 0 deletions Lib/pathlib.py
Expand Up @@ -221,6 +221,36 @@ def make_uri(self, path):
# It's a path on a network drive => 'file://host/share/a/b'
return 'file:' + urlquote_from_bytes(path.as_posix().encode('utf-8'))

def gethomedir(self, username):
if 'HOME' in os.environ:
userhome = os.environ['HOME']
elif 'USERPROFILE' in os.environ:
userhome = os.environ['USERPROFILE']
elif 'HOMEPATH' in os.environ:
try:
drv = os.environ['HOMEDRIVE']
except KeyError:
drv = ''
userhome = drv + os.environ['HOMEPATH']
else:
raise RuntimeError("Can't determine home directory")

if username:
# Try to guess user home directory. By default all users
# directories are located in the same place and are named by
# corresponding usernames. If current user home directory points
# to nonstandard place, this guess is likely wrong.
if os.environ['USERNAME'] != username:
drv, root, parts = self.parse_parts((userhome,))
if parts[-1] != os.environ['USERNAME']:
raise RuntimeError("Can't determine home directory "
"for %r" % username)
parts[-1] = username
if drv or root:
userhome = drv + root + self.join(parts[1:])
else:
userhome = self.join(parts)
return userhome

class _PosixFlavour(_Flavour):
sep = '/'
Expand Down Expand Up @@ -304,6 +334,21 @@ def make_uri(self, path):
bpath = bytes(path)
return 'file://' + urlquote_from_bytes(bpath)

def gethomedir(self, username):
if not username:
try:
return os.environ['HOME']
except KeyError:
import pwd
return pwd.getpwuid(os.getuid()).pw_dir
else:
import pwd
try:
return pwd.getpwnam(username).pw_dir
except KeyError:
raise RuntimeError("Can't determine home directory "
"for %r" % username)


_windows_flavour = _WindowsFlavour()
_posix_flavour = _PosixFlavour()
Expand Down Expand Up @@ -1333,6 +1378,17 @@ def is_socket(self):
# (see https://bitbucket.org/pitrou/pathlib/issue/12/)
return False

def expanduser(self):
""" Return a new path with expanded ~ and ~user constructs
(as returned by os.path.expanduser)
"""
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


class PosixPath(Path, PurePosixPath):
__slots__ = ()
Expand Down
110 changes: 110 additions & 0 deletions Lib/test/test_pathlib.py
Expand Up @@ -1286,6 +1286,19 @@ def test_empty_path(self):
p = self.cls('')
self.assertEqual(p.stat(), os.stat('.'))

def test_expanduser_common(self):
P = self.cls
p = P('~')
self.assertEqual(p.expanduser(), P(os.path.expanduser('~')))
p = P('foo')
self.assertEqual(p.expanduser(), p)
p = P('/~')
self.assertEqual(p.expanduser(), p)
p = P('../~')
self.assertEqual(p.expanduser(), p)
p = P(P('').absolute().anchor) / '~'
self.assertEqual(p.expanduser(), p)

def test_exists(self):
P = self.cls
p = P(BASE)
Expand Down Expand Up @@ -1959,6 +1972,48 @@ def test_rglob(self):
self.assertEqual(given, expect)
self.assertEqual(set(p.rglob("FILEd*")), set())

def test_expanduser(self):
P = self.cls
support.import_module('pwd')
import pwd
pwdent = pwd.getpwuid(os.getuid())
username = pwdent.pw_name
userhome = pwdent.pw_dir.rstrip('/')
# find arbitrary different user (if exists)
for pwdent in pwd.getpwall():
othername = pwdent.pw_name
otherhome = pwdent.pw_dir.rstrip('/')
if othername != username:
break

p1 = P('~/Documents')
p2 = P('~' + username + '/Documents')
p3 = P('~' + othername + '/Documents')
p4 = P('../~' + username + '/Documents')
p5 = P('/~' + username + '/Documents')
p6 = P('')
p7 = P('~fakeuser/Documents')

with support.EnvironmentVarGuard() as env:
env.pop('HOME', None)

self.assertEqual(p1.expanduser(), P(userhome) / 'Documents')
self.assertEqual(p2.expanduser(), P(userhome) / 'Documents')
self.assertEqual(p3.expanduser(), P(otherhome) / 'Documents')
self.assertEqual(p4.expanduser(), p4)
self.assertEqual(p5.expanduser(), p5)
self.assertEqual(p6.expanduser(), p6)
self.assertRaises(RuntimeError, p7.expanduser)

env['HOME'] = '/tmp'
self.assertEqual(p1.expanduser(), P('/tmp/Documents'))
self.assertEqual(p2.expanduser(), P(userhome) / 'Documents')
self.assertEqual(p3.expanduser(), P(otherhome) / 'Documents')
self.assertEqual(p4.expanduser(), p4)
self.assertEqual(p5.expanduser(), p5)
self.assertEqual(p6.expanduser(), p6)
self.assertRaises(RuntimeError, p7.expanduser)


@only_nt
class WindowsPathTest(_BasePathTest, unittest.TestCase):
Expand All @@ -1974,6 +2029,61 @@ def test_rglob(self):
p = P(BASE, "dirC")
self.assertEqual(set(p.rglob("FILEd")), { P(BASE, "dirC/dirD/fileD") })

def test_expanduser(self):
P = self.cls
with support.EnvironmentVarGuard() as env:
env.pop('HOME', None)
env.pop('USERPROFILE', None)
env.pop('HOMEPATH', None)
env.pop('HOMEDRIVE', None)
env['USERNAME'] = 'alice'

# test that the path returns unchanged
p1 = P('~/My Documents')
p2 = P('~alice/My Documents')
p3 = P('~bob/My Documents')
p4 = P('/~/My Documents')
p5 = P('d:~/My Documents')
p6 = P('')
self.assertRaises(RuntimeError, p1.expanduser)
self.assertRaises(RuntimeError, p2.expanduser)
self.assertRaises(RuntimeError, p3.expanduser)
self.assertEqual(p4.expanduser(), p4)
self.assertEqual(p5.expanduser(), p5)
self.assertEqual(p6.expanduser(), p6)

def check():
env.pop('USERNAME', None)
self.assertEqual(p1.expanduser(),
P('C:/Users/alice/My Documents'))
self.assertRaises(KeyError, p2.expanduser)
env['USERNAME'] = 'alice'
self.assertEqual(p2.expanduser(),
P('C:/Users/alice/My Documents'))
self.assertEqual(p3.expanduser(),
P('C:/Users/bob/My Documents'))
self.assertEqual(p4.expanduser(), p4)
self.assertEqual(p5.expanduser(), p5)
self.assertEqual(p6.expanduser(), p6)

# test the first lookup key in the env vars
env['HOME'] = 'C:\\Users\\alice'
check()

# test that HOMEPATH is available instead
env.pop('HOME', None)
env['HOMEPATH'] = 'C:\\Users\\alice'
check()

env['HOMEDRIVE'] = 'C:\\'
env['HOMEPATH'] = 'Users\\alice'
check()

env.pop('HOMEDRIVE', None)
env.pop('HOMEPATH', None)
env['USERPROFILE'] = 'C:\\Users\\alice'
check()


if __name__ == "__main__":
unittest.main()
2 changes: 2 additions & 0 deletions Misc/NEWS
Expand Up @@ -196,6 +196,8 @@ Core and Builtins
Library
-------

- Issue #19776: Add a expanduser() method on Path objects.

- Issue #23112: Fix SimpleHTTPServer to correctly carry the query string and
fragment when it redirects to add a trailing slash.

Expand Down

0 comments on commit 8477ed6

Please sign in to comment.