Repository: jmcgeheeiv/pyfakefs
Pull request #607 (2021-06-04 18:47:06)

Add type annotations to some fakefs functions
This is work in progress, trying to annotate the pyfakefs methods. 
This hopefully helps to find some inconsistencies (already found some in `str` / `byte` handling), but it will take a while.

@AustinScola - if you want to take a look, there are certainly things that can be improved, as I don't have much experience with typing.



AustinScola: Nice! I'll check it out later today or tomorrow.
mrbean-bremen: Going to merge this to avoid too many merge conflicts with other changes. Will continue in new PR(s).

Patch:
diff --git a/CHANGES.md b/CHANGES.md
index 67cb7df..01bee02 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -3,6 +3,10 @@ The released versions correspond to PyPi releases.
 
 ## Version 4.6.0 (as yet unreleased)
 
+### Fixes
+* added handling of path-like where missing
+* improved handling of `str`/`bytes` paths
+
 ### Infrastructure
 * added test dependency check (see [#608](../../issues/608))
 
diff --git a/pyfakefs/fake_filesystem.py b/pyfakefs/fake_filesystem.py
index b51b7b1..9cd366b 100644
--- a/pyfakefs/fake_filesystem.py
+++ b/pyfakefs/fake_filesystem.py
@@ -106,19 +106,21 @@ from enum import Enum
 from stat import (
     S_IFREG, S_IFDIR, S_ISLNK, S_IFMT, S_ISDIR, S_IFLNK, S_ISREG, S_IFSOCK
 )
-
+from types import ModuleType
+from typing import (
+    List, Optional, Callable, Union, Any, Dict, Tuple, cast, AnyStr, overload,
+    NoReturn
+)
 from pyfakefs.deprecator import Deprecator
 from pyfakefs.extra_packages import use_scandir
 from pyfakefs.fake_scandir import scandir, walk
 from pyfakefs.helpers import (
     FakeStatResult, BinaryBufferIO, TextBufferIO,
     is_int_type, is_byte_string, is_unicode_string, make_string_path,
-    IS_WIN, IS_PYPY, to_string, matching_string, real_encoding, now
+    IS_PYPY, to_string, matching_string, real_encoding, now, AnyPath
 )
 from pyfakefs import __version__  # noqa: F401 for upwards compatibility
 
-__pychecker__ = 'no-reimportself'
-
 PERM_READ = 0o400  # Read permission bit.
 PERM_WRITE = 0o200  # Write permission bit.
 PERM_EXE = 0o100  # Execute permission bit.
@@ -144,6 +146,15 @@ _OPEN_MODE_MAP = {
     'x+': (False, True, True, False, False, True)
 }
 
+AnyFileWrapper = Union[
+    "FakeFileWrapper", "FakeDirWrapper",
+    "StandardStreamWrapper", "FakePipeWrapper"
+]
+
+AnyString = Union[str, bytes]
+
+AnyFile = Union["FakeFile", "FakeDirectory"]
+
 if sys.platform.startswith('linux'):
     # on newer Linux system, the default maximum recursion depth is 40
     # we ignore older systems here
@@ -153,8 +164,12 @@ else:
     _MAX_LINK_DEPTH = 32
 
 NR_STD_STREAMS = 3
-USER_ID = 1 if IS_WIN else os.getuid()
-GROUP_ID = 1 if IS_WIN else os.getgid()
+if sys.platform == 'win32':
+    USER_ID = 1
+    GROUP_ID = 1
+else:
+    USER_ID = os.getuid()
+    GROUP_ID = os.getgid()
 
 
 class OSType(Enum):
@@ -173,7 +188,7 @@ class PatchMode(Enum):
     ON = 3
 
 
-def set_uid(uid):
+def set_uid(uid: int) -> None:
     """Set the global user id. This is used as st_uid for new files
     and to differentiate between a normal user and the root user (uid 0).
     For the root user, some permission restrictions are ignored.
@@ -185,7 +200,7 @@ def set_uid(uid):
     USER_ID = uid
 
 
-def set_gid(gid):
+def set_gid(gid: int) -> None:
     """Set the global group id. This is only used to set st_gid for new files,
     no permision checks are performed.
 
@@ -196,13 +211,17 @@ def set_gid(gid):
     GROUP_ID = gid
 
 
-def reset_ids():
+def reset_ids() -> None:
     """Set the global user ID and group ID back to default values."""
-    set_uid(1 if IS_WIN else os.getuid())
-    set_gid(1 if IS_WIN else os.getgid())
+    if sys.platform == 'win32':
+        set_uid(1)
+        set_gid(1)
+    else:
+        set_uid(os.getuid())
+        set_gid(os.getgid())
 
 
-def is_root():
+def is_root() -> bool:
     """Return True if the current user is the root user."""
     return USER_ID == 0
 
@@ -212,17 +231,18 @@ class FakeLargeFileIoException(Exception):
     Fake large files have a size with no real content.
     """
 
-    def __init__(self, file_path):
+    def __init__(self, file_path: str) -> None:
         super(FakeLargeFileIoException, self).__init__(
             'Read and write operations not supported for '
             'fake large file: %s' % file_path)
 
 
-def _copy_module(old):
+def _copy_module(old: ModuleType) -> ModuleType:
     """Recompiles and creates new module object."""
     saved = sys.modules.pop(old.__name__, None)
     new = __import__(old.__name__)
-    sys.modules[old.__name__] = saved
+    if saved is not None:
+        sys.modules[old.__name__] = saved
     return new
 
 
@@ -261,9 +281,13 @@ class FakeFile:
         'st_atime_ns', 'st_mtime_ns', 'st_ctime_ns'
     )
 
-    def __init__(self, name, st_mode=S_IFREG | PERM_DEF_FILE,
-                 contents=None, filesystem=None, encoding=None, errors=None,
-                 side_effect=None):
+    def __init__(self, name: AnyStr,
+                 st_mode: int = S_IFREG | PERM_DEF_FILE,
+                 contents: Optional[AnyStr] = None,
+                 filesystem: Optional["FakeFilesystem"] = None,
+                 encoding: Optional[str] = None,
+                 errors: Optional[str] = None,
+                 side_effect: Optional[Callable[["FakeFile"], None]] = None):
         """
         Args:
             name: Name of the file/directory, without parent path information
@@ -272,8 +296,9 @@ class FakeFile:
                 If no file type is set (e.g. permission flags only), a
                 regular file type is assumed.
             contents: The contents of the filesystem object; should be a string
-                or byte object for regular files, and a list of other
-                FakeFile or FakeDirectory objects for FakeDirectory objects
+                or byte object for regular files, and a dict of other
+                FakeFile or FakeDirectory objects wih the file names as
+                keys for FakeDirectory objects
             filesystem: The fake filesystem where the file is created.
             encoding: If contents is a unicode string, the encoding used
                 for serialization.
@@ -284,69 +309,70 @@ class FakeFile:
         # to be backwards compatible regarding argument order, we raise on None
         if filesystem is None:
             raise ValueError('filesystem shall not be None')
-        self.filesystem = filesystem
-        self._side_effect = side_effect
-        self.name = name
+        self.filesystem: FakeFilesystem = filesystem
+        self._side_effect: Optional[Callable] = side_effect
+        self.name: AnyStr = name  # type: ignore[assignment]
         self.stat_result = FakeStatResult(
             filesystem.is_windows_fs, USER_ID, GROUP_ID, now())
         if st_mode >> 12 == 0:
             st_mode |= S_IFREG
         self.stat_result.st_mode = st_mode
-        self.encoding = real_encoding(encoding)
-        self.errors = errors or 'strict'
-        self._byte_contents = self._encode_contents(contents)
+        self.st_size: int = 0
+        self.encoding: Optional[str] = real_encoding(encoding)
+        self.errors: str = errors or 'strict'
+        self._byte_contents: Optional[bytes] = self._encode_contents(contents)
         self.stat_result.st_size = (
             len(self._byte_contents) if self._byte_contents is not None else 0)
-        self.epoch = 0
-        self.parent_dir = None
+        self.epoch: int = 0
+        self.parent_dir: Optional[FakeDirectory] = None
         # Linux specific: extended file system attributes
-        self.xattr = {}
+        self.xattr: Dict = {}
 
     @property
-    def byte_contents(self):
+    def byte_contents(self) -> Optional[bytes]:
         """Return the contents as raw byte array."""
         return self._byte_contents
 
     @property
-    def contents(self):
+    def contents(self) -> Optional[str]:
         """Return the contents as string with the original encoding."""
         if isinstance(self.byte_contents, bytes):
             return self.byte_contents.decode(
                 self.encoding or locale.getpreferredencoding(False),
                 errors=self.errors)
-        return self.byte_contents
+        return None
 
     @property
-    def st_ctime(self):
+    def st_ctime(self) -> float:
         """Return the creation time of the fake file."""
         return self.stat_result.st_ctime
 
     @st_ctime.setter
-    def st_ctime(self, val):
+    def st_ctime(self, val: float) -> None:
         """Set the creation time of the fake file."""
         self.stat_result.st_ctime = val
 
     @property
-    def st_atime(self):
+    def st_atime(self) -> float:
         """Return the access time of the fake file."""
         return self.stat_result.st_atime
 
     @st_atime.setter
-    def st_atime(self, val):
+    def st_atime(self, val: float) -> None:
         """Set the access time of the fake file."""
         self.stat_result.st_atime = val
 
     @property
-    def st_mtime(self):
+    def st_mtime(self) -> float:
         """Return the modification time of the fake file."""
         return self.stat_result.st_mtime
 
     @st_mtime.setter
-    def st_mtime(self, val):
+    def st_mtime(self, val: float) -> None:
         """Set the modification time of the fake file."""
         self.stat_result.st_mtime = val
 
-    def set_large_file_size(self, st_size):
+    def set_large_file_size(self, st_size: int) -> None:
         """Sets the self.st_size attribute and replaces self.content with None.
 
         Provided specifically to simulate very large files without regards
@@ -369,25 +395,27 @@ class FakeFile:
         self.st_size = st_size
         self._byte_contents = None
 
-    def _check_positive_int(self, size):
+    def _check_positive_int(self, size: int) -> None:
         # the size should be an positive integer value
         if not is_int_type(size) or size < 0:
             self.filesystem.raise_os_error(errno.ENOSPC, self.name)
 
-    def is_large_file(self):
-        """Return `True` if this file was initialized with size but no contents.
+    def is_large_file(self) -> bool:
+        """Return `True` if this file was initialized with size
+         but no contents.
         """
         return self._byte_contents is None
 
-    def _encode_contents(self, contents):
+    def _encode_contents(
+            self, contents: Union[str, bytes, None]) -> Optional[bytes]:
         if is_unicode_string(contents):
             contents = bytes(
-                contents,
+                cast(str, contents),
                 self.encoding or locale.getpreferredencoding(False),
                 self.errors)
-        return contents
+        return cast(bytes, contents)
 
-    def _set_initial_contents(self, contents):
+    def set_initial_contents(self, contents: AnyStr) -> bool:
         """Sets the file contents and size.
            Called internally after initial file creation.
 
@@ -401,26 +429,27 @@ class FakeFile:
               OSError: if the st_size is not a non-negative integer,
                    or if st_size exceeds the available file system space
         """
-        contents = self._encode_contents(contents)
-        changed = self._byte_contents != contents
-        st_size = len(contents)
+        byte_contents = self._encode_contents(contents)
+        changed = self._byte_contents != byte_contents
+        st_size = len(byte_contents) if byte_contents else 0
 
         current_size = self.st_size or 0
         self.filesystem.change_disk_usage(
             st_size - current_size, self.name, self.st_dev)
         if self._byte_contents:
             self.size = 0
-        self._byte_contents = contents
+        self._byte_contents = byte_contents
         self.st_size = st_size
         self.epoch += 1
         return changed
 
-    def set_contents(self, contents, encoding=None):
+    def set_contents(self, contents: AnyStr,
+                     encoding: Optional[str] = None) -> bool:
         """Sets the file contents and size and increases the modification time.
         Also executes the side_effects if available.
 
         Args:
-          contents: (str, bytes, unicode) new content of file.
+          contents: (str, bytes) new content of file.
           encoding: (str) the encoding to be used for writing the contents
                     if they are a unicode string.
                     If not given, the locale preferred encoding is used.
@@ -430,19 +459,19 @@ class FakeFile:
                    or if it exceeds the available file system space.
         """
         self.encoding = real_encoding(encoding)
-        changed = self._set_initial_contents(contents)
+        changed = self.set_initial_contents(contents)
         if self._side_effect is not None:
             self._side_effect(self)
         return changed
 
     @property
-    def size(self):
+    def size(self) -> int:
         """Return the size in bytes of the file contents.
         """
         return self.st_size
 
     @size.setter
-    def size(self, st_size):
+    def size(self, st_size: int) -> None:
         """Resizes file content, padding with nulls if new size exceeds the
         old size.
 
@@ -467,24 +496,24 @@ class FakeFile:
         self.epoch += 1
 
     @property
-    def path(self):
+    def path(self) -> AnyStr:
         """Return the full path of the current object."""
-        names = []
-        obj = self
+        names: List[AnyString] = []
+        obj: Optional[FakeFile] = self
         while obj:
             names.insert(0, obj.name)
             obj = obj.parent_dir
-        sep = self.filesystem._path_separator(self.name)
+        sep = self.filesystem.get_path_separator(self.name)
         if names[0] == sep:
             names.pop(0)
-            dir_path = sep.join(names)
+            dir_path = sep.join(names)  # type: ignore[attr-defined]
             drive = self.filesystem.splitdrive(dir_path)[0]
             # if a Windows path already starts with a drive or UNC path,
             # no extra separator is needed
             if not drive:
                 dir_path = sep + dir_path
         else:
-            dir_path = sep.join(names)
+            dir_path = sep.join(names)  # type: ignore[attr-defined]
         dir_path = self.filesystem.absnormpath(dir_path)
         return dir_path
 
@@ -527,20 +556,20 @@ class FakeFile:
         """
         self.st_ctime = st_ctime
 
-    def __getattr__(self, item):
+    def __getattr__(self, item: str) -> Any:
         """Forward some properties to stat_result."""
         if item in self.stat_types:
             return getattr(self.stat_result, item)
-        return super(FakeFile, self).__getattr__(item)
+        return super().__getattribute__(item)
 
-    def __setattr__(self, key, value):
+    def __setattr__(self, key: str, value: Any) -> None:
         """Forward some properties to stat_result."""
         if key in self.stat_types:
             return setattr(self.stat_result, key, value)
-        return super(FakeFile, self).__setattr__(key, value)
+        return super().__setattr__(key, value)
 
-    def __str__(self):
-        return '%s(%o)' % (self.name, self.st_mode)
+    def __str__(self) -> str:
+        return '%r(%o)' % (self.name, self.st_mode)
 
     @Deprecator('st_ino')
     def SetIno(self, st_ino):
@@ -556,17 +585,17 @@ class FakeFile:
 
 
 class FakeNullFile(FakeFile):
-    def __init__(self, filesystem):
+    def __init__(self, filesystem: "FakeFilesystem") -> None:
         devnull = 'nul' if filesystem.is_windows_fs else '/dev/null'
         super(FakeNullFile, self).__init__(
-            devnull, filesystem=filesystem, contents=b'')
+            devnull, filesystem=filesystem, contents='')
 
     @property
-    def byte_contents(self):
+    def byte_contents(self) -> bytes:
         return b''
 
-    def _set_initial_contents(self, contents):
-        pass
+    def set_initial_contents(self, contents: AnyStr) -> bool:
+        return False
 
 
 Deprecator.add(FakeFile, FakeFile.set_large_file_size, 'SetLargeFileSize')
@@ -580,7 +609,8 @@ class FakeFileFromRealFile(FakeFile):
     The contents of the file are read on demand only.
     """
 
-    def __init__(self, file_path, filesystem, side_effect=None):
+    def __init__(self, file_path: str, filesystem: "FakeFilesystem",
+                 side_effect: Optional[Callable] = None) -> None:
         """
         Args:
             file_path: Path to the existing file.
@@ -590,13 +620,13 @@ class FakeFileFromRealFile(FakeFile):
             OSError: if the file does not exist in the real file system.
             OSError: if the file already exists in the fake file system.
         """
-        super(FakeFileFromRealFile, self).__init__(
+        super().__init__(
             name=os.path.basename(file_path), filesystem=filesystem,
             side_effect=side_effect)
         self.contents_read = False
 
     @property
-    def byte_contents(self):
+    def byte_contents(self) -> Optional[bytes]:
         if not self.contents_read:
             self.contents_read = True
             with io.open(self.file_path, 'rb') as f:
@@ -617,7 +647,8 @@ class FakeFileFromRealFile(FakeFile):
 class FakeDirectory(FakeFile):
     """Provides the appearance of a real directory."""
 
-    def __init__(self, name, perm_bits=PERM_DEF, filesystem=None):
+    def __init__(self, name: str, perm_bits: int = PERM_DEF,
+                 filesystem: Optional["FakeFilesystem"] = None):
         """
         Args:
             name:  name of the file/directory, without parent path information
@@ -626,28 +657,30 @@ class FakeDirectory(FakeFile):
                 is created
         """
         FakeFile.__init__(
-            self, name, S_IFDIR | perm_bits, {}, filesystem=filesystem)
+            self, name, S_IFDIR | perm_bits, '', filesystem=filesystem)
         # directories have the link count of contained entries,
-        # inclusing '.' and '..'
+        # including '.' and '..'
         self.st_nlink += 1
+        self._entries: Dict[str, AnyFile] = {}
 
-    def set_contents(self, contents, encoding=None):
+    def set_contents(self, contents: AnyStr,
+                     encoding: Optional[str] = None) -> bool:
         raise self.filesystem.raise_os_error(errno.EISDIR, self.path)
 
     @property
-    def contents(self):
+    def entries(self) -> Dict[str, FakeFile]:
         """Return the list of contained directory entries."""
-        return self.byte_contents
+        return self._entries
 
     @property
-    def ordered_dirs(self):
+    def ordered_dirs(self) -> List[str]:
         """Return the list of contained directory entry names ordered by
         creation order.
         """
         return [item[0] for item in sorted(
-            self.byte_contents.items(), key=lambda entry: entry[1].st_ino)]
+            self._entries.items(), key=lambda entry: entry[1].st_ino)]
 
-    def add_entry(self, path_object):
+    def add_entry(self, path_object: FakeFile) -> None:
         """Adds a child FakeFile to this directory.
 
         Args:
@@ -661,11 +694,11 @@ class FakeDirectory(FakeFile):
                 not self.filesystem.is_windows_fs):
             raise OSError(errno.EACCES, 'Permission Denied', self.path)
 
