oslo.utils/oslo_utils/fileutils.py
Doug Hellmann 11feb68eb3 improve docstring for last_bytes()
Change-Id: Ic402a29c9e701a6cde3e882cc97721008cdd5460
Signed-off-by: Doug Hellmann <doug@doughellmann.com>
2018-01-02 08:50:18 -05:00

158 lines
4.7 KiB
Python

# Copyright 2011 OpenStack Foundation.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
File utilities.
.. versionadded:: 1.8
"""
import contextlib
import errno
import hashlib
import os
import stat
import tempfile
from oslo_utils import excutils
_DEFAULT_MODE = stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO
def ensure_tree(path, mode=_DEFAULT_MODE):
"""Create a directory (and any ancestor directories required)
:param path: Directory to create
:param mode: Directory creation permissions
"""
try:
os.makedirs(path, mode)
except OSError as exc:
if exc.errno == errno.EEXIST:
if not os.path.isdir(path):
raise
else:
raise
def delete_if_exists(path, remove=os.unlink):
"""Delete a file, but ignore file not found error.
:param path: File to delete
:param remove: Optional function to remove passed path
"""
try:
remove(path)
except OSError as e:
if e.errno != errno.ENOENT:
raise
@contextlib.contextmanager
def remove_path_on_error(path, remove=delete_if_exists):
"""Protect code that wants to operate on PATH atomically.
Any exception will cause PATH to be removed.
:param path: File to work with
:param remove: Optional function to remove passed path
"""
try:
yield
except Exception:
with excutils.save_and_reraise_exception():
remove(path)
def write_to_tempfile(content, path=None, suffix='', prefix='tmp'):
"""Create a temporary file containing data.
Create a temporary file containing specified content, with a specified
filename suffix and prefix. The tempfile will be created in a default
location, or in the directory `path`, if it is not None. `path` and its
parent directories will be created if they don't exist.
:param content: bytestring to write to the file
:param path: same as parameter 'dir' for mkstemp
:param suffix: same as parameter 'suffix' for mkstemp
:param prefix: same as parameter 'prefix' for mkstemp
For example: it can be used in database tests for creating
configuration files.
.. versionadded:: 1.9
"""
if path:
ensure_tree(path)
(fd, path) = tempfile.mkstemp(suffix=suffix, dir=path, prefix=prefix)
try:
os.write(fd, content)
finally:
os.close(fd)
return path
def compute_file_checksum(path, read_chunksize=65536, algorithm='sha256'):
"""Compute checksum of a file's contents.
:param path: Path to the file
:param read_chunksize: Maximum number of bytes to be read from the file
at once. Default is 65536 bytes or 64KB
:param algorithm: The hash algorithm name to use. For example, 'md5',
'sha256', 'sha512' and so on. Default is 'sha256'. Refer to
hashlib.algorithms_available for available algorithms
:return: Hex digest string of the checksum
.. versionadded:: 3.31.0
"""
checksum = hashlib.new(algorithm) # Raises appropriate exceptions.
with open(path, 'rb') as f:
for chunk in iter(lambda: f.read(read_chunksize), b''):
checksum.update(chunk)
return checksum.hexdigest()
def last_bytes(path, num):
"""Return num bytes from the end of the file and unread byte count.
Returns a tuple containing some content from the file and the
number of bytes that appear in the file before the point at which
reading started. The content will be at most ``num`` bytes, taken
from the end of the file. If the file is smaller than ``num``
bytes the entire content of the file is returned.
:param path: The file path to read
:param num: The number of bytes to return
:returns: (data, unread_bytes)
"""
with open(path, 'rb') as fp:
try:
fp.seek(-num, os.SEEK_END)
except IOError as e:
# seek() fails with EINVAL when trying to go before the start of
# the file. It means that num is larger than the file size, so
# just go to the start.
if e.errno == errno.EINVAL:
fp.seek(0, os.SEEK_SET)
else:
raise
unread_bytes = fp.tell()
return (fp.read(), unread_bytes)