Skip to main content

Jupytext percent-format notebook workflow manager

Project description

juplit

Literate programming for Python — write in notebooks, commit clean Python, keep AI agents fast.

Why juplit

Jupyter notebooks are great for development: you can write prose next to code, run cells incrementally, and explore interactively. But .ipynb files are JSON blobs that create real problems:

  • Git history is cluttered — every output change, cell execution count, or metadata tweak shows up as a diff
  • AI agents struggle — JSON notebooks are token-heavy and hard to reason over compared to plain Python
  • Code review is painful — notebooks don't diff cleanly in pull requests

juplit gives you the best of both worlds. You write in jupytext percent-format .py files — plain Python that AI agents can read and reason over efficiently. You generate .ipynb files locally for interactive Jupyter sessions, but keep them out of git. The .py file is always the source of truth.

Installation

pip install juplit

CLI usage

juplit nb      # generate .ipynb from .py files (run after cloning)
juplit sync    # sync .py <-> .ipynb after editing
juplit clean   # sync then delete all .ipynb files (before AI agent sessions)
juplit skill   # print the Claude Code skill file for juplit

Project setup (pyproject.toml)

For a new project, use the cookiecutter template

[project]
dependencies = ["juplit>=0.1.0"]

[dependency-groups]
dev = ["poethepoet>=0.25.0", "pytest>=8.0.0", "ipykernel>=6.0.0", "pre-commit>=3.0.0"]

[tool.poe.tasks]
init  = {cmd = "pre-commit install"}
sync  = {cmd = "juplit sync"}
nb    = {cmd = "juplit nb"}
clean = {cmd = "juplit clean"}
test  = {cmd = "pytest"}

[tool.juplit]
notebook_src_dir = "your_module"   # directory juplit scans for paired .py files

[tool.jupytext]
formats = "ipynb,py:percent"

[tool.pytest.ini_options]
python_files = ["*.py"]
python_classes = ["Test*"]
python_functions = ["test_*"]

juplit finds the nearest pyproject.toml by walking up from the current directory, so the CLI works from any subdirectory.

Separating logic from tests with test()

Use test() to gate inline test code so it runs interactively in Jupyter and under pytest, but never on import:

from juplit import test

# %%
def add(a: int, b: int) -> int:
    return a + b

# %%
if test():
    assert add(1, 2) == 3
    assert add(-1, 1) == 0
    print("add() tests pass")

pytest picks up these blocks automatically when you configure:

[tool.pytest.ini_options]
python_files = ["*.py"]

No def test_* functions required — just if test(): blocks next to the code they test.

You can also mix standard pytest functions with if test(): scaffolding blocks. Because if test(): runs at module scope during pytest collection, variables it sets up are available to def test_* functions:

from juplit import test

# %%
def compute(x: int) -> int:
    return x * 2 + 1

# %%
if test():
    inputs   = [1,  3,  -1]
    expected = [3,  7,  -1]

def test_compute():
    for x, e in zip(inputs, expected):
        assert compute(x) == e

Paired notebook format

A .py file is recognized as a paired notebook when its header contains:

# ---
# jupyter:
#   jupytext:
#     formats: ipynb,py:percent
# ...
# ---

Cells are delimited with # %% (code) and # %% [markdown] (prose).

Claude Code integration

Generate a skill file for Claude Code so it understands the juplit workflow:

juplit skill > .claude/skills/juplit-programming.md

For a skill on how to migrate nbdev repos to juplit:

juplit skill_migrate > .claude/skills/juplit-programming-nbdev-migrate.md

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

juplit-0.0.6.tar.gz (8.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

juplit-0.0.6-py3-none-any.whl (11.3 kB view details)

Uploaded Python 3

File details

Details for the file juplit-0.0.6.tar.gz.

File metadata

  • Download URL: juplit-0.0.6.tar.gz
  • Upload date:
  • Size: 8.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.8.17

File hashes

Hashes for juplit-0.0.6.tar.gz
Algorithm Hash digest
SHA256 aa285e413dec9a024c490d50726d4bb0f59d234f59ff47f65b7820388441ddd0
MD5 6782efd4b292bfb9e29cd05fb6808dbf
BLAKE2b-256 11030a1fb05f084bb862afbbb6a66884bd5bb8661d6f9d23fb3c814158bd0114

See more details on using hashes here.

File details

Details for the file juplit-0.0.6-py3-none-any.whl.

File metadata

  • Download URL: juplit-0.0.6-py3-none-any.whl
  • Upload date:
  • Size: 11.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.8.17

File hashes

Hashes for juplit-0.0.6-py3-none-any.whl
Algorithm Hash digest
SHA256 0b0f7933e4f6dc3689ffa5a4205d815b5c1081c5a3c16233ada1e11f62b275fc
MD5 1de6dec438e063513d85ff4e0fcfea66
BLAKE2b-256 4ce2036ce6380bedb4ffef8dd76e446485401bb35e0c7c04a24246ac4e366495

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page