1"""
2Backported functions to implement the PEP 519 (Adding a file system path protocol) API.
3"""
4
5import abc
6import sys
7import io
8import pathlib
9
10try:
11    from os import PathLike, fspath, fsencode, fsdecode
12except ImportError:
13
14    class PathLike(abc.ABC):
15        """Abstract base class for implementing the file system path protocol."""
16
17        @abc.abstractmethod
18        def __fspath__(self):
19            """Return the file system path representation of the object."""
20            raise NotImplementedError
21
22    PathLike.register(pathlib.Path)
23
24    def fspath(path):
25        """Return the string representation of the path.
26
27        If str or bytes is passed in, it is returned unchanged. If __fspath__()
28        returns something other than str or bytes then TypeError is raised. If
29        this function is given something that is not str, bytes, or os.PathLike
30        then TypeError is raised.
31        """
32        if isinstance(path, (str, bytes)):
33            return path
34
35        if isinstance(path, pathlib.Path):
36            return str(path)
37
38        # Work from the object's type to match method resolution of other magic
39        # methods.
40        path_type = type(path)
41        try:
42            path = path_type.__fspath__(path)
43        except AttributeError:
44            if hasattr(path_type, "__fspath__"):
45                raise
46        else:
47            if isinstance(path, (str, bytes)):
48                return path
49            else:
50                raise TypeError(
51                    "expected __fspath__() to return str or bytes, "
52                    "not " + type(path).__name__
53                )
54
55        raise TypeError(
56            "expected str, bytes or os.PathLike object, not " + path_type.__name__
57        )
58
59    def _fscodec():
60        encoding = sys.getfilesystemencoding()
61        if encoding == "mbcs":
62            errors = "strict"
63        else:
64            errors = "surrogateescape"
65
66        def fsencode(filename):
67            """Encode filename (an os.PathLike, bytes, or str) to the filesystem
68            encoding with 'surrogateescape' error handler, return bytes unchanged.
69            On Windows, use 'strict' error handler if the file system encoding is
70            'mbcs' (which is the default encoding).
71            """
72            filename = fspath(filename)  # Does type-checking of `filename`.
73            if isinstance(filename, str):
74                return filename.encode(encoding, errors)
75            else:
76                return filename
77
78        def fsdecode(filename):
79            """Decode filename (an os.PathLike, bytes, or str) from the filesystem
80            encoding with 'surrogateescape' error handler, return str unchanged. On
81            Windows, use 'strict' error handler if the file system encoding is
82            'mbcs' (which is the default encoding).
83            """
84            filename = fspath(filename)  # Does type-checking of `filename`.
85            if isinstance(filename, bytes):
86                return filename.decode(encoding, errors)
87            else:
88                return filename
89
90        return fsencode, fsdecode
91
92    fsencode, fsdecode = _fscodec()
93    del _fscodec
94
95    def open(file, *pargs, **kwargs):
96        if isinstance(file, PathLike):
97            file = fspath(file)
98        return io.open(file, *pargs, **kwargs)
99