sqlnbfmt 0.5.0__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.5.0 → sqlnbfmt-0.6.0}/.github/workflows/ci-and-publish.yml +2 -0
- {sqlnbfmt-0.5.0 → sqlnbfmt-0.6.0}/CHANGELOG.md +36 -0
- {sqlnbfmt-0.5.0 → sqlnbfmt-0.6.0}/PKG-INFO +26 -7
- {sqlnbfmt-0.5.0 → sqlnbfmt-0.6.0}/README.md +25 -6
- {sqlnbfmt-0.5.0 → sqlnbfmt-0.6.0}/config.yaml +5 -1
- {sqlnbfmt-0.5.0 → sqlnbfmt-0.6.0}/pyproject.toml +4 -2
- sqlnbfmt-0.6.0/sqlnbfmt/formatter.py +1534 -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_function_name_matching.py +63 -0
- sqlnbfmt-0.6.0/tests/test_live.py +284 -0
- {sqlnbfmt-0.5.0 → sqlnbfmt-0.6.0}/tests/test_pyproject_config.py +158 -1
- 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.5.0 → sqlnbfmt-0.6.0}/uv.lock +1 -1
- sqlnbfmt-0.5.0/sqlnbfmt/formatter.py +0 -1015
- {sqlnbfmt-0.5.0 → sqlnbfmt-0.6.0}/.github/dependabot.yml +0 -0
- {sqlnbfmt-0.5.0 → sqlnbfmt-0.6.0}/.gitignore +0 -0
- {sqlnbfmt-0.5.0 → sqlnbfmt-0.6.0}/.pre-commit-config.yaml +0 -0
- {sqlnbfmt-0.5.0 → sqlnbfmt-0.6.0}/.pre-commit-hooks.yaml +0 -0
- {sqlnbfmt-0.5.0 → sqlnbfmt-0.6.0}/.python-version +0 -0
- {sqlnbfmt-0.5.0 → sqlnbfmt-0.6.0}/LICENSE +0 -0
- {sqlnbfmt-0.5.0 → sqlnbfmt-0.6.0}/SECURITY.md +0 -0
- {sqlnbfmt-0.5.0 → sqlnbfmt-0.6.0}/jupyter-config/sqlnbfmt.json +0 -0
- {sqlnbfmt-0.5.0 → sqlnbfmt-0.6.0}/sqlnbfmt/__init__.py +0 -0
- {sqlnbfmt-0.5.0 → sqlnbfmt-0.6.0}/sqlnbfmt/jupyterlab_integration.py +0 -0
- {sqlnbfmt-0.5.0 → sqlnbfmt-0.6.0}/tests/__init__.py +0 -0
- {sqlnbfmt-0.5.0 → sqlnbfmt-0.6.0}/tests/eval/README.md +0 -0
- {sqlnbfmt-0.5.0 → sqlnbfmt-0.6.0}/tests/eval/basic_select/expected.ipynb +0 -0
- {sqlnbfmt-0.5.0 → sqlnbfmt-0.6.0}/tests/eval/basic_select/input.ipynb +0 -0
- {sqlnbfmt-0.5.0 → sqlnbfmt-0.6.0}/tests/eval/case_expression/expected.ipynb +0 -0
- {sqlnbfmt-0.5.0 → sqlnbfmt-0.6.0}/tests/eval/case_expression/input.ipynb +0 -0
- {sqlnbfmt-0.5.0 → sqlnbfmt-0.6.0}/tests/eval/comment_preservation/expected.ipynb +0 -0
- {sqlnbfmt-0.5.0 → sqlnbfmt-0.6.0}/tests/eval/comment_preservation/input.ipynb +0 -0
- {sqlnbfmt-0.5.0 → sqlnbfmt-0.6.0}/tests/eval/cte/expected.ipynb +0 -0
- {sqlnbfmt-0.5.0 → sqlnbfmt-0.6.0}/tests/eval/cte/input.ipynb +0 -0
- {sqlnbfmt-0.5.0 → sqlnbfmt-0.6.0}/tests/eval/fstring_variables/expected.ipynb +0 -0
- {sqlnbfmt-0.5.0 → sqlnbfmt-0.6.0}/tests/eval/fstring_variables/input.ipynb +0 -0
- {sqlnbfmt-0.5.0 → sqlnbfmt-0.6.0}/tests/eval/generate_fixtures.py +0 -0
- {sqlnbfmt-0.5.0 → sqlnbfmt-0.6.0}/tests/eval/group_by_having/expected.ipynb +0 -0
- {sqlnbfmt-0.5.0 → sqlnbfmt-0.6.0}/tests/eval/group_by_having/input.ipynb +0 -0
- {sqlnbfmt-0.5.0 → sqlnbfmt-0.6.0}/tests/eval/idempotency/expected.ipynb +0 -0
- {sqlnbfmt-0.5.0 → sqlnbfmt-0.6.0}/tests/eval/idempotency/input.ipynb +0 -0
- {sqlnbfmt-0.5.0 → sqlnbfmt-0.6.0}/tests/eval/in_function_sql/expected.ipynb +0 -0
- {sqlnbfmt-0.5.0 → sqlnbfmt-0.6.0}/tests/eval/in_function_sql/input.ipynb +0 -0
- {sqlnbfmt-0.5.0 → sqlnbfmt-0.6.0}/tests/eval/joins/expected.ipynb +0 -0
- {sqlnbfmt-0.5.0 → sqlnbfmt-0.6.0}/tests/eval/joins/input.ipynb +0 -0
- {sqlnbfmt-0.5.0 → sqlnbfmt-0.6.0}/tests/eval/magic_commands/expected.ipynb +0 -0
- {sqlnbfmt-0.5.0 → sqlnbfmt-0.6.0}/tests/eval/magic_commands/input.ipynb +0 -0
- {sqlnbfmt-0.5.0 → sqlnbfmt-0.6.0}/tests/eval/marimo_basic_sql/expected.py +0 -0
- {sqlnbfmt-0.5.0 → sqlnbfmt-0.6.0}/tests/eval/marimo_basic_sql/input.py +0 -0
- {sqlnbfmt-0.5.0 → sqlnbfmt-0.6.0}/tests/eval/marimo_comment_preservation/expected.py +0 -0
- {sqlnbfmt-0.5.0 → sqlnbfmt-0.6.0}/tests/eval/marimo_comment_preservation/input.py +0 -0
- {sqlnbfmt-0.5.0 → sqlnbfmt-0.6.0}/tests/eval/marimo_complex_sql/expected.py +0 -0
- {sqlnbfmt-0.5.0 → sqlnbfmt-0.6.0}/tests/eval/marimo_complex_sql/input.py +0 -0
- {sqlnbfmt-0.5.0 → sqlnbfmt-0.6.0}/tests/eval/marimo_fstring_interpolation/expected.py +0 -0
- {sqlnbfmt-0.5.0 → sqlnbfmt-0.6.0}/tests/eval/marimo_fstring_interpolation/input.py +0 -0
- {sqlnbfmt-0.5.0 → sqlnbfmt-0.6.0}/tests/eval/marimo_idempotency/expected.py +0 -0
- {sqlnbfmt-0.5.0 → sqlnbfmt-0.6.0}/tests/eval/marimo_idempotency/input.py +0 -0
- {sqlnbfmt-0.5.0 → sqlnbfmt-0.6.0}/tests/eval/marimo_multi_cell_mixed/expected.py +0 -0
- {sqlnbfmt-0.5.0 → sqlnbfmt-0.6.0}/tests/eval/marimo_multi_cell_mixed/input.py +0 -0
- {sqlnbfmt-0.5.0 → sqlnbfmt-0.6.0}/tests/eval/marimo_multi_statement/expected.py +0 -0
- {sqlnbfmt-0.5.0 → sqlnbfmt-0.6.0}/tests/eval/marimo_multi_statement/input.py +0 -0
- {sqlnbfmt-0.5.0 → sqlnbfmt-0.6.0}/tests/eval/marimo_non_sql_unchanged/expected.py +0 -0
- {sqlnbfmt-0.5.0 → sqlnbfmt-0.6.0}/tests/eval/marimo_non_sql_unchanged/input.py +0 -0
- {sqlnbfmt-0.5.0 → sqlnbfmt-0.6.0}/tests/eval/marimo_skip_hint/expected.py +0 -0
- {sqlnbfmt-0.5.0 → sqlnbfmt-0.6.0}/tests/eval/marimo_skip_hint/input.py +0 -0
- {sqlnbfmt-0.5.0 → sqlnbfmt-0.6.0}/tests/eval/multi_cell_mixed/expected.ipynb +0 -0
- {sqlnbfmt-0.5.0 → sqlnbfmt-0.6.0}/tests/eval/multi_cell_mixed/input.ipynb +0 -0
- {sqlnbfmt-0.5.0 → sqlnbfmt-0.6.0}/tests/eval/multi_statement/expected.ipynb +0 -0
- {sqlnbfmt-0.5.0 → sqlnbfmt-0.6.0}/tests/eval/multi_statement/input.ipynb +0 -0
- {sqlnbfmt-0.5.0 → sqlnbfmt-0.6.0}/tests/eval/non_sql_unchanged/expected.ipynb +0 -0
- {sqlnbfmt-0.5.0 → sqlnbfmt-0.6.0}/tests/eval/non_sql_unchanged/input.ipynb +0 -0
- {sqlnbfmt-0.5.0 → sqlnbfmt-0.6.0}/tests/eval/skip_hint/expected.ipynb +0 -0
- {sqlnbfmt-0.5.0 → sqlnbfmt-0.6.0}/tests/eval/skip_hint/input.ipynb +0 -0
- {sqlnbfmt-0.5.0 → sqlnbfmt-0.6.0}/tests/eval/subquery/expected.ipynb +0 -0
- {sqlnbfmt-0.5.0 → sqlnbfmt-0.6.0}/tests/eval/subquery/input.ipynb +0 -0
- {sqlnbfmt-0.5.0 → sqlnbfmt-0.6.0}/tests/eval/union/expected.ipynb +0 -0
- {sqlnbfmt-0.5.0 → sqlnbfmt-0.6.0}/tests/eval/union/input.ipynb +0 -0
- {sqlnbfmt-0.5.0 → sqlnbfmt-0.6.0}/tests/eval/window_function/expected.ipynb +0 -0
- {sqlnbfmt-0.5.0 → sqlnbfmt-0.6.0}/tests/eval/window_function/input.ipynb +0 -0
- {sqlnbfmt-0.5.0 → sqlnbfmt-0.6.0}/tests/test_call_layout.py +0 -0
- {sqlnbfmt-0.5.0 → sqlnbfmt-0.6.0}/tests/test_eval.py +0 -0
- {sqlnbfmt-0.5.0 → sqlnbfmt-0.6.0}/tests/test_formatter.py +0 -0
- {sqlnbfmt-0.5.0 → sqlnbfmt-0.6.0}/tests/test_jupyterlab_integration.py +0 -0
- {sqlnbfmt-0.5.0 → sqlnbfmt-0.6.0}/tests/test_marimo.py +0 -0
- {sqlnbfmt-0.5.0 → sqlnbfmt-0.6.0}/tests/test_notebook.ipynb +0 -0
- {sqlnbfmt-0.5.0 → sqlnbfmt-0.6.0}/tests/test_notebook_copy.ipynb +0 -0
|
@@ -29,6 +29,8 @@ jobs:
|
|
|
29
29
|
- run: uv python install 3.12
|
|
30
30
|
- run: uv sync --all-extras --dev --frozen
|
|
31
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
|
|
32
34
|
|
|
33
35
|
security:
|
|
34
36
|
runs-on: ubuntu-latest
|
|
@@ -5,6 +5,42 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
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
|
+
|
|
8
44
|
## [0.5.0] - 2026-10-15
|
|
9
45
|
|
|
10
46
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
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
|
|
@@ -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]
|
|
@@ -169,6 +169,7 @@ Use `--check` in GitHub Actions to enforce formatting:
|
|
|
169
169
|
```toml
|
|
170
170
|
[tool.sqlnbfmt]
|
|
171
171
|
dialect = "postgres"
|
|
172
|
+
extend_function_names = ["run_query"] # recognized in addition to the defaults
|
|
172
173
|
|
|
173
174
|
[tool.sqlnbfmt.formatting_options]
|
|
174
175
|
indent_width = 2
|
|
@@ -188,12 +189,25 @@ When `--config` is given, `pyproject.toml` discovery is skipped. Options you don
|
|
|
188
189
|
|--------|-------------|---------|
|
|
189
190
|
| `dialect` | SQL dialect passed to sqlglot (e.g. `postgres`, `mysql`, `bigquery`); `--dialect` overrides it | none (generic SQL) |
|
|
190
191
|
| `sql_keywords` | SQL keywords to recognize and format | Common SQL keywords |
|
|
191
|
-
| `function_names` | Python functions containing SQL code | `read_sql`, `execute`, `sql` (Marimo's `mo.sql`), etc. |
|
|
192
|
-
| `sql_decorators` | Decorators
|
|
193
|
-
| `
|
|
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 | |
|
|
194
196
|
| `formatting_options.indent_width` | Number of spaces for indentation | 4 |
|
|
195
197
|
|
|
196
|
-
`sql_keywords`, `function_names` and `sql_decorators` **replace** the built-in lists
|
|
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`.
|
|
197
211
|
|
|
198
212
|
## Examples
|
|
199
213
|
|
|
@@ -255,7 +269,7 @@ def _(mo):
|
|
|
255
269
|
|
|
256
270
|
**SQL not being formatted?**
|
|
257
271
|
- Ensure the string contains at least 2 SQL keywords or a recognizable pattern like `SELECT...FROM`
|
|
258
|
-
- Check that the function name is in the recognized list (add custom ones via `
|
|
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)
|
|
259
273
|
|
|
260
274
|
**Comments being modified?**
|
|
261
275
|
- Python comments (`#`) are preserved. SQL comments (`--`) inside strings are converted to `/* */` block comments by sqlglot.
|
|
@@ -283,6 +297,11 @@ source .venv/bin/activate # On Windows: .venv\Scripts\activate
|
|
|
283
297
|
3. Run tests:
|
|
284
298
|
```bash
|
|
285
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
|
|
286
305
|
```
|
|
287
306
|
|
|
288
307
|
4. Add eval cases: see `tests/eval/generate_fixtures.py` for examples. Run `python tests/eval/generate_fixtures.py` to regenerate fixtures.
|
|
@@ -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]
|
|
@@ -141,6 +141,7 @@ Use `--check` in GitHub Actions to enforce formatting:
|
|
|
141
141
|
```toml
|
|
142
142
|
[tool.sqlnbfmt]
|
|
143
143
|
dialect = "postgres"
|
|
144
|
+
extend_function_names = ["run_query"] # recognized in addition to the defaults
|
|
144
145
|
|
|
145
146
|
[tool.sqlnbfmt.formatting_options]
|
|
146
147
|
indent_width = 2
|
|
@@ -160,12 +161,25 @@ When `--config` is given, `pyproject.toml` discovery is skipped. Options you don
|
|
|
160
161
|
|--------|-------------|---------|
|
|
161
162
|
| `dialect` | SQL dialect passed to sqlglot (e.g. `postgres`, `mysql`, `bigquery`); `--dialect` overrides it | none (generic SQL) |
|
|
162
163
|
| `sql_keywords` | SQL keywords to recognize and format | Common SQL keywords |
|
|
163
|
-
| `function_names` | Python functions containing SQL code | `read_sql`, `execute`, `sql` (Marimo's `mo.sql`), etc. |
|
|
164
|
-
| `sql_decorators` | Decorators
|
|
165
|
-
| `
|
|
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 | |
|
|
166
168
|
| `formatting_options.indent_width` | Number of spaces for indentation | 4 |
|
|
167
169
|
|
|
168
|
-
`sql_keywords`, `function_names` and `sql_decorators` **replace** the built-in lists
|
|
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`.
|
|
169
183
|
|
|
170
184
|
## Examples
|
|
171
185
|
|
|
@@ -227,7 +241,7 @@ def _(mo):
|
|
|
227
241
|
|
|
228
242
|
**SQL not being formatted?**
|
|
229
243
|
- Ensure the string contains at least 2 SQL keywords or a recognizable pattern like `SELECT...FROM`
|
|
230
|
-
- Check that the function name is in the recognized list (add custom ones via `
|
|
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)
|
|
231
245
|
|
|
232
246
|
**Comments being modified?**
|
|
233
247
|
- Python comments (`#`) are preserved. SQL comments (`--`) inside strings are converted to `/* */` block comments by sqlglot.
|
|
@@ -255,6 +269,11 @@ source .venv/bin/activate # On Windows: .venv\Scripts\activate
|
|
|
255
269
|
3. Run tests:
|
|
256
270
|
```bash
|
|
257
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
|
|
258
277
|
```
|
|
259
278
|
|
|
260
279
|
4. Add eval cases: see `tests/eval/generate_fixtures.py` for examples. Run `python tests/eval/generate_fixtures.py` to regenerate fixtures.
|
|
@@ -28,6 +28,11 @@ sql_keywords:
|
|
|
28
28
|
- OFFSET
|
|
29
29
|
- DISTINCT
|
|
30
30
|
|
|
31
|
+
# To add to the defaults instead of replacing them, use extend_function_names
|
|
32
|
+
# (and extend_sql_keywords / extend_sql_decorators), e.g.:
|
|
33
|
+
# extend_function_names:
|
|
34
|
+
# - run_query
|
|
35
|
+
|
|
31
36
|
function_names:
|
|
32
37
|
- read_sql
|
|
33
38
|
- read_sql_query
|
|
@@ -49,5 +54,4 @@ sql_decorators:
|
|
|
49
54
|
|
|
50
55
|
# Formatting options
|
|
51
56
|
formatting_options:
|
|
52
|
-
single_line_threshold: 80 # Maximum length for single-line SQL
|
|
53
57
|
indent_width: 4 # Number of spaces for indentation
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "sqlnbfmt"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.6.0"
|
|
4
4
|
description = "A tool to format SQL code in Jupyter notebooks and Marimo notebooks."
|
|
5
5
|
authors = [
|
|
6
6
|
{ name = "Qing", email = "qingye779@gmail.com" }
|
|
@@ -49,8 +49,10 @@ build-backend = "hatchling.build"
|
|
|
49
49
|
|
|
50
50
|
[tool.pytest.ini_options]
|
|
51
51
|
markers = [
|
|
52
|
-
"integration: mark a test as an integration test."
|
|
52
|
+
"integration: mark a test as an integration test.",
|
|
53
|
+
"live: end-to-end tests against the built wheel; slow, need uv, git and network (run with `pytest -m live`)",
|
|
53
54
|
]
|
|
55
|
+
addopts = "-m 'not live'"
|
|
54
56
|
|
|
55
57
|
[tool.hatch.build.targets.wheel.shared-data]
|
|
56
58
|
"jupyter-config/sqlnbfmt.json" = "etc/jupyter/jupyter_server_config.d/sqlnbfmt.json"
|