sqlnbfmt 0.4.2__tar.gz → 0.5.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 (87) hide show
  1. sqlnbfmt-0.5.0/.github/dependabot.yml +23 -0
  2. sqlnbfmt-0.5.0/.github/workflows/ci-and-publish.yml +55 -0
  3. {sqlnbfmt-0.4.2 → sqlnbfmt-0.5.0}/.pre-commit-config.yaml +3 -3
  4. {sqlnbfmt-0.4.2 → sqlnbfmt-0.5.0}/CHANGELOG.md +23 -1
  5. {sqlnbfmt-0.4.2 → sqlnbfmt-0.5.0}/PKG-INFO +55 -36
  6. {sqlnbfmt-0.4.2 → sqlnbfmt-0.5.0}/README.md +53 -34
  7. sqlnbfmt-0.5.0/SECURITY.md +13 -0
  8. {sqlnbfmt-0.4.2 → sqlnbfmt-0.5.0}/config.yaml +4 -0
  9. {sqlnbfmt-0.4.2 → sqlnbfmt-0.5.0}/pyproject.toml +7 -2
  10. sqlnbfmt-0.5.0/sqlnbfmt/__init__.py +29 -0
  11. {sqlnbfmt-0.4.2 → sqlnbfmt-0.5.0}/sqlnbfmt/formatter.py +276 -93
  12. {sqlnbfmt-0.4.2 → sqlnbfmt-0.5.0}/sqlnbfmt/jupyterlab_integration.py +29 -12
  13. {sqlnbfmt-0.4.2 → sqlnbfmt-0.5.0}/tests/eval/in_function_sql/expected.ipynb +1 -2
  14. {sqlnbfmt-0.4.2/tests/eval/marimo_idempotency → sqlnbfmt-0.5.0/tests/eval/marimo_basic_sql}/expected.py +11 -11
  15. {sqlnbfmt-0.4.2 → sqlnbfmt-0.5.0}/tests/eval/marimo_comment_preservation/expected.py +20 -20
  16. sqlnbfmt-0.5.0/tests/eval/marimo_complex_sql/expected.py +63 -0
  17. {sqlnbfmt-0.4.2 → sqlnbfmt-0.5.0}/tests/eval/marimo_fstring_interpolation/expected.py +19 -19
  18. {sqlnbfmt-0.4.2 → sqlnbfmt-0.5.0}/tests/eval/marimo_multi_cell_mixed/expected.py +22 -22
  19. sqlnbfmt-0.5.0/tests/eval/marimo_multi_statement/expected.py +34 -0
  20. sqlnbfmt-0.5.0/tests/test_call_layout.py +151 -0
  21. {sqlnbfmt-0.4.2 → sqlnbfmt-0.5.0}/tests/test_formatter.py +22 -4
  22. sqlnbfmt-0.5.0/tests/test_jupyterlab_integration.py +208 -0
  23. {sqlnbfmt-0.4.2 → sqlnbfmt-0.5.0}/tests/test_marimo.py +6 -2
  24. sqlnbfmt-0.5.0/tests/test_pyproject_config.py +244 -0
  25. sqlnbfmt-0.5.0/uv.lock +497 -0
  26. sqlnbfmt-0.4.2/.github/workflows/ci-and-publish.yml +0 -42
  27. sqlnbfmt-0.4.2/sqlnbfmt/__init__.py +0 -15
  28. sqlnbfmt-0.4.2/tests/eval/marimo_complex_sql/expected.py +0 -63
  29. sqlnbfmt-0.4.2/tests/eval/marimo_multi_statement/expected.py +0 -34
  30. sqlnbfmt-0.4.2/tests/test_jupyterlab_integration.py +0 -76
  31. sqlnbfmt-0.4.2/uv.lock +0 -455
  32. {sqlnbfmt-0.4.2 → sqlnbfmt-0.5.0}/.gitignore +0 -0
  33. {sqlnbfmt-0.4.2 → sqlnbfmt-0.5.0}/.pre-commit-hooks.yaml +0 -0
  34. {sqlnbfmt-0.4.2 → sqlnbfmt-0.5.0}/.python-version +0 -0
  35. {sqlnbfmt-0.4.2 → sqlnbfmt-0.5.0}/LICENSE +0 -0
  36. {sqlnbfmt-0.4.2 → sqlnbfmt-0.5.0}/jupyter-config/sqlnbfmt.json +0 -0
  37. {sqlnbfmt-0.4.2 → sqlnbfmt-0.5.0}/tests/__init__.py +0 -0
  38. {sqlnbfmt-0.4.2 → sqlnbfmt-0.5.0}/tests/eval/README.md +0 -0
  39. {sqlnbfmt-0.4.2 → sqlnbfmt-0.5.0}/tests/eval/basic_select/expected.ipynb +0 -0
  40. {sqlnbfmt-0.4.2 → sqlnbfmt-0.5.0}/tests/eval/basic_select/input.ipynb +0 -0
  41. {sqlnbfmt-0.4.2 → sqlnbfmt-0.5.0}/tests/eval/case_expression/expected.ipynb +0 -0
  42. {sqlnbfmt-0.4.2 → sqlnbfmt-0.5.0}/tests/eval/case_expression/input.ipynb +0 -0
  43. {sqlnbfmt-0.4.2 → sqlnbfmt-0.5.0}/tests/eval/comment_preservation/expected.ipynb +0 -0
  44. {sqlnbfmt-0.4.2 → sqlnbfmt-0.5.0}/tests/eval/comment_preservation/input.ipynb +0 -0
  45. {sqlnbfmt-0.4.2 → sqlnbfmt-0.5.0}/tests/eval/cte/expected.ipynb +0 -0
  46. {sqlnbfmt-0.4.2 → sqlnbfmt-0.5.0}/tests/eval/cte/input.ipynb +0 -0
  47. {sqlnbfmt-0.4.2 → sqlnbfmt-0.5.0}/tests/eval/fstring_variables/expected.ipynb +0 -0
  48. {sqlnbfmt-0.4.2 → sqlnbfmt-0.5.0}/tests/eval/fstring_variables/input.ipynb +0 -0
  49. {sqlnbfmt-0.4.2 → sqlnbfmt-0.5.0}/tests/eval/generate_fixtures.py +0 -0
  50. {sqlnbfmt-0.4.2 → sqlnbfmt-0.5.0}/tests/eval/group_by_having/expected.ipynb +0 -0
  51. {sqlnbfmt-0.4.2 → sqlnbfmt-0.5.0}/tests/eval/group_by_having/input.ipynb +0 -0
  52. {sqlnbfmt-0.4.2 → sqlnbfmt-0.5.0}/tests/eval/idempotency/expected.ipynb +0 -0
  53. {sqlnbfmt-0.4.2 → sqlnbfmt-0.5.0}/tests/eval/idempotency/input.ipynb +0 -0
  54. {sqlnbfmt-0.4.2 → sqlnbfmt-0.5.0}/tests/eval/in_function_sql/input.ipynb +0 -0
  55. {sqlnbfmt-0.4.2 → sqlnbfmt-0.5.0}/tests/eval/joins/expected.ipynb +0 -0
  56. {sqlnbfmt-0.4.2 → sqlnbfmt-0.5.0}/tests/eval/joins/input.ipynb +0 -0
  57. {sqlnbfmt-0.4.2 → sqlnbfmt-0.5.0}/tests/eval/magic_commands/expected.ipynb +0 -0
  58. {sqlnbfmt-0.4.2 → sqlnbfmt-0.5.0}/tests/eval/magic_commands/input.ipynb +0 -0
  59. {sqlnbfmt-0.4.2 → sqlnbfmt-0.5.0}/tests/eval/marimo_basic_sql/input.py +0 -0
  60. {sqlnbfmt-0.4.2 → sqlnbfmt-0.5.0}/tests/eval/marimo_comment_preservation/input.py +0 -0
  61. {sqlnbfmt-0.4.2 → sqlnbfmt-0.5.0}/tests/eval/marimo_complex_sql/input.py +0 -0
  62. {sqlnbfmt-0.4.2 → sqlnbfmt-0.5.0}/tests/eval/marimo_fstring_interpolation/input.py +0 -0
  63. {sqlnbfmt-0.4.2/tests/eval/marimo_basic_sql → sqlnbfmt-0.5.0/tests/eval/marimo_idempotency}/expected.py +0 -0
  64. {sqlnbfmt-0.4.2 → sqlnbfmt-0.5.0}/tests/eval/marimo_idempotency/input.py +0 -0
  65. {sqlnbfmt-0.4.2 → sqlnbfmt-0.5.0}/tests/eval/marimo_multi_cell_mixed/input.py +0 -0
  66. {sqlnbfmt-0.4.2 → sqlnbfmt-0.5.0}/tests/eval/marimo_multi_statement/input.py +0 -0
  67. {sqlnbfmt-0.4.2 → sqlnbfmt-0.5.0}/tests/eval/marimo_non_sql_unchanged/expected.py +0 -0
  68. {sqlnbfmt-0.4.2 → sqlnbfmt-0.5.0}/tests/eval/marimo_non_sql_unchanged/input.py +0 -0
  69. {sqlnbfmt-0.4.2 → sqlnbfmt-0.5.0}/tests/eval/marimo_skip_hint/expected.py +0 -0
  70. {sqlnbfmt-0.4.2 → sqlnbfmt-0.5.0}/tests/eval/marimo_skip_hint/input.py +0 -0
  71. {sqlnbfmt-0.4.2 → sqlnbfmt-0.5.0}/tests/eval/multi_cell_mixed/expected.ipynb +0 -0
  72. {sqlnbfmt-0.4.2 → sqlnbfmt-0.5.0}/tests/eval/multi_cell_mixed/input.ipynb +0 -0
  73. {sqlnbfmt-0.4.2 → sqlnbfmt-0.5.0}/tests/eval/multi_statement/expected.ipynb +0 -0
  74. {sqlnbfmt-0.4.2 → sqlnbfmt-0.5.0}/tests/eval/multi_statement/input.ipynb +0 -0
  75. {sqlnbfmt-0.4.2 → sqlnbfmt-0.5.0}/tests/eval/non_sql_unchanged/expected.ipynb +0 -0
  76. {sqlnbfmt-0.4.2 → sqlnbfmt-0.5.0}/tests/eval/non_sql_unchanged/input.ipynb +0 -0
  77. {sqlnbfmt-0.4.2 → sqlnbfmt-0.5.0}/tests/eval/skip_hint/expected.ipynb +0 -0
  78. {sqlnbfmt-0.4.2 → sqlnbfmt-0.5.0}/tests/eval/skip_hint/input.ipynb +0 -0
  79. {sqlnbfmt-0.4.2 → sqlnbfmt-0.5.0}/tests/eval/subquery/expected.ipynb +0 -0
  80. {sqlnbfmt-0.4.2 → sqlnbfmt-0.5.0}/tests/eval/subquery/input.ipynb +0 -0
  81. {sqlnbfmt-0.4.2 → sqlnbfmt-0.5.0}/tests/eval/union/expected.ipynb +0 -0
  82. {sqlnbfmt-0.4.2 → sqlnbfmt-0.5.0}/tests/eval/union/input.ipynb +0 -0
  83. {sqlnbfmt-0.4.2 → sqlnbfmt-0.5.0}/tests/eval/window_function/expected.ipynb +0 -0
  84. {sqlnbfmt-0.4.2 → sqlnbfmt-0.5.0}/tests/eval/window_function/input.ipynb +0 -0
  85. {sqlnbfmt-0.4.2 → sqlnbfmt-0.5.0}/tests/test_eval.py +0 -0
  86. {sqlnbfmt-0.4.2 → sqlnbfmt-0.5.0}/tests/test_notebook.ipynb +0 -0
  87. {sqlnbfmt-0.4.2 → sqlnbfmt-0.5.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,55 @@
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
+
33
+ security:
34
+ runs-on: ubuntu-latest
35
+ permissions:
36
+ contents: read
37
+ steps:
38
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
39
+ - uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0
40
+ - run: uvx uv-secure uv.lock
41
+
42
+ publish:
43
+ needs: [test, security]
44
+ if: github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && github.event.inputs.run_publish == 'true')
45
+ runs-on: ubuntu-latest
46
+ permissions:
47
+ id-token: write
48
+ steps:
49
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
50
+ - uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0
51
+ with:
52
+ enable-cache: true
53
+ - run: uv python install 3.12
54
+ - run: uv build --no-sources
55
+ - 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.15.6
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: v0.25
27
+ rev: '0.26'
28
28
  hooks:
