Split/rejoin Jupyter notebooks effortlessly.
Project description
jupyflow
Split & rejoin Jupyter notebooks, simply.
Overview
jupyflow is a tiny, zero‑dependency library that helps you move code in and out of Jupyter notebooks with confidence:
split(notebook_path) -> list[str]: extract all code cells from a.ipynbinto a Python list of strings.rejoin(*files, ...) -> str: take one or more.pyfiles (each file = one cell) and rebuild a valid.ipynbJSON string.
It’s ideal for lightweight refactors, quick round‑trips, or preparing notebooks for CI/CD and code reviews—without imposing a custom file layout or heavy dependencies.
Python ≥ 3.8. No third‑party dependencies.
Installation
pip install jupyflow
Quick Start
1) Split a notebook into code strings
from pathlib import Path
import jupyflow as jf
cells = jf.split("Your_filename.ipynb", skip_empty=True) # List[str]
print(f"Found {len(cells)} cells")
print(cells[0][:120]) # peek the first 120 chars
2) Rejoin multiple .py files into one notebook
from pathlib import Path
import jupyflow as jf
ipynb_text = jf.rejoin("cells/001_intro.py", "cells/002_calc.py")
Path("combined.ipynb").write_text(ipynb_text, encoding="utf-8")
3) Rejoin all .py files in a folder (natural sort)
from pathlib import Path
import re
import jupyflow as jf
def natural_key(name: str):
# Ensure 2 < 10 when sorting: ['1','2','10'] not ['1','10','2']
return [int(s) if s.isdigit() else s.lower() for s in re.split(r"(\d+)", name)]
src = Path("./Your_filename_py")
py_files = sorted(src.glob("*.py"), key=lambda p: natural_key(p.name))
ipynb_text = jf.rejoin(py_files) # respects passed order by default
Path("Your_filename_combined.ipynb").write_text(ipynb_text, encoding="utf-8")
4) Round‑trip idea (optional)
# Split → write each cell to a file → rejoin later
import jupyflow as jf
from pathlib import Path
cells = jf.split("demo.ipynb")
out = Path("cells"); out.mkdir(exist_ok=True)
for i, code in enumerate(cells, start=1):
(out / f"{i:03d}.py").write_text(code, encoding="utf-8")
# ...edit files... then rejoin:
from sortedcontainers import SortedList # or use your own sorting if desired
# (Note: this line is only illustrative; jupyflow itself has no deps.)
API Reference
split(notebook_path: str | Path, skip_empty: bool = True) -> list[str]
Extract the code cells (in order) from a notebook.
- Parameters
notebook_path: path to.ipynbskip_empty(defaultTrue): skip cells that are empty or whitespace‑only
- Returns:
list[str]— each element is a code cell’s source - Raises:
FileNotFoundErrorif the notebook path does not exist
rejoin(*files, metadata: dict | None = None, encoding: str = "utf-8", sort: bool = False) -> str
Build a valid .ipynb JSON string from one or more .py files (each file becomes one code cell).
- Parameters
*files: pass multiple paths or a single sequence (list/tuple) of pathsmetadata: optional dict to merge into the notebook’s metadata (e.g. custom kernelspec)encoding: file encoding for reading.py(defaultutf-8)sort:Trueto lexicographically sort inputs; by default the call order is preserved
- Returns:
str— the full notebook JSON (ready to write to disk) - Raises:
FileNotFoundErrorif any source file is missing - Defaults
nbformat=4,nbformat_minor=5- Basic
kernelspec&language_info(Python 3) included - Each code cell keeps original line endings via
splitlines(keepends=True)
Customize metadata example
ipynb_text = jupyflow.rejoin(
"a.py", "b.py",
metadata={"kernelspec": {"display_name": "Python 3.11", "language": "python", "name": "python3"}}
)
Minimal Tests (pytest example)
# tests/test_basic.py
import json
from pathlib import Path
import jupyflow as jf
def make_nb(tmp: Path):
nb = {
"cells": [
{"cell_type": "markdown", "source": ["# Title"]},
{"cell_type": "code", "source": ["print('A')\n"]},
{"cell_type": "code", "source": ["x = 42\n"]},
{"cell_type": "code", "source": [" \n"]},
],
"metadata": {},
"nbformat": 4,
"nbformat_minor": 5,
}
p = tmp / "mini.ipynb"
p.write_text(json.dumps(nb), encoding="utf-8")
return p
def test_split(tmp_path: Path):
nbp = make_nb(tmp_path)
cells = jf.split(nbp, skip_empty=True)
assert len(cells) == 2
assert "print('A')" in cells[0]
assert "x = 42" in cells[1]
License
MIT. See LICENSE.
简介
jupyflow 是一个零依赖的小工具,用来在 Jupyter 笔记本与纯代码之间轻盈往返:
split(notebook_path) -> list[str]:把.ipynb里的代码单元抽取为 Python 字符串列表。rejoin(*files, ...) -> str:把一个或多个.py文件(每个文件 = 一个单元)拼回合法的.ipynbJSON 字符串。
适合做轻量重构、把 Notebook 纳入 CI/CD、或作为代码评审的过渡形态,简单直接。
需要 Python ≥ 3.8。无第三方依赖。
安装
pip install jupyflow
快速上手
1)把 .ipynb 拆成代码字符串
import jupyflow as jf
cells = jf.split("Your_filename.ipynb", skip_empty=True)
print(len(cells), cells[0][:120])
2)把多个 .py 合成一个 Notebook
from pathlib import Path
import jupyflow as jf
ipynb_text = jf.rejoin("cells/001_intro.py", "cells/002_calc.py")
Path("combined.ipynb").write_text(ipynb_text, encoding="utf-8")
3)把文件夹中的所有 .py 合成(自然排序)
from pathlib import Path
import re
import jupyflow as jf
def natural_key(name: str):
return [int(s) if s.isdigit() else s.lower() for s in re.split(r"(\d+)", name)]
src = Path("./Your_filename_py")
py_files = sorted(src.glob("*.py"), key=lambda p: natural_key(p.name))
ipynb_text = jf.rejoin(py_files)
Path("Your_filename_combined.ipynb").write_text(ipynb_text, encoding="utf-8")
API 参考
split(notebook_path: str | Path, skip_empty: bool = True) -> list[str]
- 参数
notebook_path:.ipynb路径skip_empty:是否跳过空白单元,默认True
- 返回:
list[str](每个元素是一段代码) - 异常:路径不存在则抛
FileNotFoundError
rejoin(*files, metadata: dict | None = None, encoding: str = "utf-8", sort: bool = False) -> str
- 参数
*files:可传多个路径,或传入一个列表/元组metadata:可选,合并到生成的 notebook 元数据里(如自定义 kernelspec)encoding:读取.py的编码,默认utf-8sort:若为True,对输入路径按字典序排序;默认按传入顺序生成
- 返回:
.ipynb的 JSON 字符串 - 异常:任何源文件缺失会抛
FileNotFoundError - 默认设置
nbformat=4,nbformat_minor=5- 默认包含 Python 3 的
kernelspec与language_info - 代码以逐行列表形式保存,保留换行
自定义元数据示例
ipynb_text = jupyflow.rejoin(
"a.py", "b.py",
metadata={"kernelspec": {"display_name": "Python 3.11", "language": "python", "name": "python3"}}
)
简单测试(pytest 示例)
# tests/test_basic.py
import json
from pathlib import Path
import jupyflow as jf
def make_nb(tmp: Path):
nb = {
"cells": [
{"cell_type": "markdown", "source": ["# Title"]},
{"cell_type": "code", "source": ["print('A')\n"]},
{"cell_type": "code", "source": ["x = 42\n"]},
{"cell_type": "code", "source": [" \n"]},
],
"metadata": {},
"nbformat": 4,
"nbformat_minor": 5,
}
p = tmp / "mini.ipynb"
p.write_text(json.dumps(nb), encoding="utf-8")
return p
def test_split(tmp_path: Path):
nbp = make_nb(tmp_path)
cells = jf.split(nbp, skip_empty=True)
assert len(cells) == 2
assert "print('A')" in cells[0]
assert "x = 42" in cells[1]
许可
MIT(可在 LICENSE 中查看)。
Your notebooks, now fluid.
Project details
Release history Release notifications | RSS feed
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 jupyflow-1.0.1.tar.gz.
File metadata
- Download URL: jupyflow-1.0.1.tar.gz
- Upload date:
- Size: 6.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.11.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
67d4438320e2bbb999a992c942280f757f4fa64a5a163098a762412a23200bb2
|
|
| MD5 |
5686c7d47d52826f3689bf66beec7e92
|
|
| BLAKE2b-256 |
52f15bf3bda3a51c022f58d01ff4eb865dbd2a6ed21ea4c52840727090335081
|
File details
Details for the file jupyflow-1.0.1-py3-none-any.whl.
File metadata
- Download URL: jupyflow-1.0.1-py3-none-any.whl
- Upload date:
- Size: 6.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.11.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
760fe00cd17a627876f7cfbe1076081ec34ac7cabad937a1342b5fac071aec95
|
|
| MD5 |
552b2d5deb3d0251482bfb87e9d9f3d5
|
|
| BLAKE2b-256 |
5fd3acce4295d36cd32115a8f641124c82067f7209bbc53d00215a2e0123216c
|