sqlnbfmt 0.4.1__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 (89) 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.5.0/.gitignore +163 -0
  4. sqlnbfmt-0.5.0/.pre-commit-config.yaml +41 -0
  5. sqlnbfmt-0.5.0/.pre-commit-hooks.yaml +35 -0
  6. sqlnbfmt-0.5.0/.python-version +1 -0
  7. sqlnbfmt-0.5.0/CHANGELOG.md +165 -0
  8. {sqlnbfmt-0.4.1/sqlnbfmt.egg-info → sqlnbfmt-0.5.0}/PKG-INFO +65 -48
  9. sqlnbfmt-0.4.1/PKG-INFO → sqlnbfmt-0.5.0/README.md +56 -67
  10. sqlnbfmt-0.5.0/SECURITY.md +13 -0
  11. sqlnbfmt-0.5.0/config.yaml +53 -0
  12. sqlnbfmt-0.5.0/jupyter-config/sqlnbfmt.json +7 -0
  13. {sqlnbfmt-0.4.1 → sqlnbfmt-0.5.0}/pyproject.toml +11 -6
  14. sqlnbfmt-0.5.0/sqlnbfmt/__init__.py +29 -0
  15. {sqlnbfmt-0.4.1 → sqlnbfmt-0.5.0}/sqlnbfmt/formatter.py +276 -93
  16. {sqlnbfmt-0.4.1 → sqlnbfmt-0.5.0}/sqlnbfmt/jupyterlab_integration.py +31 -14
  17. sqlnbfmt-0.5.0/tests/eval/README.md +103 -0
  18. sqlnbfmt-0.5.0/tests/eval/basic_select/expected.ipynb +26 -0
  19. sqlnbfmt-0.5.0/tests/eval/basic_select/input.ipynb +17 -0
  20. sqlnbfmt-0.5.0/tests/eval/case_expression/expected.ipynb +22 -0
  21. sqlnbfmt-0.5.0/tests/eval/case_expression/input.ipynb +17 -0
  22. sqlnbfmt-0.5.0/tests/eval/comment_preservation/expected.ipynb +27 -0
  23. sqlnbfmt-0.5.0/tests/eval/comment_preservation/input.ipynb +19 -0
  24. sqlnbfmt-0.5.0/tests/eval/cte/expected.ipynb +29 -0
  25. sqlnbfmt-0.5.0/tests/eval/cte/input.ipynb +17 -0
  26. sqlnbfmt-0.5.0/tests/eval/fstring_variables/expected.ipynb +42 -0
  27. sqlnbfmt-0.5.0/tests/eval/fstring_variables/input.ipynb +30 -0
  28. sqlnbfmt-0.5.0/tests/eval/generate_fixtures.py +306 -0
  29. sqlnbfmt-0.5.0/tests/eval/group_by_having/expected.ipynb +29 -0
  30. sqlnbfmt-0.5.0/tests/eval/group_by_having/input.ipynb +17 -0
  31. sqlnbfmt-0.5.0/tests/eval/idempotency/expected.ipynb +24 -0
  32. sqlnbfmt-0.5.0/tests/eval/idempotency/input.ipynb +24 -0
  33. sqlnbfmt-0.5.0/tests/eval/in_function_sql/expected.ipynb +43 -0
  34. sqlnbfmt-0.5.0/tests/eval/in_function_sql/input.ipynb +27 -0
  35. sqlnbfmt-0.5.0/tests/eval/joins/expected.ipynb +27 -0
  36. sqlnbfmt-0.5.0/tests/eval/joins/input.ipynb +17 -0
  37. sqlnbfmt-0.5.0/tests/eval/magic_commands/expected.ipynb +50 -0
  38. sqlnbfmt-0.5.0/tests/eval/magic_commands/input.ipynb +40 -0
  39. sqlnbfmt-0.5.0/tests/eval/marimo_basic_sql/expected.py +31 -0
  40. sqlnbfmt-0.5.0/tests/eval/marimo_basic_sql/input.py +20 -0
  41. sqlnbfmt-0.5.0/tests/eval/marimo_comment_preservation/expected.py +50 -0
  42. sqlnbfmt-0.5.0/tests/eval/marimo_comment_preservation/input.py +30 -0
  43. sqlnbfmt-0.5.0/tests/eval/marimo_complex_sql/expected.py +63 -0
  44. sqlnbfmt-0.5.0/tests/eval/marimo_complex_sql/input.py +27 -0
  45. sqlnbfmt-0.5.0/tests/eval/marimo_fstring_interpolation/expected.py +47 -0
  46. sqlnbfmt-0.5.0/tests/eval/marimo_fstring_interpolation/input.py +28 -0
  47. sqlnbfmt-0.5.0/tests/eval/marimo_idempotency/expected.py +31 -0
  48. sqlnbfmt-0.5.0/tests/eval/marimo_idempotency/input.py +31 -0
  49. sqlnbfmt-0.5.0/tests/eval/marimo_multi_cell_mixed/expected.py +66 -0
  50. sqlnbfmt-0.5.0/tests/eval/marimo_multi_cell_mixed/input.py +44 -0
  51. sqlnbfmt-0.5.0/tests/eval/marimo_multi_statement/expected.py +34 -0
  52. sqlnbfmt-0.5.0/tests/eval/marimo_multi_statement/input.py +20 -0
  53. sqlnbfmt-0.5.0/tests/eval/marimo_non_sql_unchanged/expected.py +39 -0
  54. sqlnbfmt-0.5.0/tests/eval/marimo_non_sql_unchanged/input.py +39 -0
  55. sqlnbfmt-0.5.0/tests/eval/marimo_skip_hint/expected.py +21 -0
  56. sqlnbfmt-0.5.0/tests/eval/marimo_skip_hint/input.py +21 -0
  57. sqlnbfmt-0.5.0/tests/eval/multi_cell_mixed/expected.ipynb +76 -0
  58. sqlnbfmt-0.5.0/tests/eval/multi_cell_mixed/input.ipynb +65 -0
  59. sqlnbfmt-0.5.0/tests/eval/multi_statement/expected.ipynb +29 -0
  60. sqlnbfmt-0.5.0/tests/eval/multi_statement/input.ipynb +18 -0
  61. sqlnbfmt-0.5.0/tests/eval/non_sql_unchanged/expected.ipynb +44 -0
  62. sqlnbfmt-0.5.0/tests/eval/non_sql_unchanged/input.ipynb +44 -0
  63. sqlnbfmt-0.5.0/tests/eval/skip_hint/expected.ipynb +35 -0
  64. sqlnbfmt-0.5.0/tests/eval/skip_hint/input.ipynb +28 -0
  65. sqlnbfmt-0.5.0/tests/eval/subquery/expected.ipynb +29 -0
  66. sqlnbfmt-0.5.0/tests/eval/subquery/input.ipynb +17 -0
  67. sqlnbfmt-0.5.0/tests/eval/union/expected.ipynb +31 -0
  68. sqlnbfmt-0.5.0/tests/eval/union/input.ipynb +17 -0
  69. sqlnbfmt-0.5.0/tests/eval/window_function/expected.ipynb +23 -0
  70. sqlnbfmt-0.5.0/tests/eval/window_function/input.ipynb +17 -0
  71. sqlnbfmt-0.5.0/tests/test_call_layout.py +151 -0
  72. {sqlnbfmt-0.4.1 → sqlnbfmt-0.5.0}/tests/test_formatter.py +22 -4
  73. sqlnbfmt-0.5.0/tests/test_jupyterlab_integration.py +208 -0
  74. {sqlnbfmt-0.4.1 → sqlnbfmt-0.5.0}/tests/test_marimo.py +6 -2
  75. sqlnbfmt-0.5.0/tests/test_notebook.ipynb +358 -0
  76. sqlnbfmt-0.5.0/tests/test_notebook_copy.ipynb +311 -0
  77. sqlnbfmt-0.5.0/tests/test_pyproject_config.py +244 -0
  78. sqlnbfmt-0.5.0/uv.lock +497 -0
  79. sqlnbfmt-0.4.1/README.md +0 -259
  80. sqlnbfmt-0.4.1/setup.cfg +0 -4
  81. sqlnbfmt-0.4.1/sqlnbfmt.egg-info/SOURCES.txt +0 -16
  82. sqlnbfmt-0.4.1/sqlnbfmt.egg-info/dependency_links.txt +0 -1
  83. sqlnbfmt-0.4.1/sqlnbfmt.egg-info/entry_points.txt +0 -2
  84. sqlnbfmt-0.4.1/sqlnbfmt.egg-info/requires.txt +0 -3
  85. sqlnbfmt-0.4.1/sqlnbfmt.egg-info/top_level.txt +0 -1
  86. sqlnbfmt-0.4.1/tests/test_jupyterlab_integration.py +0 -76
  87. {sqlnbfmt-0.4.1 → sqlnbfmt-0.5.0}/LICENSE +0 -0
  88. {sqlnbfmt-0.4.1/sqlnbfmt → sqlnbfmt-0.5.0/tests}/__init__.py +0 -0
  89. {sqlnbfmt-0.4.1 → sqlnbfmt-0.5.0}/tests/test_eval.py +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
