MemPalace/mempalace · error · ValueError

{field_name} must be a string

Error message

{field_name} must be a string

What it means

Raised by sanitize_iso_temporal() when a temporal parameter (as_of, valid_from, valid_to, ended) is not None, not the empty string, and not a str — e.g. an int timestamp or a datetime object. Temporal values are stored as canonical TEXT and compared lexicographically, so the API accepts only strings it can normalize; pass None/'' for 'no value' and format datetimes yourself.

Source

Thrown at mempalace/config.py:181

    """Validate an ISO-8601 date or canonical UTC datetime string.

    Accepts ``None`` and ``""`` as pass-through values.

    Accepted non-empty string forms:

    - ``YYYY-MM-DD``
    - ``YYYY-MM-DDTHH:MM:SSZ``
    - ``YYYY-MM-DDTHH:MM:SS+00:00`` normalized to ``...Z``

    Partial dates are rejected because KG queries compare TEXT temporal values.
    Non-canonical datetime forms are rejected because mixed temporal string
    formats can silently return wrong KG query results.
    """

    if value is None or value == "":
        return value
    if not isinstance(value, str):
        raise ValueError(f"{field_name} must be a string")

    value = value.strip()

    try:
        _validate_iso_temporal_calendar(value)
    except ValueError:
        raise ValueError(
            f"{field_name}={value!r} is not a valid ISO-8601 date or UTC datetime "
            "(expected YYYY-MM-DD or YYYY-MM-DDTHH:MM:SSZ)"
        ) from None

    if value.endswith("+00:00"):
        value = f"{value[:-6]}Z"

    return value


def sanitize_iso_date(value, field_name: str = "date"):

View on GitHub (pinned to 06cb6987f0)

Solutions

  1. Format datetimes to the canonical string yourself: dt.strftime('%Y-%m-%dT%H:%M:%SZ') (UTC) or date.isoformat()
  2. Pass None (or "") when the temporal bound is open, not 0 or false
  3. Coerce at the boundary: str(value) is not enough — it must be ISO-formatted

Example fix

# before
kg.add_fact(s, p, o, valid_from=datetime(2026, 1, 1))

# after
kg.add_fact(s, p, o, valid_from="2026-01-01T00:00:00Z")
Defensive patterns

Strategy: type-guard

Validate before calling

from datetime import datetime, timezone

# Normalize any datetime-like input before the call:
if isinstance(v, datetime):
    v = v.astimezone(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
elif not isinstance(v, (str, type(None))):
    raise SystemExit("temporal values must be ISO strings or None")

Type guard

def is_temporal_arg(value) -> bool:
    return value is None or value == "" or isinstance(value, str)

Try / catch

try:
    t = sanitize_iso_temporal(valid_from, "valid_from")
except ValueError as exc:
    if "must be a string" in str(exc):
        t = sanitize_iso_temporal(str(valid_from), "valid_from")  # only if str(v) is ISO
    else:
        raise

Prevention

When it happens

Trigger: Calling a KG API with valid_from=1723680000 (unix int), valid_from=datetime.now(), or a date object instead of the string form; only None and "" pass through unvalidated.

Common situations: Programmatic callers forwarding datetime objects from their ORM; unix timestamps from logs; JSON payloads where numbers were auto-typed.

Related errors


AI-assisted analysis of MemPalace/mempalace@06cb6987f0 (2026-08-15). Data as JSON: /api/errors/35a767fcf2153561. Report an issue: GitHub.