querri 1.0.2__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-1.0.2/.github/CONTRIBUTING.md +60 -0
- querri-1.0.2/.github/ISSUE_TEMPLATE/bug_report.md +37 -0
- querri-1.0.2/.github/ISSUE_TEMPLATE/feature_request.md +21 -0
- querri-1.0.2/.github/PULL_REQUEST_TEMPLATE.md +19 -0
- querri-1.0.2/.github/SECURITY.md +31 -0
- querri-1.0.2/.github/workflows/ci.yml +25 -0
- querri-1.0.2/.github/workflows/release.yml +42 -0
- querri-1.0.2/.gitignore +34 -0
- querri-1.0.2/CLAUDE.md +93 -0
- querri-1.0.2/PKG-INFO +310 -0
- querri-1.0.2/README.md +265 -0
- querri-1.0.2/docs/server-sdk.md +1704 -0
- querri-1.0.2/examples/01_quick_start.py +36 -0
- querri-1.0.2/examples/02_user_management.py +88 -0
- querri-1.0.2/examples/03_access_policies.py +122 -0
- querri-1.0.2/examples/04_embed_session.py +74 -0
- querri-1.0.2/examples/05_get_session.py +131 -0
- querri-1.0.2/examples/06_projects.py +109 -0
- querri-1.0.2/examples/07_dashboards.py +75 -0
- querri-1.0.2/examples/08_chat_streaming.py +82 -0
- querri-1.0.2/examples/09_async_client.py +98 -0
- querri-1.0.2/examples/10_error_handling.py +105 -0
- querri-1.0.2/examples/11_user_scoped_client.py +95 -0
- querri-1.0.2/examples/12_data_operations.py +112 -0
- querri-1.0.2/examples/web-embed/index.html +344 -0
- querri-1.0.2/examples/web-embed/server.py +451 -0
- querri-1.0.2/pyproject.toml +83 -0
- querri-1.0.2/querri/__init__.py +51 -0
- querri-1.0.2/querri/_auth.py +818 -0
- querri-1.0.2/querri/_base_client.py +379 -0
- querri-1.0.2/querri/_client.py +395 -0
- querri-1.0.2/querri/_config.py +140 -0
- querri-1.0.2/querri/_convenience.py +538 -0
- querri-1.0.2/querri/_exceptions.py +180 -0
- querri-1.0.2/querri/_pagination.py +278 -0
- querri-1.0.2/querri/_streaming.py +663 -0
- querri-1.0.2/querri/_user_client.py +193 -0
- querri-1.0.2/querri/_version.py +1 -0
- querri-1.0.2/querri/cli/__init__.py +21 -0
- querri-1.0.2/querri/cli/_app.py +246 -0
- querri-1.0.2/querri/cli/_context.py +216 -0
- querri-1.0.2/querri/cli/_image.py +234 -0
- querri-1.0.2/querri/cli/_output.py +293 -0
- querri-1.0.2/querri/cli/audit.py +68 -0
- querri-1.0.2/querri/cli/auth.py +556 -0
- querri-1.0.2/querri/cli/chat.py +1255 -0
- querri-1.0.2/querri/cli/chats.py +358 -0
- querri-1.0.2/querri/cli/dashboards.py +294 -0
- querri-1.0.2/querri/cli/embed.py +220 -0
- querri-1.0.2/querri/cli/files.py +214 -0
- querri-1.0.2/querri/cli/keys.py +304 -0
- querri-1.0.2/querri/cli/policies.py +424 -0
- querri-1.0.2/querri/cli/projects.py +916 -0
- querri-1.0.2/querri/cli/sharing.py +403 -0
- querri-1.0.2/querri/cli/sources.py +532 -0
- querri-1.0.2/querri/cli/steps.py +123 -0
- querri-1.0.2/querri/cli/usage.py +99 -0
- querri-1.0.2/querri/cli/users.py +223 -0
- querri-1.0.2/querri/cli/views.py +449 -0
- querri-1.0.2/querri/cli/whoami.py +78 -0
- querri-1.0.2/querri/py.typed +0 -0
- querri-1.0.2/querri/resources/__init__.py +1 -0
- querri-1.0.2/querri/resources/audit.py +119 -0
- querri-1.0.2/querri/resources/dashboards.py +295 -0
- querri-1.0.2/querri/resources/embed.py +268 -0
- querri-1.0.2/querri/resources/files.py +145 -0
- querri-1.0.2/querri/resources/keys.py +191 -0
- querri-1.0.2/querri/resources/policies.py +492 -0
- querri-1.0.2/querri/resources/projects.py +808 -0
- querri-1.0.2/querri/resources/sharing.py +330 -0
- querri-1.0.2/querri/resources/sources.py +511 -0
- querri-1.0.2/querri/resources/usage.py +106 -0
- querri-1.0.2/querri/resources/users.py +303 -0
- querri-1.0.2/querri/resources/views.py +347 -0
- querri-1.0.2/querri/types/__init__.py +104 -0
- querri-1.0.2/querri/types/audit.py +21 -0
- querri-1.0.2/querri/types/chat.py +51 -0
- querri-1.0.2/querri/types/dashboard.py +51 -0
- querri-1.0.2/querri/types/data.py +61 -0
- querri-1.0.2/querri/types/embed.py +38 -0
- querri-1.0.2/querri/types/file.py +18 -0
- querri-1.0.2/querri/types/key.py +36 -0
- querri-1.0.2/querri/types/policy.py +105 -0
- querri-1.0.2/querri/types/project.py +158 -0
- querri-1.0.2/querri/types/sharing.py +19 -0
- querri-1.0.2/querri/types/usage.py +39 -0
- querri-1.0.2/querri/types/user.py +33 -0
- querri-1.0.2/skills/README.md +28 -0
- querri-1.0.2/skills/querri-cli/SKILL.md +301 -0
- querri-1.0.2/skills/querri-cli/references/json-shapes.md +135 -0
- querri-1.0.2/skills/querri-cli/references/troubleshooting.md +115 -0
- querri-1.0.2/tests/__init__.py +0 -0
- querri-1.0.2/tests/conftest.py +19 -0
- querri-1.0.2/tests/rls-integration-results.md +81 -0
- querri-1.0.2/tests/test_cli.py +530 -0
- querri-1.0.2/tests/test_cli_commands.py +2108 -0
- querri-1.0.2/tests/test_client.py +166 -0
- querri-1.0.2/tests/test_config.py +137 -0
- querri-1.0.2/tests/test_convenience.py +169 -0
- querri-1.0.2/tests/test_exceptions.py +162 -0
- querri-1.0.2/tests/test_integration.py +312 -0
- querri-1.0.2/tests/test_new_features.py +257 -0
- querri-1.0.2/tests/test_pagination.py +278 -0
- querri-1.0.2/tests/test_resources.py +2151 -0
- querri-1.0.2/tests/test_rls_integration.py +593 -0
- querri-1.0.2/tests/test_security.py +422 -0
- querri-1.0.2/tests/test_streaming.py +207 -0
- querri-1.0.2/tests/test_streaming_v2.py +528 -0
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# Contributing to querri
|
|
2
|
+
|
|
3
|
+
Thanks for your interest in contributing! This guide will help you get started.
|
|
4
|
+
|
|
5
|
+
## Getting started
|
|
6
|
+
|
|
7
|
+
1. Fork and clone the repository
|
|
8
|
+
2. Install dependencies:
|
|
9
|
+
```sh
|
|
10
|
+
pip install -e ".[dev]"
|
|
11
|
+
```
|
|
12
|
+
3. Run tests:
|
|
13
|
+
```sh
|
|
14
|
+
pytest tests/ -v
|
|
15
|
+
```
|
|
16
|
+
4. Run linting and type checking:
|
|
17
|
+
```sh
|
|
18
|
+
ruff check querri/ tests/
|
|
19
|
+
mypy querri/
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Project structure
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
querri/
|
|
26
|
+
resources/ API resource classes (Users, Projects, Dashboards, Sources, Data, etc.)
|
|
27
|
+
types/ Pydantic v2 models for API request/response types
|
|
28
|
+
_client.py Querri / AsyncQuerri public entry points
|
|
29
|
+
_base_client.py SyncHTTPClient / AsyncHTTPClient with auth, retries, error mapping
|
|
30
|
+
_user_client.py UserQuerri / AsyncUserQuerri for embed session auth
|
|
31
|
+
_config.py Configuration resolution (constructor args > env vars > defaults)
|
|
32
|
+
_pagination.py Cursor-based auto-paginating iterators
|
|
33
|
+
_streaming.py SSE chat streaming (Vercel AI SDK format)
|
|
34
|
+
_convenience.py High-level helpers (get_session, policy setup)
|
|
35
|
+
_exceptions.py Exception hierarchy (QuerriError, APIError, etc.)
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
- **Tests**: [pytest](https://docs.pytest.org/) + [pytest-asyncio](https://pytest-asyncio.readthedocs.io/) with [respx](https://lundberg.github.io/respx/) for HTTP mocking
|
|
39
|
+
- **Type checking**: [mypy](https://mypy-lang.org/) in strict mode
|
|
40
|
+
- **Linting**: [ruff](https://docs.astral.sh/ruff/)
|
|
41
|
+
|
|
42
|
+
## Submitting changes
|
|
43
|
+
|
|
44
|
+
1. Create a branch from `main`
|
|
45
|
+
2. Make your changes with clear, descriptive commits
|
|
46
|
+
3. Ensure CI passes:
|
|
47
|
+
```sh
|
|
48
|
+
pytest tests/ -v
|
|
49
|
+
ruff check querri/ tests/
|
|
50
|
+
mypy querri/
|
|
51
|
+
```
|
|
52
|
+
4. Open a pull request against `main`
|
|
53
|
+
|
|
54
|
+
## Reporting bugs
|
|
55
|
+
|
|
56
|
+
Please use the [bug report template](https://github.com/querri-ai/querri-python/issues/new?template=bug_report.md) when filing issues.
|
|
57
|
+
|
|
58
|
+
## Code of conduct
|
|
59
|
+
|
|
60
|
+
Be respectful and constructive. We're building something together — treat others the way you'd like to be treated.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Bug report
|
|
3
|
+
about: Report a bug to help us improve querri
|
|
4
|
+
labels: bug
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Describe the bug
|
|
8
|
+
|
|
9
|
+
A clear and concise description of what the bug is.
|
|
10
|
+
|
|
11
|
+
## To reproduce
|
|
12
|
+
|
|
13
|
+
Steps to reproduce the behavior:
|
|
14
|
+
|
|
15
|
+
1. Install `querri` version '...'
|
|
16
|
+
2. Call the '...' method
|
|
17
|
+
3. Pass the following options '...'
|
|
18
|
+
4. See error
|
|
19
|
+
|
|
20
|
+
## Expected behavior
|
|
21
|
+
|
|
22
|
+
A clear and concise description of what you expected to happen.
|
|
23
|
+
|
|
24
|
+
## Environment
|
|
25
|
+
|
|
26
|
+
- **querri version**: [e.g. 0.1.0]
|
|
27
|
+
- **Python version**: [e.g. 3.12.0]
|
|
28
|
+
- **Framework**: [e.g. Django 5, Flask 3, FastAPI 0.110, Plain Python]
|
|
29
|
+
- **OS**: [e.g. macOS 14, Ubuntu 24.04, Windows 11]
|
|
30
|
+
|
|
31
|
+
## Screenshots
|
|
32
|
+
|
|
33
|
+
If applicable, add screenshots to help explain the issue.
|
|
34
|
+
|
|
35
|
+
## Additional context
|
|
36
|
+
|
|
37
|
+
Add any other context about the problem here.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Feature request
|
|
3
|
+
about: Suggest an idea for querri
|
|
4
|
+
labels: enhancement
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Is your feature request related to a problem?
|
|
8
|
+
|
|
9
|
+
A clear and concise description of the problem. E.g. "I'm always frustrated when [...]"
|
|
10
|
+
|
|
11
|
+
## Describe the solution you'd like
|
|
12
|
+
|
|
13
|
+
A clear and concise description of what you want to happen.
|
|
14
|
+
|
|
15
|
+
## Alternatives considered
|
|
16
|
+
|
|
17
|
+
A clear and concise description of any alternative solutions or features you've considered.
|
|
18
|
+
|
|
19
|
+
## Additional context
|
|
20
|
+
|
|
21
|
+
Add any other context or screenshots about the feature request here.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
## Description
|
|
2
|
+
|
|
3
|
+
<!-- Summarize your changes and link any related issues (e.g. "Fixes #123"). -->
|
|
4
|
+
|
|
5
|
+
## Type of change
|
|
6
|
+
|
|
7
|
+
- [ ] Bug fix (non-breaking change that fixes an issue)
|
|
8
|
+
- [ ] New feature (non-breaking change that adds functionality)
|
|
9
|
+
- [ ] Breaking change (fix or feature that would cause existing functionality to change)
|
|
10
|
+
- [ ] Documentation update
|
|
11
|
+
|
|
12
|
+
## Checklist
|
|
13
|
+
|
|
14
|
+
- [ ] I have performed a self-review of my code
|
|
15
|
+
- [ ] I have added tests that prove my fix or feature works
|
|
16
|
+
- [ ] `pytest tests/ -v` passes locally
|
|
17
|
+
- [ ] `ruff check querri/ tests/` passes locally
|
|
18
|
+
- [ ] `mypy querri/` passes locally
|
|
19
|
+
- [ ] I have updated documentation if needed
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Security Policy
|
|
2
|
+
|
|
3
|
+
## Reporting a vulnerability
|
|
4
|
+
|
|
5
|
+
If you discover a security vulnerability in `querri`, please report it responsibly.
|
|
6
|
+
|
|
7
|
+
**Do not open a public issue.** Instead, email **security@querri.com** with:
|
|
8
|
+
|
|
9
|
+
- A description of the vulnerability
|
|
10
|
+
- Steps to reproduce it
|
|
11
|
+
- The affected version(s)
|
|
12
|
+
|
|
13
|
+
## Response timeline
|
|
14
|
+
|
|
15
|
+
- We will acknowledge your report within **48 hours**
|
|
16
|
+
- We will provide an initial assessment and next steps shortly after
|
|
17
|
+
|
|
18
|
+
## Process
|
|
19
|
+
|
|
20
|
+
1. Confirm the vulnerability and identify affected versions
|
|
21
|
+
2. Develop and test a fix
|
|
22
|
+
3. Release a patched version
|
|
23
|
+
4. Publicly disclose the issue after the fix is available
|
|
24
|
+
|
|
25
|
+
## Scope
|
|
26
|
+
|
|
27
|
+
This policy applies to the `querri` PyPI package. For vulnerabilities in the Querri platform itself, please contact security@querri.com directly.
|
|
28
|
+
|
|
29
|
+
## Feedback
|
|
30
|
+
|
|
31
|
+
Suggestions for improving this process are welcome — feel free to open a pull request.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
tags: ['v*']
|
|
7
|
+
pull_request:
|
|
8
|
+
branches: [main]
|
|
9
|
+
workflow_call:
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
test:
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
strategy:
|
|
15
|
+
matrix:
|
|
16
|
+
python-version: ['3.10', '3.11', '3.12', '3.13']
|
|
17
|
+
steps:
|
|
18
|
+
- uses: actions/checkout@v4
|
|
19
|
+
- uses: actions/setup-python@v5
|
|
20
|
+
with:
|
|
21
|
+
python-version: ${{ matrix.python-version }}
|
|
22
|
+
- run: pip install -e ".[dev]"
|
|
23
|
+
- run: ruff check querri/ tests/
|
|
24
|
+
- run: mypy querri/
|
|
25
|
+
- run: pytest tests/ -v
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- 'v[0-9]+.[0-9]+.[0-9]+*'
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: write
|
|
10
|
+
id-token: write
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
ci:
|
|
14
|
+
uses: ./.github/workflows/ci.yml
|
|
15
|
+
|
|
16
|
+
release:
|
|
17
|
+
needs: ci
|
|
18
|
+
runs-on: ubuntu-latest
|
|
19
|
+
steps:
|
|
20
|
+
- uses: actions/checkout@v4
|
|
21
|
+
|
|
22
|
+
- name: Create GitHub Release
|
|
23
|
+
uses: softprops/action-gh-release@v2
|
|
24
|
+
with:
|
|
25
|
+
tag_name: ${{ github.ref_name }}
|
|
26
|
+
name: ${{ github.ref_name }}
|
|
27
|
+
generate_release_notes: true
|
|
28
|
+
draft: false
|
|
29
|
+
prerelease: ${{ contains(github.ref_name, '-') }}
|
|
30
|
+
|
|
31
|
+
publish:
|
|
32
|
+
needs: ci
|
|
33
|
+
runs-on: ubuntu-latest
|
|
34
|
+
environment: pypi
|
|
35
|
+
steps:
|
|
36
|
+
- uses: actions/checkout@v4
|
|
37
|
+
- uses: actions/setup-python@v5
|
|
38
|
+
with:
|
|
39
|
+
python-version: '3.13'
|
|
40
|
+
- run: pip install build
|
|
41
|
+
- run: python -m build
|
|
42
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
querri-1.0.2/.gitignore
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# Distribution / packaging
|
|
7
|
+
dist/
|
|
8
|
+
build/
|
|
9
|
+
*.egg-info/
|
|
10
|
+
*.egg
|
|
11
|
+
|
|
12
|
+
# Virtual environments
|
|
13
|
+
.venv/
|
|
14
|
+
venv/
|
|
15
|
+
env/
|
|
16
|
+
|
|
17
|
+
# IDE
|
|
18
|
+
.idea/
|
|
19
|
+
.vscode/
|
|
20
|
+
*.swp
|
|
21
|
+
*.swo
|
|
22
|
+
|
|
23
|
+
# OS
|
|
24
|
+
.DS_Store
|
|
25
|
+
Thumbs.db
|
|
26
|
+
|
|
27
|
+
# Environment variables
|
|
28
|
+
.env
|
|
29
|
+
.env.*
|
|
30
|
+
|
|
31
|
+
# Testing
|
|
32
|
+
.pytest_cache/
|
|
33
|
+
.coverage
|
|
34
|
+
htmlcov/
|
querri-1.0.2/CLAUDE.md
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
## Project Overview
|
|
6
|
+
|
|
7
|
+
Querri Python SDK — a typed Python client for the Querri API, providing both sync (`Querri`) and async (`AsyncQuerri`) clients. Built on httpx + Pydantic v2. Requires Python >=3.9.
|
|
8
|
+
|
|
9
|
+
## Commands
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
# Install in development mode (includes test + lint + type-check tools)
|
|
13
|
+
pip install -e ".[dev]"
|
|
14
|
+
|
|
15
|
+
# Run all unit tests
|
|
16
|
+
pytest tests/ -v
|
|
17
|
+
|
|
18
|
+
# Run a single test file
|
|
19
|
+
pytest tests/test_client.py -v
|
|
20
|
+
|
|
21
|
+
# Run a single test by name
|
|
22
|
+
pytest tests/ -k "test_name" -v
|
|
23
|
+
|
|
24
|
+
# Run integration tests (requires QUERRI_API_KEY and QUERRI_ORG_ID env vars)
|
|
25
|
+
pytest tests/test_integration.py -m integration -v
|
|
26
|
+
|
|
27
|
+
# Type checking
|
|
28
|
+
mypy querri/
|
|
29
|
+
|
|
30
|
+
# Linting
|
|
31
|
+
ruff check querri/ tests/
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Architecture
|
|
35
|
+
|
|
36
|
+
### Client Layer
|
|
37
|
+
`Querri` / `AsyncQuerri` (`_client.py`) are the public entry points. They hold lazily-initialized resource namespaces (e.g., `client.users`, `client.projects`) and support context-manager usage. They delegate HTTP to `SyncHTTPClient` / `AsyncHTTPClient` (`_base_client.py`), which handle auth headers (Bearer token + X-Tenant-ID), retries with exponential backoff, and error-to-exception mapping.
|
|
38
|
+
|
|
39
|
+
### User-Scoped Client
|
|
40
|
+
`UserQuerri` / `AsyncUserQuerri` (`_user_client.py`) are created via `client.as_user(session)`. They use `X-Embed-Session` auth against the internal API (`/api` instead of `/api/v1`). Only expose FGA-filtered resources: `projects`, `dashboards`, `sources`, `data`, `chats`.
|
|
41
|
+
|
|
42
|
+
### Resource Pattern
|
|
43
|
+
Each API resource lives in `querri/resources/` with paired sync/async classes (e.g., `Users` / `AsyncUsers`). Resources receive the HTTP client at init and expose CRUD methods that return Pydantic models from `querri/types/`. Projects has a nested `Chats` sub-resource for chat streaming.
|
|
44
|
+
|
|
45
|
+
### Key Subsystems
|
|
46
|
+
- **Pagination** (`_pagination.py`): `SyncCursorPage[T]` / `AsyncCursorPage[T]` — cursor-based with offset fallback, auto-paginating via `__iter__`/`__aiter__`.
|
|
47
|
+
- **Streaming** (`_streaming.py`): `ChatStream` / `AsyncChatStream` — SSE parsing (Vercel AI SDK format: `0:` text, `e:` error, `d:` done).
|
|
48
|
+
- **Convenience** (`_convenience.py`): `get_session()` is the flagship method — resolves/creates a user, applies access policies, and creates an embed session in one call. `client.policies.setup()` creates a policy and assigns users in one call.
|
|
49
|
+
- **Config** (`_config.py`): Constructor args > env vars (`QUERRI_API_KEY`, `QUERRI_ORG_ID`, `QUERRI_HOST`, `QUERRI_TIMEOUT`, `QUERRI_MAX_RETRIES`) > defaults.
|
|
50
|
+
- **Exceptions** (`_exceptions.py`): `QuerriError` → `APIError` (with status-specific subclasses like `RateLimitError`), `StreamError`, `ConfigError`.
|
|
51
|
+
|
|
52
|
+
## Sync/Async Parity
|
|
53
|
+
|
|
54
|
+
Every resource, pagination class, and stream has mirrored sync/async implementations. When modifying code:
|
|
55
|
+
- Always change both sync and async variants simultaneously.
|
|
56
|
+
- Async methods use `await` and `async for` but share identical method signatures and return types.
|
|
57
|
+
- In resource files: sync class comes first, async class second.
|
|
58
|
+
- In `_convenience.py`: shared helpers at top, then sync functions, then async functions.
|
|
59
|
+
|
|
60
|
+
## Adding a New Resource
|
|
61
|
+
|
|
62
|
+
1. Create type models in `querri/types/<name>.py` — Pydantic `BaseModel` subclasses with `#:` inline field docs.
|
|
63
|
+
2. Create `querri/resources/<name>.py` with paired classes:
|
|
64
|
+
- `class Foo:` taking `SyncHTTPClient` at `__init__`
|
|
65
|
+
- `class AsyncFoo:` taking `AsyncHTTPClient` at `__init__`
|
|
66
|
+
- Both must have full Google-style docstrings (Args/Returns) on every method.
|
|
67
|
+
3. Register lazy properties on both `Querri` and `AsyncQuerri` in `_client.py` (follow the existing deferred-import pattern).
|
|
68
|
+
4. Export new public types in `querri/__init__.py` if needed.
|
|
69
|
+
5. Add tests in `tests/test_<name>.py` using respx mocking.
|
|
70
|
+
|
|
71
|
+
## Testing Patterns
|
|
72
|
+
|
|
73
|
+
- Use `@respx.mock` decorator for HTTP mocking.
|
|
74
|
+
- Create a `_make_config()` helper returning `ClientConfig` with test values (`api_key="qk_test"`, `org_id="org_test"`, `base_url="https://test.querri.com/api/v1"`).
|
|
75
|
+
- Instantiate `SyncHTTPClient(_make_config())` or `AsyncHTTPClient(_make_config())` directly.
|
|
76
|
+
- Mock specific URLs: `respx.get("https://test.querri.com/api/v1/endpoint").mock(return_value=httpx.Response(200, json={...}))`.
|
|
77
|
+
- Use `side_effect=[Response1, Response2]` for multi-page pagination tests.
|
|
78
|
+
- pytest-asyncio runs in `auto` mode — no `@pytest.mark.asyncio` decorator needed on async tests.
|
|
79
|
+
- Integration tests are marked `@pytest.mark.integration` and require `QUERRI_API_KEY` + `QUERRI_ORG_ID` env vars.
|
|
80
|
+
|
|
81
|
+
## Docstring Conventions
|
|
82
|
+
|
|
83
|
+
- **Style**: Google-style with `Args:`, `Returns:`, `Raises:`, `Example::` sections.
|
|
84
|
+
- **Inline code**: Use double backticks — `` ``value`` ``.
|
|
85
|
+
- **Sync + async**: Both classes get full docstrings with Args/Returns (don't truncate async).
|
|
86
|
+
- **Type model fields**: Use `#:` inline comments on the same line as the field.
|
|
87
|
+
- **Module docstrings**: Every module has a 1-line docstring describing its purpose.
|
|
88
|
+
|
|
89
|
+
## Code Quality
|
|
90
|
+
|
|
91
|
+
- **mypy**: Strict mode, target Python 3.9
|
|
92
|
+
- **ruff**: Rules E, F, I, N, UP, B, SIM (target Python 3.9)
|
|
93
|
+
- **Tests**: pytest + pytest-asyncio (mode: auto) + respx for HTTP mocking
|
querri-1.0.2/PKG-INFO
ADDED
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: querri
|
|
3
|
+
Version: 1.0.2
|
|
4
|
+
Summary: Python SDK and CLI for the Querri data analysis platform
|
|
5
|
+
Project-URL: Homepage, https://querri.com
|
|
6
|
+
Project-URL: Documentation, https://docs.querri.com/sdk/python
|
|
7
|
+
Project-URL: Repository, https://github.com/querri-ai/querri-python
|
|
8
|
+
Author-email: Querri <support@querri.com>
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Typing :: Typed
|
|
19
|
+
Requires-Python: >=3.10
|
|
20
|
+
Requires-Dist: httpx<1.0,>=0.27
|
|
21
|
+
Requires-Dist: pydantic<3.0,>=2.0
|
|
22
|
+
Requires-Dist: typing-extensions>=4.0
|
|
23
|
+
Provides-Extra: cli
|
|
24
|
+
Requires-Dist: pillow<12.0,>=10.0; extra == 'cli'
|
|
25
|
+
Requires-Dist: rich<14.0,>=13.0; extra == 'cli'
|
|
26
|
+
Requires-Dist: typer[all]<1.0,>=0.12; extra == 'cli'
|
|
27
|
+
Provides-Extra: dev
|
|
28
|
+
Requires-Dist: mypy>=1.0; extra == 'dev'
|
|
29
|
+
Requires-Dist: pillow<12.0,>=10.0; extra == 'dev'
|
|
30
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
31
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
32
|
+
Requires-Dist: respx>=0.21; extra == 'dev'
|
|
33
|
+
Requires-Dist: rich<14.0,>=13.0; extra == 'dev'
|
|
34
|
+
Requires-Dist: ruff>=0.4; extra == 'dev'
|
|
35
|
+
Requires-Dist: typer[all]<1.0,>=0.12; extra == 'dev'
|
|
36
|
+
Provides-Extra: test
|
|
37
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'test'
|
|
38
|
+
Requires-Dist: pytest>=8.0; extra == 'test'
|
|
39
|
+
Requires-Dist: respx>=0.21; extra == 'test'
|
|
40
|
+
Provides-Extra: tui
|
|
41
|
+
Requires-Dist: pillow<12.0,>=10.0; extra == 'tui'
|
|
42
|
+
Requires-Dist: rich<14.0,>=13.0; extra == 'tui'
|
|
43
|
+
Requires-Dist: typer[all]<1.0,>=0.12; extra == 'tui'
|
|
44
|
+
Description-Content-Type: text/markdown
|
|
45
|
+
|
|
46
|
+
# Querri — Python SDK and CLI
|
|
47
|
+
|
|
48
|
+
> **CLI:** Interact with the Querri data analysis platform from the terminal or in scripts.
|
|
49
|
+
> **Python SDK:** Embed Querri analytics in your Python web application with one method call.
|
|
50
|
+
|
|
51
|
+
## Install
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
pip install querri
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## CLI Quick Start
|
|
60
|
+
|
|
61
|
+
The `querri` CLI lets you upload data, create analysis projects, and ask questions — interactively or from scripts.
|
|
62
|
+
|
|
63
|
+
### Auth
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
querri auth login # browser-based login
|
|
67
|
+
querri whoami # confirm who you're logged in as
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
For scripted/non-interactive use, set `QUERRI_API_KEY` instead of relying on stored tokens:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
export QUERRI_API_KEY=qk_your_api_key
|
|
74
|
+
export QUERRI_ORG_ID=org_your_org_id
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Example workflow: upload a file and analyze it
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
# Upload a file (CSV, Excel, JSON, etc.)
|
|
81
|
+
querri --json file upload path/to/data.csv
|
|
82
|
+
|
|
83
|
+
# Create a project
|
|
84
|
+
querri --json project new "My Analysis"
|
|
85
|
+
|
|
86
|
+
# Add the file to the project (triggers ingestion + agent summary)
|
|
87
|
+
querri --json project add-source <file_id>
|
|
88
|
+
|
|
89
|
+
# Ask a question
|
|
90
|
+
querri --json project chat -m "What are the top 5 products by revenue?"
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
For scripting, add `--no-interactive` to prevent prompts and `--json` for parseable output. Note that `--json` and other global flags must come **before** the subcommand:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
querri --json --no-interactive project chat -m "Summarize the data" # correct
|
|
97
|
+
querri project chat -m "Summarize the data" --json # WRONG
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Self-documenting
|
|
101
|
+
|
|
102
|
+
The CLI covers all Querri resources — projects, files, sources, views, dashboards, sharing, API keys, users, policies, embed sessions, and more.
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
querri --help
|
|
106
|
+
querri <command> --help
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
See [`skills/querri-cli/SKILL.md`](skills/querri-cli/SKILL.md) for the full command reference.
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## Python SDK
|
|
114
|
+
|
|
115
|
+
For embedding Querri analytics in a Python web application. Use alongside the [`@querri-inc/embed`](https://www.npmjs.com/package/@querri-inc/embed) frontend component.
|
|
116
|
+
|
|
117
|
+
### Quick Start
|
|
118
|
+
|
|
119
|
+
```python
|
|
120
|
+
from querri import Querri
|
|
121
|
+
|
|
122
|
+
client = Querri(api_key="qk_your_api_key", org_id="org_...")
|
|
123
|
+
|
|
124
|
+
session = client.embed.get_session(
|
|
125
|
+
user="customer-42", # external ID from your system
|
|
126
|
+
ttl=3600,
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
print(session["session_token"]) # JWT to pass to the frontend
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Wire a session endpoint
|
|
133
|
+
|
|
134
|
+
**Flask:**
|
|
135
|
+
|
|
136
|
+
```python
|
|
137
|
+
from flask import Flask, jsonify, request
|
|
138
|
+
from querri import Querri
|
|
139
|
+
|
|
140
|
+
app = Flask(__name__)
|
|
141
|
+
client = Querri() # reads QUERRI_API_KEY and QUERRI_ORG_ID from env
|
|
142
|
+
|
|
143
|
+
@app.route("/api/querri-session", methods=["POST"])
|
|
144
|
+
def querri_session():
|
|
145
|
+
# Derive user identity from YOUR auth system — never from the request body.
|
|
146
|
+
auth_user = get_authenticated_user()
|
|
147
|
+
|
|
148
|
+
session = client.embed.get_session(
|
|
149
|
+
user={"external_id": auth_user.id, "email": auth_user.email},
|
|
150
|
+
access={
|
|
151
|
+
"sources": ["src_sales_data"],
|
|
152
|
+
"filters": {"tenant_id": auth_user.tenant_id},
|
|
153
|
+
},
|
|
154
|
+
origin=request.headers.get("Origin"),
|
|
155
|
+
ttl=3600,
|
|
156
|
+
)
|
|
157
|
+
return jsonify(session)
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Django and FastAPI follow the identical pattern — see **[docs/server-sdk.md](docs/server-sdk.md)** for those examples.
|
|
161
|
+
|
|
162
|
+
### Add the embed (React)
|
|
163
|
+
|
|
164
|
+
```tsx
|
|
165
|
+
import { QuerriEmbed } from '@querri-inc/embed/react';
|
|
166
|
+
|
|
167
|
+
<QuerriEmbed
|
|
168
|
+
style={{ width: '100%', height: '600px' }}
|
|
169
|
+
serverUrl="https://app.querri.com"
|
|
170
|
+
auth={{ sessionEndpoint: '/api/querri-session' }}
|
|
171
|
+
/>
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
> **Security:** Always derive user identity and access from server-side auth. Never read `user` or `access` from the request body — a malicious client can impersonate any user or escalate access.
|
|
175
|
+
|
|
176
|
+
### Configuration
|
|
177
|
+
|
|
178
|
+
The SDK reads configuration from constructor arguments or environment variables:
|
|
179
|
+
|
|
180
|
+
| Parameter | Env Variable | Default | Description |
|
|
181
|
+
|-----------|-------------|---------|-------------|
|
|
182
|
+
| `api_key` | `QUERRI_API_KEY` | *(required)* | Your `qk_` API key |
|
|
183
|
+
| `org_id` | `QUERRI_ORG_ID` | *(required)* | Organization ID |
|
|
184
|
+
| `host` | `QUERRI_HOST` | `https://app.querri.com` | Server host |
|
|
185
|
+
| `timeout` | `QUERRI_TIMEOUT` | `30.0` | Request timeout (seconds) |
|
|
186
|
+
| `max_retries` | `QUERRI_MAX_RETRIES` | `3` | Retry attempts for transient errors |
|
|
187
|
+
|
|
188
|
+
> **Note:** The parameter is `host`, not `base_url`. The SDK appends `/api/v1` automatically.
|
|
189
|
+
|
|
190
|
+
### `get_session()` — Embed Sessions
|
|
191
|
+
|
|
192
|
+
The flagship convenience method: resolves or creates a user, applies access policies, and generates a session JWT in one call.
|
|
193
|
+
|
|
194
|
+
```python
|
|
195
|
+
session = client.embed.get_session(
|
|
196
|
+
user={
|
|
197
|
+
"external_id": "customer-42",
|
|
198
|
+
"email": "alice@acme.com",
|
|
199
|
+
"first_name": "Alice",
|
|
200
|
+
},
|
|
201
|
+
access={
|
|
202
|
+
"sources": ["src_sales_data"],
|
|
203
|
+
"filters": {
|
|
204
|
+
"tenant_id": "acme",
|
|
205
|
+
"region": ["us-east", "us-west"], # list values are OR'd
|
|
206
|
+
},
|
|
207
|
+
},
|
|
208
|
+
origin="https://app.acme.com",
|
|
209
|
+
ttl=7200,
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
session["session_token"] # str — JWT for the embed
|
|
213
|
+
session["expires_in"] # int — seconds until expiry
|
|
214
|
+
session["user_id"] # str — Querri user ID
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
You can also pass pre-created policy IDs directly:
|
|
218
|
+
|
|
219
|
+
```python
|
|
220
|
+
session = client.embed.get_session(
|
|
221
|
+
user={"external_id": "customer-42"},
|
|
222
|
+
access={"policy_ids": ["pol_abc123"]},
|
|
223
|
+
)
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### User-Scoped Client (`as_user`)
|
|
227
|
+
|
|
228
|
+
```python
|
|
229
|
+
session = client.embed.get_session(user="customer-42", ttl=900)
|
|
230
|
+
|
|
231
|
+
with client.as_user(session) as user_client:
|
|
232
|
+
for project in user_client.projects.list():
|
|
233
|
+
print(project.name)
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
See **[docs/server-sdk.md](docs/server-sdk.md#user-scoped-client-as_user)** for details on granting access and available resources.
|
|
237
|
+
|
|
238
|
+
### All Resources
|
|
239
|
+
|
|
240
|
+
| Resource | Access | Key Methods |
|
|
241
|
+
|----------|--------|-------------|
|
|
242
|
+
| `client.dashboards` | Dashboard management | `list`, `create`, `get`, `update`, `delete`, `refresh` |
|
|
243
|
+
| `client.projects` | Analysis projects | `list`, `create`, `get`, `run`, `run_status`, `list_steps` |
|
|
244
|
+
| `client.projects.chats` | Chats within projects | `create`, `list`, `stream`, `cancel`, `delete` |
|
|
245
|
+
| `client.sources` | Sources, connectors & data | `list`, `create`, `create_data_source`, `query`, `source_data`, `append_rows`, `replace_data`, `ask`, `sync`, `list_connectors` |
|
|
246
|
+
| `client.views` | SQL-defined views | `list`, `create`, `get`, `update`, `delete`, `run`, `preview`, `chat` |
|
|
247
|
+
| `client.files` | File management | `upload`, `list`, `get`, `delete` |
|
|
248
|
+
| `client.users` | User management | `list`, `create`, `get`, `update`, `delete` |
|
|
249
|
+
| `client.keys` | API key management | `create`, `list`, `get`, `delete` |
|
|
250
|
+
| `client.audit` | Audit log | `list` |
|
|
251
|
+
| `client.usage` | Usage metrics | `org_usage`, `user_usage` |
|
|
252
|
+
| `client.sharing` | Sharing & permissions | `share_project`, `share_dashboard`, `share_source` |
|
|
253
|
+
|
|
254
|
+
### Async Client
|
|
255
|
+
|
|
256
|
+
`AsyncQuerri` mirrors the sync API with `async`/`await`:
|
|
257
|
+
|
|
258
|
+
```python
|
|
259
|
+
from querri import AsyncQuerri
|
|
260
|
+
|
|
261
|
+
async with AsyncQuerri() as client:
|
|
262
|
+
session = await client.embed.get_session(
|
|
263
|
+
user={"external_id": "cust-42", "email": "a@b.com"},
|
|
264
|
+
access={"sources": ["src_sales"]},
|
|
265
|
+
)
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
### Error Handling
|
|
269
|
+
|
|
270
|
+
All errors extend `QuerriError`:
|
|
271
|
+
|
|
272
|
+
```
|
|
273
|
+
QuerriError
|
|
274
|
+
├── APIError — HTTP error responses
|
|
275
|
+
│ ├── ValidationError — 400
|
|
276
|
+
│ ├── AuthenticationError — 401
|
|
277
|
+
│ ├── PermissionError — 403
|
|
278
|
+
│ ├── NotFoundError — 404
|
|
279
|
+
│ ├── ConflictError — 409
|
|
280
|
+
│ ├── RateLimitError — 429 (auto-retried)
|
|
281
|
+
│ └── ServerError — 5xx (auto-retried)
|
|
282
|
+
├── StreamError — SSE stream issues
|
|
283
|
+
└── ConfigError — missing/invalid configuration
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
The SDK automatically retries **429** (always) and **5xx** (idempotent methods only) with exponential backoff + jitter.
|
|
287
|
+
|
|
288
|
+
### Full Reference
|
|
289
|
+
|
|
290
|
+
See **[docs/server-sdk.md](docs/server-sdk.md)** for complete method signatures, all framework examples, and the `get_session()` deep dive.
|
|
291
|
+
|
|
292
|
+
---
|
|
293
|
+
|
|
294
|
+
## Requirements
|
|
295
|
+
|
|
296
|
+
- Python 3.10+
|
|
297
|
+
- [`httpx`](https://www.python-httpx.org/) >= 0.27
|
|
298
|
+
- [`pydantic`](https://docs.pydantic.dev/) >= 2.0
|
|
299
|
+
|
|
300
|
+
## Development
|
|
301
|
+
|
|
302
|
+
```bash
|
|
303
|
+
pip install -e ".[dev]"
|
|
304
|
+
pytest tests/ -v
|
|
305
|
+
pytest tests/test_integration.py -m integration -v # requires API credentials
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
## License
|
|
309
|
+
|
|
310
|
+
MIT
|