can1357/oh-my-pi · error · TypeError

Host URI read handlers must return a string or a HostUriRead

Error message

Host URI read handlers must return a string or a HostUriReadResult mapping

What it means

normalize_read_result() converts a read handler's return value into the payload dict spread into a host_uri_result message. It accepts a plain string (wrapped as {'content': value}) or a dict; anything else (None, bytes, dataclass, list) raises TypeError. The library enforces this contract because the RPC wire format can only carry a known JSON shape for read results.

Source

Thrown at python/omp-rpc/src/omp_rpc/host_uris.py:104

    return HostUri(
        scheme=cleaned,
        read=read,
        write=write,
        description=description,
        immutable=immutable,
    )


def normalize_read_result(value: HostUriReadValue) -> JsonObject:
    """Convert a handler's `read` return into the wire-frame fields.

    Returns a dict suitable for spreading into a `host_uri_result` payload.
    """

    if isinstance(value, str):
        return {"content": value}
    if not isinstance(value, dict):
        raise TypeError(
            "Host URI read handlers must return a string or a HostUriReadResult mapping"
        )

    payload: JsonObject = {}
    if "content" not in value:
        raise ValueError("HostUriReadResult requires a 'content' field")
    payload["content"] = str(value["content"])

    content_type = value.get("content_type")
    if content_type is not None:
        if content_type not in ("text/markdown", "application/json", "text/plain"):
            raise ValueError(f"Unsupported content_type: {content_type!r}")
        payload["contentType"] = content_type

    notes = value.get("notes")
    if notes is not None:
        payload["notes"] = [str(item) for item in notes]

View on GitHub (pinned to 9690622007)

Solutions

  1. Return a plain string from the read handler, e.g. `return text` — it is wrapped as {'content': text} automatically
  2. If you need metadata, return a dict with at least a 'content' key: {'content': text, 'content_type': 'text/plain'}
  3. Decode bytes before returning: `return data.decode('utf-8')`

Example fix

// before
def read(path):
    with open(path, 'rb') as f:
        return f.read()  # bytes -> TypeError
// after
def read(path):
    with open(path, 'r', encoding='utf-8') as f:
        return f.read()  # str is accepted
Defensive patterns

Strategy: type-guard

Validate before calling

def check_read_result(value):
    if isinstance(value, str) or isinstance(value, dict):
        return value
    raise TypeError(f'read handler must return str or dict, got {type(value).__name__}')

Type guard

def is_valid_read_result(value) -> bool:
    return isinstance(value, (str, dict))

Try / catch

try:
    result = read_handler(uri)
except TypeError as e:
    logger.error('read handler returned unsupported type: %s', e)
    result = {'content': 'handler error'}

Prevention

When it happens

Trigger: Registering a read handler with host_uri(read=...) whose function returns None implicitly, returns bytes from a file read, returns a custom result object/dataclass, or a handler written with an async return (returning a coroutine) that was awaited incorrectly.

Common situations: Handler does `return` with no value on an empty file path; handler returns bytes from `f.read()` on binary data; handler wraps the result in a custom class; upgrading code that previously returned None and printed instead.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/4a45484d294bcb92. Report an issue: GitHub.