Source code for cachepath

# -*- coding: utf-8 -*-

"""
Package that provides CachePath, as well as exporting a Python2/3 compatible Path.
"""

__author__ = """Hayden Flinner"""
__email__ = 'hayden@flinner.me'
__version__ = '1.1.1'

try:
    from pathlib import Path, WindowsPath, PosixPath
except ImportError:  # py2
    from pathlib2 import Path, WindowsPath, PosixPath

import os
import shutil
import tempfile
__all__ = [
    'CachePath',
    'TempPath',
    'Path',  # Helpful re-export for those who want Py2 compatibility
    'clear',
    'rm',
]

location = tempfile.gettempdir()


[docs]def clear(path): """Clear the file/dir, leaving it empty (dir) or 0 length (file). Monkey-patched onto all Paths on import. Creates file if path doesn't exist.""" if len(list(path.parents)) == 0 and '/' in str(path): # We let through the case of .location == '.', because if you # really insist on doing that, more power to ya. raise ValueError("Shouldn't be able to happen, stop clownin") if path.is_dir(): for sub in path.iterdir(): if sub.is_dir(): shutil.rmtree(sub) else: sub.unlink() else: with path.open('w'): pass
[docs]def rm(path): """Delete the file/dir, even if it's a dir with files in it. Monkey-patched onto all Paths on import. Does nothing if path doesn't exist.""" if not path.exists(): return if path.is_dir(): shutil.rmtree(str(path)) else: path.unlink()
Path.rm = rm Path.clear = clear
[docs]class CachePath(Path): """Construct a CachePath from one or several strings/Paths. Constructing a CachePath automatically creates the preceding folders necessary for the file to exist, if they're not already there. CachePaths also have a few helper methods: :func:`CachePath().clear() <cachepath.clear>` :func:`CachePath().rm() <cachepath.rm>` By accident, these methods are also attached to regular Paths after constructing a CachePath, but it's not recommended to depend on this behavior. Examples -------- Basic Usage:: CachePath() == '/tmp/xyz123randomfile' CachePath('myfilename') == '/tmp/myfilename' CachePath('myfolder', dir=True) == '/tmp/myfolder/' TempPath('myfolder') == '/tmp/myfolder/zsdskjrandom' Multi-component Paths:: p = CachePath('date/processed_data', dir=True) # Or, Alternate constructor to avoid {}/{}.format() p = CachePath('date', 'processed_data', dir=True) For an example of real usage, here's a quick cache for an arbitrary function/arg combo :: def get_scraped_ebay_stats(product_id): p = CachePath('ebay_results/{}'.format(product_id)) if not p.exists(): sh('wget {}'.format(p)) return parser.parse(p.read_text()) Parameters ---------- args : [str], optional List of strings to join for Path. If None, ``getempfile`` is used. dir : bool, optional Is the path intended to be a directory? Useful when you just need a tempdir for lots of files, and you don't want to make a CachePath out of each. :: d = CachePath(date, dir=True) (d/'tool1results').touch() (d/'tool2results').touch() list(d.iterdir()) == ['tool1results', 'tool2results'] suffix : str, optional Appended to the last path in \\*args, i.e. CachePath('a', 'b', suffix='_long_suff.txt') == '/tmp/a/b_long_suff.txt' mode : int, optional, default=0o777 Mode to create folder with, if it doesn't already exist. """ def __new__(cls, *args, **kwargs): dir = kwargs.pop('dir', False) # Py2 concessions :'( suffix = kwargs.pop('suffix', '') if cls is CachePath: # Copy-pasted from pathlib.py cls = WindowsPath if os.name == 'nt' else PosixPath # CachePath() == TempPath() == Path(tempfile.mkstemp(location)[1]) if not args: fd, loc = tempfile.mkstemp(dir=str(location), suffix=suffix) os.close(fd) args = [loc] else: # Attach the suffix args = list(args) args[-1] = str(args[-1]) + suffix # Construct the Path. Prepend the /tmp location real_args = [str(location)] real_args.extend(args) returning = cls._from_parts(real_args) # Create all of the dirs leading to the path, if they don't exist. # If the path is supposed to be a directory, make that too. dirp = returning if dir else returning.parent mkdir_kwargs = {'mode': kwargs['mode']} if 'mode' in kwargs else {} dirp.mkdir(exist_ok=True, parents=True, **mkdir_kwargs) return returning
[docs]def TempPath(cls, *args, **kwargs): """ See CachePath for more details:: TempPath('x', 'y', suffix='.z') # Is safer, easier, and explains your intent to always have a new file better than CachePath('x', 'y', 'randomstringhere', suffix='.z') # However, TempPath() == CachePath() # for convenience .. TODO Remove on __exit__? """ suffix = kwargs.pop('suffix', '') real_args = list(args) real_args.append(tempfile.mktemp(dir=str(location), suffix=suffix)) return CachePath(*args, **kwargs)