Repository navigation
feat: Db settings and connections editing - #492
Conversation
Signed-off-by: Ilya Boyandin <[email protected]>
Signed-off-by: Ilya Boyandin <[email protected]>
Signed-off-by: Ilya Boyandin <[email protected]>
Signed-off-by: Ilya Boyandin <[email protected]>
Signed-off-by: Ilya Boyandin <[email protected]>
Signed-off-by: Ilya Boyandin <[email protected]>
Signed-off-by: Ilya Boyandin <[email protected]>
Signed-off-by: Ilya Boyandin <[email protected]>
Signed-off-by: Ilya Boyandin <[email protected]>
Signed-off-by: Ilya Boyandin <[email protected]>
📝 WalkthroughWalkthroughA new Changes
Sequence Diagram(s)sequenceDiagram
participant User as Frontend User
participant UI as DbSettings UI
participant Store as Zustand Store
participant Server as Sqlrooms HTTP Server
participant Bridge as Python DB Bridge
User->>UI: Open Settings Modal
UI->>Store: read connections, diagnostics, engineConfigFields
Store-->>UI: state snapshot
User->>UI: Add/Edit connection
UI->>Store: upsertConnection(...)
Store->>Store: mark unsaved
User->>UI: Test connection
UI->>Store: testConnection(engine, config)
Store->>Server: POST /api/db/test-connection {engine, config}
Server->>Bridge: build_ephemeral_connector(engine, config)
Bridge->>Bridge: connector.test_connection()
Bridge-->>Server: result
Server-->>Store: {ok, error}
Store-->>UI: display result
User->>UI: Save
UI->>Store: saveToServer()
Store->>Server: PUT /api/db/settings {connections}
Server->>Server: _write_db_connectors_to_toml(config_path,...)
Server-->>Store: success/failure
Store-->>UI: toast
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
Signed-off-by: Ilya Boyandin <[email protected]>
There was a problem hiding this comment.
Actionable comments posted: 9
🧹 Nitpick comments (2)
python/sqlrooms-cli/tests/test_cli.py (1)
54-57: Add an assertion forportin the parsed Postgres config.The fixture now provides
port, but the test does not validate it. This leaves a gap in the new field-based parsing contract.✅ Suggested test assertion
assert data[0].host == "localhost" + assert data[0].port == "5432" assert data[0].database == "db" assert data[0].user == "u" assert data[0].password == "p"🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@python/sqlrooms-cli/tests/test_cli.py` around lines 54 - 57, Add an assertion that the parsed Postgres config includes the port value by checking data[0].port equals the expected port from the test fixture (e.g., assert data[0].port == <expected_port>); update the assertion alongside the existing checks for data[0].host, data[0].database, data[0].user, and data[0].password so the test validates the new field-based parsing contract for the Postgres config.packages/sql-editor/src/components/SchemaExplorer.tsx (1)
49-54: Potential re-render issue with inline fallback array.The
?? []fallback on Line 52 creates a new array reference each render whenschemaTreesis undefined, which may cause unnecessary re-renders ofTableSchemaTree.♻️ Option: Use a stable empty array constant
+const EMPTY_SCHEMA_TREES: never[] = []; + function SchemaExplorerTree({className}: SchemaExplorerTreeProps) { const schemaTrees = useStoreWithSqlEditor((s) => s.db.schemaTrees); return ( - <TableSchemaTree schemaTrees={schemaTrees ?? []} className={className} /> + <TableSchemaTree schemaTrees={schemaTrees ?? EMPTY_SCHEMA_TREES} className={className} /> ); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/sql-editor/src/components/SchemaExplorer.tsx` around lines 49 - 54, The fallback "?? []" in SchemaExplorerTree creates a new array each render causing TableSchemaTree to rerender; fix by returning a stable empty array instead of a new literal when schemaTrees is undefined—e.g., define a shared constant (EMPTY_SCHEMA_TREES) or memoize the fallback and pass schemaTrees ?? EMPTY_SCHEMA_TREES to TableSchemaTree; adjust SchemaExplorerTree (and any related props) so TableSchemaTree receives a stable reference to avoid unnecessary re-renders.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/db-settings/src/components/DbConnectionForm.tsx`:
- Around line 70-85: The effect that captures originalRef only on the first
truthy editing causes stale originals when the parent reuses the form for a
different connection; update the useEffect watching editing so that when editing
becomes truthy you set originalRef.current = editing if originalRef.current is
unset OR originalRef.current.id !== editing.id (i.e., capture the new record),
and when editing is falsy clear originalRef.current and reset
hasInitializedRef.current to false; apply the same guard/clear logic to the
other initialization block referenced (the code around lines 128-133) so
handleCancel() will restore the correct connection.
In `@packages/db-settings/src/components/DbSettings.tsx`:
- Around line 30-38: The settings trigger Button in DbSettings (the Button
element that forwards ref and renders <SettingsIcon />) is missing an accessible
name; add an aria-label (or aria-labelledby referencing a nearby visually-hidden
label) to the Button props so screen readers announce its purpose (e.g.,
aria-label="Database settings" or similar), and ensure the label text is
localized/unique if needed; keep the visual icon-only appearance unchanged and
apply the attribute on the Button element that renders <SettingsIcon />.
In `@packages/db-settings/src/DbSettingsSlice.ts`:
- Around line 212-230: The loop that evicts stale connections only checks
whether an existing connection is in desiredIds, but does not remove entries
whose diagnostics flipped to unavailable; update the first loop over
existingConnections (where existing.isCore is checked) to also look up
diagnosticsByKey using diagnosticsKey(existing.id, existing.engineId) and call
state.db.connectors.removeConnection(existing.id) when the diagnostic exists and
diag.available === false, so unavailable connections are removed even if their
id is still in desiredIds.
- Around line 117-148: The code captures the pre-request connections in
saveToServer but later rebuilds savedSnapshot from the live store, which can
fold in in-flight edits; change the success branch to compute savedSnapshot from
the captured connections variable (use savedSnapshot =
connectionsSnapshot(connections)) and then set
draft.dbSettings.hasUnsavedChanges based on comparing the current
draft.dbSettings.config.connections to that captured snapshot (or set to false
only when they match) so in-flight edits are not incorrectly marked saved;
update references in the success block where savedSnapshot and
draft.dbSettings.hasUnsavedChanges are set.
In `@python/sqlrooms-cli/sqlrooms/web/db_bridge/connectors/postgres.py`:
- Around line 20-22: The resolve_dsn method currently interpolates self.password
directly into the DSN which breaks when the password contains characters like @,
:, /, or %; update resolve_dsn to URL-encode the password (use
urllib.parse.quote) before inserting it into the DSN string, preserving the
existing conditional (pw = f":{encoded_pw}" if self.password else "") and
keeping the rest of the returned
f"postgresql://{self.user}{pw}@{self.host}:{self.port}/{self.database}" intact.
In `@python/sqlrooms-cli/sqlrooms/web/db_bridge/registry.py`:
- Around line 42-44: The code copies connector runtime configs directly via
conn.config_dict() into entry["config"], exposing secrets like password; update
the assignment to sanitize or filter out sensitive keys (e.g., "password",
"secret", "api_key", "token") before storing in entry["config"]—either call a
redact helper (e.g., redact_config(config)) or create a sanitized dict by
removing or masking those keys from the dict returned by conn.config_dict() so
no credentials are sent to the frontend.
In `@python/sqlrooms-cli/sqlrooms/web/launcher.py`:
- Around line 359-388: After successfully writing TOML in put_db_settings (after
_write_db_connectors_to_toml(self.config_path, connections)), reload the runtime
registry so get_db_settings and the connectionId endpoints see the new settings:
either call a reload method on self.db_bridge_registry (e.g.
self.db_bridge_registry.reload_from_config(self.config_path)) or reinitialize it
from the config file (e.g. self.db_bridge_registry =
DbBridgeRegistry(self.config_path) or equivalent), ensuring the refreshed
registry replaces the old one before returning the response; alternatively,
modify get_db_settings to read directly from self.config_path when present
instead of relying on the one-time-built self.db_bridge_registry.
- Around line 77-99: The code currently overlays only non-empty engine_config
fields onto base, so blanking a field in the form leaves the old value in base;
update the loop that processes engine_config (the variable engine_config from
conn.get("config")) to treat explicit empty/blank values as removals: for each
ck, cv in engine_config.items(), if cv is None or cv == "" then remove ck from
base if present (instead of skipping), otherwise set base[ck] = cv; this ensures
connectors_aot built from base no longer retains stale values after a user
clears a field.
In `@python/sqlrooms-cli/tests/test_launcher.py`:
- Around line 105-110: The runtime_connections() response currently includes raw
connector config_dict() values (from PostgresConnector.config_dict and
SnowflakeConnector.to_connect_kwargs), which can leak secrets like "password";
modify registry.py::runtime_connections to apply an explicit allowlist of
non-sensitive keys (e.g., host, port, database, user, maybe account) and filter
each connector.config_dict() through that allowlist before adding it to the API
payload, ensuring any "password" or other secret keys are omitted; then update
the tests in tests/test_launcher.py to assert that sensitive fields (e.g.,
"password") are not present in the returned config objects for connectors.
---
Nitpick comments:
In `@packages/sql-editor/src/components/SchemaExplorer.tsx`:
- Around line 49-54: The fallback "?? []" in SchemaExplorerTree creates a new
array each render causing TableSchemaTree to rerender; fix by returning a stable
empty array instead of a new literal when schemaTrees is undefined—e.g., define
a shared constant (EMPTY_SCHEMA_TREES) or memoize the fallback and pass
schemaTrees ?? EMPTY_SCHEMA_TREES to TableSchemaTree; adjust SchemaExplorerTree
(and any related props) so TableSchemaTree receives a stable reference to avoid
unnecessary re-renders.
In `@python/sqlrooms-cli/tests/test_cli.py`:
- Around line 54-57: Add an assertion that the parsed Postgres config includes
the port value by checking data[0].port equals the expected port from the test
fixture (e.g., assert data[0].port == <expected_port>); update the assertion
alongside the existing checks for data[0].host, data[0].database, data[0].user,
and data[0].password so the test validates the new field-based parsing contract
for the Postgres config.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: bbc216bd-e2ad-44ad-bc19-5100c3592e9d
⛔ Files ignored due to path filters (2)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yamlpython/uv.lockis excluded by!**/*.lock
📒 Files selected for processing (48)
apps/sqlrooms-cli-ui/package.jsonapps/sqlrooms-cli-ui/src/components/ConnectorDriversDialog.tsxapps/sqlrooms-cli-ui/src/components/DataSourcesPanel.tsxapps/sqlrooms-cli-ui/src/runtimeConfig.tsapps/sqlrooms-cli-ui/src/store.tsexamples/ai-rag/src/components/DataSourcesPanel.tsxexamples/ai/src/components/DataSourcesPanel.tsxexamples/canvas-sync/src/DataSourcesPanel.tsxexamples/canvas/src/DataSourcesPanel.tsxexamples/deckgl/src/components/DataPanel.tsxexamples/kepler/src/components/DataPanel.tsxexamples/notebook/src/DataSourcesPanel.tsxexamples/query-motherduck/src/DataPanel.tsxexamples/query-pwa/src/DataPanel.tsxexamples/query-websocket/src/DataPanel.tsxexamples/query/src/DataPanel.tsxpackages/db-settings/README.mdpackages/db-settings/eslint.config.jspackages/db-settings/package.jsonpackages/db-settings/src/DbSettingsSlice.tspackages/db-settings/src/DbSettingsSliceConfig.tspackages/db-settings/src/components/ConnectorDriversDiagnostics.tsxpackages/db-settings/src/components/DbConnectionForm.tsxpackages/db-settings/src/components/DbConnectionsList.tsxpackages/db-settings/src/components/DbSettings.tsxpackages/db-settings/src/index.tspackages/db-settings/tsconfig.jsonpackages/db-settings/typedoc.jspackages/db/src/index.tspackages/db/src/types.tspackages/schema-tree/src/TableSchemaTree.tsxpackages/schema-tree/src/nodes/RefreshButton.tsxpackages/sql-editor/src/components/SchemaExplorer.tsxpackages/sql-editor/src/components/TableStructurePanel.tsxpackages/sql-editor/src/index.tspython/sqlrooms-cli/README.mdpython/sqlrooms-cli/pyproject.tomlpython/sqlrooms-cli/sqlrooms/cli.pypython/sqlrooms-cli/sqlrooms/web/db_bridge/__init__.pypython/sqlrooms-cli/sqlrooms/web/db_bridge/connectors/postgres.pypython/sqlrooms-cli/sqlrooms/web/db_bridge/connectors/snowflake.pypython/sqlrooms-cli/sqlrooms/web/db_bridge/factory.pypython/sqlrooms-cli/sqlrooms/web/db_bridge/registry.pypython/sqlrooms-cli/sqlrooms/web/db_bridge/types.pypython/sqlrooms-cli/sqlrooms/web/launcher.pypython/sqlrooms-cli/tests/test_cli.pypython/sqlrooms-cli/tests/test_db_bridge.pypython/sqlrooms-cli/tests/test_launcher.py
💤 Files with no reviewable changes (1)
- apps/sqlrooms-cli-ui/src/components/ConnectorDriversDialog.tsx
| React.useEffect(() => { | ||
| if (editing) { | ||
| suppressSyncRef.current = true; | ||
| setEngineId(editing.engineId); | ||
| setConnectionId(editing.id); | ||
| setTitle(editing.title); | ||
| setBridgeId(editing.bridgeId ?? ''); | ||
| setConfig(editing.config ?? {}); | ||
| if (!hasInitializedRef.current) { | ||
| originalRef.current = editing; | ||
| hasInitializedRef.current = true; | ||
| } | ||
| } else { | ||
| hasInitializedRef.current = false; | ||
| } | ||
| }, [editing]); |
There was a problem hiding this comment.
Reset originalRef when editing switches to a different connection.
originalRef is only captured for the first truthy editing value. If the parent reuses this form for connection B after connection A, handleCancel() restores A and overwrites the wrong record.
Suggested fix
- const originalRef = React.useRef(editing);
- const suppressSyncRef = React.useRef(false);
- const hasInitializedRef = React.useRef(false);
+ const originalRef = React.useRef(editing);
+ const suppressSyncRef = React.useRef(false);
+ const lastEditingIdRef = React.useRef(editing?.id);
React.useEffect(() => {
if (editing) {
suppressSyncRef.current = true;
+ if (lastEditingIdRef.current !== editing.id) {
+ originalRef.current = editing;
+ lastEditingIdRef.current = editing.id;
+ }
setEngineId(editing.engineId);
setConnectionId(editing.id);
setTitle(editing.title);
setBridgeId(editing.bridgeId ?? '');
setConfig(editing.config ?? {});
- if (!hasInitializedRef.current) {
- originalRef.current = editing;
- hasInitializedRef.current = true;
- }
} else {
- hasInitializedRef.current = false;
+ originalRef.current = undefined;
+ lastEditingIdRef.current = undefined;
}
}, [editing]);Also applies to: 128-133
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/db-settings/src/components/DbConnectionForm.tsx` around lines 70 -
85, The effect that captures originalRef only on the first truthy editing causes
stale originals when the parent reuses the form for a different connection;
update the useEffect watching editing so that when editing becomes truthy you
set originalRef.current = editing if originalRef.current is unset OR
originalRef.current.id !== editing.id (i.e., capture the new record), and when
editing is falsy clear originalRef.current and reset hasInitializedRef.current
to false; apply the same guard/clear logic to the other initialization block
referenced (the code around lines 128-133) so handleCancel() will restore the
correct connection.
| @app.get("/api/db/settings") | ||
| async def get_db_settings(): | ||
| connections = self.db_bridge_registry.runtime_connections() | ||
| diagnostics = self.db_bridge_registry.runtime_diagnostics() | ||
| return { | ||
| "connections": connections, | ||
| "diagnostics": diagnostics, | ||
| "supportedEngines": SUPPORTED_ENGINES, | ||
| "engineConfigFields": ENGINE_CONFIG_FIELDS, | ||
| } | ||
|
|
||
| @app.put("/api/db/settings") | ||
| async def put_db_settings(payload: Dict[str, Any]): | ||
| if self.config_path is None: | ||
| return JSONResponse( | ||
| {"error": "No config file available (started with --no-config or no config found)."}, | ||
| status_code=400, | ||
| ) | ||
| connections = payload.get("connections") | ||
| if not isinstance(connections, list): | ||
| return JSONResponse( | ||
| {"error": "'connections' must be an array."}, | ||
| status_code=400, | ||
| ) | ||
| try: | ||
| _write_db_connectors_to_toml(self.config_path, connections) | ||
| except Exception as exc: | ||
| logger.error("Failed to write db settings to %s: %s", self.config_path, exc) | ||
| return JSONResponse({"error": str(exc)}, status_code=500) | ||
| return {"ok": True, "configPath": str(self.config_path)} |
There was a problem hiding this comment.
/api/db/settings stays stale after a successful save.
put_db_settings() only writes the TOML file. get_db_settings() still reads self.db_bridge_registry, and that registry is only built once in __init__. A refresh right after saving will reload the old connection list, and the connectionId-based runtime endpoints keep using stale settings until the process restarts.
Either rebuild self.db_bridge_registry after _write_db_connectors_to_toml(...), or have the settings read path load from self.config_path when it is present.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@python/sqlrooms-cli/sqlrooms/web/launcher.py` around lines 359 - 388, After
successfully writing TOML in put_db_settings (after
_write_db_connectors_to_toml(self.config_path, connections)), reload the runtime
registry so get_db_settings and the connectionId endpoints see the new settings:
either call a reload method on self.db_bridge_registry (e.g.
self.db_bridge_registry.reload_from_config(self.config_path)) or reinitialize it
from the config file (e.g. self.db_bridge_registry =
DbBridgeRegistry(self.config_path) or equivalent), ensuring the refreshed
registry replaces the old one before returning the response; alternatively,
modify get_db_settings to read directly from self.config_path when present
instead of relying on the one-time-built self.db_bridge_registry.
| "config": { | ||
| "host": "localhost", | ||
| "port": "5432", | ||
| "database": "example", | ||
| "user": "u", | ||
| }, |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# 1) Locate serializer and API response assembly paths.
fd -t f 'launcher.py|registry.py|postgres.py|snowflake.py|factory.py|types.py' python/sqlrooms-cli/sqlrooms/web
# 2) Inspect where config payload is built and whether secrets are filtered.
rg -n -C3 'config_dict|dbBridge|connections|/api/config|password|private_key|token|secret' python/sqlrooms-cli/sqlrooms/web
# 3) Inspect connector settings models for sensitive fields.
rg -n -C3 'class .*ConnectorSettings|password|private_key|token|secret' python/sqlrooms-cli/sqlrooms/web/db_bridge/connectorsRepository: sqlrooms/sqlrooms
Length of output: 19693
🏁 Script executed:
cat -n python/sqlrooms-cli/tests/test_launcher.py | sed -n '100,115p'Repository: sqlrooms/sqlrooms
Length of output: 638
🏁 Script executed:
cat -n python/sqlrooms-cli/tests/test_launcher.py | sed -n '145,160p'Repository: sqlrooms/sqlrooms
Length of output: 775
🏁 Script executed:
cat -n python/sqlrooms-cli/sqlrooms/web/db_bridge/connectors/snowflake.py | head -80Repository: sqlrooms/sqlrooms
Length of output: 2874
🏁 Script executed:
cat -n python/sqlrooms-cli/tests/test_launcher.py | head -80Repository: sqlrooms/sqlrooms
Length of output: 2866
🏁 Script executed:
rg -n 'password|test_with_postgres|test_with_snowflake' python/sqlrooms-cli/tests/test_launcher.py | head -20Repository: sqlrooms/sqlrooms
Length of output: 43
🏁 Script executed:
cat -n python/sqlrooms-cli/tests/test_launcher.py | sed -n '80,160p'Repository: sqlrooms/sqlrooms
Length of output: 3258
Credentials leak through /api/config in raw config objects.
Both PostgresConnector.config_dict() and SnowflakeConnector.config_dict() include the password field if set in connector settings. The /api/config endpoint returns these values without filtering:
postgres.py(line 38–44): iterates("host", "port", "database", "user", "password")and includes any non-empty valuesnowflake.py(line 36–51):to_connect_kwargs()iterates similar fields includingpassword
The test assertions at lines 105–110 and 149–152 pass only because the test fixtures don't set passwords. If a connector is configured with a password, it will be exposed in the API response and logs.
Add an explicit allowlist in registry.py runtime_connections() to exclude sensitive fields before including config_dict() in the response, and update assertions to verify secret fields are not present.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@python/sqlrooms-cli/tests/test_launcher.py` around lines 105 - 110, The
runtime_connections() response currently includes raw connector config_dict()
values (from PostgresConnector.config_dict and
SnowflakeConnector.to_connect_kwargs), which can leak secrets like "password";
modify registry.py::runtime_connections to apply an explicit allowlist of
non-sensitive keys (e.g., host, port, database, user, maybe account) and filter
each connector.config_dict() through that allowlist before adding it to the API
payload, ensuring any "password" or other secret keys are omitted; then update
the tests in tests/test_launcher.py to assert that sensitive fields (e.g.,
"password") are not present in the returned config objects for connectors.
Signed-off-by: Ilya Boyandin <[email protected]>
There was a problem hiding this comment.
Actionable comments posted: 3
♻️ Duplicate comments (1)
python/sqlrooms-cli/sqlrooms/web/launcher.py (1)
385-392:⚠️ Potential issue | 🟠 MajorReload the bridge registry after a successful save.
This persists the TOML file, but
get_db_settings()and theconnectionId-based endpoints still readself.db_bridge_registry, which was built once in__init__. After saving, a refresh keeps showing the old connections until restart.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@python/sqlrooms-cli/sqlrooms/web/launcher.py` around lines 385 - 392, The handler currently writes the TOML via _write_db_connectors_to_toml but does not update the in-memory registry built in __init__, so get_db_settings() and connectionId-based endpoints keep using the stale self.db_bridge_registry; after a successful write (the block that returns {"ok": True, "configPath": ...}), reload or rebuild self.db_bridge_registry (e.g., call the registry-loading method used in __init__ or a dedicated refresh method) so the in-memory registry reflects the newly saved connections and subsequent get_db_settings()/connectionId endpoints return the updated data.
🧹 Nitpick comments (2)
packages/db-settings/src/components/DbConnectionForm.tsx (2)
147-162: Consider guarding against state updates on unmounted component.If the user closes the form while a connection test is in-flight,
setTestStateandsetTestErrorwill be called on an unmounted component. While React 18+ no longer warns about this, the state update is still wasted work.Optional: Add cleanup with AbortController or mounted flag
+ const mountedRef = React.useRef(true); + React.useEffect(() => { + return () => { mountedRef.current = false; }; + }, []); + const handleTestConnection = async () => { if (!engineId) return; setTestState('testing'); setTestError(''); const cleanConfig: Record<string, string> = {}; for (const [k, v] of Object.entries(config)) { if (v) cleanConfig[k] = v; } const result = await testConnection(engineId, cleanConfig); + if (!mountedRef.current) return; if (result.ok) { setTestState('success');🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/db-settings/src/components/DbConnectionForm.tsx` around lines 147 - 162, The handleTestConnection function can call setTestState/setTestError after the component unmounts; add a mounted guard or AbortController to prevent state updates when unmounted: create a ref/flag (e.g., isMounted) or an AbortController in the component, set it true on mount and false/abort on cleanup, then check the flag or signal before calling setTestState/setTestError (and pass the signal to testConnection if using AbortController) so any in-flight testConnection results are ignored when the form unmounts.
91-111: The dependency suppression is acceptable but warrants a note.The
eslint-disableforreact-hooks/exhaustive-depsis intentional here—editingtriggers the sync effect above (lines 70-89) which setssuppressSyncRef, and including it would create a loop. The inline comment explains the pattern well.Consider adding
upsertConnectionto the dependency array since store actions are typically stable references and it makes the intent clearer, though this is a minor nitpick.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/db-settings/src/components/DbConnectionForm.tsx` around lines 91 - 111, The effect that auto-syncs form edits into the store (uses isEditing, suppressSyncRef, buildConnection) currently disables exhaustive-deps; add upsertConnection to the dependency array so the effect explicitly depends on the store action (upsertConnection) while retaining the existing suppression behavior for editing via suppressSyncRef to avoid the prop/state loop.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@python/sqlrooms-cli/sqlrooms/cli.py`:
- Line 414: The current call passes the loaded config_path through
(config_path=config_path) which lets fresh installs treat absence of a file the
same as an explicit opt-out; change the logic so you preserve two distinct
values: one "loaded_config_path" (the path found or created) used for reading
and a separate "save_config_path" that is None only when the user passed the
explicit --no-config flag; update the call site that currently uses
config_path=config_path to use save_config_path for writable targets and ensure
functions/classes that accept config_path (e.g., the initializer or function
named with parameter config_path) continue to receive None only when user opted
out and otherwise receive a concrete filesystem path for persistence.
- Around line 116-123: The port value can be an int from tomllib, so calling
_normalize_config_string(item.get("port")) returns None and falls back to
"5432"; update the code that constructs the PostgresConnectorSettings (or
_normalize_config_string) to accept integer ports by converting item.get("port")
to a string before normalization (e.g., coerce via str() when item.get("port")
is not None and not already a str) so the actual configured port (e.g., 6543) is
preserved instead of silently defaulting; reference PostgresConnectorSettings
and _normalize_config_string to locate the change.
In `@python/sqlrooms-cli/sqlrooms/web/launcher.py`:
- Around line 78-97: When building each connection entry in the loop over
connections, detect if the incoming conn's engine identifier (e.g.
conn.get("engineId") or conn.get("engine_id")) differs from the stored entry in
existing_by_id for that conn_id; if it does, don't reuse the old entry
fields—reset base to only include non-engine/shared fields (or start with an
empty dict) before merging frontend_to_toml keys and engine_config. Update the
logic around existing_by_id, base and engine_config so that when engineId
changes you clear engine-specific keys (e.g. Snowflake-only keys like account,
warehouse, schema) instead of carrying them forward into the new TOML entry.
---
Duplicate comments:
In `@python/sqlrooms-cli/sqlrooms/web/launcher.py`:
- Around line 385-392: The handler currently writes the TOML via
_write_db_connectors_to_toml but does not update the in-memory registry built in
__init__, so get_db_settings() and connectionId-based endpoints keep using the
stale self.db_bridge_registry; after a successful write (the block that returns
{"ok": True, "configPath": ...}), reload or rebuild self.db_bridge_registry
(e.g., call the registry-loading method used in __init__ or a dedicated refresh
method) so the in-memory registry reflects the newly saved connections and
subsequent get_db_settings()/connectionId endpoints return the updated data.
---
Nitpick comments:
In `@packages/db-settings/src/components/DbConnectionForm.tsx`:
- Around line 147-162: The handleTestConnection function can call
setTestState/setTestError after the component unmounts; add a mounted guard or
AbortController to prevent state updates when unmounted: create a ref/flag
(e.g., isMounted) or an AbortController in the component, set it true on mount
and false/abort on cleanup, then check the flag or signal before calling
setTestState/setTestError (and pass the signal to testConnection if using
AbortController) so any in-flight testConnection results are ignored when the
form unmounts.
- Around line 91-111: The effect that auto-syncs form edits into the store (uses
isEditing, suppressSyncRef, buildConnection) currently disables exhaustive-deps;
add upsertConnection to the dependency array so the effect explicitly depends on
the store action (upsertConnection) while retaining the existing suppression
behavior for editing via suppressSyncRef to avoid the prop/state loop.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 37ebdebb-2928-4498-9a8c-ffc06a74bf99
⛔ Files ignored due to path filters (1)
python/uv.lockis excluded by!**/*.lock
📒 Files selected for processing (12)
packages/db-settings/src/DbSettingsSlice.tspackages/db-settings/src/components/DbConnectionForm.tsxpackages/db-settings/src/components/DbSettings.tsxpackages/schema-tree/src/nodes/RefreshButton.tsxpackages/sql-editor/src/components/SchemaExplorer.tsxpython/sqlrooms-cli/sqlrooms/cli.pypython/sqlrooms-cli/sqlrooms/web/db_bridge/connectors/postgres.pypython/sqlrooms-cli/sqlrooms/web/db_bridge/connectors/snowflake.pypython/sqlrooms-cli/sqlrooms/web/db_bridge/registry.pypython/sqlrooms-cli/sqlrooms/web/launcher.pypython/sqlrooms-cli/tests/test_cli.pypython/sqlrooms-cli/tests/test_launcher.py
✅ Files skipped from review due to trivial changes (2)
- python/sqlrooms-cli/sqlrooms/web/db_bridge/registry.py
- packages/db-settings/src/DbSettingsSlice.ts
🚧 Files skipped from review as they are similar to previous changes (6)
- python/sqlrooms-cli/tests/test_cli.py
- python/sqlrooms-cli/sqlrooms/web/db_bridge/connectors/snowflake.py
- packages/schema-tree/src/nodes/RefreshButton.tsx
- packages/sql-editor/src/components/SchemaExplorer.tsx
- python/sqlrooms-cli/tests/test_launcher.py
- packages/db-settings/src/components/DbSettings.tsx
Signed-off-by: Ilya Boyandin <[email protected]>
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (1)
python/sqlrooms-cli/sqlrooms/web/launcher.py (1)
389-411:⚠️ Potential issue | 🟠 Major
/api/db/settingsreturns stale data after a successful save.After
_write_db_connectors_to_tomlwrites to disk,self.db_bridge_registryis not refreshed. SubsequentGET /api/db/settingscalls and runtime endpoints (e.g.,/api/db/execute-query) continue using the old connector list until the server restarts.Rebuild the registry after a successful write or have the GET path read directly from the config file.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@python/sqlrooms-cli/sqlrooms/web/launcher.py` around lines 389 - 411, After successfully writing connectors with _write_db_connectors_to_toml inside put_db_settings, refresh the in-memory registry so runtime reads the new connectors: call the same logic used at startup to rebuild self.db_bridge_registry (e.g., invoke an existing initializer like self._init_db_bridge_registry() or add a small helper refresh_db_bridge_registry(config_path) that re-parses the toml and repopulates self.db_bridge_registry) before returning {"ok": True, ...}; alternatively change the GET /api/db/settings handler to read directly from self.config_path on each request so it always returns the on-disk state.
🧹 Nitpick comments (1)
python/sqlrooms-cli/sqlrooms/web/launcher.py (1)
129-134: Redundant import ofre.
reis already imported at line 5. This local import on line 131 is unnecessary.♻️ Suggested fix
raw = tomlkit.dumps(doc) # Collapse runs of blank lines that tomlkit may accumulate on repeated writes - import re - raw = re.sub(r"\n{3,}", "\n\n", raw) config_path.write_text(raw, encoding="utf-8")🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@python/sqlrooms-cli/sqlrooms/web/launcher.py` around lines 129 - 134, Remove the redundant local import of the regex module inside the block that serializes the toml document: delete the inline "import re" and rely on the module-level "re" import already present; ensure the code that uses re.sub on the serialized "raw" string (around tomlkit.dumps and config_path.write_text) still references the top-level re import so the collapse-of-blank-lines logic continues to work.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@python/sqlrooms-cli/tests/test_launcher.py`:
- Around line 299-302: Replace the direct import of tomllib in test_launcher.py
with the same compatibility pattern used in cli.py: try to import tomllib, and
on ImportError import tomli as tomllib so the subsequent call doc =
tomllib.load(fh) works on Python 3.10; update the top of the test (where tomllib
is imported and doc = tomllib.load(fh) is used) to use that try/except import
fallback so tomli is used when tomllib is unavailable.
---
Duplicate comments:
In `@python/sqlrooms-cli/sqlrooms/web/launcher.py`:
- Around line 389-411: After successfully writing connectors with
_write_db_connectors_to_toml inside put_db_settings, refresh the in-memory
registry so runtime reads the new connectors: call the same logic used at
startup to rebuild self.db_bridge_registry (e.g., invoke an existing initializer
like self._init_db_bridge_registry() or add a small helper
refresh_db_bridge_registry(config_path) that re-parses the toml and repopulates
self.db_bridge_registry) before returning {"ok": True, ...}; alternatively
change the GET /api/db/settings handler to read directly from self.config_path
on each request so it always returns the on-disk state.
---
Nitpick comments:
In `@python/sqlrooms-cli/sqlrooms/web/launcher.py`:
- Around line 129-134: Remove the redundant local import of the regex module
inside the block that serializes the toml document: delete the inline "import
re" and rely on the module-level "re" import already present; ensure the code
that uses re.sub on the serialized "raw" string (around tomlkit.dumps and
config_path.write_text) still references the top-level re import so the
collapse-of-blank-lines logic continues to work.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 68b05fca-485b-499a-8d85-56c0b038106b
📒 Files selected for processing (4)
python/sqlrooms-cli/sqlrooms/cli.pypython/sqlrooms-cli/sqlrooms/web/launcher.pypython/sqlrooms-cli/tests/test_cli.pypython/sqlrooms-cli/tests/test_launcher.py
🚧 Files skipped from review as they are similar to previous changes (1)
- python/sqlrooms-cli/tests/test_cli.py
| import tomllib | ||
|
|
||
| with config_path.open("rb") as fh: | ||
| doc = tomllib.load(fh) |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check minimum Python version requirements
rg -n "python_requires|python-requires" python/sqlrooms-cli/pyproject.toml python/sqlrooms-cli/setup.py 2>/dev/null || echo "No explicit Python version requirement found"
# Check if tomli is listed as a dependency
rg -n "tomli" python/sqlrooms-cli/pyproject.toml python/sqlrooms-cli/setup.py 2>/dev/null || echo "tomli not found in dependencies"Repository: sqlrooms/sqlrooms
Length of output: 218
🏁 Script executed:
# Find cli.py and check for tomllib/tomli handling
find . -name "cli.py" -path "*/sqlrooms-cli/*" -type f | head -5Repository: sqlrooms/sqlrooms
Length of output: 98
🏁 Script executed:
# Check the full context around lines 299-302 in test_launcher.py
cat -n python/sqlrooms-cli/tests/test_launcher.py | sed -n '295,310p'Repository: sqlrooms/sqlrooms
Length of output: 548
🏁 Script executed:
# Search for tomllib/tomli imports in cli.py to verify the try/except pattern
rg -n -A 5 "tomllib|tomli" python/sqlrooms-cli/src/sqlrooms_cli/cli.pyRepository: sqlrooms/sqlrooms
Length of output: 144
🏁 Script executed:
# Search for tomllib/tomli imports in the correct cli.py path
rg -n -A 5 "tomllib|tomli" python/sqlrooms-cli/sqlrooms/cli.pyRepository: sqlrooms/sqlrooms
Length of output: 427
Update test_launcher.py to handle Python 3.10 compatibility for tomllib.
The code at lines 299-302 directly imports tomllib, which is only available in Python 3.11+. Since the project declares tomli>=2.0.0; python_version < '3.11' as a dependency, Python 3.10 support is intended. Use the same try/except pattern as in cli.py:
Suggested fix
- import tomllib
+ try:
+ import tomllib # type: ignore[attr-defined]
+ except ModuleNotFoundError:
+ import tomli as tomllib # type: ignore[no-redef]
with config_path.open("rb") as fh:
doc = tomllib.load(fh)📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| import tomllib | |
| with config_path.open("rb") as fh: | |
| doc = tomllib.load(fh) | |
| try: | |
| import tomllib # type: ignore[attr-defined] | |
| except ModuleNotFoundError: | |
| import tomli as tomllib # type: ignore[no-redef] | |
| with config_path.open("rb") as fh: | |
| doc = tomllib.load(fh) |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@python/sqlrooms-cli/tests/test_launcher.py` around lines 299 - 302, Replace
the direct import of tomllib in test_launcher.py with the same compatibility
pattern used in cli.py: try to import tomllib, and on ImportError import tomli
as tomllib so the subsequent call doc = tomllib.load(fh) works on Python 3.10;
update the top of the test (where tomllib is imported and doc = tomllib.load(fh)
is used) to use that try/except import fallback so tomli is used when tomllib is
unavailable.
Summary by CodeRabbit
New Features
Deprecated
Bug Fixes