adding fileutils docs, including google-style docstring for AtomicSaver

This commit is contained in:
Mahmoud Hashemi 2015-03-23 02:40:29 -07:00
parent c6a24dfb53
commit e150258331
1 changed files with 72 additions and 14 deletions

View File

@ -1,4 +1,11 @@
# -*- coding: utf-8 -*-
"""\
Given how often Python is used for wrangling disk contents, one
would expect more of these to be provided.
"""
import os
import re
import stat
@ -11,7 +18,42 @@ from shutil import copy2, copystat, Error
VALID_PERM_CHARS = 'rwx'
def mkdir_p(path):
"""\
Creates a directory and any parent directories that may need to
be created along the way, without raising errors for any existing
directories. This function mimics the behavior of ``mkdir -p`` of
Linux/BSD environments, but also works on Windows.
"""
try:
os.makedirs(path)
except OSError as exc:
if exc.errno == errno.EEXIST and os.path.isdir(path):
return
raise
return
class FilePerms(object):
"""\
The ``FilePerms`` type is used to represent standard POSIX filesystem permissions:
* Read
* Write
* Execute
Across three classes of user:
* Owning (u)ser
* Owner's (g)roup
* Any (o)ther user
This class assists with computing new permissions, as well as
working with numeric octal ``777``-style and ``rwx``-style
permissions. Currently it only considers the bottom 9 permission
bits; it does not support sticky bits or more advanced permission
systems.
"""
# TODO: consider more than the lower 9 bits
class _FilePermProperty(object):
def __init__(self, attribute, offset):
@ -83,6 +125,10 @@ class FilePerms(object):
def atomic_save(dest_path, **kwargs):
"""\
A convenient interface to the ``AtomicSaver`` type. See the
``AtomicSaver`` documentation for more info.
"""
return AtomicSaver(dest_path, **kwargs)
@ -95,7 +141,25 @@ def _atomic_rename(path, new_path, overwrite=False):
class AtomicSaver(object):
"""\
Use this to get a writable file which will be moved into place as
long as no exceptions are raised before its close. It returns a
standard Python ``file`` which can still be closed explicitly and
is also usable as a context manager (i.e., via the ``with``
statement).
"""
def __init__(self, dest_path, **kwargs):
"""
Args:
dest_path (str): The path where the completed file will be written
overwrite (bool, optional): Whether to overwrite the destination file if it exists at completion time. Defaults to True.
part_file (str, optional): Name of the temporary ``part_file``. Defaults to ``dest_path`` + ".part"
rm_part_on_exc (bool, optional): Remove ``part_file`` on exception. Defaults to True.
overwrite_partfile (bool, optional): Whether to overwrite the ``part_file`` should it exist at setup time. Defaults to True.
open_func (callable, optional): Function used to open the file. Override this if you want to use ``codecs.open`` or some other alternative. Defaults to ``open()``.
open_kwargs (dict, optional): Additional keyword arguments to pass to ``open_func``. Defaults to ``{}``.
"""
self.dest_path = dest_path
self.overwrite = kwargs.pop('overwrite', True)
self.overwrite_part = kwargs.pop('overwrite_partfile', True)
@ -118,15 +182,19 @@ class AtomicSaver(object):
self.part_file = None
def setup(self):
"""\
Called on context manager entry (the ``with`` statement), the
``setup()`` function creates the temporary file in the same
directory as the destination file. It tests for a writable
directory with rename permissions early, as we may be writing
to the part file for a while (not using os.access because of
the potential issues of effective vs real privileges).
"""
if os.path.lexists(self.dest_path):
if not self.overwrite:
raise OSError(errno.EEXIST,
'Overwrite disabled and file already exists',
self.dest_path)
# this tests for a writable directory with rename permissions
# early, as we may be writing to the part file for a while (not
# using os.access because of the potential issues of effective vs
# real privileges)
_, tmp_part_path = tempfile.mkstemp(dir=self.dest_dir,
text=self.text_mode)
try:
@ -195,16 +263,6 @@ def iter_find_files(directory, patterns, ignored=None):
return
def mkdir_p(path):
try:
os.makedirs(path)
except OSError as exc:
if exc.errno == errno.EEXIST and os.path.isdir(path):
return
raise
return
def copytree(src, dst, symlinks=False, ignore=None):
"""Recursively copy a directory tree using copy2().