Skip to content

Commit

Permalink
Add id_generator to policy
Browse files Browse the repository at this point in the history
Add Entry.uuid setter allowing users to change an Entry's uuid.
  • Loading branch information
danbradham committed Mar 17, 2019
1 parent 4c61a60 commit 98b4695
Show file tree
Hide file tree
Showing 7 changed files with 111 additions and 12 deletions.
2 changes: 1 addition & 1 deletion fsfs/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
__author__ = 'Dan Bradham'
__email__ = '[email protected]'
__url__ = 'http://github.com/fsfs.git'
__version__ = '0.2.5'
__version__ = '0.2.6'
__license__ = 'MIT'
__description__ = 'Tag filesystem locations and store metadata'

Expand Down
27 changes: 26 additions & 1 deletion fsfs/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@
'get_entry_factory',
'set_entry_factory',
'get_entry',
'get_id_generator',
'set_id_generator',
'generate_id',
'InvalidTag',
'validate_tag',
'make_tag_path',
Expand Down Expand Up @@ -188,6 +191,29 @@ def decode_data(data):
return get_data_decoder()(data)


def set_id_generator(func):
'''Set the global policy's id_generator function. This is used to create
unique id's for Entries.
Expects a callable that returns a string. For example the default
id_generator is `lambda: uuid.uuid4().hex`.
'''

get_policy().set_id_generator(func)


def get_id_generator():
'''Get the global policy's id_generator function.'''

return get_policy().get_id_generator()


def generate_id():
'''Uses the global policy's id_generator to create a new unique id.'''

return get_id_generator()()


class InvalidTag(Exception): pass