-        path_object_name = to_string(path_object.name)
-        if path_object_name in self.contents:
+        path_object_name: str = to_string(path_object.name)
+        if path_object_name in self.entries:
             self.filesystem.raise_os_error(errno.EEXIST, self.path)
 
-        self.contents[path_object_name] = path_object
+        self._entries[path_object_name] = path_object
         path_object.parent_dir = self
         if path_object.st_ino is None:
             self.filesystem.last_ino += 1
@@ -677,7 +710,7 @@ class FakeDirectory(FakeFile):
             self.filesystem.change_disk_usage(
                 path_object.size, path_object.name, self.st_dev)
 
-    def get_entry(self, pathname_name):
+    def get_entry(self, pathname_name: str) -> AnyFile:
         """Retrieves the specified child file or directory entry.
 
         Args:
@@ -690,17 +723,17 @@ class FakeDirectory(FakeFile):
             KeyError: if no child exists by the specified name.
         """
         pathname_name = self._normalized_entryname(pathname_name)
-        return self.contents[to_string(pathname_name)]
+        return self.entries[to_string(pathname_name)]
 
-    def _normalized_entryname(self, pathname_name):
+    def _normalized_entryname(self, pathname_name: str) -> str:
         if not self.filesystem.is_case_sensitive:
-            matching_names = [name for name in self.contents
+            matching_names = [name for name in self.entries
                               if name.lower() == pathname_name.lower()]
             if matching_names:
                 pathname_name = matching_names[0]
         return pathname_name
 
-    def remove_entry(self, pathname_name, recursive=True):
+    def remove_entry(self, pathname_name: str, recursive: bool = True) -> None:
         """Removes the specified child file or directory.
 
         Args:
@@ -727,8 +760,8 @@ class FakeDirectory(FakeFile):
                 self.filesystem.raise_os_error(errno.EACCES, pathname_name)
 
         if recursive and isinstance(entry, FakeDirectory):
-            while entry.contents:
-                entry.remove_entry(list(entry.contents)[0])
+            while entry.entries:
+                entry.remove_entry(list(entry.entries)[0])
         elif entry.st_nlink == 1:
             self.filesystem.change_disk_usage(
                 -entry.size, pathname_name, entry.st_dev)
@@ -737,36 +770,37 @@ class FakeDirectory(FakeFile):
         entry.st_nlink -= 1
         assert entry.st_nlink >= 0
 
-        del self.contents[to_string(pathname_name)]
+        del self.entries[to_string(pathname_name)]
 
     @property
-    def size(self):
+    def size(self) -> int:
         """Return the total size of all files contained in this directory tree.
         """
-        return sum([item[1].size for item in self.contents.items()])
+        return sum([item[1].size for item in self.entries.items()])
 
     @size.setter
-    def size(self, st_size):
-        super().size(st_size)
+    def size(self, st_size: int) -> None:
+        """Setting the size is an error for a directory."""
+        raise self.filesystem.raise_os_error(errno.EISDIR, self.path)
 
     @Deprecator('property size')
     def GetSize(self):
         return self.size
 
-    def has_parent_object(self, dir_object):
+    def has_parent_object(self, dir_object: "FakeDirectory") -> bool:
         """Return `True` if dir_object is a direct or indirect parent
         directory, or if both are the same object."""
-        obj = self
+        obj: Optional[FakeDirectory] = self
         while obj:
             if obj == dir_object:
                 return True
             obj = obj.parent_dir
         return False
 
-    def __str__(self):
+    def __str__(self) -> str:
         description = super(FakeDirectory, self).__str__() + ':\n'
-        for item in self.contents:
-            item_desc = self.contents[item].__str__()
+        for item in self.entries:
+            item_desc = self.entries[item].__str__()
             for line in item_desc.split('\n'):
                 if line:
                     description = description + '  ' + line + '\n'
@@ -785,8 +819,8 @@ class FakeDirectoryFromRealDirectory(FakeDirectory):
     The contents of the directory are read on demand only.
     """
 
-    def __init__(self, source_path, filesystem, read_only,
-                 target_path=None):
+    def __init__(self, source_path: AnyPath, filesystem: "FakeFilesystem",
+                 read_only: bool, target_path: Optional[AnyPath] = None):
         """
         Args:
             source_path: Full directory path.
@@ -804,7 +838,7 @@ class FakeDirectoryFromRealDirectory(FakeDirectory):
         target_path = target_path or source_path
         real_stat = os.stat(source_path)
         super(FakeDirectoryFromRealDirectory, self).__init__(
-            name=os.path.split(target_path)[1],
+            name=to_string(os.path.split(target_path)[1]),
             perm_bits=real_stat.st_mode,
             filesystem=filesystem)
 
@@ -813,12 +847,12 @@ class FakeDirectoryFromRealDirectory(FakeDirectory):
         self.st_mtime = real_stat.st_mtime
         self.st_gid = real_stat.st_gid
         self.st_uid = real_stat.st_uid
-        self.source_path = source_path
+        self.source_path = source_path  # type:ignore
         self.read_only = read_only
         self.contents_read = False
 
     @property
-    def contents(self):
+    def entries(self) -> Dict[str, FakeFile]:
         """Return the list of contained directory entries, loading them
         if not already loaded."""
         if not self.contents_read:
@@ -826,7 +860,7 @@ class FakeDirectoryFromRealDirectory(FakeDirectory):
             base = self.path
             for entry in os.listdir(self.source_path):
                 source_path = os.path.join(self.source_path, entry)
-                target_path = os.path.join(base, entry)
+                target_path = os.path.join(base, entry)  # type: ignore
                 if os.path.islink(source_path):
                     self.filesystem.add_real_symlink(source_path, target_path)
                 elif os.path.isdir(source_path):
@@ -835,18 +869,18 @@ class FakeDirectoryFromRealDirectory(FakeDirectory):
                 else:
                     self.filesystem.add_real_file(
                         source_path, self.read_only, target_path=target_path)
-        return self.byte_contents
+        return self._entries
 
     @property
-    def size(self):
+    def size(self) -> int:
         # we cannot get the size until the contents are loaded
         if not self.contents_read:
             return 0
         return super(FakeDirectoryFromRealDirectory, self).size
 
     @size.setter
-    def size(self, st_size):
-        super().size(st_size)
+    def size(self, st_size: int) -> None:
+        raise self.filesystem.raise_os_error(errno.EISDIR, self.path)
 
 
 class FakeFilesystem:
@@ -865,8 +899,9 @@ class FakeFilesystem:
             to the patcher object if using the pytest fs fixture.
     """
 
-    def __init__(self, path_separator=os.path.sep, total_size=None,
-                 patcher=None):
+    def __init__(self, path_separator: str = os.path.sep,
+                 total_size: int = None,
+                 patcher: Any = None) -> None:
         """
         Args:
             path_separator:  optional substitute for os.path.sep
@@ -878,8 +913,8 @@ class FakeFilesystem:
         >>> filesystem = FakeFilesystem(path_separator='/')
 
         """
-        self.path_separator = path_separator
-        self.alternative_path_separator = os.path.altsep
+        self.path_separator: str = path_separator
+        self.alternative_path_separator: Optional[str] = os.path.altsep
         self.patcher = patcher
         if path_separator != os.sep:
             self.alternative_path_separator = None
@@ -906,13 +941,13 @@ class FakeFilesystem:
 
         # A list of open file objects. Their position in the list is their
         # file descriptor number
-        self.open_files = []
+        self.open_files: List[Optional[List[AnyFileWrapper]]] = []
         # A heap containing all free positions in self.open_files list
-        self._free_fd_heap = []
+        self._free_fd_heap: List[int] = []
         # last used numbers for inodes (st_ino) and devices (st_dev)
         self.last_ino = 0
         self.last_dev = 0
-        self.mount_points = {}
+        self.mount_points: Dict[AnyString, Dict] = {}
         self.add_mount_point(self.root.name, total_size)
         self._add_standard_streams()
         self.dev_null = FakeNullFile(self)
@@ -920,17 +955,17 @@ class FakeFilesystem:
         self.patch_open_code = PatchMode.OFF
 
     @property
-    def is_linux(self):
+    def is_linux(self) -> bool:
         return not self.is_windows_fs and not self.is_macos
 
     @property
-    def os(self):
+    def os(self) -> OSType:
         """Return the real or simulated type of operating system."""
         return (OSType.WINDOWS if self.is_windows_fs else
                 OSType.MACOS if self.is_macos else OSType.LINUX)
 
     @os.setter
-    def os(self, value):
+    def os(self, value: OSType) -> None:
         """Set the simulated type of operating system underlying the fake
         file system."""
         self.is_windows_fs = value == OSType.WINDOWS
@@ -942,7 +977,7 @@ class FakeFilesystem:
         self.reset()
         FakePathModule.reset(self)
 
-    def reset(self, total_size=None):
+    def reset(self, total_size: Optional[int] = None):
         """Remove all file system contents and reset the root."""
         self.root = FakeDirectory(self.path_separator, filesystem=self)
         self.cwd = self.root.name
@@ -957,7 +992,7 @@ class FakeFilesystem:
         from pyfakefs import fake_pathlib
         fake_pathlib.init_module(self)
 
-    def pause(self):
+    def pause(self) -> None:
         """Pause the patching of the file system modules until `resume` is
         called. After that call, all file system calls are executed in the
         real file system.
@@ -973,7 +1008,7 @@ class FakeFilesystem:
                                'system object created by a Patcher object')
         self.patcher.pause()
 
-    def resume(self):
+    def resume(self) -> None:
         """Resume the patching of the file system modules if `pause` has
         been called before. After that call, all file system calls are
         executed in the fake file system.
@@ -986,18 +1021,20 @@ class FakeFilesystem:
                                'system object created by a Patcher object')
         self.patcher.resume()
 
-    def clear_cache(self):
+    def clear_cache(self) -> None:
         """Clear the cache of non-patched modules."""
         if self.patcher:
             self.patcher.clear_cache()
 
-    def line_separator(self):
+    def line_separator(self) -> str:
         return '\r\n' if self.is_windows_fs else '\n'
 
-    def _error_message(self, errno):
-        return os.strerror(errno) + ' in the fake filesystem'
+    def _error_message(self, err_no: int) -> str:
+        return os.strerror(err_no) + ' in the fake filesystem'
 
-    def raise_os_error(self, errno, filename=None, winerror=None):
+    def raise_os_error(self, err_no: int,
+                       filename: Optional[AnyString] = None,
+                       winerror: Optional[int] = None) -> NoReturn:
         """Raises OSError.
         The error message is constructed from the given error code and shall
         start with the error string issued in the real system.
@@ -1006,32 +1043,34 @@ class FakeFilesystem:
         real file system.
 
         Args:
-            errno: A numeric error code from the C variable errno.
+            err_no: A numeric error code from the C variable errno.
             filename: The name of the affected file, if any.
             winerror: Windows only - the specific Windows error code.
         """
-        message = self._error_message(errno)
+        message = self._error_message(err_no)
         if (winerror is not None and sys.platform == 'win32' and
                 self.is_windows_fs):
-            raise OSError(errno, message, filename, winerror)
-        raise OSError(errno, message, filename)
+            raise OSError(err_no, message, filename, winerror)
+        raise OSError(err_no, message, filename)
 
-    def _path_separator(self, path):
+    def get_path_separator(self, path: AnyStr) -> AnyStr:
         """Return the path separator as the same type as path"""
         return matching_string(path, self.path_separator)
 
-    def _alternative_path_separator(self, path):
+    def _alternative_path_separator(
+            self, path: AnyStr) -> Optional[AnyStr]:
         """Return the alternative path separator as the same type as path"""
         return matching_string(path, self.alternative_path_separator)
 
-    def _starts_with_sep(self, path):
+    def _starts_with_sep(self, path: AnyStr) -> bool:
         """Return True if path starts with a path separator."""
-        sep = self._path_separator(path)
+        sep = self.get_path_separator(path)
         altsep = self._alternative_path_separator(path)
         return (path.startswith(sep) or altsep is not None and
                 path.startswith(altsep))
 
