@@ -214,15 +214,8 @@ def __init__(self, name, st_mode=stat.S_IFREG | PERM_DEF_FILE,
214214 self .st_uid = None
215215 self .st_gid = None
216216
217- # members changed only by _CreateFile() to implement add_real_file()
218- self .read_from_real_fs = False
219- self .file_path = None
220-
221217 @property
222218 def byte_contents (self ):
223- if self ._byte_contents is None and self .read_from_real_fs :
224- with io .open (self .file_path , 'rb' ) as f :
225- self ._byte_contents = f .read ()
226219 return self ._byte_contents
227220
228221 @property
@@ -297,7 +290,7 @@ def SetLargeFileSize(self, st_size):
297290
298291 def IsLargeFile (self ):
299292 """Return True if this file was initialized with size but no contents."""
300- return self ._byte_contents is None and not self . read_from_real_fs
293+ return self ._byte_contents is None
301294
302295 def _encode_contents (self , contents ):
303296 # pylint: disable=undefined-variable
@@ -425,6 +418,53 @@ def SetIno(self, st_ino):
425418 self .st_ino = st_ino
426419
427420
421+ class FakeFileFromRealFile (FakeFile ):
422+ """Represents a fake file copied from the real file system.
423+
424+ The contents of the file are read on demand only.
425+ New in pyfakefs 3.2.
426+ """
427+
428+ def __init__ (self , file_path , filesystem , read_only = True ):
429+ """init.
430+
431+ Args:
432+ file_path: path to the existing file.
433+ filesystem: the fake filesystem where the file is created.
434+ read_only: if set, the file is treated as read-only, e.g. a write access raises an exception;
435+ otherwise, writing to the file changes the fake file only as usually.
436+
437+ Raises:
438+ OSError: if the file does not exist in the real file system.
439+ """
440+ real_stat = os .stat (file_path )
441+ # for read-only mode, remove the write/executable permission bits
442+ mode = real_stat .st_mode & 0o777444 if read_only else real_stat .st_mode
443+ super (FakeFileFromRealFile , self ).__init__ (name = os .path .basename (file_path ),
444+ st_mode = mode ,
445+ filesystem = filesystem )
446+ self .st_ctime = real_stat .st_ctime
447+ self .st_atime = real_stat .st_atime
448+ self .st_mtime = real_stat .st_mtime
449+ self .st_gid = real_stat .st_gid
450+ self .st_uid = real_stat .st_uid
451+ self .st_size = real_stat .st_size
452+ self .file_path = file_path
453+ self .contents_read = False
454+
455+ @property
456+ def byte_contents (self ):
457+ if not self .contents_read :
458+ self .contents_read = True
459+ with io .open (self .file_path , 'rb' ) as f :
460+ self ._byte_contents = f .read ()
461+ return self ._byte_contents
462+
463+ def IsLargeFile (self ):
464+ """The contents are never faked."""
465+ return False
466+
467+
428468class FakeDirectory (FakeFile ):
429469 """Provides the appearance of a real directory."""
430470
@@ -520,6 +560,58 @@ def __str__(self):
520560 return description
521561
522562
563+ class FakeDirectoryFromRealDirectory (FakeDirectory ):
564+ """Represents a fake directory copied from the real file system.
565+
566+ The contents of the directory are read on demand only.
567+ New in pyfakefs 3.2.
568+ """
569+
570+ def __init__ (self , dir_path , filesystem , read_only ):
571+ """init.
572+
573+ Args:
574+ dir_path: full directory path
575+ filesystem: the fake filesystem where the directory is created
576+ read_only: if set, all files under the directory are treated as read-only,
577+ e.g. a write access raises an exception;
578+ otherwise, writing to the files changes the fake files only as usually.
579+
580+ Raises:
581+ OSError if the directory does not exist in the real file system
582+ """
583+ real_stat = os .stat (dir_path )
584+ super (FakeDirectoryFromRealDirectory , self ).__init__ (
585+ name = os .path .split (dir_path )[1 ],
586+ perm_bits = real_stat .st_mode ,
587+ filesystem = filesystem )
588+
589+ self .st_ctime = real_stat .st_ctime
590+ self .st_atime = real_stat .st_atime
591+ self .st_mtime = real_stat .st_mtime
592+ self .st_gid = real_stat .st_gid
593+ self .st_uid = real_stat .st_uid
594+ self .dir_path = dir_path
595+ self .read_only = read_only
596+ self .contents_read = False
597+
598+ @property
599+ def contents (self ):
600+ """Return the list of contained directory entries, loading them if not already loaded."""
601+ if not self .contents_read :
602+ self .contents_read = True
603+ self .filesystem .add_real_paths (
604+ [os .path .join (self .dir_path , entry ) for entry in os .listdir (self .dir_path )],
605+ read_only = self .read_only )
606+ return self .byte_contents
607+
608+ def GetSize (self ):
609+ # we cannot get the size until the contents are loaded
610+ if not self .contents_read :
611+ return 0
612+ return super (FakeDirectoryFromRealDirectory , self ).GetSize ()
613+
614+
523615class FakeFilesystem (object ):
524616 """Provides the appearance of a real directory tree for unit testing."""
525617
@@ -1167,6 +1259,7 @@ def _DirectoryContent(self, directory, component):
11671259 if subdir .lower () == component .lower ()]
11681260 if matching_content :
11691261 return matching_content [0 ]
1262+
11701263 return None , None
11711264
11721265 def Exists (self , file_path ):
@@ -1642,13 +1735,11 @@ def add_real_file(self, file_path, read_only=True):
16421735 OSError: if the file does not exist in the real file system.
16431736 IOError: if the file already exists in the fake file system.
16441737 """
1645- real_stat = os .stat (file_path )
1646- # for read-only mode, remove the write/executable permission bits
1647- mode = real_stat .st_mode & 0o777444 if read_only else real_stat .st_mode
1648- return self ._CreateFile (file_path , contents = None , read_from_real_fs = True ,
1649- st_mode = mode , real_stat = real_stat )
1738+ return self ._CreateFile (file_path ,
1739+ read_from_real_fs = True ,
1740+ read_only = read_only )
16501741
1651- def add_real_directory (self , dir_path , read_only = True ):
1742+ def add_real_directory (self , dir_path , read_only = True , lazy_read = True ):
16521743 """Create fake directory for the existing directory at path, and entries for all contained
16531744 files in the real file system.
16541745 New in pyfakefs 3.2.
@@ -1658,6 +1749,11 @@ def add_real_directory(self, dir_path, read_only=True):
16581749 read_only: if set, all files under the directory are treated as read-only,
16591750 e.g. a write access raises an exception;
16601751 otherwise, writing to the files changes the fake files only as usually.
1752+ lazy_read: if set (default), directory contents are only read when accessed,
1753+ and only until the needed subdirectory level
1754+ Note: this means that the file system size is only updated at the time
1755+ the directory contents are read; set this to False only if you
1756+ are dependent on accurate file system size in your test
16611757
16621758 Returns:
16631759 the newly created FakeDirectory object.
@@ -1668,12 +1764,24 @@ def add_real_directory(self, dir_path, read_only=True):
16681764 """
16691765 if not os .path .exists (dir_path ):
16701766 raise IOError (errno .ENOENT , 'No such directory' , dir_path )
1671- self .CreateDirectory (dir_path )
1672- for base , _ , files in os .walk (dir_path ):
1673- for fileEntry in files :
1674- self .add_real_file (os .path .join (base , fileEntry ), read_only )
1767+ if lazy_read :
1768+ parent_path = os .path .split (dir_path )[0 ]
1769+ if self .Exists (parent_path ):
1770+ parent_dir = self .GetObject (parent_path )
1771+ else :
1772+ parent_dir = self .CreateDirectory (parent_path )
1773+ new_dir = FakeDirectoryFromRealDirectory (dir_path , filesystem = self , read_only = read_only )
1774+ parent_dir .AddEntry (new_dir )
1775+ self .last_ino += 1
1776+ new_dir .SetIno (self .last_ino )
1777+ else :
1778+ new_dir = self .CreateDirectory (dir_path )
1779+ for base , _ , files in os .walk (dir_path ):
1780+ for fileEntry in files :
1781+ self .add_real_file (os .path .join (base , fileEntry ), read_only )
1782+ return new_dir
16751783
1676- def add_real_paths (self , path_list , read_only = True ):
1784+ def add_real_paths (self , path_list , read_only = True , lazy_dir_read = True ):
16771785 """Convenience method to add several files and directories from the real file system
16781786 in the fake file system. See `add_real_file()` and `add_real_directory()`.
16791787 New in pyfakefs 3.2.
@@ -1683,22 +1791,24 @@ def add_real_paths(self, path_list, read_only=True):
16831791 read_only: if set, all files and files under under the directories are treated as read-only,
16841792 e.g. a write access raises an exception;
16851793 otherwise, writing to the files changes the fake files only as usually.
1794+ lazy_dir_read: uses lazy reading of directory contents if set
1795+ (see `add_real_directory`)
16861796
16871797 Raises:
16881798 OSError: if any of the files and directories in the list does not exist in the real file system.
16891799 OSError: if any of the files and directories in the list already exists in the fake file system.
16901800 """
16911801 for path in path_list :
16921802 if os .path .isdir (path ):
1693- self .add_real_directory (path , read_only )
1803+ self .add_real_directory (path , read_only , lazy_dir_read )
16941804 else :
16951805 self .add_real_file (path , read_only )
16961806
16971807 def _CreateFile (self , file_path , st_mode = stat .S_IFREG | PERM_DEF_FILE ,
16981808 contents = '' , st_size = None , create_missing_dirs = True ,
16991809 apply_umask = False , encoding = None , errors = None ,
1700- read_from_real_fs = False , real_stat = None ):
1701- """Create file_path, including all the parent directories along the way .
1810+ read_from_real_fs = False , read_only = True ):
1811+ """Internal fake file creation, supports both normal fake files and fake files from real files .
17021812
17031813 Args:
17041814 file_path: path to the file to create.
@@ -1708,12 +1818,10 @@ def _CreateFile(self, file_path, st_mode=stat.S_IFREG | PERM_DEF_FILE,
17081818 create_missing_dirs: if True, auto create missing directories.
17091819 apply_umask: whether or not the current umask must be applied on st_mode.
17101820 encoding: if contents is a unicode string, the encoding used for serialization.
1711- New in pyfakefs 2.9.
17121821 errors: the error mode used for encoding/decoding errors
1713- New in pyfakefs 3.2.
17141822 read_from_real_fs: if True, the contents are reaf from the real file system on demand.
1715- New in pyfakefs 3.2.
1716- real_stat: used in combination with read_from_real_fs; stat result of the real file
1823+ read_only: if set, the file is treated as read-only, e.g. a write access raises an exception;
1824+ otherwise, writing to the file changes the fake file only as usually.
17171825 """
17181826 file_path = self .NormalizePath (file_path )
17191827 if self .Exists (file_path ):
@@ -1732,22 +1840,16 @@ def _CreateFile(self, file_path, st_mode=stat.S_IFREG | PERM_DEF_FILE,
17321840 parent_directory = self .NormalizeCase (parent_directory )
17331841 if apply_umask :
17341842 st_mode &= ~ self .umask
1735- file_object = FakeFile (new_file , st_mode , filesystem = self , encoding = encoding , errors = errors )
17361843 if read_from_real_fs :
1737- file_object .st_ctime = real_stat .st_ctime
1738- file_object .st_atime = real_stat .st_atime
1739- file_object .st_mtime = real_stat .st_mtime
1740- file_object .st_gid = real_stat .st_gid
1741- file_object .st_uid = real_stat .st_uid
1742- file_object .st_size = real_stat .st_size
1743- file_object .read_from_real_fs = True
1744- file_object .file_path = file_path
1844+ file_object = FakeFileFromRealFile (file_path , filesystem = self , read_only = read_only )
1845+ else :
1846+ file_object = FakeFile (new_file , st_mode , filesystem = self , encoding = encoding , errors = errors )
17451847
17461848 self .last_ino += 1
17471849 file_object .SetIno (self .last_ino )
17481850 self .AddObject (parent_directory , file_object )
17491851
1750- if contents is not None or st_size is not None :
1852+ if not read_from_real_fs and ( contents is not None or st_size is not None ) :
17511853 try :
17521854 if st_size is not None :
17531855 file_object .SetLargeFileSize (st_size )
0 commit comments