querri 1.0.8__tar.gz → 2.0.0__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.
- querri-2.0.0/CHANGELOG.md +89 -0
- querri-2.0.0/LICENSE +21 -0
- {querri-1.0.8 → querri-2.0.0}/PKG-INFO +48 -5
- {querri-1.0.8 → querri-2.0.0}/README.md +45 -3
- querri-2.0.0/docs/MIGRATION.md +120 -0
- {querri-1.0.8 → querri-2.0.0}/docs/server-sdk.md +124 -33
- {querri-1.0.8 → querri-2.0.0}/examples/11_user_scoped_client.py +5 -3
- {querri-1.0.8 → querri-2.0.0}/examples/web-embed/index.html +18 -14
- {querri-1.0.8 → querri-2.0.0}/examples/web-embed/server.py +6 -4
- {querri-1.0.8 → querri-2.0.0}/querri/__init__.py +2 -0
- {querri-1.0.8 → querri-2.0.0}/querri/_client.py +8 -8
- {querri-1.0.8 → querri-2.0.0}/querri/_convenience.py +20 -0
- {querri-1.0.8 → querri-2.0.0}/querri/_exceptions.py +21 -0
- {querri-1.0.8 → querri-2.0.0}/querri/_pagination.py +28 -12
- {querri-1.0.8 → querri-2.0.0}/querri/_user_client.py +52 -21
- querri-2.0.0/querri/_version.py +1 -0
- {querri-1.0.8 → querri-2.0.0}/querri/cli/_output.py +33 -0
- {querri-1.0.8 → querri-2.0.0}/querri/cli/audit.py +9 -2
- {querri-1.0.8 → querri-2.0.0}/querri/cli/embed.py +26 -0
- {querri-1.0.8 → querri-2.0.0}/querri/cli/files.py +4 -2
- {querri-1.0.8 → querri-2.0.0}/querri/cli/keys.py +10 -2
- {querri-1.0.8 → querri-2.0.0}/querri/cli/sharing.py +10 -3
- {querri-1.0.8 → querri-2.0.0}/querri/cli/sources.py +12 -4
- {querri-1.0.8 → querri-2.0.0}/querri/cli/views.py +4 -2
- {querri-1.0.8 → querri-2.0.0}/querri/resources/audit.py +13 -17
- {querri-1.0.8 → querri-2.0.0}/querri/resources/embed.py +160 -24
- {querri-1.0.8 → querri-2.0.0}/querri/resources/files.py +34 -13
- {querri-1.0.8 → querri-2.0.0}/querri/resources/keys.py +35 -12
- {querri-1.0.8 → querri-2.0.0}/querri/resources/sources.py +63 -32
- {querri-1.0.8 → querri-2.0.0}/querri/resources/views.py +33 -12
- {querri-1.0.8 → querri-2.0.0}/skills/querri-cli/SKILL.md +2 -1
- {querri-1.0.8 → querri-2.0.0}/tests/test_cli_commands.py +143 -15
- {querri-1.0.8 → querri-2.0.0}/tests/test_integration.py +1 -1
- {querri-1.0.8 → querri-2.0.0}/tests/test_new_features.py +36 -3
- {querri-1.0.8 → querri-2.0.0}/tests/test_pagination.py +36 -0
- {querri-1.0.8 → querri-2.0.0}/tests/test_resources.py +302 -54
- querri-2.0.0/uv.lock +936 -0
- querri-1.0.8/querri/_version.py +0 -1
- {querri-1.0.8 → querri-2.0.0}/.github/CONTRIBUTING.md +0 -0
- {querri-1.0.8 → querri-2.0.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {querri-1.0.8 → querri-2.0.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {querri-1.0.8 → querri-2.0.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {querri-1.0.8 → querri-2.0.0}/.github/SECURITY.md +0 -0
- {querri-1.0.8 → querri-2.0.0}/.github/workflows/ci.yml +0 -0
- {querri-1.0.8 → querri-2.0.0}/.github/workflows/release.yml +0 -0
- {querri-1.0.8 → querri-2.0.0}/.gitignore +0 -0
- {querri-1.0.8 → querri-2.0.0}/CLAUDE.md +0 -0
- {querri-1.0.8 → querri-2.0.0}/TODO.md +0 -0
- {querri-1.0.8 → querri-2.0.0}/examples/01_quick_start.py +0 -0
- {querri-1.0.8 → querri-2.0.0}/examples/02_user_management.py +0 -0
- {querri-1.0.8 → querri-2.0.0}/examples/03_access_policies.py +0 -0
- {querri-1.0.8 → querri-2.0.0}/examples/04_embed_session.py +0 -0
- {querri-1.0.8 → querri-2.0.0}/examples/05_get_session.py +0 -0
- {querri-1.0.8 → querri-2.0.0}/examples/06_projects.py +0 -0
- {querri-1.0.8 → querri-2.0.0}/examples/07_dashboards.py +0 -0
- {querri-1.0.8 → querri-2.0.0}/examples/08_chat_streaming.py +0 -0
- {querri-1.0.8 → querri-2.0.0}/examples/09_async_client.py +0 -0
- {querri-1.0.8 → querri-2.0.0}/examples/10_error_handling.py +0 -0
- {querri-1.0.8 → querri-2.0.0}/examples/12_data_operations.py +0 -0
- {querri-1.0.8 → querri-2.0.0}/pyproject.toml +0 -0
- {querri-1.0.8 → querri-2.0.0}/querri/_auth.py +0 -0
- {querri-1.0.8 → querri-2.0.0}/querri/_base_client.py +0 -0
- {querri-1.0.8 → querri-2.0.0}/querri/_config.py +0 -0
- {querri-1.0.8 → querri-2.0.0}/querri/_streaming.py +0 -0
- {querri-1.0.8 → querri-2.0.0}/querri/cli/__init__.py +0 -0
- {querri-1.0.8 → querri-2.0.0}/querri/cli/_app.py +0 -0
- {querri-1.0.8 → querri-2.0.0}/querri/cli/_context.py +0 -0
- {querri-1.0.8 → querri-2.0.0}/querri/cli/_image.py +0 -0
- {querri-1.0.8 → querri-2.0.0}/querri/cli/auth.py +0 -0
- {querri-1.0.8 → querri-2.0.0}/querri/cli/chat.py +0 -0
- {querri-1.0.8 → querri-2.0.0}/querri/cli/chats.py +0 -0
- {querri-1.0.8 → querri-2.0.0}/querri/cli/dashboards.py +0 -0
- {querri-1.0.8 → querri-2.0.0}/querri/cli/policies.py +0 -0
- {querri-1.0.8 → querri-2.0.0}/querri/cli/projects.py +0 -0
- {querri-1.0.8 → querri-2.0.0}/querri/cli/steps.py +0 -0
- {querri-1.0.8 → querri-2.0.0}/querri/cli/usage.py +0 -0
- {querri-1.0.8 → querri-2.0.0}/querri/cli/users.py +0 -0
- {querri-1.0.8 → querri-2.0.0}/querri/cli/whoami.py +0 -0
- {querri-1.0.8 → querri-2.0.0}/querri/py.typed +0 -0
- {querri-1.0.8 → querri-2.0.0}/querri/resources/__init__.py +0 -0
- {querri-1.0.8 → querri-2.0.0}/querri/resources/dashboards.py +0 -0
- {querri-1.0.8 → querri-2.0.0}/querri/resources/policies.py +0 -0
- {querri-1.0.8 → querri-2.0.0}/querri/resources/projects.py +0 -0
- {querri-1.0.8 → querri-2.0.0}/querri/resources/sharing.py +0 -0
- {querri-1.0.8 → querri-2.0.0}/querri/resources/usage.py +0 -0
- {querri-1.0.8 → querri-2.0.0}/querri/resources/users.py +0 -0
- {querri-1.0.8 → querri-2.0.0}/querri/types/__init__.py +0 -0
- {querri-1.0.8 → querri-2.0.0}/querri/types/audit.py +0 -0
- {querri-1.0.8 → querri-2.0.0}/querri/types/chat.py +0 -0
- {querri-1.0.8 → querri-2.0.0}/querri/types/dashboard.py +0 -0
- {querri-1.0.8 → querri-2.0.0}/querri/types/data.py +0 -0
- {querri-1.0.8 → querri-2.0.0}/querri/types/embed.py +0 -0
- {querri-1.0.8 → querri-2.0.0}/querri/types/file.py +0 -0
- {querri-1.0.8 → querri-2.0.0}/querri/types/key.py +0 -0
- {querri-1.0.8 → querri-2.0.0}/querri/types/policy.py +0 -0
- {querri-1.0.8 → querri-2.0.0}/querri/types/project.py +0 -0
- {querri-1.0.8 → querri-2.0.0}/querri/types/sharing.py +0 -0
- {querri-1.0.8 → querri-2.0.0}/querri/types/usage.py +0 -0
- {querri-1.0.8 → querri-2.0.0}/querri/types/user.py +0 -0
- {querri-1.0.8 → querri-2.0.0}/scripts/release.sh +0 -0
- {querri-1.0.8 → querri-2.0.0}/skills/README.md +0 -0
- {querri-1.0.8 → querri-2.0.0}/skills/querri-cli/references/json-shapes.md +0 -0
- {querri-1.0.8 → querri-2.0.0}/skills/querri-cli/references/troubleshooting.md +0 -0
- {querri-1.0.8 → querri-2.0.0}/tests/__init__.py +0 -0
- {querri-1.0.8 → querri-2.0.0}/tests/conftest.py +0 -0
- {querri-1.0.8 → querri-2.0.0}/tests/rls-integration-results.md +0 -0
- {querri-1.0.8 → querri-2.0.0}/tests/test_cli.py +0 -0
- {querri-1.0.8 → querri-2.0.0}/tests/test_client.py +0 -0
- {querri-1.0.8 → querri-2.0.0}/tests/test_config.py +0 -0
- {querri-1.0.8 → querri-2.0.0}/tests/test_convenience.py +0 -0
- {querri-1.0.8 → querri-2.0.0}/tests/test_exceptions.py +0 -0
- {querri-1.0.8 → querri-2.0.0}/tests/test_rls_integration.py +0 -0
- {querri-1.0.8 → querri-2.0.0}/tests/test_security.py +0 -0
- {querri-1.0.8 → querri-2.0.0}/tests/test_streaming.py +0 -0
- {querri-1.0.8 → querri-2.0.0}/tests/test_streaming_v2.py +0 -0
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 2.0.0 (2026-08-31)
|
|
4
|
+
|
|
5
|
+
Migration guide with before/after snippets: [docs/MIGRATION.md](docs/MIGRATION.md).
|
|
6
|
+
|
|
7
|
+
### Breaking
|
|
8
|
+
|
|
9
|
+
- **`source_scope` removed from `client.embed.create_session()`** (sync and
|
|
10
|
+
async). It was never enforced by the server — a silently-ignored parameter
|
|
11
|
+
that *looked* like access control. Passing it now raises `TypeError`. Scope
|
|
12
|
+
sessions with access policies instead: `client.embed.get_session(access=...)`.
|
|
13
|
+
- **`as_user()` / `UserQuerri` rebased from `/api` to `/api/v1`.** Embed
|
|
14
|
+
sessions (`X-Embed-Session`) are the public API's highest-priority
|
|
15
|
+
credential, so user-scoped clients now call the same public API paths as
|
|
16
|
+
the admin client.
|
|
17
|
+
- **`user_client.dashboards` is read-only.** Embed sessions exclude the
|
|
18
|
+
`admin:dashboards:write` scope server-side; the user-scoped surface now
|
|
19
|
+
exposes only `list()`, `get()`, and `refresh_status()` — `create`,
|
|
20
|
+
`update`, `delete`, and `refresh` no longer exist there (they always
|
|
21
|
+
belonged to the admin client).
|
|
22
|
+
- There is no `user_client.embed`: embed sessions exclude
|
|
23
|
+
`embed:session:create` server-side (a session cannot mint sessions).
|
|
24
|
+
The accessor never existed in the SDK; the contract is now documented
|
|
25
|
+
and tested.
|
|
26
|
+
- **`ttl` is validated client-side**: `create_session()` and
|
|
27
|
+
`get_session()` raise `ValueError` for TTLs outside [900, 86400] seconds
|
|
28
|
+
instead of sending a value the server would silently clamp.
|
|
29
|
+
|
|
30
|
+
### Added
|
|
31
|
+
|
|
32
|
+
- `client.embed.get_ui_config(org)` — fetches the public, unauthenticated
|
|
33
|
+
`GET {host}/api/embed/ui-config?org=...` (main app path, not `/api/v1`)
|
|
34
|
+
and returns the raw `{chrome, theme, privacy}` dict.
|
|
35
|
+
- `querri session ui-config --org <id>` CLI command for the same.
|
|
36
|
+
- `OriginRequiredError` (subclass of `ValidationError`), raised for
|
|
37
|
+
`400 origin_required` with a message explaining the org's embed-domain
|
|
38
|
+
allowlist and how to pass `origin`.
|
|
39
|
+
- `querri share source org` gained `--disable` to turn org-wide sharing
|
|
40
|
+
off (the server contract always supported `enabled: false`).
|
|
41
|
+
- `querri keys create` interactive scope picker now includes the
|
|
42
|
+
`admin:skills:*`, `admin:library:*` scopes and `*` (superadmin).
|
|
43
|
+
|
|
44
|
+
### Fixed
|
|
45
|
+
|
|
46
|
+
- `client.embed.revoke_user_sessions()` no longer stops at the first
|
|
47
|
+
listing page: the server caps listings at 200 with no cursor, so it now
|
|
48
|
+
loops revoke-and-relist until a listing shows no sessions for the user
|
|
49
|
+
(bounded at 50 passes), and tolerates a session expiring between list
|
|
50
|
+
and delete. Still best-effort — with more than 200 concurrent sessions
|
|
51
|
+
org-wide the listing is a sample and stragglers can survive; do not
|
|
52
|
+
treat a return as proof every session is gone.
|
|
53
|
+
|
|
54
|
+
## 1.1.1 (2026-08-31)
|
|
55
|
+
|
|
56
|
+
### Fixed
|
|
57
|
+
|
|
58
|
+
- **Web-embed example** (`examples/web-embed/server.py`): three dispatched
|
|
59
|
+
actions called keyword-only SDK methods positionally, raising `TypeError`
|
|
60
|
+
(returned as HTTP 500): `embed.refreshSession`, `users.getOrCreate`, and
|
|
61
|
+
`policies.resolve`. All now pass keyword arguments.
|
|
62
|
+
- **Web-embed example** (`examples/web-embed/index.html`): the SDK Explorer's
|
|
63
|
+
"Data" group sent `data.*` action names the server never dispatched — every
|
|
64
|
+
action in the group returned "Unknown action". Renamed to the real
|
|
65
|
+
`sources.*` actions.
|
|
66
|
+
- **Docs**: `docs/server-sdk.md` and `README.md` showed `create_session`,
|
|
67
|
+
`refresh_session`, and `get_session` with positional parameters; all three
|
|
68
|
+
are keyword-only (`create_session(*, user_id=..., origin=..., ttl=...)`,
|
|
69
|
+
`refresh_session(session_token=...)`). Snippets updated to keyword form.
|
|
70
|
+
- **Browser embed loading**: examples and docs now load the embed script from
|
|
71
|
+
the product — `<script src="{serverUrl}/sdk/querri-embed.js">` — instead of
|
|
72
|
+
an unverifiable CDN/npm IIFE path. The web-embed example injects the script
|
|
73
|
+
after fetching `serverUrl` from its `/api/config` endpoint.
|
|
74
|
+
- **Embed chrome options**: examples use the v2 vocabulary
|
|
75
|
+
(`chrome: { rail: { show: true }, header: { show: true } }` — `rail`
|
|
76
|
+
replaces `sidebar`).
|
|
77
|
+
- **Origin allowlist guidance**: create-session docs and examples now pass
|
|
78
|
+
`origin` and document the enforcement behavior — when the organization
|
|
79
|
+
configures allowed embed domains, session creation without an origin fails
|
|
80
|
+
with `400 origin_required`, and an origin not on the allowlist fails with
|
|
81
|
+
`403 origin_not_allowed`.
|
|
82
|
+
|
|
83
|
+
### Deprecated
|
|
84
|
+
|
|
85
|
+
- `source_scope` on `client.embed.create_session()` (sync and async): the
|
|
86
|
+
server does not enforce it, and it will be removed in v2.0.0. Passing it now
|
|
87
|
+
emits a `DeprecationWarning`; the field is still sent, so behavior is
|
|
88
|
+
unchanged. Scope sessions with access policies instead — see
|
|
89
|
+
`client.embed.get_session(access=...)`.
|
querri-2.0.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Querri, Inc.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: querri
|
|
3
|
-
Version:
|
|
3
|
+
Version: 2.0.0
|
|
4
4
|
Summary: Python SDK and CLI for the Querri data analysis platform
|
|
5
5
|
Project-URL: Homepage, https://querri.com
|
|
6
6
|
Project-URL: Documentation, https://docs.querri.com/sdk/python
|
|
7
7
|
Project-URL: Repository, https://github.com/Querri-inc/querri-python
|
|
8
8
|
Author-email: Querri <support@querri.com>
|
|
9
9
|
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
10
11
|
Classifier: Development Status :: 3 - Alpha
|
|
11
12
|
Classifier: Intended Audience :: Developers
|
|
12
13
|
Classifier: License :: OSI Approved :: MIT License
|
|
@@ -117,7 +118,7 @@ See [`skills/querri-cli/SKILL.md`](skills/querri-cli/SKILL.md) for the full comm
|
|
|
117
118
|
|
|
118
119
|
## Python SDK
|
|
119
120
|
|
|
120
|
-
For embedding Querri analytics in a Python web application.
|
|
121
|
+
For embedding Querri analytics in a Python web application. The backend SDK creates embed sessions; the frontend loads the embed script directly from your Querri server at `{serverUrl}/sdk/querri-embed.js` (a React wrapper is also available — see below).
|
|
121
122
|
|
|
122
123
|
### Quick Start
|
|
123
124
|
|
|
@@ -164,15 +165,52 @@ def querri_session():
|
|
|
164
165
|
|
|
165
166
|
Django and FastAPI follow the identical pattern — see **[docs/server-sdk.md](docs/server-sdk.md)** for those examples.
|
|
166
167
|
|
|
168
|
+
Always forward the request's `Origin` header as shown. If your organization configures an allowlist of embed domains, the server enforces it when the session is created: a missing origin fails with `400 origin_required`, and an origin not on the allowlist fails with `403 origin_not_allowed`. Without an allowlist, `origin` is accepted but not enforced.
|
|
169
|
+
|
|
170
|
+
### Add the embed (browser)
|
|
171
|
+
|
|
172
|
+
Load the embed script from your Querri server — it is served at `{serverUrl}/sdk/querri-embed.js` and always matches the server's version:
|
|
173
|
+
|
|
174
|
+
```html
|
|
175
|
+
<div id="querri" style="width: 100%; height: 600px;"></div>
|
|
176
|
+
<script src="https://app.querri.com/sdk/querri-embed.js"></script>
|
|
177
|
+
<script>
|
|
178
|
+
QuerriEmbed.create(document.getElementById('querri'), {
|
|
179
|
+
serverUrl: 'https://app.querri.com',
|
|
180
|
+
auth: {
|
|
181
|
+
fetchSessionToken: async () => {
|
|
182
|
+
const res = await fetch('/api/querri-session', { method: 'POST' });
|
|
183
|
+
const data = await res.json();
|
|
184
|
+
if (!res.ok) throw new Error(data.error || 'Session failed');
|
|
185
|
+
return data.session_token;
|
|
186
|
+
},
|
|
187
|
+
},
|
|
188
|
+
startView: '/home',
|
|
189
|
+
chrome: { rail: { show: true }, header: { show: true } },
|
|
190
|
+
});
|
|
191
|
+
</script>
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
Use the same host for the script tag and `serverUrl`. See [`examples/web-embed/`](examples/web-embed/) for a working page that fetches the server URL from its backend before injecting the script.
|
|
195
|
+
|
|
167
196
|
### Add the embed (React)
|
|
168
197
|
|
|
198
|
+
A React wrapper is available (requires `@querri-inc/embed` >= 1.0.0):
|
|
199
|
+
|
|
169
200
|
```tsx
|
|
170
201
|
import { QuerriEmbed } from '@querri-inc/embed/react';
|
|
171
202
|
|
|
172
203
|
<QuerriEmbed
|
|
173
204
|
style={{ width: '100%', height: '600px' }}
|
|
174
205
|
serverUrl="https://app.querri.com"
|
|
175
|
-
auth={{
|
|
206
|
+
auth={{
|
|
207
|
+
fetchSessionToken: async () => {
|
|
208
|
+
const res = await fetch('/api/querri-session', { method: 'POST' });
|
|
209
|
+
const data = await res.json();
|
|
210
|
+
if (!res.ok) throw new Error(data.error || 'Session failed');
|
|
211
|
+
return data.session_token;
|
|
212
|
+
},
|
|
213
|
+
}}
|
|
176
214
|
/>
|
|
177
215
|
```
|
|
178
216
|
|
|
@@ -244,7 +282,7 @@ See **[docs/server-sdk.md](docs/server-sdk.md#user-scoped-client-as_user)** for
|
|
|
244
282
|
|
|
245
283
|
| Resource | Access | Key Methods |
|
|
246
284
|
|----------|--------|-------------|
|
|
247
|
-
| `client.embed` | Embed sessions (flagship) | `get_session`, `create_session`, `refresh_session`, `revoke_session`, `list_sessions` |
|
|
285
|
+
| `client.embed` | Embed sessions (flagship) | `get_session`, `create_session`, `refresh_session`, `revoke_session`, `list_sessions`, `get_ui_config` |
|
|
248
286
|
| `client.policies` | Row-level access control | `setup`, `create`, `list`, `get`, `update`, `delete`, `assign_users`, `remove_user`, `resolve`, `columns` |
|
|
249
287
|
| `client.users` | User management | `list`, `create`, `get`, `get_or_create`, `update`, `delete` |
|
|
250
288
|
| `client.dashboards` | Dashboard management | `list`, `create`, `get`, `update`, `delete`, `refresh`, `refresh_status` |
|
|
@@ -280,6 +318,7 @@ All errors extend `QuerriError`:
|
|
|
280
318
|
QuerriError
|
|
281
319
|
├── APIError — HTTP error responses
|
|
282
320
|
│ ├── ValidationError — 400
|
|
321
|
+
│ │ └── OriginRequiredError — 400 origin_required (org has an embed-domain allowlist)
|
|
283
322
|
│ ├── AuthenticationError — 401
|
|
284
323
|
│ ├── PermissionError — 403
|
|
285
324
|
│ ├── NotFoundError — 404
|
|
@@ -315,3 +354,7 @@ pytest tests/test_integration.py -m integration -v # requires API credentials
|
|
|
315
354
|
## License
|
|
316
355
|
|
|
317
356
|
MIT
|
|
357
|
+
|
|
358
|
+
## Support policy
|
|
359
|
+
|
|
360
|
+
The 1.x line receives security fixes only, for 6 months from the 2.0.0 release (2026-08-31). Migrate with [docs/MIGRATION.md](docs/MIGRATION.md).
|
|
@@ -72,7 +72,7 @@ See [`skills/querri-cli/SKILL.md`](skills/querri-cli/SKILL.md) for the full comm
|
|
|
72
72
|
|
|
73
73
|
## Python SDK
|
|
74
74
|
|
|
75
|
-
For embedding Querri analytics in a Python web application.
|
|
75
|
+
For embedding Querri analytics in a Python web application. The backend SDK creates embed sessions; the frontend loads the embed script directly from your Querri server at `{serverUrl}/sdk/querri-embed.js` (a React wrapper is also available — see below).
|
|
76
76
|
|
|
77
77
|
### Quick Start
|
|
78
78
|
|
|
@@ -119,15 +119,52 @@ def querri_session():
|
|
|
119
119
|
|
|
120
120
|
Django and FastAPI follow the identical pattern — see **[docs/server-sdk.md](docs/server-sdk.md)** for those examples.
|
|
121
121
|
|
|
122
|
+
Always forward the request's `Origin` header as shown. If your organization configures an allowlist of embed domains, the server enforces it when the session is created: a missing origin fails with `400 origin_required`, and an origin not on the allowlist fails with `403 origin_not_allowed`. Without an allowlist, `origin` is accepted but not enforced.
|
|
123
|
+
|
|
124
|
+
### Add the embed (browser)
|
|
125
|
+
|
|
126
|
+
Load the embed script from your Querri server — it is served at `{serverUrl}/sdk/querri-embed.js` and always matches the server's version:
|
|
127
|
+
|
|
128
|
+
```html
|
|
129
|
+
<div id="querri" style="width: 100%; height: 600px;"></div>
|
|
130
|
+
<script src="https://app.querri.com/sdk/querri-embed.js"></script>
|
|
131
|
+
<script>
|
|
132
|
+
QuerriEmbed.create(document.getElementById('querri'), {
|
|
133
|
+
serverUrl: 'https://app.querri.com',
|
|
134
|
+
auth: {
|
|
135
|
+
fetchSessionToken: async () => {
|
|
136
|
+
const res = await fetch('/api/querri-session', { method: 'POST' });
|
|
137
|
+
const data = await res.json();
|
|
138
|
+
if (!res.ok) throw new Error(data.error || 'Session failed');
|
|
139
|
+
return data.session_token;
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
startView: '/home',
|
|
143
|
+
chrome: { rail: { show: true }, header: { show: true } },
|
|
144
|
+
});
|
|
145
|
+
</script>
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Use the same host for the script tag and `serverUrl`. See [`examples/web-embed/`](examples/web-embed/) for a working page that fetches the server URL from its backend before injecting the script.
|
|
149
|
+
|
|
122
150
|
### Add the embed (React)
|
|
123
151
|
|
|
152
|
+
A React wrapper is available (requires `@querri-inc/embed` >= 1.0.0):
|
|
153
|
+
|
|
124
154
|
```tsx
|
|
125
155
|
import { QuerriEmbed } from '@querri-inc/embed/react';
|
|
126
156
|
|
|
127
157
|
<QuerriEmbed
|
|
128
158
|
style={{ width: '100%', height: '600px' }}
|
|
129
159
|
serverUrl="https://app.querri.com"
|
|
130
|
-
auth={{
|
|
160
|
+
auth={{
|
|
161
|
+
fetchSessionToken: async () => {
|
|
162
|
+
const res = await fetch('/api/querri-session', { method: 'POST' });
|
|
163
|
+
const data = await res.json();
|
|
164
|
+
if (!res.ok) throw new Error(data.error || 'Session failed');
|
|
165
|
+
return data.session_token;
|
|
166
|
+
},
|
|
167
|
+
}}
|
|
131
168
|
/>
|
|
132
169
|
```
|
|
133
170
|
|
|
@@ -199,7 +236,7 @@ See **[docs/server-sdk.md](docs/server-sdk.md#user-scoped-client-as_user)** for
|
|
|
199
236
|
|
|
200
237
|
| Resource | Access | Key Methods |
|
|
201
238
|
|----------|--------|-------------|
|
|
202
|
-
| `client.embed` | Embed sessions (flagship) | `get_session`, `create_session`, `refresh_session`, `revoke_session`, `list_sessions` |
|
|
239
|
+
| `client.embed` | Embed sessions (flagship) | `get_session`, `create_session`, `refresh_session`, `revoke_session`, `list_sessions`, `get_ui_config` |
|
|
203
240
|
| `client.policies` | Row-level access control | `setup`, `create`, `list`, `get`, `update`, `delete`, `assign_users`, `remove_user`, `resolve`, `columns` |
|
|
204
241
|
| `client.users` | User management | `list`, `create`, `get`, `get_or_create`, `update`, `delete` |
|
|
205
242
|
| `client.dashboards` | Dashboard management | `list`, `create`, `get`, `update`, `delete`, `refresh`, `refresh_status` |
|
|
@@ -235,6 +272,7 @@ All errors extend `QuerriError`:
|
|
|
235
272
|
QuerriError
|
|
236
273
|
├── APIError — HTTP error responses
|
|
237
274
|
│ ├── ValidationError — 400
|
|
275
|
+
│ │ └── OriginRequiredError — 400 origin_required (org has an embed-domain allowlist)
|
|
238
276
|
│ ├── AuthenticationError — 401
|
|
239
277
|
│ ├── PermissionError — 403
|
|
240
278
|
│ ├── NotFoundError — 404
|
|
@@ -270,3 +308,7 @@ pytest tests/test_integration.py -m integration -v # requires API credentials
|
|
|
270
308
|
## License
|
|
271
309
|
|
|
272
310
|
MIT
|
|
311
|
+
|
|
312
|
+
## Support policy
|
|
313
|
+
|
|
314
|
+
The 1.x line receives security fixes only, for 6 months from the 2.0.0 release (2026-08-31). Migrate with [docs/MIGRATION.md](docs/MIGRATION.md).
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# Migrating from 1.x to 2.0
|
|
2
|
+
|
|
3
|
+
v2.0.0 removes a fake security parameter, aligns the user-scoped client with
|
|
4
|
+
what the server actually authorizes, and makes two silent server behaviors
|
|
5
|
+
loud. Most 1.x code runs unchanged; the breaking changes are below, each with
|
|
6
|
+
before/after.
|
|
7
|
+
|
|
8
|
+
## `source_scope` removed from `create_session()`
|
|
9
|
+
|
|
10
|
+
**This is a security fix, not a cleanup.** `source_scope` was accepted by the
|
|
11
|
+
SDK and sent to the server, but the server never enforced it. Code that passed
|
|
12
|
+
it got sessions that *looked* scoped to specific sources and were not — a
|
|
13
|
+
silently-ignored access-control parameter is worse than none, because it reads
|
|
14
|
+
as a guarantee. v1.1.1 started warning; v2.0.0 removes it, so passing it now
|
|
15
|
+
raises `TypeError`.
|
|
16
|
+
|
|
17
|
+
Scope sessions with access policies, which the server enforces:
|
|
18
|
+
|
|
19
|
+
```python
|
|
20
|
+
# Before (1.x) — source_scope was silently ignored
|
|
21
|
+
session = client.embed.create_session(
|
|
22
|
+
user_id="usr_1",
|
|
23
|
+
source_scope=["src_sales"], # did nothing
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
# After (2.0) — access policies are enforced server-side
|
|
27
|
+
session = client.embed.get_session(
|
|
28
|
+
user="customer-42",
|
|
29
|
+
access={"sources": ["src_sales"], "filters": {"tenant_id": "acme"}},
|
|
30
|
+
)
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
`get_session()` resolves the user, finds-or-creates a policy from the access
|
|
34
|
+
spec, assigns it, and mints the session in one call. If you manage policies
|
|
35
|
+
yourself, use `client.policies.setup(...)` / `assign_users(...)` and then
|
|
36
|
+
`create_session(user_id=...)` — the session inherits whatever the user's
|
|
37
|
+
policies grant.
|
|
38
|
+
|
|
39
|
+
## `as_user()` now calls `/api/v1`
|
|
40
|
+
|
|
41
|
+
`UserQuerri` previously called the internal API at `{host}/api`. Embed
|
|
42
|
+
sessions are the public API's highest-priority credential, so user-scoped
|
|
43
|
+
clients now call the same `/api/v1` paths as the admin client, still with the
|
|
44
|
+
`X-Embed-Session` header. No code change needed unless you depended on the
|
|
45
|
+
URL (e.g. in proxies, allowlists, or request logging):
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
# Same code in 1.x and 2.0 — only the wire paths changed:
|
|
49
|
+
# 1.x: GET {host}/api/projects
|
|
50
|
+
# 2.0: GET {host}/api/v1/projects
|
|
51
|
+
with client.as_user(session) as uc:
|
|
52
|
+
for project in uc.projects.list():
|
|
53
|
+
print(project.name)
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## User-scoped dashboards are read-only
|
|
57
|
+
|
|
58
|
+
Embed sessions exclude the `admin:dashboards:write` scope server-side —
|
|
59
|
+
writes always failed with 403. The SDK surface now says so:
|
|
60
|
+
`user_client.dashboards` exposes `list()`, `get()`, and `refresh_status()`
|
|
61
|
+
only.
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
# Before (1.x) — compiled, then 403'd at runtime
|
|
65
|
+
with client.as_user(session) as uc:
|
|
66
|
+
uc.dashboards.delete("dash_1") # AttributeError in 2.0
|
|
67
|
+
|
|
68
|
+
# After (2.0) — dashboard writes belong to the admin client
|
|
69
|
+
client.dashboards.delete("dash_1")
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Similarly, there is no `user_client.embed` (there never was one that worked:
|
|
73
|
+
`embed:session:create` is excluded from embed-session scopes). Mint and
|
|
74
|
+
manage sessions from the admin client.
|
|
75
|
+
|
|
76
|
+
## `ttl` outside [900, 86400] raises `ValueError`
|
|
77
|
+
|
|
78
|
+
The server silently clamps out-of-range TTLs. The SDK now refuses to send a
|
|
79
|
+
TTL that would not be honored as given:
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
# Before (1.x) — sent ttl=60, server silently created a 900s session
|
|
83
|
+
client.embed.create_session(user_id="usr_1", ttl=60)
|
|
84
|
+
|
|
85
|
+
# After (2.0) — fails loudly before any HTTP
|
|
86
|
+
# ValueError: ttl must be between 900 and 86400 seconds, got 60
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## `400 origin_required` raises `OriginRequiredError`
|
|
90
|
+
|
|
91
|
+
When the org configures an embed-domain allowlist and no `origin` is passed,
|
|
92
|
+
the SDK now raises `OriginRequiredError` — a subclass of `ValidationError`,
|
|
93
|
+
so existing `except ValidationError` handlers keep working — with a message
|
|
94
|
+
that says what to do (pass the embedding page's origin):
|
|
95
|
+
|
|
96
|
+
```python
|
|
97
|
+
from querri import OriginRequiredError
|
|
98
|
+
|
|
99
|
+
try:
|
|
100
|
+
session = client.embed.create_session(user_id="usr_1")
|
|
101
|
+
except OriginRequiredError:
|
|
102
|
+
session = client.embed.create_session(
|
|
103
|
+
user_id="usr_1", origin="https://app.customer.com"
|
|
104
|
+
)
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## `revoke_user_sessions()` paginates
|
|
108
|
+
|
|
109
|
+
In 1.x it revoked at most whatever one listing returned (the server caps
|
|
110
|
+
listings at 200 with no cursor). It now loops revoke-and-relist until a
|
|
111
|
+
listing shows no sessions for the user. The signature and return value are
|
|
112
|
+
unchanged; only the completeness improved — expect more requests (and a
|
|
113
|
+
larger returned count) for users with many sessions.
|
|
114
|
+
|
|
115
|
+
## New in 2.0 (non-breaking)
|
|
116
|
+
|
|
117
|
+
- `client.embed.get_ui_config(org)` — the public embed UI config
|
|
118
|
+
(`{chrome, theme, privacy}`) from `GET {host}/api/embed/ui-config?org=...`.
|
|
119
|
+
- `querri session ui-config --org <id>` — CLI for the same.
|
|
120
|
+
- `querri share source org SOURCE_ID --disable` — turn org-wide sharing off.
|