Expand Down Expand Up @@ -488,7 +514,6 @@ def quick_select(root, selector, sep=DEFAULT_SELECTOR_SEP,
if entries:
match = min(entries, key=len)[:-6]
matches.append(match)
depth = depth
break
else:
return
Expand Down
3 changes: 1 addition & 2 deletions fsfs/factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,6 @@ def on_entry_missing(self, entry, exc):

def on_entry_deleted(self, entry):
'''Removes entry from cache when it's deleted...'''
print(entry)
self._cache.pop(entry.path)


Expand Down Expand Up @@ -269,7 +268,7 @@ def _update_cache(self, path, proxy=None):
if path not in self._cache:
self._cache[path] = entry_type(path)

elif not type(self._cache[path]) is entry_type:
elif type(self._cache[path]) is not entry_type:

old_entry = self._cache[path]
new_entry = entry_type(path)
Expand Down
2 changes: 1 addition & 1 deletion fsfs/lockfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,7 @@ def _release_locks(cls):

@contextmanager
def lockfile(path, timeout=0):
'''LockFile contextmanager, for when you only need to acquire a lock one.
'''LockFile contextmanager, for when you only need to acquire a lock once.
Arguments:
path (str): path to the lockfile
Expand Down
35 changes: 28 additions & 7 deletions fsfs/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ def relink_uuid(entry):
data.uuid, data.uuid_file = uuid_info
return
# uuid file deleted
data._new_uuid()
data._set_uuid()
return

if os.path.isdir(entry.path):
Expand Down Expand Up @@ -208,19 +208,22 @@ def _find_uuid(self):
self.uuid_file = entry.path
return True

def _new_uuid(self):
def _set_uuid(self, _id=None):
'''Use this at your own risk. UUID is used for Entry rediscovery.
If you change it other processes will become out of sync.
'''

# If this Entry already has a uuid it means we are
# creating a new one in which case we will emit the
# uuid_changed event.
is_new_uuid = bool(self.uuid)

with self._lock:

if self.uuid_file and os.path.isfile(self.uuid_file):
os.remove(self.uuid_file)

self.uuid = str(uuid.uuid4())
self.uuid = _id or api.generate_id()
self.uuid_file = self._make_uuid_path(self.uuid)
util.touch(self.uuid_file)

Expand All @@ -232,7 +235,7 @@ def _init_uuid(self):
return
if self._find_uuid():
return
self._new_uuid()
self._set_uuid()

def _init(self):
if self._requires_relink():
Expand Down Expand Up @@ -409,7 +412,10 @@ class Entry(object):
def __init__(self, path):
self.path = path
self.name = os.path.basename(path)
self.data = EntryData(self, util.unipath(path, api.get_data_root()))
self.data = EntryData(
self,
util.unipath(path, api.get_data_root())
)

def __repr__(self):
return '<fsfs.Entry>(name={name}, path={path})'.format(**self.__dict__)
Expand All @@ -434,6 +440,17 @@ def uuid(self):

return self.data.uuid

@uuid.setter
def uuid(self, _id):
'''Set this Entry's uuid.
.. warning:: This will invalidate other users cache. You really only
want to set this at the moment of Entry creation, and hope that nobody
holds this Entry in their cache.
'''

return self.data._set_uuid(_id)

@property
def tags(self):
'''List of this Entry's tags'''
Expand Down Expand Up @@ -616,10 +633,10 @@ def copy(self, dest, only_data=False):

# Update uuids and send EntryCreated signals
new_entry = api.get_entry(dest)
new_entry.data._new_uuid()
new_entry.data._set_uuid()
new_entry.created.send(new_entry)
for child in new_entry.children():
child.data._new_uuid()
child.data._set_uuid()
child.created.send(child)
return new_entry

Expand Down Expand Up @@ -665,6 +682,10 @@ def delete(self, remove_root=False):
'''

self.data.delete()
self.data = EntryData(
self,
util.unipath(self.path, api.get_data_root())
)

if remove_root:
# Delete children depth-first making sure we send all
Expand Down
13 changes: 13 additions & 0 deletions fsfs/policy.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,15 @@ def __init__(
data_root=None,
data_file=None,
entry_factory=None,
id_generator=None
):
self._data_encoder = data_encoder
self._data_decoder = data_decoder
self._data_root = data_root
self._data_file = data_file
self._entry_factory = entry_factory
self._setup_entry_factory(entry_factory)
self._id_generator = id_generator

def set_data_encoder(self, data_encoder):
self._data_encoder = data_encoder
Expand Down Expand Up @@ -103,6 +105,12 @@ def set_entry_factory(self, entry_factory):
def get_entry_factory(self):
return self._entry_factory

def get_id_generator(self):
return self._id_generator

def set_id_generator(self, func):
self._id_generator = func


# Json Encoder / Decoder
import json
Expand Down Expand Up @@ -131,6 +139,10 @@ def get_entry_factory(self):
DefaultDecoder = JsonDecoder
DefaultEncoder = JsonEncoder

# Default ID Generator
import uuid
DefaultIdGenerator = lambda: uuid.uuid4().hex

# Default Data Paths
DefaultRoot = '.data'
DefaultFile = 'data'
Expand All @@ -145,5 +157,6 @@ def get_entry_factory(self):
data_root=DefaultRoot,
data_file=DefaultFile,
entry_factory=DefaultFactory,
id_generator=DefaultIdGenerator
)
_global_policy = DefaultPolicy
41 changes: 41 additions & 0 deletions test_fsfs.py
Original file line number Diff line number Diff line change
Expand Up @@ -545,3 +545,44 @@ def test_new_uuid_after_copy(tempdir):
assert new_entry.path == dest_path
assert new_entry.uuid != entry.uuid
assert len(glob.glob(new_entry.data.path + '/uuid_*')) == 1


@provide_tempdir
def test_id_generator(tempdir):
'''Custom id generator'''

def make_id(count=[0]):
_id = str(count[0])
count[0] += 1
return _id

fsfs.set_id_generator(make_id)

generated = []
for i in range(10):
e = fsfs.get_entry(util.unipath(tempdir, 'entry_' + str(i)))
e.tag('generic')
generated.append(e)

for i, e in enumerate(generated):
assert e.uuid == str(i)

fsfs.set_default_policy()


@provide_tempdir
def test_custom_uuid(tempdir):
'''Assign custom uuid using Entry.uuid setter'''

path = util.unipath(tempdir, 'entry')

entry = fsfs.get_entry(path)
entry.tag('generic')

old_uuid = entry.uuid
new_uuid = 'custom_uuid'

entry.uuid = new_uuid

assert entry.uuid != old_uuid
assert entry.uuid == new_uuid

0 comments on commit 98b4695

Please sign in to comment.