• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Licensed under the Apache License, Version 2.0 (the "License");
2# you may not use this file except in compliance with the License.
3# You may obtain a copy of the License at
4#
5#      http://www.apache.org/licenses/LICENSE-2.0
6#
7# Unless required by applicable law or agreed to in writing, software
8# distributed under the License is distributed on an "AS IS" BASIS,
9# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10# See the License for the specific language governing permissions and
11# limitations under the License.
12
13"""A fake implementation for pathlib working with FakeFilesystem.
14New in pyfakefs 3.0.
15
16Usage:
17
18* With fake_filesystem_unittest:
19  If using fake_filesystem_unittest.TestCase, pathlib gets replaced
20  by fake_pathlib together with other file system related modules.
21
22* Stand-alone with FakeFilesystem:
23  `filesystem = fake_filesystem.FakeFilesystem()`
24  `fake_pathlib_module = fake_filesystem.FakePathlibModule(filesystem)`
25  `path = fake_pathlib_module.Path('/foo/bar')`
26
27Note: as the implementation is based on FakeFilesystem, all faked classes
28(including PurePosixPath, PosixPath, PureWindowsPath and WindowsPath)
29get the properties of the underlying fake filesystem.
30"""
31import errno
32import fnmatch
33import functools
34import os
35import pathlib
36from pathlib import PurePath
37import re
38import sys
39from urllib.parse import quote_from_bytes as urlquote_from_bytes
40
41from pyfakefs import fake_scandir
42from pyfakefs.extra_packages import use_scandir
43from pyfakefs.fake_filesystem import FakeFileOpen, FakeFilesystem
44
45
46def init_module(filesystem):
47    """Initializes the fake module with the fake file system."""
48    # pylint: disable=protected-access
49    FakePath.filesystem = filesystem
50    FakePathlibModule.PureWindowsPath._flavour = _FakeWindowsFlavour(
51        filesystem)
52    FakePathlibModule.PurePosixPath._flavour = _FakePosixFlavour(filesystem)
53
54
55def _wrap_strfunc(strfunc):
56    @functools.wraps(strfunc)
57    def _wrapped(pathobj, *args, **kwargs):
58        return strfunc(pathobj.filesystem, str(pathobj), *args, **kwargs)
59
60    return staticmethod(_wrapped)
61
62
63def _wrap_binary_strfunc(strfunc):
64    @functools.wraps(strfunc)
65    def _wrapped(pathobj1, pathobj2, *args):
66        return strfunc(
67            pathobj1.filesystem, str(pathobj1), str(pathobj2), *args)
68
69    return staticmethod(_wrapped)
70
71
72def _wrap_binary_strfunc_reverse(strfunc):
73    @functools.wraps(strfunc)
74    def _wrapped(pathobj1, pathobj2, *args):
75        return strfunc(
76            pathobj2.filesystem, str(pathobj2), str(pathobj1), *args)
77
78    return staticmethod(_wrapped)
79
80
81try:
82    accessor = pathlib._Accessor  # type: ignore [attr-defined]
83except AttributeError:
84    accessor = object
85
86
87class _FakeAccessor(accessor):  # type: ignore [valid-type, misc]
88    """Accessor which forwards some of the functions to FakeFilesystem methods.
89    """
90
91    stat = _wrap_strfunc(FakeFilesystem.stat)
92
93    lstat = _wrap_strfunc(
94        lambda fs, path: FakeFilesystem.stat(fs, path, follow_symlinks=False))
95
96    listdir = _wrap_strfunc(FakeFilesystem.listdir)
97
98    if use_scandir:
99        scandir = _wrap_strfunc(fake_scandir.scandir)
100
101    chmod = _wrap_strfunc(FakeFilesystem.chmod)
102
103    if hasattr(os, "lchmod"):
104        lchmod = _wrap_strfunc(lambda fs, path, mode: FakeFilesystem.chmod(
105            fs, path, mode, follow_symlinks=False))
106    else:
107        def lchmod(self, pathobj,  *args, **kwargs):
108            """Raises not implemented for Windows systems."""
109            raise NotImplementedError("lchmod() not available on this system")
110
111        def chmod(self, pathobj, *args, **kwargs):
112            if "follow_symlinks" in kwargs:
113                if sys.version_info < (3, 10):
114                    raise TypeError("chmod() got an unexpected keyword "
115                                    "argument 'follow_synlinks'")
116                if (not kwargs["follow_symlinks"] and
117                        os.chmod not in os.supports_follow_symlinks):
118                    raise NotImplementedError(
119                        "`follow_symlinks` for chmod() is not available "
120                        "on this system")
121            return pathobj.filesystem.chmod(str(pathobj), *args, **kwargs)
122
123    mkdir = _wrap_strfunc(FakeFilesystem.makedir)
124
125    unlink = _wrap_strfunc(FakeFilesystem.remove)
126
127    rmdir = _wrap_strfunc(FakeFilesystem.rmdir)
128
129    rename = _wrap_binary_strfunc(FakeFilesystem.rename)
130
131    replace = _wrap_binary_strfunc(
132        lambda fs, old_path, new_path: FakeFilesystem.rename(
133            fs, old_path, new_path, force_replace=True))
134
135    symlink = _wrap_binary_strfunc_reverse(
136        lambda fs, file_path, link_target, target_is_directory:
137        FakeFilesystem.create_symlink(fs, file_path, link_target,
138                                      create_missing_dirs=False))
139
140    if (3, 8) <= sys.version_info:
141        link_to = _wrap_binary_strfunc(
142            lambda fs, file_path, link_target:
143            FakeFilesystem.link(fs, file_path, link_target))
144
145    if sys.version_info >= (3, 10):
146        link = _wrap_binary_strfunc(
147            lambda fs, file_path, link_target:
148            FakeFilesystem.link(fs, file_path, link_target))
149
150        # this will use the fake filesystem because os is patched
151        def getcwd(self):
152            return os.getcwd()
153
154    readlink = _wrap_strfunc(FakeFilesystem.readlink)
155
156    utime = _wrap_strfunc(FakeFilesystem.utime)
157
158
159_fake_accessor = _FakeAccessor()
160
161flavour = pathlib._Flavour  # type: ignore [attr-defined]
162
163
164class _FakeFlavour(flavour):  # type: ignore [valid-type, misc]
165    """Fake Flavour implementation used by PurePath and _Flavour"""
166
167    filesystem = None
168    sep = '/'
169    altsep = None
170    has_drv = False
171
172    ext_namespace_prefix = '\\\\?\\'
173
174    drive_letters = (
175            set(chr(x) for x in range(ord('a'), ord('z') + 1)) |
176            set(chr(x) for x in range(ord('A'), ord('Z') + 1))
177    )
178
179    def __init__(self, filesystem):
180        self.filesystem = filesystem
181        self.sep = filesystem.path_separator
182        self.altsep = filesystem.alternative_path_separator
183        self.has_drv = filesystem.is_windows_fs
184        super(_FakeFlavour, self).__init__()
185
186    @staticmethod
187    def _split_extended_path(path, ext_prefix=ext_namespace_prefix):
188        prefix = ''
189        if path.startswith(ext_prefix):
190            prefix = path[:4]
191            path = path[4:]
192            if path.startswith('UNC\\'):
193                prefix += path[:3]
194                path = '\\' + path[3:]
195        return prefix, path
196
197    def _splitroot_with_drive(self, path, sep):
198        first = path[0:1]
199        second = path[1:2]
200        if second == sep and first == sep:
201            # extended paths should also disable the collapsing of "."
202            # components (according to MSDN docs).
203            prefix, path = self._split_extended_path(path)
204            first = path[0:1]
205            second = path[1:2]
206        else:
207            prefix = ''
208        third = path[2:3]
209        if second == sep and first == sep and third != sep:
210            # is a UNC path:
211            # vvvvvvvvvvvvvvvvvvvvv root
212            # \\machine\mountpoint\directory\etc\...
213            #            directory ^^^^^^^^^^^^^^
214            index = path.find(sep, 2)
215            if index != -1:
216                index2 = path.find(sep, index + 1)
217                # a UNC path can't have two slashes in a row
218                # (after the initial two)
219                if index2 != index + 1:
220                    if index2 == -1:
221                        index2 = len(path)
222                    if prefix:
223                        return prefix + path[1:index2], sep, path[index2 + 1:]
224                    return path[:index2], sep, path[index2 + 1:]
225        drv = root = ''
226        if second == ':' and first in self.drive_letters:
227            drv = path[:2]
228            path = path[2:]
229            first = third
230        if first == sep:
231            root = first
232            path = path.lstrip(sep)
233        return prefix + drv, root, path
234
235    @staticmethod
236    def _splitroot_posix(path, sep):
237        if path and path[0] == sep:
238            stripped_part = path.lstrip(sep)
239            if len(path) - len(stripped_part) == 2:
240                return '', sep * 2, stripped_part
241            return '', sep, stripped_part
242        else:
243            return '', '', path
244
245    def splitroot(self, path, sep=None):
246        """Split path into drive, root and rest."""
247        if sep is None:
248            sep = self.filesystem.path_separator
249        if self.filesystem.is_windows_fs:
250            return self._splitroot_with_drive(path, sep)
251        return self._splitroot_posix(path, sep)
252
253    def casefold(self, path):
254        """Return the lower-case version of s for a Windows filesystem."""
255        if self.filesystem.is_windows_fs:
256            return path.lower()
257        return path
258
259    def casefold_parts(self, parts):
260        """Return the lower-case version of parts for a Windows filesystem."""
261        if self.filesystem.is_windows_fs:
262            return [p.lower() for p in parts]
263        return parts
264
265    def _resolve_posix(self, path, strict):
266        sep = self.sep
267        seen = {}
268
269        def _resolve(path, rest):
270            if rest.startswith(sep):
271                path = ''
272
273            for name in rest.split(sep):
274                if not name or name == '.':
275                    # current dir
276                    continue
277                if name == '..':
278                    # parent dir
279                    path, _, _ = path.rpartition(sep)
280                    continue
281                newpath = path + sep + name
282                if newpath in seen:
283                    # Already seen this path
284                    path = seen[newpath]
285                    if path is not None:
286                        # use cached value
287                        continue
288                    # The symlink is not resolved, so we must have
289                    # a symlink loop.
290                    raise RuntimeError("Symlink loop from %r" % newpath)
291                # Resolve the symbolic link
292                try:
293                    target = self.filesystem.readlink(newpath)
294                except OSError as e:
295                    if e.errno != errno.EINVAL and strict:
296                        raise
297                    # Not a symlink, or non-strict mode. We just leave the path
298                    # untouched.
299                    path = newpath
300                else:
301                    seen[newpath] = None  # not resolved symlink
302                    path = _resolve(path, target)
303                    seen[newpath] = path  # resolved symlink
304
305            return path
306
307        # NOTE: according to POSIX, getcwd() cannot contain path components
308        # which are symlinks.
309        base = '' if path.is_absolute() else self.filesystem.cwd
310        return _resolve(base, str(path)) or sep
311
312    def _resolve_windows(self, path, strict):
313        path = str(path)
314        if not path:
315            return os.getcwd()
316        previous_s = None
317        if strict:
318            if not self.filesystem.exists(path):
319                self.filesystem.raise_os_error(errno.ENOENT, path)
320            return self.filesystem.resolve_path(path)
321        else:
322            while True:
323                try:
324                    path = self.filesystem.resolve_path(path)
325                except OSError:
326                    previous_s = path
327                    path = self.filesystem.splitpath(path)[0]
328                else:
329                    if previous_s is None:
330                        return path
331                    return self.filesystem.joinpaths(
332                        path, os.path.basename(previous_s))
333
334    def resolve(self, path, strict):
335        """Make the path absolute, resolving any symlinks."""
336        if self.filesystem.is_windows_fs:
337            return self._resolve_windows(path, strict)
338        return self._resolve_posix(path, strict)
339
340    def gethomedir(self, username):
341        """Return the home directory of the current user."""
342        if not username:
343            try:
344                return os.environ['HOME']
345            except KeyError:
346                import pwd
347                return pwd.getpwuid(os.getuid()).pw_dir
348        else:
349            import pwd
350            try:
351                return pwd.getpwnam(username).pw_dir
352            except KeyError:
353                raise RuntimeError("Can't determine home directory "
354                                   "for %r" % username)
355
356
357class _FakeWindowsFlavour(_FakeFlavour):
358    """Flavour used by PureWindowsPath with some Windows specific
359    implementations independent of FakeFilesystem properties.
360    """
361    reserved_names = (
362            {'CON', 'PRN', 'AUX', 'NUL'} |
363            {'COM%d' % i for i in range(1, 10)} |
364            {'LPT%d' % i for i in range(1, 10)}
365    )
366
367    def is_reserved(self, parts):
368        """Return True if the path is considered reserved under Windows."""
369
370        # NOTE: the rules for reserved names seem somewhat complicated
371        # (e.g. r"..\NUL" is reserved but not r"foo\NUL").
372        # We err on the side of caution and return True for paths which are
373        # not considered reserved by Windows.
374        if not parts:
375            return False
376        if self.filesystem.is_windows_fs and parts[0].startswith('\\\\'):
377            # UNC paths are never reserved
378            return False
379        return parts[-1].partition('.')[0].upper() in self.reserved_names
380
381    def make_uri(self, path):
382        """Return a file URI for the given path"""
383
384        # Under Windows, file URIs use the UTF-8 encoding.
385        # original version, not faked
386        drive = path.drive
387        if len(drive) == 2 and drive[1] == ':':
388            # It's a path on a local drive => 'file:///c:/a/b'
389            rest = path.as_posix()[2:].lstrip('/')
390            return 'file:///%s/%s' % (
391                drive, urlquote_from_bytes(rest.encode('utf-8')))
392        else:
393            # It's a path on a network drive => 'file://host/share/a/b'
394            return ('file:' +
395                    urlquote_from_bytes(path.as_posix().encode('utf-8')))
396
397    def gethomedir(self, username):
398        """Return the home directory of the current user."""
399
400        # original version, not faked
401        if 'HOME' in os.environ:
402            userhome = os.environ['HOME']
403        elif 'USERPROFILE' in os.environ:
404            userhome = os.environ['USERPROFILE']
405        elif 'HOMEPATH' in os.environ:
406            try:
407                drv = os.environ['HOMEDRIVE']
408            except KeyError:
409                drv = ''
410            userhome = drv + os.environ['HOMEPATH']
411        else:
412            raise RuntimeError("Can't determine home directory")
413
414        if username:
415            # Try to guess user home directory.  By default all users
416            # directories are located in the same place and are named by
417            # corresponding usernames.  If current user home directory points
418            # to nonstandard place, this guess is likely wrong.
419            if os.environ['USERNAME'] != username:
420                drv, root, parts = self.parse_parts((userhome,))
421                if parts[-1] != os.environ['USERNAME']:
422                    raise RuntimeError("Can't determine home directory "
423                                       "for %r" % username)
424                parts[-1] = username
425                if drv or root:
426                    userhome = drv + root + self.join(parts[1:])
427                else:
428                    userhome = self.join(parts)
429        return userhome
430
431    def compile_pattern(self, pattern):
432        return re.compile(fnmatch.translate(pattern), re.IGNORECASE).fullmatch
433
434
435class _FakePosixFlavour(_FakeFlavour):
436    """Flavour used by PurePosixPath with some Unix specific implementations
437    independent of FakeFilesystem properties.
438    """
439
440    def is_reserved(self, parts):
441        return False
442
443    def make_uri(self, path):
444        # We represent the path using the local filesystem encoding,
445        # for portability to other applications.
446        bpath = bytes(path)
447        return 'file://' + urlquote_from_bytes(bpath)
448
449    def gethomedir(self, username):
450        # original version, not faked
451        if not username:
452            try:
453                return os.environ['HOME']
454            except KeyError:
455                import pwd
456                return pwd.getpwuid(os.getuid()).pw_dir
457        else:
458            import pwd
459            try:
460                return pwd.getpwnam(username).pw_dir
461            except KeyError:
462                raise RuntimeError("Can't determine home directory "
463                                   "for %r" % username)
464
465    def compile_pattern(self, pattern):
466        return re.compile(fnmatch.translate(pattern)).fullmatch
467
468
469class FakePath(pathlib.Path):
470    """Replacement for pathlib.Path. Reimplement some methods to use
471    fake filesystem. The rest of the methods work as they are, as they will
472    use the fake accessor.
473    New in pyfakefs 3.0.
474    """
475
476    # the underlying fake filesystem
477    filesystem = None
478
479    def __new__(cls, *args, **kwargs):
480        """Creates the correct subclass based on OS."""
481        if cls is FakePathlibModule.Path:
482            cls = (FakePathlibModule.WindowsPath
483                   if cls.filesystem.is_windows_fs
484                   else FakePathlibModule.PosixPath)
485        self = cls._from_parts(args)
486        return self
487
488    @classmethod
489    def _from_parts(cls, args, init=False):  # pylint: disable=unused-argument
490        # Overwritten to call _init to set the fake accessor,
491        # which is not done since Python 3.10
492        self = object.__new__(cls)
493        self._init()
494        drv, root, parts = self._parse_args(args)
495        self._drv = drv
496        self._root = root
497        self._parts = parts
498        return self
499
500    @classmethod
501    def _from_parsed_parts(cls, drv, root, parts):
502        # Overwritten to call _init to set the fake accessor,
503        # which is not done since Python 3.10
504        self = object.__new__(cls)
505        self._init()
506        self._drv = drv
507        self._root = root
508        self._parts = parts
509        return self
510
511    def _init(self, template=None):
512        """Initializer called from base class."""
513        self._accessor = _fake_accessor
514        self._closed = False
515
516    def _path(self):
517        """Returns the underlying path string as used by the fake filesystem.
518        """
519        return str(self)
520
521    @classmethod
522    def cwd(cls):
523        """Return a new path pointing to the current working directory
524        (as returned by os.getcwd()).
525        """
526        return cls(cls.filesystem.cwd)
527
528    def resolve(self, strict=None):
529        """Make the path absolute, resolving all symlinks on the way and also
530        normalizing it (for example turning slashes into backslashes
531        under Windows).
532
533        Args:
534            strict: If False (default) no exception is raised if the path
535                does not exist.
536                New in Python 3.6.
537
538        Raises:
539            OSError: if the path doesn't exist (strict=True or Python < 3.6)
540        """
541        if sys.version_info >= (3, 6):
542            if strict is None:
543                strict = False
544        else:
545            if strict is not None:
546                raise TypeError(
547                    "resolve() got an unexpected keyword argument 'strict'")
548            strict = True
549        if self._closed:
550            self._raise_closed()
551        path = self._flavour.resolve(self, strict=strict)
552        if path is None:
553            self.stat()
554            path = str(self.absolute())
555        path = self.filesystem.absnormpath(path)
556        return FakePath(path)
557
558    def open(self, mode='r', buffering=-1, encoding=None,
559             errors=None, newline=None):
560        """Open the file pointed by this path and return a fake file object.
561
562        Raises:
563            OSError: if the target object is a directory, the path is invalid
564                or permission is denied.
565        """
566        if self._closed:
567            self._raise_closed()
568        return FakeFileOpen(self.filesystem)(
569            self._path(), mode, buffering, encoding, errors, newline)
570
571    def read_bytes(self):
572        """Open the fake file in bytes mode, read it, and close the file.
573
574        Raises:
575            OSError: if the target object is a directory, the path is
576                invalid or permission is denied.
577        """
578        with FakeFileOpen(self.filesystem)(self._path(), mode='rb') as f:
579            return f.read()
580
581    def read_text(self, encoding=None, errors=None):
582        """
583        Open the fake file in text mode, read it, and close the file.
584        """
585        with FakeFileOpen(self.filesystem)(self._path(), mode='r',
586                                           encoding=encoding,
587                                           errors=errors) as f:
588            return f.read()
589
590    def write_bytes(self, data):
591        """Open the fake file in bytes mode, write to it, and close the file.
592        Args:
593            data: the bytes to be written
594        Raises:
595            OSError: if the target object is a directory, the path is
596                invalid or permission is denied.
597        """
598        # type-check for the buffer interface before truncating the file
599        view = memoryview(data)
600        with FakeFileOpen(self.filesystem)(self._path(), mode='wb') as f:
601            return f.write(view)
602
603    def write_text(self, data, encoding=None, errors=None, newline=None):
604        """Open the fake file in text mode, write to it, and close
605        the file.
606
607        Args:
608            data: the string to be written
609            encoding: the encoding used for the string; if not given, the
610                default locale encoding is used
611            errors: (str) Defines how encoding errors are handled.
612            newline: Controls universal newlines, passed to stream object.
613                New in Python 3.10.
614        Raises:
615            TypeError: if data is not of type 'str'.
616            OSError: if the target object is a directory, the path is
617                invalid or permission is denied.
618        """
619        if not isinstance(data, str):
620            raise TypeError('data must be str, not %s' %
621                            data.__class__.__name__)
622        if newline is not None and sys.version_info < (3, 10):
623            raise TypeError("write_text() got an unexpected "
624                            "keyword argument 'newline'")
625        with FakeFileOpen(self.filesystem)(self._path(),
626                                           mode='w',
627                                           encoding=encoding,
628                                           errors=errors,
629                                           newline=newline) as f:
630            return f.write(data)
631
632    @classmethod
633    def home(cls):
634        """Return a new path pointing to the user's home directory (as
635        returned by os.path.expanduser('~')).
636        """
637        home = os.path.expanduser("~")
638        if cls.filesystem.is_windows_fs != (os.name == 'nt'):
639            username = os.path.split(home)[1]
640            if cls.filesystem.is_windows_fs:
641                home = os.path.join('C:', 'Users', username)
642            else:
643                home = os.path.join('home', username)
644            cls.filesystem.create_dir(home)
645        return cls(home.replace(os.sep, cls.filesystem.path_separator))
646
647    def samefile(self, other_path):
648        """Return whether other_path is the same or not as this file
649        (as returned by os.path.samefile()).
650
651        Args:
652            other_path: A path object or string of the file object
653            to be compared with
654
655        Raises:
656            OSError: if the filesystem object doesn't exist.
657        """
658        st = self.stat()
659        try:
660            other_st = other_path.stat()
661        except AttributeError:
662            other_st = self.filesystem.stat(other_path)
663        return (st.st_ino == other_st.st_ino and
664                st.st_dev == other_st.st_dev)
665
666    def expanduser(self):
667        """ Return a new path with expanded ~ and ~user constructs
668        (as returned by os.path.expanduser)
669        """
670        return FakePath(os.path.expanduser(self._path())
671                        .replace(os.path.sep,
672                                 self.filesystem.path_separator))
673
674    def touch(self, mode=0o666, exist_ok=True):
675        """Create a fake file for the path with the given access mode,
676        if it doesn't exist.
677
678        Args:
679            mode: the file mode for the file if it does not exist
680            exist_ok: if the file already exists and this is True, nothing
681                happens, otherwise FileExistError is raised
682
683        Raises:
684            FileExistsError: if the file exists and exits_ok is False.
685        """
686        if self._closed:
687            self._raise_closed()
688        if self.exists():
689            if exist_ok:
690                self.filesystem.utime(self._path(), times=None)
691            else:
692                self.filesystem.raise_os_error(errno.EEXIST, self._path())
693        else:
694            fake_file = self.open('w')
695            fake_file.close()
696            self.chmod(mode)
697
698
699class FakePathlibModule:
700    """Uses FakeFilesystem to provide a fake pathlib module replacement.
701    Can be used to replace both the standard `pathlib` module and the
702    `pathlib2` package available on PyPi.
703
704    You need a fake_filesystem to use this:
705    `filesystem = fake_filesystem.FakeFilesystem()`
706    `fake_pathlib_module = fake_filesystem.FakePathlibModule(filesystem)`
707    """
708
709    def __init__(self, filesystem):
710        """
711        Initializes the module with the given filesystem.
712
713        Args:
714            filesystem: FakeFilesystem used to provide file system information
715        """
716        init_module(filesystem)
717        self._pathlib_module = pathlib
718
719    class PurePosixPath(PurePath):
720        """A subclass of PurePath, that represents non-Windows filesystem
721        paths"""
722        __slots__ = ()
723
724    class PureWindowsPath(PurePath):
725        """A subclass of PurePath, that represents Windows filesystem paths"""
726        __slots__ = ()
727
728    class WindowsPath(FakePath, PureWindowsPath):
729        """A subclass of Path and PureWindowsPath that represents
730        concrete Windows filesystem paths.
731        """
732        __slots__ = ()
733
734        def owner(self):
735            raise NotImplementedError(
736                "Path.owner() is unsupported on this system")
737
738        def group(self):
739            raise NotImplementedError(
740                "Path.group() is unsupported on this system")
741
742        def is_mount(self):
743            raise NotImplementedError(
744                "Path.is_mount() is unsupported on this system")
745
746    class PosixPath(FakePath, PurePosixPath):
747        """A subclass of Path and PurePosixPath that represents
748        concrete non-Windows filesystem paths.
749        """
750        __slots__ = ()
751
752        def owner(self):
753            """Return the current user name. It is assumed that the fake
754            file system was created by the current user.
755            """
756            import pwd
757
758            return pwd.getpwuid(os.getuid()).pw_name
759
760        def group(self):
761            """Return the current group name. It is assumed that the fake
762            file system was created by the current user.
763            """
764            import grp
765
766            return grp.getgrgid(os.getgid()).gr_name
767
768    Path = FakePath
769
770    def __getattr__(self, name):
771        """Forwards any unfaked calls to the standard pathlib module."""
772        return getattr(self._pathlib_module, name)
773
774
775class FakePathlibPathModule:
776    """Patches `pathlib.Path` by passing all calls to FakePathlibModule."""
777    fake_pathlib = None
778
779    def __init__(self, filesystem=None):
780        if self.fake_pathlib is None:
781            self.__class__.fake_pathlib = FakePathlibModule(filesystem)
782
783    def __call__(self, *args, **kwargs):
784        return self.fake_pathlib.Path(*args, **kwargs)
785
786    def __getattr__(self, name):
787        return getattr(self.fake_pathlib.Path, name)
788
789
790class RealPath(pathlib.Path):
791    """Replacement for `pathlib.Path` if it shall not be faked.
792    Needed because `Path` in `pathlib` is always faked, even if `pathlib`
793    itself is not.
794    """
795
796    def __new__(cls, *args, **kwargs):
797        """Creates the correct subclass based on OS."""
798        if cls is RealPathlibModule.Path:
799            cls = (RealPathlibModule.WindowsPath if os.name == 'nt'
800                   else RealPathlibModule.PosixPath)
801        self = cls._from_parts(args)
802        return self
803
804
805class RealPathlibModule:
806    """Used to replace `pathlib` for skipped modules.
807    As the original `pathlib` is always patched to use the fake path,
808    we need to provide a version which does not do this.
809    """
810
811    def __init__(self):
812        RealPathlibModule.PureWindowsPath._flavour = pathlib._WindowsFlavour()
813        RealPathlibModule.PurePosixPath._flavour = pathlib._PosixFlavour()
814        self._pathlib_module = pathlib
815
816    class PurePosixPath(PurePath):
817        """A subclass of PurePath, that represents Posix filesystem paths"""
818        __slots__ = ()
819
820    class PureWindowsPath(PurePath):
821        """A subclass of PurePath, that represents Windows filesystem paths"""
822        __slots__ = ()
823
824    if sys.platform == 'win32':
825        class WindowsPath(RealPath, PureWindowsPath):
826            """A subclass of Path and PureWindowsPath that represents
827            concrete Windows filesystem paths.
828            """
829            __slots__ = ()
830    else:
831        class PosixPath(RealPath, PurePosixPath):
832            """A subclass of Path and PurePosixPath that represents
833            concrete non-Windows filesystem paths.
834            """
835            __slots__ = ()
836
837    Path = RealPath
838
839    def __getattr__(self, name):
840        """Forwards any unfaked calls to the standard pathlib module."""
841        return getattr(self._pathlib_module, name)
842
843
844class RealPathlibPathModule:
845    """Patches `pathlib.Path` by passing all calls to RealPathlibModule."""
846    real_pathlib = None
847
848    @classmethod
849    def __instancecheck__(cls, instance):
850        # as we cannot derive from pathlib.Path, we fake
851        # the inheritance to pass isinstance checks - see #666
852        return isinstance(instance, PurePath)
853
854    def __init__(self):
855        if self.real_pathlib is None:
856            self.__class__.real_pathlib = RealPathlibModule()
857
858    def __call__(self, *args, **kwargs):
859        return self.real_pathlib.Path(*args, **kwargs)
860
861    def __getattr__(self, name):
862        return getattr(self.real_pathlib.Path, name)
863