import re
import time
from os.path import exists, isfile, join
-from typing import Callable, List, Literal, Optional, TextIO
+from typing import IO, Any, Callable, List, Literal, Optional
from uuid import uuid4
logger = logging.getLogger(__name__)
def slurp_file(
filename: str,
*,
- skip_blank_lines=False,
+ skip_blank_lines: bool = False,
line_transformers: Optional[List[Callable[[str], str]]] = None,
):
"""Reads in a file's contents line-by-line to a memory buffer applying
Returns:
A list of lines from the read and transformed file contents.
+
+ Raises:
+ Exception: filename not found or can't be read.
"""
ret = []
Args:
path: the path of the file to delete
+ Raises:
+ FileNotFoundError: the path to remove does not exist
+
>>> import os
>>> filename = '/tmp/file_utils_test_file'
>>> os.system(f'touch {filename}')
>>> remove(filename)
>>> does_file_exist(filename)
False
+
+ >>> remove("/tmp/23r23r23rwdfwfwefgdfgwerhwrgewrgergerg22r")
+ Traceback (most recent call last):
+ ...
+ FileNotFoundError: [Errno 2] No such file or directory: '/tmp/23r23r23rwdfwfwefgdfgwerhwrgewrgergerg22r'
"""
os.remove(path)
See also :meth:`get_path`, :meth:`without_path`.
- >>> get_canonical_path('/home/scott/../../home/lynn/../scott/foo.txt')
- '/usr/home/scott/foo.txt'
+ >>> get_canonical_path('./../../pyutils/files/file_utils.py')
+ '/usr/home/scott/lib/release/pyutils/files/file_utils.py'
"""
return os.path.realpath(filespec)
-def create_path_if_not_exist(path, on_error=None) -> None:
+def create_path_if_not_exist(
+ path: str, on_error: Callable[[str, OSError], None] = None
+) -> None:
"""
Attempts to create path if it does not exist already.
Args:
path: the path to attempt to create
- on_error: If True, it's invoked on error conditions. Otherwise
- any exceptions are raised.
+ on_error: if provided, this is invoked on error conditions and
+ passed the path and OSError that it caused
+
+ Raises:
+ OSError: an exception occurred and on_error was not set.
See also :meth:`does_file_exist`.
.. warning::
-
Files are created with mode 0o0777 (i.e. world read/writeable).
>>> import uuid
Returns:
True if filename exists and is a normal file.
+ .. note::
+ A Python core philosophy is: it's easier to ask forgiveness
+ than permission (https://docs.python.org/3/glossary.html#term-EAFP).
+ That is, code that just tries an operation and handles the set of
+ Exceptions that may arise is the preferred style. That said, this
+ function can still be useful in some situations.
+
See also :meth:`create_path_if_not_exist`, :meth:`is_readable`.
>>> does_file_exist(__file__)
True if file exists, is a normal file and is writable by the
current process. False otherwise.
+ .. note::
+ A Python core philosophy is: it's easier to ask forgiveness
+ than permission (https://docs.python.org/3/glossary.html#term-EAFP).
+ That is, code that just tries an operation and handles the set of
+ Exceptions that may arise is the preferred style. That said, this
+ function can still be useful in some situations.
+
See also :meth:`is_readable`, :meth:`does_file_exist`.
"""
return os.access(filename, os.W_OK)
True if file exists, is a normal file and is executable by the
current process. False otherwise.
+ .. note::
+ A Python core philosophy is: it's easier to ask forgiveness
+ than permission (https://docs.python.org/3/glossary.html#term-EAFP).
+ That is, code that just tries an operation and handles the set of
+ Exceptions that may arise is the preferred style. That said, this
+ function can still be useful in some situations.
+
See also :meth:`does_file_exist`, :meth:`is_readable`,
:meth:`is_writable`.
"""
Returns:
True if filename is a normal file.
+ .. note::
+ A Python core philosophy is: it's easier to ask forgiveness
+ than permission (https://docs.python.org/3/glossary.html#term-EAFP).
+ That is, code that just tries an operation and handles the set of
+ Exceptions that may arise is the preferred style. That said, this
+ function can still be useful in some situations.
+
See also :meth:`is_directory`, :meth:`does_file_exist`, :meth:`is_symlink`.
>>> is_normal_file(__file__)
Returns:
True if filename is a directory
+ .. note::
+ A Python core philosophy is: it's easier to ask forgiveness
+ than permission (https://docs.python.org/3/glossary.html#term-EAFP).
+ That is, code that just tries an operation and handles the set of
+ Exceptions that may arise is the preferred style. That said, this
+ function can still be useful in some situations.
+
See also :meth:`does_directory_exist`, :meth:`is_normal_file`,
:meth:`is_symlink`.
Returns:
True if filename is a symlink, False otherwise.
+ .. note::
+ A Python core philosophy is: it's easier to ask forgiveness
+ than permission (https://docs.python.org/3/glossary.html#term-EAFP).
+ That is, code that just tries an operation and handles the set of
+ Exceptions that may arise is the preferred style. That said, this
+ function can still be useful in some situations.
+
See also :meth:`is_directory`, :meth:`is_normal_file`.
>>> is_symlink('/tmp')
def _convert_file_timestamp_to_datetime(
- filename: str, producer
+ filename: str,
+ producer: Callable[[str], Optional[float]],
) -> Optional[datetime.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.
"""
return describe_duration(age)
-def describe_file_atime(filename: str, *, brief=False) -> Optional[str]:
+def describe_file_atime(filename: str, *, brief: bool = False) -> Optional[str]:
"""
Describe how long ago a file was accessed.
return describe_file_timestamp(filename, lambda x: x.st_atime, brief=brief)
-def describe_file_ctime(filename: str, *, brief=False) -> Optional[str]:
+def describe_file_ctime(filename: str, *, brief: bool = False) -> Optional[str]:
"""Describes a file's creation time.
Args:
return describe_file_timestamp(filename, lambda x: x.st_ctime, brief=brief)
-def describe_file_mtime(filename: str, *, brief=False) -> Optional[str]:
+def describe_file_mtime(filename: str, *, brief: bool = False) -> Optional[str]:
"""Describes how long ago a file was modified.
Args:
yield full_path
-def get_matching_files(directory: str, glob: str):
+def get_matching_files(directory: str, glob_string: str):
"""
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
+ glob_string: the globbing pattern (may include '*' and '?') to
use when matching files.
Returns:
See also :meth:`get_files`, :meth:`expand_globs`.
"""
for filename in get_files(directory):
- if fnmatch.fnmatch(filename, glob):
+ if fnmatch.fnmatch(filename, glob_string):
yield filename
yield file_or_directory
-def get_matching_files_recursive(directory: str, glob: str):
- """
- Returns the subset of files whose name matches a glob under a root recursively.
+def get_matching_files_recursive(directory: str, glob_string: str):
+ """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 '*'.
+ glob_string: 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):
+ if fnmatch.fnmatch(filename, glob_string):
yield filename
self.filename = filename
uuid = uuid4()
self.tempfile = f"{filename}-{uuid}.tmp"
- self.handle: Optional[TextIO] = None
+ self.handle: Optional[IO[Any]] = None
- def __enter__(self) -> TextIO:
+ def __enter__(self) -> IO[Any]:
assert not does_path_exist(self.tempfile)
self.handle = open(self.tempfile, mode="w")
+ assert self.handle
return self.handle
def __exit__(self, exc_type, exc_val, exc_tb) -> Literal[False]:
class CreateFileWithMode(contextlib.AbstractContextManager):
"""This helper context manager can be used instead of the typical
pattern for creating a file if you want to ensure that the file
- created is a particular permission mode upon creation.
+ created is a particular filesystem permission mode upon creation.
Python's open doesn't support this; you need to set the os.umask
and then create a descriptor to open via os.open, see below.
>>> import os
>>> filename = f'/tmp/CreateFileWithModeTest.{os.getpid()}'
- >>> with CreateFileWithMode(filename, mode=0o600) as wf:
+ >>> with CreateFileWithMode(filename, filesystem_mode=0o600) as wf:
... print('This is a test', file=wf)
>>> result = os.stat(filename)
- Note: there is a high order bit set in this that is S_IFREG indicating
- that the file is a "normal file". Clear it with the mask.
+ Note: there is a high order bit set in this that is S_IFREG
+ indicating that the file is a "normal file". Clear it with
+ the mask.
>>> print(f'{result.st_mode & 0o7777:o}')
600
>>> contents
'This is a test\\n'
>>> remove(filename)
+
"""
- def __init__(self, filename: str, mode: Optional[int] = 0o600) -> None:
+ def __init__(
+ self,
+ filename: str,
+ filesystem_mode: Optional[int] = 0o600,
+ open_mode: Optional[str] = "w",
+ ) -> None:
"""
Args:
filename: path of the file to create.
- mode: the UNIX-style octal mode with which to create the
- filename. Defaults to 0o600.
+ filesystem_mode: the UNIX-style octal mode with which to create
+ the filename. Defaults to 0o600.
+ open_mode: the mode to use when opening the file (e.g. 'w', 'wb',
+ etc...)
.. warning::
"""
self.filename = filename
- if mode is not None:
- self.mode = mode & 0o7777
+ if filesystem_mode is not None:
+ self.filesystem_mode = filesystem_mode & 0o7777
+ else:
+ self.filesystem_mode = 0o666
+ if open_mode is not None:
+ self.open_mode = open_mode
else:
- self.mode = 0o666
- self.handle: Optional[TextIO] = None
+ self.open_mode = "w"
+ self.handle: Optional[IO[Any]] = None
self.old_umask = os.umask(0)
- def __enter__(self) -> TextIO:
+ def __enter__(self) -> IO[Any]:
descriptor = os.open(
path=self.filename,
flags=(os.O_WRONLY | os.O_CREAT | os.O_TRUNC),
- mode=self.mode,
+ mode=self.filesystem_mode,
)
- self.handle = open(descriptor, "w")
+ self.handle = open(descriptor, self.open_mode)
+ assert self.handle
return self.handle
def __exit__(self, exc_type, exc_val, exc_tb) -> Literal[False]: