Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Write layer-source of object into Data.FS #132

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions perfact/zodbsync/tests/test_sync.py
Original file line number Diff line number Diff line change
@@ -1667,6 +1667,7 @@ def addlayer(self, seqnum='00', frozen=True):
with open(path, 'w') as f:
f.write('base_dir = "{}"\n'.format(layer))
f.write('frozen = {}\n'.format(frozen))
f.write('ident = "{}"\n'.format(name))
os.mkdir(os.path.join(layer, '__root__'))
# Force re-reading config
if hasattr(self, 'runner'):
@@ -2186,3 +2187,31 @@ def test_layer_frozen(self):
with open(source_fmt.format(self.repo.path)) as f:
# ... content is in custom layer!
assert f.read() == 'text_content'

def test_layer_info_datafs(self):
"""
Validate the correct writing and clearing of the layer ident
in the Data.FS
"""
with self.runner.sync.tm:
self.app.manage_addProduct['OFSP'].manage_addFile(id='blob')

with self.addlayer(frozen=True) as layer:
self.run('record', '/blob')
assert getattr(self.app.blob, 'zodbsync_layer', None) is None
# Move file to layer and check that layer info is stored in Data.FS
shutil.move(
'{}/__root__/blob'.format(self.repo.path),
'{}/__root__/blob'.format(layer),
)
self.run('record', '/')
assert getattr(self.app.blob, 'zodbsync_layer') is not None
# Change file in Data.FS and verify that layer info is cleared
with self.runner.sync.tm:
self.app.blob.manage_edit(
filedata='text_content',
content_type='text/plain',
title='BLOB'
)
self.run('record', '/')
assert getattr(self.app.blob, 'zodbsync_layer', None) is None
25 changes: 23 additions & 2 deletions perfact/zodbsync/zodbsync.py
Original file line number Diff line number Diff line change
@@ -93,11 +93,13 @@ def mod_read(obj=None, onerrorstop=False, default_owner=None,
if 'owner' in meta:
del meta['owner']

meta['zodbsync_layer'] = getattr(obj, 'zodbsync_layer', None)

return meta


def mod_write(data, parent=None, obj_id=None, override=False, root=None,
default_owner=None, force_default_owner=False):
default_owner=None, force_default_owner=False, layer=None):
'''
Given object data in <data>, store the object, creating it if it was
missing. With <override> = True, this method will remove an existing object
@@ -162,6 +164,9 @@ def mod_write(data, parent=None, obj_id=None, override=False, root=None,
for handler in mod_implemented_handlers(obj, meta_type):
handler.write(obj, d)

# Also write zodbsync layer information
obj.zodbsync_layer = layer
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm pretty sure this will have no effect on most record runs since they don't commit their transaction. In particular, the watch command explicitly aborts its transaction (https://github.com/perfact/zodbsync/blob/main/perfact/zodbsync/commands/watch.py#L357) since it is used to only make sure a consistent state is viewed and it does not expect that anything is changed in the Data.FS. And a regular record run uses an implicit transaction that is neither commited nor aborted, which I am pretty sure means an implicit abort.


if temp_obj:
children = temp_obj.manage_cutObjects(temp_obj.objectIds())
obj.manage_pasteObjects(children)
@@ -448,7 +453,12 @@ def fs_write(self, path, data):
old_data = self.fs_read(pathinfo['fspath'])

# Build object
meta = {key: value for key, value in data.items() if key != 'source'}
exclude_keys = ['source', 'zodbsync_layer']
meta = {
key: value
for key, value in data.items()
if key not in exclude_keys
}
fmt = mod_format(meta)
if isinstance(fmt, str):
fmt = fmt.encode('utf-8')
@@ -677,6 +687,11 @@ def record_obj(self, obj, path, recurse=True, skip_errors=False):
raise

pathinfo = self.fs_write(path, data)
path_layer = pathinfo['layers'][pathinfo['layeridx']]['ident']

current_layer = getattr(obj, 'zodbsync_layer', None)
if current_layer != path_layer:
obj.zodbsync_layer = path_layer

if not recurse:
return
@@ -739,6 +754,11 @@ def _playback_path(self, pathinfo):
# fspath is None if the object is to be deleted
fs_data = pathinfo['fspath'] and self.fs_parse(pathinfo['fspath'])

# extend fs_data with layerinfo
if fs_data:
fs_data['zodbsync_layer'] = pathinfo['layers'][
pathinfo['layeridx']]['ident']

# Traverse to the object if it exists
parent_obj = None
obj = self.app
@@ -823,6 +843,7 @@ def _playback_path(self, pathinfo):
root=(obj if parent_obj is None else None),
default_owner=self.default_owner,
force_default_owner=self.force_default_owner,
layer=pathinfo['layers'][pathinfo['layeridx']]['ident']
)
except Exception:
# If we do not want to get errors from missing