sqlnbfmt 0.4.2__tar.gz → 0.6.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.
- sqlnbfmt-0.6.0/.github/dependabot.yml +23 -0
- sqlnbfmt-0.6.0/.github/workflows/ci-and-publish.yml +57 -0
- {sqlnbfmt-0.4.2 → sqlnbfmt-0.6.0}/.pre-commit-config.yaml +3 -3
- sqlnbfmt-0.6.0/CHANGELOG.md +201 -0
- {sqlnbfmt-0.4.2 → sqlnbfmt-0.6.0}/PKG-INFO +75 -37
- {sqlnbfmt-0.4.2 → sqlnbfmt-0.6.0}/README.md +73 -35
- sqlnbfmt-0.6.0/SECURITY.md +13 -0
- {sqlnbfmt-0.4.2 → sqlnbfmt-0.6.0}/config.yaml +9 -1
- {sqlnbfmt-0.4.2 → sqlnbfmt-0.6.0}/pyproject.toml +10 -3
- sqlnbfmt-0.6.0/sqlnbfmt/__init__.py +29 -0
- sqlnbfmt-0.6.0/sqlnbfmt/formatter.py +1534 -0
- {sqlnbfmt-0.4.2 → sqlnbfmt-0.6.0}/sqlnbfmt/jupyterlab_integration.py +29 -12
- {sqlnbfmt-0.4.2 → sqlnbfmt-0.6.0}/tests/eval/in_function_sql/expected.ipynb +1 -2
- {sqlnbfmt-0.4.2/tests/eval/marimo_idempotency → sqlnbfmt-0.6.0/tests/eval/marimo_basic_sql}/expected.py +11 -11
- {sqlnbfmt-0.4.2 → sqlnbfmt-0.6.0}/tests/eval/marimo_comment_preservation/expected.py +20 -20
- sqlnbfmt-0.6.0/tests/eval/marimo_complex_sql/expected.py +63 -0
- {sqlnbfmt-0.4.2 → sqlnbfmt-0.6.0}/tests/eval/marimo_fstring_interpolation/expected.py +19 -19
- {sqlnbfmt-0.4.2 → sqlnbfmt-0.6.0}/tests/eval/marimo_multi_cell_mixed/expected.py +22 -22
- sqlnbfmt-0.6.0/tests/eval/marimo_multi_statement/expected.py +34 -0
- sqlnbfmt-0.6.0/tests/eval/python_sql_decorators/expected.py +56 -0
- sqlnbfmt-0.6.0/tests/eval/python_sql_decorators/input.py +27 -0
- sqlnbfmt-0.6.0/tests/test_call_layout.py +151 -0
- {sqlnbfmt-0.4.2 → sqlnbfmt-0.6.0}/tests/test_formatter.py +22 -4
- sqlnbfmt-0.6.0/tests/test_function_name_matching.py +63 -0
- sqlnbfmt-0.6.0/tests/test_jupyterlab_integration.py +208 -0
- sqlnbfmt-0.6.0/tests/test_live.py +284 -0
- {sqlnbfmt-0.4.2 → sqlnbfmt-0.6.0}/tests/test_marimo.py +6 -2
- sqlnbfmt-0.6.0/tests/test_pyproject_config.py +401 -0
- sqlnbfmt-0.6.0/tests/test_round_trip.py +642 -0
- sqlnbfmt-0.6.0/tests/test_sql_decorators.py +136 -0
- sqlnbfmt-0.6.0/tests/test_string_literals.py +131 -0
- sqlnbfmt-0.6.0/uv.lock +497 -0
- sqlnbfmt-0.4.2/.github/workflows/ci-and-publish.yml +0 -42
- sqlnbfmt-0.4.2/CHANGELOG.md +0 -143
- sqlnbfmt-0.4.2/sqlnbfmt/__init__.py +0 -15
- sqlnbfmt-0.4.2/sqlnbfmt/formatter.py +0 -832
- sqlnbfmt-0.4.2/tests/eval/marimo_complex_sql/expected.py +0 -63
- sqlnbfmt-0.4.2/tests/eval/marimo_multi_statement/expected.py +0 -34
- sqlnbfmt-0.4.2/tests/test_jupyterlab_integration.py +0 -76
- sqlnbfmt-0.4.2/uv.lock +0 -455
- {sqlnbfmt-0.4.2 → sqlnbfmt-0.6.0}/.gitignore +0 -0
- {sqlnbfmt-0.4.2 → sqlnbfmt-0.6.0}/.pre-commit-hooks.yaml +0 -0
- {sqlnbfmt-0.4.2 → sqlnbfmt-0.6.0}/.python-version +0 -0
- {sqlnbfmt-0.4.2 → sqlnbfmt-0.6.0}/LICENSE +0 -0
- {sqlnbfmt-0.4.2 → sqlnbfmt-0.6.0}/jupyter-config/sqlnbfmt.json +0 -0
- {sqlnbfmt-0.4.2 → sqlnbfmt-0.6.0}/tests/__init__.py +0 -0
- {sqlnbfmt-0.4.2 → sqlnbfmt-0.6.0}/tests/eval/README.md +0 -0
- {sqlnbfmt-0.4.2 → sqlnbfmt-0.6.0}/tests/eval/basic_select/expected.ipynb +0 -0
- {sqlnbfmt-0.4.2 → sqlnbfmt-0.6.0}/tests/eval/basic_select/input.ipynb +0 -0
- {sqlnbfmt-0.4.2 → sqlnbfmt-0.6.0}/tests/eval/case_expression/expected.ipynb +0 -0
- {sqlnbfmt-0.4.2 → sqlnbfmt-0.6.0}/tests/eval/case_expression/input.ipynb +0 -0
- {sqlnbfmt-0.4.2 → sqlnbfmt-0.6.0}/tests/eval/comment_preservation/expected.ipynb +0 -0
- {sqlnbfmt-0.4.2 → sqlnbfmt-0.6.0}/tests/eval/comment_preservation/input.ipynb +0 -0
- {sqlnbfmt-0.4.2 → sqlnbfmt-0.6.0}/tests/eval/cte/expected.ipynb +0 -0
- {sqlnbfmt-0.4.2 → sqlnbfmt-0.6.0}/tests/eval/cte/input.ipynb +0 -0
- {sqlnbfmt-0.4.2 → sqlnbfmt-0.6.0}/tests/eval/fstring_variables/expected.ipynb +0 -0
- {sqlnbfmt-0.4.2 → sqlnbfmt-0.6.0}/tests/eval/fstring_variables/input.ipynb +0 -0
- {sqlnbfmt-0.4.2 → sqlnbfmt-0.6.0}/tests/eval/generate_fixtures.py +0 -0
- {sqlnbfmt-0.4.2 → sqlnbfmt-0.6.0}/tests/eval/group_by_having/expected.ipynb +0 -0
- {sqlnbfmt-0.4.2 → sqlnbfmt-0.6.0}/tests/eval/group_by_having/input.ipynb +0 -0
- {sqlnbfmt-0.4.2 → sqlnbfmt-0.6.0}/tests/eval/idempotency/expected.ipynb +0 -0
- {sqlnbfmt-0.4.2 → sqlnbfmt-0.6.0}/tests/eval/idempotency/input.ipynb +0 -0
- {sqlnbfmt-0.4.2 → sqlnbfmt-0.6.0}/tests/eval/in_function_sql/input.ipynb +0 -0
- {sqlnbfmt-0.4.2 → sqlnbfmt-0.6.0}/tests/eval/joins/expected.ipynb +0 -0
- {sqlnbfmt-0.4.2 → sqlnbfmt-0.6.0}/tests/eval/joins/input.ipynb +0 -0
- {sqlnbfmt-0.4.2 → sqlnbfmt-0.6.0}/tests/eval/magic_commands/expected.ipynb +0 -0
- {sqlnbfmt-0.4.2 → sqlnbfmt-0.6.0}/tests/eval/magic_commands/input.ipynb +0 -0
- {sqlnbfmt-0.4.2 → sqlnbfmt-0.6.0}/tests/eval/marimo_basic_sql/input.py +0 -0
- {sqlnbfmt-0.4.2 → sqlnbfmt-0.6.0}/tests/eval/marimo_comment_preservation/input.py +0 -0
- {sqlnbfmt-0.4.2 → sqlnbfmt-0.6.0}/tests/eval/marimo_complex_sql/input.py +0 -0
- {sqlnbfmt-0.4.2 → sqlnbfmt-0.6.0}/tests/eval/marimo_fstring_interpolation/input.py +0 -0
- {sqlnbfmt-0.4.2/tests/eval/marimo_basic_sql → sqlnbfmt-0.6.0/tests/eval/marimo_idempotency}/expected.py +0 -0
- {sqlnbfmt-0.4.2 → sqlnbfmt-0.6.0}/tests/eval/marimo_idempotency/input.py +0 -0
- {sqlnbfmt-0.4.2 → sqlnbfmt-0.6.0}/tests/eval/marimo_multi_cell_mixed/input.py +0 -0
- {sqlnbfmt-0.4.2 → sqlnbfmt-0.6.0}/tests/eval/marimo_multi_statement/input.py +0 -0
- {sqlnbfmt-0.4.2 → sqlnbfmt-0.6.0}/tests/eval/marimo_non_sql_unchanged/expected.py +0 -0
- {sqlnbfmt-0.4.2 → sqlnbfmt-0.6.0}/tests/eval/marimo_non_sql_unchanged/input.py +0 -0
- {sqlnbfmt-0.4.2 → sqlnbfmt-0.6.0}/tests/eval/marimo_skip_hint/expected.py +0 -0
- {sqlnbfmt-0.4.2 → sqlnbfmt-0.6.0}/tests/eval/marimo_skip_hint/input.py +0 -0
- {sqlnbfmt-0.4.2 → sqlnbfmt-0.6.0}/tests/eval/multi_cell_mixed/expected.ipynb +0 -0
- {sqlnbfmt-0.4.2 → sqlnbfmt-0.6.0}/tests/eval/multi_cell_mixed/input.ipynb +0 -0
- {sqlnbfmt-0.4.2 → sqlnbfmt-0.6.0}/tests/eval/multi_statement/expected.ipynb +0 -0
- {sqlnbfmt-0.4.2 → sqlnbfmt-0.6.0}/tests/eval/multi_statement/input.ipynb +0 -0
- {sqlnbfmt-0.4.2 → sqlnbfmt-0.6.0}/tests/eval/non_sql_unchanged/expected.ipynb +0 -0
- {sqlnbfmt-0.4.2 → sqlnbfmt-0.6.0}/tests/eval/non_sql_unchanged/input.ipynb +0 -0
- {sqlnbfmt-0.4.2 → sqlnbfmt-0.6.0}/tests/eval/skip_hint/expected.ipynb +0 -0
- {sqlnbfmt-0.4.2 → sqlnbfmt-0.6.0}/tests/eval/skip_hint/input.ipynb +0 -0
- {sqlnbfmt-0.4.2 → sqlnbfmt-0.6.0}/tests/eval/subquery/expected.ipynb +0 -0
- {sqlnbfmt-0.4.2 → sqlnbfmt-0.6.0}/tests/eval/subquery/input.ipynb +0 -0
- {sqlnbfmt-0.4.2 → sqlnbfmt-0.6.0}/tests/eval/union/expected.ipynb +0 -0
- {sqlnbfmt-0.4.2 → sqlnbfmt-0.6.0}/tests/eval/union/input.ipynb +0 -0
- {sqlnbfmt-0.4.2 → sqlnbfmt-0.6.0}/tests/eval/window_function/expected.ipynb +0 -0
- {sqlnbfmt-0.4.2 → sqlnbfmt-0.6.0}/tests/eval/window_function/input.ipynb +0 -0
- {sqlnbfmt-0.4.2 → sqlnbfmt-0.6.0}/tests/test_eval.py +0 -0
- {sqlnbfmt-0.4.2 → sqlnbfmt-0.6.0}/tests/test_notebook.ipynb +0 -0
- {sqlnbfmt-0.4.2 → sqlnbfmt-0.6.0}/tests/test_notebook_copy.ipynb +0 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
version: 2
|
|
2
|
+
updates:
|
|
3
|
+
- package-ecosystem: "github-actions"
|
|
4
|
+
directory: "/"
|
|
5
|
+
schedule:
|
|
6
|
+
interval: "weekly"
|
|
7
|
+
day: "monday"
|
|
8
|
+
labels:
|
|
9
|
+
- "dependencies"
|
|
10
|
+
|
|
11
|
+
- package-ecosystem: "pip"
|
|
12
|
+
directory: "/"
|
|
13
|
+
schedule:
|
|
14
|
+
interval: "weekly"
|
|
15
|
+
day: "monday"
|
|
16
|
+
open-pull-requests-limit: 10
|
|
17
|
+
labels:
|
|
18
|
+
- "dependencies"
|
|
19
|
+
groups:
|
|
20
|
+
minor-and-patch:
|
|
21
|
+
update-types:
|
|
22
|
+
- "minor"
|
|
23
|
+
- "patch"
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
name: Python CI and Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
release:
|
|
8
|
+
types: [created]
|
|
9
|
+
workflow_dispatch:
|
|
10
|
+
inputs:
|
|
11
|
+
run_publish:
|
|
12
|
+
description: 'Set to true to run the publish job'
|
|
13
|
+
required: false
|
|
14
|
+
default: 'false'
|
|
15
|
+
|
|
16
|
+
permissions: {}
|
|
17
|
+
|
|
18
|
+
jobs:
|
|
19
|
+
test:
|
|
20
|
+
runs-on: ubuntu-latest
|
|
21
|
+
permissions:
|
|
22
|
+
contents: read
|
|
23
|
+
steps:
|
|
24
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
25
|
+
- uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0
|
|
26
|
+
with:
|
|
27
|
+
enable-cache: true
|
|
28
|
+
cache-dependency-glob: "pyproject.toml"
|
|
29
|
+
- run: uv python install 3.12
|
|
30
|
+
- run: uv sync --all-extras --dev --frozen
|
|
31
|
+
- run: uv run pytest
|
|
32
|
+
# Also the oldest sqlglot that pyproject.toml allows (keep in sync)
|
|
33
|
+
- run: uv run --isolated --with "sqlglot==25.27.0" pytest
|
|
34
|
+
|
|
35
|
+
security:
|
|
36
|
+
runs-on: ubuntu-latest
|
|
37
|
+
permissions:
|
|
38
|
+
contents: read
|
|
39
|
+
steps:
|
|
40
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
41
|
+
- uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0
|
|
42
|
+
- run: uvx uv-secure uv.lock
|
|
43
|
+
|
|
44
|
+
publish:
|
|
45
|
+
needs: [test, security]
|
|
46
|
+
if: github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && github.event.inputs.run_publish == 'true')
|
|
47
|
+
runs-on: ubuntu-latest
|
|
48
|
+
permissions:
|
|
49
|
+
id-token: write
|
|
50
|
+
steps:
|
|
51
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
52
|
+
- uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0
|
|
53
|
+
with:
|
|
54
|
+
enable-cache: true
|
|
55
|
+
- run: uv python install 3.12
|
|
56
|
+
- run: uv build --no-sources
|
|
57
|
+
- uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
repos:
|
|
2
2
|
# Linting + formatting (Astral)
|
|
3
3
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
4
|
-
rev: v0.
|
|
4
|
+
rev: v0.17.1
|
|
5
5
|
hooks:
|
|
6
|
-
- id: ruff
|
|
6
|
+
- id: ruff-check
|
|
7
7
|
args: [--fix]
|
|
8
8
|
- id: ruff-format
|
|
9
9
|
|
|
@@ -24,7 +24,7 @@ repos:
|
|
|
24
24
|
|
|
25
25
|
# Validate pyproject.toml against PEP 621
|
|
26
26
|
- repo: https://github.com/abravalheri/validate-pyproject
|
|
27
|
-
rev:
|
|
27
|
+
rev: '0.26'
|
|
28
28
|
hooks:
|
|
29
29
|
- id: validate-pyproject
|
|
30
30
|
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [0.6.0] - 2026-10-16
|
|
9
|
+
|
|
10
|
+
**Upgrading is recommended.** Earlier versions could change what code means: query parameters were rewritten with the postgres, duckdb and bigquery dialects, non-ASCII text could cut a rewrite in the wrong place, f-string fields lost their quotes, and `%sql` line magics deleted the rest of the cell. sqlnbfmt now checks every rewrite and leaves a string unchanged, with a warning, when it can't format it safely.
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- `extend_sql_keywords`, `extend_function_names` and `extend_sql_decorators` config options, which add entries to the built-in lists instead of replacing them, so adding one function name no longer drops defaults like `sql` (Marimo's `mo.sql()`) ([#26](https://github.com/flyersworder/sqlnbfmt/issues/26))
|
|
15
|
+
- The unknown-option warning suggests the intended option for kebab-case keys (e.g. `extend-function-names`, `indent-width`), at the top level and under `formatting_options`
|
|
16
|
+
|
|
17
|
+
### Deprecated
|
|
18
|
+
|
|
19
|
+
- `formatting_options.single_line_threshold`: it was documented as the maximum length before splitting SQL, but no version ever read it; line breaks are decided by sqlglot. Setting it now logs a warning, and it will be removed in a future release
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
|
|
23
|
+
- **Query parameters were corrupted with the postgres, duckdb and bigquery dialects.** `%s`/`?` parameters became `%(AUTO_PLACEHOLDER_0)s`, `$AUTO_PLACEHOLDER_0` or `@AUTO_PLACEHOLDER_0`, f-string fields became `%({table})s`, `${table}` or `@{table}`, and user-written `:name` parameters were converted to the dialect's own style. All parameter styles (`%s`, `?`, `%(name)s`, `:name`, `:1`, `$1`) and f-string fields now pass through unchanged; `?` or `%s` inside SQL string literals and comments is left as text, using sqlglot's tokenizer for the dialect to tell them apart (so `'\'` in postgres, `$$…$$` bodies and MySQL/BigQuery `#` comments are read correctly). Queries with 11 or more parameters were also mangled (`_1` matched inside `_10`)
|
|
24
|
+
- **Formatting could move parameters or rewrap fields.** Positional parameters bind by order, but `… OFFSET %s LIMIT %s` was reordered to `LIMIT %s OFFSET %s` (and T-SQL `LIMIT ?` to `TOP (?)`), a field holding a clause, as in `f"SELECT * FROM t {where_clause}"`, became `FROM t AS {where_clause}`, and `SELECT {distinct} a` became `SELECT {distinct} AS a`. Parameters and fields could also be pulled into a string (`INTERVAL %s DAY` → `INTERVAL '%s DAY'`) or wrapped in parentheses (`VALUES %s` → `VALUES (%s)`, which breaks psycopg2's `execute_values`), and the quotes around a field could be rewritten (`"{name}"` → `'{name}'` in MySQL, `$${body}$$` → `'{body}'`), which breaks when the value contains a quote. A `%s` inside a SQL comment, which drivers fill in too, could be moved or dropped along with the comment, shifting the values of the parameters after it. Positional parameters must now keep their order (comments included), fields and parameters must stay outside strings without added parentheses, a string holding a field must be written exactly as before, and one may only become an alias, or be given one, if the original wrote `AS` there; otherwise the string is left unformatted with a warning that says why. Harmless rewrites (comments, a trailing `;`, `%s::int` → `CAST(%s AS INT)`) are still formatted
|
|
25
|
+
- **Multi-line SQL string literals changed when re-indented.** In SQL passed to a call, the continuation lines of a string such as `'multi\nline'`, a quoted name (`` `x\ny` ``, `[x\ny]`) or a `$$…$$` function body were indented too, changing its value; only lines outside SQL strings are indented now, following the dialect's quoting rules
|
|
26
|
+
- **Magic cells lost code.** A `%sql` line magic deleted every line after it, `%%sql` arguments were dropped (`%%sql df <<` lost the assignment, `%%sql postgresql://…` the connection), a `%%sql` cell with lone `\r` line breaks lost its SQL, and a line magic collapsed spaces inside strings (`'a b'` → `'a b'`). Line breaks around the magic are now kept as written
|
|
27
|
+
- **Multi-line block comments never settled.** In SQL passed to a call, `/* … */` comments spanning lines were indented further on every run, so `--check` always failed
|
|
28
|
+
- `%%` (an escaped `%` in pyformat SQL) followed by `s` was read as a `%s` parameter
|
|
29
|
+
- **Non-ASCII text could break the file.** AST column offsets count UTF-8 bytes, but they were used as character positions, so with `é`, `日本語` or emoji on the same line, a rewrite was cut at the wrong place (e.g. deleting the closing `)` of `cursor.execute("… 'é' …")`). A lone `\r` line break could crash the run; line breaks are now counted like Python does, and an unexpected error skips the cell or file with a warning instead of aborting
|
|
30
|
+
- **F-string fields lost their quotes or format.** `b = '{y}'` became `b = {y}`, conversions and format specs (`{x!r}`, `{y:>3}`) were dropped, and field expressions were rewritten (`{d["k"]}` became `{d['k']}`). Fields are now copied from the source verbatim. F-strings containing backslashes or literal braces are now formatted correctly instead of being skipped
|
|
31
|
+
- **Re-parse check:** formatted SQL is parsed again and must give the same syntax tree as the original (ignoring quoting of names), so a printing bug in sqlglot can't change a query. Before sqlglot 30.23, for example, the second line of a multi-line quoted name was indented
|
|
32
|
+
- **Whole-file safety net:** after all rewrites, the edited source is parsed again and every string literal is compared with the original (untouched ones) or its intended value (rewritten ones). A rewrite that fails the check is dropped and reported with its line number (the others are still applied), with a request for a bug report. Strings that are skipped because they can't be written back safely are now reported as warnings with their line number
|
|
33
|
+
- **`sql_decorators` now works.** The option was parsed and documented but never used by the formatter in any version ([#30](https://github.com/flyersworder/sqlnbfmt/issues/30)). SQL in decorator arguments (`@query("SELECT ...")`) and string literals returned from decorated functions (`@query` above `def f(): return "SELECT ..."`) are now formatted. Decorator names match like function names, and docstrings and returns of nested functions are left alone
|
|
34
|
+
- **Rewritten SQL strings could corrupt the code.** When writing formatted SQL back, sqlnbfmt now keeps the value of the string exactly. In particular:
|
|
35
|
+
- backslash escapes are preserved (`'\\n'` used to become a real newline)
|
|
36
|
+
- the raw `r` prefix is kept (dropping it turned `\_` into an invalid escape)
|
|
37
|
+
- quotes are chosen so they don't clash with the SQL (`cursor.execute('delete from "users"')` produced broken Python, as did `"""` inside the SQL)
|
|
38
|
+
- literal `{{ }}` braces in f-strings are no longer turned into placeholders
|
|
39
|
+
|
|
40
|
+
Each rewritten literal is parsed back and checked; a string that can't be written back faithfully is left unchanged
|
|
41
|
+
- `sql_keywords` entries are now case-insensitive. SQL detection compares against upper-cased code, so a lowercase entry such as `"merge"` was silently never matched
|
|
42
|
+
- Blank entries in `sql_keywords`, `function_names` and `sql_decorators` (and their `extend_*` forms) are rejected. Function names match as substrings, so `""` matched every call
|
|
43
|
+
|
|
44
|
+
## [0.5.0] - 2026-10-15
|
|
45
|
+
|
|
46
|
+
### Added
|
|
47
|
+
|
|
48
|
+
- **`pyproject.toml` configuration**: set options in a `[tool.sqlnbfmt]` table instead of a separate YAML file. Without `--config`, sqlnbfmt uses the nearest `pyproject.toml` that has the table, searching up to the repository root. The JupyterLab integration uses the same discovery ([#22](https://github.com/flyersworder/sqlnbfmt/issues/22))
|
|
49
|
+
- `--config` accepts a `.toml` path as well as YAML
|
|
50
|
+
- `dialect` config option (in `[tool.sqlnbfmt]` or YAML), so the dialect no longer has to be passed as `--dialect` on every run; `--dialect` still overrides it
|
|
51
|
+
|
|
52
|
+
### Changed
|
|
53
|
+
|
|
54
|
+
- Config values are now type-checked: a wrong type (e.g. a string where a list is expected) is reported as an error naming the file. Previously such values were silently mis-parsed or crashed with a traceback
|
|
55
|
+
- Unknown config options now produce a warning instead of being silently ignored
|
|
56
|
+
|
|
57
|
+
### Fixed
|
|
58
|
+
|
|
59
|
+
- SQL passed to a function call is now indented relative to the line the call is on, and a closing `)` or remaining arguments no longer land at column 0. This was most visible in Marimo cells, where every `mo.sql(...)` sits inside a function. It had regressed in 0.3.0, when the `black` post-processing pass was removed. Code already formatted by an earlier version keeps its layout until its SQL changes
|
|
60
|
+
- The Jupyter server extension no longer overrides a formatter registered in `jupyter_server_config.py` (e.g. `register(dialect="postgres")`), and it now logs configuration errors instead of silently not registering
|
|
61
|
+
- An unknown dialect (e.g. `--dialect postgress`) is now rejected with a suggestion. Previously every SQL string silently failed to parse, so `--check` reported all files as properly formatted
|
|
62
|
+
- The `config.yaml` template was missing the `sql` function name (default since 0.4.0). Because config lists replace the defaults, copying the template silently disabled Marimo `mo.sql()` formatting. The README now states the replace semantics explicitly
|
|
63
|
+
- Upgraded locked dependencies, including sqlglot 29 → 30 (formatting output unchanged across the eval suite)
|
|
64
|
+
- A skipped Jupyter extension registration is now logged at debug level instead of being silently swallowed
|
|
65
|
+
|
|
66
|
+
## [0.4.2] - 2026-03-24
|
|
67
|
+
|
|
68
|
+
### Added
|
|
69
|
+
|
|
70
|
+
- **Jupyter server extension**: `pip install sqlnbfmt` now auto-registers with jupyterlab-code-formatter — no manual config needed
|
|
71
|
+
|
|
72
|
+
### Changed
|
|
73
|
+
|
|
74
|
+
- Switched build system from setuptools to hatchling
|
|
75
|
+
- Updated JupyterLab docs to use `jupyter_server_config.py` (modern) instead of `jupyter_notebook_config.py` (legacy)
|
|
76
|
+
|
|
77
|
+
## [0.4.0] - 2026-03-24
|
|
78
|
+
|
|
79
|
+
### Added
|
|
80
|
+
|
|
81
|
+
- **Marimo notebook support**: format SQL in Marimo `.py` notebooks via `mo.sql()` calls — same `sqlnbfmt` command, auto-detected by file extension
|
|
82
|
+
- **Python file support**: `sqlnbfmt` now accepts `.py` files alongside `.ipynb` — works on any Python file containing SQL strings
|
|
83
|
+
- **JupyterLab integration**: `sqlnbfmt.jupyterlab_integration.register()` for use with `jupyterlab-code-formatter`
|
|
84
|
+
- `format_cell_source()` public API for formatting a single cell's source code
|
|
85
|
+
- `_format_python_source()`, `process_python_file()`, `diff_python_file()` public API for programmatic use
|
|
86
|
+
- `sql` added to default recognized function names (matches `mo.sql()`, etc.)
|
|
87
|
+
- `sqlnbfmt-py` and `sqlnbfmt-py-check` pre-commit hooks for Python files
|
|
88
|
+
- Marimo eval fixtures: `marimo_basic_sql`, `marimo_fstring_interpolation`, `marimo_complex_sql`, `marimo_multi_cell_mixed`, `marimo_comment_preservation`, `marimo_multi_statement`, `marimo_skip_hint`, `marimo_non_sql_unchanged`, `marimo_idempotency`
|
|
89
|
+
|
|
90
|
+
### Fixed
|
|
91
|
+
|
|
92
|
+
- Idempotency: already-formatted SQL in triple-quoted strings (with leading whitespace/indentation) is no longer re-formatted on every run
|
|
93
|
+
|
|
94
|
+
### Changed
|
|
95
|
+
|
|
96
|
+
- CLI description and help text updated to reflect multi-format support
|
|
97
|
+
- Project description and keywords updated for Marimo discoverability
|
|
98
|
+
|
|
99
|
+
## [0.3.2] - 2026-03-16
|
|
100
|
+
|
|
101
|
+
### Fixed
|
|
102
|
+
|
|
103
|
+
- Multiple SQL statements in a single cell are no longer concatenated on one line — each statement now starts on its own line after the `;` separator ([#3](https://github.com/flyersworder/sqlnbfmt/issues/3))
|
|
104
|
+
|
|
105
|
+
### Changed
|
|
106
|
+
|
|
107
|
+
- SQL parsing switched from `sqlglot.parse_one()` to `sqlglot.parse()` to correctly handle multi-statement inputs
|
|
108
|
+
|
|
109
|
+
### Added
|
|
110
|
+
|
|
111
|
+
- `multi_statement` eval fixture case covering `DROP TABLE` + `CREATE TABLE` sequences
|
|
112
|
+
|
|
113
|
+
## [0.3.1] - 2026-03-15
|
|
114
|
+
|
|
115
|
+
### Fixed
|
|
116
|
+
|
|
117
|
+
- `diff_notebook()` no longer duplicates cell-processing logic — extracted shared `_format_cells()` helper to prevent future divergence
|
|
118
|
+
- `--check --diff` combined mode no longer double-counts notebooks or processes them twice
|
|
119
|
+
- Skip hint (`# sqlnbfmt: skip`) no longer triggers on string literals containing the directive — only dedicated comment lines are recognised
|
|
120
|
+
- `load_config()` with a partial config file (missing keys) now falls back to built-in defaults instead of empty sets
|
|
121
|
+
- `load_config()` with an empty YAML file no longer raises `AttributeError`
|
|
122
|
+
- In-function SQL indentation now respects `indent_width` config instead of hardcoded 4 spaces
|
|
123
|
+
|
|
124
|
+
### Added
|
|
125
|
+
|
|
126
|
+
- `main()` integration tests for `--diff`, `--check`, and `--check --diff` exit code behavior
|
|
127
|
+
- Docstring on `_has_skip_hint()` clarifying that inline trailing comments are intentionally ignored
|
|
128
|
+
|
|
129
|
+
## [0.3.0] - 2026-03-15
|
|
130
|
+
|
|
131
|
+
### Added
|
|
132
|
+
|
|
133
|
+
- **Zero-config defaults**: sqlnbfmt now works out of the box without a `config.yaml` file — built-in defaults for SQL keywords, function names, and decorators
|
|
134
|
+
- **`--check` mode**: verify formatting without modifying files; exits with code 1 if changes are needed (CI-friendly)
|
|
135
|
+
- **`--diff` mode**: print a unified diff of formatting changes without modifying files
|
|
136
|
+
- **Skip hints**: add `# sqlnbfmt: skip` to any cell to skip formatting
|
|
137
|
+
- **`sqlnbfmt-check` pre-commit hook**: a read-only hook variant for CI pipelines
|
|
138
|
+
- Cell index in all warning/debug messages for easier debugging
|
|
139
|
+
- PyPI keywords and project URLs in package metadata
|
|
140
|
+
- `skip_hint` eval fixture case
|
|
141
|
+
|
|
142
|
+
### Changed
|
|
143
|
+
|
|
144
|
+
- `--config` argument is now optional (defaults to built-in settings instead of requiring `config.yaml`)
|
|
145
|
+
- `load_config()` accepts `None` to return defaults; passing a missing path still raises `FileNotFoundError`
|
|
146
|
+
- Version bumped to 0.3.0
|
|
147
|
+
|
|
148
|
+
## [0.2.0] - 2026-03-15
|
|
149
|
+
|
|
150
|
+
### Fixed
|
|
151
|
+
|
|
152
|
+
- Python comments in code cells are no longer stripped during formatting ([#2](https://github.com/flyersworder/sqlnbfmt/issues/2))
|
|
153
|
+
|
|
154
|
+
### Changed
|
|
155
|
+
|
|
156
|
+
- Replaced AST-mutation approach (`ast.NodeTransformer` + `astor` + `black`) with surgical string replacement (`ast.NodeVisitor` + offset-based text splicing), preserving all non-SQL content exactly
|
|
157
|
+
- Removed `astor` and `black` from dependencies — fewer install requirements
|
|
158
|
+
- CI publish now uses PyPI Trusted Publishers (OIDC) instead of API tokens
|
|
159
|
+
- Migrated `tool.uv.dev-dependencies` to `dependency-groups.dev` (PEP 735)
|
|
160
|
+
- Updated `actions/checkout` from v3 to v4
|
|
161
|
+
|
|
162
|
+
### Added
|
|
163
|
+
|
|
164
|
+
- Python 3.13 classifier
|
|
165
|
+
- Comment-preservation test case
|
|
166
|
+
|
|
167
|
+
### Fixed
|
|
168
|
+
|
|
169
|
+
- Typo in config.yaml: `exeucte_query` → `execute_query`
|
|
170
|
+
|
|
171
|
+
## [0.1.9] - 2025-01-01
|
|
172
|
+
|
|
173
|
+
### Changed
|
|
174
|
+
|
|
175
|
+
- Updated dependencies (attrs, black, referencing, sqlglot, typing-extensions)
|
|
176
|
+
|
|
177
|
+
## [0.1.8] - 2024-12-01
|
|
178
|
+
|
|
179
|
+
### Fixed
|
|
180
|
+
|
|
181
|
+
- First-line indentation error for in-function SQL
|
|
182
|
+
- New-line handling issues in formatted output
|
|
183
|
+
- Introduced `black` formatter to resolve in-function SQL formatting edge cases
|
|
184
|
+
|
|
185
|
+
## [0.1.7] - 2024-11-01
|
|
186
|
+
|
|
187
|
+
### Added
|
|
188
|
+
|
|
189
|
+
- Pre-commit hook support
|
|
190
|
+
- F-string SQL formatting (with and without variables)
|
|
191
|
+
- In-function SQL formatting (e.g., `pd.read_sql(...)`)
|
|
192
|
+
|
|
193
|
+
## [0.1.0] - 2024-10-01
|
|
194
|
+
|
|
195
|
+
### Added
|
|
196
|
+
|
|
197
|
+
- Initial release
|
|
198
|
+
- SQL formatting in Jupyter notebook code cells using sqlglot
|
|
199
|
+
- Support for `%sql` / `%%sql` magic commands
|
|
200
|
+
- Configurable SQL keywords, function names, and formatting options
|
|
201
|
+
- GitHub Actions CI pipeline
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: sqlnbfmt
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.0
|
|
4
4
|
Summary: A tool to format SQL code in Jupyter notebooks and Marimo notebooks.
|
|
5
5
|
Project-URL: Homepage, https://github.com/flyersworder/sqlnbfmt
|
|
6
6
|
Project-URL: Repository, https://github.com/flyersworder/sqlnbfmt
|
|
@@ -104,7 +104,7 @@ pip install sqlnbfmt jupyterlab-code-formatter
|
|
|
104
104
|
|
|
105
105
|
That's it — sqlnbfmt auto-registers as a Jupyter server extension. In JupyterLab, use **Edit > Format Cell** or configure format-on-save in Settings > Code Formatter, and select "Apply SQL Notebook Formatter".
|
|
106
106
|
|
|
107
|
-
To
|
|
107
|
+
The extension uses `[tool.sqlnbfmt]` (including `dialect`) from the nearest `pyproject.toml` of the directory you start Jupyter in. To set the dialect explicitly instead, add this to `~/.jupyter/jupyter_server_config.py`:
|
|
108
108
|
```python
|
|
109
109
|
from sqlnbfmt.jupyterlab_integration import register
|
|
110
110
|
register(dialect="postgres")
|
|
@@ -121,7 +121,7 @@ pip install pre-commit
|
|
|
121
121
|
```yaml
|
|
122
122
|
repos:
|
|
123
123
|
- repo: https://github.com/flyersworder/sqlnbfmt
|
|
124
|
-
rev: v0.
|
|
124
|
+
rev: v0.6.0
|
|
125
125
|
hooks:
|
|
126
126
|
- id: sqlnbfmt
|
|
127
127
|
types: [jupyter]
|
|
@@ -133,7 +133,7 @@ To also format Marimo notebooks (Python files), add the Python hook:
|
|
|
133
133
|
files: '\.py$' # optionally narrow with: files: 'marimo_.*\.py$'
|
|
134
134
|
```
|
|
135
135
|
|
|
136
|
-
All arguments are optional.
|
|
136
|
+
All arguments are optional. Settings in `[tool.sqlnbfmt]` in your `pyproject.toml` are picked up automatically (see [Configuration](#configuration)); alternatively pass them as arguments:
|
|
137
137
|
```yaml
|
|
138
138
|
args: [--dialect, postgres, --config, config.yaml]
|
|
139
139
|
```
|
|
@@ -162,19 +162,52 @@ Use `--check` in GitHub Actions to enforce formatting:
|
|
|
162
162
|
|
|
163
163
|
## Configuration
|
|
164
164
|
|
|
165
|
-
`sqlnbfmt` works without any configuration
|
|
165
|
+
`sqlnbfmt` works without any configuration. To override defaults, use a `[tool.sqlnbfmt]` table in `pyproject.toml` or a separate YAML file.
|
|
166
166
|
|
|
167
|
-
|
|
167
|
+
### pyproject.toml
|
|
168
|
+
|
|
169
|
+
```toml
|
|
170
|
+
[tool.sqlnbfmt]
|
|
171
|
+
dialect = "postgres"
|
|
172
|
+
extend_function_names = ["run_query"] # recognized in addition to the defaults
|
|
173
|
+
|
|
174
|
+
[tool.sqlnbfmt.formatting_options]
|
|
175
|
+
indent_width = 2
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
`sqlnbfmt` searches the current directory and its parents for the nearest `pyproject.toml` with a `[tool.sqlnbfmt]` table, stopping at the repository root (the directory containing `.git`). A `pyproject.toml` without that table is skipped, so a sub-package's file doesn't hide the repository-level configuration. The JupyterLab integration does the same, starting from the Jupyter server's working directory.
|
|
179
|
+
|
|
180
|
+
### YAML file
|
|
181
|
+
|
|
182
|
+
Pass a YAML file with `--config config.yaml`. [Here](https://github.com/flyersworder/sqlnbfmt/blob/main/config.yaml) is a template. `--config` also accepts a `.toml` path, which is read from its `[tool.sqlnbfmt]` table.
|
|
183
|
+
|
|
184
|
+
When `--config` is given, `pyproject.toml` discovery is skipped. Options you don't set keep their defaults, and unknown option names produce a warning.
|
|
168
185
|
|
|
169
186
|
### Configuration Options
|
|
170
187
|
|
|
171
188
|
| Option | Description | Default |
|
|
172
189
|
|--------|-------------|---------|
|
|
190
|
+
| `dialect` | SQL dialect passed to sqlglot (e.g. `postgres`, `mysql`, `bigquery`); `--dialect` overrides it | none (generic SQL) |
|
|
173
191
|
| `sql_keywords` | SQL keywords to recognize and format | Common SQL keywords |
|
|
174
|
-
| `function_names` | Python functions containing SQL code | `read_sql`, `execute`, etc. |
|
|
175
|
-
| `sql_decorators` | Decorators
|
|
176
|
-
| `
|
|
177
|
-
| `
|
|
192
|
+
| `function_names` | Python functions containing SQL code (see [matching](#how-function-and-decorator-names-match)) | `read_sql`, `execute`, `sql` (Marimo's `mo.sql`), etc. |
|
|
193
|
+
| `sql_decorators` | Decorators whose SQL is formatted: string arguments of `@query("SELECT ...")`, and strings returned by a function decorated with `@query` | `query`, `db_query`, `sql_query`, `sql_decorator` |
|
|
194
|
+
| `extend_sql_keywords`, `extend_function_names`, `extend_sql_decorators` | Entries added to the corresponding list instead of replacing it | none |
|
|
195
|
+
| `formatting_options.single_line_threshold` | **Deprecated:** never had any effect and will be removed. Line breaks are decided by sqlglot | |
|
|
196
|
+
| `formatting_options.indent_width` | Number of spaces for indentation | 4 |
|
|
197
|
+
|
|
198
|
+
To add to the built-in lists, use the `extend_*` options. `sql_keywords`, `function_names` and `sql_decorators` **replace** the built-in lists, so setting `function_names = ["run_query"]` drops `sql`, and Marimo's `mo.sql()` calls stop being formatted. If you do replace a list, copy the defaults you still rely on from the [template](https://github.com/flyersworder/sqlnbfmt/blob/main/config.yaml). You can set both: the `extend_*` entries are added to your replacement list.
|
|
199
|
+
|
|
200
|
+
### How function and decorator names match
|
|
201
|
+
|
|
202
|
+
A call counts as SQL-bearing when a configured name appears **anywhere** in its full dotted name. Matching is case-sensitive. This is deliberately permissive, so that wrappers and variants are covered without configuration:
|
|
203
|
+
|
|
204
|
+
- `execute` matches `cursor.execute`, `cursor.executemany` and `execute_sql`
|
|
205
|
+
- `sql` matches `mo.sql`, `spark.sql`, pandasql's `sqldf`/`pysqldf` and `sqlalchemy.text`
|
|
206
|
+
- `sql` does **not** match `runSQLQuery`, because matching is case-sensitive; add `SQL` if you need it
|
|
207
|
+
|
|
208
|
+
Decorator names in `sql_decorators` match the same way, against the decorator's dotted name (`query` matches `@query`, `@db_query` and `@app.query(...)`).
|
|
209
|
+
|
|
210
|
+
A matched call's string arguments are only reformatted if they look like SQL. Still, prefer specific names: `run` would also match `rerun(...)` and `subprocess.run(...)`, so use `run_query` rather than `run`.
|
|
178
211
|
|
|
179
212
|
## Examples
|
|
180
213
|
|
|
@@ -187,20 +220,20 @@ execute_sql("""SELECT a.col1, b.col2 FROM table_a a JOIN table_b b ON a.id = b.a
|
|
|
187
220
|
|
|
188
221
|
After formatting:
|
|
189
222
|
```python
|
|
190
|
-
execute_sql(
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
JOIN
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
223
|
+
execute_sql(
|
|
224
|
+
"""
|
|
225
|
+
SELECT
|
|
226
|
+
a.col1,
|
|
227
|
+
b.col2
|
|
228
|
+
FROM table_a AS a
|
|
229
|
+
JOIN table_b AS b
|
|
230
|
+
ON a.id = b.a_id
|
|
231
|
+
WHERE
|
|
232
|
+
a.status = 'active'
|
|
233
|
+
ORDER BY
|
|
234
|
+
a.created_at DESC
|
|
235
|
+
"""
|
|
236
|
+
)
|
|
204
237
|
```
|
|
205
238
|
|
|
206
239
|
### Marimo Notebook
|
|
@@ -218,17 +251,17 @@ After formatting:
|
|
|
218
251
|
@app.cell
|
|
219
252
|
def _(mo):
|
|
220
253
|
_df = mo.sql(
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
)
|
|
254
|
+
f"""
|
|
255
|
+
SELECT
|
|
256
|
+
id,
|
|
257
|
+
name
|
|
258
|
+
FROM users
|
|
259
|
+
WHERE
|
|
260
|
+
active = 1
|
|
261
|
+
ORDER BY
|
|
262
|
+
name
|
|
263
|
+
"""
|
|
264
|
+
)
|
|
232
265
|
return
|
|
233
266
|
```
|
|
234
267
|
|
|
@@ -236,7 +269,7 @@ def _(mo):
|
|
|
236
269
|
|
|
237
270
|
**SQL not being formatted?**
|
|
238
271
|
- Ensure the string contains at least 2 SQL keywords or a recognizable pattern like `SELECT...FROM`
|
|
239
|
-
- Check that the function name is in the recognized list (
|
|
272
|
+
- Check that the function name is in the recognized list (add custom ones via `extend_function_names` in `[tool.sqlnbfmt]` or your `--config` file)
|
|
240
273
|
|
|
241
274
|
**Comments being modified?**
|
|
242
275
|
- Python comments (`#`) are preserved. SQL comments (`--`) inside strings are converted to `/* */` block comments by sqlglot.
|
|
@@ -264,6 +297,11 @@ source .venv/bin/activate # On Windows: .venv\Scripts\activate
|
|
|
264
297
|
3. Run tests:
|
|
265
298
|
```bash
|
|
266
299
|
pytest
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
Before a release, also run the live end-to-end tests. They build the wheel, install it into a fresh virtualenv with Jupyter Server and jupyterlab-code-formatter, and exercise the CLI, the pre-commit hooks and the Jupyter extension against a scratch project. They need `uv`, `git` and network access, so the default `pytest` run skips them:
|
|
303
|
+
```bash
|
|
304
|
+
uv run pytest -m live
|
|
267
305
|
```
|
|
268
306
|
|
|
269
307
|
4. Add eval cases: see `tests/eval/generate_fixtures.py` for examples. Run `python tests/eval/generate_fixtures.py` to regenerate fixtures.
|
|
@@ -76,7 +76,7 @@ pip install sqlnbfmt jupyterlab-code-formatter
|
|
|
76
76
|
|
|
77
77
|
That's it — sqlnbfmt auto-registers as a Jupyter server extension. In JupyterLab, use **Edit > Format Cell** or configure format-on-save in Settings > Code Formatter, and select "Apply SQL Notebook Formatter".
|
|
78
78
|
|
|
79
|
-
To
|
|
79
|
+
The extension uses `[tool.sqlnbfmt]` (including `dialect`) from the nearest `pyproject.toml` of the directory you start Jupyter in. To set the dialect explicitly instead, add this to `~/.jupyter/jupyter_server_config.py`:
|
|
80
80
|
```python
|
|
81
81
|
from sqlnbfmt.jupyterlab_integration import register
|
|
82
82
|
register(dialect="postgres")
|
|
@@ -93,7 +93,7 @@ pip install pre-commit
|
|
|
93
93
|
```yaml
|
|
94
94
|
repos:
|
|
95
95
|
- repo: https://github.com/flyersworder/sqlnbfmt
|
|
96
|
-
rev: v0.
|
|
96
|
+
rev: v0.6.0
|
|
97
97
|
hooks:
|
|
98
98
|
- id: sqlnbfmt
|
|
99
99
|
types: [jupyter]
|
|
@@ -105,7 +105,7 @@ To also format Marimo notebooks (Python files), add the Python hook:
|
|
|
105
105
|
files: '\.py$' # optionally narrow with: files: 'marimo_.*\.py$'
|
|
106
106
|
```
|
|
107
107
|
|
|
108
|
-
All arguments are optional.
|
|
108
|
+
All arguments are optional. Settings in `[tool.sqlnbfmt]` in your `pyproject.toml` are picked up automatically (see [Configuration](#configuration)); alternatively pass them as arguments:
|
|
109
109
|
```yaml
|
|
110
110
|
args: [--dialect, postgres, --config, config.yaml]
|
|
111
111
|
```
|
|
@@ -134,19 +134,52 @@ Use `--check` in GitHub Actions to enforce formatting:
|
|
|
134
134
|
|
|
135
135
|
## Configuration
|
|
136
136
|
|
|
137
|
-
`sqlnbfmt` works without any configuration
|
|
137
|
+
`sqlnbfmt` works without any configuration. To override defaults, use a `[tool.sqlnbfmt]` table in `pyproject.toml` or a separate YAML file.
|
|
138
138
|
|
|
139
|
-
|
|
139
|
+
### pyproject.toml
|
|
140
|
+
|
|
141
|
+
```toml
|
|
142
|
+
[tool.sqlnbfmt]
|
|
143
|
+
dialect = "postgres"
|
|
144
|
+
extend_function_names = ["run_query"] # recognized in addition to the defaults
|
|
145
|
+
|
|
146
|
+
[tool.sqlnbfmt.formatting_options]
|
|
147
|
+
indent_width = 2
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
`sqlnbfmt` searches the current directory and its parents for the nearest `pyproject.toml` with a `[tool.sqlnbfmt]` table, stopping at the repository root (the directory containing `.git`). A `pyproject.toml` without that table is skipped, so a sub-package's file doesn't hide the repository-level configuration. The JupyterLab integration does the same, starting from the Jupyter server's working directory.
|
|
151
|
+
|
|
152
|
+
### YAML file
|
|
153
|
+
|
|
154
|
+
Pass a YAML file with `--config config.yaml`. [Here](https://github.com/flyersworder/sqlnbfmt/blob/main/config.yaml) is a template. `--config` also accepts a `.toml` path, which is read from its `[tool.sqlnbfmt]` table.
|
|
155
|
+
|
|
156
|
+
When `--config` is given, `pyproject.toml` discovery is skipped. Options you don't set keep their defaults, and unknown option names produce a warning.
|
|
140
157
|
|
|
141
158
|
### Configuration Options
|
|
142
159
|
|
|
143
160
|
| Option | Description | Default |
|
|
144
161
|
|--------|-------------|---------|
|
|
162
|
+
| `dialect` | SQL dialect passed to sqlglot (e.g. `postgres`, `mysql`, `bigquery`); `--dialect` overrides it | none (generic SQL) |
|
|
145
163
|
| `sql_keywords` | SQL keywords to recognize and format | Common SQL keywords |
|
|
146
|
-
| `function_names` | Python functions containing SQL code | `read_sql`, `execute`, etc. |
|
|
147
|
-
| `sql_decorators` | Decorators
|
|
148
|
-
| `
|
|
149
|
-
| `
|
|
164
|
+
| `function_names` | Python functions containing SQL code (see [matching](#how-function-and-decorator-names-match)) | `read_sql`, `execute`, `sql` (Marimo's `mo.sql`), etc. |
|
|
165
|
+
| `sql_decorators` | Decorators whose SQL is formatted: string arguments of `@query("SELECT ...")`, and strings returned by a function decorated with `@query` | `query`, `db_query`, `sql_query`, `sql_decorator` |
|
|
166
|
+
| `extend_sql_keywords`, `extend_function_names`, `extend_sql_decorators` | Entries added to the corresponding list instead of replacing it | none |
|
|
167
|
+
| `formatting_options.single_line_threshold` | **Deprecated:** never had any effect and will be removed. Line breaks are decided by sqlglot | |
|
|
168
|
+
| `formatting_options.indent_width` | Number of spaces for indentation | 4 |
|
|
169
|
+
|
|
170
|
+
To add to the built-in lists, use the `extend_*` options. `sql_keywords`, `function_names` and `sql_decorators` **replace** the built-in lists, so setting `function_names = ["run_query"]` drops `sql`, and Marimo's `mo.sql()` calls stop being formatted. If you do replace a list, copy the defaults you still rely on from the [template](https://github.com/flyersworder/sqlnbfmt/blob/main/config.yaml). You can set both: the `extend_*` entries are added to your replacement list.
|
|
171
|
+
|
|
172
|
+
### How function and decorator names match
|
|
173
|
+
|
|
174
|
+
A call counts as SQL-bearing when a configured name appears **anywhere** in its full dotted name. Matching is case-sensitive. This is deliberately permissive, so that wrappers and variants are covered without configuration:
|
|
175
|
+
|
|
176
|
+
- `execute` matches `cursor.execute`, `cursor.executemany` and `execute_sql`
|
|
177
|
+
- `sql` matches `mo.sql`, `spark.sql`, pandasql's `sqldf`/`pysqldf` and `sqlalchemy.text`
|
|
178
|
+
- `sql` does **not** match `runSQLQuery`, because matching is case-sensitive; add `SQL` if you need it
|
|
179
|
+
|
|
180
|
+
Decorator names in `sql_decorators` match the same way, against the decorator's dotted name (`query` matches `@query`, `@db_query` and `@app.query(...)`).
|
|
181
|
+
|
|
182
|
+
A matched call's string arguments are only reformatted if they look like SQL. Still, prefer specific names: `run` would also match `rerun(...)` and `subprocess.run(...)`, so use `run_query` rather than `run`.
|
|
150
183
|
|
|
151
184
|
## Examples
|
|
152
185
|
|
|
@@ -159,20 +192,20 @@ execute_sql("""SELECT a.col1, b.col2 FROM table_a a JOIN table_b b ON a.id = b.a
|
|
|
159
192
|
|
|
160
193
|
After formatting:
|
|
161
194
|
```python
|
|
162
|
-
execute_sql(
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
JOIN
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
195
|
+
execute_sql(
|
|
196
|
+
"""
|
|
197
|
+
SELECT
|
|
198
|
+
a.col1,
|
|
199
|
+
b.col2
|
|
200
|
+
FROM table_a AS a
|
|
201
|
+
JOIN table_b AS b
|
|
202
|
+
ON a.id = b.a_id
|
|
203
|
+
WHERE
|
|
204
|
+
a.status = 'active'
|
|
205
|
+
ORDER BY
|
|
206
|
+
a.created_at DESC
|
|
207
|
+
"""
|
|
208
|
+
)
|
|
176
209
|
```
|
|
177
210
|
|
|
178
211
|
### Marimo Notebook
|
|
@@ -190,17 +223,17 @@ After formatting:
|
|
|
190
223
|
@app.cell
|
|
191
224
|
def _(mo):
|
|
192
225
|
_df = mo.sql(
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
)
|
|
226
|
+
f"""
|
|
227
|
+
SELECT
|
|
228
|
+
id,
|
|
229
|
+
name
|
|
230
|
+
FROM users
|
|
231
|
+
WHERE
|
|
232
|
+
active = 1
|
|
233
|
+
ORDER BY
|
|
234
|
+
name
|
|
235
|
+
"""
|
|
236
|
+
)
|
|
204
237
|
return
|
|
205
238
|
```
|
|
206
239
|
|
|
@@ -208,7 +241,7 @@ def _(mo):
|
|
|
208
241
|
|
|
209
242
|
**SQL not being formatted?**
|
|
210
243
|
- Ensure the string contains at least 2 SQL keywords or a recognizable pattern like `SELECT...FROM`
|
|
211
|
-
- Check that the function name is in the recognized list (
|
|
244
|
+
- Check that the function name is in the recognized list (add custom ones via `extend_function_names` in `[tool.sqlnbfmt]` or your `--config` file)
|
|
212
245
|
|
|
213
246
|
**Comments being modified?**
|
|
214
247
|
- Python comments (`#`) are preserved. SQL comments (`--`) inside strings are converted to `/* */` block comments by sqlglot.
|
|
@@ -236,6 +269,11 @@ source .venv/bin/activate # On Windows: .venv\Scripts\activate
|
|
|
236
269
|
3. Run tests:
|
|
237
270
|
```bash
|
|
238
271
|
pytest
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
Before a release, also run the live end-to-end tests. They build the wheel, install it into a fresh virtualenv with Jupyter Server and jupyterlab-code-formatter, and exercise the CLI, the pre-commit hooks and the Jupyter extension against a scratch project. They need `uv`, `git` and network access, so the default `pytest` run skips them:
|
|
275
|
+
```bash
|
|
276
|
+
uv run pytest -m live
|
|
239
277
|
```
|
|
240
278
|
|
|
241
279
|
4. Add eval cases: see `tests/eval/generate_fixtures.py` for examples. Run `python tests/eval/generate_fixtures.py` to regenerate fixtures.
|