Repository navigation
feat(api): add device-scoped bucket and query routing - #200
TimeToBuildBob wants to merge 21 commits into
Conversation
Mirror aw-server-rust's /api/0/devices endpoint in Python aw-server. Returns the local device id (UUID, stable across restarts) and hostname. Paired with aw-core#177 which adds device_id to the SQLite schema. Part of ActivityWatch/activitywatch#302 (bucket identity unification). Git-Session-Id: 02ce
…stability Git-Session-Id: aa774620-bec5-581f-adf4-2c8cc3cf4eb6
Git-Session-Id: 931478ca-2d2b-48c5-bb99-0bb5ad608478
[Medium impact] The follow-up appears safe to merge; the previous duplicate-import issue is fixed. SummaryAdds device-scoped bucket and query routes, grouped import/export, and request-owned SQLite connections.
Diagram%%{init: {'theme': 'neutral'}}%%
flowchart LR
Request[HTTP request] --> Connection[Request-owned SQLite connection]
Connection --> Scope[Device-scoped API view]
Scope --> Bucket[Bucket and event operations]
Scope --> Query[Scope-keyed query cache]
Connection --> Export[Read-only export snapshot]
Export --> File[Staged download]
Reviews (3) · Last reviewed commit: "fix(api): skip duplicate new events with..." · Reviewed by Greptile |
Git-Session-Id: 931478ca-2d2b-48c5-bb99-0bb5ad608478
|
The previous format failure is superseded: The implementation session is actively using this branch/worktree, so I left it untouched and sent the owner the two exact findings and the minimal repair (execute each DELETE before asserting its response status). No merge or CI-green claim; this remains a draft. |
Git-Session-Id: 931478ca-2d2b-48c5-bb99-0bb5ad608478
Device import now deduplicates existing same-device events, matching Rust, and merges aliases into the stored row instead of rejecting. Grouped export pins a read-only SQLite snapshot so staging no longer holds the API write lock. Pins aw-core#183 at 4ac2146. Git-Session-Id: aeaa4ffa-df33-4fe0-8487-f35c72996602
|
@greptileai review |
| new_events = [ | ||
| event | ||
| for event in incoming | ||
| if self._import_event_identity(event) not in existing | ||
| ] |
There was a problem hiding this comment.
Imports keep duplicate new events
When an existing bucket receives two identical new events in one device import, _merge_imported_events keeps both. Each event is checked against the same existing set, which contains only stored events. Both copies reach create_events, so overlapping source histories can inflate event counts and activity totals.
Add each accepted event's identity to the set before checking the next event.
| new_events = [ | |
| event | |
| for event in incoming | |
| if self._import_event_identity(event) not in existing | |
| ] | |
| new_events = [] | |
| for event in incoming: | |
| identity = self._import_event_identity(event) | |
| if identity not in existing: | |
| existing.add(identity) | |
| new_events.append(event) |
There was a problem hiding this comment.
Fixed in 0278a28.
_merge_imported_events compared each incoming event only against stored history, so two identical new events in one batch both reached create_events. Accepted identities are now recorded as they land. Regression test_import_skips_duplicate_new_events_in_one_batch failed at count 3 before the set update and passes at 2 after. The two CodeQL DELETE-in-assert findings are split so the request runs before the status check.
Existing-bucket merge compared each incoming event only against stored history, so two identical new events in one envelope both reached create_events. Record accepted identities as they land, and execute DELETE requests before asserting their status in the HTTP tests. Git-Session-Id: 72dcece2-03b4-59da-979e-db600720e05b
|
@greptileai review |
Git-Session-Id: 0dc232f4-6721-4652-9e55-bd51fe8eec69
Existing-bucket merge compared each incoming event only against stored history, so two identical new events in one envelope both reached create_events. Record accepted identities as they land, and execute DELETE requests before asserting their status in the HTTP tests. Git-Session-Id: 72dcece2-03b4-59da-979e-db600720e05b
Git-Session-Id: 0dc232f4-6721-4652-9e55-bd51fe8eec69
Git-Session-Id: 0dc232f4-6721-4652-9e55-bd51fe8eec69
Git-Session-Id: 0dc232f4-6721-4652-9e55-bd51fe8eec69
Git-Session-Id: c2d218fa-690b-4e77-96e3-959267ad65f3
Git-Session-Id: c2d218fa-690b-4e77-96e3-959267ad65f3
Git-Session-Id: c2d218fa-690b-4e77-96e3-959267ad65f3
|
Integrated #201 in d6e9d4c, retaining the existing same-batch import dedup repair. Current aa99cfc pins/locks integrated core 26001d3; verified installed direct_url.json, no PYTHONPATH. tests/test_device_backend_contract.py is included in make test: set AW_RUST_CONTRACT_BINARY to run identical real-loopback HTTP fixtures on both backends. Eight shared cases pass, including Python→Rust and Rust→Python idempotent grouped transfer, aliases/query/export, heartbeat state, UUID/local equivalence and forged legacy ownership. They exposed two repaired gaps: core alias-query existence and naive created timestamps causing Rust import 422. New creation is aware UTC; grouped export normalizes historical naive metadata without rewriting SQL (regression verifies this). Package slice 156 passed; mypy32, Ruff/Black/hooks pass. Both Rust name guards remain and full same-name acceptance is unfinished; no production migration, watcher/V2 rollout or release claim. |
…meout The macOS Build job times out waiting for the Python backend's /info while the server banner has printed; the loop discarded every response and exception, so the failure was undiagnosable. Git-Session-Id: 422c02db-c672-451c-95ff-206cb6ce083b
HTTPServer.server_bind calls socket.getfqdn on the bind address before listening. On macOS runners that lookup outlives the 20s readiness deadline, so the server log stops after the Flask banner and /info times out. Stub getfqdn in the test subprocess only; reproduced locally by forcing a slow getfqdn via sitecustomize (red before, green after). Git-Session-Id: 422c02db-c672-451c-95ff-206cb6ce083b
Git-Session-Id: c81281b2-9cbe-4e63-a85e-e227184324d1
Git-Session-Id: c81281b2-9cbe-4e63-a85e-e227184324d1
Git-Session-Id: c81281b2-9cbe-4e63-a85e-e227184324d1
Git-Session-Id: 914f36a8-05d5-4b6a-865c-3aaf7e1995f8
| logs = [] | ||
| running = {} | ||
|
|
||
| def start(backend, label, restart=False): |
Summary
Continue ActivityWatch/activitywatch#302 with Python HTTP integration over draft ActivityWatch/aw-core#183 at
4ac214618dd9e93cc8e054b4e797ad70df96f641(Git-pinned and locked). Includes #183 local discovery commits rebased on master; that PR remains independently mergeable.localto one stored scope; preserve peer ownership. Legacy POST is local-only, alias-aware, and rejects local/global routing disagreement. Other unique global-name compatibility remains; ambiguity is 409.data./importstill rejects existing exact names. Rollback deletes only buckets this call created.Verification
Initial 4 HTTP contract tests failed on missing routes. Additional regressions reproduced metadata loss, unsafe legacy POST acknowledgement, catalogue/import error-path gaps, per-bucket snapshot commits, request-thread SQLite 500s, reject-on-existing device re-import, and export holding the write lock before repair.
make test, including re-import dedup/alias merge, concurrent write during staged export, real concurrent WAL writer exclusion, staging failure before success, real request-thread export and parallel same-name device isolation.make test(live-clienttest_client.pyandtest_integration.pyexcluded). Repository mypy 31 files passed.4ac2146: 392 passed / 5 skipped, repository mypy 37 files passed (using its prescribed stubs/import flags).A same-version PyPI aw-core installation survived the first Poetry install despite the Git lock. Removed only aw-core from the isolated venv, reinstalled and verified
direct_url.jsonidentifies the exact Git pin before the no-PYTHONPATH tests. Subsequent pin update to4ac2146is locked inpoetry.lock.Explicit draft boundaries