-    def add_mount_point(self, path, total_size=None):
+    def add_mount_point(self, path: AnyStr,
+                        total_size: Optional[int] = None) -> Dict:
         """Add a new mount point for a filesystem device.
         The mount point gets a new unique device number.
 
@@ -1064,26 +1103,21 @@ class FakeFilesystem:
         root_dir.st_dev = self.last_dev
         return self.mount_points[path]
 
-    def _auto_mount_drive_if_needed(self, path, force=False):
+    def _auto_mount_drive_if_needed(self, path: AnyStr,
+                                    force: bool = False) -> Optional[Dict]:
         if (self.is_windows_fs and
                 (force or not self._mount_point_for_path(path))):
             drive = self.splitdrive(path)[0]
             if drive:
                 return self.add_mount_point(path=drive)
+        return None
 
-    def _mount_point_for_path(self, path):
-        def to_str(string):
-            """Convert the str, unicode or byte object to a str
-            using the default encoding."""
-            if string is None or isinstance(string, str):
-                return string
-            return string.decode(locale.getpreferredencoding(False))
-
+    def _mount_point_for_path(self, path: AnyStr) -> Dict:
         path = self.absnormpath(self._original_path(path))
         if path in self.mount_points:
             return self.mount_points[path]
         mount_path = matching_string(path, '')
-        drive = self.splitdrive(path)[:1]
+        drive = self.splitdrive(path)[0]
         for root_path in self.mount_points:
             root_path = matching_string(path, root_path)
             if drive and not root_path.startswith(drive):
@@ -1091,17 +1125,19 @@ class FakeFilesystem:
             if path.startswith(root_path) and len(root_path) > len(mount_path):
                 mount_path = root_path
         if mount_path:
-            return self.mount_points[to_str(mount_path)]
+            return self.mount_points[to_string(mount_path)]
         mount_point = self._auto_mount_drive_if_needed(path, force=True)
         assert mount_point
         return mount_point
 
-    def _mount_point_for_device(self, idev):
+    def _mount_point_for_device(self, idev: int) -> Optional[Dict]:
         for mount_point in self.mount_points.values():
             if mount_point['idev'] == idev:
                 return mount_point
+        return None
 
-    def get_disk_usage(self, path=None):
+    def get_disk_usage(
+            self, path: AnyStr = None) -> Tuple[int, int, int]:
         """Return the total, used and free disk space in bytes as named tuple,
         or placeholder values simulating unlimited space if not set.
 
@@ -1112,7 +1148,7 @@ class FakeFilesystem:
                 `path` resides.
                 Defaults to the root path (e.g. '/' on Unix systems).
         """
-        DiskUsage = namedtuple('usage', 'total, used, free')
+        DiskUsage = namedtuple('DiskUsage', 'total, used, free')
         if path is None:
             mount_point = self.mount_points[self.root.name]
         else:
@@ -1125,8 +1161,10 @@ class FakeFilesystem:
         return DiskUsage(
             1024 * 1024 * 1024 * 1024, 0, 1024 * 1024 * 1024 * 1024)
 
-    def set_disk_usage(self, total_size, path=None):
-        """Changes the total size of the file system, preserving the used space.
+    def set_disk_usage(
+            self, total_size: int, path: Optional[AnyStr] = None) -> None:
+        """Changes the total size of the file system, preserving the
+        used space.
         Example usage: set the size of an auto-mounted Windows drive.
 
         Args:
@@ -1139,15 +1177,16 @@ class FakeFilesystem:
         Raises:
             OSError: if the new space is smaller than the used size.
         """
-        if path is None:
-            path = self.root.name
-        mount_point = self._mount_point_for_path(path)
+        file_path: AnyStr = (path if path is not None  # type:ignore
+                             else self.root.name)
+        mount_point = self._mount_point_for_path(file_path)
         if (mount_point['total_size'] is not None and
                 mount_point['used_size'] > total_size):
             self.raise_os_error(errno.ENOSPC, path)
         mount_point['total_size'] = total_size
 
-    def change_disk_usage(self, usage_change, file_path, st_dev):
+    def change_disk_usage(self, usage_change: int,
+                          file_path: AnyStr, st_dev: int) -> None:
         """Change the used disk space by the given amount.
 
         Args:
@@ -1169,7 +1208,8 @@ class FakeFilesystem:
                     self.raise_os_error(errno.ENOSPC, file_path)
             mount_point['used_size'] += usage_change
 
-    def stat(self, entry_path, follow_symlinks=True):
+    def stat(self, entry_path: AnyStr,
+             follow_symlinks: bool = True):
         """Return the os.stat-like tuple for the FakeFile object of entry_path.
 
         Args:
@@ -1194,17 +1234,18 @@ class FakeFilesystem:
             # make sure stat raises if a parent dir is not readable
             parent_dir = file_object.parent_dir
             if parent_dir:
-                self.get_object(parent_dir.path)
+                self.get_object(parent_dir.path)  # type:ignore[arg-type]
 
         self.raise_for_filepath_ending_with_separator(
             entry_path, file_object, follow_symlinks)
 
         return file_object.stat_result.copy()
 
-    def raise_for_filepath_ending_with_separator(self, entry_path,
-                                                 file_object,
-                                                 follow_symlinks=True,
-                                                 macos_handling=False):
+    def raise_for_filepath_ending_with_separator(
+            self, entry_path: AnyStr,
+            file_object: FakeFile,
+            follow_symlinks: bool = True,
+            macos_handling: bool = False) -> None:
         if self.ends_with_path_separator(entry_path):
             if S_ISLNK(file_object.st_mode):
                 try:
@@ -1228,7 +1269,8 @@ class FakeFilesystem:
                             else errno.ENOTDIR)
                 self.raise_os_error(error_nr, entry_path)
 
-    def chmod(self, path, mode, follow_symlinks=True):
+    def chmod(self, path: AnyStr, mode: int,
+              follow_symlinks: bool = True) -> None:
         """Change the permissions of a file as encoded in integer mode.
 
         Args:
@@ -1248,7 +1290,10 @@ class FakeFilesystem:
                                    (mode & PERM_ALL))
         file_object.st_ctime = now()
 
-    def utime(self, path, times=None, *, ns=None, follow_symlinks=True):
+    def utime(self, path: AnyStr,
+              times: Optional[Tuple[Union[int, float], Union[int, float]]] =
+              None, *, ns: Optional[Tuple[int, int]] = None,
+              follow_symlinks: bool = True) -> None:
         """Change the access and modified times of a file.
 
         Args:
@@ -1290,7 +1335,9 @@ class FakeFilesystem:
             file_object.st_atime = current_time
             file_object.st_mtime = current_time
 
-    def _handle_utime_arg_errors(self, ns, times):
+    def _handle_utime_arg_errors(
+            self, ns: Optional[Tuple[int, int]],
+            times: Optional[Tuple[Union[int, float], Union[int, float]]]):
         if times is not None and ns is not None:
             raise ValueError(
                 "utime: you may specify either 'times' or 'ns' but not both")
@@ -1313,7 +1360,9 @@ class FakeFilesystem:
         """
         self.get_object(path).st_ino = st_ino
 
-    def _add_open_file(self, file_obj):
+    def _add_open_file(
+            self,
+            file_obj: AnyFileWrapper) -> int:
         """Add file_obj to the list of open files on the filesystem.
         Used internally to manage open files.
 
@@ -1333,7 +1382,7 @@ class FakeFilesystem:
         self.open_files.append([file_obj])
         return len(self.open_files) - 1
 
-    def _close_open_file(self, file_des):
+    def _close_open_file(self, file_des: int) -> None:
         """Remove file object with given descriptor from the list
         of open files.
 
@@ -1346,7 +1395,7 @@ class FakeFilesystem:
         self.open_files[file_des] = None
         heapq.heappush(self._free_fd_heap, file_des)
 
-    def get_open_file(self, file_des):
+    def get_open_file(self, file_des: int) -> AnyFileWrapper:
         """Return an open file.
 
         Args:
@@ -1361,12 +1410,14 @@ class FakeFilesystem:
         """
         if not is_int_type(file_des):
             raise TypeError('an integer is required')
-        if (file_des >= len(self.open_files) or
-                self.open_files[file_des] is None):
-            self.raise_os_error(errno.EBADF, str(file_des))
-        return self.open_files[file_des][0]
-
-    def has_open_file(self, file_object):
+        valid = file_des < len(self.open_files)
+        if valid:
+            file_list = self.open_files[file_des]
+            if file_list is not None:
+                return file_list[0]
+        self.raise_os_error(errno.EBADF, str(file_des))
+
+    def has_open_file(self, file_object: FakeFile) -> bool:
         """Return True if the given file object is in the list of open files.
 
         Args:
@@ -1378,13 +1429,13 @@ class FakeFilesystem:
         return (file_object in [wrappers[0].get_object()
                                 for wrappers in self.open_files if wrappers])
 
-    def _normalize_path_sep(self, path):
-        if self.alternative_path_separator is None or not path:
-            return path
-        return path.replace(self._alternative_path_separator(path),
-                            self._path_separator(path))
+    def _normalize_path_sep(self, path: AnyStr) -> AnyStr:
+        alt_sep = self._alternative_path_separator(path)
+        if alt_sep is not None:
+            return path.replace(alt_sep, self.get_path_separator(path))
+        return path
 
-    def normcase(self, path):
+    def normcase(self, path: AnyStr) -> AnyStr:
         """Replace all appearances of alternative path separator
         with path separator.
 
@@ -1396,10 +1447,10 @@ class FakeFilesystem:
         Returns:
             The normalized path that will be used internally.
         """
-        path = make_string_path(path)
-        return self._normalize_path_sep(path)
+        file_path = make_string_path(path)
+        return self._normalize_path_sep(file_path)
 
-    def normpath(self, path):
+    def normpath(self, path: AnyStr) -> AnyStr:
         """Mimic os.path.normpath using the specified path_separator.
 
         Mimics os.path.normpath using the path_separator that was specified
@@ -1421,14 +1472,14 @@ class FakeFilesystem:
             (str) A copy of path with empty components and dot components
             removed.
         """
-        path = self.normcase(path)
-        drive, path = self.splitdrive(path)
-        sep = self._path_separator(path)
-        is_absolute_path = path.startswith(sep)
-        path_components = path.split(sep)
-        collapsed_path_components = []
-        dot = matching_string(path, '.')
-        dotdot = matching_string(path, '..')
+        path_str = self.normcase(path)
+        drive, path_str = self.splitdrive(path_str)
+        sep = self.get_path_separator(path_str)
+        is_absolute_path = path_str.startswith(sep)
+        path_components: List[AnyStr] = path_str.split(sep)
+        collapsed_path_components: List[AnyStr] = []
+        dot = matching_string(path_str, '.')
+        dotdot = matching_string(path_str, '..')
         for component in path_components:
             if (not component) or (component == dot):
                 continue
@@ -1448,7 +1499,7 @@ class FakeFilesystem:
             collapsed_path = sep + collapsed_path
         return drive + collapsed_path or dot
 
-    def _original_path(self, path):
+    def _original_path(self, path: AnyStr) -> AnyStr:
         """Return a normalized case version of the given path for
         case-insensitive file systems. For case-sensitive file systems,
         return path unchanged.
@@ -1463,8 +1514,9 @@ class FakeFilesystem:
         def components_to_path():
             if len(path_components) > len(normalized_components):
                 normalized_components.extend(
-                    path_components[len(normalized_components):])
-            sep = self._path_separator(path)
+                    to_string(p) for p in path_components[len(
+                        normalized_components):])
+            sep = self.path_separator
             normalized_path = sep.join(normalized_components)
             if (self._starts_with_sep(path)
                     and not self._starts_with_sep(normalized_path)):
@@ -1479,17 +1531,18 @@ class FakeFilesystem:
         for component in path_components:
             if not isinstance(current_dir, FakeDirectory):
                 return components_to_path()
-            dir_name, current_dir = self._directory_content(
-                current_dir, component)
-            if current_dir is None or (
-                    isinstance(current_dir, FakeDirectory) and
-                    current_dir._byte_contents is None and
-                    current_dir.st_size == 0):
+            dir_name, directory = self._directory_content(
+                current_dir, to_string(component))
+            if directory is None or (
+                    isinstance(directory, FakeDirectory) and
+                    directory._byte_contents is None and
+                    directory.st_size == 0):
                 return components_to_path()
+            current_dir = cast(FakeDirectory, directory)
             normalized_components.append(dir_name)
         return components_to_path()
 
-    def absnormpath(self, path):
+    def absnormpath(self, path: AnyStr) -> AnyStr:
         """Absolutize and minimalize the given path.
 
         Forces all relative paths to be absolute, and normalizes the path to
@@ -1505,20 +1558,20 @@ class FakeFilesystem:
         path = self.normcase(path)
         cwd = matching_string(path, self.cwd)
         if not path:
-            path = self.path_separator
+            path = matching_string(path, self.path_separator)
         if path == matching_string(path, '.'):
             path = cwd
         elif not self._starts_with_root_path(path):
             # Prefix relative paths with cwd, if cwd is not root.
             root_name = matching_string(path, self.root.name)
             empty = matching_string(path, '')
