queryview 0.0.2__tar.gz → 0.0.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {queryview-0.0.2 → queryview-0.0.3}/PKG-INFO +36 -9
- {queryview-0.0.2 → queryview-0.0.3}/README.md +34 -8
- {queryview-0.0.2 → queryview-0.0.3}/backend/queryview/connect.py +9 -1
- {queryview-0.0.2 → queryview-0.0.3}/backend/queryview/main.py +15 -1
- queryview-0.0.3/backend/queryview/test_db_path.py +58 -0
- queryview-0.0.3/backend/queryview/test_mcp_mount.py +38 -0
- {queryview-0.0.2 → queryview-0.0.3}/docker/Dockerfile +4 -3
- {queryview-0.0.2 → queryview-0.0.3}/docs/api.md +4 -3
- {queryview-0.0.2 → queryview-0.0.3}/docs/remote.md +9 -1
- {queryview-0.0.2 → queryview-0.0.3}/pyproject.toml +1 -0
- {queryview-0.0.2 → queryview-0.0.3}/uv.lock +2 -0
- {queryview-0.0.2 → queryview-0.0.3}/.claude/settings.json +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/.github/actions/start-git-daemon/action.yaml +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/.github/workflows/ci.yml +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/.github/workflows/publish.yaml +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/.gitignore +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/.mcp.json +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/.pre-commit-config.yaml +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/CLAUDE.md +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/LICENSE +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/backend/alembic.ini +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/backend/queryview/__init__.py +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/backend/queryview/conftest.py +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/backend/queryview/dashboard_queries.py +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/backend/queryview/dashboards.py +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/backend/queryview/drivers/__init__.py +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/backend/queryview/drivers/base.py +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/backend/queryview/drivers/clickhouse.py +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/backend/queryview/drivers/duckdb.py +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/backend/queryview/drivers/postgres.py +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/backend/queryview/drivers/test_base.py +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/backend/queryview/drivers/test_clickhouse.py +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/backend/queryview/drivers/test_contract.py +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/backend/queryview/drivers/test_duckdb.py +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/backend/queryview/drivers/test_postgres.py +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/backend/queryview/gitsync.py +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/backend/queryview/mcp_server.py +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/backend/queryview/migrations/env.py +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/backend/queryview/migrations/script.py.mako +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/backend/queryview/migrations/versions/9a536b7c0328_initial_schema.py +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/backend/queryview/migrations/versions/a1b2c3d4e5f6_connection_config_blob.py +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/backend/queryview/migrations/versions/b2c3d4e5f6a7_predefined_presentation.py +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/backend/queryview/migrations/versions/c7d8e9f0a1b2_workspaces.py +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/backend/queryview/queries.py +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/backend/queryview/remote.py +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/backend/queryview/static/assets/index-CvnC_D68.js +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/backend/queryview/static/assets/index-Qe7bhycG.css +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/backend/queryview/static/favicon.svg +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/backend/queryview/static/index.html +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/backend/queryview/test_api_db.py +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/backend/queryview/test_api_export_import.py +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/backend/queryview/test_api_gitsync.py +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/backend/queryview/test_api_workspaces.py +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/backend/queryview/test_connect_flow.py +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/backend/queryview/test_connect_store.py +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/backend/queryview/test_dashboards.py +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/backend/queryview/test_gitsync.py +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/backend/queryview/test_main.py +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/backend/queryview/test_mcp_gitsync.py +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/backend/queryview/test_migrations.py +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/backend/queryview/test_queries.py +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/backend/queryview/test_remote.py +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/backend/queryview/test_validation.py +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/backend/queryview/test_workspaces.py +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/backend/queryview/test_yamlio.py +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/backend/queryview/validation.py +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/backend/queryview/workspaces.py +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/backend/queryview/yamlio.py +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/docs/connect.md +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/docs/dashboard.md +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/docs/explorer.md +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/docs/export-import.md +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/docs/future.md +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/docs/gitsync.md +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/docs/query.md +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/docs/queryview.md +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/docs/workspace.md +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/e2e/conftest.py +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/e2e/test_app.py +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/e2e/test_dashboard.py +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/e2e/test_drivers.py +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/e2e/test_explorer.py +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/e2e/test_export_import.py +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/e2e/test_gitsync.py +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/e2e/test_query.py +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/e2e/test_remote.py +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/e2e/test_workspaces.py +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/frontend/.gitignore +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/frontend/README.md +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/frontend/eslint.config.js +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/frontend/index.html +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/frontend/package.json +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/frontend/public/favicon.svg +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/frontend/src/App.tsx +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/frontend/src/CellViewModal.tsx +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/frontend/src/ComplexCell.tsx +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/frontend/src/DashboardView.tsx +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/frontend/src/ExplorerView.tsx +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/frontend/src/ExportImportControls.tsx +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/frontend/src/FieldPickers.tsx +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/frontend/src/GitSyncControls.tsx +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/frontend/src/QueryView.tsx +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/frontend/src/ResultsTable.tsx +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/frontend/src/Toast.tsx +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/frontend/src/WorkspaceSwitcher.tsx +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/frontend/src/cellView.test.ts +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/frontend/src/cellView.ts +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/frontend/src/compactNumber.test.ts +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/frontend/src/compactNumber.ts +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/frontend/src/complexCellParsing.test.ts +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/frontend/src/complexCellParsing.ts +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/frontend/src/drivers.ts +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/frontend/src/gitsync.test.ts +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/frontend/src/gitsync.ts +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/frontend/src/index.css +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/frontend/src/main.tsx +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/frontend/src/presentation.test.ts +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/frontend/src/presentation.ts +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/frontend/src/promptSuggestions.test.ts +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/frontend/src/promptSuggestions.ts +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/frontend/src/queryParams.test.ts +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/frontend/src/queryParams.ts +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/frontend/src/sessionLock.test.ts +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/frontend/src/sessionLock.ts +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/frontend/src/tsv.ts +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/frontend/src/workspace.test.ts +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/frontend/src/workspace.ts +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/frontend/src/yamlio.test.ts +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/frontend/src/yamlio.ts +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/frontend/tsconfig.app.json +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/frontend/tsconfig.json +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/frontend/tsconfig.node.json +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/frontend/vite.config.ts +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/package-lock.json +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/package.json +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/pyrightconfig.json +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/scripts/dev.sh +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/scripts/setup.sh +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/scripts/setup_browser.sh +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/scripts/setup_clickhouse.sh +0 -0
- {queryview-0.0.2 → queryview-0.0.3}/scripts/setup_postgres.sh +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: queryview
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.3
|
|
4
4
|
Summary: QueryView: FastAPI + SQLModel backend and React SPA over the ClickHouse HTTP interface
|
|
5
5
|
Project-URL: Homepage, https://github.com/kolodkin/queryview
|
|
6
6
|
Project-URL: Repository, https://github.com/kolodkin/queryview
|
|
@@ -16,6 +16,7 @@ Requires-Dist: duckdb>=1.0
|
|
|
16
16
|
Requires-Dist: fastapi>=0.110
|
|
17
17
|
Requires-Dist: httpx>=0.27
|
|
18
18
|
Requires-Dist: mcp<2,>=1.9
|
|
19
|
+
Requires-Dist: platformdirs>=4
|
|
19
20
|
Requires-Dist: pyyaml>=6
|
|
20
21
|
Requires-Dist: sqlalchemy[asyncio]>=2.0
|
|
21
22
|
Requires-Dist: sqlmodel>=0.0.16
|
|
@@ -46,7 +47,9 @@ docker run -p 8000:8000 ghcr.io/kolodkin/queryview:latest
|
|
|
46
47
|
```
|
|
47
48
|
|
|
48
49
|
To serve on a different host port, remap it (the container keeps listening on
|
|
49
|
-
8000, which its healthcheck probes): `docker run -p 9000:8000 ...`.
|
|
50
|
+
8000, which its healthcheck probes): `docker run -p 9000:8000 ...`. QueryView
|
|
51
|
+
expects to be reached from localhost only, so prefer binding the published port
|
|
52
|
+
to loopback: `docker run -p 127.0.0.1:8000:8000 ...`.
|
|
50
53
|
|
|
51
54
|
State (the SQLite DB and its encryption key) lives in `/home/queryview`; mount a
|
|
52
55
|
volume there to persist it across containers:
|
|
@@ -154,14 +157,34 @@ An installed wheel serves the bundled UI by default — see
|
|
|
154
157
|
## MCP server
|
|
155
158
|
|
|
156
159
|
The backend mounts a FastMCP server (Streamable HTTP) at
|
|
157
|
-
`http://localhost:8000/mcp
|
|
158
|
-
the server process (`uvx queryview`, `npm run dev`, ...).
|
|
159
|
-
|
|
160
|
+
`http://localhost:8000/mcp/`. There is nothing extra to start — it runs inside
|
|
161
|
+
the server process (`uvx queryview`, `npm run dev`, ...). Registering the client
|
|
162
|
+
is a separate, one-time step on the machine running the agent: an HTTP MCP
|
|
163
|
+
server can't install itself into someone else's client.
|
|
160
164
|
|
|
161
165
|
```bash
|
|
162
|
-
claude mcp add --transport http queryview http://localhost:8000/mcp
|
|
166
|
+
claude mcp add --transport http queryview http://localhost:8000/mcp/
|
|
163
167
|
```
|
|
164
168
|
|
|
169
|
+
Three things to get right:
|
|
170
|
+
|
|
171
|
+
- **Prefer the trailing slash.** The mount serves `/mcp/`. The slashless
|
|
172
|
+
`/mcp` also works — it 307-redirects — but registering the canonical path
|
|
173
|
+
skips a round trip on every call.
|
|
174
|
+
- **Match the port.** The URL must point at the port QueryView actually
|
|
175
|
+
listens on — `--port 9000` means `http://localhost:9000/mcp/`, and
|
|
176
|
+
`docker run -p 9000:8000` means the *host* port, `9000`, not the container's
|
|
177
|
+
`8000`.
|
|
178
|
+
- **Start QueryView first.** The client dials this URL when it starts; if
|
|
179
|
+
nothing is listening it reports a connection error and stays failed until you
|
|
180
|
+
reconnect it.
|
|
181
|
+
|
|
182
|
+
QueryView is a **local, single-user tool**: it assumes it is reachable only from
|
|
183
|
+
localhost. `/mcp/` is unauthenticated, and its tools can query every configured
|
|
184
|
+
connection and rewrite workspace git state, so don't publish the port. Bind the
|
|
185
|
+
container to loopback — `docker run -p 127.0.0.1:8000:8000 ...` — since a plain
|
|
186
|
+
`-p 8000:8000` listens on all interfaces.
|
|
187
|
+
|
|
165
188
|
Tools: `run_query` (read-only SQL, rows returned to the agent), `push_query`
|
|
166
189
|
and `push_dashboard` (fill a live browser session), `list_queries` /
|
|
167
190
|
`list_dashboards`, and `git_store` / `git_history` / `git_restore` (workspace
|
|
@@ -178,6 +201,10 @@ The single-page prompt UI is described in [docs/queryview.md](docs/queryview.md)
|
|
|
178
201
|
connecting (`new <type>` / `connect <name>`), SQLite persistence, and session
|
|
179
202
|
auto-connect are specified in [docs/connect.md](docs/connect.md).
|
|
180
203
|
|
|
181
|
-
Connections are stored in SQLite
|
|
182
|
-
|
|
183
|
-
|
|
204
|
+
Connections are stored in SQLite. The default location is the platform's
|
|
205
|
+
user-data directory — `$XDG_DATA_HOME/queryview/queryview.db` (i.e.
|
|
206
|
+
`~/.local/share/queryview/`) on Linux, `~/Library/Application Support/queryview/`
|
|
207
|
+
on macOS, `%LOCALAPPDATA%\queryview\` on Windows — overridable with `DB_PATH`.
|
|
208
|
+
Alongside it the backend writes a local password-encryption key
|
|
209
|
+
(`<db>.key`, override with `DB_KEY_PATH`) and the workspace git-sync clones
|
|
210
|
+
(`<db>.gitsync/`, override with `GIT_SYNC_DIR`).
|
|
@@ -22,7 +22,9 @@ docker run -p 8000:8000 ghcr.io/kolodkin/queryview:latest
|
|
|
22
22
|
```
|
|
23
23
|
|
|
24
24
|
To serve on a different host port, remap it (the container keeps listening on
|
|
25
|
-
8000, which its healthcheck probes): `docker run -p 9000:8000 ...`.
|
|
25
|
+
8000, which its healthcheck probes): `docker run -p 9000:8000 ...`. QueryView
|
|
26
|
+
expects to be reached from localhost only, so prefer binding the published port
|
|
27
|
+
to loopback: `docker run -p 127.0.0.1:8000:8000 ...`.
|
|
26
28
|
|
|
27
29
|
State (the SQLite DB and its encryption key) lives in `/home/queryview`; mount a
|
|
28
30
|
volume there to persist it across containers:
|
|
@@ -130,14 +132,34 @@ An installed wheel serves the bundled UI by default — see
|
|
|
130
132
|
## MCP server
|
|
131
133
|
|
|
132
134
|
The backend mounts a FastMCP server (Streamable HTTP) at
|
|
133
|
-
`http://localhost:8000/mcp
|
|
134
|
-
the server process (`uvx queryview`, `npm run dev`, ...).
|
|
135
|
-
|
|
135
|
+
`http://localhost:8000/mcp/`. There is nothing extra to start — it runs inside
|
|
136
|
+
the server process (`uvx queryview`, `npm run dev`, ...). Registering the client
|
|
137
|
+
is a separate, one-time step on the machine running the agent: an HTTP MCP
|
|
138
|
+
server can't install itself into someone else's client.
|
|
136
139
|
|
|
137
140
|
```bash
|
|
138
|
-
claude mcp add --transport http queryview http://localhost:8000/mcp
|
|
141
|
+
claude mcp add --transport http queryview http://localhost:8000/mcp/
|
|
139
142
|
```
|
|
140
143
|
|
|
144
|
+
Three things to get right:
|
|
145
|
+
|
|
146
|
+
- **Prefer the trailing slash.** The mount serves `/mcp/`. The slashless
|
|
147
|
+
`/mcp` also works — it 307-redirects — but registering the canonical path
|
|
148
|
+
skips a round trip on every call.
|
|
149
|
+
- **Match the port.** The URL must point at the port QueryView actually
|
|
150
|
+
listens on — `--port 9000` means `http://localhost:9000/mcp/`, and
|
|
151
|
+
`docker run -p 9000:8000` means the *host* port, `9000`, not the container's
|
|
152
|
+
`8000`.
|
|
153
|
+
- **Start QueryView first.** The client dials this URL when it starts; if
|
|
154
|
+
nothing is listening it reports a connection error and stays failed until you
|
|
155
|
+
reconnect it.
|
|
156
|
+
|
|
157
|
+
QueryView is a **local, single-user tool**: it assumes it is reachable only from
|
|
158
|
+
localhost. `/mcp/` is unauthenticated, and its tools can query every configured
|
|
159
|
+
connection and rewrite workspace git state, so don't publish the port. Bind the
|
|
160
|
+
container to loopback — `docker run -p 127.0.0.1:8000:8000 ...` — since a plain
|
|
161
|
+
`-p 8000:8000` listens on all interfaces.
|
|
162
|
+
|
|
141
163
|
Tools: `run_query` (read-only SQL, rows returned to the agent), `push_query`
|
|
142
164
|
and `push_dashboard` (fill a live browser session), `list_queries` /
|
|
143
165
|
`list_dashboards`, and `git_store` / `git_history` / `git_restore` (workspace
|
|
@@ -154,6 +176,10 @@ The single-page prompt UI is described in [docs/queryview.md](docs/queryview.md)
|
|
|
154
176
|
connecting (`new <type>` / `connect <name>`), SQLite persistence, and session
|
|
155
177
|
auto-connect are specified in [docs/connect.md](docs/connect.md).
|
|
156
178
|
|
|
157
|
-
Connections are stored in SQLite
|
|
158
|
-
|
|
159
|
-
|
|
179
|
+
Connections are stored in SQLite. The default location is the platform's
|
|
180
|
+
user-data directory — `$XDG_DATA_HOME/queryview/queryview.db` (i.e.
|
|
181
|
+
`~/.local/share/queryview/`) on Linux, `~/Library/Application Support/queryview/`
|
|
182
|
+
on macOS, `%LOCALAPPDATA%\queryview\` on Windows — overridable with `DB_PATH`.
|
|
183
|
+
Alongside it the backend writes a local password-encryption key
|
|
184
|
+
(`<db>.key`, override with `DB_KEY_PATH`) and the workspace git-sync clones
|
|
185
|
+
(`<db>.gitsync/`, override with `GIT_SYNC_DIR`).
|
|
@@ -14,6 +14,7 @@ from dataclasses import dataclass
|
|
|
14
14
|
from pathlib import Path
|
|
15
15
|
from typing import TYPE_CHECKING, Any, ClassVar
|
|
16
16
|
|
|
17
|
+
import platformdirs
|
|
17
18
|
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
|
|
18
19
|
from sqlalchemy.ext.asyncio import create_async_engine
|
|
19
20
|
from sqlmodel import Field, SQLModel, col, select
|
|
@@ -40,10 +41,14 @@ class Connection(SQLModel, table=True):
|
|
|
40
41
|
|
|
41
42
|
|
|
42
43
|
def _db_path() -> Path:
|
|
44
|
+
"""The SQLite store's path: DB_PATH, else the platform's user-data dir
|
|
45
|
+
(`$XDG_DATA_HOME/queryview` on Linux, `Application Support` on macOS,
|
|
46
|
+
`%LOCALAPPDATA%` on Windows). Deliberately not package-relative — that put
|
|
47
|
+
the DB in site-packages, which is uv's disposable cache under `uvx`."""
|
|
43
48
|
env = os.environ.get("DB_PATH")
|
|
44
49
|
if env:
|
|
45
50
|
return Path(env)
|
|
46
|
-
return Path(
|
|
51
|
+
return Path(platformdirs.user_data_dir("queryview")) / "queryview.db"
|
|
47
52
|
|
|
48
53
|
|
|
49
54
|
_engine = None
|
|
@@ -80,6 +85,9 @@ async def _ensure_schema() -> None:
|
|
|
80
85
|
return
|
|
81
86
|
from alembic import command
|
|
82
87
|
|
|
88
|
+
# SQLite won't create a missing parent, and the user-data dir doesn't exist
|
|
89
|
+
# on a fresh install; the key file and git-sync clones land here too.
|
|
90
|
+
_db_path().parent.mkdir(parents=True, exist_ok=True)
|
|
83
91
|
command.upgrade(_alembic_config(), "head")
|
|
84
92
|
_schema_ready = True
|
|
85
93
|
|
|
@@ -11,7 +11,13 @@ from pathlib import Path
|
|
|
11
11
|
from typing import Any
|
|
12
12
|
|
|
13
13
|
from fastapi import FastAPI, Request
|
|
14
|
-
from fastapi.responses import
|
|
14
|
+
from fastapi.responses import (
|
|
15
|
+
FileResponse,
|
|
16
|
+
JSONResponse,
|
|
17
|
+
PlainTextResponse,
|
|
18
|
+
RedirectResponse,
|
|
19
|
+
StreamingResponse,
|
|
20
|
+
)
|
|
15
21
|
|
|
16
22
|
from . import gitsync, remote, workspaces, yamlio
|
|
17
23
|
from .connect import (
|
|
@@ -100,6 +106,14 @@ app = FastAPI(title="queryview-backend", lifespan=lifespan)
|
|
|
100
106
|
app.mount("/mcp", mcp.streamable_http_app())
|
|
101
107
|
|
|
102
108
|
|
|
109
|
+
# The GET-only catch-all below suppresses Starlette's own /mcp -> /mcp/ redirect
|
|
110
|
+
# (see test_mcp_mount.py), so issue it explicitly. 307 preserves method and
|
|
111
|
+
# body; Streamable HTTP uses POST, GET and DELETE.
|
|
112
|
+
@app.api_route("/mcp", methods=["GET", "POST", "DELETE"], include_in_schema=False)
|
|
113
|
+
async def mcp_slash_redirect() -> RedirectResponse:
|
|
114
|
+
return RedirectResponse("/mcp/", status_code=307)
|
|
115
|
+
|
|
116
|
+
|
|
103
117
|
@app.middleware("http")
|
|
104
118
|
async def session_cookie(request: Request, call_next):
|
|
105
119
|
sid = request.cookies.get("qv_session")
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""Where the SQLite store lives.
|
|
2
|
+
|
|
3
|
+
The default used to be package-relative (`Path(__file__).parent.parent`), which
|
|
4
|
+
meant `backend/queryview.db` in a checkout but `<site-packages>/queryview.db`
|
|
5
|
+
once the wheel shipped — under `uvx`, that is inside uv's *cache*, where a
|
|
6
|
+
`uv cache clean` or a version bump silently takes the DB with it. The default
|
|
7
|
+
is now the platform's user-data directory.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
import platformdirs
|
|
15
|
+
|
|
16
|
+
import queryview
|
|
17
|
+
from queryview.connect import _db_path, _key_path
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def test_db_path_defaults_to_user_data_dir(monkeypatch):
|
|
21
|
+
monkeypatch.delenv("DB_PATH", raising=False)
|
|
22
|
+
expected = Path(platformdirs.user_data_dir("queryview")) / "queryview.db"
|
|
23
|
+
assert _db_path() == expected
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def test_db_path_is_not_inside_the_installed_package(monkeypatch):
|
|
27
|
+
"""The regression: a package-relative default puts user data in
|
|
28
|
+
site-packages (and under uvx, in a disposable cache directory)."""
|
|
29
|
+
monkeypatch.delenv("DB_PATH", raising=False)
|
|
30
|
+
package_root = Path(queryview.__file__).resolve().parent.parent
|
|
31
|
+
assert package_root not in _db_path().resolve().parents
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def test_db_path_env_var_still_wins(monkeypatch, tmp_path):
|
|
35
|
+
monkeypatch.setenv("DB_PATH", str(tmp_path / "custom.db"))
|
|
36
|
+
assert _db_path() == tmp_path / "custom.db"
|
|
37
|
+
# The key file follows the DB unless DB_KEY_PATH says otherwise.
|
|
38
|
+
monkeypatch.delenv("DB_KEY_PATH", raising=False)
|
|
39
|
+
assert _key_path() == tmp_path / "custom.db.key"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def test_ensure_schema_creates_a_missing_data_dir(monkeypatch, tmp_path):
|
|
43
|
+
"""The user-data dir does not exist on a fresh install, so startup has to
|
|
44
|
+
create it — SQLite will not create a missing parent directory."""
|
|
45
|
+
import asyncio
|
|
46
|
+
|
|
47
|
+
import queryview.connect as c
|
|
48
|
+
|
|
49
|
+
target = tmp_path / "fresh" / "nested" / "queryview.db"
|
|
50
|
+
monkeypatch.setenv("DB_PATH", str(target))
|
|
51
|
+
monkeypatch.setenv("DB_KEY_PATH", str(target.with_suffix(".db.key")))
|
|
52
|
+
monkeypatch.setattr(c, "_engine", None)
|
|
53
|
+
monkeypatch.setattr(c, "_schema_ready", False)
|
|
54
|
+
assert not target.parent.exists()
|
|
55
|
+
|
|
56
|
+
asyncio.run(c._ensure_schema())
|
|
57
|
+
|
|
58
|
+
assert target.parent.is_dir()
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""The MCP mount's slashless path.
|
|
2
|
+
|
|
3
|
+
The FastMCP app is mounted at `/mcp` with its own path set to `/`, so the
|
|
4
|
+
canonical endpoint is `/mcp/`. Starlette would normally redirect `/mcp` there,
|
|
5
|
+
but the SPA catch-all (`@app.get("/{full_path:path}")`) matches the slashless
|
|
6
|
+
path on GET only, which Starlette scores as a partial match — that suppresses
|
|
7
|
+
the redirect and answers `405 allow: GET` instead, pointing a client at a path
|
|
8
|
+
the MCP server never serves. These tests pin the redirect so the catch-all
|
|
9
|
+
can't swallow it again.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import pytest
|
|
15
|
+
from fastapi.testclient import TestClient
|
|
16
|
+
|
|
17
|
+
from queryview.main import app
|
|
18
|
+
|
|
19
|
+
# Streamable HTTP uses POST (messages), GET (the SSE stream) and DELETE
|
|
20
|
+
# (session termination) — all three must reach the mount.
|
|
21
|
+
MCP_METHODS = ["POST", "GET", "DELETE"]
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@pytest.mark.parametrize("method", MCP_METHODS)
|
|
25
|
+
def test_slashless_mcp_redirects_to_canonical_path(method):
|
|
26
|
+
c = TestClient(app, follow_redirects=False)
|
|
27
|
+
r = c.request(method, "/mcp")
|
|
28
|
+
assert r.status_code == 307, f"{method} /mcp returned {r.status_code}, not a redirect"
|
|
29
|
+
assert r.headers["location"].endswith("/mcp/")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@pytest.mark.parametrize("method", MCP_METHODS)
|
|
33
|
+
def test_canonical_mcp_path_is_not_redirected(method):
|
|
34
|
+
"""The redirect must not shadow the mount itself."""
|
|
35
|
+
c = TestClient(app, follow_redirects=False, raise_server_exceptions=False)
|
|
36
|
+
r = c.request(method, "/mcp/")
|
|
37
|
+
assert r.status_code != 307
|
|
38
|
+
assert "location" not in r.headers
|
|
@@ -18,9 +18,10 @@ RUN useradd --create-home --uid 1000 queryview
|
|
|
18
18
|
WORKDIR /home/queryview
|
|
19
19
|
USER queryview
|
|
20
20
|
|
|
21
|
-
# The default
|
|
22
|
-
#
|
|
23
|
-
# volume mounted at /home/queryview persists all
|
|
21
|
+
# The default is the user-data dir (/home/queryview/.local/share/queryview);
|
|
22
|
+
# pin the SQLite file (and its sibling .key file and git-sync clones) to the top
|
|
23
|
+
# of the home dir instead, so a volume mounted at /home/queryview persists all
|
|
24
|
+
# state at a flat, documented path.
|
|
24
25
|
ENV DB_PATH=/home/queryview/queryview.db
|
|
25
26
|
|
|
26
27
|
EXPOSE 8000
|
|
@@ -30,9 +30,10 @@ independently. Saved connections are shared (SQLite).
|
|
|
30
30
|
| GET | `/api/dashboards` | `?workspace=` | List a workspace's dashboards (no payload): `{dashboards:[{name, connection, updated_at}]}`, ordered by name. |
|
|
31
31
|
| GET | `/api/dashboards/{name}` | `?workspace=` | A saved dashboard `{name, connection, html, queries}` (`queries` parsed to a dict), or `404 {error:"not found"}`. |
|
|
32
32
|
|
|
33
|
-
**MCP:** a FastMCP server is mounted at `/mcp
|
|
34
|
-
`push_query` (push SQL to a session's query panel)
|
|
35
|
-
|
|
33
|
+
**MCP:** a FastMCP server is mounted at `/mcp/` (Streamable HTTP) exposing
|
|
34
|
+
`push_query` (push SQL to a session's query panel), `push_dashboard` (push a
|
|
35
|
+
dashboard draft to a session), `run_query`, `list_queries` / `list_dashboards`
|
|
36
|
+
and `git_store` / `git_history` / `git_restore`. They delegate to the in-process
|
|
36
37
|
hubs the matching REST endpoints call. See [remote.md](./remote.md) and
|
|
37
38
|
[dashboard.md](./dashboard.md).
|
|
38
39
|
|
|
@@ -16,9 +16,17 @@ session's **id** and a copyable command, e.g.:
|
|
|
16
16
|
Turning the toggle off (or closing the tab) disarms the session immediately —
|
|
17
17
|
pushes to its id are then reported as not delivered.
|
|
18
18
|
|
|
19
|
+
## Connecting a client
|
|
20
|
+
|
|
21
|
+
The server runs inside the backend process — nothing extra to start — but the
|
|
22
|
+
client registration is a separate one-time step: point it at
|
|
23
|
+
`http://localhost:8000/mcp/` (the slashless `/mcp` redirects there; match the
|
|
24
|
+
port QueryView listens on, and start it first). See
|
|
25
|
+
[the README](../README.md#mcp-server).
|
|
26
|
+
|
|
19
27
|
## MCP tools
|
|
20
28
|
|
|
21
|
-
The backend mounts a FastMCP server (Streamable HTTP) at `/mcp
|
|
29
|
+
The backend mounts a FastMCP server (Streamable HTTP) at `/mcp/` exposing four
|
|
22
30
|
tools:
|
|
23
31
|
|
|
24
32
|
- `push_query(session_id, query, limit?=100, offset?=0, order_by?, fields?, cell_view?, name?)` —
|
|
@@ -1185,6 +1185,7 @@ dependencies = [
|
|
|
1185
1185
|
{ name = "fastapi" },
|
|
1186
1186
|
{ name = "httpx" },
|
|
1187
1187
|
{ name = "mcp" },
|
|
1188
|
+
{ name = "platformdirs" },
|
|
1188
1189
|
{ name = "pyyaml" },
|
|
1189
1190
|
{ name = "sqlalchemy", extra = ["asyncio"] },
|
|
1190
1191
|
{ name = "sqlmodel" },
|
|
@@ -1214,6 +1215,7 @@ requires-dist = [
|
|
|
1214
1215
|
{ name = "fastapi", specifier = ">=0.110" },
|
|
1215
1216
|
{ name = "httpx", specifier = ">=0.27" },
|
|
1216
1217
|
{ name = "mcp", specifier = ">=1.9,<2" },
|
|
1218
|
+
{ name = "platformdirs", specifier = ">=4" },
|
|
1217
1219
|
{ name = "pyyaml", specifier = ">=6" },
|
|
1218
1220
|
{ name = "sqlalchemy", extras = ["asyncio"], specifier = ">=2.0" },
|
|
1219
1221
|
{ name = "sqlmodel", specifier = ">=0.0.16" },
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{queryview-0.0.2 → queryview-0.0.3}/backend/queryview/migrations/versions/c7d8e9f0a1b2_workspaces.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|