Is there a better way to duplicate note in note_will_be_added?

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.

My current solution is this hacky function:

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):

    def _make_backend_note(self) -> notes_pb2.Note:
        return notes_pb2.Note(
            id=self.id,
            guid=self.guid,
            notetype_id=self.mid,
            mtime_secs=self.mod,
            usn=self.usn,
            tags=self.tags,
            fields=self.fields,
        )

    def _to_backend_note(self) -> notes_pb2.Note:
        hooks.note_will_flush(self)
        return self._make_backend_note(self)
    
    def duplicate_note(self) -> Note:
        dupe_note = Note(col=self.col, model=self.note_type())
        dupe_note._load_from_backend_note(self._to_backend_note())
        return dupe_note

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.