-            path = self._path_separator(path).join(
+            path = self.get_path_separator(path).join(
                 (cwd != root_name and cwd or empty, path))
         if path == matching_string(path, '.'):
             path = cwd
         return self.normpath(path)
 
-    def splitpath(self, path):
+    def splitpath(self, path: AnyStr) -> Tuple[AnyStr, AnyStr]:
         """Mimic os.path.split using the specified path_separator.
 
         Mimics os.path.split using the path_separator that was specified
@@ -1533,9 +1586,9 @@ class FakeFilesystem:
         """
         full_path = path
         path = self.normcase(path)
-        sep = self._path_separator(path)
+        sep: AnyStr = self.get_path_separator(path)
         drive, path = self.splitdrive(path)
-        path_components = path.split(sep)
+        path_components: List[AnyStr] = path.split(sep)
         if not path_components:
             return drive, matching_string(path, '')
         basename = path_components.pop()
@@ -1557,7 +1610,7 @@ class FakeFilesystem:
             drive += sep
         return drive or sep, basename
 
-    def splitdrive(self, path):
+    def splitdrive(self, path: AnyStr) -> Tuple[AnyStr, AnyStr]:
         """Splits the path into the drive part and the rest of the path.
 
         Taken from Windows specific implementation in Python 3.5
@@ -1571,35 +1624,36 @@ class FakeFilesystem:
             an empty string and the full path if drive letters are
             not supported or no drive is present.
         """
-        path = make_string_path(path)
+        path_str = make_string_path(path)
         if self.is_windows_fs:
-            if len(path) >= 2:
-                path = self.normcase(path)
-                sep = self._path_separator(path)
-                # UNC path handling
-                if (path[0:2] == sep * 2) and (
-                        path[2:3] != sep):
-                    # UNC path handling - splits off the mount point
+            if len(path_str) >= 2:
+                path_str = self.normcase(path_str)
+                sep = self.get_path_separator(path_str)
+                # UNC path_str handling
+                if (path_str[0:2] == sep * 2) and (
+                        path_str[2:3] != sep):
+                    # UNC path_str handling - splits off the mount point
                     # instead of the drive
-                    sep_index = path.find(sep, 2)
+                    sep_index = path_str.find(sep, 2)
                     if sep_index == -1:
-                        return path[:0], path
-                    sep_index2 = path.find(sep, sep_index + 1)
+                        return path_str[:0], path_str
+                    sep_index2 = path_str.find(sep, sep_index + 1)
                     if sep_index2 == sep_index + 1:
-                        return path[:0], path
+                        return path_str[:0], path_str
                     if sep_index2 == -1:
-                        sep_index2 = len(path)
-                    return path[:sep_index2], path[sep_index2:]
-                if path[1:2] == matching_string(path, ':'):
-                    return path[:2], path[2:]
-        return path[:0], path
-
-    def _join_paths_with_drive_support(self, *all_paths):
+                        sep_index2 = len(path_str)
+                    return path_str[:sep_index2], path_str[sep_index2:]
+                if path_str[1:2] == matching_string(path_str, ':'):
+                    return path_str[:2], path_str[2:]
+        return path_str[:0], path_str
+
+    def _join_paths_with_drive_support(
+            self, *all_paths: AnyStr) -> AnyStr:
         """Taken from Python 3.5 os.path.join() code in ntpath.py
         and slightly adapted"""
         base_path = all_paths[0]
         paths_to_add = all_paths[1:]
-        sep = self._path_separator(base_path)
+        sep = self.get_path_separator(base_path)
         seps = [sep, self._alternative_path_separator(base_path)]
         result_drive, result_path = self.splitdrive(base_path)
         for path in paths_to_add:
@@ -1630,7 +1684,7 @@ class FakeFilesystem:
             return result_drive + sep + result_path
         return result_drive + result_path
 
-    def joinpaths(self, *paths):
+    def joinpaths(self, *paths: AnyStr) -> AnyStr:
         """Mimic os.path.join using the specified path_separator.
 
         Args:
@@ -1640,15 +1694,14 @@ class FakeFilesystem:
             (str) The paths joined by the path separator, starting with
             the last absolute path in paths.
         """
-        if sys.version_info >= (3, 6):
-            paths = [os.fspath(path) for path in paths]
-        if len(paths) == 1:
+        file_paths = [os.fspath(path) for path in paths]
+        if len(file_paths) == 1:
             return paths[0]
         if self.is_windows_fs:
-            return self._join_paths_with_drive_support(*paths)
+            return self._join_paths_with_drive_support(*file_paths)
         joined_path_segments = []
-        sep = self._path_separator(paths[0])
-        for path_segment in paths:
+        sep = self.get_path_separator(file_paths[0])
+        for path_segment in file_paths:
             if self._starts_with_root_path(path_segment):
                 # An absolute path
                 joined_path_segments = [path_segment]
@@ -1658,9 +1711,15 @@ class FakeFilesystem:
                     joined_path_segments.append(sep)
                 if path_segment:
                     joined_path_segments.append(path_segment)
-        return matching_string(paths[0], '').join(joined_path_segments)
+        return matching_string(file_paths[0], '').join(joined_path_segments)
+
+    @overload
+    def _path_components(self, path: str) -> List[str]: ...
+
+    @overload
+    def _path_components(self, path: bytes) -> List[bytes]: ...
 
-    def _path_components(self, path):
+    def _path_components(self, path: AnyStr) -> List[AnyStr]:
         """Breaks the path into a list of component names.
 
         Does not include the root directory as a component, as all paths
@@ -1673,7 +1732,7 @@ class FakeFilesystem:
             path_components = self._path_components(file_path)
             current_dir = self.root
             for component in path_components:
-                if component not in current_dir.contents:
+                if component not in current_dir.entries:
                     raise OSError
                 _do_stuff_with_component(current_dir, component)
                 current_dir = current_dir.get_entry(component)
@@ -1684,10 +1743,10 @@ class FakeFilesystem:
         Returns:
             The list of names split from path.
         """
-        if not path or path == self._path_separator(path):
+        if not path or path == self.get_path_separator(path):
             return []
         drive, path = self.splitdrive(path)
-        path_components = path.split(self._path_separator(path))
+        path_components = path.split(self.get_path_separator(path))
         assert drive or path_components
         if not path_components[0]:
             if len(path_components) > 1 and not path_components[1]:
@@ -1699,7 +1758,7 @@ class FakeFilesystem:
             path_components.insert(0, drive)
         return path_components
 
-    def _starts_with_drive_letter(self, file_path):
+    def _starts_with_drive_letter(self, file_path: AnyStr) -> bool:
         """Return True if file_path starts with a drive letter.
 
         Args:
@@ -1725,7 +1784,7 @@ class FakeFilesystem:
                     return False
         return False
 
-    def _starts_with_root_path(self, file_path):
+    def _starts_with_root_path(self, file_path: AnyStr) -> bool:
         root_name = matching_string(file_path, self.root.name)
         file_path = self._normalize_path_sep(file_path)
         return (file_path.startswith(root_name) or
@@ -1733,50 +1792,53 @@ class FakeFilesystem:
                     root_name.lower()) or
                 self._starts_with_drive_letter(file_path))
 
-    def _is_root_path(self, file_path):
+    def _is_root_path(self, file_path: AnyStr) -> bool:
         root_name = matching_string(file_path, self.root.name)
         return (file_path == root_name or not self.is_case_sensitive and
                 file_path.lower() == root_name.lower() or
                 2 <= len(file_path) <= 3 and
                 self._starts_with_drive_letter(file_path))
 
-    def ends_with_path_separator(self, file_path):
+    def ends_with_path_separator(self, path: Union[int, AnyPath]) -> bool:
         """Return True if ``file_path`` ends with a valid path separator."""
-        if is_int_type(file_path):
+        if isinstance(path, int):
             return False
-        file_path = make_string_path(file_path)
-        return (file_path and
-                file_path not in (self.path_separator,
-                                  self.alternative_path_separator) and
-                (file_path.endswith(self._path_separator(file_path)) or
-                 self.alternative_path_separator is not None and
-                 file_path.endswith(
-                     self._alternative_path_separator(file_path))))
-
-    def is_filepath_ending_with_separator(self, path):
+        file_path = make_string_path(path)
+        if not file_path:
+            return False
+        sep = self.get_path_separator(file_path)
+        altsep = self._alternative_path_separator(file_path)
+        return (file_path not in (sep, altsep) and
+                (file_path.endswith(sep) or
+                 altsep is not None and file_path.endswith(altsep)))
+
+    def is_filepath_ending_with_separator(self, path: AnyStr) -> bool:
         if not self.ends_with_path_separator(path):
             return False
         return self.isfile(self._path_without_trailing_separators(path))
 
-    def _directory_content(self, directory, component):
+    def _directory_content(self, directory: FakeDirectory,
+                           component: str) -> Tuple[Optional[str],
+                                                    Optional[AnyFile]]:
         if not isinstance(directory, FakeDirectory):
             return None, None
-        if component in directory.contents:
-            return component, directory.contents[component]
+        if component in directory.entries:
+            return component, directory.entries[component]
         if not self.is_case_sensitive:
-            matching_content = [(subdir, directory.contents[subdir]) for
-                                subdir in directory.contents
+            matching_content = [(subdir, directory.entries[subdir]) for
+                                subdir in directory.entries
                                 if subdir.lower() == component.lower()]
             if matching_content:
                 return matching_content[0]
 
         return None, None
 
-    def exists(self, file_path, check_link=False):
+    def exists(self, file_path: AnyPath, check_link: bool = False) -> bool:
         """Return true if a path points to an existing file system object.
 
         Args:
             file_path:  The path to examine.
+            check_link: If True, links are not followed
 
         Returns:
             (bool) True if the corresponding object exists.
@@ -1786,31 +1848,34 @@ class FakeFilesystem:
         """
         if check_link and self.islink(file_path):
             return True
-        file_path = to_string(make_string_path(file_path))
-        if file_path is None:
+        path = to_string(make_string_path(file_path))
+        if path is None:
             raise TypeError
-        if not file_path:
+        if not path:
             return False
-        if file_path == self.dev_null.name:
+        if path == self.dev_null.name:
             return not self.is_windows_fs or sys.version_info >= (3, 8)
         try:
-            if self.is_filepath_ending_with_separator(file_path):
+            if self.is_filepath_ending_with_separator(path):
                 return False
-            file_path = self.resolve_path(file_path)
+            path = self.resolve_path(path)
         except OSError:
             return False
-        if file_path == self.root.name:
+        if path == self.root.name:
             return True
 
-        path_components = self._path_components(file_path)
+        path_components: List[str] = self._path_components(path)
         current_dir = self.root
         for component in path_components:
-            current_dir = self._directory_content(current_dir, component)[1]
-            if not current_dir:
+            directory = self._directory_content(
+                current_dir, to_string(component))[1]
+            if directory is None:
                 return False
+            current_dir = cast(FakeDirectory, directory)
         return True
 
-    def resolve_path(self, file_path, allow_fd=False, raw_io=True):
+    def resolve_path(self,
+                     file_path: AnyStr, allow_fd: bool = False) -> AnyStr:
         """Follow a path, resolving symlinks.
 
         ResolvePath traverses the filesystem along the specified file path,
@@ -1839,10 +1904,9 @@ class FakeFilesystem:
         Args:
             file_path: The path to examine.
             allow_fd: If `True`, `file_path` may be open file descriptor.
-            raw_io: `True` if called from low-level I/O functions.
 
         Returns:
-            The resolved_path (string) or None.
+            The resolved_path (str or byte).
 
         Raises:
             TypeError: if `file_path` is `None`.
@@ -1851,40 +1915,41 @@ class FakeFilesystem:
 
         if allow_fd and isinstance(file_path, int):
             return self.get_open_file(file_path).get_object().path
-        file_path = make_string_path(file_path)
-        if file_path is None:
+        path = make_string_path(file_path)
+        if path is None:
             # file.open(None) raises TypeError, so mimic that.
             raise TypeError('Expected file system path string, received None')
-        if not file_path or not self._valid_relative_path(file_path):
+        if not path or not self._valid_relative_path(path):
             # file.open('') raises OSError, so mimic that, and validate that
             # all parts of a relative path exist.
-            self.raise_os_error(errno.ENOENT, file_path)
-        file_path = self.absnormpath(self._original_path(file_path))
-        if self._is_root_path(file_path):
-            return file_path
-        if file_path == self.dev_null.name:
-            return file_path
-        path_components = self._path_components(file_path)
-        resolved_components = self._resolve_components(path_components, raw_io)
+            self.raise_os_error(errno.ENOENT, path)
+        path = self.absnormpath(self._original_path(path))
+        if self._is_root_path(path):
+            return path
+        if path == matching_string(path, self.dev_null.name):
+            return path
+        path_components = self._path_components(path)
+        resolved_components = self._resolve_components(path_components)
         return self._components_to_path(resolved_components)
 
     def _components_to_path(self, component_folders):
-        sep = (self._path_separator(component_folders[0])
+        sep = (self.get_path_separator(component_folders[0])
                if component_folders else self.path_separator)
         path = sep.join(component_folders)
         if not self._starts_with_root_path(path):
             path = sep + path
         return path
 
-    def _resolve_components(self, path_components, raw_io):
+    def _resolve_components(self, components: List[AnyStr]) -> List[str]:
         current_dir = self.root
         link_depth = 0
-        resolved_components = []
+        path_components = [to_string(comp) for comp in components]
+        resolved_components: List[str] = []
         while path_components:
             component = path_components.pop(0)
             resolved_components.append(component)
-            current_dir = self._directory_content(current_dir, component)[1]
-            if current_dir is None:
+            directory = self._directory_content(current_dir, component)[1]
+            if directory is None:
                 # The component of the path at this point does not actually
                 # exist in the folder.  We can't resolve the path any more.
                 # It is legal to link to a file that does not yet exist, so
@@ -1893,9 +1958,8 @@ class FakeFilesystem:
                 # return that.
                 resolved_components.extend(path_components)
                 break
-
             # Resolve any possible symlinks in the current path component.
-            if S_ISLNK(current_dir.st_mode):
+            elif S_ISLNK(directory.st_mode):
                 # This link_depth check is not really meant to be an accurate
                 # check. It is just a quick hack to prevent us from looping
                 # forever on cycles.
@@ -1903,7 +1967,7 @@ class FakeFilesystem:
                     self.raise_os_error(errno.ELOOP,
                                         self._components_to_path(
                                             resolved_components))
-                link_path = self._follow_link(resolved_components, current_dir)
+                link_path = self._follow_link(resolved_components, directory)
 
                 # Following the link might result in the complete replacement
                 # of the current_dir, so we evaluate the entire resulting path.
@@ -1912,9 +1976,11 @@ class FakeFilesystem:
                 resolved_components = []
                 current_dir = self.root
                 link_depth += 1
+            else:
+                current_dir = cast(FakeDirectory, directory)
         return resolved_components
 
-    def _valid_relative_path(self, file_path):
+    def _valid_relative_path(self, file_path: AnyStr) -> bool:
         if self.is_windows_fs:
             return True
         slash_dotdot = matching_string(
@@ -1925,7 +1991,8 @@ class FakeFilesystem:
                 return False
         return True
 
-    def _follow_link(self, link_path_components, link):
+    def _follow_link(self, link_path_components: List[str],
+                     link: AnyFile) -> str:
         """Follow a link w.r.t. a path resolved so far.
 
         The component is either a real file, which is a no-op, or a
@@ -1949,27 +2016,31 @@ class FakeFilesystem:
             OSError: if there are too many levels of symbolic link
         """
         link_path = link.contents
-        # ignore UNC prefix for local files
-        if self.is_windows_fs and link_path.startswith('\\\\?\\'):
-            link_path = link_path[4:]
-        sep = self._path_separator(link_path)
-        # For links to absolute paths, we want to throw out everything
-        # in the path built so far and replace with the link. For relative
-        # links, we have to append the link to what we have so far,
-        if not self._starts_with_root_path(link_path):
-            # Relative path. Append remainder of path to what we have
-            # processed so far, excluding the name of the link itself.
-            # /a/b => ../c  should yield /a/../c
-            # (which will normalize to /c)
-            # /a/b => d should yield a/d
-            components = link_path_components[:-1]
-            components.append(link_path)
-            link_path = sep.join(components)
-        # Don't call self.NormalizePath(), as we don't want to prepend
-        # self.cwd.
-        return self.normpath(link_path)
-
-    def get_object_from_normpath(self, file_path, check_read_perm=True):
+        if link_path is not None:
+            # ignore UNC prefix for local files
+            if self.is_windows_fs and link_path.startswith('\\\\?\\'):
+                link_path = link_path[4:]
+            sep = self.get_path_separator(link_path)
+            # For links to absolute paths, we want to throw out everything
+            # in the path built so far and replace with the link. For relative
+            # links, we have to append the link to what we have so far,
+            if not self._starts_with_root_path(link_path):
+                # Relative path. Append remainder of path to what we have
+                # processed so far, excluding the name of the link itself.
+                # /a/b => ../c  should yield /a/../c
+                # (which will normalize to /c)
+                # /a/b => d should yield a/d
+                components = link_path_components[:-1]
+                components.append(link_path)
+                link_path = sep.join(components)
+            # Don't call self.NormalizePath(), as we don't want to prepend
+            # self.cwd.
+            return self.normpath(link_path)
+        raise ValueError("Invalid link")
+
+    def get_object_from_normpath(self,
+                                 file_path: AnyPath,
+                                 check_read_perm: bool = True) -> AnyFile:
         """Search for the specified filesystem object within the fake
         filesystem.
 
@@ -1985,32 +2056,35 @@ class FakeFilesystem:
         Raises:
             OSError: if the object is not found.
         """
-        file_path = make_string_path(file_path)
-        if file_path == self.root.name:
+        path = make_string_path(file_path)
+        if path == self.root.name:
             return self.root
-        if file_path == self.dev_null.name:
+        if path == self.dev_null.name:
             return self.dev_null
 
-        file_path = self._original_path(file_path)
-        path_components = self._path_components(file_path)
-        target_object = self.root
+        path = self._original_path(path)
+        path_components = self._path_components(path)
+        target = self.root
         try:
             for component in path_components:
-                if S_ISLNK(target_object.st_mode):
-                    target_object = self.resolve(target_object.contents)
-                if not S_ISDIR(target_object.st_mode):
+                if S_ISLNK(target.st_mode):
+                    if target.contents:
+                        target = cast(FakeDirectory,
+                                      self.resolve(target.contents))
+                if not S_ISDIR(target.st_mode):
                     if not self.is_windows_fs:
-                        self.raise_os_error(errno.ENOTDIR, file_path)
-                    self.raise_os_error(errno.ENOENT, file_path)
-                target_object = target_object.get_entry(component)
-                if (not is_root() and check_read_perm and target_object and
-                        not target_object.st_mode & PERM_READ):
-                    self.raise_os_error(errno.EACCES, target_object.path)
+                        self.raise_os_error(errno.ENOTDIR, path)
+                    self.raise_os_error(errno.ENOENT, path)
+                target = target.get_entry(component)  # type: ignore
+                if (not is_root() and check_read_perm and target and
+                        not target.st_mode & PERM_READ):
+                    self.raise_os_error(errno.EACCES, target.path)
         except KeyError:
-            self.raise_os_error(errno.ENOENT, file_path)
-        return target_object
+            self.raise_os_error(errno.ENOENT, path)
+        return target
 
-    def get_object(self, file_path, check_read_perm=True):
+    def get_object(self, file_path: AnyPath,
+                   check_read_perm: bool = True) -> FakeFile:
         """Search for the specified filesystem object within the fake
         filesystem.
 
@@ -2025,12 +2099,14 @@ class FakeFilesystem:
         Raises:
             OSError: if the object is not found.
         """
-        file_path = make_string_path(file_path)
-        file_path = self.absnormpath(self._original_path(file_path))
-        return self.get_object_from_normpath(file_path, check_read_perm)
+        path = make_string_path(file_path)
+        path = self.absnormpath(self._original_path(path))
+        return self.get_object_from_normpath(path, check_read_perm)
 
-    def resolve(self, file_path, follow_symlinks=True, allow_fd=False,
-                check_read_perm=True):
+    def resolve(self, file_path: AnyStr,
+                follow_symlinks: bool = True,
+                allow_fd: bool = False,
+                check_read_perm: bool = True) -> FakeFile:
         """Search for the specified filesystem object, resolving all links.
 
         Args:
@@ -2051,15 +2127,14 @@ class FakeFilesystem:
             if allow_fd:
                 return self.get_open_file(file_path).get_object()
             raise TypeError('path should be string, bytes or '
-                            'os.PathLike (if supported), not int')
+                            'os.PathLike, not int')
 
         if follow_symlinks:
-            file_path = make_string_path(file_path)
             return self.get_object_from_normpath(self.resolve_path(
                 file_path, check_read_perm), check_read_perm)
         return self.lresolve(file_path)
 
-    def lresolve(self, path):
+    def lresolve(self, path: AnyPath) -> FakeFile:
         """Search for the specified object, resolving only parent links.
 
         This is analogous to the stat/lstat difference.  This resolves links
@@ -2074,37 +2149,37 @@ class FakeFilesystem:
         Raises:
             OSError: if the object is not found.
         """
-        path = make_string_path(path)
-        if not path:
-            raise OSError(errno.ENOENT, path)
-        if path == self.root.name:
+        path_str = make_string_path(path)
+        if not path_str:
+            raise OSError(errno.ENOENT, path_str)
+        if path_str == self.root.name:
             # The root directory will never be a link
             return self.root
 
         # remove trailing separator
-        path = self._path_without_trailing_separators(path)
-        if path == matching_string(path, '.'):
-            path = self.cwd
-        path = self._original_path(path)
+        path_str = self._path_without_trailing_separators(path_str)
+        if path_str == matching_string(path_str, '.'):
+            path_str = matching_string(path_str, self.cwd)
+        path_str = self._original_path(path_str)
 
-        parent_directory, child_name = self.splitpath(path)
+        parent_directory, child_name = self.splitpath(path_str)
         if not parent_directory:
-            parent_directory = self.cwd
+            parent_directory = matching_string(path_str, self.cwd)
         try:
             parent_obj = self.resolve(parent_directory)
             assert parent_obj
             if not isinstance(parent_obj, FakeDirectory):
                 if not self.is_windows_fs and isinstance(parent_obj, FakeFile):
-                    self.raise_os_error(errno.ENOTDIR, path)
-                self.raise_os_error(errno.ENOENT, path)
+                    self.raise_os_error(errno.ENOTDIR, path_str)
+                self.raise_os_error(errno.ENOENT, path_str)
             if not parent_obj.st_mode & PERM_READ:
                 self.raise_os_error(errno.EACCES, parent_directory)
-            return (parent_obj.get_entry(child_name) if child_name
+            return (parent_obj.get_entry(to_string(child_name)) if child_name
                     else parent_obj)
         except KeyError:
-            self.raise_os_error(errno.ENOENT, path)
+            self.raise_os_error(errno.ENOENT, path_str)
 
-    def add_object(self, file_path, file_object):
+    def add_object(self, file_path: AnyStr, file_object: AnyFile) -> None:
         """Add a fake file or directory into the filesystem at file_path.
 
         Args:
@@ -2118,13 +2193,15 @@ class FakeFilesystem:
         if not file_path:
             target_directory = self.root
         else:
-            target_directory = self.resolve(file_path)
+            target_directory = cast(FakeDirectory, self.resolve(file_path))
             if not S_ISDIR(target_directory.st_mode):
                 error = errno.ENOENT if self.is_windows_fs else errno.ENOTDIR
                 self.raise_os_error(error, file_path)
         target_directory.add_entry(file_object)
 
-    def rename(self, old_file_path, new_file_path, force_replace=False):
+    def rename(self, old_file_path: AnyPath,
+               new_file_path: AnyPath,
+               force_replace: bool = False) -> None:
         """Renames a FakeFile object at old_file_path to new_file_path,
         preserving all properties.
 
@@ -2149,52 +2226,56 @@ class FakeFilesystem:
             OSError: if the file would be moved to another filesystem
                 (e.g. mount point).
         """
-        ends_with_sep = self.ends_with_path_separator(old_file_path)
-        old_file_path = self.absnormpath(old_file_path)
-        new_file_path = self.absnormpath(new_file_path)
-        if not self.exists(old_file_path, check_link=True):
-            self.raise_os_error(errno.ENOENT, old_file_path, 2)
+        old_path = make_string_path(old_file_path)
+        new_path = make_string_path(new_file_path)
+        ends_with_sep = self.ends_with_path_separator(old_path)
+        old_path = self.absnormpath(old_path)
+        new_path = self.absnormpath(new_path)
+        if not self.exists(old_path, check_link=True):
+            self.raise_os_error(errno.ENOENT, old_path, 2)
         if ends_with_sep:
-            self._handle_broken_link_with_trailing_sep(old_file_path)
+            self._handle_broken_link_with_trailing_sep(old_path)
 
-        old_object = self.lresolve(old_file_path)
+        old_object = self.lresolve(old_path)
         if not self.is_windows_fs:
             self._handle_posix_dir_link_errors(
-                new_file_path, old_file_path, ends_with_sep)
+                new_path, old_path, ends_with_sep)
 
-        if self.exists(new_file_path, check_link=True):
-            new_file_path = self._rename_to_existing_path(
-                force_replace, new_file_path, old_file_path,
+        if self.exists(new_path, check_link=True):
+            renamed_path = self._rename_to_existing_path(
+                force_replace, new_path, old_path,
                 old_object, ends_with_sep)
 
-        if not new_file_path:
-            return
+            if renamed_path is None:
+                return
+            else:
+                new_path = renamed_path
 
-        old_dir, old_name = self.splitpath(old_file_path)
-        new_dir, new_name = self.splitpath(new_file_path)
+        old_dir, old_name = self.splitpath(old_path)
+        new_dir, new_name = self.splitpath(new_path)
         if not self.exists(new_dir):
             self.raise_os_error(errno.ENOENT, new_dir)
         old_dir_object = self.resolve(old_dir)
         new_dir_object = self.resolve(new_dir)
         if old_dir_object.st_dev != new_dir_object.st_dev:
-            self.raise_os_error(errno.EXDEV, old_file_path)
+            self.raise_os_error(errno.EXDEV, old_path)
         if not S_ISDIR(new_dir_object.st_mode):
             self.raise_os_error(
                 errno.EACCES if self.is_windows_fs else errno.ENOTDIR,
-                new_file_path)
+                new_path)
         if new_dir_object.has_parent_object(old_object):
-            self.raise_os_error(errno.EINVAL, new_file_path)
+            self.raise_os_error(errno.EINVAL, new_path)
 
         object_to_rename = old_dir_object.get_entry(old_name)
         old_dir_object.remove_entry(old_name, recursive=False)
         object_to_rename.name = new_name
         new_name = new_dir_object._normalized_entryname(new_name)
-        if new_name in new_dir_object.contents:
+        if new_name in new_dir_object.entries:
             # in case of overwriting remove the old entry first
             new_dir_object.remove_entry(new_name)
         new_dir_object.add_entry(object_to_rename)
 
-    def _handle_broken_link_with_trailing_sep(self, path):
+    def _handle_broken_link_with_trailing_sep(self, path: AnyStr) -> None:
         # note that the check for trailing sep has to be done earlier
         if self.islink(path):
             if not self.exists(path):
@@ -2202,8 +2283,9 @@ class FakeFilesystem:
                          errno.EINVAL if self.is_windows_fs else errno.ENOTDIR)
                 self.raise_os_error(error, path)
 
-    def _handle_posix_dir_link_errors(self, new_file_path, old_file_path,
-                                      ends_with_sep):
+    def _handle_posix_dir_link_errors(self, new_file_path: AnyStr,
+                                      old_file_path: AnyStr,
+                                      ends_with_sep: bool) -> None:
         if (self.isdir(old_file_path, follow_symlinks=False) and
                 self.islink(new_file_path)):
             self.raise_os_error(errno.ENOTDIR, new_file_path)
@@ -2217,19 +2299,21 @@ class FakeFilesystem:
                 old_file_path == new_file_path and not self.is_windows_fs):
             self.raise_os_error(errno.ENOTDIR, new_file_path)
 
