From 45fb826e8a2249138ea724ad40d1d26357259bdf Mon Sep 17 00:00:00 2001 From: jb2170 Date: Thu, 2 Jan 2025 03:28:50 +0000 Subject: [PATCH 01/13] Add `shutil.umask` context manager implementation --- Lib/shutil.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/Lib/shutil.py b/Lib/shutil.py index 171489ca41f2a7..33e6041b10313c 100644 --- a/Lib/shutil.py +++ b/Lib/shutil.py @@ -10,6 +10,7 @@ import fnmatch import collections import errno +from contextlib import AbstractContextManager try: import zlib @@ -61,7 +62,7 @@ "get_unpack_formats", "register_unpack_format", "unregister_unpack_format", "unpack_archive", "ignore_patterns", "chown", "which", "get_terminal_size", - "SameFileError"] + "SameFileError", "umask"] # disk_usage is added later, if available on the platform class Error(OSError): @@ -1581,6 +1582,21 @@ def which(cmd, mode=os.F_OK | os.X_OK, path=None): return name return None + +class umask(AbstractContextManager): + """Non thread-safe context manager to change the process's umask.""" + + def __init__(self, mask): + self.mask = mask + self._old_mask = [] + + def __enter__(self): + self._old_mask.append(os.umask(self.mask)) + + def __exit__(self, *excinfo): + os.umask(self._old_mask.pop()) + + def __getattr__(name): if name == "ExecError": import warnings From fa40444857b3641bb9e790549ce2ca3c1a8b16c9 Mon Sep 17 00:00:00 2001 From: jb2170 Date: Thu, 2 Jan 2025 03:29:05 +0000 Subject: [PATCH 02/13] Add `shutil.umask` context manager documentation --- Doc/library/shutil.rst | 44 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/Doc/library/shutil.rst b/Doc/library/shutil.rst index 2a8592f8bd69c1..b45c035275bd97 100644 --- a/Doc/library/shutil.rst +++ b/Doc/library/shutil.rst @@ -877,6 +877,50 @@ Querying the size of the output terminal The ``fallback`` values are also used if :func:`os.get_terminal_size` returns zeroes. +.. _shutil-context-managers: + +Context managers to modify process execution environments +--------------------------------------------------------- + +High-level :term:`context managers ` for changing a process's execution environment. + +.. warning:: + + These may change process-wide states, such as the current working directory, + and as such are not suitable for use in most threaded or async contexts. + They are also not suitable for most non-linear code execution, like generators, + where the program execution is temporarily relinquished. Unless explicitly + desired, you should not yield within these context managers. + +.. class:: umask(mask) + + This is a simple wrapper around :func:`os.umask`. It changes the process's + umask upon entering and restores the old one on exit. + + This context manager is :ref:`reentrant `. + + .. versionadded:: 3.14 + +In this example, we use a :class:`umask` context manager, within which we +create a file that only the user can access:: + + >>> from shutil import umask + >>> private_umask = umask(0o077) + >>> with private_umask: + ... with open("my-secret-file", "w") as f: + ... f.write("I ate all the cake!\n") + +The file's permissions are empty for anyone but the user: + +.. code-block:: shell-session + + $ ls -l my-secret-file + -rw------- 1 jb2170 jb2170 20 Jan 2 01:23 my-secret-file + +Using :class:`umask` like this is better practice than first creating the file, +and later changing its permissions with :func:`~os.chmod`, between which a +period of time exists in which the file may have too lenient permissions. + .. _`fcopyfile`: http://www.manpagez.com/man/3/copyfile/ From c66565518e545acaa030c570eadfb7669eb9c674 Mon Sep 17 00:00:00 2001 From: jb2170 Date: Thu, 2 Jan 2025 03:29:39 +0000 Subject: [PATCH 03/13] Add `shutil.umask` context manager tests, based off those of `contextlib.chdir` --- Lib/test/test_shutil.py | 52 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py index 1f18b1f09b5858..3f11c6cf71d526 100644 --- a/Lib/test/test_shutil.py +++ b/Lib/test/test_shutil.py @@ -21,7 +21,7 @@ get_archive_formats, Error, unpack_archive, register_unpack_format, RegistryError, unregister_unpack_format, get_unpack_formats, - SameFileError, _GiveupOnFastCopy) + SameFileError, _GiveupOnFastCopy, umask) import tarfile import zipfile try: @@ -3466,5 +3466,55 @@ def test_module_all_attribute(self): from shutil import ExecError +class TestUmask(unittest.TestCase): + # make target masks in here sufficiently exotic, away from 0o022 + + mask_private = 0o777 + mask_public = 0o000 + + def get_mask(self): + os.umask(mask := os.umask(0)) + return mask + + def test_simple(self): + old_mask = self.get_mask() + target_mask = self.mask_private + self.assertNotEqual(old_mask, target_mask) + + with umask(target_mask): + self.assertEqual(self.get_mask(), target_mask) + self.assertEqual(self.get_mask(), old_mask) + + def test_reentrant(self): + old_mask = self.get_mask() + target_mask_1 = self.mask_private + target_mask_2 = self.mask_public + self.assertNotIn(old_mask, (target_mask_1, target_mask_2)) + umask1, umask2 = umask(target_mask_1), umask(target_mask_2) + + with umask1: + self.assertEqual(self.get_mask(), target_mask_1) + with umask2: + self.assertEqual(self.get_mask(), target_mask_2) + with umask1: + self.assertEqual(self.get_mask(), target_mask_1) + self.assertEqual(self.get_mask(), target_mask_2) + self.assertEqual(self.get_mask(), target_mask_1) + self.assertEqual(self.get_mask(), old_mask) + + def test_exception(self): + old_mask = self.get_mask() + target_mask = self.mask_private + self.assertNotEqual(old_mask, target_mask) + + try: + with umask(target_mask): + self.assertEqual(self.get_mask(), target_mask) + raise RuntimeError("boom") + except RuntimeError as re: + self.assertEqual(str(re), "boom") + self.assertEqual(self.get_mask(), old_mask) + + if __name__ == '__main__': unittest.main() From 9438fa1a95daef04ac0e7296e8a81be7b69d8b76 Mon Sep 17 00:00:00 2001 From: jb2170 Date: Thu, 2 Jan 2025 05:02:24 +0000 Subject: [PATCH 04/13] Fix PublicAPITests.test_module_all_attribute --- Lib/test/test_shutil.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py index 3f11c6cf71d526..fe40ab97a93da8 100644 --- a/Lib/test/test_shutil.py +++ b/Lib/test/test_shutil.py @@ -3458,7 +3458,7 @@ def test_module_all_attribute(self): 'unregister_archive_format', 'get_unpack_formats', 'register_unpack_format', 'unregister_unpack_format', 'unpack_archive', 'ignore_patterns', 'chown', 'which', - 'get_terminal_size', 'SameFileError'] + 'get_terminal_size', 'SameFileError', 'umask'] if hasattr(os, 'statvfs') or os.name == 'nt': target_api.append('disk_usage') self.assertEqual(set(shutil.__all__), set(target_api)) From 943d0c910155758dfefb9c79419c5424edba035a Mon Sep 17 00:00:00 2001 From: jb2170 Date: Fri, 3 Jan 2025 01:21:31 +0000 Subject: [PATCH 05/13] Add NEWS entry for shutil.umask --- .../next/Library/2025-01-03-01-12-47.gh-issue-128432.8kg5DK.rst | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2025-01-03-01-12-47.gh-issue-128432.8kg5DK.rst diff --git a/Misc/NEWS.d/next/Library/2025-01-03-01-12-47.gh-issue-128432.8kg5DK.rst b/Misc/NEWS.d/next/Library/2025-01-03-01-12-47.gh-issue-128432.8kg5DK.rst new file mode 100644 index 00000000000000..436eb9bb856ca0 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-01-03-01-12-47.gh-issue-128432.8kg5DK.rst @@ -0,0 +1,2 @@ +Add a :class:`~shutil.umask` context manager to :mod:`shutil`, to provide a +stdlib context manager for changing a process's umask From e8c824f7b85c01c421fd28fb8b10d7aa3b1499bf Mon Sep 17 00:00:00 2001 From: jb2170 Date: Fri, 3 Jan 2025 02:45:35 +0000 Subject: [PATCH 06/13] Skip TestUmask for non-posix OSs + wasi + emscripten --- Lib/test/test_shutil.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py index fe40ab97a93da8..23edc3fbb9799e 100644 --- a/Lib/test/test_shutil.py +++ b/Lib/test/test_shutil.py @@ -3466,6 +3466,8 @@ def test_module_all_attribute(self): from shutil import ExecError +@unittest.skipIf(os.name != "posix" or support.is_wasi or support.is_emscripten, + "need proper os.umask()") class TestUmask(unittest.TestCase): # make target masks in here sufficiently exotic, away from 0o022 From d53cf30f3c8fe313b25aee170a18c0b77716fc26 Mon Sep 17 00:00:00 2001 From: jb2170 Date: Fri, 3 Jan 2025 22:15:52 +0000 Subject: [PATCH 07/13] Tweak proposed shutil documentation --- Doc/library/shutil.rst | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/Doc/library/shutil.rst b/Doc/library/shutil.rst index b45c035275bd97..b4d4f3caac82c5 100644 --- a/Doc/library/shutil.rst +++ b/Doc/library/shutil.rst @@ -886,11 +886,11 @@ High-level :term:`context managers ` for changing a process's e .. warning:: - These may change process-wide states, such as the current working directory, - and as such are not suitable for use in most threaded or async contexts. - They are also not suitable for most non-linear code execution, like generators, - where the program execution is temporarily relinquished. Unless explicitly - desired, you should not yield within these context managers. + These may change process-wide states, and as such are not suitable for use + in most threaded or async contexts. They are also not suitable for most + non-linear code execution, like generators, where the program execution is + temporarily relinquished. Unless explicitly desired, you should not yield + within these context managers. .. class:: umask(mask) @@ -899,23 +899,24 @@ High-level :term:`context managers ` for changing a process's e This context manager is :ref:`reentrant `. - .. versionadded:: 3.14 + .. versionadded:: next In this example, we use a :class:`umask` context manager, within which we -create a file that only the user can access:: +create a file that only the user can access: - >>> from shutil import umask - >>> private_umask = umask(0o077) - >>> with private_umask: - ... with open("my-secret-file", "w") as f: - ... f.write("I ate all the cake!\n") +.. code-block:: pycon + + >>> from shutil import umask + >>> with umask(0o077): + ... with open("my-secret-file", "w") as f: + ... f.write("I ate all the cake!\n") The file's permissions are empty for anyone but the user: .. code-block:: shell-session $ ls -l my-secret-file - -rw------- 1 jb2170 jb2170 20 Jan 2 01:23 my-secret-file + -rw------- 1 guest guest 20 Jan 1 23:45 my-secret-file Using :class:`umask` like this is better practice than first creating the file, and later changing its permissions with :func:`~os.chmod`, between which a From dc1dcc02a6417be8eab13163c30a9fbe512ce8aa Mon Sep 17 00:00:00 2001 From: jb2170 Date: Fri, 3 Jan 2025 22:20:43 +0000 Subject: [PATCH 08/13] Change proposed name of shutil.umask to shutil.umask_of --- Doc/library/shutil.rst | 10 +++++----- Lib/shutil.py | 4 ++-- Lib/test/test_shutil.py | 12 ++++++------ .../2025-01-03-01-12-47.gh-issue-128432.8kg5DK.rst | 2 +- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/Doc/library/shutil.rst b/Doc/library/shutil.rst index b4d4f3caac82c5..6f38c6604037fc 100644 --- a/Doc/library/shutil.rst +++ b/Doc/library/shutil.rst @@ -892,7 +892,7 @@ High-level :term:`context managers ` for changing a process's e temporarily relinquished. Unless explicitly desired, you should not yield within these context managers. -.. class:: umask(mask) +.. class:: umask_of(mask) This is a simple wrapper around :func:`os.umask`. It changes the process's umask upon entering and restores the old one on exit. @@ -901,13 +901,13 @@ High-level :term:`context managers ` for changing a process's e .. versionadded:: next -In this example, we use a :class:`umask` context manager, within which we +In this example, we use a :class:`umask_of` context manager, within which we create a file that only the user can access: .. code-block:: pycon - >>> from shutil import umask - >>> with umask(0o077): + >>> from shutil import umask_of + >>> with umask_of(0o077): ... with open("my-secret-file", "w") as f: ... f.write("I ate all the cake!\n") @@ -918,7 +918,7 @@ The file's permissions are empty for anyone but the user: $ ls -l my-secret-file -rw------- 1 guest guest 20 Jan 1 23:45 my-secret-file -Using :class:`umask` like this is better practice than first creating the file, +Using :class:`umask_of` like this is better practice than first creating the file, and later changing its permissions with :func:`~os.chmod`, between which a period of time exists in which the file may have too lenient permissions. diff --git a/Lib/shutil.py b/Lib/shutil.py index 33e6041b10313c..eea348e2390760 100644 --- a/Lib/shutil.py +++ b/Lib/shutil.py @@ -62,7 +62,7 @@ "get_unpack_formats", "register_unpack_format", "unregister_unpack_format", "unpack_archive", "ignore_patterns", "chown", "which", "get_terminal_size", - "SameFileError", "umask"] + "SameFileError", "umask_of"] # disk_usage is added later, if available on the platform class Error(OSError): @@ -1583,7 +1583,7 @@ def which(cmd, mode=os.F_OK | os.X_OK, path=None): return None -class umask(AbstractContextManager): +class umask_of(AbstractContextManager): """Non thread-safe context manager to change the process's umask.""" def __init__(self, mask): diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py index 23edc3fbb9799e..fe74e7d0dd4903 100644 --- a/Lib/test/test_shutil.py +++ b/Lib/test/test_shutil.py @@ -21,7 +21,7 @@ get_archive_formats, Error, unpack_archive, register_unpack_format, RegistryError, unregister_unpack_format, get_unpack_formats, - SameFileError, _GiveupOnFastCopy, umask) + SameFileError, _GiveupOnFastCopy, umask_of) import tarfile import zipfile try: @@ -3458,7 +3458,7 @@ def test_module_all_attribute(self): 'unregister_archive_format', 'get_unpack_formats', 'register_unpack_format', 'unregister_unpack_format', 'unpack_archive', 'ignore_patterns', 'chown', 'which', - 'get_terminal_size', 'SameFileError', 'umask'] + 'get_terminal_size', 'SameFileError', 'umask_of'] if hasattr(os, 'statvfs') or os.name == 'nt': target_api.append('disk_usage') self.assertEqual(set(shutil.__all__), set(target_api)) @@ -3468,7 +3468,7 @@ def test_module_all_attribute(self): @unittest.skipIf(os.name != "posix" or support.is_wasi or support.is_emscripten, "need proper os.umask()") -class TestUmask(unittest.TestCase): +class TestUmaskOf(unittest.TestCase): # make target masks in here sufficiently exotic, away from 0o022 mask_private = 0o777 @@ -3483,7 +3483,7 @@ def test_simple(self): target_mask = self.mask_private self.assertNotEqual(old_mask, target_mask) - with umask(target_mask): + with umask_of(target_mask): self.assertEqual(self.get_mask(), target_mask) self.assertEqual(self.get_mask(), old_mask) @@ -3492,7 +3492,7 @@ def test_reentrant(self): target_mask_1 = self.mask_private target_mask_2 = self.mask_public self.assertNotIn(old_mask, (target_mask_1, target_mask_2)) - umask1, umask2 = umask(target_mask_1), umask(target_mask_2) + umask1, umask2 = umask_of(target_mask_1), umask_of(target_mask_2) with umask1: self.assertEqual(self.get_mask(), target_mask_1) @@ -3510,7 +3510,7 @@ def test_exception(self): self.assertNotEqual(old_mask, target_mask) try: - with umask(target_mask): + with umask_of(target_mask): self.assertEqual(self.get_mask(), target_mask) raise RuntimeError("boom") except RuntimeError as re: diff --git a/Misc/NEWS.d/next/Library/2025-01-03-01-12-47.gh-issue-128432.8kg5DK.rst b/Misc/NEWS.d/next/Library/2025-01-03-01-12-47.gh-issue-128432.8kg5DK.rst index 436eb9bb856ca0..70a63252862b96 100644 --- a/Misc/NEWS.d/next/Library/2025-01-03-01-12-47.gh-issue-128432.8kg5DK.rst +++ b/Misc/NEWS.d/next/Library/2025-01-03-01-12-47.gh-issue-128432.8kg5DK.rst @@ -1,2 +1,2 @@ -Add a :class:`~shutil.umask` context manager to :mod:`shutil`, to provide a +Add a :class:`~shutil.umask_of` context manager to :mod:`shutil`, to provide a stdlib context manager for changing a process's umask From 0ff7997f96f5d7143addcf322d05fc3b83800dbb Mon Sep 17 00:00:00 2001 From: jb2170 Date: Sat, 4 Jan 2025 01:57:41 +0000 Subject: [PATCH 09/13] Update What'sNew --- Doc/whatsnew/3.14.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst index 61f5ffdb6c89d1..d765934b563f52 100644 --- a/Doc/whatsnew/3.14.rst +++ b/Doc/whatsnew/3.14.rst @@ -588,6 +588,13 @@ pydoc (Contributed by Jelle Zijlstra in :gh:`101552`.) +shutil +------ + +* Add a new context manager :class:`~shutil.umask_of`, within which a + process's umask is temporarily changed + + ssl --- From df59fdeacb4b4ba4fcd78fc15440bac4c9090a71 Mon Sep 17 00:00:00 2001 From: jb2170 Date: Sat, 4 Jan 2025 04:35:21 +0000 Subject: [PATCH 10/13] Add example usage of umask_of to write permission-agnostic code --- Doc/library/shutil.rst | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/Doc/library/shutil.rst b/Doc/library/shutil.rst index 6f38c6604037fc..45f302265b95ac 100644 --- a/Doc/library/shutil.rst +++ b/Doc/library/shutil.rst @@ -915,13 +915,39 @@ The file's permissions are empty for anyone but the user: .. code-block:: shell-session - $ ls -l my-secret-file - -rw------- 1 guest guest 20 Jan 1 23:45 my-secret-file + $ ls -l my-secret-file + -rw------- 1 guest guest 20 Jan 1 23:45 my-secret-file Using :class:`umask_of` like this is better practice than first creating the file, and later changing its permissions with :func:`~os.chmod`, between which a period of time exists in which the file may have too lenient permissions. +It also allows you to write code that creates files, in a way that is agnostic +of permissions -- that is without the need to pass a custom ``mode`` keyword +argument to :func:`open` every time. + +In this example we create files with a function ``touch_file`` which uses the +:func:`open` built-in without setting ``mode``. Permissions are managed by +:class:`!umask_of`: + +.. code-block:: pycon + + >>> from shutil import umask_of + >>> + >>> def touch_file(path): + ... with open(path, "a"): + ... pass + ... + >>> touch_file("normal-file") + >>> with umask_of(0o077): + ... touch_file("private-file") + +.. code-block:: shell-session + + $ ls -l normal-file private-file + -rw-r--r-- 1 guest guest 0 Jan 1 23:45 normal-file + -rw------- 1 guest guest 0 Jan 1 23:45 private-file + .. _`fcopyfile`: http://www.manpagez.com/man/3/copyfile/ From 12ceaace8443ebac12bb8694952a76d9a287c7b9 Mon Sep 17 00:00:00 2001 From: jb2170 Date: Sat, 4 Jan 2025 19:54:03 +0000 Subject: [PATCH 11/13] Change proposed name to shutil.umask_context --- Doc/library/shutil.rst | 22 +++++++++---------- Doc/whatsnew/3.14.rst | 2 +- Lib/shutil.py | 4 ++-- Lib/test/test_shutil.py | 10 ++++----- ...-01-03-01-12-47.gh-issue-128432.8kg5DK.rst | 4 ++-- 5 files changed, 21 insertions(+), 21 deletions(-) diff --git a/Doc/library/shutil.rst b/Doc/library/shutil.rst index 45f302265b95ac..2e51dad6ba1315 100644 --- a/Doc/library/shutil.rst +++ b/Doc/library/shutil.rst @@ -892,7 +892,7 @@ High-level :term:`context managers ` for changing a process's e temporarily relinquished. Unless explicitly desired, you should not yield within these context managers. -.. class:: umask_of(mask) +.. class:: umask_context(mask) This is a simple wrapper around :func:`os.umask`. It changes the process's umask upon entering and restores the old one on exit. @@ -901,13 +901,13 @@ High-level :term:`context managers ` for changing a process's e .. versionadded:: next -In this example, we use a :class:`umask_of` context manager, within which we -create a file that only the user can access: +In this example, we use a :class:`umask_context`, within which we create +a file that only the user can access: .. code-block:: pycon - >>> from shutil import umask_of - >>> with umask_of(0o077): + >>> from shutil import umask_context + >>> with umask_context(0o077): ... with open("my-secret-file", "w") as f: ... f.write("I ate all the cake!\n") @@ -918,9 +918,9 @@ The file's permissions are empty for anyone but the user: $ ls -l my-secret-file -rw------- 1 guest guest 20 Jan 1 23:45 my-secret-file -Using :class:`umask_of` like this is better practice than first creating the file, -and later changing its permissions with :func:`~os.chmod`, between which a -period of time exists in which the file may have too lenient permissions. +Using :class:`umask_context` like this is better practice than first creating +the file, and later changing its permissions with :func:`~os.chmod`, between +which a period of time exists in which the file may have too lenient permissions. It also allows you to write code that creates files, in a way that is agnostic of permissions -- that is without the need to pass a custom ``mode`` keyword @@ -928,18 +928,18 @@ argument to :func:`open` every time. In this example we create files with a function ``touch_file`` which uses the :func:`open` built-in without setting ``mode``. Permissions are managed by -:class:`!umask_of`: +:class:`umask_context`: .. code-block:: pycon - >>> from shutil import umask_of + >>> from shutil import umask_context >>> >>> def touch_file(path): ... with open(path, "a"): ... pass ... >>> touch_file("normal-file") - >>> with umask_of(0o077): + >>> with umask_context(0o077): ... touch_file("private-file") .. code-block:: shell-session diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst index d765934b563f52..2b713f653d9c19 100644 --- a/Doc/whatsnew/3.14.rst +++ b/Doc/whatsnew/3.14.rst @@ -591,7 +591,7 @@ pydoc shutil ------ -* Add a new context manager :class:`~shutil.umask_of`, within which a +* Add a new context manager :class:`~shutil.umask_context`, within which a process's umask is temporarily changed diff --git a/Lib/shutil.py b/Lib/shutil.py index eea348e2390760..868e17baddbc08 100644 --- a/Lib/shutil.py +++ b/Lib/shutil.py @@ -62,7 +62,7 @@ "get_unpack_formats", "register_unpack_format", "unregister_unpack_format", "unpack_archive", "ignore_patterns", "chown", "which", "get_terminal_size", - "SameFileError", "umask_of"] + "SameFileError", "umask_context"] # disk_usage is added later, if available on the platform class Error(OSError): @@ -1583,7 +1583,7 @@ def which(cmd, mode=os.F_OK | os.X_OK, path=None): return None -class umask_of(AbstractContextManager): +class umask_context(AbstractContextManager): """Non thread-safe context manager to change the process's umask.""" def __init__(self, mask): diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py index fe74e7d0dd4903..ff13d953a92734 100644 --- a/Lib/test/test_shutil.py +++ b/Lib/test/test_shutil.py @@ -21,7 +21,7 @@ get_archive_formats, Error, unpack_archive, register_unpack_format, RegistryError, unregister_unpack_format, get_unpack_formats, - SameFileError, _GiveupOnFastCopy, umask_of) + SameFileError, _GiveupOnFastCopy, umask_context) import tarfile import zipfile try: @@ -3458,7 +3458,7 @@ def test_module_all_attribute(self): 'unregister_archive_format', 'get_unpack_formats', 'register_unpack_format', 'unregister_unpack_format', 'unpack_archive', 'ignore_patterns', 'chown', 'which', - 'get_terminal_size', 'SameFileError', 'umask_of'] + 'get_terminal_size', 'SameFileError', 'umask_context'] if hasattr(os, 'statvfs') or os.name == 'nt': target_api.append('disk_usage') self.assertEqual(set(shutil.__all__), set(target_api)) @@ -3483,7 +3483,7 @@ def test_simple(self): target_mask = self.mask_private self.assertNotEqual(old_mask, target_mask) - with umask_of(target_mask): + with umask_context(target_mask): self.assertEqual(self.get_mask(), target_mask) self.assertEqual(self.get_mask(), old_mask) @@ -3492,7 +3492,7 @@ def test_reentrant(self): target_mask_1 = self.mask_private target_mask_2 = self.mask_public self.assertNotIn(old_mask, (target_mask_1, target_mask_2)) - umask1, umask2 = umask_of(target_mask_1), umask_of(target_mask_2) + umask1, umask2 = umask_context(target_mask_1), umask_context(target_mask_2) with umask1: self.assertEqual(self.get_mask(), target_mask_1) @@ -3510,7 +3510,7 @@ def test_exception(self): self.assertNotEqual(old_mask, target_mask) try: - with umask_of(target_mask): + with umask_context(target_mask): self.assertEqual(self.get_mask(), target_mask) raise RuntimeError("boom") except RuntimeError as re: diff --git a/Misc/NEWS.d/next/Library/2025-01-03-01-12-47.gh-issue-128432.8kg5DK.rst b/Misc/NEWS.d/next/Library/2025-01-03-01-12-47.gh-issue-128432.8kg5DK.rst index 70a63252862b96..45c2d1fc87e31e 100644 --- a/Misc/NEWS.d/next/Library/2025-01-03-01-12-47.gh-issue-128432.8kg5DK.rst +++ b/Misc/NEWS.d/next/Library/2025-01-03-01-12-47.gh-issue-128432.8kg5DK.rst @@ -1,2 +1,2 @@ -Add a :class:`~shutil.umask_of` context manager to :mod:`shutil`, to provide a -stdlib context manager for changing a process's umask +Add a :class:`~shutil.umask_context` context manager to :mod:`shutil`, to +provide a stdlib context manager for changing a process's umask From bb8955c3d9a317a7b4a4dae09c07b2ea6fbaa246 Mon Sep 17 00:00:00 2001 From: jb2170 Date: Sat, 4 Jan 2025 20:56:08 +0000 Subject: [PATCH 12/13] Typo: TestUmaskOf -> TestUmaskContext --- Lib/test/test_shutil.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py index ff13d953a92734..b772f3db5bba11 100644 --- a/Lib/test/test_shutil.py +++ b/Lib/test/test_shutil.py @@ -3468,7 +3468,7 @@ def test_module_all_attribute(self): @unittest.skipIf(os.name != "posix" or support.is_wasi or support.is_emscripten, "need proper os.umask()") -class TestUmaskOf(unittest.TestCase): +class TestUmaskContext(unittest.TestCase): # make target masks in here sufficiently exotic, away from 0o022 mask_private = 0o777 From 786dddf1ff868357544d4d6898f4ceca46bcd455 Mon Sep 17 00:00:00 2001 From: jb2170 Date: Sat, 4 Jan 2025 23:08:19 +0000 Subject: [PATCH 13/13] Add Contributed by... in WhatsNew, woop! --- Doc/whatsnew/3.14.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst index 2b713f653d9c19..202c4374ac5de7 100644 --- a/Doc/whatsnew/3.14.rst +++ b/Doc/whatsnew/3.14.rst @@ -593,6 +593,7 @@ shutil * Add a new context manager :class:`~shutil.umask_context`, within which a process's umask is temporarily changed + (Contributed by Jay Berry in :gh:`128433`.) ssl