Repository navigation
feat(supabase): add close() and aclose() to the top-level client - #2035
Merged
spydon merged 2 commits intoNov 11, 2026
Merged
Conversation
|
Warning Review limit reachedYou'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. View limit details
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. |
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Resolves SDK-2715. Supersedes SDK-1683.
Summary
The v3 client could already be used with
withandasync 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 awithblock.Client.close()andAsyncClient.aclose()now do both steps, and__exit__and__aexit__call them:create_clientandcreate_async_clientcreate an HTTP client when nohttp_clientis passed, and the supabase client now closes it. Anhttp_clientpassed to a factory, or anhttp_sessionpassed to a constructor, stays open and belongs to the caller.Closing twice is safe.
Changes
supabase
Client.close()andAsyncClient.aclose(), used by the context managers.httpxandaiohttpfactories mark the session as owned by the client when they create the HTTP client.Client.closeandAsyncClient.acloseare registered insdk-compliance.yaml. The capability registry has no lifecycle feature, so they go in the top-levelsupporting_symbols.auth
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)closedmy_clienton exit, and so didwith 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
AsyncClientholds no realtime socket. Everyclient.realtimereturns a newconnect_oncethat the caller scopes withasync with client.realtime as realtime:, soaclose()leaves realtime connections alone. The docstring and README say so.Tests
src/supabase/tests/test_close.pycovers both clients with httpx, plus the async client with aiohttp:http_clientorhttp_sessionstays open;