-    def _rename_to_existing_path(self, force_replace, new_file_path,
-                                 old_file_path, old_object, ends_with_sep):
+    def _rename_to_existing_path(self, force_replace: bool,
+                                 new_file_path: AnyStr,
+                                 old_file_path: AnyStr,
+                                 old_object: FakeFile,
+                                 ends_with_sep: bool) -> Optional[AnyStr]:
         new_object = self.get_object(new_file_path)
         if old_file_path == new_file_path:
             if not S_ISLNK(new_object.st_mode) and ends_with_sep:
                 error = errno.EINVAL if self.is_windows_fs else errno.ENOTDIR
                 self.raise_os_error(error, old_file_path)
-            return  # Nothing to do here.
+            return None  # Nothing to do here
 
         if old_object == new_object:
-            new_file_path = self._rename_same_object(
-                new_file_path, old_file_path)
-        elif (S_ISDIR(new_object.st_mode) or S_ISLNK(new_object.st_mode)):
+            return self._rename_same_object(new_file_path, old_file_path)
+        if S_ISDIR(new_object.st_mode) or S_ISLNK(new_object.st_mode):
             self._handle_rename_error_for_dir_or_link(
                 force_replace, new_file_path,
                 new_object, old_object, ends_with_sep)
@@ -2242,23 +2326,26 @@ class FakeFilesystem:
             self.remove_object(new_file_path)
         return new_file_path
 
-    def _handle_rename_error_for_dir_or_link(self, force_replace,
-                                             new_file_path, new_object,
-                                             old_object, ends_with_sep):
+    def _handle_rename_error_for_dir_or_link(self, force_replace: bool,
+                                             new_file_path: AnyStr,
+                                             new_object: FakeFile,
+                                             old_object: FakeFile,
+                                             ends_with_sep: bool) -> None:
         if self.is_windows_fs:
             if force_replace:
                 self.raise_os_error(errno.EACCES, new_file_path)
             else:
                 self.raise_os_error(errno.EEXIST, new_file_path)
         if not S_ISLNK(new_object.st_mode):
-            if new_object.contents:
+            if new_object.entries:
                 if (not S_ISLNK(old_object.st_mode) or
                         not ends_with_sep or not self.is_macos):
                     self.raise_os_error(errno.ENOTEMPTY, new_file_path)
             if S_ISREG(old_object.st_mode):
                 self.raise_os_error(errno.EISDIR, new_file_path)
 
-    def _rename_same_object(self, new_file_path, old_file_path):
+    def _rename_same_object(self, new_file_path: AnyStr,
+                            old_file_path: AnyStr) -> Optional[AnyStr]:
         do_rename = old_file_path.lower() == new_file_path.lower()
         if not do_rename:
             try:
@@ -2289,10 +2376,10 @@ class FakeFilesystem:
                 pass
         if not do_rename:
             # hard links to the same file - nothing to do
-            new_file_path = None
+            return None
         return new_file_path
 
-    def remove_object(self, file_path):
+    def remove_object(self, file_path: AnyStr) -> None:
         """Remove an existing file or directory.
 
         Args:
@@ -2315,13 +2402,14 @@ class FakeFilesystem:
         except AttributeError:
             self.raise_os_error(errno.ENOTDIR, file_path)
 
-    def make_string_path(self, path):
-        path = make_string_path(path)
-        os_sep = matching_string(path, os.sep)
-        fake_sep = matching_string(path, self.path_separator)
-        return path.replace(os_sep, fake_sep)
+    def make_string_path(self, path: AnyPath) -> AnyStr:
+        path_str = make_string_path(path)
+        os_sep = matching_string(path_str, os.sep)
+        fake_sep = matching_string(path_str, self.path_separator)
+        return path_str.replace(os_sep, fake_sep)  # type:ignore[return-value]
 
-    def create_dir(self, directory_path, perm_bits=PERM_DEF):
+    def create_dir(self, directory_path: AnyPath,
+                   perm_bits: int = PERM_DEF) -> FakeDirectory:
         """Create `directory_path`, and all the parent directories.
 
         Helper method to set up your test faster.
@@ -2336,17 +2424,18 @@ class FakeFilesystem:
         Raises:
             OSError: if the directory already exists.
         """
-        directory_path = self.make_string_path(directory_path)
-        directory_path = self.absnormpath(directory_path)
-        self._auto_mount_drive_if_needed(directory_path)
-        if self.exists(directory_path, check_link=True):
-            self.raise_os_error(errno.EEXIST, directory_path)
-        path_components = self._path_components(directory_path)
+        dir_path = self.make_string_path(directory_path)
+        dir_path = self.absnormpath(dir_path)
+        self._auto_mount_drive_if_needed(dir_path)
+        if self.exists(dir_path, check_link=True):
+            self.raise_os_error(errno.EEXIST, dir_path)
+        path_components = self._path_components(dir_path)
         current_dir = self.root
 
         new_dirs = []
-        for component in path_components:
-            directory = self._directory_content(current_dir, component)[1]
+        for component in [to_string(p) for p in path_components]:
+            directory = self._directory_content(
+                current_dir, to_string(component))[1]
             if not directory:
                 new_dir = FakeDirectory(component, filesystem=self)
                 new_dirs.append(new_dir)
@@ -2354,8 +2443,10 @@ class FakeFilesystem:
                 current_dir = new_dir
             else:
                 if S_ISLNK(directory.st_mode):
+                    assert directory.contents
                     directory = self.resolve(directory.contents)
-                current_dir = directory
+                    assert directory
+                current_dir = cast(FakeDirectory, directory)
                 if directory.st_mode & S_IFDIR != S_IFDIR:
                     self.raise_os_error(errno.ENOTDIR, current_dir.path)
 
@@ -2366,10 +2457,15 @@ class FakeFilesystem:
 
         return current_dir
 
-    def create_file(self, file_path, st_mode=S_IFREG | PERM_DEF_FILE,
-                    contents='', st_size=None, create_missing_dirs=True,
-                    apply_umask=False, encoding=None, errors=None,
-                    side_effect=None):
+    def create_file(self, file_path: AnyStr,
+                    st_mode: int = S_IFREG | PERM_DEF_FILE,
+                    contents: AnyString = '',
+                    st_size: Optional[int] = None,
+                    create_missing_dirs: bool = True,
+                    apply_umask: bool = False,
+                    encoding: Optional[str] = None,
+                    errors: Optional[str] = None,
+                    side_effect: Optional[Callable] = None) -> FakeFile:
         """Create `file_path`, including all the parent directories along
         the way.
 
