Repository: pytest-dev/pyfakefs (python)

Issue:
please make applying the umask configurable for create_dir()
Recent changes made pyfakefs apply the umask by default when creating files or dirs. For `create_file()`, this behaviour is configurable via the `apply_umask` parameter. Please add a similar parameter for `create_dir()`, to allow for ignoring the umask and thus enabling full control of the exact permissions on the new dir via the `perm_bits` parameter.

Currently, we work around the umask always getting applied to directories by configuring an unrealistic all-permissive umask ("0000") on the fake filesystem ([like so](https://github.com/sabnzbd/sabnzbd/blob/f50810fb58656d9a3c33befb601ed63c1be105cf/tests/test_filesystem.py#L952)) to get rid of some test failures (https://github.com/sabnzbd/sabnzbd/issues/2883). Having an option to control the behaviour of `create_dir()` would obviously be a lot nicer.

Pull request description:
Add apply_umask argument to FakeFilesystem.create_dir
* allows to ignore the umask
* closes #1038

#### Tasks
- [x] Unit tests added that reproduce the issue or prove feature is working
- [x] Fix or feature added
- [x] Entry to release notes added
- [x] Pre-commit CI shows no errors
- [x] Unit tests passing
- [x] For documentation changes: The Read the Docs preview builds and looks as expected


Patch:
diff --git a/CHANGES.md b/CHANGES.md
index 8df7f03..79ed4ba 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,14 +1,22 @@
 # pyfakefs Release Notes
 The released versions correspond to PyPI releases.
 
+## Policy for Python version support
+* support for new versions is usually added preliminarily during the Python release beta phase,
+  official support after the final release
+* support for EOL versions is removed as soon as the CI (GitHub actions) does no longer provide
+  these versions (usually several months after the official EOL)
+
 ## Planned changes for next major release (6.0.0)
-* remove support for patching legacy modules `scandir` and `pathlib2`
-* remove support for Python 3.7
+* support for patching legacy modules `scandir` and `pathlib2` will be removed
+* the default for `FakeFilesystem.shuffle_listdir_results` will change to `True` to reflect
+  the real filesystem behavior
 
 ## Unreleased
 
 ### Enhancements
 * added preliminary support for Python 3.13 (tested with beta2) (see [#1017](../../issues/1017))
+* added `apply_umask` argument to `FakeFilesystem.create_dir` to allow ignoring the umask (see [#1038](../../issues/1038))
 
 ### Fixes
 * use real open calls for remaining `pathlib` functions so that it works nice with skippedmodules (see [#1012](../../issues/1012))
diff --git a/pyfakefs/fake_filesystem.py b/pyfakefs/fake_filesystem.py
index 98c754e..0421859 100644
--- a/pyfakefs/fake_filesystem.py
+++ b/pyfakefs/fake_filesystem.py
@@ -2090,7 +2090,10 @@ class FakeFilesystem:
         return path_str.replace(os_sep, fake_sep)  # type: ignore[return-value]
 
     def create_dir(
-        self, directory_path: AnyPath, perm_bits: int = helpers.PERM_DEF
+        self,
+        directory_path: AnyPath,
+        perm_bits: int = helpers.PERM_DEF,
+        apply_umask: bool = True,
     ) -> FakeDirectory:
         """Create `directory_path` and all the parent directories, and return
         the created :py:class:`FakeDirectory<pyfakefs.fake_file.FakeDirectory>` object.
@@ -2100,6 +2103,8 @@ class FakeFilesystem:
         Args:
             directory_path: The full directory path to create.
             perm_bits: The permission bits as set by ``chmod``.
+            apply_umask: If `True` (default), the current umask is applied
+                to `perm_bits`.
 
         Returns:
             The newly created
@@ -2138,7 +2143,9 @@ class FakeFilesystem:
         # set the permission after creating the directories
         # to allow directory creation inside a read-only directory
         for new_dir in new_dirs:
-            new_dir.st_mode = S_IFDIR | (perm_bits & ~self.umask)
+            if apply_umask:
+                perm_bits &= ~self.umask
+            new_dir.st_mode = S_IFDIR | perm_bits
 
         return current_dir
 
@@ -2169,8 +2176,8 @@ class FakeFilesystem:
                 the file is considered to be in "large file mode" and trying
                 to read from or write to the file will result in an exception.
             create_missing_dirs: If `True`, auto create missing directories.
-            apply_umask: `True` if the current umask must be applied
-                on `st_mode`.
+            apply_umask: If `True` (default), the current umask is applied
+                to `st_mode`.
             encoding: If `contents` is of type `str`, the encoding used
                 for serialization.
             errors: The error mode used for encoding/decoding errors.


Test patch:
diff --git a/pyfakefs/tests/fake_filesystem_test.py b/pyfakefs/tests/fake_filesystem_test.py
index 7c9e1a5..12c05b8 100644
--- a/pyfakefs/tests/fake_filesystem_test.py
+++ b/pyfakefs/tests/fake_filesystem_test.py
@@ -515,6 +515,20 @@ class FakeFilesystemUnitTest(TestCase):
         self.assertEqual(os.path.basename(path), new_dir.name)
         self.assertTrue(stat.S_IFDIR & new_dir.st_mode)
 
+    def test_create_dir_umask(self):
+        old_umask = self.filesystem.umask
+        self.filesystem.umask = 0o22
+        path = "foo/bar/baz"
+        self.filesystem.create_dir(path, perm_bits=0o777)
+        new_dir = self.filesystem.get_object(path)
+        self.assertEqual(stat.S_IFDIR | 0o755, new_dir.st_mode)
+
+        path = "foo/bar/boo"
+        self.filesystem.create_dir(path, perm_bits=0o777, apply_umask=False)
+        new_dir = self.filesystem.get_object(path)
+        self.assertEqual(stat.S_IFDIR | 0o777, new_dir.st_mode)
+        self.filesystem.umask = old_umask
+
     def test_create_directory_already_exists_error(self):
         path = "foo/bar/baz"
         self.filesystem.create_dir(path)