Intro
A umask
context manager is useful for when one requires files created by a section of a program to have a restricted set of permissions. For example it’s no good to
$ touch new-secret-file
$ chmod g=,o= new-secret-file
with a default umask of 0o022 active, as there’s a race condition present: anyone has read-access to new-secret-file
before the chmod takes place. Therefore setting the umask to 0o077 before this whole block of code, and afterwards unconditionally restoring the umask back to whatever it was before (eg 0o022), is the correct practice (and renders the chmod-ing of each individual file unnecessary). This makes for a nice context manager.
I find myself writing / importing my own umask context manager in several projects and it would be nice to have it as a standard library import. I’ve seen other people do the same. A naive implementation is
@contextmanager
def umask(mask):
umask_old = os.umask(mask)
try:
yield
finally:
os.umask(umask_old)
Implementation
But wait, we can make it even better! We make umask
a class (like chdir
is), rather than just a @contextmanager
decorated generator. This allows a umask
instance itself to be passed around as a re-usable object, rather than passing around the argument(s) to construct a new one each time it’s required. The proposed implementation is as follows:
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())
Remarks, Quirks, and Teaching
The style of having a list of _old_mask
makes the context manager reentrant-safe as has been discussed here for chdir
. I hadn’t originally considered this, and it’s a good example of why joining a discussion site like this is so useful: instead of rushing an implementation, first look at previous discussions!
As mentioned several times previously, this is in the same vein as the chdir
context manager which was eventually added in 2021 per bpo-25625. The degree of contaminating side effects of umask(2)
on sibling threads, or asyncio sibling tasks, is the same as that of chdir(2)
, and any objections / arguments seem to have already been discussed. umask(2)
is a process-wide effect, and so long as this is sufficiently acknowledged in the contextlib documentation, contextlib.umask
seems as acceptable as contextlib.chdir
is.
The os
import is already present in contextlib
so there’s no more modules to be imported, which is nice.