@@ -2403,7 +2499,9 @@ class FakeFilesystem:
             file_path, st_mode, contents, st_size, create_missing_dirs,
             apply_umask, encoding, errors, side_effect=side_effect)
 
-    def add_real_file(self, source_path, read_only=True, target_path=None):
+    def add_real_file(self, source_path: AnyStr,
+                      read_only: bool = True,
+                      target_path: Optional[AnyStr] = None) -> FakeFile:
         """Create `file_path`, including all the parent directories along the
         way, for an existing real file. The contents of the real file are read
         only on demand.
@@ -2429,9 +2527,8 @@ class FakeFilesystem:
             that `pyfakefs` must not modify the real file system.
         """
         target_path = target_path or source_path
-        source_path = make_string_path(source_path)
-        target_path = self.make_string_path(target_path)
-        real_stat = os.stat(source_path)
+        source_path_str = make_string_path(source_path)
+        real_stat = os.stat(source_path_str)
         fake_file = self.create_file_internally(target_path,
                                                 read_from_real_fs=True)
 
@@ -2439,12 +2536,13 @@ class FakeFilesystem:
         fake_file.stat_result.set_from_stat_result(real_stat)
         if read_only:
             fake_file.st_mode &= 0o777444
-        fake_file.file_path = source_path
+        fake_file.file_path = source_path_str
         self.change_disk_usage(fake_file.size, fake_file.name,
                                fake_file.st_dev)
         return fake_file
 
-    def add_real_symlink(self, source_path, target_path=None):
+    def add_real_symlink(self, source_path: AnyPath,
+                         target_path: Optional[AnyPath] = None) -> FakeFile:
         """Create a symlink at source_path (or target_path, if given).  It will
         point to the same path as the symlink on the real filesystem.  Relative
         symlinks will point relative to their new location.  Absolute symlinks
@@ -2453,10 +2551,10 @@ class FakeFilesystem:
         Args:
             source_path: The path to the existing symlink.
             target_path: If given, the name of the symlink in the fake
-                fileystem, otherwise, the same as `source_path`.
+                filesystem, otherwise, the same as `source_path`.
 
         Returns:
-            the newly created FakeDirectory object.
+            the newly created FakeFile object.
 
         Raises:
             OSError: if the directory does not exist in the real file system.
@@ -2464,19 +2562,25 @@ class FakeFilesystem:
                 (see :py:meth:`create_file`).
             OSError: if the directory already exists in the fake file system.
         """
-        source_path = self._path_without_trailing_separators(source_path)
-        if not os.path.exists(source_path) and not os.path.islink(source_path):
-            self.raise_os_error(errno.ENOENT, source_path)
+        source_path_str = make_string_path(source_path)  # TODO: add test
+        source_path_str = self._path_without_trailing_separators(
+            source_path_str)
+        if (not os.path.exists(source_path_str) and
+                not os.path.islink(source_path_str)):
+            self.raise_os_error(errno.ENOENT, source_path_str)
 
-        target = os.readlink(source_path)
+        target = os.readlink(source_path_str)
 
         if target_path:
             return self.create_symlink(target_path, target)
         else:
-            return self.create_symlink(source_path, target)
+            return self.create_symlink(source_path_str, target)
 
-    def add_real_directory(self, source_path, read_only=True, lazy_read=True,
-                           target_path=None):
+    def add_real_directory(
+            self, source_path: AnyPath,
+            read_only: bool = True,
+            lazy_read: bool = True,
+            target_path: Optional[AnyPath] = None) -> FakeDirectory:
         """Create a fake directory corresponding to the real directory at the
         specified path.  Add entries in the fake directory corresponding to
         the entries in the real directory.  Symlinks are supported.
@@ -2504,10 +2608,13 @@ class FakeFilesystem:
             OSError: if the directory does not exist in the real file system.
             OSError: if the directory already exists in the fake file system.
         """
-        source_path = self._path_without_trailing_separators(source_path)
-        if not os.path.exists(source_path):
-            self.raise_os_error(errno.ENOENT, source_path)
-        target_path = target_path or source_path
+        source_path_str = make_string_path(source_path)  # TODO: add test
+        source_path_str = self._path_without_trailing_separators(
+            source_path_str)
+        if not os.path.exists(source_path_str):
+            self.raise_os_error(errno.ENOENT, source_path_str)
+        target_path = target_path or source_path_str
+        new_dir: FakeDirectory
         if lazy_read:
             parent_path = os.path.split(target_path)[0]
             if self.exists(parent_path):
@@ -2515,13 +2622,13 @@ class FakeFilesystem:
             else:
                 parent_dir = self.create_dir(parent_path)
             new_dir = FakeDirectoryFromRealDirectory(
-                source_path, self, read_only, target_path)
+                source_path_str, self, read_only, target_path)
             parent_dir.add_entry(new_dir)
         else:
             new_dir = self.create_dir(target_path)
-            for base, _, files in os.walk(source_path):
-                new_base = os.path.join(new_dir.path,
-                                        os.path.relpath(base, source_path))
+            for base, _, files in os.walk(source_path_str):
+                new_base = os.path.join(new_dir.path,  # type:ignore[arg-type]
+                                        os.path.relpath(base, source_path_str))
                 for fileEntry in os.listdir(base):
                     abs_fileEntry = os.path.join(base, fileEntry)
 
@@ -2539,7 +2646,9 @@ class FakeFilesystem:
                                        os.path.join(new_base, fileEntry))
         return new_dir
 
-    def add_real_paths(self, path_list, read_only=True, lazy_dir_read=True):
+    def add_real_paths(self, path_list: List[AnyStr],
+                       read_only: bool = True,
+                       lazy_dir_read: bool = True) -> None:
         """This convenience method adds multiple files and/or directories from
         the real file system to the fake file system. See `add_real_file()` and
         `add_real_directory()`.
@@ -2566,13 +2675,17 @@ class FakeFilesystem:
             else:
                 self.add_real_file(path, read_only)
 
-    def create_file_internally(self, file_path,
-                               st_mode=S_IFREG | PERM_DEF_FILE,
-                               contents='', st_size=None,
-                               create_missing_dirs=True,
-                               apply_umask=False, encoding=None, errors=None,
-                               read_from_real_fs=False, raw_io=False,
-                               side_effect=None):
+    def create_file_internally(
+            self, file_path: AnyPath,
+            st_mode: int = S_IFREG | PERM_DEF_FILE,
+            contents: AnyString = '',
+            st_size: Optional[int] = None,
+            create_missing_dirs: bool = True,
+            apply_umask: bool = False,
+            encoding: Optional[str] = None,
+            errors: Optional[str] = None,
+            read_from_real_fs: bool = False,
+            side_effect: Optional[Callable] = None) -> FakeFile:
         """Internal fake file creator that supports both normal fake files
         and fake files based on real files.
 
@@ -2592,21 +2705,20 @@ class FakeFilesystem:
             errors: the error mode used for encoding/decoding errors
             read_from_real_fs: if True, the contents are read from the real
                 file system on demand.
-            raw_io: `True` if called from low-level API (`os.open`)
             side_effect: function handle that is executed when file is written,
                 must accept the file object as an argument.
         """
-        file_path = self.make_string_path(file_path)
-        file_path = self.absnormpath(file_path)
+        path = self.make_string_path(file_path)
+        path = self.absnormpath(path)
         if not is_int_type(st_mode):
             raise TypeError(
                 'st_mode must be of int type - did you mean to set contents?')
 
-        if self.exists(file_path, check_link=True):
-            self.raise_os_error(errno.EEXIST, file_path)
-        parent_directory, new_file = self.splitpath(file_path)
+        if self.exists(path, check_link=True):
+            self.raise_os_error(errno.EEXIST, path)
+        parent_directory, new_file = self.splitpath(path)
         if not parent_directory:
-            parent_directory = self.cwd
+            parent_directory = matching_string(path, self.cwd)
         self._auto_mount_drive_if_needed(parent_directory)
         if not self.exists(parent_directory):
             if not create_missing_dirs:
@@ -2616,8 +2728,10 @@ class FakeFilesystem:
             parent_directory = self._original_path(parent_directory)
         if apply_umask:
             st_mode &= ~self.umask
+        file_object: FakeFile
         if read_from_real_fs:
-            file_object = FakeFileFromRealFile(file_path, filesystem=self,
+            file_object = FakeFileFromRealFile(to_string(path),
+                                               filesystem=self,
                                                side_effect=side_effect)
         else:
             file_object = FakeFile(new_file, st_mode, filesystem=self,
@@ -2634,14 +2748,16 @@ class FakeFilesystem:
                 if st_size is not None:
                     file_object.set_large_file_size(st_size)
                 else:
-                    file_object._set_initial_contents(contents)
+                    file_object.set_initial_contents(contents)  # type:ignore
             except OSError:
-                self.remove_object(file_path)
+                self.remove_object(path)
                 raise
 
         return file_object
 
-    def create_symlink(self, file_path, link_target, create_missing_dirs=True):
+    def create_symlink(self, file_path: AnyPath,
+                       link_target: AnyPath,
+                       create_missing_dirs: bool = True) -> FakeFile:
         """Create the specified symlink, pointed at the specified link target.
 
         Args:
@@ -2657,43 +2773,43 @@ class FakeFilesystem:
             OSError: if the symlink could not be created
                 (see :py:meth:`create_file`).
         """
+        link_path = self.make_string_path(file_path)
+        link_target_path = self.make_string_path(link_target)
+        link_path = self.normcase(link_path)
         # the link path cannot end with a path separator
-        file_path = self.make_string_path(file_path)
-        link_target = self.make_string_path(link_target)
-        file_path = self.normcase(file_path)
-        if self.ends_with_path_separator(file_path):
-            if self.exists(file_path):
-                self.raise_os_error(errno.EEXIST, file_path)
-            if self.exists(link_target):
+        if self.ends_with_path_separator(link_path):
+            if self.exists(link_path):
+                self.raise_os_error(errno.EEXIST, link_path)
+            if self.exists(link_target_path):
                 if not self.is_windows_fs:
-                    self.raise_os_error(errno.ENOENT, file_path)
+                    self.raise_os_error(errno.ENOENT, link_path)
             else:
                 if self.is_windows_fs:
-                    self.raise_os_error(errno.EINVAL, link_target)
+                    self.raise_os_error(errno.EINVAL, link_target_path)
                 if not self.exists(
-                        self._path_without_trailing_separators(file_path),
+                        self._path_without_trailing_separators(link_path),
                         check_link=True):
-                    self.raise_os_error(errno.ENOENT, link_target)
+                    self.raise_os_error(errno.ENOENT, link_target_path)
                 if self.is_macos:
                     # to avoid EEXIST exception, remove the link
                     # if it already exists
-                    if self.exists(file_path, check_link=True):
-                        self.remove_object(file_path)
+                    if self.exists(link_path, check_link=True):
+                        self.remove_object(link_path)
                 else:
-                    self.raise_os_error(errno.EEXIST, link_target)
+                    self.raise_os_error(errno.EEXIST, link_target_path)
 
         # resolve the link path only if it is not a link itself
-        if not self.islink(file_path):
-            file_path = self.resolve_path(file_path)
-        link_target = make_string_path(link_target)
+        if not self.islink(link_path):
+            link_path = self.resolve_path(link_path)
         return self.create_file_internally(
-            file_path, st_mode=S_IFLNK | PERM_DEF,
-            contents=link_target,
-            create_missing_dirs=create_missing_dirs,
-            raw_io=True)
-
-    def create_link(self, old_path, new_path,
-                    follow_symlinks=True, create_missing_dirs=True):
+            link_path, st_mode=S_IFLNK | PERM_DEF,
+            contents=link_target_path,
+            create_missing_dirs=create_missing_dirs)
+
+    def create_link(self, old_path: AnyPath,
+                    new_path: AnyPath,
+                    follow_symlinks: bool = True,
+                    create_missing_dirs: bool = True) -> FakeFile:
         """Create a hard link at new_path, pointing at old_path.
 
         Args:
@@ -2712,14 +2828,16 @@ class FakeFilesystem:
             OSError:  if old_path is a directory.
             OSError:  if the parent directory doesn't exist.
         """
-        new_path_normalized = self.absnormpath(new_path)
+        old_path_str = make_string_path(old_path)
+        new_path_str = make_string_path(new_path)
+        new_path_normalized = self.absnormpath(new_path_str)
         if self.exists(new_path_normalized, check_link=True):
-            self.raise_os_error(errno.EEXIST, new_path)
+            self.raise_os_error(errno.EEXIST, new_path_str)
 
         new_parent_directory, new_basename = self.splitpath(
             new_path_normalized)
         if not new_parent_directory:
-            new_parent_directory = self.cwd
+            new_parent_directory = matching_string(new_path_str, self.cwd)
 
         if not self.exists(new_parent_directory):
             if create_missing_dirs:
@@ -2727,30 +2845,35 @@ class FakeFilesystem:
             else:
                 self.raise_os_error(errno.ENOENT, new_parent_directory)
 
-        if self.ends_with_path_separator(old_path):
+        if self.ends_with_path_separator(old_path_str):
             error = errno.EINVAL if self.is_windows_fs else errno.ENOTDIR
-            self.raise_os_error(error, old_path)
+            self.raise_os_error(error, old_path_str)
 
         if not self.is_windows_fs and self.ends_with_path_separator(new_path):
-            self.raise_os_error(errno.ENOENT, old_path)
+            self.raise_os_error(errno.ENOENT, old_path_str)
 
         # Retrieve the target file
         try:
-            old_file = self.resolve(old_path, follow_symlinks=follow_symlinks)
+            old_file = self.resolve(old_path_str,
+                                    follow_symlinks=follow_symlinks)
         except OSError:
-            self.raise_os_error(errno.ENOENT, old_path)
+            self.raise_os_error(errno.ENOENT, old_path_str)
 
         if old_file.st_mode & S_IFDIR:
             self.raise_os_error(
-                errno.EACCES if self.is_windows_fs else errno.EPERM, old_path)
+                errno.EACCES if self.is_windows_fs
+                else errno.EPERM, old_path_str
+            )
 
         # abuse the name field to control the filename of the
         # newly created link
-        old_file.name = new_basename
+        old_file.name = new_basename  # type:ignore[assignment]
         self.add_object(new_parent_directory, old_file)
         return old_file
 
-    def link(self, old_path, new_path, follow_symlinks=True):
+    def link(self, old_path: AnyPath,
+             new_path: AnyPath,
+             follow_symlinks: bool = True) -> FakeFile:
         """Create a hard link at new_path, pointing at old_path.
 
         Args:
@@ -2770,14 +2893,15 @@ class FakeFilesystem:
         return self.create_link(old_path, new_path, follow_symlinks,
                                 create_missing_dirs=False)
 
-    def _is_circular_link(self, link_obj):
+    def _is_circular_link(self, link_obj: FakeFile) -> bool:
         try:
+            assert link_obj.contents
             self.resolve_path(link_obj.contents)
         except OSError as exc:
             return exc.errno == errno.ELOOP
         return False
 
-    def readlink(self, path):
+    def readlink(self, path: AnyPath) -> str:
         """Read the target of a symlink.
 
         Args:
@@ -2794,31 +2918,33 @@ class FakeFilesystem:
         """
         if path is None:
             raise TypeError
-        link_obj = self.lresolve(path)
+        link_path = make_string_path(path)
+        link_obj = self.lresolve(link_path)
         if S_IFMT(link_obj.st_mode) != S_IFLNK:
-            self.raise_os_error(errno.EINVAL, path)
+            self.raise_os_error(errno.EINVAL, link_path)
 
-        if self.ends_with_path_separator(path):
-            if not self.is_windows_fs and self.exists(path):
-                self.raise_os_error(errno.EINVAL, path)
-            if not self.exists(link_obj.path):
+        if self.ends_with_path_separator(link_path):
+            if not self.is_windows_fs and self.exists(link_path):
+                self.raise_os_error(errno.EINVAL, link_path)
+            if not self.exists(link_obj.path):  # type:ignore
                 if self.is_windows_fs:
                     error = errno.EINVAL
                 elif self._is_circular_link(link_obj):
                     if self.is_macos:
-                        return link_obj.path
+                        return link_obj.path  # type:ignore[return-value]
                     error = errno.ELOOP
                 else:
                     error = errno.ENOENT
                 self.raise_os_error(error, link_obj.path)
 
+        assert link_obj.contents
         return link_obj.contents
 
-    def makedir(self, dir_name, mode=PERM_DEF):
+    def makedir(self, dir_path: AnyPath, mode: int = PERM_DEF) -> None:
         """Create a leaf Fake directory.
 
         Args:
-            dir_name: (str) Name of directory to create.
+            dir_path: (str) Name of directory to create.
                 Relative paths are assumed to be relative to '/'.
             mode: (int) Mode to create directory with.  This argument defaults
                 to 0o777. The umask is applied to this mode.
@@ -2827,7 +2953,7 @@ class FakeFilesystem:
             OSError: if the directory name is invalid or parent directory is
                 read only or as per :py:meth:`add_object`.
         """
-        dir_name = make_string_path(dir_name)
+        dir_name = make_string_path(dir_path)
         ends_with_sep = self.ends_with_path_separator(dir_name)
         dir_name = self._path_without_trailing_separators(dir_name)
         if not dir_name:
@@ -2858,14 +2984,17 @@ class FakeFilesystem:
         head, tail = self.splitpath(dir_name)
 
         self.add_object(
-            head, FakeDirectory(tail, mode & ~self.umask, filesystem=self))
+            to_string(head),
+            FakeDirectory(to_string(tail), mode & ~self.umask,
+                          filesystem=self))
 
-    def _path_without_trailing_separators(self, path):
+    def _path_without_trailing_separators(self, path: AnyStr) -> AnyStr:
         while self.ends_with_path_separator(path):
             path = path[:-1]
         return path
 
-    def makedirs(self, dir_name, mode=PERM_DEF, exist_ok=False):
+    def makedirs(self, dir_name: AnyStr, mode: int = PERM_DEF,
+                 exist_ok: bool = False) -> None:
         """Create a leaf Fake directory and create any non-existent
         parent dirs.
 
@@ -2883,7 +3012,6 @@ class FakeFilesystem:
         """
         if not dir_name:
             self.raise_os_error(errno.ENOENT, '')
-        dir_name = to_string(dir_name)
         ends_with_sep = self.ends_with_path_separator(dir_name)
         dir_name = self.absnormpath(dir_name)
         if (ends_with_sep and self.is_macos and
@@ -2892,17 +3020,19 @@ class FakeFilesystem:
             # to avoid EEXIST exception, remove the link
             self.remove_object(dir_name)
 
-        path_components = self._path_components(dir_name)
+        dir_name_str = to_string(dir_name)
+        path_components = self._path_components(dir_name_str)
 
-        # Raise a permission denied error if thioe first existing directory
+        # Raise a permission denied error if the first existing directory
         # is not writeable.
         current_dir = self.root
         for component in path_components:
-            if (component not in current_dir.contents
-                    or not isinstance(current_dir.contents, dict)):
+            if (not hasattr(current_dir, "entries") or
+                    component not in current_dir.entries):
                 break
             else:
-                current_dir = current_dir.contents[component]
+                current_dir = cast(FakeDirectory,
+                                   current_dir.entries[component])
         try:
             self.create_dir(dir_name, mode & ~self.umask)
         except OSError as e:
@@ -2915,8 +3045,9 @@ class FakeFilesystem:
                     e.errno = errno.ENOENT
                 self.raise_os_error(e.errno, e.filename)
 
-    def _is_of_type(self, path, st_flag, follow_symlinks=True,
-                    check_read_perm=True):
+    def _is_of_type(self, path: AnyPath, st_flag: int,
+                    follow_symlinks: bool = True,
+                    check_read_perm: bool = True) -> bool:
         """Helper function to implement isdir(), islink(), etc.
 
         See the stat(2) man page for valid stat.S_I* flag values
@@ -2933,21 +3064,21 @@ class FakeFilesystem:
         Raises:
           TypeError: if path is None
         """
-        path = to_string(make_string_path(path))
         if path is None:
             raise TypeError
+        file_path = make_string_path(path)
         try:
-            obj = self.resolve(path, follow_symlinks,
+            obj = self.resolve(file_path, follow_symlinks,
                                check_read_perm=check_read_perm)
             if obj:
                 self.raise_for_filepath_ending_with_separator(
-                    path, obj, macos_handling=not follow_symlinks)
+                    file_path, obj, macos_handling=not follow_symlinks)
                 return S_IFMT(obj.st_mode) == st_flag
         except OSError:
             return False
         return False
 
-    def isdir(self, path, follow_symlinks=True):
+    def isdir(self, path: AnyPath, follow_symlinks: bool = True) -> bool:
         """Determine if path identifies a directory.
 
         Args:
@@ -2961,7 +3092,7 @@ class FakeFilesystem:
         """
         return self._is_of_type(path, S_IFDIR, follow_symlinks)
 
-    def isfile(self, path, follow_symlinks=True):
+    def isfile(self, path: AnyPath, follow_symlinks: bool = True) -> bool:
         """Determine if path identifies a regular file.
 
         Args:
@@ -2976,7 +3107,7 @@ class FakeFilesystem:
         return self._is_of_type(path, S_IFREG, follow_symlinks,
                                 check_read_perm=False)
 
-    def islink(self, path):
+    def islink(self, path: AnyPath) -> bool:
         """Determine if path identifies a symbolic link.
 
         Args:
@@ -2990,7 +3121,7 @@ class FakeFilesystem:
         """
         return self._is_of_type(path, S_IFLNK, follow_symlinks=False)
 
-    def confirmdir(self, target_directory):
+    def confirmdir(self, target_directory: AnyStr) -> FakeDirectory:
         """Test that the target is actually a directory, raising OSError
         if not.
 
@@ -3004,12 +3135,12 @@ class FakeFilesystem:
         Raises:
             OSError: if the target is not a directory.
         """
-        directory = self.resolve(target_directory)
+        directory = cast(FakeDirectory, self.resolve(target_directory))
         if not directory.st_mode & S_IFDIR:
             self.raise_os_error(errno.ENOTDIR, target_directory, 267)
         return directory
 
-    def remove(self, path):
+    def remove(self, path: AnyStr) -> None:
         """Remove the FakeFile object at the specified file path.
 
         Args:
@@ -3020,7 +3151,8 @@ class FakeFilesystem:
             OSError: if path does not exist.
             OSError: if removal failed.
         """
-        norm_path = self.absnormpath(path)
+        norm_path = make_string_path(path)
+        norm_path = self.absnormpath(norm_path)
         if self.ends_with_path_separator(path):
             self._handle_broken_link_with_trailing_sep(norm_path)
         if self.exists(norm_path):
@@ -3036,8 +3168,7 @@ class FakeFilesystem:
                         error = errno.EISDIR
                     self.raise_os_error(error, norm_path)
 
-                norm_path = make_string_path(norm_path)
-                if path.endswith(self.path_separator):
+                if path.endswith(matching_string(path, self.path_separator)):
                     if self.is_windows_fs:
                         error = errno.EACCES
                     elif self.is_macos:
@@ -3050,7 +3181,8 @@ class FakeFilesystem:
 
         self.remove_object(norm_path)
 
-    def rmdir(self, target_directory, allow_symlink=False):
+    def rmdir(self, target_directory: AnyStr,
+              allow_symlink: bool = False) -> None:
         """Remove a leaf Fake directory.
 
         Args:
@@ -3077,11 +3209,11 @@ class FakeFilesystem:
                     self.raise_os_error(errno.ENOTDIR, target_directory)
 
             dir_object = self.resolve(target_directory)
-            if dir_object.contents:
+            if dir_object.entries:
                 self.raise_os_error(errno.ENOTEMPTY, target_directory)
             self.remove_object(target_directory)
 
-    def listdir(self, target_directory):
+    def listdir(self, target_directory: AnyStr) -> List[AnyStr]:
         """Return a list of file names in target_directory.
 
         Args:
@@ -3097,13 +3229,13 @@ class FakeFilesystem:
         """
         target_directory = self.resolve_path(target_directory, allow_fd=True)
         directory = self.confirmdir(target_directory)
-        directory_contents = directory.contents
-        return list(directory_contents.keys())
+        directory_contents = directory.entries
+        return list(directory_contents.keys())  # type:ignore[arg-type]
 
-    def __str__(self):
+    def __str__(self) -> str:
         return str(self.root)
 
-    def _add_standard_streams(self):
+    def _add_standard_streams(self) -> None:
         self._add_open_file(StandardStreamWrapper(sys.stdin))
         self._add_open_file(StandardStreamWrapper(sys.stdout))
         self._add_open_file(StandardStreamWrapper(sys.stderr))
@@ -3442,7 +3574,7 @@ class FakePathModule:
         curdir = matching_string(path, '.')
         pardir = matching_string(path, '..')
 
-        sep = self.filesystem._path_separator(path)
+        sep = self.filesystem.get_path_separator(path)
         if self.isabs(rest):
             rest = rest[1:]
             path = sep
@@ -3511,7 +3643,7 @@ class FakePathModule:
         if not path:
             return False
         normed_path = self.filesystem.absnormpath(path)
-        sep = self.filesystem._path_separator(path)
+        sep = self.filesystem.get_path_separator(path)
         if self.filesystem.is_windows_fs:
             if self.filesystem.alternative_path_separator is not None:
                 path_seps = (
@@ -3751,7 +3883,7 @@ class FakeOsModule:
             OSError: bad file descriptor.
             TypeError: if file descriptor is not an integer.
         """
-        file_handle = self.filesystem.get_open_file(fd)
+        file_handle: FakeFileWrapper = self.filesystem.get_open_file(fd)
         if isinstance(file_handle, FakeDirWrapper):
             self.filesystem.raise_os_error(errno.EBADF, file_handle.file_path)
 
@@ -4195,7 +4327,7 @@ class FakeOsModule:
         """
         name = self.filesystem.absnormpath(name)
         directory = self.filesystem.confirmdir(name)
-        if directory.contents:
+        if directory.entries:
             self.filesystem.raise_os_error(
                 errno.ENOTEMPTY, self.path.basename(name))
         else:
@@ -4205,7 +4337,7 @@ class FakeOsModule:
             head, tail = self.path.split(head)
         while head and tail:
             head_dir = self.filesystem.confirmdir(head)
-            if head_dir.contents:
+            if head_dir.entries:
                 break
             # only the top-level dir may not be a symlink
             self.filesystem.rmdir(head, allow_symlink=True)
@@ -4255,21 +4387,26 @@ class FakeOsModule:
 
     def _path_with_dir_fd(self, path, fct, dir_fd):
         """Return the path considering dir_fd. Raise on invalid parameters."""
-        path = to_string(path)
+        try:
+            path_str = make_string_path(path)
+        except TypeError:
+            # the error is handled later
+            path_str = path
+        path_str = to_string(path_str)
         if dir_fd is not None:
             # check if fd is supported for the built-in real function
             real_fct = getattr(os, fct.__name__)
             if real_fct not in self.supports_dir_fd:
                 raise NotImplementedError(
                     'dir_fd unavailable on this platform')
-            if isinstance(path, int):
+            if isinstance(path_str, int):
                 raise ValueError("%s: Can't specify dir_fd without "
-                                 "matching path" % fct.__name__)
-            if not self.path.isabs(path):
+                                 "matching path_str" % fct.__name__)
+            if not self.path.isabs(path_str):
                 return self.path.join(
                     self.filesystem.get_open_file(
-                        dir_fd).get_object().path, path)
-        return path
+                        dir_fd).get_object().path, path_str)
+        return path_str
 
     def truncate(self, path, length):
         """Truncate the file corresponding to path, so that it is
@@ -5340,13 +5477,15 @@ class FakeFileOpen:
             else:
                 target_path = file_path
             if self.filesystem.ends_with_path_separator(target_path):
-                error = (errno.EINVAL if self.filesystem.is_windows_fs
-                         else errno.ENOENT if self.filesystem.is_macos
-                         else errno.EISDIR)
+                error = (
+                    errno.EINVAL if self.filesystem.is_windows_fs
+                    else errno.ENOENT if self.filesystem.is_macos
+                    else errno.EISDIR
+                )
                 self.filesystem.raise_os_error(error, file_path)
             file_object = self.filesystem.create_file_internally(
                 real_path, create_missing_dirs=False,
-                apply_umask=True, raw_io=self.raw_io)
+                apply_umask=True)
         return file_object
 
     def _handle_file_arg(self, file_):
@@ -5369,8 +5508,7 @@ class FakeFileOpen:
                 file_object = self.filesystem.dev_null
                 real_path = file_path
             else:
-                real_path = self.filesystem.resolve_path(
-                    file_path, raw_io=self.raw_io)
+                real_path = self.filesystem.resolve_path(file_path)
                 if self.filesystem.exists(file_path):
                     file_object = self.filesystem.get_object_from_normpath(
                         real_path, check_read_perm=False)
diff --git a/pyfakefs/fake_scandir.py b/pyfakefs/fake_scandir.py
index 6a6bdde..8941973 100644
--- a/pyfakefs/fake_scandir.py
+++ b/pyfakefs/fake_scandir.py
@@ -133,14 +133,14 @@ class ScanDirIter:
         else:
             self.abspath = self.filesystem.absnormpath(path)
             self.path = to_string(path)
-        contents = self.filesystem.confirmdir(self.abspath).contents
-        self.contents_iter = iter(contents)
+        entries = self.filesystem.confirmdir(self.abspath).entries
+        self.entry_iter = iter(entries)
 
     def __iter__(self):
         return self
 
     def __next__(self):
-        entry = self.contents_iter.__next__()
+        entry = self.entry_iter.__next__()
         dir_entry = DirEntry(self.filesystem)
         dir_entry.name = entry
         dir_entry.path = self.filesystem.joinpaths(self.path,
diff --git a/pyfakefs/helpers.py b/pyfakefs/helpers.py
index 95e83af..67c1fcd 100644
--- a/pyfakefs/helpers.py
+++ b/pyfakefs/helpers.py
@@ -13,44 +13,52 @@
 """Helper classes use for fake file system implementation."""
 import io
 import locale
+import os
 import platform
 import stat
 import sys
 import time
 from copy import copy
 from stat import S_IFLNK
-
-import os
+from typing import Union, Optional, Any, AnyStr, overload, cast
 
 IS_PYPY = platform.python_implementation() == 'PyPy'
 IS_WIN = sys.platform == 'win32'
 IN_DOCKER = os.path.exists('/.dockerenv')
 
+AnyPath = Union[AnyStr, os.PathLike]
+
 
-def is_int_type(val):
+def is_int_type(val: Any) -> bool:
     """Return True if `val` is of integer type."""
     return isinstance(val, int)
 
 
-def is_byte_string(val):
+def is_byte_string(val: Any) -> bool:
     """Return True if `val` is a bytes-like object, False for a unicode
     string."""
     return not hasattr(val, 'encode')
 
 
-def is_unicode_string(val):
+def is_unicode_string(val: Any) -> bool:
     """Return True if `val` is a unicode string, False for a bytes-like
     object."""
     return hasattr(val, 'encode')
 
 
-def make_string_path(dir_name):
-    if sys.version_info >= (3, 6):
-        dir_name = os.fspath(dir_name)
-    return dir_name
+@overload
+def make_string_path(dir_name: AnyStr) -> AnyStr: ...
+
+
+@overload
+def make_string_path(dir_name: os.PathLike) -> str: ...
+
+
+def make_string_path(dir_name: AnyPath) -> AnyStr:
+    return cast(AnyStr, os.fspath(dir_name))
 
 
-def to_string(path):
+def to_string(path: AnyStr) -> str:
     """Return the string representation of a byte string using the preferred
      encoding, or the string itself if path is a str."""
     if isinstance(path, bytes):
@@ -58,7 +66,12 @@ def to_string(path):
     return path
 
 
-def real_encoding(encoding):
+def join_strings(s1: AnyStr, s2: AnyStr) -> AnyStr:
+    """This is a bit of a hack to satisfy mypy - may be refactored."""
+    return s1 + s2
+
+
+def real_encoding(encoding: Optional[str]) -> Optional[str]:
     """Since Python 3.10, the new function ``io.text_encoding`` returns
     "locale" as the encoding if None is defined. This will be handled
     as no encoding in pyfakefs."""
@@ -71,7 +84,20 @@ def now():
     return time.time()
 
 
-def matching_string(matched, string):
+@overload
+def matching_string(matched: bytes, string: AnyStr) -> bytes: ...
+
+
+@overload
+def matching_string(matched: str, string: AnyStr) -> str: ...
+
+
+@overload
+def matching_string(matched: AnyStr, string: None) -> None: ...
+
+
+def matching_string(  # type: ignore[misc]
+        matched: AnyStr, string: Optional[AnyStr]) -> Optional[AnyStr]:
     """Return the string as byte or unicode depending
     on the type of matched, assuming string is an ASCII string.
     """
@@ -87,36 +113,34 @@ class FakeStatResult:
     This is needed as `os.stat_result` has no possibility to set
     nanosecond times directly.
     """
