Skip to content

feat(supabase): add close() and aclose() to the top-level client - #2035

Merged
spydon merged 2 commits into
mainfrom
lukasklingsbo/sdk-2715-supabase-add-close-and-aclose-with
Nov 11, 2026
Merged

spydon merged 2 commits into
mainfrom
lukasklingsbo/sdk-2715-supabase-add-close-and-aclose-with

Conversation

@spydon

@spydon spydon commented Nov 11, 2026

Copy link
Copy Markdown
Contributor

Resolves SDK-2715. Supersedes SDK-1683.

Summary

The v3 client could already be used with with and async with, but leaving the block closed the HTTP session even when the caller had passed it in, and left the auth auto-refresh running. There was no way to close the client outside a with block.

Client.close() and AsyncClient.aclose() now do both steps, and __exit__ and __aexit__ call them:

  1. Stop the auth auto-refresh: the scheduled refresh and, on the async client, a refresh that is in flight.
  2. Close the HTTP session, but only one the client created. create_client and create_async_client create an HTTP client when no http_client is passed, and the supabase client now closes it. An http_client passed to a factory, or an http_session passed to a constructor, stays open and belongs to the caller.

Closing twice is safe.

Changes

supabase

  • Client.close() and AsyncClient.aclose(), used by the context managers.
  • The httpx and aiohttp factories mark the session as owned by the client when they create the HTTP client.
  • The docstrings of the factories and constructors, and the "Closing the client" section of the README, say which session gets closed.
  • Client.close and AsyncClient.aclose are registered in sdk-compliance.yaml. The capability registry has no lifecycle feature, so they go in the top-level supporting_symbols.

auth

  • The private session managers get stop_auto_refresh(), which their __exit__ and __aexit__ now call. The supabase client calls it directly, since exiting the auth client would also close the HTTP session it shares with the other services.

Behavior change

On 3.0.0-a1, with create_client(url, key, http_client=my_client) closed my_client on exit, and so did with Client(url, key, HttpxSession(client=my_client)). Both now leave it open, so callers who pass in a client should close it themselves.

Realtime

The v3 AsyncClient holds no realtime socket. Every client.realtime returns a new connect_once that the caller scopes with async with client.realtime as realtime:, so aclose() leaves realtime connections alone. The docstring and README say so.

Tests

src/supabase/tests/test_close.py covers both clients with httpx, plus the async client with aiohttp:

  • the HTTP client the factory created gets closed;
  • a passed-in http_client or http_session stays open;
  • the refresh timer and the refresh in flight are cancelled;
  • closing twice works.

@coderabbitai

coderabbitai Bot commented Nov 11, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 23 seconds.

Check out review usage here.

View limit details

Limit details: You’ve used all 8 included reviews currently available.

Learn how review limits work.

Review configuration:

Run configuration

  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 61b0a37c-1083-4561-8ed1-9f9301b8390c
Commits

Reviewing files that changed from the base of the PR and between 92d524c and fd77ff8.

Files selected for processing: 7

  • sdk-compliance.yaml
  • src/auth/src/supabase_auth/session.py
  • src/supabase/README.md
  • src/supabase/src/supabase/aiohttp.py
  • src/supabase/src/supabase/client.py
  • src/supabase/src/supabase/httpx.py
  • src/supabase/tests/test_close.py
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

@spydon
spydon merged commit 1ab137d into main Nov 11, 2026
26 checks passed
@spydon
spydon deleted the lukasklingsbo/sdk-2715-supabase-add-close-and-aclose-with branch November 11, 2026 13:13
spydon added a commit that referenced this pull request Nov 11, 2026
Resolves SDK-2977.

## What

`RealtimeClient.close()` is renamed to `aclose()`. `close()` is removed,
not deprecated, since `main` is still in alpha.

## Why

`aclose()` is the Python name for an async close, and the top-level
`AsyncClient` already uses it since #2035. With the new name,
`contextlib.aclosing(realtime_client)` works, the same as for async
generators (PEP 525) and `httpx.AsyncClient`.

## Changes

- `realtime/client.py`: renames the method and updates the call in
`connect_once.__aexit__`.
- `sdk-compliance.yaml`: lists `RealtimeClient.aclose` under
`realtime.client.disconnect`.
- `MIGRATION.md`: adds a section on the rename.
- `tests/test_close.py`: checks that `aclose()` and
`contextlib.aclosing` close the websocket.

## Breaking change

Code that calls `await realtime_client.close()` must call `await
realtime_client.aclose()` instead. Code that uses `async with
connect_once(...)` is unaffected.
spydon added a commit that referenced this pull request Nov 11, 2026
## What

Servers such as FastAPI and Django apps need one client per request,
scoped to the caller's JWT so that row level security applies. This adds
`with_token()`, a cheap and supported way to do that.

## Changes

- `SupabaseClient.with_token(access_token)` returns a client of the same
type that:
- shares the HTTP session, headers, request timeout, retry policy and
service options of the original,
- sends `Authorization: Bearer <access_token>` to postgrest, storage and
functions, and uses the token for realtime,
  - never refreshes or replaces the token,
  - does no I/O, so it is cheap to create per request (about 27 µs),
- has no auth client, like a supabase-js client created with
`accessToken`. Using its `auth` raises `SupabaseException`. Use the
original client's `auth`, for example `auth.get_claims(token)`, which
also keeps the JWKS cache across requests,
- does not close the shared HTTP session when it is closed, or when one
of its services such as `postgrest` is left with `with` or `async with`.
It sends through a private wrapper of the session whose exit does
nothing.
  - It rejects an empty token with `ValueError`.
- The auth client is still built in the constructor, so threads share
one. A `with_token()` copy drops it right after construction, and
`close()` and `aclose()` stop the auto-refresh only when an auth client
exists.
- `HttpxSession`, `AsyncHttpxSession` and `AsyncAiohttpSession` in
`supabase_utils` take a keyword-only `owns_client` (default `True`).
When it is false, leaving the session does not close the HTTP client.
The factories pass `owns_client=False` for an `http_client` given by the
caller, so leaving a service such as `postgrest` no longer closes it
either. #2035 already covered closing the top-level client.
- README: a section on the one-client-per-request pattern for servers,
and the `with_token()` case in the error tree.
- `sdk-compliance.yaml`: registers `SupabaseClient.with_token` under
`client.authentication_integration.cross_client_token_sync`.

## Not included

The `access_token` callback option, like supabase-js `accessToken`, is
`client.authentication_integration.third_party_auth` in the registry and
is tracked in SDK-111. It can reuse the auth-less client from this PR.

## Tests

`src/supabase/tests/test_with_token.py` covers:
- the headers sent by every service, and the realtime token,
- isolation from the original client, including a sign-out on it,
- the shared options,
- that `auth` raises on a `with_token()` client,
- the empty token,
- that closing or leaving a scoped client or its services keeps the
session open,
- that the factories keep a given `http_client` open (httpx sync and
async, aiohttp) and close one they created.

The adapter tests in `supabase_utils` cover `owns_client` both ways.

Closes SDK-2658
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant