What’s the best way to duplicate a Note class instance as a note is being added in the note_will_be_added hook? The following don’t work:
duplicate_note = Note(id=note.id, col=note.col), new notes don’t exist in the collection so note.load() fails.
duplicate_note = copy.deepcopy(note), fails as it tries to copy some rust backend link
The use case is that I’m modifying the note using values from the note in a sequence of operations, where each operation modifies the note. If I were to just use the same Note instance as the source there will occur undesired effects as the source data is modified during the process.
def duplicate_note(note: Note) -> Note:
"""
Duplicate a note by creating a new instance and copying the fields.
Using copy.deepcopy on a Note object does not work, and Note(id=note.id) does not
work on a new note (where id is 0), thus this utility function.
"""
new_note = Note(col=note.col, model=note.note_type())
# Copied code from notes.py _to_backend_note method
# the method calls hooks.note_will_flush(self) which is not desired here
# This code may break if the Note class changes in the future.
backend_note = notes_pb2.Note(
id=note.id,
guid=note.guid,
notetype_id=note.mid,
mtime_secs=note.mod,
usn=note.usn,
tags=note.tags,
fields=note.fields,
)
# Calling internal method that is not part of the public API, so this may break if the
# Note class changes in the future.
new_note._load_from_backend_note(backend_note)
return new_note
I’d like to know if there’s some method that’d be less likely to break in the future.
I’m wondering if adding a duplicate_note method to notes.py would actually be as simple as adding one new internal method that does the copying to notes_pb2.Note only and use that in the duplicate method (exactly as my function above does):
I’d be happy to accept a PR that handles this in __deepcopy__, by e.g. nulling out the backend handle prior to copying, then restoring it afterwards so both objects share it.
Not exactly sure how a __deepcopy__ customization is supposed to be done. So, is the general idea that you should use the original deepcopy like this, whenever possible? Without testing this, I guess this ought to be using the default __deepcopy__ on the note by detaching the col ref and then re-attaching it to the original and copy at the end. If this looks like the right path, I’ll test an implementation along these lines on my addon.
def __deepcopy__(self, memo):
# Unset col ref so we can deepcopy normally
col_ref = self.col
self.col = None
# Perform default deepcopy
deepcopy_method = self.__deepcopy__
self.__deepcopy__ = None
dupe_note= deepcopy(self, memo)
self.__deepcopy__ = deepcopy_method
# Bind to dupe_note by types.MethodType, so a subsequent deepcopy on the
# copy copies it and not the original
dupe_note.__deepcopy__ = types.MethodType(deepcopy_method.__func__, cp)
# Reattach col ref to original and dupe
self.col = col_ref
dupe_note.col = col_ref
return dupe_note
def __deepcopy__(self, memo):
from copy import deepcopy
new = type(self).__new__(type(self))
memo[id(self)] = new
for k,v in self.__dict__.items():
setattr(new, k, v if k=="col" else deepcopy(v, memo))
return new
FWIW, I would be happy to see us drop the col property from our other objects like Note, and pass it in when required instead, as that makes it more explicit when the collection is being accessed. Doing so without breaking existing add-ons might be tricky though.