postgrest: count="planned" on insert, update, delete or rpc raises ValueError

Open Beginner friendly
#1,891 0 comments 0 reactions 0 assignees View on GitHub

Maintainers usually reply within 3 days

@hsusul is already working on this.

Since Oct 30, 2026.

  • #1892 by @hsusul — open

Assessment

Difficulty
2/5
Estimated time
1-3 hours
Newbie friendliness
84/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Active
Tech stack
postgresql, python
Domain
api

Research direction

Start in src/postgrest/src/postgrest/base_request_builder.py at APIResponse._get_count_from_content_range_header (around line 252) and trace how execute() uses its result. Use the supplied MockTransport reproduction for a Content-Range: */* response; done means the call returns its data and reports count as None instead of raising a ValueError.

Written by the indexing model from the issue text.

Description

bug PostgREST python
Describe the bug

Passing count="planned" to insert(), upsert(), update(), delete() or rpc() makes execute() raise ValueError: invalid literal for int() with base 10: '*'. For the mutations, PostgREST has already committed the write by then, so the caller gets an exception for a request that succeeded.

PostgREST only computes a total for these requests with count=exact or count=estimated (shouldCount in src/PostgREST/ApiRequest/Preferences.hs). With count=planned it leaves the total unknown and sends it as * (RFC 9110 allows * for an unknown complete length):

  • insert/upsert and delete: Content-Range: */* (Response.hs, contentRangeH 1 0 Nothing)
  • update: Content-Range: 0-0/* for one row
  • rpc: Content-Range: 0-0/* (the function's count query is skipped and there is no EXPLAIN for calls)

PostgREST v11.2.2, the version in src/postgrest/infra, has the same shouldCount logic.

APIResponse._get_count_from_content_range_header (src/postgrest/src/postgrest/base_request_builder.py:252) calls int() on whatever follows the /:

content_range = content_range_header.split("/")
return None if len(content_range) < 2 else int(content_range[1])

Expected: count is None when the server reports the total as unknown, the same as when no total is sent, and data is returned.

Reproduction
from httpx import Client, MockTransport, Response
from postgrest import SyncPostgrestClient

# Stands in for PostgREST's reply to POST /todos with Prefer: count=planned
def handler(request):
    return Response(201, json=[{"id": 1}], headers={"Content-Range": "*/*"})

with Client(transport=MockTransport(handler)) as http:
    client = SyncPostgrestClient("https://example.com", http_client=http)
    client.from_("todos").insert({"id": 1}, count="planned").execute()
# ValueError: invalid literal for int() with base 10: '*'

With a real project, the same path runs on this call. I confirmed the * totals from PostgREST's source, not against a live server:

supabase.table("todos").insert({"task": "x"}, count="planned").execute()
Steps to reproduce
  1. Call any mutation or rpc() with count="planned".
  2. execute() raises ValueError. For a mutation, the row is already written.
Library affected

postgrest

Library version

postgrest 2.33.0 (main at 1ef4688)

Python version

3.13.5

Dominant language
Python
Stars
2.6k
Forks
608
Avg merge
3d 18h
Merged PRs (30d)
85

Getting set up

Open in Codespaces

Starts the project's dev container in your browser, under your own GitHub account.

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from supabase/supabase-py

All issues in supabase/supabase-py

Similar issues

More Python issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.