Quick start:
pip install sqlnbfmt && sqlnbfmt notebook.ipynb
A SQL formatter for Jupyter Notebooks and Marimo notebooks. sqlnbfmt automatically formats SQL queries embedded in notebook cells — Python strings, SQL magic cells (%%sql), and Marimo mo.sql() calls — helping you maintain clean and consistent code.
- Zero-config: Works out of the box with sensible defaults — no config file needed
- Jupyter + Marimo: Formats SQL in
.ipynbnotebooks and Marimo.pynotebooks - Smart SQL Detection: Automatically identifies and formats SQL queries in code cells, magic SQL cells, and
mo.sql()calls - AST-Powered: Uses Abstract Syntax Tree parsing for accurate SQL string identification
- Safe Formatting: Preserves Python comments, query parameters (e.g.,
%s,?), f-string placeholders, and SQL comments - CI-Friendly:
--checkmode exits non-zero when formatting is needed;--diffshows what would change - Skip Hints: Add
# sqlnbfmt: skipto any cell to exclude it from formatting - Pre-commit Ready: Seamlessly integrates with pre-commit hooks
- JupyterLab Plugin: Works with
jupyterlab-code-formatterfor interactive format-on-save - Lightweight: Only three runtime dependencies (sqlglot, nbformat, pyyaml)
pip install sqlnbfmtFormat Jupyter notebooks:
sqlnbfmt path/to/your_notebook.ipynbFormat Marimo notebooks (or any Python file with SQL strings):
sqlnbfmt path/to/your_notebook.pyMix both in a single invocation:
sqlnbfmt *.ipynb marimo_app.pyCheck formatting without modifying files (useful in CI):
sqlnbfmt --check path/to/your_notebook.ipynb path/to/marimo_app.pyShow a diff of what would change:
sqlnbfmt --diff path/to/your_notebook.ipynbAdd a # sqlnbfmt: skip comment to skip formatting. In Jupyter notebooks this applies per-cell; in Python files it applies to the entire file.
# sqlnbfmt: skip
query = "select * from my_special_table where id = 1"Format SQL cells interactively with jupyterlab-code-formatter:
pip install sqlnbfmt jupyterlab-code-formatterThat'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".
To customize the dialect, add this to ~/.jupyter/jupyter_server_config.py:
from sqlnbfmt.jupyterlab_integration import register
register(dialect="postgres")- Install pre-commit:
pip install pre-commit- Add to
.pre-commit-config.yaml:
repos:
- repo: https://github.com/flyersworder/sqlnbfmt
rev: v0.4.0
hooks:
- id: sqlnbfmt
types: [jupyter]To also format Marimo notebooks (Python files), add the Python hook:
- id: sqlnbfmt-py
files: '\.py$' # optionally narrow with: files: 'marimo_.*\.py$'All arguments are optional. To specify a dialect or custom config:
args: [--dialect, postgres, --config, config.yaml]- Install the hook:
pre-commit install- (Optional) Run on all files:
pre-commit run --all-filesUse --check in GitHub Actions to enforce formatting:
- name: Check SQL notebook formatting
run: |
pip install sqlnbfmt
sqlnbfmt --check **/*.ipynb
sqlnbfmt --check marimo_*.py # if using Marimosqlnbfmt works without any configuration file. A config.yaml is only needed to override defaults.
Create a config.yaml file to customize formatting behavior. Here is a template.
| Option | Description | Default |
|---|---|---|
sql_keywords |
SQL keywords to recognize and format | Common SQL keywords |
function_names |
Python functions containing SQL code | read_sql, execute, etc. |
sql_decorators |
Decorators indicating SQL code | query, sql_query, etc. |
single_line_threshold |
Maximum length before splitting SQL | 80 |
indent_width |
Number of spaces for indentation | 4 |
Before formatting:
execute_sql("""SELECT a.col1, b.col2 FROM table_a a JOIN table_b b ON a.id = b.a_id WHERE a.status = 'active' ORDER BY a.created_at DESC""")After formatting:
execute_sql("""
SELECT
a.col1,
b.col2
FROM
table_a AS a
JOIN
table_b AS b
ON a.id = b.a_id
WHERE
a.status = 'active'
ORDER BY
a.created_at DESC
""")Before formatting:
@app.cell
def _(mo):
_df = mo.sql(f"select id, name from users where active = 1 order by name")
returnAfter formatting:
@app.cell
def _(mo):
_df = mo.sql(
f"""
SELECT
id,
name
FROM users
WHERE
active = 1
ORDER BY
name
"""
)
returnSQL not being formatted?
- Ensure the string contains at least 2 SQL keywords or a recognizable pattern like
SELECT...FROM - Check that the function name is in the recognized list (use
--configto add custom ones)
Comments being modified?
- Python comments (
#) are preserved. SQL comments (--) inside strings are converted to/* */block comments by sqlglot.
Pre-commit hook fails?
- Make sure the
revmatches the installed version - Run
pre-commit autoupdateto get the latest version
We welcome contributions! Here's how to get started:
- Clone the repository:
git clone https://github.com/flyersworder/sqlnbfmt.git
cd sqlnbfmt- Use
uvto sync the environment:
uv sync
source .venv/bin/activate # On Windows: .venv\Scripts\activate- Run tests:
pytest-
Add eval cases: see
tests/eval/generate_fixtures.pyfor examples. Runpython tests/eval/generate_fixtures.pyto regenerate fixtures. -
Install dev pre-commit hooks:
pre-commit installThis project is licensed under the MIT License - see the LICENSE file for details.
- sqlglot - SQL parsing and formatting engine
- All contributors and early adopters who helped shape this tool
Made with