29
29
  - id: validate-pyproject
30
30
 
@@ -5,7 +5,29 @@ 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.4.2] - Unreleased
8
+ ## [0.5.0] - 2026-10-15
9
+
10
+ ### Added
11
+
12
+ - **`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))
13
+ - `--config` accepts a `.toml` path as well as YAML
14
+ - `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
15
+
16
+ ### Changed
17
+
18
+ - 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
19
+ - Unknown config options now produce a warning instead of being silently ignored
20
+
21
+ ### Fixed
22
+
23
+ - 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
24
+ - 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
25
+ - 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
26
+ - 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
27
+ - Upgraded locked dependencies, including sqlglot 29 → 30 (formatting output unchanged across the eval suite)
28
+ - A skipped Jupyter extension registration is now logged at debug level instead of being silently swallowed
29
+
30
+ ## [0.4.2] - 2026-03-24
9
31
 
10
32
  ### Added
11
33
 
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: sqlnbfmt
3
- Version: 0.4.2
3
+ Version: 0.5.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 customize the dialect, add this to `~/.jupyter/jupyter_server_config.py`:
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.4.0
124
+ rev: v0.5.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. To specify a dialect or custom config:
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,38 @@ Use `--check` in GitHub Actions to enforce formatting:
162
162
 
163
163
  ## Configuration
