# © Copyright 2021-2022, Scott Gasch
-"""Utilities for working with files."""
+"""
+This is a grab bag of file-related utilities. It has code to, for example,
+read files transforming the text as its read, normalize pathnames, strip
+extensions, read and manipulate atimes/mtimes/ctimes, compute a signature
+based on a file's contents, traverse the file system recursively, etc...
+"""
import contextlib
import datetime
filename: file to be read
skip_blank_lines: should reading skip blank lines?
line_transformers: little string->string transformations
+
+ Returns:
+ A list of lines from the read and transformed file contents.
"""
ret = []
Returns:
the path with one extension removed.
+ See also :meth:`without_all_extensions`.
+
>>> without_extension('foobar.txt')
'foobar'
Returns:
the path with all extensions removed.
+ See also :meth:`without_extension`
+
>>> without_all_extensions('/home/scott/foobar.1.tar.gz')
'/home/scott/foobar'
Returns:
The last extension from the file path.
+ See also :meth:`without_extension`, :meth:`without_all_extensions`,
+ :meth:`get_all_extensions`.
+
>>> get_extension('this_is_a_test.txt')
'.txt'
Returns:
a list containing each extension which may be empty.
+ See also :meth:`without_extension`, :meth:`without_all_extensions`,
+ :meth:`get_extension`.
+
>>> get_all_extensions('/home/scott/foo.tar.gz.1')
['.tar', '.gz', '.1']
Returns:
filespec without leading dir components.
+ See also :meth:`get_path`, :meth:`get_canonical_path`.
+
>>> without_path('/home/scott/foo.py')
'foo.py'
filespec with just the leading directory components and no
filename or extension(s)
+ See also :meth:`without_path`, :meth:`get_canonical_path`.
+
>>> get_path('/home/scott/foobar.py')
'/home/scott'
Returns:
the canonicalized path
+ See also :meth:`get_path`, :meth:`without_path`.
+
>>> get_canonical_path('/home/scott/../../home/lynn/../scott/foo.txt')
'/usr/home/scott/foo.txt'
"""
Attempts to create path if it does not exist already.
- .. warning::
-
- Files are created with mode 0x0777 (i.e. world read/writeable).
-
Args:
path: the path to attempt to create
on_error: If True, it's invoked on error conditions. Otherwise
any exceptions are raised.
+ See also :meth:`does_file_exist`.
+
+ .. warning::
+
+ Files are created with mode 0x0777 (i.e. world read/writeable).
+
>>> import uuid
>>> import os
>>> path = os.path.join("/tmp", str(uuid.uuid4()), str(uuid.uuid4()))
Returns:
True if filename exists and is a normal file.
+ See also :meth:`create_path_if_not_exist`, :meth:`file_is_readable`.
+
>>> does_file_exist(__file__)
True
>>> does_file_exist('/tmp/2492043r9203r9230r9230r49230r42390r4230')
def file_is_readable(filename: str) -> bool:
- """True if file exists, is a normal file and is readable by the
- current process. False otherwise.
+ """Is the file readable?
Args:
filename: the filename to check for read access
+
+ Returns:
+ True if the file exists, is a normal file, and is readable
+ by the current process. False otherwise.
+
+ See also :meth:`does_file_exist`, :meth:`file_is_writable`,
+ :meth:`file_is_executable`.
"""
return does_file_exist(filename) and os.access(filename, os.R_OK)
def file_is_writable(filename: str) -> bool:
- """True if file exists, is a normal file and is writable by the
- current process. False otherwise.
+ """Is the file writable?
Args:
filename: the file to check for write access.
+
+ Returns:
+ True if file exists, is a normal file and is writable by the
+ current process. False otherwise.
+
+ See also :meth:`file_is_readable`, :meth:`does_file_exist`.
"""
return does_file_exist(filename) and os.access(filename, os.W_OK)
def file_is_executable(filename: str) -> bool:
- """True if file exists, is a normal file and is executable by the
- current process. False otherwise.
+ """Is the file executable?
Args:
filename: the file to check for execute access.
+
+ Returns:
+ True if file exists, is a normal file and is executable by the
+ current process. False otherwise.
+
+ See also :meth:`does_file_exist`, :meth:`file_is_readable`,
+ :meth:`file_is_writable`.
"""
return does_file_exist(filename) and os.access(filename, os.X_OK)
def does_directory_exist(dirname: str) -> bool:
- """Returns True if a file exists and is a directory.
+ """Does the given directory exist?
+
+ Args:
+ dirname: the name of the directory to check
+
+ Returns:
+ True if a path exists and is a directory, not a regular file.
+
+ See also :meth:`does_file_exist`.
>>> does_directory_exist('/tmp')
True
def is_normal_file(filename: str) -> bool:
- """Returns True if filename is a normal file.
+ """Is that file normal (not a directory or some special file?)
+
+ Args:
+ filename: the path of the file to check
+
+ Returns:
+ True if filename is a normal file.
+
+ See also :meth:`is_directory`, :meth:`does_file_exist`, :meth:`is_symlink`.
>>> is_normal_file(__file__)
True
def is_directory(filename: str) -> bool:
- """Returns True if filename is a directory.
+ """Is that path a directory (not a normal file?)
+
+ Args:
+ filename: the path of the file to check
+
+ Returns:
+ True if filename is a directory
+
+ See also :meth:`does_directory_exist`, :meth:`is_normal_file`,
+ :meth:`is_symlink`.
>>> is_directory('/tmp')
True
def is_symlink(filename: str) -> bool:
- """True if filename is a symlink, False otherwise.
+ """Is that path a symlink?
+
+ Args:
+ filename: the path of the file to check
+
+ Returns:
+ True if filename is a symlink, False otherwise.
+
+ See also :meth:`is_directory`, :meth:`is_normal_file`.
>>> is_symlink('/tmp')
False
>>> is_symlink('/home')
True
-
"""
return os.path.islink(filename)
def is_same_file(file1: str, file2: str) -> bool:
- """Returns True if the two files are the same inode.
+ """Determine if two paths reference the same inode.
+
+ Args:
+ file1: the first file
+ file2: the second file
+
+ Returns:
+ True if the two files are the same file.
+
+ See also :meth:`is_symlink`, :meth:`is_normal_file`.
>>> is_same_file('/tmp', '/tmp/../tmp')
True
>>> is_same_file('/tmp', '/home')
False
-
"""
return os.path.samefile(file1, file2)
def get_file_raw_timestamps(filename: str) -> Optional[os.stat_result]:
- """Stats the file and returns an os.stat_result or None on error.
+ """Stats the file and returns an `os.stat_result` or None on error.
Args:
filename: the file whose timestamps to fetch
Returns:
the os.stat_result or None to indicate an error occurred
+
+ See also
+ :meth:`get_file_raw_atime`,
+ :meth:`get_file_raw_ctime`,
+ :meth:`get_file_raw_mtime`,
+ :meth:`get_file_raw_timestamp`
"""
try:
return os.stat(filename)
filename: str, extractor: Callable[[os.stat_result], Optional[float]]
) -> Optional[float]:
"""Stat a file and, if successful, use extractor to fetch some
- subset of the information in the os.stat_result. See also
- :meth:`get_file_raw_atime`, :meth:`get_file_raw_mtime`, and
- :meth:`get_file_raw_ctime` which just call this with a lambda
- extractor.
+ subset of the information in the `os.stat_result`.
Args:
filename: the filename to stat
Returns:
whatever the extractor produced or None on error.
+
+ See also
+ :meth:`get_file_raw_atime`,
+ :meth:`get_file_raw_ctime`,
+ :meth:`get_file_raw_mtime`,
+ :meth:`get_file_raw_timestamps`
"""
tss = get_file_raw_timestamps(filename)
if tss is not None:
def get_file_raw_atime(filename: str) -> Optional[float]:
- """Get a file's raw access time or None on error.
+ """Get a file's raw access time.
- See also :meth:`get_file_atime_as_datetime`,
+ Args:
+ filename: the path to the file to stat
+
+ Returns:
+ The file's raw atime (seconds since the Epoch) or
+ None on error.
+
+ See also
+ :meth:`get_file_atime_age_seconds`,
+ :meth:`get_file_atime_as_datetime`,
:meth:`get_file_atime_timedelta`,
- and :meth:`get_file_atime_age_seconds`.
+ :meth:`get_file_raw_ctime`,
+ :meth:`get_file_raw_mtime`,
+ :meth:`get_file_raw_timestamps`
"""
return get_file_raw_timestamp(filename, lambda x: x.st_atime)
def get_file_raw_mtime(filename: str) -> Optional[float]:
- """Get a file's raw modification time or None on error.
+ """Get a file's raw modification time.
- See also :meth:`get_file_mtime_as_datetime`,
+ Args:
+ filename: the path to the file to stat
+
+ Returns:
+ The file's raw mtime (seconds since the Epoch) or
+ None on error.
+
+ See also
+ :meth:`get_file_raw_atime`,
+ :meth:`get_file_raw_ctime`,
+ :meth:`get_file_mtime_age_seconds`,
+ :meth:`get_file_mtime_as_datetime`,
:meth:`get_file_mtime_timedelta`,
- and :meth:`get_file_mtime_age_seconds`.
+ :meth:`get_file_raw_timestamps`
"""
return get_file_raw_timestamp(filename, lambda x: x.st_mtime)
def get_file_raw_ctime(filename: str) -> Optional[float]:
- """Get a file's raw creation time or None on error.
+ """Get a file's raw creation time.
+
+ Args:
+ filename: the path to the file to stat
- See also :meth:`get_file_ctime_as_datetime`,
+ Returns:
+ The file's raw ctime (seconds since the Epoch) or
+ None on error.
+
+ See also
+ :meth:`get_file_raw_atime`,
+ :meth:`get_file_ctime_age_seconds`,
+ :meth:`get_file_ctime_as_datetime`,
:meth:`get_file_ctime_timedelta`,
- and :meth:`get_file_ctime_age_seconds`.
+ :meth:`get_file_raw_mtime`,
+ :meth:`get_file_raw_timestamps`
"""
return get_file_raw_timestamp(filename, lambda x: x.st_ctime)
filename: the file whose contents to hash
Returns:
- the MD5 digest of the file's contents. Raises on errors.
+ the MD5 digest of the file's contents. Raises on error.
"""
file_hash = hashlib.md5()
with open(filename, "rb") as f:
return file_hash.hexdigest()
-def set_file_raw_atime(filename: str, atime: float):
+def set_file_raw_atime(filename: str, atime: float) -> None:
"""Sets a file's raw access time.
- See also :meth:`get_file_atime_as_datetime`,
- :meth:`get_file_atime_timedelta`,
+ Args:
+ filename: the file whose atime should be set
+ atime: raw atime as number of seconds since the Epoch to set
+
+ See also
+ :meth:`get_file_raw_atime`,
:meth:`get_file_atime_age_seconds`,
- and :meth:`get_file_raw_atime`.
+ :meth:`get_file_atime_as_datetime`,
+ :meth:`get_file_atime_timedelta`,
+ :meth:`get_file_raw_timestamps`,
+ :meth:`set_file_raw_mtime`,
+ :meth:`set_file_raw_atime_and_mtime`,
+ :meth:`touch_file`
"""
mtime = get_file_raw_mtime(filename)
assert mtime is not None
def set_file_raw_mtime(filename: str, mtime: float):
"""Sets a file's raw modification time.
- See also :meth:`get_file_mtime_as_datetime`,
- :meth:`get_file_mtime_timedelta`,
+ Args:
+ filename: the file whose mtime should be set
+ mtime: the raw mtime as number of seconds since the Epoch to set
+
+ See also
+ :meth:`get_file_raw_mtime`,
:meth:`get_file_mtime_age_seconds`,
- and :meth:`get_file_raw_mtime`.
+ :meth:`get_file_mtime_as_datetime`,
+ :meth:`get_file_mtime_timedelta`,
+ :meth:`get_file_raw_timestamps`,
+ :meth:`set_file_raw_atime`,
+ :meth:`set_file_raw_atime_and_mtime`,
+ :meth:`touch_file`
"""
atime = get_file_raw_atime(filename)
assert atime is not None
def set_file_raw_atime_and_mtime(filename: str, ts: float = None):
- """Sets both a file's raw modification and access times
+ """Sets both a file's raw modification and access times.
Args:
filename: the file whose times to set
ts: the raw time to set or None to indicate time should be
set to the current time.
+
+ See also
+ :meth:`get_file_raw_atime`,
+ :meth:`get_file_raw_mtime`,
+ :meth:`get_file_raw_timestamps`,
+ :meth:`set_file_raw_atime`,
+ :meth:`set_file_raw_mtime`
"""
if ts is not None:
os.utime(filename, (ts, ts))
os.utime(filename, None)
-def convert_file_timestamp_to_datetime(
+def _convert_file_timestamp_to_datetime(
filename: str, producer
) -> Optional[datetime.datetime]:
- """Convert a raw file timestamp into a python datetime."""
+ """
+ Converts a raw file timestamp into a Python datetime.
+
+ Args:
+ filename: file whose timestamps should be converted.
+ producer: source of the timestamp.
+
+ Returns:
+ The datetime.
+ """
ts = producer(filename)
if ts is not None:
return datetime.datetime.fromtimestamp(ts)
def get_file_atime_as_datetime(filename: str) -> Optional[datetime.datetime]:
- """Fetch a file's access time as a python datetime.
+ """Fetch a file's access time as a Python datetime.
- See also :meth:`get_file_atime_as_datetime`,
- :meth:`get_file_atime_timedelta`,
+ Args:
+ filename: the file whose atime should be fetched.
+
+ Returns:
+ The file's atime as a Python :class:`datetime.datetime`.
+
+ See also
+ :meth:`get_file_raw_atime`,
:meth:`get_file_atime_age_seconds`,
- :meth:`describe_file_atime`,
- and :meth:`get_file_raw_atime`.
+ :meth:`get_file_atime_timedelta`,
+ :meth:`get_file_raw_ctime`,
+ :meth:`get_file_raw_mtime`,
+ :meth:`get_file_raw_timestamps`,
+ :meth:`set_file_raw_atime`,
+ :meth:`set_file_raw_atime_and_mtime`
"""
- return convert_file_timestamp_to_datetime(filename, get_file_raw_atime)
+ return _convert_file_timestamp_to_datetime(filename, get_file_raw_atime)
def get_file_mtime_as_datetime(filename: str) -> Optional[datetime.datetime]:
- """Fetches a file's modification time as a python datetime.
+ """Fetch a file's modification time as a Python datetime.
- See also :meth:`get_file_mtime_as_datetime`,
- :meth:`get_file_mtime_timedelta`,
+ Args:
+ filename: the file whose mtime should be fetched.
+
+ Returns:
+ The file's mtime as a Python :class:`datetime.datetime`.
+
+ See also
+ :meth:`get_file_raw_mtime`,
:meth:`get_file_mtime_age_seconds`,
- and :meth:`get_file_raw_mtime`.
+ :meth:`get_file_mtime_timedelta`,
+ :meth:`get_file_raw_ctime`,
+ :meth:`get_file_raw_atime`,
+ :meth:`get_file_raw_timestamps`,
+ :meth:`set_file_raw_atime`,
+ :meth:`set_file_raw_atime_and_mtime`
"""
- return convert_file_timestamp_to_datetime(filename, get_file_raw_mtime)
+ return _convert_file_timestamp_to_datetime(filename, get_file_raw_mtime)
def get_file_ctime_as_datetime(filename: str) -> Optional[datetime.datetime]:
- """Fetches a file's creation time as a python datetime.
+ """Fetches a file's creation time as a Python datetime.
- See also :meth:`get_file_ctime_as_datetime`,
- :meth:`get_file_ctime_timedelta`,
+ Args:
+ filename: the file whose ctime should be fetched.
+
+ Returns:
+ The file's ctime as a Python :class:`datetime.datetime`.
+
+ See also
+ :meth:`get_file_raw_ctime`,
:meth:`get_file_ctime_age_seconds`,
- and :meth:`get_file_raw_ctime`.
+ :meth:`get_file_ctime_timedelta`,
+ :meth:`get_file_raw_atime`,
+ :meth:`get_file_raw_mtime`,
+ :meth:`get_file_raw_timestamps`
"""
- return convert_file_timestamp_to_datetime(filename, get_file_raw_ctime)
+ return _convert_file_timestamp_to_datetime(filename, get_file_raw_ctime)
-def get_file_timestamp_age_seconds(filename: str, extractor) -> Optional[int]:
+def _get_file_timestamp_age_seconds(filename: str, extractor) -> Optional[int]:
"""~Internal helper"""
now = time.time()
ts = get_file_raw_timestamps(filename)
def get_file_atime_age_seconds(filename: str) -> Optional[int]:
"""Gets a file's access time as an age in seconds (ago).
- See also :meth:`get_file_atime_as_datetime`,
+ Args:
+ filename: file whose atime should be checked.
+
+ Returns:
+ The number of seconds ago that filename was last accessed.
+
+ See also
+ :meth:`get_file_raw_atime`,
+ :meth:`get_file_atime_as_datetime`,
:meth:`get_file_atime_timedelta`,
- :meth:`get_file_atime_age_seconds`,
- :meth:`describe_file_atime`,
- and :meth:`get_file_raw_atime`.
+ :meth:`get_file_raw_ctime`,
+ :meth:`get_file_raw_mtime`,
+ :meth:`get_file_raw_timestamps`,
+ :meth:`set_file_raw_atime`,
+ :meth:`set_file_raw_atime_and_mtime`
"""
- return get_file_timestamp_age_seconds(filename, lambda x: x.st_atime)
+ return _get_file_timestamp_age_seconds(filename, lambda x: x.st_atime)
def get_file_ctime_age_seconds(filename: str) -> Optional[int]:
"""Gets a file's creation time as an age in seconds (ago).
- See also :meth:`get_file_ctime_as_datetime`,
- :meth:`get_file_ctime_timedelta`,
+ Args:
+ filename: file whose ctime should be checked.
+
+ Returns:
+ The number of seconds ago that filename was created.
+
+ See also
+ :meth:`get_file_raw_ctime`,
:meth:`get_file_ctime_age_seconds`,
- and :meth:`get_file_raw_ctime`.
+ :meth:`get_file_ctime_as_datetime`,
+ :meth:`get_file_ctime_timedelta`,
+ :meth:`get_file_raw_mtime`,
+ :meth:`get_file_raw_atime`,
+ :meth:`get_file_raw_timestamps`
"""
- return get_file_timestamp_age_seconds(filename, lambda x: x.st_ctime)
+ return _get_file_timestamp_age_seconds(filename, lambda x: x.st_ctime)
def get_file_mtime_age_seconds(filename: str) -> Optional[int]:
"""Gets a file's modification time as seconds (ago).
- See also :meth:`get_file_mtime_as_datetime`,
+ Args:
+ filename: file whose mtime should be checked.
+
+ Returns:
+ The number of seconds ago that filename was last modified.
+
+ See also
+ :meth:`get_file_raw_atime`,
+ :meth:`get_file_raw_ctime`,
+ :meth:`get_file_raw_mtime`,
+ :meth:`get_file_mtime_as_datetime`,
:meth:`get_file_mtime_timedelta`,
- :meth:`get_file_mtime_age_seconds`,
- and :meth:`get_file_raw_mtime`.
+ :meth:`get_file_raw_timestamps`,
+ :meth:`set_file_raw_atime`,
+ :meth:`set_file_raw_atime_and_mtime`
"""
- return get_file_timestamp_age_seconds(filename, lambda x: x.st_mtime)
+ return _get_file_timestamp_age_seconds(filename, lambda x: x.st_mtime)
-def get_file_timestamp_timedelta(
+def _get_file_timestamp_timedelta(
filename: str, extractor
) -> Optional[datetime.timedelta]:
"""~Internal helper"""
- age = get_file_timestamp_age_seconds(filename, extractor)
+ age = _get_file_timestamp_age_seconds(filename, extractor)
if age is not None:
return datetime.timedelta(seconds=float(age))
return None
def get_file_atime_timedelta(filename: str) -> Optional[datetime.timedelta]:
"""How long ago was a file accessed as a timedelta?
- See also :meth:`get_file_atime_as_datetime`,
- :meth:`get_file_atime_timedelta`,
+ Args:
+ filename: the file whose atime should be checked.
+
+ Returns:
+ A Python :class:`datetime.timedelta` representing how long
+ ago filename was last accessed.
+
+ See also
+ :meth:`get_file_raw_atime`,
:meth:`get_file_atime_age_seconds`,
- :meth:`describe_file_atime`,
- and :meth:`get_file_raw_atime`.
+ :meth:`get_file_atime_as_datetime`,
+ :meth:`get_file_raw_ctime`,
+ :meth:`get_file_raw_mtime`,
+ :meth:`get_file_raw_timestamps`,
+ :meth:`set_file_raw_atime`,
+ :meth:`set_file_raw_atime_and_mtime`
"""
- return get_file_timestamp_timedelta(filename, lambda x: x.st_atime)
+ return _get_file_timestamp_timedelta(filename, lambda x: x.st_atime)
def get_file_ctime_timedelta(filename: str) -> Optional[datetime.timedelta]:
"""How long ago was a file created as a timedelta?
- See also :meth:`get_file_ctime_as_datetime`,
- :meth:`get_file_ctime_timedelta`,
+ Args:
+ filename: the file whose ctime should be checked.
+
+ Returns:
+ A Python :class:`datetime.timedelta` representing how long
+ ago filename was created.
+
+ See also
+ :meth:`get_file_raw_atime`,
+ :meth:`get_file_raw_ctime`,
:meth:`get_file_ctime_age_seconds`,
- and :meth:`get_file_raw_ctime`.
+ :meth:`get_file_ctime_as_datetime`,
+ :meth:`get_file_raw_mtime`,
+ :meth:`get_file_raw_timestamps`
"""
- return get_file_timestamp_timedelta(filename, lambda x: x.st_ctime)
+ return _get_file_timestamp_timedelta(filename, lambda x: x.st_ctime)
def get_file_mtime_timedelta(filename: str) -> Optional[datetime.timedelta]:
"""
- Gets a file's modification time as a python timedelta.
+ Gets a file's modification time as a Python timedelta.
- See also :meth:`get_file_mtime_as_datetime`,
- :meth:`get_file_mtime_timedelta`,
+ Args:
+ filename: the file whose mtime should be checked.
+
+ Returns:
+ A Python :class:`datetime.timedelta` representing how long
+ ago filename was last modified.
+
+ See also
+ :meth:`get_file_raw_atime`,
+ :meth:`get_file_raw_ctime`,
+ :meth:`get_file_raw_mtime`,
:meth:`get_file_mtime_age_seconds`,
- and :meth:`get_file_raw_mtime`.
+ :meth:`get_file_mtime_as_datetime`,
+ :meth:`get_file_raw_timestamps`,
+ :meth:`set_file_raw_atime`,
+ :meth:`set_file_raw_atime_and_mtime`
"""
- return get_file_timestamp_timedelta(filename, lambda x: x.st_mtime)
+ return _get_file_timestamp_timedelta(filename, lambda x: x.st_mtime)
def describe_file_timestamp(filename: str, extractor, *, brief=False) -> Optional[str]:
describe_duration_briefly,
)
- age = get_file_timestamp_age_seconds(filename, extractor)
+ age = _get_file_timestamp_age_seconds(filename, extractor)
if age is None:
return None
if brief:
"""
Describe how long ago a file was accessed.
- See also :meth:`get_file_atime_as_datetime`,
- :meth:`get_file_atime_timedelta`,
+ Args:
+ filename: the file whose atime should be described.
+ brief: if True, describe atime briefly.
+
+ Returns:
+ A string that represents how long ago filename was last
+ accessed. The description will be verbose or brief depending
+ on the brief argument.
+
+ See also
+ :meth:`get_file_raw_atime`,
:meth:`get_file_atime_age_seconds`,
- :meth:`describe_file_atime`,
- and :meth:`get_file_raw_atime`.
+ :meth:`get_file_atime_as_datetime`,
+ :meth:`get_file_atime_timedelta`,
+ :meth:`get_file_raw_ctime`,
+ :meth:`get_file_raw_mtime`,
+ :meth:`get_file_raw_timestamps`
+ :meth:`set_file_raw_atime`,
+ :meth:`set_file_raw_atime_and_mtime`
"""
return describe_file_timestamp(filename, lambda x: x.st_atime, brief=brief)
def describe_file_ctime(filename: str, *, brief=False) -> Optional[str]:
"""Describes a file's creation time.
- See also :meth:`get_file_ctime_as_datetime`,
- :meth:`get_file_ctime_timedelta`,
+ Args:
+ filename: the file whose ctime should be described.
+ brief: if True, describe ctime briefly.
+
+ Returns:
+ A string that represents how long ago filename was created.
+ The description will be verbose or brief depending
+ on the brief argument.
+
+ See also
+ :meth:`get_file_raw_atime`,
+ :meth:`get_file_raw_ctime`,
:meth:`get_file_ctime_age_seconds`,
- and :meth:`get_file_raw_ctime`.
+ :meth:`get_file_ctime_as_datetime`,
+ :meth:`get_file_ctime_timedelta`,
+ :meth:`get_file_raw_mtime`,
+ :meth:`get_file_raw_timestamps`
"""
return describe_file_timestamp(filename, lambda x: x.st_ctime, brief=brief)
def describe_file_mtime(filename: str, *, brief=False) -> Optional[str]:
- """
- Describes how long ago a file was modified.
+ """Describes how long ago a file was modified.
- See also :meth:`get_file_mtime_as_datetime`,
- :meth:`get_file_mtime_timedelta`,
+ Args:
+ filename: the file whose mtime should be described.
+ brief: if True, describe mtime briefly.
+
+ Returns:
+ A string that represents how long ago filename was last
+ modified. The description will be verbose or brief depending
+ on the brief argument.
+
+ See also
+ :meth:`get_file_raw_atime`,
+ :meth:`get_file_raw_ctime`,
+ :meth:`get_file_raw_mtime`,
:meth:`get_file_mtime_age_seconds`,
- and :meth:`get_file_raw_mtime`.
+ :meth:`get_file_mtime_as_datetime`,
+ :meth:`get_file_mtime_timedelta`,
+ :meth:`get_file_raw_timestamps`,
+ :meth:`set_file_raw_atime`,
+ :meth:`set_file_raw_atime_and_mtime`
"""
return describe_file_timestamp(filename, lambda x: x.st_mtime, brief=brief)
Args:
filename: the filename
mode: the mode to create the file with
+
+ .. warning::
+
+ The default creation mode is 0x666 which is world readable
+ and writable. Override this by passing in your own mode
+ parameter if desired.
+
+ See also :meth:`set_file_raw_atime`, :meth:`set_file_raw_atime_and_mtime`,
+ :meth:`set_file_raw_mtime`, :meth:`create_path_if_not_exist`
"""
pathlib.Path(filename, mode=mode).touch()
def expand_globs(in_filename: str):
- """Expands shell globs (* and ? wildcards) to the matching files."""
+ """
+ Expands shell globs (* and ? wildcards) to the matching files.
+
+ Args:
+ in_filename: the filepath to be expanded. May contain '*' and '?'
+ globbing characters.
+
+ Returns:
+ A Generator that yields filenames that match the input pattern.
+
+ See also :meth:`get_files`, :meth:`get_files_recursive`.
+ """
for filename in glob.glob(in_filename):
yield filename
def get_files(directory: str):
- """Returns the files in a directory as a generator."""
+ """Returns the files in a directory as a generator.
+
+ Args:
+ directory: the directory to list files under.
+
+ Returns:
+ A generator that yields all files in the input directory.
+
+ See also :meth:`expand_globs`, :meth:`get_files_recursive`,
+ :meth:`get_matching_files`.
+ """
for filename in os.listdir(directory):
full_path = join(directory, filename)
if isfile(full_path) and exists(full_path):
def get_matching_files(directory: str, glob: str):
- """Returns the subset of files whose name matches a glob."""
+ """
+ Returns the subset of files whose name matches a glob.
+
+ Args:
+ directory: the directory to match files within.
+ glob: the globbing pattern (may include '*' and '?') to
+ use when matching files.
+
+ Returns:
+ A generator that yields filenames in directory that match
+ the given glob pattern.
+
+ See also :meth:`get_files`, :meth:`expand_globs`.
+ """
for filename in get_files(directory):
if fnmatch.fnmatch(filename, glob):
yield filename
def get_directories(directory: str):
- """Returns the subdirectories in a directory as a generator."""
+ """
+ Returns the subdirectories in a directory as a generator.
+
+ Args:
+ directory: the directory to list subdirectories within.
+
+ Returns:
+ A generator that yields all subdirectories within the given
+ input directory.
+
+ See also :meth:`get_files`, :meth:`get_files_recursive`.
+ """
for d in os.listdir(directory):
full_path = join(directory, d)
if not isfile(full_path) and exists(full_path):
def get_files_recursive(directory: str):
- """Find the files and directories under a root recursively."""
+ """
+ Find the files and directories under a root recursively.
+
+ Args:
+ directory: the root directory under which to list subdirectories
+ and file contents.
+
+ Returns:
+ A generator that yields all directories and files beneath the input
+ root directory.
+
+ See also :meth:`get_files`, :meth:`get_matching_files`,
+ :meth:`get_matching_files_recursive`
+ """
for filename in get_files(directory):
yield filename
for subdir in get_directories(directory):
def get_matching_files_recursive(directory: str, glob: str):
- """Returns the subset of files whose name matches a glob under a root recursively."""
+ """
+ Returns the subset of files whose name matches a glob under a root recursively.
+
+ Args:
+ directory: the root under which to search
+ glob: a globbing pattern that describes the subset of files and directories
+ to return. May contain '?' and '*'.
+
+ Returns:
+ A generator that yields all files and directories under the given root
+ directory that match the given globbing pattern.
+
+ See also :meth:`get_files_recursive`.
+ """
for filename in get_files_recursive(directory):
if fnmatch.fnmatch(filename, glob):
yield filename
class FileWriter(contextlib.AbstractContextManager):
- """A helper that writes a file to a temporary location and then moves
- it atomically to its ultimate destination on close.
+ """A helper that writes a file to a temporary location and then
+ moves it atomically to its ultimate destination on close.
+
+ Example usage. Creates a temporary file that is populated by the
+ print statements within the context. Until the context is exited,
+ the true destination file does not exist so no reader of it can
+ see partial writes due to buffering or code timing. Once the
+ context is exited, the file is moved from its temporary location
+ to its permanent location by a call to `/bin/mv` which should be
+ atomic::
+
+ with FileWriter('/home/bob/foobar.txt') as w:
+ print("This is a test!", file=w)
+ time.sleep(2)
+ print("This is only a test...", file=w)
+
"""
def __init__(self, filename: str) -> None:
+ """
+ Args:
+ filename: the ultimate destination file we want to populate.
+ On exit, the file will be atomically created.
+ """
self.filename = filename
uuid = uuid4()
self.tempfile = f'{filename}-{uuid}.tmp'