Functional Weave
Code in Rust

todo.list@1.0.0

impl/python/add_todo.py

2,140 bytes · the Python implementation · view raw

Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.

from typing import List, Sequence

from .todo_item import Todo, TodoDraft, validate_draft  ← from todo.item ^1.0.0 · built alongside by fune
from .todo_item_validate_draft import trim_space
from .todo_item_validate_todo import is_utc_timestamp
from .todo_normalise_tags import normalise_tags  ← from todo.normalise-tags ^1.0.0 · built alongside by fune


def require_utc_now(now: str) -> None:
    """Refuse a clock reading that would not compare as a string with the stored ones."""
    if not is_utc_timestamp(now):
        raise ValueError("now must be a UTC timestamp like 2026-09-28T09:30:00Z")


def checked_draft(draft: TodoDraft) -> TodoDraft:
    """The draft as it will be stored (trimmed, blank notes None, tags normalised), checked.

    The first field in field order that fails validate_draft is the error.
    """
    notes = "" if draft.notes is None else trim_space(draft.notes)
    clean = TodoDraft(
        title=trim_space(draft.title),
        notes=None if notes == "" else notes,
        priority=draft.priority,
        due=draft.due,
        tags=list(normalise_tags(draft.tags)),
        recurrence=draft.recurrence,
    )
    verdict = validate_draft(clean)
    for field, message in verdict.errors.items():
        raise ValueError(f"invalid todo: {field}: {message}")
    return clean


def add_todo(todos: Sequence[Todo], draft: TodoDraft, id: str, now: str) -> List[Todo]:
    """Add a todo from the add form: open, created now, last in manual order, appended.

    Caller mistakes (id, clock) are reported before form mistakes (the draft).
    """
    if trim_space(id) == "":
        raise ValueError("a todo needs an id")
    if any(t.id == id for t in todos):
        raise ValueError(f'a todo with id "{id}" already exists')
    require_utc_now(now)
    clean = checked_draft(draft)
    order = 0
    for t in todos:
        if t.order + 1 > order:
            order = t.order + 1
    todo = Todo(
        id=id,
        title=clean.title,
        notes=clean.notes,
        done=False,
        priority=clean.priority,
        due=clean.due,
        tags=clean.tags,
        recurrence=clean.recurrence,
        created_at=now,
        completed_at=None,
        order=order,
    )
    return list(todos) + [todo]