Commit 568be632 authored by Brett Cannon's avatar Brett Cannon

Issue #27186: Add os.PathLike support to pathlib.

This adds support both to pathlib.PurePath's constructor as well as
implementing __fspath__(). This removes the provisional status for
pathlib.

Initial patch by Dusty Phillips.
parent f41b82fb
...@@ -31,12 +31,6 @@ Pure paths are useful in some special cases; for example: ...@@ -31,12 +31,6 @@ Pure paths are useful in some special cases; for example:
accessing the OS. In this case, instantiating one of the pure classes may be accessing the OS. In this case, instantiating one of the pure classes may be
useful since those simply don't have any OS-accessing operations. useful since those simply don't have any OS-accessing operations.
.. note::
This module has been included in the standard library on a
:term:`provisional basis <provisional package>`. Backwards incompatible
changes (up to and including removal of the package) may occur if deemed
necessary by the core developers.
.. seealso:: .. seealso::
:pep:`428`: The pathlib module -- object-oriented filesystem paths. :pep:`428`: The pathlib module -- object-oriented filesystem paths.
...@@ -107,7 +101,8 @@ we also call *flavours*: ...@@ -107,7 +101,8 @@ we also call *flavours*:
PurePosixPath('setup.py') PurePosixPath('setup.py')
Each element of *pathsegments* can be either a string representing a Each element of *pathsegments* can be either a string representing a
path segment, or another path object:: path segment, an object implementing the :class:`os.PathLike` interface
which returns a string, or another path object::
>>> PurePath('foo', 'some/path', 'bar') >>> PurePath('foo', 'some/path', 'bar')
PurePosixPath('foo/some/path/bar') PurePosixPath('foo/some/path/bar')
...@@ -148,6 +143,12 @@ we also call *flavours*: ...@@ -148,6 +143,12 @@ we also call *flavours*:
to ``PurePosixPath('bar')``, which is wrong if ``foo`` is a symbolic link to ``PurePosixPath('bar')``, which is wrong if ``foo`` is a symbolic link
to another directory) to another directory)
Pure path objects implement the :class:`os.PathLike` interface, allowing them
to be used anywhere the interface is accepted.
.. versionchanged:: 3.6
Added support for the :class:`os.PathLike` interface.
.. class:: PurePosixPath(*pathsegments) .. class:: PurePosixPath(*pathsegments)
A subclass of :class:`PurePath`, this path flavour represents non-Windows A subclass of :class:`PurePath`, this path flavour represents non-Windows
...@@ -212,6 +213,14 @@ The slash operator helps create child paths, similarly to :func:`os.path.join`:: ...@@ -212,6 +213,14 @@ The slash operator helps create child paths, similarly to :func:`os.path.join`::
>>> '/usr' / q >>> '/usr' / q
PurePosixPath('/usr/bin') PurePosixPath('/usr/bin')
A path object can be used anywhere an object implementing :class:`os.PathLike`
is accepted::
>>> import os
>>> p = PurePath('/etc')
>>> os.fspath(p)
'/etc'
The string representation of a path is the raw filesystem path itself The string representation of a path is the raw filesystem path itself
(in native form, e.g. with backslashes under Windows), which you can (in native form, e.g. with backslashes under Windows), which you can
pass to any function taking a file path as a string:: pass to any function taking a file path as a string::
......
...@@ -634,13 +634,16 @@ class PurePath(object): ...@@ -634,13 +634,16 @@ class PurePath(object):
for a in args: for a in args:
if isinstance(a, PurePath): if isinstance(a, PurePath):
parts += a._parts parts += a._parts
elif isinstance(a, str):
# Force-cast str subclasses to str (issue #21127)
parts.append(str(a))
else: else:
raise TypeError( a = os.fspath(a)
"argument should be a path or str object, not %r" if isinstance(a, str):
% type(a)) # Force-cast str subclasses to str (issue #21127)
parts.append(str(a))
else:
raise TypeError(
"argument should be a str object or an os.PathLike "
"object returning str, not %r"
% type(a))
return cls._flavour.parse_parts(parts) return cls._flavour.parse_parts(parts)
@classmethod @classmethod
...@@ -693,6 +696,9 @@ class PurePath(object): ...@@ -693,6 +696,9 @@ class PurePath(object):
self._parts) or '.' self._parts) or '.'
return self._str return self._str
def __fspath__(self):
return str(self)
def as_posix(self): def as_posix(self):
"""Return the string representation of the path with forward (/) """Return the string representation of the path with forward (/)
slashes.""" slashes."""
...@@ -943,6 +949,10 @@ class PurePath(object): ...@@ -943,6 +949,10 @@ class PurePath(object):
return False return False
return True return True
# Can't subclass os.PathLike from PurePath and keep the constructor
# optimizations in PurePath._parse_args().
os.PathLike.register(PurePath)
class PurePosixPath(PurePath): class PurePosixPath(PurePath):
_flavour = _posix_flavour _flavour = _posix_flavour
......
...@@ -190,13 +190,18 @@ class _BasePurePathTest(object): ...@@ -190,13 +190,18 @@ class _BasePurePathTest(object):
P = self.cls P = self.cls
p = P('a') p = P('a')
self.assertIsInstance(p, P) self.assertIsInstance(p, P)
class PathLike:
def __fspath__(self):
return "a/b/c"
P('a', 'b', 'c') P('a', 'b', 'c')
P('/a', 'b', 'c') P('/a', 'b', 'c')
P('a/b/c') P('a/b/c')
P('/a/b/c') P('/a/b/c')
P(PathLike())
self.assertEqual(P(P('a')), P('a')) self.assertEqual(P(P('a')), P('a'))
self.assertEqual(P(P('a'), 'b'), P('a/b')) self.assertEqual(P(P('a'), 'b'), P('a/b'))
self.assertEqual(P(P('a'), P('b')), P('a/b')) self.assertEqual(P(P('a'), P('b')), P('a/b'))
self.assertEqual(P(P('a'), P('b'), P('c')), P(PathLike()))
def _check_str_subclass(self, *args): def _check_str_subclass(self, *args):
# Issue #21127: it should be possible to construct a PurePath object # Issue #21127: it should be possible to construct a PurePath object
...@@ -384,6 +389,12 @@ class _BasePurePathTest(object): ...@@ -384,6 +389,12 @@ class _BasePurePathTest(object):
parts = p.parts parts = p.parts
self.assertEqual(parts, (sep, 'a', 'b')) self.assertEqual(parts, (sep, 'a', 'b'))
def test_fspath_common(self):
P = self.cls
p = P('a/b')
self._check_str(p.__fspath__(), ('a/b',))
self._check_str(os.fspath(p), ('a/b',))
def test_equivalences(self): def test_equivalences(self):
for k, tuples in self.equivalences.items(): for k, tuples in self.equivalences.items():
canon = k.replace('/', self.sep) canon = k.replace('/', self.sep)
......
...@@ -10,6 +10,8 @@ What's New in Python 3.6.0 alpha 2 ...@@ -10,6 +10,8 @@ What's New in Python 3.6.0 alpha 2
Core and Builtins Core and Builtins
----------------- -----------------
- Issue #27186: Add support for os.PathLike objects to open() (part of PEP 519).
- Issue #27066: Fixed SystemError if a custom opener (for open()) returns a - Issue #27066: Fixed SystemError if a custom opener (for open()) returns a
negative number without setting an exception. negative number without setting an exception.
...@@ -36,6 +38,14 @@ Core and Builtins ...@@ -36,6 +38,14 @@ Core and Builtins
Library Library
------- -------
- Issue #27186: Add os.PathLike support to pathlib, removing its provisional
status (part of PEP 519).
- Issue #27186: Add support for os.PathLike objects to os.fsencode() and
os.fsdecode() (part of PEP 519).
- Issue #27186: Introduce os.PathLike and os.fspath() (part of PEP 519).
- A new version of typing.py provides several new classes and - A new version of typing.py provides several new classes and
features: @overload outside stubs, Reversible, DefaultDict, Text, features: @overload outside stubs, Reversible, DefaultDict, Text,
ContextManager, Type[], NewType(), TYPE_CHECKING, and numerous bug ContextManager, Type[], NewType(), TYPE_CHECKING, and numerous bug
...@@ -198,12 +208,14 @@ Build ...@@ -198,12 +208,14 @@ Build
Misc Misc
---- ----
- Issue #17500, and https://github.com/python/pythondotorg/issues/945: Remove - Issue #17500, and https://github.com/python/pythondotorg/issues/945: Remove
unused and outdated icons. unused and outdated icons.
C API C API
----- -----
- Issue #27186: Add the PyOS_FSPath() function (part of PEP 519).
- Issue #26282: PyArg_ParseTupleAndKeywords() now supports positional-only - Issue #26282: PyArg_ParseTupleAndKeywords() now supports positional-only
parameters. parameters.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment