Routine: fetch_all_notes_from_server

Fetch all JSONHTL notes from a remote notes server using the keys endpoint and per-key retrieval.

Contract

Input: base_url (str) — Base URL of the notes server (e.g. http://localhost:8021).

Output: dict[str, dict] — Mapping of note key to validated JSONHTL document.

Failure mode: Raises RuntimeError or ValueError on any HTTP, parsing, or structural validation error (fail-fast, no silent recovery).

Source

import json
from urllib import request, error, parse


def fetch_all_notes_from_server(base_url):
    if not isinstance(base_url, str) or not base_url.strip():
        raise ValueError('base_url must be a non-empty string')

    base_url = base_url.rstrip('/')

    # Step 1: POST / with {"op": "keys"} to retrieve list of note keys
    payload = json.dumps({'op': 'keys'}).encode('utf-8')
    req = request.Request(
        base_url + '/',
        data=payload,
        headers={'Content-Type': 'application/json'},
        method='POST'
    )

    try:
        with request.urlopen(req) as response:
            status = getattr(response, 'status', None)
            if status is not None and status != 200:
                raise RuntimeError(f'HTTP POST / failed with status {status}')
            raw = response.read()
    except error.URLError as e:
        raise RuntimeError(f'Failed to retrieve note keys: {e}') from e

    try:
        keys = json.loads(raw.decode('utf-8'))
    except Exception as e:
        raise RuntimeError(f'Failed to parse keys response JSON: {e}') from e

    if not isinstance(keys, list):
        raise ValueError('Keys response must be a JSON list of strings')

    validated = {}

    for key in keys:
        if not isinstance(key, str) or not key:
            raise ValueError(f'Invalid note key returned by server: {key!r}')

        encoded_key = parse.quote(key, safe='')
        url = base_url + '/' + encoded_key

        try:
            with request.urlopen(url) as response:
                status = getattr(response, 'status', None)
                if status is not None and status != 200:
                    raise RuntimeError(f'HTTP GET {key!r} failed with status {status}')
                raw_doc = response.read()
        except error.URLError as e:
            raise RuntimeError(f'Failed to fetch document {key!r}: {e}') from e

        try:
            doc = json.loads(raw_doc.decode('utf-8'))
        except Exception as e:
            raise RuntimeError(f'Failed to parse JSON for document {key!r}: {e}') from e

        if not isinstance(doc, dict):
            raise ValueError(f'Document for key {key!r} is not an object')

        title = doc.get('title')
        content = doc.get('content')

        if not isinstance(title, str):
            raise ValueError(f'Document {key!r} missing valid title string')

        if not isinstance(content, list):
            raise ValueError(f'Document {key!r} missing valid content list')

        validated[key] = doc

    return validated