redis/redis-py · error · DataError

block_min_count requires block_milliseconds to be set; the…

Error message

block_min_count requires block_milliseconds to be set; the BLOCK group is all-or-nothing.

What it means

Raised by _append_block (used by TS.RANGE/XRANGE-style blocking reads) when block_min_count is set but block_milliseconds is None. The BLOCK clause is all-or-nothing on the wire: blocking requires the milliseconds value, and min_count alone is not a valid standalone keyword.

Solutions

  1. Always set block_milliseconds when you set block_min_count.
  2. If you do not want blocking, omit both block_milliseconds and block_min_count.
  3. Validate the pair together in your wrapper before calling the API.

Example fix

// before
client.ts().range(key, f, t,
    block_milliseconds=None, block_min_count=5)
// after
client.ts().range(key, f, t,
    block_milliseconds=1000, block_min_count=5)
# or non-blocking: omit both
Defensive patterns

Strategy: validation

Validate before calling

if block_min_count is not None and block_milliseconds is None:
    raise ValueError('block_milliseconds required when block_min_count is set')
client.ts().range(key, f, t, block_milliseconds=block_milliseconds, block_min_count=block_min_count)

Try / catch

from redis.exceptions import DataError
try:
    client.ts().range(key, f, t, block_milliseconds=block_milliseconds, block_min_count=block_min_count)
except DataError:
    client.ts().range(key, f, t)  # fall back to non-blocking

Prevention

When it happens

Trigger: Calling a range read with block_min_count=5 but block_milliseconds=None (omitted). Passing only the min_count half of a blocking pair.

Common situations: Config objects that map block_min_count without block_milliseconds. Partial migration where one parameter was renamed or dropped.

Related errors


AI-assisted analysis of redis/redis-py@6a6b581b48 (2026-08-10). Data as JSON: /api/errors/a52eef52d1e94e6d. Report an issue: GitHub.

Appendix: source

Thrown at redis/commands/timeseries/commands.py:1999

        if count is not None:
            params.extend(["COUNT", count])

    @staticmethod
    def _append_block(
        params: list[EncodableT],
        block_milliseconds: int | None,
        block_min_count: int | None,
    ):
        """Append the BLOCK group to params.

        The BLOCK group is all-or-nothing: when blocking is requested
        (`block_milliseconds` is set), both `milliseconds` and `min_count` are
        always emitted, with `min_count` defaulting to 1. There is no standalone
        MIN_COUNT keyword in this command.
        """
        if block_milliseconds is None:
            if block_min_count is not None:
                raise DataError(
                    "block_min_count requires block_milliseconds to be set; the "
                    "BLOCK group is all-or-nothing."
                )
            return
        min_count = 1 if block_min_count is None else block_min_count
        params.extend(["BLOCK", block_milliseconds, min_count])

    @staticmethod
    def _append_max_count(params: list[EncodableT], max_count: int | None):
        """Append MAX_COUNT property to params."""
        if max_count is not None:
            params.extend(["MAX_COUNT", max_count])

    @staticmethod
    def _append_timestamp(params: list[EncodableT], timestamp: int | None):
        """Append TIMESTAMP property to params."""
        if timestamp is not None:
            params.extend(["TIMESTAMP", timestamp])

View on GitHub (pinned to 6a6b581b48)