164
164
 
165
- `sqlnbfmt` works without any configuration file. A `config.yaml` is only needed to override defaults.
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
- Create a `config.yaml` file to customize formatting behavior. [Here](https://github.com/flyersworder/sqlnbfmt/blob/main/config.yaml) is a template.
167
+ ### pyproject.toml
168
+
169
+ ```toml
170
+ [tool.sqlnbfmt]
171
+ dialect = "postgres"
172
+
173
+ [tool.sqlnbfmt.formatting_options]
174
+ indent_width = 2
175
+ ```
176
+
177
+ `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.
178
+
179
+ ### YAML file
180
+
181
+ 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.
182
+
183
+ 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
184
 
169
185
  ### Configuration Options
170
186
 
171
187
  | Option | Description | Default |
172
188
  |--------|-------------|---------|
189
+ | `dialect` | SQL dialect passed to sqlglot (e.g. `postgres`, `mysql`, `bigquery`); `--dialect` overrides it | none (generic SQL) |
173
190
  | `sql_keywords` | SQL keywords to recognize and format | Common SQL keywords |
174
- | `function_names` | Python functions containing SQL code | `read_sql`, `execute`, etc. |
191
+ | `function_names` | Python functions containing SQL code | `read_sql`, `execute`, `sql` (Marimo's `mo.sql`), etc. |
175
192
  | `sql_decorators` | Decorators indicating SQL code | `query`, `sql_query`, etc. |
176
- | `single_line_threshold` | Maximum length before splitting SQL | 80 |
177
- | `indent_width` | Number of spaces for indentation | 4 |
193
+ | `formatting_options.single_line_threshold` | Maximum length before splitting SQL | 80 |
194
+ | `formatting_options.indent_width` | Number of spaces for indentation | 4 |
195
+
196
+ `sql_keywords`, `function_names` and `sql_decorators` **replace** the built-in lists rather than extending them. Copy the defaults you still rely on from the [template](https://github.com/flyersworder/sqlnbfmt/blob/main/config.yaml); for example, keep `sql` in `function_names` or Marimo's `mo.sql()` calls stop being formatted.
178
197
 
179
198
  ## Examples
180
199
 
@@ -187,20 +206,20 @@ execute_sql("""SELECT a.col1, b.col2 FROM table_a a JOIN table_b b ON a.id = b.a
187
206
 
188
207
  After formatting:
189
208
  ```python
190
- execute_sql("""
191
- SELECT
192
- a.col1,
193
- b.col2
194
- FROM
195
- table_a AS a
196
- JOIN
197
- table_b AS b
198
- ON a.id = b.a_id
199
- WHERE
200
- a.status = 'active'
201
- ORDER BY
202
- a.created_at DESC
203
- """)
209
+ execute_sql(
210
+ """
211
+ SELECT
212
+ a.col1,
213
+ b.col2
214
+ FROM table_a AS a
215
+ JOIN table_b AS b
216
+ ON a.id = b.a_id
217
+ WHERE
218
+ a.status = 'active'
219
+ ORDER BY
220
+ a.created_at DESC
221
+ """
222
+ )
204
223
  ```
205
224
 
206
225
  ### Marimo Notebook
@@ -218,17 +237,17 @@ After formatting:
218
237
  @app.cell
219
238
  def _(mo):
220
239
  _df = mo.sql(
221
- f"""
222
- SELECT
223
- id,
224
- name
225
- FROM users
226
- WHERE
227
- active = 1
228
- ORDER BY
229
- name
230
- """
231
- )
240
+ f"""
241
+ SELECT
242
+ id,
243
+ name
244
+ FROM users
245
+ WHERE
246
+ active = 1
247
+ ORDER BY
248
+ name
249
+ """
250
+ )
232
251
  return
233
252
  ```
234
253
 
@@ -236,7 +255,7 @@ def _(mo):
236
255
 
237
256
  **SQL not being formatted?**
238
257
  - 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 (use `--config` to add custom ones)
258
+ - Check that the function name is in the recognized list (add custom ones via `function_names` in `[tool.sqlnbfmt]` or `--config`)
240
259
 
241
260
  **Comments being modified?**
242
261
  - Python comments (`#`) are preserved. SQL comments (`--`) inside strings are converted to `/* */` block comments by sqlglot.
@@ -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 customize the dialect, add this to `~/.jupyter/jupyter_server_config.py`:
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.4.0
96
+ rev: v0.5.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. To specify a dialect or custom config:
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,38 @@ Use `--check` in GitHub Actions to enforce formatting:
134
134
 
135
135
  ## Configuration
136
136
 
137
- `sqlnbfmt` works without any configuration file. A `config.yaml` is only needed to override defaults.
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
- Create a `config.yaml` file to customize formatting behavior. [Here](https://github.com/flyersworder/sqlnbfmt/blob/main/config.yaml) is a template.
139
+ ### pyproject.toml
140
+
141
+ ```toml
142
+ [tool.sqlnbfmt]
143
+ dialect = "postgres"
144
+
145
+ [tool.sqlnbfmt.formatting_options]
146
+ indent_width = 2
147
+ ```
148
+
149
+ `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.
150
+
151
+ ### YAML file
152
+
153
+ 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.
154
+
155
+ 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
156
 
141
157
  ### Configuration Options
142
158
 
143
159
  | Option | Description | Default |
144
160
  |--------|-------------|---------|
161
+ | `dialect` | SQL dialect passed to sqlglot (e.g. `postgres`, `mysql`, `bigquery`); `--dialect` overrides it | none (generic SQL) |
145
162
  | `sql_keywords` | SQL keywords to recognize and format | Common SQL keywords |
146
- | `function_names` | Python functions containing SQL code | `read_sql`, `execute`, etc. |
163
+ | `function_names` | Python functions containing SQL code | `read_sql`, `execute`, `sql` (Marimo's `mo.sql`), etc. |
147
164
  | `sql_decorators` | Decorators indicating SQL code | `query`, `sql_query`, etc. |
148
- | `single_line_threshold` | Maximum length before splitting SQL | 80 |
149
- | `indent_width` | Number of spaces for indentation | 4 |
165
+ | `formatting_options.single_line_threshold` | Maximum length before splitting SQL | 80 |
166
+ | `formatting_options.indent_width` | Number of spaces for indentation | 4 |
167
+
168
+ `sql_keywords`, `function_names` and `sql_decorators` **replace** the built-in lists rather than extending them. Copy the defaults you still rely on from the [template](https://github.com/flyersworder/sqlnbfmt/blob/main/config.yaml); for example, keep `sql` in `function_names` or Marimo's `mo.sql()` calls stop being formatted.
150
169
 
151
170
  ## Examples
152
171
 
@@ -159,20 +178,20 @@ execute_sql("""SELECT a.col1, b.col2 FROM table_a a JOIN table_b b ON a.id = b.a
159
178
 
160
179
  After formatting:
161
180
  ```python
162
- execute_sql("""
163
- SELECT
164
- a.col1,
165
- b.col2
166
- FROM
167
- table_a AS a
168
- JOIN
169
- table_b AS b
170
- ON a.id = b.a_id
171
- WHERE
172
- a.status = 'active'
173
- ORDER BY
174
- a.created_at DESC
175
- """)
181
+ execute_sql(
182
+ """
183
+ SELECT
184
+ a.col1,
185
+ b.col2
186
+ FROM table_a AS a
187
+ JOIN table_b AS b
188
+ ON a.id = b.a_id
189
+ WHERE
190
+ a.status = 'active'
191
+ ORDER BY
192
+ a.created_at DESC
193
+ """
194
+ )
176
195
  ```
177
196
 
178
197
  ### Marimo Notebook
@@ -190,17 +209,17 @@ After formatting:
190
209
  @app.cell
191
210
  def _(mo):
192
211
  _df = mo.sql(
193
- f"""
194
- SELECT
195
- id,
196
- name
197
- FROM users
198
- WHERE
199
- active = 1
200
- ORDER BY
201
- name
202
- """
203
- )
212
+ f"""
213
+ SELECT
214
+ id,
215
+ name
216
+ FROM users
217
+ WHERE
218
+ active = 1
219
+ ORDER BY
220
+ name
221
+ """
222
+ )
204
223
  return
205
224
  ```
206
225
 
@@ -208,7 +227,7 @@ def _(mo):
208
227
 
209
228
  **SQL not being formatted?**
210
229
  - 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 (use `--config` to add custom ones)
230
+ - Check that the function name is in the recognized list (add custom ones via `function_names` in `[tool.sqlnbfmt]` or `--config`)
212
231
 
213
232
  **Comments being modified?**
214
233
  - Python comments (`#`) are preserved. SQL comments (`--`) inside strings are converted to `/* */` block comments by sqlglot.
@@ -0,0 +1,13 @@
1
+ # Security Policy
2
+
3
+ ## Supported Versions
4
+
5
+ Only the latest release receives security fixes.
6
+
7
+ ## Reporting a Vulnerability
8
+
9
+ Please **do not** open a public GitHub issue for security vulnerabilities.
10
+
11
+ Report them privately via [GitHub Security Advisories](https://github.com/flyersworder/sqlnbfmt/security/advisories/new). Include a description of the issue, steps to reproduce, and the potential impact.
12
+
13
+ You can expect an acknowledgement within 48 hours and a patch or mitigation plan within 14 days for confirmed issues.
@@ -2,6 +2,9 @@
2
2
  # Use this file only to override specific settings.
3
3
  # config.yaml
4
4
 
5
+ # SQL dialect for sqlglot (e.g. postgres, mysql, bigquery); --dialect overrides it
6
+ # dialect: postgres
7
+
5
8
  sql_keywords:
6
9
  - SELECT
7
10
  - FROM
@@ -35,6 +38,7 @@ function_names:
35
38
  - fetchone
36
39
  - fetchmany
37
40
  - execute_query
41
+ - sql # Marimo mo.sql()
38
42
 
39
43
  # Decorator names that contain SQL
40
44
  sql_decorators:
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "sqlnbfmt"
3
- version = "0.4.2"
3
+ version = "0.5.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" }
@@ -56,4 +56,9 @@ markers = [
56
56
  "jupyter-config/sqlnbfmt.json" = "etc/jupyter/jupyter_server_config.d/sqlnbfmt.json"
57
57
 
58
58
  [tool.ruff]
59
- extend-exclude = ["tests/eval/*/"]
59
+ # Eval cases and test notebooks are fixtures: their exact formatting is the test input.
60
+ extend-exclude = ["tests/eval/*/", "tests/*.ipynb"]
61
+
62
+ [tool.ruff.format]
63
+ # Code blocks in docs show sqlnbfmt's own output, not ruff's.
64
+ exclude = ["*.md"]
@@ -0,0 +1,29 @@
1
+ def _jupyter_server_extension_points():
2
+ return [{"module": "sqlnbfmt"}]
3
+
4
+
5
+ def _load_jupyter_server_extension(server_app):
6
+ try:
7
+ from jupyterlab_code_formatter.formatters import SERVER_FORMATTERS
8
+ except ImportError:
9
+ server_app.log.debug(
10
+ "sqlnbfmt: jupyterlab-code-formatter not installed; skipping registration"
11
+ )
12
+ return
13
+
14
+ # jupyter_server_config.py runs before extensions load; keep a formatter the
15
+ # user registered there (e.g. register(dialect="postgres")).
16
+ if "sqlnbfmt" in SERVER_FORMATTERS:
17
+ server_app.log.info("sqlnbfmt: keeping the formatter registered in config")
18
+ return
19
+
20
+ try:
21
+ from sqlnbfmt.jupyterlab_integration import register
22
+
23
+ register()
24
+ except Exception as e: # noqa: BLE001 - must never break Jupyter server startup
25
+ server_app.log.error(f"sqlnbfmt: SQL formatter not registered: {e}")
26
+ return
27
+ server_app.log.info(
28
+ "sqlnbfmt: registered SQL formatter with jupyterlab-code-formatter"
29
+ )