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.
Files changed (63) hide show
  1. neqo-0.1.0/.github/workflows/ci.yml +24 -0
  2. neqo-0.1.0/.github/workflows/release.yml +90 -0
  3. neqo-0.1.0/.gitignore +41 -0
  4. neqo-0.1.0/CONTRIBUTING.md +26 -0
  5. neqo-0.1.0/LICENSE +21 -0
  6. neqo-0.1.0/PKG-INFO +411 -0
  7. neqo-0.1.0/README.md +378 -0
  8. neqo-0.1.0/docs/architecture.md +111 -0
  9. neqo-0.1.0/docs/releasing.md +51 -0
  10. neqo-0.1.0/examples/macros/errors.sql +3 -0
  11. neqo-0.1.0/examples/macros/investigate_request.sql +3 -0
  12. neqo-0.1.0/examples/macros/slow_requests.sql +3 -0
  13. neqo-0.1.0/examples/neqo.yaml +27 -0
  14. neqo-0.1.0/examples/playground/README.md +147 -0
  15. neqo-0.1.0/examples/playground/build_demo.py +32 -0
  16. neqo-0.1.0/examples/playground/macros/customer_orders.sql +8 -0
  17. neqo-0.1.0/examples/playground/macros/errors.sql +7 -0
  18. neqo-0.1.0/examples/playground/macros/investigate_request.sql +9 -0
  19. neqo-0.1.0/examples/playground/macros/preview_table.sql +1 -0
  20. neqo-0.1.0/examples/playground/macros/service_health.sql +3 -0
  21. neqo-0.1.0/examples/playground/macros/slow_requests.sql +7 -0
  22. neqo-0.1.0/examples/playground/neqo.yaml +37 -0
  23. neqo-0.1.0/examples/playground/setup.sql +142 -0
  24. neqo-0.1.0/pyproject.toml +51 -0
  25. neqo-0.1.0/src/neqo/__init__.py +5 -0
  26. neqo-0.1.0/src/neqo/catalog/__init__.py +3 -0
  27. neqo-0.1.0/src/neqo/catalog/cache.py +76 -0
  28. neqo-0.1.0/src/neqo/catalog/models.py +23 -0
  29. neqo-0.1.0/src/neqo/cli/__init__.py +1 -0
  30. neqo-0.1.0/src/neqo/cli/main.py +168 -0
  31. neqo-0.1.0/src/neqo/cli/output.py +25 -0
  32. neqo-0.1.0/src/neqo/cli/repl.py +114 -0
  33. neqo-0.1.0/src/neqo/completion/__init__.py +4 -0
  34. neqo-0.1.0/src/neqo/completion/context.py +57 -0
  35. neqo-0.1.0/src/neqo/completion/engine.py +87 -0
  36. neqo-0.1.0/src/neqo/completion/models.py +9 -0
  37. neqo-0.1.0/src/neqo/config.py +69 -0
  38. neqo-0.1.0/src/neqo/engines/__init__.py +40 -0
  39. neqo-0.1.0/src/neqo/engines/athena.py +310 -0
  40. neqo-0.1.0/src/neqo/engines/base.py +69 -0
  41. neqo-0.1.0/src/neqo/engines/duckdb.py +151 -0
  42. neqo-0.1.0/src/neqo/errors.py +16 -0
  43. neqo-0.1.0/src/neqo/export.py +65 -0
  44. neqo-0.1.0/src/neqo/macros/__init__.py +5 -0
  45. neqo-0.1.0/src/neqo/macros/loader.py +38 -0
  46. neqo-0.1.0/src/neqo/macros/models.py +31 -0
  47. neqo-0.1.0/src/neqo/macros/registry.py +28 -0
  48. neqo-0.1.0/src/neqo/macros/renderer.py +98 -0
  49. neqo-0.1.0/src/neqo/py.typed +0 -0
  50. neqo-0.1.0/src/neqo/result.py +60 -0
  51. neqo-0.1.0/src/neqo/runner.py +81 -0
  52. neqo-0.1.0/src/neqo/workflows/__init__.py +3 -0
  53. neqo-0.1.0/src/neqo/workflows/runner.py +33 -0
  54. neqo-0.1.0/tests/integration/test_duckdb.py +93 -0
  55. neqo-0.1.0/tests/unit/test_athena.py +205 -0
  56. neqo-0.1.0/tests/unit/test_cache_cli_workflow.py +65 -0
  57. neqo-0.1.0/tests/unit/test_completion_input.py +49 -0
  58. neqo-0.1.0/tests/unit/test_config_result.py +92 -0
  59. neqo-0.1.0/tests/unit/test_csv_render_cli.py +80 -0
  60. neqo-0.1.0/tests/unit/test_export.py +62 -0
  61. neqo-0.1.0/tests/unit/test_macros.py +126 -0
  62. neqo-0.1.0/tests/unit/test_repl.py +109 -0
  63. 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.