Repository navigation
Conversation
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…etions Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
FK constraints are deferred, so saving a record whose target the sink failed or removed earlier in the run would only fail at commit. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
bjester
force-pushed
the
streaming-deserialize-part-3
branch
from
October 14, 2026 22:29
d7d0fc1 to
0cf4927
Compare
5 tasks
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Defaults the test Facility to not load its parent during validation, like models that don't load related objects. With sink lag, a dirty chain of models that do takes one run per level, so the test that deserializes a whole chain at once relies on the new default. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Keeps the legacy signature and return shape for manual use. The deferred FK references are no longer produced, so defer_fks warns and the second element is always empty. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
bjester
force-pushed
the
streaming-deserialize-part-3
branch
from
October 14, 2026 23:04
0cf4927 to
af3a4f4
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Completes the streaming deserialization migration. It adds the sink, switches all callers to the new pipeline, and removes the legacy
_deserialize_from_storepath. Deserialization now runs as a stream, like serialization:What's new (
morango/sync/stream/deserialize.py)DeserializeSinkapplies each task's resolved outcome per chunk:SAVE: per-record save in its own savepoint, withpre_save/post_savemuted.DELETE: one DjangoCollectordelete per chunk.PROPAGATE_DELETE/PROPAGATE_HARD_DELETE: batchedbulk_create(ignore_conflicts=True)into the tracking tables.ERROR: an error write on the Store record.QuerySet.updateper chunk. Error writes are per record, becausebulk_updatemeasured slower than per-record saves.deserialize_from_store(profile, sync_filter=None, skip_errored=False)is the entry point, shaped likeserialize_into_store. It logs a per-outcome summary.delete_app_modelsandrecord_propagated_deletionsare shared by the sink andStore._deserialize_store_model.Why the sink checks saves against two sets. The buffer means a record is validated up to one chunk before it is written. FK constraints are
DEFERRABLE INITIALLY DEFERRED, so a save that breaks one succeeds inside its savepoint and fails the whole transaction at commit. Two sink writes can remove a target that a later save was validated against:DELETEcascades to app rows that a dirty record references.The sink tracks failed IDs and cascaded row keys, and gives an error write to any
SAVEthat references them instead of saving it. Each problem record gets a per-record error, and the run always commits.Callers and removal
ReceiverDeserializeOperationandMorangoProfileController.deserialize_from_storecall the new entry point. The controller's public signature is unchanged._deserialize_from_store,_save_deserialized_record,_validate_store_foreign_keys,_validate_missing_store_foreign_keysand_handle_deleted_store_foreign_keysare removed, along with the imports only they used.Store._deserialize_store_modelkeeps its signature and(app_model | None, dict)return so it can still be called by hand. It is rebuilt on the transforms and write helpers.defer_fks=Trueis ignored with aDeprecationWarning, and the second element is always{}.Behavior differences from legacy
dirty_bit=False. Legacy left it dirty.skip_erroredor the sync filter) is now saved, because it validates against the parent's older app row. Legacy never deserialized children of a dirty parent, and failed them withMorangoMissingParent. This difference comes from the Part 2 transforms (Streaming deserialization part 2: transformations #390).clean_fieldsloads a related object. A child in the same chunk as its dirty parent can't load the parent yet, so it fails withMorangoDirtyParent. A dirty chain of depth d in one chunk takes d runs. Kolibri models don't load related objects inclean_fields, so Kolibri is not affected. The testFacility.clean_dereferences_parentnow defaults toFalseto match, and tests patch it toTrueto cover such models.Store._deserialize_store_modelraises the error the transforms resolved, which can beMorangoDirtyParent,MorangoMissingParentorIntegrityError.morango.sync.operationstomorango.sync.stream.deserialize.Not in scope
post_deletestays muted, as in legacy.deferred_clean_fields,ForeignKeyReference,TemporaryTableand the backend bulk helpers lose their only callers. They are kept for a later cleanup.References
_deserialize_from_store#210 (deserialization memory use).Reviewer guidance
Verification performed
prek run --all-filesis clean, andmakemigrations --checkreports no changes.DeserializeFromStoreTestCasetests that check the run commits fail at commit withFOREIGN KEY constraint failed. This shows they cover the deferred-constraint case.Test changes
dirty_bitbecomesFalse;test_dirty_parent_not_in_run, where the child is now saved and ends clean, instead of failing withMorangoMissingParent.DeserializationTestCaseskeeps the same expected results; only the call and its keyword arguments change.test_controller.pychanges only the logger name intest_unique_violation_logs_warning.Facilitydefault:SelfReferentialFKDeserializationTestCase::test_models_created_successfully. It deserializes a whole dirty chain in one run, which is the multi-run difference described above.Worth a close look
DeserializeSink.consumeorder: errors, then deletes, then propagated deletions, then saves, then Store writes. Deletes run before saves so the save guard can see the rows that a delete in the same chunk cascades to.delete_app_modelsbuilds itsCollectorwhilepost_deleteis muted. This matters because Django checks for signal receivers when choosing to fast-delete. Fast-deleted querysets are read with one extraSELECTeach, only in chunks that have deletes.AI usage
I used Claude Code (Claude Opus 5.5) throughout this part, in a spec → plan → implementation workflow. The design spec and implementation plan were drafted with Claude, and I reviewed and approved both. An additional AI review of the spec found two problems that we fixed before implementation: a missing cascade case and an impossible query-count criterion. I directed key decisions, including the buffered sink, per-record saves over
bulk_update, and keepingStore._deserialize_store_modelfor manual use. Claude then implemented the plan task by task, writing failing tests first. It also captured the legacy parity values and ran mutation checks to confirm the new tests catch the deferred-constraint failures.