-    _stat_float_times = True
-
-    def __init__(self, is_windows, user_id, group_id, initial_time=None):
-        self._use_float = None
-        self.st_mode = None
-        self.st_ino = None
-        self.st_dev = None
-        self.st_nlink = 0
-        self.st_uid = user_id
-        self.st_gid = group_id
-        self._st_size = None
-        self.is_windows = is_windows
-        if initial_time is not None:
-            self._st_atime_ns = int(initial_time * 1e9)
-        else:
-            self._st_atime_ns = None
-        self._st_mtime_ns = self._st_atime_ns
-        self._st_ctime_ns = self._st_atime_ns
+    _stat_float_times: bool = True
+
+    def __init__(self, is_windows: bool, user_id: int, group_id: int,
+                 initial_time: Optional[float] = None):
+        self._use_float: Optional[bool] = None
+        self.st_mode: int = 0
+        self.st_ino: Optional[int] = None
+        self.st_dev: int = 0
+        self.st_nlink: int = 0
+        self.st_uid: int = user_id
+        self.st_gid: int = group_id
+        self._st_size: int = 0
+        self.is_windows: bool = is_windows
+        self._st_atime_ns: int = int((initial_time or 0) * 1e9)
+        self._st_mtime_ns: int = self._st_atime_ns
+        self._st_ctime_ns: int = self._st_atime_ns
 
     @property
-    def use_float(self):
+    def use_float(self) -> bool:
         if self._use_float is None:
             return self.stat_float_times()
         return self._use_float
 
     @use_float.setter
-    def use_float(self, val):
+    def use_float(self, val: bool) -> None:
         self._use_float = val
 
-    def __eq__(self, other):
+    def __eq__(self, other: Any) -> bool:
         return (
                 isinstance(other, FakeStatResult) and
                 self._st_atime_ns == other._st_atime_ns and
@@ -131,10 +155,10 @@ class FakeStatResult:
                 self.st_mode == other.st_mode
         )
 
-    def __ne__(self, other):
+    def __ne__(self, other: Any) -> bool:
         return not self == other
 
-    def copy(self):
+    def copy(self) -> "FakeStatResult":
         """Return a copy where the float usage is hard-coded to mimic the
         behavior of the real os.stat_result.
         """
@@ -142,7 +166,7 @@ class FakeStatResult:
         stat_result.use_float = self.use_float
         return stat_result
 
-    def set_from_stat_result(self, stat_result):
+    def set_from_stat_result(self, stat_result: os.stat_result) -> None:
         """Set values from a real os.stat_result.
         Note: values that are controlled by the fake filesystem are not set.
         This includes st_ino, st_dev and st_nlink.
@@ -156,7 +180,7 @@ class FakeStatResult:
         self._st_ctime_ns = stat_result.st_ctime_ns
 
     @classmethod
-    def stat_float_times(cls, newvalue=None):
+    def stat_float_times(cls, newvalue: Optional[bool] = None) -> bool:
         """Determine whether a file's time stamps are reported as floats
         or ints.
 
@@ -172,50 +196,50 @@ class FakeStatResult:
         return cls._stat_float_times
 
     @property
-    def st_ctime(self):
+    def st_ctime(self) -> Union[int, float]:
         """Return the creation time in seconds."""
         ctime = self._st_ctime_ns / 1e9
         return ctime if self.use_float else int(ctime)
 
     @st_ctime.setter
-    def st_ctime(self, val):
+    def st_ctime(self, val: Union[int, float]) -> None:
         """Set the creation time in seconds."""
         self._st_ctime_ns = int(val * 1e9)
 
     @property
-    def st_atime(self):
+    def st_atime(self) -> Union[int, float]:
         """Return the access time in seconds."""
         atime = self._st_atime_ns / 1e9
         return atime if self.use_float else int(atime)
 
     @st_atime.setter
-    def st_atime(self, val):
+    def st_atime(self, val: Union[int, float]) -> None:
         """Set the access time in seconds."""
         self._st_atime_ns = int(val * 1e9)
 
     @property
-    def st_mtime(self):
+    def st_mtime(self) -> Union[int, float]:
         """Return the modification time in seconds."""
         mtime = self._st_mtime_ns / 1e9
         return mtime if self.use_float else int(mtime)
 
     @st_mtime.setter
-    def st_mtime(self, val):
+    def st_mtime(self, val: Union[int, float]) -> None:
         """Set the modification time in seconds."""
         self._st_mtime_ns = int(val * 1e9)
 
     @property
-    def st_size(self):
+    def st_size(self) -> int:
         if self.st_mode & S_IFLNK == S_IFLNK and self.is_windows:
             return 0
         return self._st_size
 
     @st_size.setter
-    def st_size(self, val):
+    def st_size(self, val: int) -> None:
         self._st_size = val
 
     @property
-    def st_file_attributes(self):
+    def st_file_attributes(self) -> int:
         if not self.is_windows:
             raise AttributeError("module 'os.stat_result' "
                                  "has no attribute 'st_file_attributes'")
@@ -232,15 +256,15 @@ class FakeStatResult:
         return mode
 
     @property
-    def st_reparse_tag(self):
+    def st_reparse_tag(self) -> int:
         if not self.is_windows or sys.version_info < (3, 8):
             raise AttributeError("module 'os.stat_result' "
                                  "has no attribute 'st_reparse_tag'")
         if self.st_mode & stat.S_IFLNK:
-            return stat.IO_REPARSE_TAG_SYMLINK
+            return stat.IO_REPARSE_TAG_SYMLINK  # type: ignore[attr-defined]
         return 0
 
-    def __getitem__(self, item):
+    def __getitem__(self, item: int) -> Optional[int]:
         """Implement item access to mimic `os.stat_result` behavior."""
         import stat
 
@@ -268,32 +292,32 @@ class FakeStatResult:
         raise ValueError('Invalid item')
 
     @property
-    def st_atime_ns(self):
+    def st_atime_ns(self) -> int:
         """Return the access time in nanoseconds."""
         return self._st_atime_ns
 
     @st_atime_ns.setter
-    def st_atime_ns(self, val):
+    def st_atime_ns(self, val: int) -> None:
         """Set the access time in nanoseconds."""
         self._st_atime_ns = val
 
     @property
-    def st_mtime_ns(self):
+    def st_mtime_ns(self) -> int:
         """Return the modification time in nanoseconds."""
         return self._st_mtime_ns
 
     @st_mtime_ns.setter
-    def st_mtime_ns(self, val):
+    def st_mtime_ns(self, val: int) -> None:
         """Set the modification time of the fake file in nanoseconds."""
         self._st_mtime_ns = val
 
     @property
-    def st_ctime_ns(self):
+    def st_ctime_ns(self) -> int:
         """Return the creation time in nanoseconds."""
         return self._st_ctime_ns
 
     @st_ctime_ns.setter
-    def st_ctime_ns(self, val):
+    def st_ctime_ns(self, val: int) -> None:
         """Set the creation time of the fake file in nanoseconds."""
         self._st_ctime_ns = val
 
@@ -301,7 +325,7 @@ class FakeStatResult:
 class BinaryBufferIO(io.BytesIO):
     """Stream class that handles byte contents for files."""
 
-    def putvalue(self, value):
+    def putvalue(self, value: bytes) -> None:
         self.write(value)
 
 
@@ -309,13 +333,15 @@ class TextBufferIO(io.TextIOWrapper):
     """Stream class that handles Python string contents for files.
     """
 
-    def __init__(self, contents=None, newline=None, encoding=None,
-                 errors='strict'):
-        self._bytestream = io.BytesIO(contents)
+    def __init__(self, contents: Optional[bytes] = None,
+                 newline: Optional[str] = None,
+                 encoding: Optional[str] = None,
+                 errors: str = 'strict'):
+        self._bytestream = io.BytesIO(contents or b'')
         super().__init__(self._bytestream, encoding, errors, newline)
 
-    def getvalue(self):
+    def getvalue(self) -> bytes:
         return self._bytestream.getvalue()
 
-    def putvalue(self, value):
+    def putvalue(self, value: bytes) -> None:
         self._bytestream.write(value)


Test patch:
diff --git a/pyfakefs/tests/fake_filesystem_test.py b/pyfakefs/tests/fake_filesystem_test.py
index 0a71f6f..cd2cac3 100644
--- a/pyfakefs/tests/fake_filesystem_test.py
+++ b/pyfakefs/tests/fake_filesystem_test.py
@@ -45,12 +45,13 @@ class FakeDirectoryUnitTest(TestCase):
     def test_new_file_and_directory(self):
         self.assertTrue(stat.S_IFREG & self.fake_file.st_mode)
         self.assertTrue(stat.S_IFDIR & self.fake_dir.st_mode)
-        self.assertEqual({}, self.fake_dir.contents)
+        self.assertEqual({}, self.fake_dir.entries)
         self.assertEqual(12, self.fake_file.st_ctime)
 
     def test_add_entry(self):
         self.fake_dir.add_entry(self.fake_file)
-        self.assertEqual({'foobar': self.fake_file}, self.fake_dir.contents)
+        self.assertEqual({'foobar': self.fake_file},
+                         self.fake_dir.entries)
 
     def test_get_entry(self):
         self.fake_dir.add_entry(self.fake_file)
@@ -265,7 +266,7 @@ class FakeFilesystemUnitTest(TestCase):
         self.assertEqual('/', self.filesystem.path_separator)
         self.assertTrue(stat.S_IFDIR & self.filesystem.root.st_mode)
         self.assertEqual(self.root_name, self.filesystem.root.name)
-        self.assertEqual({}, self.filesystem.root.contents)
+        self.assertEqual({}, self.filesystem.root.entries)
 
     def test_none_raises_type_error(self):
         with self.assertRaises(TypeError):
@@ -293,7 +294,7 @@ class FakeFilesystemUnitTest(TestCase):
     def test_add_object_to_root(self):
         self.filesystem.add_object(self.root_name, self.fake_file)
         self.assertEqual({'foobar': self.fake_file},
-                         self.filesystem.root.contents)
+                         self.filesystem.root.entries)
 
     def test_exists_added_file(self):
         self.filesystem.add_object(self.root_name, self.fake_file)
@@ -362,7 +363,7 @@ class FakeFilesystemUnitTest(TestCase):
         self.filesystem.add_object(self.fake_child.name, self.fake_file)
         self.assertEqual(
             {self.fake_file.name: self.fake_file},
-            self.filesystem.root.get_entry(self.fake_child.name).contents)
+            self.filesystem.root.get_entry(self.fake_child.name).entries)
 
     def test_add_object_to_regular_file_error_posix(self):
         self.filesystem.is_windows_fs = False
diff --git a/pyfakefs/tests/fake_pathlib_test.py b/pyfakefs/tests/fake_pathlib_test.py
index ce0ea48..9528a7d 100644
--- a/pyfakefs/tests/fake_pathlib_test.py
+++ b/pyfakefs/tests/fake_pathlib_test.py
@@ -952,13 +952,25 @@ class FakePathlibUsageInOsFunctionsTest(RealPathlibTestCase):
         self.os.makedirs(self.path(path))
         self.assertTrue(self.os.path.exists(path))
 
-    @unittest.skipIf(is_windows, 'os.readlink seems not to support '
-                                 'path-like objects under Windows')
+    @unittest.skipIf(is_windows and sys.version_info < (3, 8),
+                     'os.readlink does not to support path-like objects '
+                     'under Windows before Python 3.8')
     def test_readlink(self):
+        self.skip_if_symlink_not_supported()
         link_path = self.make_path('foo', 'bar', 'baz')
         target = self.make_path('tarJAY')
         self.create_symlink(link_path, target)
-        self.assertEqual(self.os.readlink(self.path(link_path)), target)
+        self.assert_equal_paths(self.os.readlink(self.path(link_path)), target)
+
+    @unittest.skipIf(is_windows and sys.version_info < (3, 8),
+                     'os.readlink does not to support path-like objects '
+                     'under Windows before Python 3.8')
+    def test_readlink_bytes(self):
+        self.skip_if_symlink_not_supported()
+        link_path = self.make_path(b'foo', b'bar', b'baz')
+        target = self.make_path(b'tarJAY')
+        self.create_symlink(link_path, target)
+        self.assert_equal_paths(self.os.readlink(self.path(link_path)), target)
 
     def test_remove(self):
         path = self.make_path('test.txt')