chroma-core/chroma · error · TypeError

Expected dict for Limit, got {type(data).__name__}

Error message

Expected dict for Limit, got {type(data).__name__}

What it means

Limit.from_dict deserializes a pagination spec and only accepts a Python dict with 'offset'/'limit' keys. This TypeError is the first guard: any non-dict (list, str, int, None, tuple) is rejected before field validation runs. It is reached from Search(limit={...}) (chromadb/execution/expression/plan.py:138) and from code that round-trips a serialized Search payload.

Source

Thrown at chromadb/execution/expression/operator.py:567

    def to_dict(self) -> Dict[str, Any]:
        """Convert the Limit to a dictionary for JSON serialization"""
        result = {"offset": self.offset}
        if self.limit is not None:
            result["limit"] = self.limit
        return result

    @staticmethod
    def from_dict(data: Dict[str, Any]) -> "Limit":
        """Create Limit from dictionary.

        Examples:
        - {"offset": 10} -> Limit(offset=10)
        - {"offset": 10, "limit": 20} -> Limit(offset=10, limit=20)
        - {"limit": 20} -> Limit(offset=0, limit=20)
        """
        if not isinstance(data, dict):
            raise TypeError(f"Expected dict for Limit, got {type(data).__name__}")

        offset = data.get("offset", 0)
        if not isinstance(offset, int):
            raise TypeError(
                f"Limit offset must be an integer, got {type(offset).__name__}"
            )
        if offset < 0:
            raise ValueError(f"Limit offset must be non-negative, got {offset}")

        limit = data.get("limit")
        if limit is not None:
            if not isinstance(limit, int):
                raise TypeError(
                    f"Limit limit must be an integer, got {type(limit).__name__}"
                )
            if limit <= 0:
                raise ValueError(f"Limit limit must be positive, got {limit}")

View on GitHub (pinned to aecdd12c8a)

Solutions

  1. Pass the value to Search() instead - Search(limit=20) accepts an int and builds Limit.from_dict({'limit': 20, 'offset': 0}) for you (plan.py:135-136).
  2. Wrap scalars before calling from_dict: Limit.from_dict({'limit': value}).
  3. If the input comes from JSON/YAML, check isinstance(data, dict) first and return a clear 400-style error to the caller.

Example fix

# before
Limit.from_dict(cfg['limit'])          # cfg['limit'] == 20 -> TypeError

# after
Search(limit=cfg['limit'])             # int handled by Search
# or
Limit.from_dict({'limit': cfg['limit']})
Defensive patterns

Strategy: type-guard

Validate before calling

def as_limit_dict(data):
    '''Coerce common shapes into a Limit.from_dict-compatible dict.'''
    if isinstance(data, bool):
        raise TypeError('bool is not a valid limit payload')
    if isinstance(data, int):
        return {'limit': data}
    if not isinstance(data, dict):
        raise TypeError(f'limit payload must be a dict or int, got {type(data).__name__}')
    return data

Search(limit=as_limit_dict(cfg['limit']))

Type guard

def is_limit_payload(data) -> bool:
    return isinstance(data, dict) and set(data) <= {'offset', 'limit'}

Try / catch

try:
    limit = Limit.from_dict(raw)
except TypeError as e:
    raise ValueError(f'invalid limit payload: {e}') from e

Prevention

When it happens

Trigger: Calling Limit.from_dict(20), Limit.from_dict('20'), or Limit.from_dict([10, 20]) directly; feeding a JSON-decoded value whose limit member is a bare scalar or array; deserializing a stored Search payload where the limit field lost its object shape.

Common situations: Porting from the classic collection.query(n_results=10) API and feeding the int into Limit.from_dict; YAML/JSON configs storing limit as a scalar or list; generic parse-then-build pipelines that assume from_dict accepts ints because Search(limit=...) does.

Related errors


AI-assisted analysis of chroma-core/chroma@aecdd12c8a (2026-08-16). Data as JSON: /api/errors/9451f6659a92c93b. Report an issue: GitHub.