Skip to content

tools.py

Some utility functions.

TemporaryDirectory (TemporaryDirectory)

A custom version of tempfile.TemporaryDirectory that handles read-only files better.

On Windows, before Python 3.8, shutil.rmtree does not handle read-only files very well. This custom class makes use of a special error handler to make sure that a temporary directory containing read-only files (typically created when git-cloning a repository) is properly cleaned-up (i.e. removed) after using it in a context manager.

Source code in copier/tools.py
class TemporaryDirectory(tempfile.TemporaryDirectory):
    """A custom version of `tempfile.TemporaryDirectory` that handles read-only files better.

    On Windows, before Python 3.8, `shutil.rmtree` does not handle read-only files very well.
    This custom class makes use of a [special error handler][copier.tools.handle_remove_readonly]
    to make sure that a temporary directory containing read-only files (typically created
    when git-cloning a repository) is properly cleaned-up (i.e. removed) after using it
    in a context manager.
    """

    @classmethod
    def _cleanup(cls, name, warn_message):
        cls._robust_cleanup(name)
        warnings.warn(warn_message, ResourceWarning)

    def cleanup(self):
        if self._finalizer.detach():
            self._robust_cleanup(self.name)

    @staticmethod
    def _robust_cleanup(name):
        shutil.rmtree(name, ignore_errors=False, onerror=handle_remove_readonly)

cast_str_to_bool(value)

Parse anything to bool.

Parameters:

Name Type Description Default
value Any

Anything to be casted to a bool. Tries to be as smart as possible.

  1. Cast to number. Then: 0 = False; anything else = True.
  2. Find YAML booleans, YAML nulls or none in it and use it appropriately.
  3. Cast to boolean using standard python bool(value).
required
Source code in copier/tools.py
def cast_str_to_bool(value: Any) -> bool:
    """Parse anything to bool.

    Params:
        value:
            Anything to be casted to a bool. Tries to be as smart as possible.

            1.  Cast to number. Then: 0 = False; anything else = True.
            1.  Find [YAML booleans](https://yaml.org/type/bool.html),
                [YAML nulls](https://yaml.org/type/null.html) or `none` in it
                and use it appropriately.
            1.  Cast to boolean using standard python `bool(value)`.
    """
    # Assume it's a number
    with suppress(TypeError, ValueError):
        return bool(float(value))
    # Assume it's a string
    with suppress(AttributeError):
        lower = value.lower()
        if lower in {"y", "yes", "t", "true", "on"}:
            return True
        elif lower in {"n", "no", "f", "false", "off", "~", "null", "none"}:
            return False
    # Assume nothing
    return bool(value)

copier_version()

Get closest match for the installed copier version.

Source code in copier/tools.py
def copier_version() -> Version:
    """Get closest match for the installed copier version."""
    # Importing __version__ at the top of the module creates a circular import
    # ("cannot import name '__version__' from partially initialized module 'copier'"),
    # so instead we do a lazy import here
    from . import __version__

    if __version__ != "0.0.0":
        return Version(__version__)

    # Get the installed package version otherwise, which is sometimes more specific
    return Version(version("copier"))

force_str_end(original_str, end='\n')

Make sure a original_str ends with end.

Parameters:

Name Type Description Default
original_str str

String that you want to ensure ending.

required
end str

String that must exist at the end of original_str

'\n'
Source code in copier/tools.py
def force_str_end(original_str: str, end: str = "\n") -> str:
    """Make sure a `original_str` ends with `end`.

    Params:
        original_str: String that you want to ensure ending.
        end: String that must exist at the end of `original_str`
    """
    if not original_str.endswith(end):
        return original_str + end
    return original_str

handle_remove_readonly(func, path, exc)

Handle errors when trying to remove read-only files through shutil.rmtree.

This handler makes sure the given file is writable, then re-execute the given removal function.

Parameters:

Name Type Description Default
func Callable

An OS-dependant function used to remove a file.

required
path str

The path to the file to remove.

required
exc Tuple[BaseException, OSError, traceback]

A sys.exc_info() object.

required
Source code in copier/tools.py
def handle_remove_readonly(
    func: Callable, path: str, exc: Tuple[BaseException, OSError, TracebackType]
) -> None:
    """Handle errors when trying to remove read-only files through `shutil.rmtree`.

    This handler makes sure the given file is writable, then re-execute the given removal function.

    Arguments:
        func: An OS-dependant function used to remove a file.
        path: The path to the file to remove.
        exc: A `sys.exc_info()` object.
    """
    excvalue = exc[1]
    if func in (os.rmdir, os.remove, os.unlink) and excvalue.errno == errno.EACCES:
        os.chmod(path, stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO)  # 0777
        func(path)
    else:
        raise
Back to top