Source code for vlermv._fs
import os
from random import randint
from string import ascii_letters
from ._exceptions import DeleteError, PermissionError, out_of_space
from ._abstract import AbstractVlermv
from ._exceptions import OpenError
def _get_fn(fn, mode, load):
'''
Load a contents, checking that the file was not modified during the read.
'''
try:
mtime_before = os.path.getmtime(fn)
except OSError:
mtime_before = None
try:
with open(fn, mode) as fp:
item = load(fp)
except OpenError:
raise
else:
mtime_after = os.path.getmtime(fn)
if mtime_before in {None, mtime_after}:
return item
else:
raise EnvironmentError('File was edited during read: %s' % fn)
def _random_file_name():
n = len(ascii_letters) - 1
return ''.join(ascii_letters[randint(0, n)] for _ in range(10))
def mktemp(tempdir, filename = _random_file_name):
try:
os.makedirs(tempdir, exist_ok = True)
except TypeError:
# Python 2
if not os.path.isdir(tempdir):
os.makedirs(tempdir)
return os.path.join(tempdir, filename())
def _reversed_directories(outer, inner):
while outer != inner:
yield inner
inner = os.path.dirname(inner)
[docs]class Vlermv(AbstractVlermv):
'''
A :py:class:`dict` API to a filesystem
'''
#: Should the cache directory be created when a Vlermv is initialized?
#: This is is mostly relevant for testing.
_mkdir = True
def __init__(self, *directory, tempdir = '.tmp', **kwargs):
'''
:param str directory: Top-level directory of the vlermv
:param serializer: A thing with dump and load functions for
serializing and deserializing Python objects,
like :py:mod:`json`, :py:mod:`yaml`, or
anything in :py:mod:`vlermv.serializers`
:type serializer: :py:mod:`serializer <vlermv.serializers>`
:param key_transformer: A thing with to_path and from_path functions
for transforming keys to file paths and back.
Several are available in :py:mod:`vlermvtransformers`.
:type key_transformer: :py:mod:`key_transformer <vlermvtransformers>`
:param bool mutable: Whether values can be updated and deleted
:param str tempdir: Subdirectory inside of base_directory to use for temporary files
These are mostly relevant for initialization via :py:func:`vlermv.cache`.
:param bool appendable: Whether new values can be added to the Vlermv
(Set this to False to ensure that the decorated function is never
run and that the all results are cached; this is useful for reviewing
old data in a read-only mode.)
:param bool cache_exceptions: If the decorated function raises
an exception, should the failure and exception be cached?
The exception is raised either way.
:raises TypeError: If cache_exceptions is True but the serializer
can't cache exceptions
'''
super(Vlermv, self).__init__(**kwargs)
self.base_directory = os.path.expanduser(os.path.join(*directory))
self.tempdir = os.path.join(self.base_directory, tempdir)
if self._mkdir:
os.makedirs(self.tempdir, exist_ok = True)
def __repr__(self):
return 'Vlermv(%s)' % repr(self.base_directory)
def __setitem__(self, index, obj):
super(Vlermv, self).__setitem__(index, obj)
fn = self.filename(index)
os.makedirs(os.path.dirname(fn), exist_ok = True)
exists = os.path.exists(fn)
if (not self.mutable) and exists:
raise PermissionError('This warehouse is immutable, and %s already exists.' % fn)
elif (not self.appendable) and (not exists):
raise PermissionError('This warehouse not appendable, and %s does not exist.' % fn)
else:
tmp = mktemp(self.tempdir)
with open(tmp, 'w+' + self._b()) as fp:
try:
self.serializer.dump(obj, fp)
except Exception as e:
if out_of_space(e):
fp.close()
os.remove(tmp)
raise BufferError('Out of space')
else:
raise
os.rename(tmp, fn)
def __getitem__(self, index):
try:
return _get_fn(self.filename(index), 'r+' + self._b(), self.serializer.load)
except OpenError:
raise KeyError(index)
def __delitem__(self, index):
super(Vlermv, self).__delitem__(index)
fn = self.filename(index)
try:
os.remove(fn)
except DeleteError as e:
raise KeyError(*e.args)
else:
for fn in _reversed_directories(self.base_directory, os.path.dirname(fn)):
if os.listdir(fn) == []:
os.rmdir(fn)
else:
break
def __len__(self):
length = 0
for dirpath, _, filenames in os.walk(self.base_directory):
for filename in filenames:
length += 1
return length
def keys(self):
for dirpath, _, filenames in os.walk(self.base_directory):
if dirpath != os.path.join(self.base_directory, self.tempdir):
for filename in filenames:
path = self.from_filename(os.path.abspath(os.path.join(dirpath, filename)))
if path != None:
yield path