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.
|
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 |
'\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 |
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