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 testing()
Use testing() 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.
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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file juplit-0.0.3.tar.gz.
File metadata
- Download URL: juplit-0.0.3.tar.gz
- Upload date:
- Size: 8.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.8.17
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
bbb9be95978c738f1b7a041e9865516bcd085dad221dab47a861bf0a5ab59a87
|
|
| MD5 |
b5183d3725ee3d352723b79cbe6c056d
|
|
| BLAKE2b-256 |
64479f66227f703ee623aa8030f5f7c82d41bab43fb870fa1e8944c23c2132d5
|
File details
Details for the file juplit-0.0.3-py3-none-any.whl.
File metadata
- Download URL: juplit-0.0.3-py3-none-any.whl
- Upload date:
- Size: 11.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.8.17
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
48be84f064a67413915785295e44a5e8cfe2d2f976e51eef738d37a448a4c171
|
|
| MD5 |
f422cbb79e50b765357ddce63163d9f4
|
|
| BLAKE2b-256 |
f409a06851891d628d24f72f12b63f155e8819420401054d958355ebb28f57b5
|