fix(py): restore server(data_source=) for Shiny/R parity - #301
Merged
Conversation
cpsievert
added a commit
that referenced
this pull request
Sep 12, 2026
Copilot review of #301 found that replacing a table via server(data_source=...) always cleaned up the table it replaced and invalidated the shared query executor -- correct for a config-time add_table(replace=True), but for the per-session registration path this tore down a still-in-use resource out from under an earlier, already- running session (e.g. disposing its SQLAlchemy engine or closing its DuckDB connection). _add_or_replace_table() gains cleanup_replaced=False for this path, so the caller (not querychat) remains responsible for closing a replaced per-session data source, e.g. via session.on_ended(). Separately, greeting generation runs asynchronously after .server() returns, and read the shared, mutable greeter.tables/_data_sources at that later point -- so a second session's registration could clobber an earlier session's in-flight greeting. QueryChatGreeter gains a private _generate_async_snapshot() that mod_server() feeds an explicit tables/data_sources snapshot captured synchronously at .server()-call time, leaving the public greeter API unchanged.
This comment was marked as outdated.
This comment was marked as outdated.
Contributor
There was a problem hiding this comment.
🟡 Changes recommended
Per-session registration can close an executor still used by an earlier session; two naming nits also remain.
Get a fresh assessment by requesting another Copilot review.
Review details
Suppressed comments (2)
pkg-py/src/querychat/_querychat_base.py:474
- This new helper is defined in
_querychat_base.py, which is already a private module, so the leading underscore is redundant and violates the package's Python naming convention. Rename it toadd_or_replace_tableand update its callers.
def _add_or_replace_table(
pkg-py/src/querychat/_querychat_greeter.py:74
- This new method is defined in
_querychat_greeter.py, which is already a private module, so the leading underscore is redundant and violates the package's Python naming convention. Rename it togenerate_async_snapshotand update its callers.
async def _generate_async_snapshot(
- Files reviewed: 8/8 changed files
- Comments generated: 1
- Review effort level: Lite
Python's QueryChat.server() lost its deferred data_source registration in the multi-table redesign (#195), forcing per-session data sources (e.g. per-user OAuth-scoped connections on Posit Connect) to require a second full QueryChat instance kept manually in sync with the UI-only one. Restores server(data_source=, table_name=), registering via the existing add_table(replace=True, include_in_greeting=True) -- matching R's current $server(data_source=) behavior exactly, guard included: a second session's call still raises, same as R today. Making per-session registration actually survive a second session is a separate follow-up.
cpsievert
force-pushed
the
fix/py-server-data-source-parity
branch
from
September 12, 2026 01:18
ddc2306 to
3b30ec0
Compare
Contributor
There was a problem hiding this comment.
🔵 Needs a closer look
Broaden the server(data_source=...) annotation to include supported BaseBoard sources.
Review details
Suppressed comments (1)
pkg-py/src/querychat/_shiny.py:636
- This new annotation is narrower than the data-source contract used by the rest of the API:
QueryChatBase.add_table()acceptspins.boards.BaseBoard(_querychat_base.py:456-458), and the public docs showQueryChat(board, "my_pin")(docs/data-sources.qmd:341-354), but a typed deferred call such asqc.server(data_source=board, table_name="my_pin")is rejected by type checkers even thoughnormalize_data_source()supports it at runtime. Include the same supported source type (ideally via a shared input alias) in the newserver()annotation.
data_source: IntoFrame | sqlalchemy.Engine | ibis.Table | None = None,
- Files reviewed: 4/4 changed files
- Comments generated: 0 new
- Review effort level: Lite
cpsievert
added this pull request to stack #307
September 12, 2026 16:09
This was referenced Sep 12, 2026
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.
Part 1 of 4
This is the first PR in a stack restoring safe per-session data source registration:
server(data_source=)Each PR is reviewable independently; #302-#304 build on this one.
The problem
Some deployments can only create a table's data source inside the Shiny server function — for example, on Posit Connect, a per-user database connection depends on that user's session-scoped OAuth credentials, which don't exist until the server function runs. querychat supports this "deferred" pattern: construct
QueryChatwithout a data source, then register one per session via.server(data_source=...).Python's
server()used to acceptdata_source=for exactly this, but it was dropped in a multi-table redesign (#195), so this pattern currently requires building a second, fully-duplicateQueryChatinstance per session, kept manually in sync with a UI-only global one:The fix
QueryChat.server()gainsdata_sourceandtable_name:table_nameis optional — it falls back to the name given to the constructor, or to the first already-registered table, matching R's equivalent behavior.add_table(replace=True, include_in_greeting=True)machinery.This PR intentionally matches R's current
$server(data_source = )behavior exactly, guard included: a second session's call still raisesRuntimeError("Cannot add tables after server initialization."), same as R does today. That's a real limitation — R's version has never actually worked for more than one session either — but it's a separate, focused problem from "restore the parameter," and is fixed in #302.Test plan
table_nameoverride, falling back to the first registered table, a clear error when no name can be inferred, and greeting inclusion.make py-check-format,make py-check-types,make py-check-testsall pass.Part of #300.