I have a rest api service that updates the "foo_data" field, which is zope.schema.List
@implementer(IPublishTraverse)
class PatchFooData(Service):
def reply(self):
data = json_body(self.request)
if not getattr(self.context, "foo_data", []):
self.context.foo_data = []
self.context.foo_data.append({"archive_url": data.get("archive_url", ""), "comment": data.get("comment", "")})
return self.context.foo_data
Can you see the problem here? It is not immediately obvious, because the python object will be updated in memory so as long as you are on the same WSGI client it appears correct. But by appending to the list the ZODB is apparently not triggered to update, so it's not saved. If you are load balanced to a different WSGI client, or the client reboots, your changes will revert.
What I've done for now is rewrite the entirety of that attribute, which triggers a ZODB write.
@implementer(IPublishTraverse)
class PatchFooData(Service):
def reply(self):
data = json_body(self.request)
foo_data = getattr(self.context, "foo_data", []) or []
foo_data.append({"archive_url": data.get("archive_url", ""), "comment": data.get("comment", "")})
self.context.foo_data = foo_data
return self.context.foo_data
What's the best practice here? Do as the above, or call something like zope.lifecycleevent.modified?
This is a classic ZODB gotcha. Standard Python types like list and dict do not know which persistent objects they are referenced by, so there's no way changes can be automatically tracked by the ZODB connection.
Re-setting the entire attribute is what I would do in this situation.
If you could arrange for the value to be a PersistentList instead of a plain Python list, then it would automatically track changes (and be stored as a separate persistent object rather than part of the self.context record).
zope.lifecycleevent.modified would help, but only by accident. One of its side effects is updating the object's modification time, which marks the object as changed.
I think that self.context._p_changed = 1 is the old magic to do what you need, but:
you could also re-assign, like you do, and leave a comment in your code as to why
you could choose to use a PersistentList instead of a plain-old-Python-list, which makes your list a first-class persistent object not living inside the pickle of its container (self.context in this case).
Thank you both. The PersistentList is just an example "in theory", and not something I would actually want to do for the case of Dexterity fields, right? I have used Persistent* classes in annotations and registry records, but since it is not tied to an object I assume I don't want to use it for content.
I haven't tried to use a PersistentList for a Dexterity field value myself. I think it would work in principle as long as you have an accessor + mutator or data manager to convert between a PersistentList for storage and normal lists for the existing REST API and/or form widgets.