Move `contextlib.chdir` to become `shutil.chdir`

I’m still against the idea of programmatically deprecating contextlib.chdir in the near term. Maybe document it as deprecated and suggest importing the equivalent functionality from shutil instead, but the earliest that programmatic deprecation should be considered is when the last version without the shutil interface is no longer supported (which would be 3.13 assuming the shutil interface is added in 3.14, so it would drop out of support when 3.18 was released).

Regarding chdir as the verb form vs cwd as the noun form, that distinction already exists in the os module, due to os.getcwd() being the query counterpart to os.chdir(dirpath). cwd also shows up as a parameter name in the os.startfile Windows-only API, as well as in the cross-platform subprocess APIs.

I do like the idea of a common naming convention for these kinds of CM wrappers, but given the umask and chdir examples, my preferred convention would actually be ensure_{NOUN}, with the name being based on the related query API, rather than the modification API.

For umask, the modification API is the query API, so it’s ensure_umask either way.
For chdir, the corresponding query API is getcwd, so that convention gives ensure_cwd.

Until contextlib.chdir is actively deprecated (in 3.18 or so), the implementation of the latter can just be:

from contextlib import chdir as _ensure_cwd_impl

class ensure_cwd(_ensure_cwd_impl):
    pass

Alternatively, the implementation could be moved to shutil immediately, and the __getattr__ approach could be added to contextlib without the programmatic deprecation warning[1]:

def __getattr__(name):
    global chdir

    if name == 'chdir':
        # This will be deprecated once 3.18 is released and 3.13 is no longer supported
        # (as all still supported versions will provide shutil.ensure_cwd at that point)
        from shutil import ensure_cwd as chdir
        return chdir

    raise AttributeError(f"module {__name__!r} has no attribute {name!r}")

While the latter technically breaks pickle compatibility (since only targets with shutil.ensure_cwd will be able to unpickle the result), this isn’t the kind of object that anyone is likely to try to transmit or save via pickle.


  1. import statements (with or without as clauses) can bind global names directly, so there’s no need for a separate local variable name. ↩︎

4 Likes