@@ -0,0 +1,163 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ share/python-wheels/
24
+ *.egg-info/
25
+ .installed.cfg
26
+ *.egg
27
+ MANIFEST
28
+
29
+ # PyInstaller
30
+ # Usually these files are written by a python script from a template
31
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
32
+ *.manifest
33
+ *.spec
34
+
35
+ # Installer logs
36
+ pip-log.txt
37
+ pip-delete-this-directory.txt
38
+
39
+ # Unit test / coverage reports
40
+ htmlcov/
41
+ .tox/
42
+ .nox/
43
+ .coverage
44
+ .coverage.*
45
+ .cache
46
+ nosetests.xml
47
+ coverage.xml
48
+ *.cover
49
+ *.py,cover
50
+ .hypothesis/
51
+ .pytest_cache/
52
+ cover/
53
+
54
+ # Translations
55
+ *.mo
56
+ *.pot
57
+
58
+ # Django stuff:
59
+ *.log
60
+ local_settings.py
61
+ db.sqlite3
62
+ db.sqlite3-journal
63
+
64
+ # Flask stuff:
65
+ instance/
66
+ .webassets-cache
67
+
68
+ # Scrapy stuff:
69
+ .scrapy
70
+
71
+ # Sphinx documentation
72
+ docs/_build/
73
+
74
+ # PyBuilder
75
+ .pybuilder/
76
+ target/
77
+
78
+ # Jupyter Notebook
79
+ .ipynb_checkpoints
80
+
81
+ # IPython
82
+ profile_default/
83
+ ipython_config.py
84
+
85
+ # pyenv
86
+ # For a library or package, you might want to ignore these files since the code is
87
+ # intended to run in multiple environments; otherwise, check them in:
88
+ # .python-version
89
+
90
+ # pipenv
91
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
93
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
94
+ # install all needed dependencies.
95
+ #Pipfile.lock
96
+
97
+ # poetry
98
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
99
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
100
+ # commonly ignored for libraries.
101
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
102
+ #poetry.lock
103
+
104
+ # pdm
105
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
106
+ #pdm.lock
107
+ # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
108
+ # in version control.
109
+ # https://pdm.fming.dev/latest/usage/project/#working-with-version-control
110
+ .pdm.toml
111
+ .pdm-python
112
+ .pdm-build/
113
+
114
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
115
+ __pypackages__/
116
+
117
+ # Celery stuff
118
+ celerybeat-schedule
119
+ celerybeat.pid
120
+
121
+ # SageMath parsed files
122
+ *.sage.py
123
+
124
+ # Environments
125
+ .env
126
+ .venv
127
+ env/
128
+ venv/
129
+ ENV/
130
+ env.bak/
131
+ venv.bak/
132
+
133
+ # Spyder project settings
134
+ .spyderproject
135
+ .spyproject
136
+
137
+ # Rope project settings
138
+ .ropeproject
139
+
140
+ # mkdocs documentation
141
+ /site
142
+
143
+ # mypy
144
+ .mypy_cache/
145
+ .dmypy.json
146
+ dmypy.json
147
+
148
+ # Pyre type checker
149
+ .pyre/
150
+
151
+ # pytype static type analyzer
152
+ .pytype/
153
+
154
+ # Cython debug symbols
155
+ cython_debug/
156
+
157
+ # PyCharm
158
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
159
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
160
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
161
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
162
+ #.idea/
163
+ .DS_STORE
@@ -0,0 +1,41 @@
1
+ repos:
2
+ # Linting + formatting (Astral)
3
+ - repo: https://github.com/astral-sh/ruff-pre-commit
4
+ rev: v0.17.1
5
+ hooks:
6
+ - id: ruff-check
7
+ args: [--fix]
8
+ - id: ruff-format
9
+
10
+ # File quality + security
11
+ - repo: https://github.com/pre-commit/pre-commit-hooks
12
+ rev: v6.0.0
13
+ hooks:
14
+ - id: check-ast
15
+ - id: check-yaml
16
+ - id: check-toml
17
+ - id: check-json
18
+ - id: check-merge-conflict
19
+ - id: check-added-large-files
20
+ - id: debug-statements
21
+ - id: detect-private-key
22
+ - id: trailing-whitespace
23
+ - id: end-of-file-fixer
24
+
25
+ # Validate pyproject.toml against PEP 621
26
+ - repo: https://github.com/abravalheri/validate-pyproject
27
+ rev: '0.26'
28
+ hooks:
29
+ - id: validate-pyproject
30
+
31
+ # Local hooks
32
+ - repo: local
33
+ hooks:
34
+ # Fast unit tests
35
+ - id: pytest-fast
36
+ name: pytest (fast)
37
+ entry: uv run pytest tests/ -x -q --timeout=10
38
+ language: system
39
+ types: [python]
40
+ pass_filenames: false
41
+ stages: [pre-push]
@@ -0,0 +1,35 @@
1
+ - id: sqlnbfmt
2
+ name: SQL Notebook Formatter
3
+ description: Format SQL queries in Jupyter notebooks
4
+ entry: sqlnbfmt
5
+ language: python
6
+ types: [jupyter]
7
+ require_serial: true
8
+ minimum_pre_commit_version: '3.0.0'
9
+
10
+ - id: sqlnbfmt-check
11
+ name: SQL Notebook Formatter (check)
12
+ description: Check SQL formatting in Jupyter notebooks without modifying files
13
+ entry: sqlnbfmt --check
14
+ language: python
15
+ types: [jupyter]
16
+ require_serial: true
17
+ minimum_pre_commit_version: '3.0.0'
18
+
19
+ - id: sqlnbfmt-py
20
+ name: SQL Python File Formatter
21
+ description: Format SQL queries in Python files (e.g. Marimo notebooks)
22
+ entry: sqlnbfmt
23
+ language: python
24
+ types: [python]
25
+ require_serial: true
26
+ minimum_pre_commit_version: '3.0.0'
27
+
28
+ - id: sqlnbfmt-py-check
29
+ name: SQL Python File Formatter (check)
30
+ description: Check SQL formatting in Python files without modifying files
31
+ entry: sqlnbfmt --check
32
+ language: python
33
+ types: [python]
34
+ require_serial: true
35
+ minimum_pre_commit_version: '3.0.0'
@@ -0,0 +1 @@
1
+ 3.12
@@ -0,0 +1,165 @@
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.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
31
+
32
+ ### Added
33
+
34
+ - **Jupyter server extension**: `pip install sqlnbfmt` now auto-registers with jupyterlab-code-formatter — no manual config needed
35
+
36
+ ### Changed
37
+
38
+ - Switched build system from setuptools to hatchling
39
+ - Updated JupyterLab docs to use `jupyter_server_config.py` (modern) instead of `jupyter_notebook_config.py` (legacy)
40
+
41
+ ## [0.4.0] - 2026-03-24
42
+
43
+ ### Added
44
+
45
+ - **Marimo notebook support**: format SQL in Marimo `.py` notebooks via `mo.sql()` calls — same `sqlnbfmt` command, auto-detected by file extension
46
+ - **Python file support**: `sqlnbfmt` now accepts `.py` files alongside `.ipynb` — works on any Python file containing SQL strings
47
+ - **JupyterLab integration**: `sqlnbfmt.jupyterlab_integration.register()` for use with `jupyterlab-code-formatter`
48
+ - `format_cell_source()` public API for formatting a single cell's source code
49
+ - `_format_python_source()`, `process_python_file()`, `diff_python_file()` public API for programmatic use
50
+ - `sql` added to default recognized function names (matches `mo.sql()`, etc.)
51
+ - `sqlnbfmt-py` and `sqlnbfmt-py-check` pre-commit hooks for Python files
52
+ - 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`
53
+
54
+ ### Fixed
55
+
56
+ - Idempotency: already-formatted SQL in triple-quoted strings (with leading whitespace/indentation) is no longer re-formatted on every run
57
+
58
+ ### Changed
59
+
60
+ - CLI description and help text updated to reflect multi-format support
61
+ - Project description and keywords updated for Marimo discoverability
62
+
63
+ ## [0.3.2] - 2026-03-16
64
+
65
+ ### Fixed
66
+
67
+ - 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))
68
+
69
+ ### Changed
70
+
71
+ - SQL parsing switched from `sqlglot.parse_one()` to `sqlglot.parse()` to correctly handle multi-statement inputs
72
+
73
+ ### Added
74
+
75
+ - `multi_statement` eval fixture case covering `DROP TABLE` + `CREATE TABLE` sequences
76
+
77
+ ## [0.3.1] - 2026-03-15
78
+
79
+ ### Fixed
80
+
81
+ - `diff_notebook()` no longer duplicates cell-processing logic — extracted shared `_format_cells()` helper to prevent future divergence
82
+ - `--check --diff` combined mode no longer double-counts notebooks or processes them twice
83
+ - Skip hint (`# sqlnbfmt: skip`) no longer triggers on string literals containing the directive — only dedicated comment lines are recognised
84
+ - `load_config()` with a partial config file (missing keys) now falls back to built-in defaults instead of empty sets
85
+ - `load_config()` with an empty YAML file no longer raises `AttributeError`
86
+ - In-function SQL indentation now respects `indent_width` config instead of hardcoded 4 spaces
87
+
88
+ ### Added
89
+
90
+ - `main()` integration tests for `--diff`, `--check`, and `--check --diff` exit code behavior
91
+ - Docstring on `_has_skip_hint()` clarifying that inline trailing comments are intentionally ignored
92
+
93
+ ## [0.3.0] - 2026-03-15
94
+
95
+ ### Added
96
+
97
+ - **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
98
+ - **`--check` mode**: verify formatting without modifying files; exits with code 1 if changes are needed (CI-friendly)
99
+ - **`--diff` mode**: print a unified diff of formatting changes without modifying files
100
+ - **Skip hints**: add `# sqlnbfmt: skip` to any cell to skip formatting
101
+ - **`sqlnbfmt-check` pre-commit hook**: a read-only hook variant for CI pipelines
102
+ - Cell index in all warning/debug messages for easier debugging
103
+ - PyPI keywords and project URLs in package metadata
104
+ - `skip_hint` eval fixture case
105
+
106
+ ### Changed
107
+
108
+ - `--config` argument is now optional (defaults to built-in settings instead of requiring `config.yaml`)
109
+ - `load_config()` accepts `None` to return defaults; passing a missing path still raises `FileNotFoundError`
110
+ - Version bumped to 0.3.0
111
+
112
+ ## [0.2.0] - 2026-03-15
113
+
114
+ ### Fixed
115
+
116
+ - Python comments in code cells are no longer stripped during formatting ([#2](https://github.com/flyersworder/sqlnbfmt/issues/2))
117
+
118
+ ### Changed
119
+
120
+ - Replaced AST-mutation approach (`ast.NodeTransformer` + `astor` + `black`) with surgical string replacement (`ast.NodeVisitor` + offset-based text splicing), preserving all non-SQL content exactly
121
+ - Removed `astor` and `black` from dependencies — fewer install requirements
122
+ - CI publish now uses PyPI Trusted Publishers (OIDC) instead of API tokens
123
+ - Migrated `tool.uv.dev-dependencies` to `dependency-groups.dev` (PEP 735)
124
+ - Updated `actions/checkout` from v3 to v4
125
+
126
+ ### Added
127
+
128
+ - Python 3.13 classifier
129
+ - Comment-preservation test case
130
+
131
+ ### Fixed
132
+
133
+ - Typo in config.yaml: `exeucte_query` → `execute_query`
134
+
135
+ ## [0.1.9] - 2025-01-01
136
+
137
+ ### Changed
138
+
139
+ - Updated dependencies (attrs, black, referencing, sqlglot, typing-extensions)
140
+
141
+ ## [0.1.8] - 2024-12-01
142
+
143
+ ### Fixed
144
+
145
+ - First-line indentation error for in-function SQL
146
+ - New-line handling issues in formatted output
147
+ - Introduced `black` formatter to resolve in-function SQL formatting edge cases
148
+
149
+ ## [0.1.7] - 2024-11-01
150
+
151
+ ### Added
152
+
153
+ - Pre-commit hook support
154
+ - F-string SQL formatting (with and without variables)
155
+ - In-function SQL formatting (e.g., `pd.read_sql(...)`)
156
+
157
+ ## [0.1.0] - 2024-10-01
158
+
159
+ ### Added
160
+
161
+ - Initial release
162
+ - SQL formatting in Jupyter notebook code cells using sqlglot
163
+ - Support for `%sql` / `%%sql` magic commands
164
+ - Configurable SQL keywords, function names, and formatting options
165
+ - GitHub Actions CI pipeline
@@ -1,31 +1,30 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: sqlnbfmt
3
- Version: 0.4.1
3
+ Version: 0.5.0
4
4
  Summary: A tool to format SQL code in Jupyter notebooks and Marimo notebooks.
5
- Author-email: Qing <qingye779@gmail.com>
6
- License: MIT
7
5
  Project-URL: Homepage, https://github.com/flyersworder/sqlnbfmt
8
6
  Project-URL: Repository, https://github.com/flyersworder/sqlnbfmt
9
7
  Project-URL: Changelog, https://github.com/flyersworder/sqlnbfmt/blob/main/CHANGELOG.md
10
8
  Project-URL: Issues, https://github.com/flyersworder/sqlnbfmt/issues
11
- Keywords: sql,jupyter,marimo,notebook,formatter,sqlglot,pre-commit
9
+ Author-email: Qing <qingye779@gmail.com>
10
+ License: MIT
11
+ License-File: LICENSE
12
+ Keywords: formatter,jupyter,marimo,notebook,pre-commit,sql,sqlglot
12
13
  Classifier: Development Status :: 4 - Beta
14
+ Classifier: Framework :: Jupyter
13
15
  Classifier: Intended Audience :: Developers
14
16
  Classifier: Intended Audience :: Science/Research
15
17
  Classifier: License :: OSI Approved :: MIT License
16
18
  Classifier: Programming Language :: Python :: 3
17
19
  Classifier: Programming Language :: Python :: 3.12
18
20
  Classifier: Programming Language :: Python :: 3.13
19
- Classifier: Topic :: Software Development :: Quality Assurance
20
21
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
21
- Classifier: Framework :: Jupyter
22
+ Classifier: Topic :: Software Development :: Quality Assurance
22
23
  Requires-Python: >=3.12
23
- Description-Content-Type: text/markdown
24
- License-File: LICENSE
25
24
  Requires-Dist: nbformat>=5.10.4
26
25
  Requires-Dist: pyyaml>=6.0.2
27
26
  Requires-Dist: sqlglot>=25.27.0
28
- Dynamic: license-file
27
+ Description-Content-Type: text/markdown
29
28
 
30
29
  # sqlnbfmt
31
30
 
@@ -99,19 +98,18 @@ query = "select * from my_special_table where id = 1"
99
98
 
100
99
  Format SQL cells interactively with [jupyterlab-code-formatter](https://github.com/jupyterlab-contrib/jupyterlab_code_formatter):
101
100
 
102
- 1. Install both packages:
103
101
  ```bash
104
102
  pip install sqlnbfmt jupyterlab-code-formatter
105
103
  ```
106
104
 
107
- 2. Register the formatter in `~/.jupyter/jupyter_notebook_config.py`:
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
+
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
- register() # or: register(dialect="postgres")
110
+ register(dialect="postgres")
111
111
  ```
112
112
 
113
- 3. In JupyterLab, use **Edit > Format Cell** or configure format-on-save in Settings > Code Formatter.
114
-
115
113
  ### Pre-commit Integration
116
114
 
117
115
  1. Install pre-commit:
@@ -123,7 +121,7 @@ pip install pre-commit
123
121
  ```yaml
124
122
  repos:
125
123
  - repo: https://github.com/flyersworder/sqlnbfmt
126
- rev: v0.4.0
124
+ rev: v0.5.0
127
125
  hooks:
128
126
  - id: sqlnbfmt
129
127
  types: [jupyter]
@@ -135,7 +133,7 @@ To also format Marimo notebooks (Python files), add the Python hook:
135
133
  files: '\.py$' # optionally narrow with: files: 'marimo_.*\.py$'
136
134
  ```
137
135
 
138
- 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:
139
137
  ```yaml
140
138
  args: [--dialect, postgres, --config, config.yaml]
141
139
  ```
@@ -164,19 +162,38 @@ Use `--check` in GitHub Actions to enforce formatting:
164
162
 
165
163
  ## Configuration
166
164
 
167
- `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
+
167
+ ### pyproject.toml
168
168
 
169
- Create a `config.yaml` file to customize formatting behavior. [Here](https://github.com/flyersworder/sqlnbfmt/blob/main/config.yaml) is a template.
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.
170
184
 
171
185
  ### Configuration Options
172
186
 
173
187
  | Option | Description | Default |
174
188
  |--------|-------------|---------|
189
+ | `dialect` | SQL dialect passed to sqlglot (e.g. `postgres`, `mysql`, `bigquery`); `--dialect` overrides it | none (generic SQL) |
175
190
  | `sql_keywords` | SQL keywords to recognize and format | Common SQL keywords |
176
- | `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. |
177
192
  | `sql_decorators` | Decorators indicating SQL code | `query`, `sql_query`, etc. |
178
- | `single_line_threshold` | Maximum length before splitting SQL | 80 |
179
- | `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.
180
197
 
181
198
  ## Examples
182
199
 
@@ -189,20 +206,20 @@ execute_sql("""SELECT a.col1, b.col2 FROM table_a a JOIN table_b b ON a.id = b.a
189
206
 
190
207
  After formatting:
191
208
  ```python
192
- execute_sql("""
193
- SELECT
194
- a.col1,
195
- b.col2
196
- FROM
197
- table_a AS a
198
- JOIN
199
- table_b AS b
200
- ON a.id = b.a_id
201
- WHERE
202
- a.status = 'active'
203
- ORDER BY
204
- a.created_at DESC
205
- """)
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
+ )
206
223
  ```
207
224
 
208
225
  ### Marimo Notebook
@@ -220,17 +237,17 @@ After formatting:
220
237
  @app.cell
221
238
  def _(mo):
222
239
  _df = mo.sql(
223
- f"""
224
- SELECT
225
- id,
226
- name
227
- FROM users
228
- WHERE
229
- active = 1
230
- ORDER BY
231
- name
232
- """
233
- )
240
+ f"""
241
+ SELECT
242
+ id,
243
+ name
244
+ FROM users
245
+ WHERE
246
+ active = 1
247
+ ORDER BY
248
+ name
249
+ """
250
+ )
234
251
  return
235
252
  ```
236
253
 
@@ -238,7 +255,7 @@ def _(mo):
238
255
 
239
256
  **SQL not being formatted?**
240
257
  - Ensure the string contains at least 2 SQL keywords or a recognizable pattern like `SELECT...FROM`
241
- - 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`)
242
259
 
243
260
  **Comments being modified?**
244
261
  - Python comments (`#`) are preserved. SQL comments (`--`) inside strings are converted to `/* */` block comments by sqlglot.