Skip to content

Fix update_task wiping unrelated fields on partial updates#10

Open
partymola wants to merge 1 commit into
jen6:mainfrom
partymola:fix/update-task-preserve-fields
Open

Fix update_task wiping unrelated fields on partial updates#10
partymola wants to merge 1 commit into
jen6:mainfrom
partymola:fix/update-task-preserve-fields

Conversation

@partymola

Copy link
Copy Markdown

Problem

update_task silently resets fields that were not included in the update call. Three distinct bugs combine to cause this:

1. model_dump() includes defaults for unset fields

TaskObject declares priority with a default of 0, so calling update_task with only a new title still sends priority=0 to the API, resetting any existing priority. The same applies to every Optional field that defaults to None -- dates, reminders, content, etc. are all sent as null.

2. The merge result is never used

Lines 312-313 fetch the existing task and call task_obj.update(task_object), intending to merge, but line 315 sends task_object.model_dump() (the raw partial input) instead of the merged task_obj. The merge has no effect.

3. dict.update() with a Pydantic model doesn't work correctly

task_obj is a dict (from get_by_id), but task_object is a Pydantic TaskObject. dict.update() iterates the model's attribute names as keys, which does not produce a correct field-level merge.

Failure scenarios

  • User sets priority=5 on a task with dates and reminders. Later calls update_task to change only the title. The task loses its priority (reset to 0), its dates, and its reminders.
  • User calls update_task to reschedule a task to a new date. The task's priority is reset to 0 and its reminders are cleared.
  • Any partial update effectively becomes a full replace with defaults for all unspecified fields.

Additionally, sending the full API-response dict back (which includes read-only fields like creator, deleted, kind, isFloating) causes HTTP 500 errors from the TickTick API. And reminders returned as objects [{id, trigger}] must be normalized to strings before sending back.

Fix

  • Use model_dump(exclude_unset=True) so only explicitly provided fields are included in the update payload
  • Merge those fields into the existing task fetched via get_by_id()
  • Filter the merged dict to only API-accepted fields (UPDATABLE_FIELDS whitelist)
  • Normalize reminders from object format [{id, trigger}] to string format ["TRIGGER:..."]

Tests

10 unit tests added covering:

  • Partial update (only priority) preserves existing dates, reminders, title, tags
  • Partial update (only dates) preserves existing priority
  • Reminders in object format are normalized to strings
  • Mixed object/string reminders are handled correctly
  • Read-only fields (creator, deleted, kind, isFloating, etag, etc.) are filtered out
  • Default priority=0 is NOT sent when unset (preserves existing priority)
  • Explicitly set priority=0 IS sent (overwrites existing)

Problem
-------
When a caller updates a single field (e.g. priority), every other field
on the task is silently reset to its default or cleared. Three distinct
bugs combine to cause this:

1. model_dump() includes defaults for unset fields. TaskObject declares
   priority with a default of 0, so calling update_task with only a new
   title still sends priority=0 to the API, resetting any existing
   priority. The same applies to every Optional field that defaults to
   None -- dates, reminders, content, etc. are all sent as null.

2. The merge result is never used. Lines 312-313 fetch the existing task
   and call task_obj.update(task_object), intending to merge, but line
   315 sends task_object.model_dump() (the raw partial input) instead of
   the merged task_obj. The merge has no effect.

3. dict.update() with a Pydantic model does not work as intended.
   task_obj is a dict (from get_by_id), but task_object is a Pydantic
   TaskObject. dict.update() iterates the model's attribute names as
   keys, which does not produce a correct field-level merge.

Failure scenarios:
- User sets priority=5 on a task. Later calls update_task to change
  only the title. The task loses its priority (reset to 0), its dates,
  and its reminders.
- User calls update_task to reschedule a task to a new date. The task's
  priority is reset to 0 and its reminders are cleared.
- Any partial update effectively becomes a full replace with defaults
  for all unspecified fields.

Additionally, sending the full API-response dict back (which includes
read-only fields like creator, deleted, kind, isFloating) causes HTTP
500 errors from the TickTick API. And reminders returned as objects
[{id, trigger}] must be normalized to strings before sending back.

Fix
---
- Use model_dump(exclude_unset=True) so only explicitly provided fields
  are included in the update payload.
- Merge those fields into the existing task fetched via get_by_id().
- Filter the merged dict to only API-accepted fields (UPDATABLE_FIELDS).
- Normalize reminders from object format to string format.
- Add unit tests covering all scenarios above.
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.

1 participant