Skip to content

Commit

Permalink
Fix #1; fix #2
Browse files Browse the repository at this point in the history
  • Loading branch information
miek770 committed Nov 18, 2024
1 parent 20a8902 commit e2ad79b
Show file tree
Hide file tree
Showing 5 changed files with 112 additions and 34 deletions.
35 changes: 32 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,20 +20,21 @@ A fully functional [LaTeX environnement](https://nbconvert.readthedocs.io/en/lat

## Usage

The package provides 2 templater functions: `render_nb` and `render_pdf`. Both work the same way; a [Jupyter Notebook](https://jupyter.org/) template must be provided (`.ipynb` file), the output path must be specified and keywords (identified as `{{keyword}}` in the template) can be provided with their type to update the template.
The package provides 2 templater functions: `render_nb` and `render_pdf`. Both work the same way; a [Jupyter Notebook](https://jupyter.org/) template must be provided (`.ipynb` file or `nbformat.NotebookNode` object), the output path must be specified and keywords (identified as `{{keyword}}` in the template) can be provided with their type to update the template.

Here is `render_pdf`'s function definition:

```python
def render_pdf(
*,
template_path: Path,
output_path: Path,
template_nb: Optional[nbformat.NotebookNode] = None,
template_path: Optional[Path] = None,
**kwargs,
):
```

Any number of keyword arguments can be provided to match and replace `{{keyword}}` fields in the [Jupyter Notebook](https://jupyter.org/) template (`.ipynb` file). If desired, a keyword argument with the same name plus `_cell_type` can be provided to change the resulting notebook cell type, e.g.: to `markdown`.
Any number of keyword arguments can be provided to match and replace `{{keyword}}` fields in the [Jupyter Notebook](https://jupyter.org/) template (`.ipynb` file or `nbformat.NotebookNode` object). If desired, a keyword argument with the same name plus `_cell_type` can be provided to change the resulting notebook cell type, e.g.: to `markdown`.

### Example

Expand Down Expand Up @@ -72,6 +73,34 @@ Here is the PDF result (margins were cropped for this picture, and the `{{introd

![example](https://raw.githubusercontent.com/miek770/eznbtemplater/refs/heads/main/media/test_pandas_pdf.png)

This is the `test_nb_programatically()` test from [tests/test_nb_programatically.py](tests/test_nb_programatically.py); it works with a notebook template created with [nbformat](https://pypi.org/project/nbformat/) inspired by [this example](https://gist.github.com/fperez/9716279) instead of a notebook file:

```python
from eznbtemplater.eznbtemplater import render_nb
import nbformat as nbf
from pathlib import Path


def test_nb_programatically() -> None:
nb: nbf.NotebookNode = nbf.v4.new_notebook()
nb["cells"] = [
nbf.v4.new_raw_cell(r"\renewcommand{\contentsname}{Table of Contents}"),
nbf.v4.new_raw_cell(r"\tableofcontents"),
nbf.v4.new_markdown_cell("# Introduction"),
nbf.v4.new_markdown_cell("{{introduction}}"),
]

output_path: Path = Path("tests/test_nb_programatically.ipynb")

introduction: str = "This is a ***test***, don't mind me."
render_nb(
template_nb=nb,
output_path=output_path,
introduction=introduction,
)
assert output_path.exists()
```

See [tests/test_renderers.py](tests/test_renderers.py) for a few additional examples.

## Contributing
Expand Down
66 changes: 41 additions & 25 deletions eznbtemplater/eznbtemplater.py
Original file line number Diff line number Diff line change
@@ -1,51 +1,61 @@
from nbconvert.exporters import LatexExporter
import nbformat
from pathlib import Path
from typing import Any


import tempfile
from pathlib import Path
from typing import Any
import nbformat
from nbconvert import PDFExporter
import nbformat as nbf
from pathlib import Path
from typing import Any, Optional


def render_nb(
*,
template_path: Path,
output_path: Path,
template_nb: Optional[nbf.NotebookNode] = None,
template_path: Optional[Path] = None,
**kwargs,
):
assert template_nb is not None or template_path is not None

# Replace {{keys}} in the template
nb: Any = _process_template(
template_path=template_path,
**kwargs,
)
nb: Any
if template_nb is not None:
nb = _process_template(
template_nb=template_nb,
**kwargs,
)
elif template_path is not None:
nb = _process_template(
template_path=template_path,
**kwargs,
)

# Write the modified notebook to a temporary file
with open(output_path, "w", encoding="utf-8") as output_file:
nbformat.write(nb, output_file)
nbf.write(nb, output_file)


def render_pdf(
*,
template_path: Path,
output_path: Path,
template_nb: Optional[nbf.NotebookNode] = None,
template_path: Optional[Path] = None,
**kwargs,
):

# Replace {{keys}} in the template
nb: Any = _process_template(
template_path=template_path,
**kwargs,
)
nb: Any
if template_nb is not None:
nb = _process_template(
template_nb=template_nb,
**kwargs,
)
elif template_path is not None:
nb = _process_template(
template_path=template_path,
**kwargs,
)

# Write the modified notebook to a transition file
transit_filename: Path = output_path.with_suffix(".ipynb")
with open(transit_filename, mode="w", encoding="utf-8") as output_file:
nbformat.write(nb, output_file)
nbf.write(nb, output_file)

# Use nbconvert to convert the temporary notebook to a PDF
pdf_exporter = PDFExporter()
Expand All @@ -58,13 +68,19 @@ def render_pdf(


def _process_template(
template_path: Path,
template_nb: Optional[nbf.NotebookNode] = None,
template_path: Optional[Path] = None,
**kwargs,
) -> Any:
assert template_nb is not None or template_path is not None

# Read the template notebook
with open(template_path, "r", encoding="utf-8") as template_file:
nb: Any = nbformat.read(template_file, as_version=4)
nb: Any
if template_nb is not None:
nb = template_nb
elif template_path is not None:
with open(template_path, "r", encoding="utf-8") as template_file:
nb = nbf.read(template_file, as_version=4)

# Replace placeholders in the notebook
for cell in nb.cells:
Expand Down
20 changes: 15 additions & 5 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,23 +1,33 @@
[project]
name = "eznbtemplater"
version = "0.1.2"
version = "0.1.3"
authors = [
{ name="Michel Lavoie", email="[email protected]" },
]
description = "Generate PDF and Jupyter Notebook files from a Notebook template"
readme = "README.md"
requires-python = ">=3.10"
dependencies = [
"nbconvert>=7.16.4",
"nbformat>=5.10.4",
]

packages = [
{ include = "eznbtemplater" },
classifiers = [
"Programming Language :: Python :: 3",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
]
include = ["eznbtemplater/templates/*"]

[project.urls]
homepage = "https://github.com/miek770/eznbtemplater"
issues = "https://github.com/miek770/eznbtemplater/issues"

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[tool.hatch.build]
include = ["eznbtemplater/templates/*"]

[tool.uv]
dev-dependencies = [
"black>=24.10.0",
Expand Down
23 changes: 23 additions & 0 deletions tests/test_nb_programatically.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
from eznbtemplater.eznbtemplater import render_nb
import nbformat as nbf
from pathlib import Path


def test_nb_programatically() -> None:
nb: nbf.NotebookNode = nbf.v4.new_notebook()
nb["cells"] = [
nbf.v4.new_raw_cell(r"\renewcommand{\contentsname}{Table of Contents}"),
nbf.v4.new_raw_cell(r"\tableofcontents"),
nbf.v4.new_markdown_cell("# Introduction"),
nbf.v4.new_markdown_cell("{{introduction}}"),
]

output_path: Path = Path("tests/test_nb_programatically.ipynb")

introduction: str = "This is a ***test***, don't mind me."
render_nb(
template_nb=nb,
output_path=output_path,
introduction=introduction,
)
assert output_path.exists()
2 changes: 1 addition & 1 deletion uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit e2ad79b

Please sign in to comment.