fileutils docs complete

This commit is contained in:
Mahmoud Hashemi 2015-04-05 15:01:20 -07:00
parent d9feeea62c
commit 2337022f19
2 changed files with 89 additions and 41 deletions

View File

@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
"""Given how often Python is used for wrangling disk contents, one
would expect the standard library to have grown a few of the following:
"""Virtually every Python programmer has used Python for wrangling
disk contents, and ``fileutils`` collects solutions to some of the
most commonly-found gaps in the standard library.
"""
import os
@ -12,7 +13,8 @@ import tempfile
from shutil import copy2, copystat, Error
VALID_PERM_CHARS = 'rwx'
__all__ = ['mkdir_p', 'atomic_save', 'AtomicSaver', 'FilePerms',
'iter_find_files', 'copytree']
def mkdir_p(path):
@ -72,6 +74,7 @@ class FilePerms(object):
"""
# TODO: consider more than the lower 9 bits
class _FilePermProperty(object):
_perm_chars = 'rwx'
_perm_val = {'r': 4, 'w': 2, 'x': 1} # for sorting
def __init__(self, attribute, offset):
@ -88,14 +91,14 @@ class FilePerms(object):
if cur == value:
return
try:
invalid_chars = str(value).translate(None, VALID_PERM_CHARS)
invalid_chars = str(value).translate(None, self._perm_chars)
except (TypeError, UnicodeEncodeError):
raise TypeError('expected string, not %r' % value)
if invalid_chars:
raise ValueError('got invalid chars %r in permission'
' specification %r, expected empty string'
' or one or more of %r'
% (invalid_chars, value, VALID_PERM_CHARS))
% (invalid_chars, value, self._perm_chars))
sort_key = lambda c: self._perm_val[c]
new_value = ''.join(sorted(set(value),
@ -168,8 +171,7 @@ class FilePerms(object):
def atomic_save(dest_path, **kwargs):
"""A convenient interface to the :class:`AtomicSaver` type. See the
:class:`AtomicSaver` documentation for more info.
:class:`AtomicSaver` documentation for details.
"""
return AtomicSaver(dest_path, **kwargs)
@ -183,11 +185,11 @@ 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 it is closed. It returns a
standard Python :class:`file` object which can be closed
explicitly or used as a context manager (i.e., via the :keyword:`with`
statement).
"""``AtomicSaver`` is a configurable context manager that provides a
writable file which will be moved into place as long as no
exceptions are raised before it is closed. It returns a standard
Python :class:`file` object which can be closed explicitly or used
as a context manager (i.e., via the :keyword:`with` statement).
Args:
dest_path (str): The path where the completed file will be
@ -207,6 +209,8 @@ class AtomicSaver(object):
open_kwargs (dict): Additional keyword arguments to pass to
*open_func*. Defaults to ``{}``.
"""
# TODO: option to abort if target file modify date has changed
# since start?
def __init__(self, dest_path, **kwargs):
self.dest_path = dest_path
self.overwrite = kwargs.pop('overwrite', True)
@ -285,17 +289,30 @@ _CUR_DIR = os.path.dirname(os.path.abspath(__file__))
def iter_find_files(directory, patterns, ignored=None):
"""\
Finds files under a `directory`, matching `patterns` using "glob"
syntax (e.g., "*.txt"). It's also possible to ignore patterns with
the `ignored` argument, which uses the same format as `patterns.
"""Returns a generator that yields file paths under a *directory*,
matching *patterns* using `glob`_ syntax (e.g., ``*.txt``). Also
supports *ignored* patterns.
Args:
directory (str): Path that serves as the root of the
search. Yielded paths will include this as a prefix.
patterns (str or list): A single pattern or list of
glob-formatted patterns to find under *directory*.
ignored (str or list): A single pattern or list of
glob-formatted patterns to ignore.
For example, finding Python files in the current directory:
>>> filenames = sorted(iter_find_files(_CUR_DIR, '*.py'))
>>> filenames[-1].split('/')[-1]
'tzutils.py'
Or, Python files while ignoring emacs lockfiles:
>>> filenames = iter_find_files(_CUR_DIR, '*.py', ignored='.#*')
That last example ignores emacs lockfiles.
.. _glob: https://en.wikipedia.org/wiki/Glob_%28programming%29
"""
if isinstance(patterns, basestring):
patterns = [patterns]
@ -316,31 +333,23 @@ def iter_find_files(directory, patterns, ignored=None):
return
def copytree(src, dst, symlinks=False, ignore=None):
"""Recursively copy a directory tree using copy2().
def copy_tree(src, dst, symlinks=False, ignore=None):
"""The ``copy_tree`` function is an exact copy of the built-in
:func:`shutil.copytree`, with one key difference: it will not
raise an exception if part of the tree already exists. It achieves
this by using :func:`mkdir_p`.
The destination directory is allowed to already exist.
If exception(s) occur, an Error is raised with a list of reasons.
Args:
src (str): Path of the source directory to copy.
dst (str): Destination path. Existing directories accepted.
symlinks (bool): If ``True``, copy symlinks rather than their
contents.
ignore (callable): A callable that takes a path and directory
listing, returning the files within the listing to be ignored.
If the optional symlinks flag is true, symbolic links in the
source tree result in symbolic links in the destination tree; if
it is false, the contents of the files pointed to by symbolic
links are copied.
For more details, check out :func:`shutil.copytree` and
:func:`shutil.copy2`.
The optional ignore argument is a callable. If given, it
is called with the `src` parameter, which is the directory
being visited by copytree(), and `names` which is the list of
`src` contents, as returned by os.listdir()::
callable(src, names) -> ignored_names
Since copytree() is called recursively, the callable will be
called once for each directory that is copied. It returns a
list of names relative to the `src` directory that should
not be copied.
Note that the standard library bears the warning: "Consider this
example code rather than the ultimate tool."
"""
names = os.listdir(src)
if ignore is not None:
@ -382,6 +391,9 @@ def copytree(src, dst, symlinks=False, ignore=None):
raise Error(errors)
copytree = copy_tree # alias for drop-in replacement of shutil
if __name__ == '__main__':
#with atomic_save('/tmp/final.txt') as f:
# f.write('rofl')

View File

@ -2,5 +2,41 @@
==================================
.. automodule:: boltons.fileutils
:members:
:undoc-members:
Creating, Finding, and Copying
------------------------------
Python's :mod:`os`, :mod:`os.path`, and :mod:`shutil` modules provide
good coverage of file wrangling fundaments, and these functions help
close a few remaining gaps.
.. autofunction:: boltons.fileutils.mkdir_p
.. autofunction:: boltons.fileutils.iter_find_files
.. autofunction:: boltons.fileutils.copytree
Atomic File Saving
------------------
Ideally, the road to success should never put current progress at
risk. That's why overwriting a file should only happen after the task
at hand has completed. And that's exactly what :func:`atomic_save` and
:class:`AtomicSaver` do.
.. autofunction:: boltons.fileutils.atomic_save
.. autoclass:: boltons.fileutils.AtomicSaver
File Permissions
----------------
Linux, BSD, Mac OS, and other Unix-like operating systems all share a
simple, foundational file permission structure that is commonly
complicit in accidental access denial, as well as file
leakage. :class:`FilePerms` was built to increase clarity and cut down
on permission-related accidents when working with files from Python
code.
.. autoclass:: boltons.fileutils.FilePerms