neqo 0.1.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.
- neqo-0.1.0/.github/workflows/ci.yml +24 -0
- neqo-0.1.0/.github/workflows/release.yml +90 -0
- neqo-0.1.0/.gitignore +41 -0
- neqo-0.1.0/CONTRIBUTING.md +26 -0
- neqo-0.1.0/LICENSE +21 -0
- neqo-0.1.0/PKG-INFO +411 -0
- neqo-0.1.0/README.md +378 -0
- neqo-0.1.0/docs/architecture.md +111 -0
- neqo-0.1.0/docs/releasing.md +51 -0
- neqo-0.1.0/examples/macros/errors.sql +3 -0
- neqo-0.1.0/examples/macros/investigate_request.sql +3 -0
- neqo-0.1.0/examples/macros/slow_requests.sql +3 -0
- neqo-0.1.0/examples/neqo.yaml +27 -0
- neqo-0.1.0/examples/playground/README.md +147 -0
- neqo-0.1.0/examples/playground/build_demo.py +32 -0
- neqo-0.1.0/examples/playground/macros/customer_orders.sql +8 -0
- neqo-0.1.0/examples/playground/macros/errors.sql +7 -0
- neqo-0.1.0/examples/playground/macros/investigate_request.sql +9 -0
- neqo-0.1.0/examples/playground/macros/preview_table.sql +1 -0
- neqo-0.1.0/examples/playground/macros/service_health.sql +3 -0
- neqo-0.1.0/examples/playground/macros/slow_requests.sql +7 -0
- neqo-0.1.0/examples/playground/neqo.yaml +37 -0
- neqo-0.1.0/examples/playground/setup.sql +142 -0
- neqo-0.1.0/pyproject.toml +51 -0
- neqo-0.1.0/src/neqo/__init__.py +5 -0
- neqo-0.1.0/src/neqo/catalog/__init__.py +3 -0
- neqo-0.1.0/src/neqo/catalog/cache.py +76 -0
- neqo-0.1.0/src/neqo/catalog/models.py +23 -0
- neqo-0.1.0/src/neqo/cli/__init__.py +1 -0
- neqo-0.1.0/src/neqo/cli/main.py +168 -0
- neqo-0.1.0/src/neqo/cli/output.py +25 -0
- neqo-0.1.0/src/neqo/cli/repl.py +114 -0
- neqo-0.1.0/src/neqo/completion/__init__.py +4 -0
- neqo-0.1.0/src/neqo/completion/context.py +57 -0
- neqo-0.1.0/src/neqo/completion/engine.py +87 -0
- neqo-0.1.0/src/neqo/completion/models.py +9 -0
- neqo-0.1.0/src/neqo/config.py +69 -0
- neqo-0.1.0/src/neqo/engines/__init__.py +40 -0
- neqo-0.1.0/src/neqo/engines/athena.py +310 -0
- neqo-0.1.0/src/neqo/engines/base.py +69 -0
- neqo-0.1.0/src/neqo/engines/duckdb.py +151 -0
- neqo-0.1.0/src/neqo/errors.py +16 -0
- neqo-0.1.0/src/neqo/export.py +65 -0
- neqo-0.1.0/src/neqo/macros/__init__.py +5 -0
- neqo-0.1.0/src/neqo/macros/loader.py +38 -0
- neqo-0.1.0/src/neqo/macros/models.py +31 -0
- neqo-0.1.0/src/neqo/macros/registry.py +28 -0
- neqo-0.1.0/src/neqo/macros/renderer.py +98 -0
- neqo-0.1.0/src/neqo/py.typed +0 -0
- neqo-0.1.0/src/neqo/result.py +60 -0
- neqo-0.1.0/src/neqo/runner.py +81 -0
- neqo-0.1.0/src/neqo/workflows/__init__.py +3 -0
- neqo-0.1.0/src/neqo/workflows/runner.py +33 -0
- neqo-0.1.0/tests/integration/test_duckdb.py +93 -0
- neqo-0.1.0/tests/unit/test_athena.py +205 -0
- neqo-0.1.0/tests/unit/test_cache_cli_workflow.py +65 -0
- neqo-0.1.0/tests/unit/test_completion_input.py +49 -0
- neqo-0.1.0/tests/unit/test_config_result.py +92 -0
- neqo-0.1.0/tests/unit/test_csv_render_cli.py +80 -0
- neqo-0.1.0/tests/unit/test_export.py +62 -0
- neqo-0.1.0/tests/unit/test_macros.py +126 -0
- neqo-0.1.0/tests/unit/test_repl.py +109 -0
- neqo-0.1.0/uv.lock +1227 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
on:
|
|
3
|
+
push:
|
|
4
|
+
pull_request:
|
|
5
|
+
permissions:
|
|
6
|
+
contents: read
|
|
7
|
+
jobs:
|
|
8
|
+
check:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
strategy:
|
|
11
|
+
fail-fast: false
|
|
12
|
+
matrix:
|
|
13
|
+
python: ['3.11', '3.12', '3.13']
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v4
|
|
16
|
+
- uses: astral-sh/setup-uv@v6
|
|
17
|
+
with:
|
|
18
|
+
python-version: ${{ matrix.python }}
|
|
19
|
+
- run: uv sync --locked --all-extras
|
|
20
|
+
- run: uv run ruff check .
|
|
21
|
+
- run: uv run ruff format --check .
|
|
22
|
+
- run: uv run pytest --cov=neqo --cov-report=term-missing
|
|
23
|
+
- run: uv build
|
|
24
|
+
- run: uv run twine check dist/*
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- 'v*'
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: read
|
|
10
|
+
|
|
11
|
+
concurrency:
|
|
12
|
+
group: release-${{ github.ref }}
|
|
13
|
+
cancel-in-progress: false
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
check:
|
|
17
|
+
name: Check Python ${{ matrix.python }}
|
|
18
|
+
runs-on: ubuntu-latest
|
|
19
|
+
strategy:
|
|
20
|
+
fail-fast: false
|
|
21
|
+
matrix:
|
|
22
|
+
python: ['3.11', '3.12', '3.13']
|
|
23
|
+
steps:
|
|
24
|
+
- uses: actions/checkout@v4
|
|
25
|
+
with:
|
|
26
|
+
persist-credentials: false
|
|
27
|
+
- uses: astral-sh/setup-uv@v6
|
|
28
|
+
with:
|
|
29
|
+
python-version: ${{ matrix.python }}
|
|
30
|
+
- run: uv sync --locked --all-extras
|
|
31
|
+
- name: Check release version
|
|
32
|
+
env:
|
|
33
|
+
RELEASE_TAG: ${{ github.ref_name }}
|
|
34
|
+
run: |
|
|
35
|
+
uv run python - <<'PY'
|
|
36
|
+
import os
|
|
37
|
+
import tomllib
|
|
38
|
+
from pathlib import Path
|
|
39
|
+
from neqo import __version__
|
|
40
|
+
|
|
41
|
+
version = tomllib.loads(Path('pyproject.toml').read_text())['project']['version']
|
|
42
|
+
assert os.environ['RELEASE_TAG'] == f'v{version}', 'Tag and package version differ'
|
|
43
|
+
assert __version__ == version, 'Public API and package version differ'
|
|
44
|
+
PY
|
|
45
|
+
- run: uv run ruff check .
|
|
46
|
+
- run: uv run ruff format --check .
|
|
47
|
+
- run: uv run pytest
|
|
48
|
+
|
|
49
|
+
build:
|
|
50
|
+
needs: check
|
|
51
|
+
runs-on: ubuntu-latest
|
|
52
|
+
steps:
|
|
53
|
+
- uses: actions/checkout@v4
|
|
54
|
+
with:
|
|
55
|
+
persist-credentials: false
|
|
56
|
+
- uses: astral-sh/setup-uv@v6
|
|
57
|
+
with:
|
|
58
|
+
python-version: '3.11'
|
|
59
|
+
- run: uv sync --locked
|
|
60
|
+
- run: uv build
|
|
61
|
+
- run: uv run twine check dist/*
|
|
62
|
+
- name: Smoke test the built wheel
|
|
63
|
+
run: |
|
|
64
|
+
uv venv /tmp/neqo-wheel
|
|
65
|
+
for wheel in dist/*.whl; do
|
|
66
|
+
uv pip install --python /tmp/neqo-wheel/bin/python "${wheel}[duckdb]"
|
|
67
|
+
done
|
|
68
|
+
/tmp/neqo-wheel/bin/neqo query 'SELECT 42 AS answer' --json
|
|
69
|
+
- uses: actions/upload-artifact@v4
|
|
70
|
+
with:
|
|
71
|
+
name: distributions
|
|
72
|
+
path: dist/
|
|
73
|
+
if-no-files-found: error
|
|
74
|
+
retention-days: 7
|
|
75
|
+
|
|
76
|
+
publish:
|
|
77
|
+
needs: build
|
|
78
|
+
runs-on: ubuntu-latest
|
|
79
|
+
environment:
|
|
80
|
+
name: pypi
|
|
81
|
+
url: https://pypi.org/project/neqo/
|
|
82
|
+
permissions:
|
|
83
|
+
id-token: write
|
|
84
|
+
steps:
|
|
85
|
+
- uses: actions/download-artifact@v4
|
|
86
|
+
with:
|
|
87
|
+
name: distributions
|
|
88
|
+
path: dist/
|
|
89
|
+
- name: Publish to PyPI using Trusted Publishing
|
|
90
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
neqo-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
.venv/
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
.pytest_cache/
|
|
5
|
+
.ruff_cache/
|
|
6
|
+
.coverage
|
|
7
|
+
htmlcov/
|
|
8
|
+
dist/
|
|
9
|
+
*.egg-info/
|
|
10
|
+
*.duckdb
|
|
11
|
+
*.duckdb.wal
|
|
12
|
+
.env
|
|
13
|
+
|
|
14
|
+
# Local configuration and credentials; examples remain versioned.
|
|
15
|
+
.env.*
|
|
16
|
+
!.env.example
|
|
17
|
+
/neqo.yaml
|
|
18
|
+
/neqo.yml
|
|
19
|
+
neqo.local.yaml
|
|
20
|
+
neqo.local.yml
|
|
21
|
+
.aws/
|
|
22
|
+
*.pem
|
|
23
|
+
*.key
|
|
24
|
+
|
|
25
|
+
# Local agent state, editor files and operating-system metadata.
|
|
26
|
+
.codex/
|
|
27
|
+
.idea/
|
|
28
|
+
.vscode/
|
|
29
|
+
.DS_Store
|
|
30
|
+
|
|
31
|
+
# Query outputs. Keep rendered SQL in exports/ instead of the macro directory.
|
|
32
|
+
*.csv
|
|
33
|
+
*.parquet
|
|
34
|
+
*.arrow
|
|
35
|
+
exports/
|
|
36
|
+
output/
|
|
37
|
+
|
|
38
|
+
# Additional build and verification artifacts.
|
|
39
|
+
build/
|
|
40
|
+
.mypy_cache/
|
|
41
|
+
.coverage.*
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
Use Python 3.11 or newer and run `uv sync`. Before submitting a change:
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
uv run ruff check .
|
|
7
|
+
uv run ruff format --check .
|
|
8
|
+
uv run pytest
|
|
9
|
+
uv build
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Keep engine-specific behavior in engine adapters. Add tests for changes in query
|
|
13
|
+
semantics, macro validation, metadata and completion. Tests must not need AWS
|
|
14
|
+
credentials or create remote resources by default. Never include credentials,
|
|
15
|
+
private queries, result datasets or local databases in contributions.
|
|
16
|
+
|
|
17
|
+
Describe the problem, resulting behavior, and validation in pull requests. Small,
|
|
18
|
+
focused changes are easier to review. Discuss API changes before large refactors.
|
|
19
|
+
|
|
20
|
+
Before a first push, review `git add --dry-run .`, then inspect the staged diff.
|
|
21
|
+
The root `neqo.yaml`, `neqo.local.yaml`, environment files, database files, CSV /
|
|
22
|
+
Parquet / Arrow outputs, and `exports/` are ignored. Keep rendered SQL outputs in
|
|
23
|
+
`exports/`; SQL files elsewhere may be intentional, versioned macros. Configuration
|
|
24
|
+
files under `examples/` are public samples: do not put private connection settings
|
|
25
|
+
there. `uv.lock` is intentionally versioned for reproducible development and CI.
|
|
26
|
+
Ignore patterns do not remove already tracked files or prevent `git add -f`.
|
neqo-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 NEQO contributors
|
|
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.
|
neqo-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: neqo
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Nimble Engine Query Orchestrator: unified queries and macros for Athena and DuckDB
|
|
5
|
+
Project-URL: Homepage, https://github.com/hirokikana/neqo
|
|
6
|
+
Project-URL: Repository, https://github.com/hirokikana/neqo
|
|
7
|
+
Project-URL: Issues, https://github.com/hirokikana/neqo/issues
|
|
8
|
+
Project-URL: Documentation, https://github.com/hirokikana/neqo#readme
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: athena,duckdb,macros,query,sql
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Topic :: Database
|
|
18
|
+
Requires-Python: >=3.11
|
|
19
|
+
Requires-Dist: jinja2<4,>=3.1.6
|
|
20
|
+
Requires-Dist: prompt-toolkit<4,>=3.0.48
|
|
21
|
+
Requires-Dist: pyyaml<7,>=6
|
|
22
|
+
Requires-Dist: rich<15,>=13.9
|
|
23
|
+
Requires-Dist: sqlglot<31,>=26
|
|
24
|
+
Requires-Dist: typer<1,>=0.15
|
|
25
|
+
Provides-Extra: all
|
|
26
|
+
Requires-Dist: boto3<2,>=1.35; extra == 'all'
|
|
27
|
+
Requires-Dist: duckdb<2,>=1.2; extra == 'all'
|
|
28
|
+
Provides-Extra: athena
|
|
29
|
+
Requires-Dist: boto3<2,>=1.35; extra == 'athena'
|
|
30
|
+
Provides-Extra: duckdb
|
|
31
|
+
Requires-Dist: duckdb<2,>=1.2; extra == 'duckdb'
|
|
32
|
+
Description-Content-Type: text/markdown
|
|
33
|
+
|
|
34
|
+
# NEQO
|
|
35
|
+
|
|
36
|
+
**Nimble Engine Query Orchestrator**
|
|
37
|
+
|
|
38
|
+
A lightweight, unified query and macro layer for Athena, DuckDB, and beyond.
|
|
39
|
+
|
|
40
|
+
Pronounced "neko". Python 3.11+. MIT licensed. Version 0.1 is an early API: expect
|
|
41
|
+
changes before 1.0.
|
|
42
|
+
|
|
43
|
+
NEQO brings Athena and DuckDB behind a Python API, with reusable SQL macros,
|
|
44
|
+
context-aware autocomplete, and a CLI / REPL. Its core can run in applications
|
|
45
|
+
and AWS Lambda without importing the CLI. Engines and completion are extensible.
|
|
46
|
+
|
|
47
|
+
It is intended for ad-hoc analysis, operational investigation and reusable team
|
|
48
|
+
queries. It is not a terminal SQL IDE or a dbt replacement.
|
|
49
|
+
|
|
50
|
+
## Quick start
|
|
51
|
+
|
|
52
|
+
Install DuckDB support for a local query session:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
pip install 'neqo[duckdb]'
|
|
56
|
+
neqo --engine duckdb query 'SELECT 42 AS answer'
|
|
57
|
+
neqo --no-history duckdb ./analytics.duckdb
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
For both engines, use `pip install 'neqo[all]'`, or install the CLI with uv:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
uv tool install 'neqo[all]'
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
`pip install neqo` installs the library and CLI but no engine drivers. Choose
|
|
67
|
+
`neqo[duckdb]`, `neqo[athena]`, or `neqo[all]` for query execution. With no
|
|
68
|
+
`neqo.yaml` in the current directory, the default engine is in-memory DuckDB.
|
|
69
|
+
|
|
70
|
+
To develop NEQO or try its sample data, clone the repository:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
git clone https://github.com/hirokikana/neqo.git
|
|
74
|
+
cd neqo
|
|
75
|
+
uv sync --locked
|
|
76
|
+
uv run neqo --help
|
|
77
|
+
uv run neqo query 'SELECT 42 AS answer'
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Or `pip install -e '.[all]'` in a virtual environment. Engine drivers are optional;
|
|
81
|
+
install `neqo[athena]` for an Athena-only deployment.
|
|
82
|
+
|
|
83
|
+
### Try macros and completion locally
|
|
84
|
+
|
|
85
|
+
From the repository root, generate synthetic data and run a macro:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
uv run python examples/playground/build_demo.py
|
|
89
|
+
uv run neqo --config examples/playground/neqo.yaml run errors --limit 5
|
|
90
|
+
uv run neqo --config examples/playground/neqo.yaml render errors --date 2026-09-05
|
|
91
|
+
uv run neqo --config examples/playground/neqo.yaml --no-history duckdb
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
In the REPL, enter `SELECT * FROM access_` and press Tab. Use `:quit` to close
|
|
95
|
+
the database before running another CLI process against it. The playground has
|
|
96
|
+
6,000 access logs, five schemas, views and six macros; its generated database is
|
|
97
|
+
not included in Git or the installed wheel. See the
|
|
98
|
+
[playground guide](https://github.com/hirokikana/neqo/blob/main/examples/playground/README.md).
|
|
99
|
+
|
|
100
|
+
## Configuration
|
|
101
|
+
|
|
102
|
+
CLI commands and Runner read `neqo.yaml` from the current working directory by
|
|
103
|
+
default. The following is a template for your own connection settings; the
|
|
104
|
+
`errors` macro also requires the SQL file shown in the next section.
|
|
105
|
+
|
|
106
|
+
```yaml
|
|
107
|
+
default_engine: local
|
|
108
|
+
engines:
|
|
109
|
+
local:
|
|
110
|
+
type: duckdb
|
|
111
|
+
database: ./analytics.duckdb
|
|
112
|
+
max_rows: 10000
|
|
113
|
+
athena-prod:
|
|
114
|
+
type: athena
|
|
115
|
+
database: analytics
|
|
116
|
+
catalog: AwsDataCatalog
|
|
117
|
+
workgroup: primary
|
|
118
|
+
region: ap-northeast-1
|
|
119
|
+
# aws_profile: prod # omit in Lambda; use its execution role
|
|
120
|
+
# output_location: s3://your-query-results/prefix/
|
|
121
|
+
cache_ttl: 300
|
|
122
|
+
timeout: 300
|
|
123
|
+
macros:
|
|
124
|
+
path: ./macros
|
|
125
|
+
errors:
|
|
126
|
+
file: macros/errors.sql
|
|
127
|
+
params:
|
|
128
|
+
date: {type: date}
|
|
129
|
+
status: {type: integer, default: 500}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
`default_engine` accepts a connection profile name or a registered engine name.
|
|
133
|
+
`--profile` selects a configured connection. `--engine` selects an engine or
|
|
134
|
+
connection by name. Explicit Python keyword arguments override connection
|
|
135
|
+
options. Relative macro paths and configured DuckDB database paths resolve from
|
|
136
|
+
the YAML file's directory. An explicit `database=` resolves from the process
|
|
137
|
+
working directory. Use `--config PATH` / `Runner(config=PATH)` for deterministic
|
|
138
|
+
configuration discovery. No parent-directory search takes place.
|
|
139
|
+
|
|
140
|
+
### Athena authentication
|
|
141
|
+
|
|
142
|
+
NEQO connection profiles and AWS credential profiles are separate:
|
|
143
|
+
|
|
144
|
+
| Setting | Purpose | Example |
|
|
145
|
+
| --- | --- | --- |
|
|
146
|
+
| `--profile` | Select an entry under `engines` in `neqo.yaml` | `athena-prod` |
|
|
147
|
+
| `aws_profile` in that entry, or `AWS_PROFILE` | Select the boto3/AWS authentication profile | `company-prod` |
|
|
148
|
+
|
|
149
|
+
For an AWS profile previously configured with `aws configure sso`:
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
pip install 'neqo[athena]'
|
|
153
|
+
aws sso login --profile company-prod
|
|
154
|
+
AWS_PROFILE=company-prod neqo --profile athena-prod query 'SELECT 1'
|
|
155
|
+
AWS_PROFILE=company-prod neqo --profile athena-prod --no-history athena
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Alternatively, add `aws_profile: company-prod` to the `athena-prod` connection
|
|
159
|
+
above. After SSO login, `neqo --profile athena-prod ...` then uses that profile
|
|
160
|
+
without an `AWS_PROFILE` prefix. An explicit `aws_profile` takes precedence over
|
|
161
|
+
the environment selection. Do not put access keys or session tokens in the YAML.
|
|
162
|
+
|
|
163
|
+
Athena needs a region, query/catalog permissions, and access to the S3 query-result
|
|
164
|
+
location. Configure `output_location` in NEQO or configure it in the workgroup;
|
|
165
|
+
enforced workgroup settings take precedence. This S3 location is separate from
|
|
166
|
+
the local file written by `--csv`. For Lambda, EC2 and ECS, omit `aws_profile` to
|
|
167
|
+
use the execution/instance/task role through boto3's credential provider chain.
|
|
168
|
+
|
|
169
|
+
Keep personal connection settings in the repository-root `neqo.yaml` (ignored by
|
|
170
|
+
Git), or use `--config neqo.local.yaml`. Files under `examples/` are public samples.
|
|
171
|
+
|
|
172
|
+
## SQL macros
|
|
173
|
+
|
|
174
|
+
`macros/errors.sql`:
|
|
175
|
+
|
|
176
|
+
```sql
|
|
177
|
+
SELECT * FROM access_logs
|
|
178
|
+
WHERE dt = {{ date }} AND status >= {{ status }}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
YAML keeps parameter schemas and defaults separate from executable SQL, leaves
|
|
182
|
+
SQL files usable by editors, and permits connection configuration in one place.
|
|
183
|
+
Files under `macros.path` are discovered automatically; undeclared variables
|
|
184
|
+
default to string parameters. Hyphens and underscores in macro names are aliases.
|
|
185
|
+
|
|
186
|
+
Types: `string`, `integer`, `float`, `boolean`, `date`, `identifier`. Values are
|
|
187
|
+
validated and rendered as dialect-specific SQL literals. Identifiers accept
|
|
188
|
+
dot-separated ASCII names and quote each component. They cannot contain SQL
|
|
189
|
+
fragments. Required and unknown parameters raise errors. Use `--name=value` for
|
|
190
|
+
CLI values that begin with `--`; booleans require explicit `true` or `false`.
|
|
191
|
+
`--json` and `--csv` are reserved for execution output; `render` reserves `--output` / `-o`.
|
|
192
|
+
|
|
193
|
+
Templates support only `{{ parameter }}` substitution. Jinja control flow,
|
|
194
|
+
filters, function calls and attribute access are rejected. Do not surround a
|
|
195
|
+
placeholder with quotes: the renderer supplies them. SQL templates and raw SQL
|
|
196
|
+
are trusted executable code; review them just like Python code. Value escaping
|
|
197
|
+
does not make arbitrary third-party templates safe.
|
|
198
|
+
|
|
199
|
+
```python
|
|
200
|
+
from neqo import Runner
|
|
201
|
+
from neqo.macros import Macro, MacroRegistry, Parameter
|
|
202
|
+
|
|
203
|
+
macros = MacroRegistry()
|
|
204
|
+
macros.register(Macro("lookup", "SELECT {{ name }} AS name", {"name": Parameter()}))
|
|
205
|
+
with Runner(engine="duckdb", macros=macros) as runner:
|
|
206
|
+
result = runner.run("lookup", name="O'Reilly")
|
|
207
|
+
print(result.to_dict())
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
Both initial engines expand macros through this same renderer. Native DuckDB
|
|
211
|
+
macro compilation and SQL table-function macro invocation are future features.
|
|
212
|
+
|
|
213
|
+
### Render SQL without execution
|
|
214
|
+
|
|
215
|
+
```bash
|
|
216
|
+
neqo render errors --date 2026-09-05
|
|
217
|
+
neqo --profile athena-prod render errors --date 2026-09-05 --output errors.sql
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
`render` writes plain SQL to stdout, or UTF-8 SQL to `--output`. It uses the same
|
|
221
|
+
typed parameters, defaults and validation as `run`. For Athena and DuckDB it does
|
|
222
|
+
not create an engine, open a database, fetch AWS credentials, or submit a query.
|
|
223
|
+
The selected profile determines the SQL dialect. Python uses
|
|
224
|
+
`runner.render("errors", date="2026-09-05")` for the same behavior.
|
|
225
|
+
|
|
226
|
+
### Export CSV
|
|
227
|
+
|
|
228
|
+
```bash
|
|
229
|
+
AWS_PROFILE=prod neqo --profile athena-prod query 'SELECT * FROM access_logs' --csv logs.csv
|
|
230
|
+
AWS_PROFILE=prod neqo --profile athena-prod run errors --date 2026-09-05 --csv errors.csv
|
|
231
|
+
neqo query 'SELECT 42 AS answer' --csv -
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
`--csv FILE` exports all result rows, including those beyond `max_rows`. Athena
|
|
235
|
+
submits the query once, waits for success, and fetches result pages sequentially.
|
|
236
|
+
DuckDB also exports in batches. The export has one header, UTF-8 encoding and
|
|
237
|
+
standard CSV quoting for commas, quotes and embedded newlines. NULL and empty
|
|
238
|
+
strings both produce empty fields. Dates use ISO strings, decimals keep precision,
|
|
239
|
+
bytes use base64, and DuckDB nested values use JSON cells.
|
|
240
|
+
|
|
241
|
+
Use `--csv -` for pure CSV on stdout. `--json` and `--csv` cannot be combined.
|
|
242
|
+
File paths are relative to the current working directory; the parent directory
|
|
243
|
+
must exist. Existing files are replaced only after the export succeeds. On failure,
|
|
244
|
+
the temporary file is removed and any previous output is retained. Stdout and
|
|
245
|
+
caller-supplied streams cannot be rolled back and can contain partial output on failure.
|
|
246
|
+
|
|
247
|
+
```python
|
|
248
|
+
from neqo import Runner
|
|
249
|
+
from neqo.export import write_csv
|
|
250
|
+
|
|
251
|
+
with Runner(profile="athena-prod") as runner:
|
|
252
|
+
count = runner.export_csv("SELECT * FROM access_logs", "logs.csv")
|
|
253
|
+
sql = runner.render("errors", date="2026-09-05")
|
|
254
|
+
runner.export_csv(sql, "errors.csv")
|
|
255
|
+
# For an existing, successfully completed Athena query, without rerunning it:
|
|
256
|
+
# write_csv(runner.engine.iter_results(query_id), "existing.csv")
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
`result.to_csv(path_or_stream)` exports a materialized `QueryResult`, but rejects
|
|
260
|
+
results marked as truncated. Use `runner.export_csv` for full query results.
|
|
261
|
+
|
|
262
|
+
## Python API
|
|
263
|
+
|
|
264
|
+
With an existing `access_logs` table and the `errors` macro configured as above:
|
|
265
|
+
|
|
266
|
+
```python
|
|
267
|
+
from neqo import Runner
|
|
268
|
+
|
|
269
|
+
with Runner(engine="duckdb", database="analytics.duckdb") as runner:
|
|
270
|
+
result = runner.execute("SELECT count(*) FROM access_logs")
|
|
271
|
+
errors = runner.run("errors", date="2026-09-05")
|
|
272
|
+
print(result.to_json())
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
`Runner(engine=an_engine)` accepts an existing `Engine`; its owner is responsible
|
|
276
|
+
for closing it. A Runner that creates its own engine closes it on context exit.
|
|
277
|
+
Connection creation is lazy: rendering alone does not open a connection. Accessing
|
|
278
|
+
`runner.engine` or executing a query initializes it and may raise connection errors.
|
|
279
|
+
Instances are intended for sequential use, not shared concurrent execution.
|
|
280
|
+
|
|
281
|
+
`QueryResult` has `columns`, tuple `rows`, and `metadata`. `to_dict()` is JSON-ready:
|
|
282
|
+
dates use ISO strings, decimals use lossless strings, bytes use base64, and tuples
|
|
283
|
+
become lists. Nonfinite floats become strings (`nan`, `inf`, `-inf`) for valid JSON.
|
|
284
|
+
Duplicate column names are preserved. Athena complex values remain
|
|
285
|
+
strings; numeric, boolean, date and timestamp columns use Python values.
|
|
286
|
+
|
|
287
|
+
### Asynchronous Athena and Lambda
|
|
288
|
+
|
|
289
|
+
```python
|
|
290
|
+
from neqo import Runner
|
|
291
|
+
|
|
292
|
+
runner = Runner(engine="athena", database="analytics", config="/var/task/neqo.yaml")
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def lambda_handler(event, context):
|
|
296
|
+
handle = runner.submit("errors", date=event["date"])
|
|
297
|
+
return {"query_id": handle.query_id, "engine": handle.engine}
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
Bundle the YAML and SQL files with the deployment, install `neqo[athena]`, and
|
|
301
|
+
grant the execution role Athena, catalog and result-bucket permissions. NEQO
|
|
302
|
+
does not store credentials. AWS_PROFILE, instance/task roles and Lambda execution
|
|
303
|
+
roles work through boto3. Use `/tmp/neqo-cache` for `cache_dir` in Lambda.
|
|
304
|
+
|
|
305
|
+
Another invocation can use `runner.status(query_id)` and `runner.result(query_id)`.
|
|
306
|
+
`runner.submit_sql(sql)` submits raw SQL. For short synchronous Lambda queries,
|
|
307
|
+
`return runner.run("errors", date=event["date"]).to_dict()` works; set Athena's
|
|
308
|
+
`timeout` below the Lambda time limit. A wait timeout raises `QueryError` with
|
|
309
|
+
`query_id` and leaves the remote query running. Explicit cancellation is available
|
|
310
|
+
through `runner.engine.cancel(query_id)` on Athena.
|
|
311
|
+
|
|
312
|
+
### Results and limits
|
|
313
|
+
|
|
314
|
+
Default materialization is capped at 10,000 rows and reports `metadata.truncated`.
|
|
315
|
+
This is a client memory bound, not a SQL limit or Athena scan-cost bound. Use
|
|
316
|
+
SQL `LIMIT` / predicates to control work. A single very large cell may still be
|
|
317
|
+
large. Athena `engine.iter_results(query_id)` yields pages of at most 1,000 API
|
|
318
|
+
rows without applying the materialization cap:
|
|
319
|
+
|
|
320
|
+
```python
|
|
321
|
+
for page in runner.engine.iter_results(query_id):
|
|
322
|
+
consume(page) # application-defined sink
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
Pages report query ID, scanned bytes, execution time, output location and state.
|
|
326
|
+
Restart page iteration from the query ID to retrieve the entire result; a
|
|
327
|
+
truncated materialized result's token is not a row-exact resume cursor.
|
|
328
|
+
`runner.execute_iter(sql)` executes a query and yields all result batches on either
|
|
329
|
+
initial engine; consume the iterator before reusing or closing its Runner.
|
|
330
|
+
DuckDB submit is synchronous; handles live in that engine instance and the most
|
|
331
|
+
recent 32 results are retained. DuckDB page iteration currently yields its bounded
|
|
332
|
+
materialized result; use `execute_iter` for a new query's full streamed result.
|
|
333
|
+
|
|
334
|
+
## REPL and completion
|
|
335
|
+
|
|
336
|
+
Terminate SQL with `;`. When the completion menu is open, Enter accepts the selected
|
|
337
|
+
candidate (or the first candidate if none is selected) without submitting the input.
|
|
338
|
+
Otherwise, Enter continues incomplete input; Ctrl-C clears input and
|
|
339
|
+
Ctrl-D exits. Tab offers keywords, tables/views, columns, functions and macros.
|
|
340
|
+
To complete a SELECT column, write the FROM clause first and move the cursor back,
|
|
341
|
+
for example `SELECT l. FROM access_logs l` with the cursor after `l.`. Without a
|
|
342
|
+
FROM clause, v0.1 does not search all tables for column names. NEQO macro candidates
|
|
343
|
+
are suggestions only: run them with `neqo run` in the shell, not `FROM macro(...)`.
|
|
344
|
+
|
|
345
|
+
| Command | Action |
|
|
346
|
+
| --- | --- |
|
|
347
|
+
| `:tables` | List tables and views |
|
|
348
|
+
| `:schema TABLE` | List columns |
|
|
349
|
+
| `:macros` | List macro signatures |
|
|
350
|
+
| `:refresh` | Invalidate metadata caches |
|
|
351
|
+
| `:engine` | Show engine |
|
|
352
|
+
| `:quit` | Exit |
|
|
353
|
+
|
|
354
|
+
```python
|
|
355
|
+
from neqo.completion import CompletionEngine
|
|
356
|
+
|
|
357
|
+
completion = CompletionEngine(runner.engine, runner.macros)
|
|
358
|
+
sql = "SELECT l. FROM access_logs l"
|
|
359
|
+
items = completion.complete(sql, len("SELECT l."))
|
|
360
|
+
# Completion(value=..., kind="column", signature=..., start_position=...)
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
Completion uses SQLGlot plus a token fallback for incomplete SQL. Alias handling
|
|
364
|
+
is best effort; nested scopes, CTE-derived columns and quoted partial identifiers
|
|
365
|
+
are not fully resolved in v0.1. DuckDB's `sql_auto_complete()` is used when its
|
|
366
|
+
autocomplete extension is already loaded; NEQO never installs extensions on Tab.
|
|
367
|
+
|
|
368
|
+
Athena metadata comes from Athena catalog APIs, including partition columns and
|
|
369
|
+
views. Disk cache defaults to `$XDG_CACHE_HOME/neqo` or `~/.cache/neqo` with a
|
|
370
|
+
300-second TTL. Namespaces include the STS caller ARN, region, catalog, database
|
|
371
|
+
and workgroup. STS identity is fetched lazily once on metadata access, never on
|
|
372
|
+
query submission. Injected test clients use isolated cache namespaces. Disk
|
|
373
|
+
write failures fall back to memory. Athena's function suggestions are a small
|
|
374
|
+
static list; there is no claim of a complete remote function catalog.
|
|
375
|
+
|
|
376
|
+
History is stored in `$XDG_DATA_HOME/neqo/history` or `~/.local/share/neqo/history`.
|
|
377
|
+
**Query history may contain sensitive values.** Use `neqo --no-history ...` to
|
|
378
|
+
disable persistence. Cached catalog names may also be sensitive. Protect cache
|
|
379
|
+
and history directories, and do not share them between trust boundaries.
|
|
380
|
+
|
|
381
|
+
`--verbose` enables only NEQO debug messages. NEQO does not log SQL, parameter
|
|
382
|
+
values, credentials or raw SDK exception payloads. Applications should take the
|
|
383
|
+
same care when logging exceptions from their own code.
|
|
384
|
+
|
|
385
|
+
## Development
|
|
386
|
+
|
|
387
|
+
```bash
|
|
388
|
+
uv sync --locked
|
|
389
|
+
uv run ruff check .
|
|
390
|
+
uv run ruff format --check .
|
|
391
|
+
uv run pytest
|
|
392
|
+
uv build
|
|
393
|
+
uv run twine check dist/*
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
CI runs lint, tests and packaging on Python 3.11, 3.12 and 3.13. Unit tests use
|
|
397
|
+
mock Athena clients / botocore Stubber and never need AWS credentials. DuckDB
|
|
398
|
+
integration tests use an in-memory database. No live AWS test runs by default.
|
|
399
|
+
|
|
400
|
+
See [architecture](https://github.com/hirokikana/neqo/blob/main/docs/architecture.md)
|
|
401
|
+
for extension contracts, workflow boundaries and next steps, and
|
|
402
|
+
[contributing](https://github.com/hirokikana/neqo/blob/main/CONTRIBUTING.md) for contributions.
|
|
403
|
+
Maintainers can follow the
|
|
404
|
+
[release guide](https://github.com/hirokikana/neqo/blob/main/docs/releasing.md)
|
|
405
|
+
for PyPI publication through `release.yml` and the `pypi` GitHub environment.
|
|
406
|
+
|
|
407
|
+
## References
|
|
408
|
+
|
|
409
|
+
Engine integrations follow the official [Athena boto3 API](https://docs.aws.amazon.com/boto3/latest/reference/services/athena.html)
|
|
410
|
+
and [DuckDB autocomplete extension](https://duckdb.org/docs/current/core_extensions/autocomplete)
|
|
411
|
+
documentation.
|