Skip to content

fix: handle full collection sync - #160

Merged
lervag merged 4 commits into
lervag:masterfrom
robertn702:fix/sync-full-sync-159
Oct 10, 2026
Merged

lervag merged 4 commits into
lervag:masterfrom
robertn702:fix/sync-full-sync-159

Conversation

@robertn702

@robertn702 robertn702 commented Sep 15, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • Fail with a clear error when no Anki profile or sync key is available.
  • Read the collection sync response and handle required full downloads, uploads, and conflicts.
  • Use a redirected sync endpoint for the remaining collection and media work.
  • Print server messages instead of discarding them.

Safety

Full uploads and downloads require confirmation. Downloads create and await a local backup before replacing the collection. The collection is reopened after a failed full transfer and closed if automatic sync aborts.

Tests

  • uv run ruff format --check
  • uv run ruff check
  • uv run pyrefly check --baseline pyrefly-baseline.json
  • uv run pytest (38 passed)
  • git diff --check

Closes #159

@robertn702
robertn702 force-pushed the fix/sync-full-sync-159 branch from e9e8907 to c5f071b Compare September 15, 2026 19:10
@robertn702
robertn702 marked this pull request as ready for review September 15, 2026 19:10
Comment thread src/apyanki/anki.py
Comment thread src/apyanki/anki.py
Comment thread src/apyanki/anki.py Outdated
Comment thread src/apyanki/anki.py Outdated

@lervag lervag left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Thanks, I appreciate the PR, it addresses a real bug!

On tests/test_sync.py: Thanks for adding these. I'd like to redirect the effort though, because as written they don't constrain much.

The tests construct the object under test with Anki.__new__(Anki) and assign _profile and col by hand, then assert exact call sequences against a hand-written FakeCollection. That describes the implementation rather than the behaviour: assert collection.operations == [("normal", True), ("backup", ...), ("close", None), ...] breaks on any harmless reordering, while real defects pass straight through. There are two real defects in this PR that passes all seven tests:

  1. The confirmation prompt is never visible. It's issued inside the live Progress block, so rich erases it on the next refresh (~100ms). Under a pty you get a spinner and an apparently hung program. Pressing Enter takes the default and aborts. The tests can't see this because console.confirm is monkeypatched away. That is, the test substitutes exactly the part that's broken.

  2. sync() now raises Abort with no profile or sync key, which __exit__ reaches via auto_sync. With "auto_sync": true in ~/.config/apy/apy.json, this branch has 25 failed tests. CI missed it because CI has no apy.json config. And yes, this is a pre-existing gap in the test setup which I'll fix separately.

What I'd find more valuable:

  • Keep the behavioural tests and drop the sequence ones. test_sync_without_key_fails_instead_of_reporting_success and test_sync_conflict_can_be_cancelled encode the actual promise of #159 and are worth having. For the rest, the membership style you already used in test_sync_uploads_when_only_full_upload_is_allowed (assert ("full", ...) in collection.operations) says what matters without freezing the call order.
  • For real coverage, anki ships a sync server: SYNC_BASE=... SYNC_USER1=user:pass python -m anki.syncserver. I checked, it starts and listens on a local port. A fixture that spawns it would let you drive the exact scenario in the issue through the real backend, with no fake to keep in sync with anki's API.

I'll let the second "real coverage" item be optional here, since I can understand it may feel like a larger task. But if you want, feel free to address it.

@robertn702

Copy link
Copy Markdown
Contributor Author

@lervag Thanks for the thorough review! I'll get around to updating the PR in the next few days.

@robertn702
robertn702 marked this pull request as draft September 23, 2026 15:41
@lervag

lervag commented Sep 23, 2026

Copy link
Copy Markdown
Owner

@lervag Thanks for the thorough review! I'll get around to updating the PR in the next few days.

I see you've pushed some updates now; let me know when you want me to take another look. And don't hesitate to ask for input or assistance!

@robertn702
robertn702 force-pushed the fix/sync-full-sync-159 branch from a3fff1c to 92ed7c2 Compare October 9, 2026 15:10
@robertn702

Copy link
Copy Markdown
Contributor Author

Sorry for the delay. Rebased onto master and pushed the changes from your review. Auto-sync without a profile or sync key now prints a warning and skips the sync, and only an explicit apy sync errors. The prompts pause the progress display, and I checked in a real terminal that the prompt and the answer stay on screen. An unexpected NORMAL_SYNC aborts, and the currentSyncUrl write is gone.

I also took your suggestion to test against anki.syncserver. tests/test_sync_server.py runs the download, upload and conflict cases through the real backend, and all four tests fail on master. I cut tests/test_sync.py down to the cases the server can't easily trigger and replaced the call-order checks with behaviour checks.

Ready for another look when you have time.

@robertn702
robertn702 marked this pull request as ready for review October 9, 2026 15:28
Replace the fake-collection tests for full download, full upload and
cancelled conflicts with tests that drive apy against anki's bundled
sync server. Keep the unit tests for cases the server cannot easily
trigger, and assert spinner visibility instead of call sequences.
Restarting a stopped rich Progress moves the cursor up by its previous
height and erases the prompt and the user's answer, leaving stale
spinner rows above it. Hide the tasks before stopping so the stopped
display has no height, and show them again after the prompt.
@lervag
lervag force-pushed the fix/sync-full-sync-159 branch from 832fd63 to b0edd30 Compare October 10, 2026 21:09
@lervag

lervag commented Oct 10, 2026

Copy link
Copy Markdown
Owner

Great, thanks! And thanks for the nice contribution!

@lervag
lervag merged commit 335c803 into lervag:master Oct 10, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

apy sync reports success without syncing when a full sync is required

2 participants