Skip to main content

sqlnbfmt

PyPI Tests License Python Versions

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.

Features

  • Zero-config: Works out of the box with sensible defaults — no config file needed
  • Jupyter + Marimo: Formats SQL in .ipynb notebooks and Marimo .py notebooks
  • 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: --check mode exits non-zero when formatting is needed; --diff shows what would change
  • Skip Hints: Add # sqlnbfmt: skip to any cell to exclude it from formatting
  • Pre-commit Ready: Seamlessly integrates with pre-commit hooks
  • JupyterLab Plugin: Works with jupyterlab-code-formatter for interactive format-on-save
  • Lightweight: Only three runtime dependencies (sqlglot, nbformat, pyyaml)

Installation

pip install sqlnbfmt

Usage

Command Line

Format Jupyter notebooks:

sqlnbfmt path/to/your_notebook.ipynb

Format Marimo notebooks (or any Python file with SQL strings):

sqlnbfmt path/to/your_notebook.py

Mix both in a single invocation:

sqlnbfmt *.ipynb marimo_app.py

Check formatting without modifying files (useful in CI):

sqlnbfmt --check path/to/your_notebook.ipynb path/to/marimo_app.py

Show a diff of what would change:

sqlnbfmt --diff path/to/your_notebook.ipynb

Skipping Cells

Add 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"

JupyterLab Integration

Format SQL cells interactively with jupyterlab-code-formatter:

pip install sqlnbfmt jupyterlab-code-formatter

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".

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:

from sqlnbfmt.jupyterlab_integration import register
register(dialect="postgres")

Pre-commit Integration

  1. Install pre-commit:
pip install pre-commit
  1. Add to .pre-commit-config.yaml:
repos:
  - repo: https://github.com/flyersworder/sqlnbfmt
    rev: v0.5.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. Settings in [tool.sqlnbfmt] in your pyproject.toml are picked up automatically (see Configuration); alternatively pass them as arguments:

        args: [--dialect, postgres, --config, config.yaml]
  1. Install the hook:
pre-commit install
  1. (Optional) Run on all files:
pre-commit run --all-files

CI Usage

Use --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 Marimo

Configuration

sqlnbfmt works without any configuration. To override defaults, use a [tool.sqlnbfmt] table in pyproject.toml or a separate YAML file.

pyproject.toml

[tool.sqlnbfmt]
dialect = "postgres"

[tool.sqlnbfmt.formatting_options]
indent_width = 2

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.

YAML file

Pass a YAML file with --config config.yaml. Here is a template. --config also accepts a .toml path, which is read from its [tool.sqlnbfmt] table.

When --config is given, pyproject.toml discovery is skipped. Options you don't set keep their defaults, and unknown option names produce a warning.

Configuration Options

Option Description Default
dialect SQL dialect passed to sqlglot (e.g. postgres, mysql, bigquery); --dialect overrides it none (generic SQL)
sql_keywords SQL keywords to recognize and format Common SQL keywords
function_names Python functions containing SQL code read_sql, execute, sql (Marimo's mo.sql), etc.
sql_decorators Decorators indicating SQL code query, sql_query, etc.
formatting_options.single_line_threshold Maximum length before splitting SQL 80
formatting_options.indent_width Number of spaces for indentation 4

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; for example, keep sql in function_names or Marimo's mo.sql() calls stop being formatted.

Examples

Jupyter Notebook

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
    """
)

Marimo Notebook

Before formatting:

@app.cell
def _(mo):
    _df = mo.sql(f"select id, name from users where active = 1 order by name")
    return

After formatting:

@app.cell
def _(mo):
    _df = mo.sql(
        f"""
        SELECT
          id,
          name
        FROM users
        WHERE
          active = 1
        ORDER BY
          name
        """
    )
    return

Troubleshooting

SQL 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 (add custom ones via function_names in [tool.sqlnbfmt] or --config)

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 rev matches the installed version
  • Run pre-commit autoupdate to get the latest version

Contributing

We welcome contributions! Here's how to get started:

  1. Clone the repository:
git clone https://github.com/flyersworder/sqlnbfmt.git
cd sqlnbfmt
  1. Use uv to sync the environment:
uv sync
source .venv/bin/activate  # On Windows: .venv\Scripts\activate
  1. Run tests:
pytest
  1. Add eval cases: see tests/eval/generate_fixtures.py for examples. Run python tests/eval/generate_fixtures.py to regenerate fixtures.

  2. Install dev pre-commit hooks:

pre-commit install

License

This project is licensed under the MIT License - see the LICENSE file for details.

Acknowledgments

  • sqlglot - SQL parsing and formatting engine
  • All contributors and early adopters who helped shape this tool

Made with ♥️ by the sqlnbfmt team

Metadata

Release files for sqlnbfmt 0.5.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for sqlnbfmt 0.5.0
File Size Uploaded Provenance Actions
sqlnbfmt-0.5.0.tar.gz 69.1 kB With provenance Details for sqlnbfmt-0.5.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for sqlnbfmt 0.5.0
File Interpreter ABI Platform Actions
sqlnbfmt-0.5.0-py3-none-any.whl Python 3 none any Details for sqlnbfmt-0.5.0-py3-none-any.whl

Total release size: 86.0 kB

Release files / sqlnbfmt-0.5.0.tar.gz

Download URL sqlnbfmt-0.5.0.tar.gz
Size 69.1 kB
Tags Source
SHA-256 checksum
How to use checksums
d63be09d7800b3d53bd45fed078750a4b7999187b315cfef04c302687a5e44f4
BLAKE2b-256 checksum
How to use checksums
742ef594bf9bf859e92699792fed34b422084f54b49f05e6ac4f844eb1b1d4fe
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 15, 2026.

Transparency log

Release files / sqlnbfmt-0.5.0-py3-none-any.whl

Download URL sqlnbfmt-0.5.0-py3-none-any.whl
Size 16.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
17fdca0a9cc8d9435bd273439835c5257e84ff25ea111829ac9d79e0c65bffad
BLAKE2b-256 checksum
How to use checksums
290a7f13e1b3ea977a47ff5fb282694e7cdfd8299cc73ea0cf4804b46890e3c1
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 15, 2026.

Transparency log

Security for sqlnbfmt 0.5.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for sqlnbfmt-0.5.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 15, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page