Repository: mwouts/jupytext
Branch: main
Commit: e939bf5359be
Files: 805
Total size: 8.1 MB
Directory structure:
gitextract_26hsbj6f/
├── .git-blame-ignore-revs
├── .git_archival.txt
├── .gitattributes
├── .github/
│ ├── codecov.yml
│ ├── dependabot.yml
│ └── workflows/
│ ├── ci.yml
│ ├── comment-pr.yml
│ ├── publish.yml
│ ├── step_build.yml
│ ├── step_coverage.yml
│ ├── step_pre-commit.yml
│ ├── step_static-analysis.yml
│ ├── step_tests-conda.yml
│ ├── step_tests-pip.yml
│ ├── step_tests-ui.yml
│ └── update-playwright-snapshots.yml
├── .gitignore
├── .pre-commit-config.yaml
├── .pre-commit-hooks.yaml
├── .readthedocs.yml
├── CHANGELOG.md
├── LICENSE
├── README.md
├── binder/
│ ├── labconfig/
│ │ └── default_setting_overrides.json
│ ├── postBuild
│ └── requirements.txt
├── demo/
│ ├── Benchmarking Jupytext.py
│ ├── Jupytext's word cloud.py
│ ├── Tests in a notebook.md
│ ├── World population.Rmd
│ ├── World population.ipynb
│ ├── World population.lgt.py
│ ├── World population.md
│ ├── World population.myst.md
│ ├── World population.pandoc.md
│ ├── World population.pct.py
│ ├── World population.spx.py
│ ├── get_started.md
│ └── vscode/
│ ├── notebook.ipynb
│ └── notebook.py
├── docs/
│ ├── Makefile
│ ├── advanced-options.md
│ ├── changelog.md
│ ├── conf.py
│ ├── config.md
│ ├── contributing.md
│ ├── developing.md
│ ├── doc-requirements.txt
│ ├── faq.md
│ ├── formats-markdown.md
│ ├── formats-scripts.md
│ ├── index.md
│ ├── install.md
│ ├── jupyter-collaboration.md
│ ├── jupyterlab-extension.md
│ ├── languages.md
│ ├── make.bat
│ ├── paired-notebooks.md
│ ├── text-notebooks.md
│ ├── tutorials.md
│ ├── using-cli.md
│ ├── using-pre-commit.md
│ └── vs-code.md
├── jupyterlab/
│ ├── .gitignore
│ ├── .prettierignore
│ ├── .yarnrc.yml
│ ├── install.json
│ ├── jupyter-config/
│ │ ├── jupyter_notebook_config.d/
│ │ │ └── jupytext.json
│ │ └── jupyter_server_config.d/
│ │ └── jupytext.json
│ ├── jupyterlab_jupytext/
│ │ └── __init__.py
│ ├── lerna.json
│ ├── package.json
│ ├── packages/
│ │ └── jupyterlab-jupytext-extension/
│ │ ├── .gitignore
│ │ ├── CHANGELOG.md
│ │ ├── README.md
│ │ ├── package.json
│ │ ├── schema/
│ │ │ └── plugin.json
│ │ ├── src/
│ │ │ ├── commands.ts
│ │ │ ├── factory.ts
│ │ │ ├── index.ts
│ │ │ ├── registry.ts
│ │ │ ├── svg.d.ts
│ │ │ ├── tokens.ts
│ │ │ └── utils.ts
│ │ ├── style/
│ │ │ ├── base.css
│ │ │ ├── index.css
│ │ │ └── index.js
│ │ ├── tsconfig.json
│ │ └── ui-tests/
│ │ ├── README.md
│ │ ├── jupyter_server_test_config.py
│ │ ├── package.json
│ │ ├── playwright.config.js
│ │ └── tests/
│ │ ├── jupytext-launcher.spec.ts
│ │ ├── jupytext-menu.spec.ts
│ │ ├── jupytext-notebook.spec.ts
│ │ └── jupytext-settings.spec.ts
│ ├── scripts/
│ │ └── install_extension.py
│ └── tsconfig.eslint.json
├── pyproject.toml
├── src/
│ ├── jupytext/
│ │ ├── __init__.py
│ │ ├── __main__.py
│ │ ├── async_contentsmanager.py
│ │ ├── async_pairs.py
│ │ ├── cell_metadata.py
│ │ ├── cell_reader.py
│ │ ├── cell_to_text.py
│ │ ├── cli.py
│ │ ├── combine.py
│ │ ├── compare.py
│ │ ├── config.py
│ │ ├── doxygen.py
│ │ ├── formats.py
│ │ ├── header.py
│ │ ├── jupytext.py
│ │ ├── kernels.py
│ │ ├── languages.py
│ │ ├── magics.py
│ │ ├── marimo.py
│ │ ├── metadata_filter.py
│ │ ├── myst.py
│ │ ├── paired_paths.py
│ │ ├── pairs.py
│ │ ├── pandoc.py
│ │ ├── pep8.py
│ │ ├── quarto.py
│ │ ├── reraise.py
│ │ ├── stringparser.py
│ │ ├── sync_contentsmanager.py
│ │ ├── sync_pairs.py
│ │ └── version.py
│ └── jupytext_config/
│ ├── __init__.py
│ ├── __main__.py
│ ├── jupytext_config.py
│ └── labconfig.py
├── tests/
│ ├── conftest.py
│ ├── data/
│ │ └── notebooks/
│ │ ├── inputs/
│ │ │ ├── R/
│ │ │ │ └── simple_r_script.R
│ │ │ ├── R_spin/
│ │ │ │ └── knitr-spin.R
│ │ │ ├── Rmd/
│ │ │ │ ├── R_sample.Rmd
│ │ │ │ ├── chunk_options.Rmd
│ │ │ │ ├── ioslides.Rmd
│ │ │ │ ├── knitr-spin.Rmd
│ │ │ │ └── markdown.Rmd
│ │ │ ├── hydrogen/
│ │ │ │ └── hydrogen_latex_html_R_magics.py
│ │ │ ├── ipynb_R/
│ │ │ │ ├── R notebook with invalid cell keys.ipynb
│ │ │ │ └── ir_notebook.ipynb
│ │ │ ├── ipynb_bash/
│ │ │ │ └── sample_bash_notebook.ipynb
│ │ │ ├── ipynb_clojure/
│ │ │ │ └── html-demo.ipynb
│ │ │ ├── ipynb_coconut/
│ │ │ │ └── coconut_homepage_demo.ipynb
│ │ │ ├── ipynb_cpp/
│ │ │ │ ├── root_cpp.ipynb
│ │ │ │ └── xcpp_by_quantstack.ipynb
│ │ │ ├── ipynb_cs/
│ │ │ │ └── csharp.ipynb
│ │ │ ├── ipynb_fs/
│ │ │ │ └── fsharp.ipynb
│ │ │ ├── ipynb_gnuplot/
│ │ │ │ └── gnuplot_notebook.ipynb
│ │ │ ├── ipynb_go/
│ │ │ │ └── hello_world_gonb.ipynb
│ │ │ ├── ipynb_groovy/
│ │ │ │ └── tailrecursive-factorial.ipynb
│ │ │ ├── ipynb_haskell/
│ │ │ │ └── haskell_notebook.ipynb
│ │ │ ├── ipynb_idl/
│ │ │ │ └── demo_gdl_fbp.ipynb
│ │ │ ├── ipynb_java/
│ │ │ │ └── simple-helloworld.ipynb
│ │ │ ├── ipynb_js/
│ │ │ │ └── ijavascript.ipynb
│ │ │ ├── ipynb_julia/
│ │ │ │ ├── julia_benchmark_plotly_barchart.ipynb
│ │ │ │ └── julia_functional_geometry.ipynb
│ │ │ ├── ipynb_logtalk/
│ │ │ │ └── logtalk_notebook.ipynb
│ │ │ ├── ipynb_lua/
│ │ │ │ └── lua_example.ipynb
│ │ │ ├── ipynb_m/
│ │ │ │ └── octave_notebook.ipynb
│ │ │ ├── ipynb_maxima/
│ │ │ │ └── maxima_example.ipynb
│ │ │ ├── ipynb_ocaml/
│ │ │ │ └── ocaml_notebook.ipynb
│ │ │ ├── ipynb_ps1/
│ │ │ │ └── powershell.ipynb
│ │ │ ├── ipynb_py/
│ │ │ │ ├── Line_breaks_in_LateX_305.ipynb
│ │ │ │ ├── Notebook with function and cell metadata 164.ipynb
│ │ │ │ ├── Notebook with html and latex cells.ipynb
│ │ │ │ ├── Notebook with many hash signs.ipynb
│ │ │ │ ├── Notebook with metadata and long cells.ipynb
│ │ │ │ ├── Notebook_with_R_magic.ipynb
│ │ │ │ ├── Notebook_with_more_R_magic_111.ipynb
│ │ │ │ ├── The flavors of raw cells.ipynb
│ │ │ │ ├── cat_variable.ipynb
│ │ │ │ ├── convert_to_py_then_test_with_update83.ipynb
│ │ │ │ ├── frozen_cell.ipynb
│ │ │ │ ├── jupyter.ipynb
│ │ │ │ ├── jupyter_again.ipynb
│ │ │ │ ├── jupyter_with_raw_cell_in_body.ipynb
│ │ │ │ ├── jupyter_with_raw_cell_on_top.ipynb
│ │ │ │ ├── jupyter_with_raw_cell_with_invalid_yaml.ipynb
│ │ │ │ ├── jupyterlab-slideshow_1441.ipynb
│ │ │ │ ├── notebook_with_complex_metadata.ipynb
│ │ │ │ ├── nteract_with_parameter.ipynb
│ │ │ │ ├── plotly_graphs.ipynb
│ │ │ │ ├── raw_cell_with_complex_yaml_like_content.ipynb
│ │ │ │ ├── raw_cell_with_non_dict_yaml_content.ipynb
│ │ │ │ ├── sample_rise_notebook_66.ipynb
│ │ │ │ └── text_outputs_and_images.ipynb
│ │ │ ├── ipynb_q/
│ │ │ │ └── kalman_filter_and_visualization.ipynb
│ │ │ ├── ipynb_robot/
│ │ │ │ └── simple_robot_notebook.ipynb
│ │ │ ├── ipynb_rust/
│ │ │ │ └── evcxr_jupyter_tour.ipynb
│ │ │ ├── ipynb_sage/
│ │ │ │ └── sage_print_hello.ipynb
│ │ │ ├── ipynb_sas/
│ │ │ │ └── sas.ipynb
│ │ │ ├── ipynb_scala/
│ │ │ │ └── simple_scala_notebook.ipynb
│ │ │ ├── ipynb_scheme/
│ │ │ │ └── Reference Guide for Calysto Scheme.ipynb
│ │ │ ├── ipynb_sos/
│ │ │ │ └── jupytext_replication.ipynb
│ │ │ ├── ipynb_stata/
│ │ │ │ └── stata_notebook.ipynb
│ │ │ ├── ipynb_tcl/
│ │ │ │ └── tcl_test.ipynb
│ │ │ ├── ipynb_ts/
│ │ │ │ └── itypescript.ipynb
│ │ │ ├── ipynb_wolfram/
│ │ │ │ └── wolfram.ipynb
│ │ │ ├── ipynb_xonsh/
│ │ │ │ └── xonsh_example.ipynb
│ │ │ ├── julia/
│ │ │ │ └── julia_sample_script.jl
│ │ │ ├── marimo/
│ │ │ │ └── basic_marimo_example.py
│ │ │ ├── md/
│ │ │ │ ├── jupytext_markdown.md
│ │ │ │ └── plain_markdown.md
│ │ │ ├── myst/
│ │ │ │ ├── fenced_code_vs_code_cells.md
│ │ │ │ └── reference_link.md
│ │ │ ├── percent/
│ │ │ │ └── hydrogen.py
│ │ │ ├── ps1/
│ │ │ │ └── build.ps1
│ │ │ ├── python/
│ │ │ │ ├── light_sample.py
│ │ │ │ └── python_notebook_sample.py
│ │ │ └── sphinx/
│ │ │ └── plot_notebook.py
│ │ └── outputs/
│ │ ├── Rmd_to_ipynb/
│ │ │ ├── R_sample.ipynb
│ │ │ ├── chunk_options.ipynb
│ │ │ ├── ioslides.ipynb
│ │ │ ├── knitr-spin.ipynb
│ │ │ └── markdown.ipynb
│ │ ├── ipynb_to_Rmd/
│ │ │ ├── Line_breaks_in_LateX_305.Rmd
│ │ │ ├── Notebook with function and cell metadata 164.Rmd
│ │ │ ├── Notebook with html and latex cells.Rmd
│ │ │ ├── Notebook with many hash signs.Rmd
│ │ │ ├── Notebook with metadata and long cells.Rmd
│ │ │ ├── Notebook_with_R_magic.Rmd
│ │ │ ├── Notebook_with_more_R_magic_111.Rmd
│ │ │ ├── R notebook with invalid cell keys.Rmd
│ │ │ ├── Reference Guide for Calysto Scheme.Rmd
│ │ │ ├── The flavors of raw cells.Rmd
│ │ │ ├── cat_variable.Rmd
│ │ │ ├── coconut_homepage_demo.Rmd
│ │ │ ├── convert_to_py_then_test_with_update83.Rmd
│ │ │ ├── csharp.Rmd
│ │ │ ├── demo_gdl_fbp.Rmd
│ │ │ ├── evcxr_jupyter_tour.Rmd
│ │ │ ├── frozen_cell.Rmd
│ │ │ ├── fsharp.Rmd
│ │ │ ├── gnuplot_notebook.Rmd
│ │ │ ├── haskell_notebook.Rmd
│ │ │ ├── hello_world_gonb.Rmd
│ │ │ ├── html-demo.Rmd
│ │ │ ├── ijavascript.Rmd
│ │ │ ├── ir_notebook.Rmd
│ │ │ ├── itypescript.Rmd
│ │ │ ├── julia_benchmark_plotly_barchart.Rmd
│ │ │ ├── julia_functional_geometry.Rmd
│ │ │ ├── jupyter.Rmd
│ │ │ ├── jupyter_again.Rmd
│ │ │ ├── jupyter_with_raw_cell_in_body.Rmd
│ │ │ ├── jupyter_with_raw_cell_on_top.Rmd
│ │ │ ├── jupyter_with_raw_cell_with_invalid_yaml.Rmd
│ │ │ ├── jupyterlab-slideshow_1441.Rmd
│ │ │ ├── jupytext_replication.Rmd
│ │ │ ├── kalman_filter_and_visualization.Rmd
│ │ │ ├── logtalk_notebook.Rmd
│ │ │ ├── lua_example.Rmd
│ │ │ ├── maxima_example.Rmd
│ │ │ ├── notebook_with_complex_metadata.Rmd
│ │ │ ├── nteract_with_parameter.Rmd
│ │ │ ├── ocaml_notebook.Rmd
│ │ │ ├── octave_notebook.Rmd
│ │ │ ├── plotly_graphs.Rmd
│ │ │ ├── powershell.Rmd
│ │ │ ├── raw_cell_with_complex_yaml_like_content.Rmd
│ │ │ ├── raw_cell_with_non_dict_yaml_content.Rmd
│ │ │ ├── root_cpp.Rmd
│ │ │ ├── sage_print_hello.Rmd
│ │ │ ├── sample_bash_notebook.Rmd
│ │ │ ├── sample_rise_notebook_66.Rmd
│ │ │ ├── sas.Rmd
│ │ │ ├── simple-helloworld.Rmd
│ │ │ ├── simple_robot_notebook.Rmd
│ │ │ ├── simple_scala_notebook.Rmd
│ │ │ ├── stata_notebook.Rmd
│ │ │ ├── tailrecursive-factorial.Rmd
│ │ │ ├── tcl_test.Rmd
│ │ │ ├── text_outputs_and_images.Rmd
│ │ │ ├── wolfram.Rmd
│ │ │ ├── xcpp_by_quantstack.Rmd
│ │ │ └── xonsh_example.Rmd
│ │ ├── ipynb_to_hydrogen/
│ │ │ ├── Line_breaks_in_LateX_305.py
│ │ │ ├── Notebook with function and cell metadata 164.py
│ │ │ ├── Notebook with html and latex cells.py
│ │ │ ├── Notebook with many hash signs.py
│ │ │ ├── Notebook with metadata and long cells.py
│ │ │ ├── Notebook_with_R_magic.py
│ │ │ ├── Notebook_with_more_R_magic_111.py
│ │ │ ├── R notebook with invalid cell keys.R
│ │ │ ├── Reference Guide for Calysto Scheme.ss
│ │ │ ├── The flavors of raw cells.py
│ │ │ ├── cat_variable.py
│ │ │ ├── coconut_homepage_demo.coco
│ │ │ ├── convert_to_py_then_test_with_update83.py
│ │ │ ├── csharp.cs
│ │ │ ├── demo_gdl_fbp.pro
│ │ │ ├── evcxr_jupyter_tour.rs
│ │ │ ├── frozen_cell.py
│ │ │ ├── fsharp.fsx
│ │ │ ├── gnuplot_notebook.gp
│ │ │ ├── haskell_notebook.hs
│ │ │ ├── hello_world_gonb.go
│ │ │ ├── html-demo.clj
│ │ │ ├── ijavascript.js
│ │ │ ├── ir_notebook.R
│ │ │ ├── itypescript.ts
│ │ │ ├── julia_benchmark_plotly_barchart.jl
│ │ │ ├── julia_functional_geometry.jl
│ │ │ ├── jupyter.py
│ │ │ ├── jupyter_again.py
│ │ │ ├── jupyter_with_raw_cell_in_body.py
│ │ │ ├── jupyter_with_raw_cell_on_top.py
│ │ │ ├── jupyter_with_raw_cell_with_invalid_yaml.py
│ │ │ ├── jupyterlab-slideshow_1441.py
│ │ │ ├── jupytext_replication.sos
│ │ │ ├── kalman_filter_and_visualization.q
│ │ │ ├── logtalk_notebook.lgt
│ │ │ ├── lua_example.lua
│ │ │ ├── maxima_example.mac
│ │ │ ├── notebook_with_complex_metadata.py
│ │ │ ├── nteract_with_parameter.py
│ │ │ ├── ocaml_notebook.ml
│ │ │ ├── octave_notebook.m
│ │ │ ├── plotly_graphs.py
│ │ │ ├── powershell.ps1
│ │ │ ├── raw_cell_with_complex_yaml_like_content.py
│ │ │ ├── raw_cell_with_non_dict_yaml_content.py
│ │ │ ├── root_cpp.cpp
│ │ │ ├── sage_print_hello.sage
│ │ │ ├── sample_bash_notebook.sh
│ │ │ ├── sample_rise_notebook_66.py
│ │ │ ├── sas.sas
│ │ │ ├── simple-helloworld.java
│ │ │ ├── simple_robot_notebook.robot
│ │ │ ├── simple_scala_notebook.scala
│ │ │ ├── stata_notebook.do
│ │ │ ├── tailrecursive-factorial.groovy
│ │ │ ├── tcl_test.tcl
│ │ │ ├── text_outputs_and_images.py
│ │ │ ├── wolfram.wolfram
│ │ │ ├── xcpp_by_quantstack.cpp
│ │ │ └── xonsh_example.xsh
│ │ ├── ipynb_to_marimo/
│ │ │ ├── Line_breaks_in_LateX_305.py
│ │ │ ├── Notebook with function and cell metadata 164.py
│ │ │ ├── Notebook with many hash signs.py
│ │ │ ├── cat_variable.py
│ │ │ ├── frozen_cell.py
│ │ │ ├── jupyter.py
│ │ │ ├── notebook_with_complex_metadata.py
│ │ │ ├── plotly_graphs.py
│ │ │ ├── sample_rise_notebook_66.py
│ │ │ └── text_outputs_and_images.py
│ │ ├── ipynb_to_md/
│ │ │ ├── Line_breaks_in_LateX_305.md
│ │ │ ├── Notebook with function and cell metadata 164.md
│ │ │ ├── Notebook with html and latex cells.md
│ │ │ ├── Notebook with many hash signs.md
│ │ │ ├── Notebook with metadata and long cells.md
│ │ │ ├── Notebook_with_R_magic.md
│ │ │ ├── Notebook_with_more_R_magic_111.md
│ │ │ ├── R notebook with invalid cell keys.md
│ │ │ ├── Reference Guide for Calysto Scheme.md
│ │ │ ├── The flavors of raw cells.md
│ │ │ ├── cat_variable.md
│ │ │ ├── coconut_homepage_demo.md
│ │ │ ├── convert_to_py_then_test_with_update83.md
│ │ │ ├── csharp.md
│ │ │ ├── demo_gdl_fbp.md
│ │ │ ├── evcxr_jupyter_tour.md
│ │ │ ├── frozen_cell.md
│ │ │ ├── fsharp.md
│ │ │ ├── gnuplot_notebook.md
│ │ │ ├── haskell_notebook.md
│ │ │ ├── hello_world_gonb.md
│ │ │ ├── html-demo.md
│ │ │ ├── ijavascript.md
│ │ │ ├── ir_notebook.md
│ │ │ ├── itypescript.md
│ │ │ ├── julia_benchmark_plotly_barchart.md
│ │ │ ├── julia_functional_geometry.md
│ │ │ ├── jupyter.md
│ │ │ ├── jupyter_again.md
│ │ │ ├── jupyter_with_raw_cell_in_body.md
│ │ │ ├── jupyter_with_raw_cell_on_top.md
│ │ │ ├── jupyter_with_raw_cell_with_invalid_yaml.md
│ │ │ ├── jupyterlab-slideshow_1441.md
│ │ │ ├── jupytext_replication.md
│ │ │ ├── kalman_filter_and_visualization.md
│ │ │ ├── logtalk_notebook.md
│ │ │ ├── lua_example.md
│ │ │ ├── maxima_example.md
│ │ │ ├── notebook_with_complex_metadata.md
│ │ │ ├── nteract_with_parameter.md
│ │ │ ├── ocaml_notebook.md
│ │ │ ├── octave_notebook.md
│ │ │ ├── plotly_graphs.md
│ │ │ ├── powershell.md
│ │ │ ├── raw_cell_with_complex_yaml_like_content.md
│ │ │ ├── raw_cell_with_non_dict_yaml_content.md
│ │ │ ├── root_cpp.md
│ │ │ ├── sage_print_hello.md
│ │ │ ├── sample_bash_notebook.md
│ │ │ ├── sample_rise_notebook_66.md
│ │ │ ├── sas.md
│ │ │ ├── simple-helloworld.md
│ │ │ ├── simple_robot_notebook.md
│ │ │ ├── simple_scala_notebook.md
│ │ │ ├── stata_notebook.md
│ │ │ ├── tailrecursive-factorial.md
│ │ │ ├── tcl_test.md
│ │ │ ├── text_outputs_and_images.md
│ │ │ ├── wolfram.md
│ │ │ ├── xcpp_by_quantstack.md
│ │ │ └── xonsh_example.md
│ │ ├── ipynb_to_myst/
│ │ │ ├── Line_breaks_in_LateX_305.md
│ │ │ ├── Notebook with function and cell metadata 164.md
│ │ │ ├── Notebook with html and latex cells.md
│ │ │ ├── Notebook with many hash signs.md
│ │ │ ├── Notebook with metadata and long cells.md
│ │ │ ├── Notebook_with_R_magic.md
│ │ │ ├── Notebook_with_more_R_magic_111.md
│ │ │ ├── R notebook with invalid cell keys.md
│ │ │ ├── Reference Guide for Calysto Scheme.md
│ │ │ ├── The flavors of raw cells.md
│ │ │ ├── cat_variable.md
│ │ │ ├── coconut_homepage_demo.md
│ │ │ ├── convert_to_py_then_test_with_update83.md
│ │ │ ├── csharp.md
│ │ │ ├── demo_gdl_fbp.md
│ │ │ ├── evcxr_jupyter_tour.md
│ │ │ ├── frozen_cell.md
│ │ │ ├── fsharp.md
│ │ │ ├── gnuplot_notebook.md
│ │ │ ├── haskell_notebook.md
│ │ │ ├── hello_world_gonb.md
│ │ │ ├── ijavascript.md
│ │ │ ├── ir_notebook.md
│ │ │ ├── itypescript.md
│ │ │ ├── julia_benchmark_plotly_barchart.md
│ │ │ ├── jupyter.md
│ │ │ ├── jupyter_again.md
│ │ │ ├── jupyter_with_raw_cell_in_body.md
│ │ │ ├── jupyter_with_raw_cell_on_top.md
│ │ │ ├── jupyter_with_raw_cell_with_invalid_yaml.md
│ │ │ ├── jupyterlab-slideshow_1441.md
│ │ │ ├── jupytext_replication.md
│ │ │ ├── kalman_filter_and_visualization.md
│ │ │ ├── logtalk_notebook.md
│ │ │ ├── lua_example.md
│ │ │ ├── maxima_example.md
│ │ │ ├── notebook_with_complex_metadata.md
│ │ │ ├── nteract_with_parameter.md
│ │ │ ├── ocaml_notebook.md
│ │ │ ├── octave_notebook.md
│ │ │ ├── plotly_graphs.md
│ │ │ ├── powershell.md
│ │ │ ├── raw_cell_with_complex_yaml_like_content.md
│ │ │ ├── raw_cell_with_non_dict_yaml_content.md
│ │ │ ├── root_cpp.md
│ │ │ ├── sage_print_hello.md
│ │ │ ├── sample_bash_notebook.md
│ │ │ ├── sample_rise_notebook_66.md
│ │ │ ├── sas.md
│ │ │ ├── simple-helloworld.md
│ │ │ ├── simple_robot_notebook.md
│ │ │ ├── simple_scala_notebook.md
│ │ │ ├── stata_notebook.md
│ │ │ ├── tailrecursive-factorial.md
│ │ │ ├── tcl_test.md
│ │ │ ├── text_outputs_and_images.md
│ │ │ ├── wolfram.md
│ │ │ └── xonsh_example.md
│ │ ├── ipynb_to_pandoc/
│ │ │ ├── Notebook_with_R_magic.md
│ │ │ ├── Notebook_with_more_R_magic_111.md
│ │ │ ├── cat_variable.md
│ │ │ ├── convert_to_py_then_test_with_update83.md
│ │ │ ├── frozen_cell.md
│ │ │ ├── ir_notebook.md
│ │ │ ├── julia_benchmark_plotly_barchart.md
│ │ │ ├── jupyter.md
│ │ │ ├── jupyter_again.md
│ │ │ ├── jupyter_with_raw_cell_in_body.md
│ │ │ ├── jupyter_with_raw_cell_on_top.md
│ │ │ ├── notebook_with_complex_metadata.md
│ │ │ ├── nteract_with_parameter.md
│ │ │ ├── plotly_graphs.md
│ │ │ ├── raw_cell_with_complex_yaml_like_content.md
│ │ │ ├── raw_cell_with_non_dict_yaml_content.md
│ │ │ ├── sample_rise_notebook_66.md
│ │ │ └── text_outputs_and_images.md
│ │ ├── ipynb_to_percent/
│ │ │ ├── Line_breaks_in_LateX_305.py
│ │ │ ├── Notebook with function and cell metadata 164.py
│ │ │ ├── Notebook with html and latex cells.py
│ │ │ ├── Notebook with many hash signs.py
│ │ │ ├── Notebook with metadata and long cells.py
│ │ │ ├── Notebook_with_R_magic.py
│ │ │ ├── Notebook_with_more_R_magic_111.py
│ │ │ ├── R notebook with invalid cell keys.R
│ │ │ ├── R notebook with invalid cell keys.low.r
│ │ │ ├── Reference Guide for Calysto Scheme.scm
│ │ │ ├── Reference Guide for Calysto Scheme.ss
│ │ │ ├── The flavors of raw cells.py
│ │ │ ├── cat_variable.py
│ │ │ ├── coconut_homepage_demo.coco
│ │ │ ├── convert_to_py_then_test_with_update83.py
│ │ │ ├── csharp.cs
│ │ │ ├── demo_gdl_fbp.pro
│ │ │ ├── evcxr_jupyter_tour.rs
│ │ │ ├── frozen_cell.py
│ │ │ ├── fsharp.fsx
│ │ │ ├── gnuplot_notebook.gp
│ │ │ ├── haskell_notebook.hs
│ │ │ ├── hello_world_gonb.go
│ │ │ ├── html-demo.clj
│ │ │ ├── ijavascript.js
│ │ │ ├── ir_notebook.R
│ │ │ ├── ir_notebook.low.r
│ │ │ ├── itypescript.ts
│ │ │ ├── julia_benchmark_plotly_barchart.jl
│ │ │ ├── julia_functional_geometry.jl
│ │ │ ├── jupyter.py
│ │ │ ├── jupyter_again.py
│ │ │ ├── jupyter_with_raw_cell_in_body.py
│ │ │ ├── jupyter_with_raw_cell_on_top.py
│ │ │ ├── jupyter_with_raw_cell_with_invalid_yaml.py
│ │ │ ├── jupyterlab-slideshow_1441.py
│ │ │ ├── jupytext_replication.sos
│ │ │ ├── kalman_filter_and_visualization.q
│ │ │ ├── logtalk_notebook.lgt
│ │ │ ├── lua_example.lua
│ │ │ ├── maxima_example.mac
│ │ │ ├── notebook_with_complex_metadata.py
│ │ │ ├── nteract_with_parameter.py
│ │ │ ├── ocaml_notebook.ml
│ │ │ ├── octave_notebook.m
│ │ │ ├── plotly_graphs.py
│ │ │ ├── powershell.ps1
│ │ │ ├── raw_cell_with_complex_yaml_like_content.py
│ │ │ ├── raw_cell_with_non_dict_yaml_content.py
│ │ │ ├── root_cpp.cpp
│ │ │ ├── sage_print_hello.sage
│ │ │ ├── sample_bash_notebook.sh
│ │ │ ├── sample_rise_notebook_66.py
│ │ │ ├── sas.sas
│ │ │ ├── simple-helloworld.java
│ │ │ ├── simple_robot_notebook.robot
│ │ │ ├── simple_scala_notebook.scala
│ │ │ ├── stata_notebook.do
│ │ │ ├── tailrecursive-factorial.groovy
│ │ │ ├── tcl_test.tcl
│ │ │ ├── text_outputs_and_images.py
│ │ │ ├── wolfram.wolfram
│ │ │ ├── xcpp_by_quantstack.cpp
│ │ │ └── xonsh_example.xsh
│ │ ├── ipynb_to_quarto/
│ │ │ ├── Notebook_with_more_R_magic_111.qmd
│ │ │ ├── cat_variable.qmd
│ │ │ ├── frozen_cell.qmd
│ │ │ └── julia_benchmark_plotly_barchart.qmd
│ │ ├── ipynb_to_script/
│ │ │ ├── Line_breaks_in_LateX_305.py
│ │ │ ├── Notebook with function and cell metadata 164.py
│ │ │ ├── Notebook with html and latex cells.py
│ │ │ ├── Notebook with metadata and long cells.py
│ │ │ ├── Notebook_with_R_magic.py
│ │ │ ├── Notebook_with_more_R_magic_111.py
│ │ │ ├── R notebook with invalid cell keys.R
│ │ │ ├── R notebook with invalid cell keys.low.r
│ │ │ ├── Reference Guide for Calysto Scheme.scm
│ │ │ ├── Reference Guide for Calysto Scheme.ss
│ │ │ ├── The flavors of raw cells.py
│ │ │ ├── cat_variable.py
│ │ │ ├── coconut_homepage_demo.coco
│ │ │ ├── convert_to_py_then_test_with_update83.py
│ │ │ ├── csharp.cs
│ │ │ ├── demo_gdl_fbp.pro
│ │ │ ├── evcxr_jupyter_tour.rs
│ │ │ ├── frozen_cell.py
│ │ │ ├── fsharp.fsx
│ │ │ ├── gnuplot_notebook.gp
│ │ │ ├── haskell_notebook.hs
│ │ │ ├── hello_world_gonb.go
│ │ │ ├── html-demo.clj
│ │ │ ├── ijavascript.js
│ │ │ ├── ir_notebook.R
│ │ │ ├── ir_notebook.low.r
│ │ │ ├── itypescript.ts
│ │ │ ├── julia_benchmark_plotly_barchart.jl
│ │ │ ├── julia_functional_geometry.jl
│ │ │ ├── jupyter.py
│ │ │ ├── jupyter_again.py
│ │ │ ├── jupyter_with_raw_cell_in_body.py
│ │ │ ├── jupyter_with_raw_cell_on_top.py
│ │ │ ├── jupyter_with_raw_cell_with_invalid_yaml.py
│ │ │ ├── jupyterlab-slideshow_1441.py
│ │ │ ├── jupytext_replication.sos
│ │ │ ├── kalman_filter_and_visualization.q
│ │ │ ├── logtalk_notebook.lgt
│ │ │ ├── lua_example.lua
│ │ │ ├── maxima_example.mac
│ │ │ ├── notebook_with_complex_metadata.py
│ │ │ ├── nteract_with_parameter.py
│ │ │ ├── ocaml_notebook.ml
│ │ │ ├── octave_notebook.m
│ │ │ ├── plotly_graphs.py
│ │ │ ├── powershell.ps1
│ │ │ ├── raw_cell_with_complex_yaml_like_content.py
│ │ │ ├── raw_cell_with_non_dict_yaml_content.py
│ │ │ ├── root_cpp.cpp
│ │ │ ├── sage_print_hello.sage
│ │ │ ├── sample_bash_notebook.sh
│ │ │ ├── sample_rise_notebook_66.py
│ │ │ ├── sas.sas
│ │ │ ├── simple-helloworld.java
│ │ │ ├── simple_robot_notebook.robot
│ │ │ ├── simple_scala_notebook.scala
│ │ │ ├── stata_notebook.do
│ │ │ ├── tailrecursive-factorial.groovy
│ │ │ ├── tcl_test.tcl
│ │ │ ├── text_outputs_and_images.py
│ │ │ ├── wolfram.wolfram
│ │ │ ├── xcpp_by_quantstack.cpp
│ │ │ └── xonsh_example.xsh
│ │ ├── ipynb_to_script_vim_folding_markers/
│ │ │ ├── Line_breaks_in_LateX_305.py
│ │ │ ├── Notebook with function and cell metadata 164.py
│ │ │ ├── Notebook with html and latex cells.py
│ │ │ ├── Notebook with many hash signs.py
│ │ │ ├── Notebook with metadata and long cells.py
│ │ │ ├── Notebook_with_R_magic.py
│ │ │ ├── Notebook_with_more_R_magic_111.py
│ │ │ ├── The flavors of raw cells.py
│ │ │ ├── cat_variable.py
│ │ │ ├── convert_to_py_then_test_with_update83.py
│ │ │ ├── frozen_cell.py
│ │ │ ├── jupyter.py
│ │ │ ├── jupyter_again.py
│ │ │ ├── jupyter_with_raw_cell_in_body.py
│ │ │ ├── jupyter_with_raw_cell_on_top.py
│ │ │ ├── jupyter_with_raw_cell_with_invalid_yaml.py
│ │ │ ├── jupyterlab-slideshow_1441.py
│ │ │ ├── notebook_with_complex_metadata.py
│ │ │ ├── nteract_with_parameter.py
│ │ │ ├── plotly_graphs.py
│ │ │ ├── raw_cell_with_complex_yaml_like_content.py
│ │ │ ├── raw_cell_with_non_dict_yaml_content.py
│ │ │ ├── sample_rise_notebook_66.py
│ │ │ └── text_outputs_and_images.py
│ │ ├── ipynb_to_script_vscode_folding_markers/
│ │ │ ├── Line_breaks_in_LateX_305.py
│ │ │ ├── Notebook with function and cell metadata 164.py
│ │ │ ├── Notebook with html and latex cells.py
│ │ │ ├── Notebook with many hash signs.py
│ │ │ ├── Notebook with metadata and long cells.py
│ │ │ ├── Notebook_with_R_magic.py
│ │ │ ├── Notebook_with_more_R_magic_111.py
│ │ │ ├── The flavors of raw cells.py
│ │ │ ├── cat_variable.py
│ │ │ ├── convert_to_py_then_test_with_update83.py
│ │ │ ├── frozen_cell.py
│ │ │ ├── jupyter.py
│ │ │ ├── jupyter_again.py
│ │ │ ├── jupyter_with_raw_cell_in_body.py
│ │ │ ├── jupyter_with_raw_cell_on_top.py
│ │ │ ├── jupyter_with_raw_cell_with_invalid_yaml.py
│ │ │ ├── jupyterlab-slideshow_1441.py
│ │ │ ├── notebook_with_complex_metadata.py
│ │ │ ├── nteract_with_parameter.py
│ │ │ ├── plotly_graphs.py
│ │ │ ├── raw_cell_with_complex_yaml_like_content.py
│ │ │ ├── raw_cell_with_non_dict_yaml_content.py
│ │ │ ├── sample_rise_notebook_66.py
│ │ │ └── text_outputs_and_images.py
│ │ ├── ipynb_to_sphinx/
│ │ │ ├── Line_breaks_in_LateX_305.py
│ │ │ ├── cat_variable.py
│ │ │ ├── convert_to_py_then_test_with_update83.py
│ │ │ ├── jupyter.py
│ │ │ ├── jupyter_again.py
│ │ │ ├── jupyterlab-slideshow_1441.py
│ │ │ ├── notebook_with_complex_metadata.py
│ │ │ ├── nteract_with_parameter.py
│ │ │ ├── plotly_graphs.py
│ │ │ ├── sample_rise_notebook_66.py
│ │ │ └── text_outputs_and_images.py
│ │ ├── ipynb_to_spin/
│ │ │ ├── R notebook with invalid cell keys.R
│ │ │ ├── R notebook with invalid cell keys.low.r
│ │ │ ├── ir_notebook.R
│ │ │ └── ir_notebook.low.r
│ │ ├── md_to_ipynb/
│ │ │ ├── jupytext_markdown.ipynb
│ │ │ └── plain_markdown.ipynb
│ │ ├── myst_to_ipynb/
│ │ │ ├── fenced_code_vs_code_cells.ipynb
│ │ │ └── reference_link.ipynb
│ │ ├── script_to_ipynb/
│ │ │ ├── basic_marimo_example.ipynb
│ │ │ ├── build.ipynb
│ │ │ ├── hydrogen.ipynb
│ │ │ ├── hydrogen_latex_html_R_magics.ipynb
│ │ │ ├── julia_sample_script.ipynb
│ │ │ ├── knitr-spin.ipynb
│ │ │ ├── light_sample.ipynb
│ │ │ ├── python_notebook_sample.ipynb
│ │ │ └── simple_r_script.ipynb
│ │ ├── sphinx-rst2md_to_ipynb/
│ │ │ └── plot_notebook.ipynb
│ │ └── sphinx_to_ipynb/
│ │ └── plot_notebook.ipynb
│ ├── external/
│ │ ├── cli/
│ │ │ ├── test_black.py
│ │ │ ├── test_cli_check.py
│ │ │ └── test_isort.py
│ │ ├── conftest.py
│ │ ├── contents_manager/
│ │ │ └── test_contentsmanager_external.py
│ │ ├── docs/
│ │ │ └── test_using_cli.py
│ │ ├── jupyter_fs/
│ │ │ └── test_jupyter_fs.py
│ │ ├── pre_commit/
│ │ │ ├── test_pre_commit_0_ipynb_to_py.py
│ │ │ ├── test_pre_commit_1_sync.py
│ │ │ ├── test_pre_commit_1_sync_with_config.py
│ │ │ ├── test_pre_commit_1_sync_with_no_config.py
│ │ │ ├── test_pre_commit_2_sync_nbstripout.py
│ │ │ ├── test_pre_commit_3_sync_black_nbstripout.py
│ │ │ ├── test_pre_commit_4_sync_execute.py
│ │ │ ├── test_pre_commit_5_reformat_markdown.py
│ │ │ ├── test_pre_commit_mode.py
│ │ │ └── test_pre_commit_scripts.py
│ │ ├── round_trip/
│ │ │ └── test_mirror_external.py
│ │ ├── rst2md/
│ │ │ └── test_rst2md.py
│ │ ├── simple_external_notebooks/
│ │ │ ├── test_read_simple_pandoc.py
│ │ │ └── test_read_simple_quarto.py
│ │ └── test_marimo.py
│ ├── functional/
│ │ ├── cli/
│ │ │ ├── test_cli.py
│ │ │ ├── test_cli_config.py
│ │ │ ├── test_source_is_newer.py
│ │ │ └── test_synchronous_changes.py
│ │ ├── config/
│ │ │ └── test_config.py
│ │ ├── contents_manager/
│ │ │ └── test_async_and_sync_contents_manager_are_in_sync.py
│ │ ├── docs/
│ │ │ ├── test_changelog.py
│ │ │ └── test_doc_files_are_notebooks.py
│ │ ├── metadata/
│ │ │ ├── test_metadata_filter.py
│ │ │ └── test_metadata_filters_from_config.py
│ │ ├── others/
│ │ │ ├── invalid_file_896.md
│ │ │ ├── test_active_cells.py
│ │ │ ├── test_auto_ext.py
│ │ │ ├── test_cell_markers.py
│ │ │ ├── test_cell_metadata.py
│ │ │ ├── test_cell_tags_are_preserved.py
│ │ │ ├── test_cells.py
│ │ │ ├── test_combine.py
│ │ │ ├── test_custom_cell_magics.py
│ │ │ ├── test_doxygen.py
│ │ │ ├── test_hide_remove_input_outputs_rmarkdown.py
│ │ │ ├── test_invalid_file.py
│ │ │ ├── test_jupytext_errors.py
│ │ │ ├── test_jupytext_read.py
│ │ │ ├── test_nbformat_version.py
│ │ │ ├── test_preserve_empty_cells.py
│ │ │ ├── test_pytest.py
│ │ │ ├── test_raw_strings.py
│ │ │ ├── test_read_write_functions.py
│ │ │ ├── test_remove_encoding.py
│ │ │ ├── test_sample_notebooks_are_normalized.py
│ │ │ ├── test_save_multiple.py
│ │ │ ├── test_trust_notebook.py
│ │ │ ├── test_unicode.py
│ │ │ └── test_write_does_not_modify_notebook.py
│ │ ├── round_trip/
│ │ │ ├── test_jupytext_nbconvert_round_trip.py
│ │ │ ├── test_mirror.py
│ │ │ ├── test_myst_header.py
│ │ │ ├── test_read_all_py.py
│ │ │ └── test_rmd_to_ipynb.py
│ │ └── simple_notebooks/
│ │ ├── test_ipynb_to_R.py
│ │ ├── test_ipynb_to_myst.py
│ │ ├── test_ipynb_to_py.py
│ │ ├── test_ipynb_to_rmd.py
│ │ ├── test_knitr_spin.py
│ │ ├── test_read_dotnet_try_markdown.py
│ │ ├── test_read_empty_text_notebook.py
│ │ ├── test_read_folding_markers.py
│ │ ├── test_read_incomplete_rmd.py
│ │ ├── test_read_simple_R.py
│ │ ├── test_read_simple_clojure.py
│ │ ├── test_read_simple_csharp.py
│ │ ├── test_read_simple_go.py
│ │ ├── test_read_simple_groovy.py
│ │ ├── test_read_simple_hydrogen.py
│ │ ├── test_read_simple_ipynb.py
│ │ ├── test_read_simple_java.py
│ │ ├── test_read_simple_julia.py
│ │ ├── test_read_simple_markdown.py
│ │ ├── test_read_simple_matlab.py
│ │ ├── test_read_simple_nomarker.py
│ │ ├── test_read_simple_ocaml.py
│ │ ├── test_read_simple_percent.py
│ │ ├── test_read_simple_python.py
│ │ ├── test_read_simple_rmd.py
│ │ ├── test_read_simple_rust.py
│ │ ├── test_read_simple_scheme.py
│ │ └── test_read_simple_sphinx.py
│ ├── integration/
│ │ ├── cli/
│ │ │ ├── test_cli_pipe.py
│ │ │ └── test_execute.py
│ │ ├── contents_manager/
│ │ │ ├── test_cm_config.py
│ │ │ ├── test_contentsmanager.py
│ │ │ └── test_load_multiple.py
│ │ └── jupytext_config/
│ │ └── test_jupytext_config.py
│ └── unit/
│ ├── test_cell_id.py
│ ├── test_compare.py
│ ├── test_escape_magics.py
│ ├── test_formats.py
│ ├── test_header.py
│ ├── test_labconfig.py
│ ├── test_markdown_in_code_cells.py
│ ├── test_paired_paths.py
│ ├── test_pep8.py
│ └── test_stringparser.py
└── tools/
└── absolute_links_in_readme.py
================================================
FILE CONTENTS
================================================
================================================
FILE: .git-blame-ignore-revs
================================================
# git config blame.ignoreRevsFile .git-blame-ignore-revs
961ce04d240560ea5d54b5d1ab40e6b5d76b4474
================================================
FILE: .git_archival.txt
================================================
node: $Format:%H$
node-date: $Format:%cI$
describe-name: $Format:%(describe:tags=true,match=*[0-9]*)$
ref-names: $Format:%D$
================================================
FILE: .gitattributes
================================================
World\ population.ipynb linguist-documentation
.git_archival.txt export-subst
# SCM syntax highlighting & preventing 3-way merges
pixi.lock merge=binary linguist-language=TOML linguist-generated=true
================================================
FILE: .github/codecov.yml
================================================
codecov:
token: 8bdc5016-2a81-4a43-bdd5-7b3b9adc37e7
notify:
wait_for_ci: true
coverage:
status:
project:
source:
paths:
- "src/jupytext/"
target: 96%
threshold: 0.2%
tests:
paths:
- "tests/"
target: 100%
unit-tests:
threshold: 0.2%
flags:
- unit
functional-tests:
threshold: 0.2%
flags:
- functional
integration-tests:
threshold: 0.2%
flags:
- integration
external-tests:
threshold: 0.2%
flags:
- external
patch:
default:
target: 80%
================================================
FILE: .github/dependabot.yml
================================================
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
version: 2
updates:
# Let's not bother about bumping top level dependencies as they are mostly
# developmental packages like linters. We can bump them manually
# when we make changes to extension packages to be consistent with JupyterLab.
# Importantly, these packages will not and should not effect the working of the
# extension, so lets try to keep maintenance low by ignoring this.
# - package-ecosystem: "npm" # See documentation for possible values
# directory: "jupyterlab" # Location of package manifests
# schedule:
# interval: "weekly"
# groups:
# top-level-dependencies:
# patterns:
# - "*"
- package-ecosystem: "npm" # See documentation for possible values
directory: "jupyterlab/packages/jupyterlab-jupytext-extension" # Location of package manifests
schedule:
interval: "weekly"
groups:
jupytext-extension-dependencies:
patterns:
- "*"
- package-ecosystem: "npm" # See documentation for possible values
directory: "jupyterlab/packages/jupyterlab-jupytext-extension/ui-tests" # Location of package manifests
schedule:
interval: "weekly"
groups:
jupytext-extension-ui-tests-dependencies:
patterns:
- "*"
================================================
FILE: .github/workflows/ci.yml
================================================
name: CI
on:
workflow_dispatch:
inputs:
upload-build-artifacts:
type: boolean
required: false
default: false
description: Upload build artifacts
push:
paths-ignore:
- "CHANGELOG.md"
branches: [ main ]
pull_request:
branches: [ main ]
schedule:
- cron: "0 11 * * 4"
permissions:
# All nested workflows will inherit these permissions and so no need to declare
# in each step file
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
pre-commit:
uses: ./.github/workflows/step_pre-commit.yml
static-analysis:
needs: [ pre-commit ]
uses: ./.github/workflows/step_static-analysis.yml
permissions:
contents: read
security-events: write
test-pip:
needs: [ pre-commit ]
uses: ./.github/workflows/step_tests-pip.yml
with:
coverage: ${{ github.event_name != 'schedule' }}
coverage:
needs: [ test-pip ]
uses: ./.github/workflows/step_coverage.yml
if: github.event_name != 'schedule'
test-conda:
needs: [ pre-commit ]
uses: ./.github/workflows/step_tests-conda.yml
with:
coverage: ${{ github.event_name != 'schedule' }}
test-ui:
needs: [ test-pip ]
uses: ./.github/workflows/step_tests-ui.yml
build:
needs: [ test-pip, test-conda, test-ui ]
uses: ./.github/workflows/step_build.yml
with:
upload: ${{ inputs.upload-build-artifacts || false }}
pass:
name: Pass
needs: [ pre-commit, static-analysis, test-pip, coverage, test-conda, test-ui, build ]
runs-on: ubuntu-latest
steps:
- uses: re-actors/alls-green@release/v1
with:
jobs: ${{ toJSON(needs) }}
allowed-skips: coverage
if: always()
================================================
FILE: .github/workflows/comment-pr.yml
================================================
name: Comment PR
on:
pull_request_target:
permissions:
pull-requests: write
jobs:
comment-pr:
runs-on: ubuntu-latest
name: Comment PR
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Comment PR
uses: thollander/actions-comment-pull-request@v2
with:
message: |
Thank you for making this pull request.
Did you know? You can try it on Binder: [](https://mybinder.org/v2/gh/${{ github.event.pull_request.head.repo.full_name }}/${{ github.event.pull_request.head.ref }}?urlpath=lab/tree/demo/get_started.ipynb) or [](https://mybinder.org/v2/gh/${{ github.event.pull_request.head.repo.full_name }}/${{ github.event.pull_request.head.ref }}?filepath=demo).
Also, the version of Jupytext developed in this PR can be installed with `pip`:
```
HATCH_BUILD_HOOKS_ENABLE=true pip install git+${{ github.event.pull_request.head.repo.clone_url }}@${{ github.event.pull_request.head.ref }}
```
(this requires `nodejs`, see more at [Developing Jupytext](https://jupytext.readthedocs.io/en/latest/developing.html))
comment_tag: binder_link
================================================
FILE: .github/workflows/publish.yml
================================================
name: Publish
on:
push:
tags:
- "v[0-9]+.[0-9]+.[0-9]+*"
permissions:
contents: read
jobs:
build:
uses: ./.github/workflows/step_build.yml
publish:
needs: [ build ]
runs-on: ubuntu-latest
environment:
name: pypi
url: https://pypi.org/p/jupytext
permissions:
contents: read
id-token: write
steps:
- name: Checkout source
uses: actions/checkout@v3
- name: Install pixi
uses: prefix-dev/setup-pixi@v0.9.1
with:
pixi-version: v0.59.0
cache: true
- name: Build package
run: |
eval "$(pixi shell-hook)"
HATCH_BUILD_HOOKS_ENABLE=true hatch build
- name: Publish
uses: pypa/gh-action-pypi-publish@release/v1
================================================
FILE: .github/workflows/step_build.yml
================================================
name: build
run-name: Build package
on:
workflow_call:
inputs:
upload:
type: boolean
required: false
default: false
description: Upload build artifacts
ref:
type: string
description: Tag to build
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout source
uses: actions/checkout@v4
- name: Install pixi
uses: prefix-dev/setup-pixi@v0.9.1
with:
pixi-version: v0.59.0
cache: true
- name: Build package
run: |
eval "$(pixi shell-hook)"
HATCH_BUILD_HOOKS_ENABLE=true hatch build
- name: Archive build artifacts
uses: actions/upload-artifact@v4
with:
name: dist
path: dist
if: ${{ inputs.upload }}
================================================
FILE: .github/workflows/step_coverage.yml
================================================
name: coverage
run-name: Check coverage
on:
workflow_call:
permissions:
contents: read
jobs:
coverage:
name: >
${{ matrix.coverage }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
coverage: [unit, functional, integration, external]
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Base Setup
uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1
with:
python_version: 3.x
- name: Install from source
run: python -m pip install -e '.[test-cov,test-${{ matrix.coverage }}]'
- name: Install a Jupyter Kernel
run: python -m ipykernel install --name python_kernel --user
- name: Run the tests
run: pytest tests/${{ matrix.coverage }} -n logical --cov --cov-report=xml
- name: Upload the coverage
uses: codecov/codecov-action@v5
with:
flags: ${{ matrix.coverage }}
fail_ci_if_error: true
verbose: true
================================================
FILE: .github/workflows/step_pre-commit.yml
================================================
name: pre-commit
run-name: Run pre-commit tests
on:
workflow_call:
permissions:
contents: read
jobs:
pre-commit:
runs-on: ubuntu-latest
steps:
- name: Checkout source
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.x"
- name: Install pixi
uses: prefix-dev/setup-pixi@v0.9.1
with:
pixi-version: v0.55.0
cache: true
- uses: pre-commit/action@v3.0.0
lint-extension:
runs-on: ubuntu-latest
steps:
- name: Checkout source
uses: actions/checkout@v4
- name: Base Setup
uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1
with:
python_version: "3.x"
# Current repo organization will not permit to set up pre-commit config for
# TS lint related packages due to absence of package.json in the top level
# repo. So we run lint step separately here
- name: Lint the extension
run: |
# Install JupyterLab in an isolated env to get jlpm
pip install jupyterlab>=4
# Install lint deps and lint extension
cd jupyterlab/
jlpm
jlpm run lint:check
================================================
FILE: .github/workflows/step_static-analysis.yml
================================================
name: static-analysis
run-name: Run CodeQL analysis
on:
workflow_call:
permissions:
contents: read
security-events: write
jobs:
codeql:
runs-on: ubuntu-latest
continue-on-error: true
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: python, javascript
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
================================================
FILE: .github/workflows/step_tests-conda.yml
================================================
name: test-conda
run-name: Run Conda tests for different OS
on:
workflow_call:
inputs:
coverage:
type: boolean
description: Upload coverage to CodeCov
default: true
permissions:
contents: read
jobs:
test-conda:
name: >
${{ matrix.os }} (${{ matrix.dependency-mode }} dependencies)
strategy:
fail-fast: false
matrix:
os: [ 'ubuntu-latest', 'macos-latest', 'windows-latest' ]
dependency-mode: [ 'locked', 'latest' ]
runs-on: ${{ matrix.os }}
defaults:
run:
shell: bash -el {0}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install pixi
uses: prefix-dev/setup-pixi@v0.9.1
with:
pixi-version: v0.59.0
cache: true
- name: Update pixi environment
if: matrix.dependency-mode == 'latest'
run: |
eval "$(pixi shell-hook)"
pixi update
- name: Run tests
run: |
eval "$(pixi shell-hook)"
# Install from source
HATCH_BUILD_HOOKS_ENABLE=true python -m pip install -e .
# Install a Jupyter Kernel
python -m ipykernel install --name jupytext-ci --user
# Test with pytest
pytest -n logical --cov --cov-report=xml
# Uninstall jupyter-fs as it overrides the original browser-test.js to
# check its own functionality (https://github.com/jpmorganchase/jupyter-fs/blob/main/jupyterfs/browser-test.js)
# So uninstall jupyter-fs before running browser check
pip uninstall jupyter-fs -y
# Check extension
jupyter labextension list
jupyter labextension list 2>&1 | grep -ie "jupyterlab-jupytext.*OK"
# Test lab extension
python -m jupyterlab.browser_check
- name: Upload coverage
uses: codecov/codecov-action@v5
with:
fail_ci_if_error: true
verbose: true
if: inputs.coverage
================================================
FILE: .github/workflows/step_tests-pip.yml
================================================
name: test-pip
run-name: Run main tests using Pip
on:
workflow_call:
inputs:
coverage:
type: boolean
description: Upload coverage to CodeCov
default: true
permissions:
contents: read
jobs:
test-pip:
name: >
🐍 ${{ matrix.python-version }}
${{ matrix.dependency_type && format('({0})', matrix.dependency_type) }}
${{ matrix.quarto && '(Quarto)' }}
${{ matrix.no_kernel && '(No kernel)' }}
${{ matrix.markdown-it-py && format('(markdown-it-py {0})', matrix.markdown-it-py) }}
${{ matrix.no_markdown-it-py && '(No markdown-it-py)'}}
${{ matrix.jupyter_server && format('(Jupyter-server {0})', matrix.jupyter_server) }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version: [ "3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
experimental: [false]
include:
# Test with jupyter-server=2.10 that does not have the 'require_hash' argument
- python-version: "3.x"
jupyter_server: "2.10.0"
# Test minimum markdown-it-py supported (otherwise the most recent version is used)
- python-version: "3.x"
markdown-it-py: "~=2.0"
- python-version: "3.x"
no_markdown-it-py: true
- python-version: "3.x"
no_kernel: true
- python-version: "3.x"
quarto: true
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Base Setup
uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1
with:
python_version: ${{ matrix.python-version }}
dependency_type: ${{ matrix.dependency_type }}
- name: Install from source
run: >
HATCH_BUILD_HOOKS_ENABLE=true python -m pip install
-e '.[test-cov,test-external]'
jupyterlab
${{ format('markdown-it-py{0}', matrix.markdown-it-py) }}
- name: Install Jupyter Server
if: ${{ matrix.jupyter_server }}
run: python -m pip install 'jupyter_server~=${{ matrix.jupyter_server }}'
- name: List the versions of the Jupyter components
run: jupyter --version
- name: Install a Jupyter Kernel
if: ${{ !matrix.no_kernel }}
run: python -m ipykernel install --name python_kernel --user
- name: Uninstall markdown-it-py
# Markdown-it-py is a dependency of Jupytext,
# but Jupytext should still work if it is not installed
if: ${{ matrix.no_markdown-it-py }}
run: python -m pip uninstall markdown-it-py --yes
- name: Install Quarto
if: ${{ matrix.quarto }}
env:
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
run: |
# download the latest release
gh release download --repo quarto-dev/quarto-cli --pattern 'quarto-*-linux-amd64.deb'
# install it
sudo apt install ./*.deb
- name: Test with pytest
continue-on-error: ${{ matrix.experimental }}
run: pytest -n logical --cov --cov-report=xml
- name: Test lab extension
run: |
# Uninstall jupyter-fs as it overrides the original browser-test.js to
# check its own functionality (https://github.com/jpmorganchase/jupyter-fs/blob/main/jupyterfs/browser-test.js)
# So uninstall jupyter-fs before running browser check
#
pip uninstall jupyter-fs -y
# Check extension
jupyter labextension list
jupyter labextension list 2>&1 | grep -ie "jupyterlab-jupytext.*OK"
python -m jupyterlab.browser_check
- name: Upload coverage
uses: codecov/codecov-action@v5
with:
fail_ci_if_error: true
verbose: true
if: inputs.coverage
================================================
FILE: .github/workflows/step_tests-ui.yml
================================================
name: test-ui
run-name: Run UI tests with Galata
on:
workflow_call:
permissions:
contents: read
jobs:
test-ui:
continue-on-error: false
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install pixi
uses: prefix-dev/setup-pixi@v0.9.1
with:
pixi-version: v0.59.0
cache: true
- name: Install from source
run: |
eval "$(pixi shell-hook)"
HATCH_BUILD_HOOKS_ENABLE=true python -m pip install -e '.[test-ui]'
- name: Uninstall jupyter-fs
run: |
eval "$(pixi shell-hook)"
# Uninstall jupyter-fs as it overrides the original browser-test.js to
# check its own functionality (https://github.com/jpmorganchase/jupyter-fs/blob/main/jupyterfs/browser-test.js)
pip uninstall jupyter-fs -y
- name: Install galata
working-directory: jupyterlab/packages/jupyterlab-jupytext-extension/ui-tests
env:
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
run: |
eval "$(pixi shell-hook)"
jlpm install
- name: Install browser
working-directory: jupyterlab/packages/jupyterlab-jupytext-extension/ui-tests
run: |
eval "$(pixi shell-hook)"
jlpm playwright install chromium
- name: Integration tests
working-directory: jupyterlab/packages/jupyterlab-jupytext-extension/ui-tests
run: |
eval "$(pixi shell-hook)"
jlpm playwright test
- name: Upload UI Test artifacts
if: failure()
uses: actions/upload-artifact@v4
with:
name: ui-test-output
path: |
jupyterlab/packages/jupyterlab-jupytext-extension/ui-tests/test-results
================================================
FILE: .github/workflows/update-playwright-snapshots.yml
================================================
name: Update Playwright Snapshots
on:
issue_comment:
types: [created, edited]
permissions:
contents: write
pull-requests: write
jobs:
update-snapshots:
if: ${{ github.event.issue.pull_request && contains(github.event.comment.body, 'update playwright snapshots') }}
runs-on: ubuntu-latest
steps:
- name: React to the triggering comment
run: |
gh api repos/${{ github.repository }}/issues/comments/${{ github.event.comment.id }}/reactions --raw-field 'content=+1'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Checkout
uses: actions/checkout@v4
- name: Checkout the branch from the PR that triggered the job
run: |
gh pr checkout ${{ github.event.issue.number }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Install jupytext
run: |
HATCH_BUILD_HOOKS_ENABLE=true python -m pip install -e '.[test-ui]' jupyterlab
# Uninstall jupyter-fs as it overrides the original browser-test.js to
# check its own functionality (https://github.com/jpmorganchase/jupyter-fs/blob/main/jupyterfs/browser-test.js)
pip uninstall jupyter-fs -y
- name: Update snapshots
uses: jupyterlab/maintainer-tools/.github/actions/update-snapshots@v1
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
# Test folder within your repository
test_folder: jupyterlab/packages/jupyterlab-jupytext-extension/ui-tests/tests
# Playwright knows how to start JupyterLab server
start_server_script: 'null'
npm_client: jlpm
================================================
FILE: .gitignore
================================================
.build
.cache
.eggs
.tox
build
/dist
docs/_build
# Will be created by postBuild
demo/get_started.ipynb
# jetbrains ide stuff
*.iml
.idea/
# vscode ide stuff
*.code-workspace
.history
.vscode/*
!.vscode/*.template
# Notebooks
*.pyc
.ipynb_checkpoints
.jupyter_ystore.db*
# Test related files
.coverage
.pytest_cache
*.coverage*
*.xml
.ruff_cache
# Ignore node_modules and yarn cache
node_modules
.yarn
# Ignore jupyter-releaser related stuff
.jupyter_releaser_checkout
# pixi environments
.pixi
*.egg-info
================================================
FILE: .pre-commit-config.yaml
================================================
# Install the pre-commit hooks below with
# 'pre-commit install'
# Auto-update the version of the hooks with
# 'pre-commit autoupdate'
# Run the hooks on all files with
# 'pre-commit run --all'
# NB: In this config we exclude the example and tests notebooks
# from the code hooks (black, flake8, etc) as we want to keep
# the text representation of the test notebooks unchanged, plus
# the (sync) contentsmanager.py which is generated from the async one.
exclude: >
(?x)^(
demo/.*|
tests/data/notebooks/.*|
src/jupytext/sync_pairs.py|
src/jupytext/sync_contentsmanager.py|
)$
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v6.0.0
hooks:
- id: check-json
- id: check-yaml
- id: end-of-file-fixer
- id: trailing-whitespace
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.14.13
hooks:
- id: ruff-check
args: ["--fix", "--show-fixes"]
- id: ruff-format
- repo: https://github.com/asottile/pyupgrade
rev: v3.21.2
hooks:
- id: pyupgrade
args: [--py39-plus]
- repo: local
hooks:
- id: pixi-lock
name: Update pixi.lock file
entry: pixi install
language: system
pass_filenames: false
files: ^(pyproject\.toml|pixi\.toml|pixi\.lock|src/jupytext/version\.py)$
================================================
FILE: .pre-commit-hooks.yaml
================================================
- id: jupytext
name: jupytext
description: Runs jupytext on all notebooks and paired files.
language: python
entry: jupytext --pre-commit-mode
require_serial: true
# We have only added the most frequent text notebook types, but
# many more types are actually supported (r, matlab, js, bash, powershell, robot, ...)
# If you need support for one of these other extensions, please override this default value
types_or: [jupyter, markdown, python]
================================================
FILE: .readthedocs.yml
================================================
# Read the Docs configuration file
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
# Required
version: 2
# Build the documentation using Pixi
build:
os: "ubuntu-24.04"
tools:
python: "3.12"
commands:
- asdf plugin add pixi
- asdf install pixi latest
- asdf global pixi latest
- pixi run -e docs sphinx-build -b html docs ${READTHEDOCS_OUTPUT:-docs/_build}/html
================================================
FILE: CHANGELOG.md
================================================
Jupytext ChangeLog
==================
1.19.1 (2026-01-25)
-------------------
**Changed**
- Jupytext does not change the file icons anymore! Thanks to [Michał Krassowski](https://github.com/krassowski) for finally solving this long standing issue! ([#398](https://github.com/mwouts/jupytext/issues/398))
- We have bumped lodash from 4.17.21 to 4.17.23 in JupyterLab extension ([#1483](https://github.com/mwouts/jupytext/pull/1483))
- We require `marimo<=0.19.4` in tests following a change in the location of the `marimo` import ([#1485](https://github.com/mwouts/jupytext/issues/1485))
1.19.0 (2026-01-18)
-------------------
**Changed**
- The environment used to develop Jupytext is now maintained using Pixi ([#1459](https://github.com/mwouts/jupytext/pull/1459))
**Fixed**
- We have restored the support for `.qmd` documents in Jupyter ([#1435](https://github.com/mwouts/jupytext/issues/1435))
- We have fixed warnings about the cell ids like either `Cell is missing an id field` or `Additional properties are not allowed ('id' was unexpected)` when converting to some formats, including Pandoc ([#1464](https://github.com/mwouts/jupytext/pull/1464))
- We have updated the dependencies of the Jupytext extension for JupyterLab. Many thanks to [Mahendra Paipuri](https://github.com/mahendrapaipuri) for his PR ([#1477](https://github.com/mwouts/jupytext/pull/1477))!
**Added**
- We have added a new `py:marimo` format, which uses the `marimo` command line interface to convert notebooks to and from the Marimo format ([#1431](https://github.com/mwouts/jupytext/pull/1431))
1.18.1 (2025-10-19)
-------------------
**Fixed**
- Fixed test `test_check_source_is_newer_when_using_jupytext_sync` to work reliably on systems with low filesystem timestamp resolution ([#1460](https://github.com/mwouts/jupytext/issues/1460))
**Changed**
- We now use `pixi` to maintain the local Python environments used for developing and testing Jupytext.
1.18.0 (2025-10-18)
-------------------
**Added**
- The documentation has a chapter on the [Jupytext Sync](https://jupytext.readthedocs.io/en/latest/vs-code.html) extension for VS Code ([#1395](https://github.com/mwouts/jupytext/issues/1395))
- We have added a new option `--check-source-is-newer` to the Jupytext CLI. Use this option if you want to make sure that the file passed as argument to Jupytext is the newest of all paired files, and/or newer than the destination file.
- We have added a section on [Jupyter Collaboration](https://jupytext.readthedocs.io/en/latest/jupyter-collaboration.html) which also provides a very useful autoreload functionality ([#406](https://github.com/mwouts/jupytext/issues/406), [#1401](https://github.com/mwouts/jupytext/issues/1401))
- Pairing groups allow you to define different pairing configurations for specific subsets of notebooks. The `formats` configuration option now supports a list of format dictionaries for first-match pairing. Use `[[formats]]` sections in your TOML configuration to define multiple format specifications, where the first matching format is used. This allows applying different pairing rules to notebooks in different locations, such as generating documentation markdown files only for tutorial notebooks ([#1383](https://github.com/mwouts/jupytext/issues/1383))
- We have added more tests to document the complex pairing formats, which also work on Windows ([#1028](https://github.com/mwouts/jupytext/issues/1028))
- Pairing into nested folders was fixed on Windows ([#1028](https://github.com/mwouts/jupytext/issues/1028))
- Jupytext is now tested with Python 3.14 ([#1456](https://github.com/mwouts/jupytext/issues/1456))
**Fixed**
- The Jupytext CLI now detects if a file it has read or consulted has been modified while it was processing it. That can happen in the context of the [Jupytext Sync](https://marketplace.visualstudio.com/items?itemName=caenrigen.jupytext-sync) extension for VS Code ([#1411](https://github.com/mwouts/jupytext/issues/1411), [vscode-jupytext-sync-#12](https://github.com/caenrigen/vscode-jupytext-sync/issues/12)). When such a synchronous modification is detected, Jupytext now raises an error. Many thanks to [Anne Archibald](https://github.com/aarchiba) for reporting the issue and preparing an inspiring PR ([#1417](https://github.com/mwouts/jupytext/pull/1417)).
- We don't import `notebook` when `jupyter_server` is available ([#1436](https://github.com/mwouts/jupytext/issues/1436))
- Jupytext now supports more characters in the cell metadata keys, to improve compatibility with `jupyterlab-slideshow` ([#1441](https://github.com/mwouts/jupytext/issues/1441)). Thanks to [Nicolas Thierry](https://github.com/nthiery) for this fix.
- The function `find_jupytext_configuration_file` now works with relative directories ([#1440](https://github.com/mwouts/jupytext/issues/1440)). This fix was contributed by [Thierry Parmentelat](https://github.com/parmentelat).
- We have fixed a parsing error for R Markdown files ([#1429](https://github.com/mwouts/jupytext/issues/1429))
- We have fixed a parsing error when the first cell of the notebook contains a YAML header that is not a dict ([#1444](https://github.com/mwouts/jupytext/issues/1444))
**Changed**
- We have updated the pre-commit hooks. The code is now formatted using `ruff format`, and updated for Python 3.9+ using https://github.com/asottile/pyupgrade ([#1423](https://github.com/mwouts/jupytext/issues/1423))
1.17.3 (2025-08-28)
-------------------
**Added**
- All the extensions supported by Jupytext now appear in the `--to` paragraph
of `jupytext --help` ([#433](https://github.com/mwouts/jupytext/issues/433))
- We have added a new function `get_formats_from_notebook_path` that returns the list of paired
formats for a given notebook ([#1419](https://github.com/mwouts/jupytext/issues/1419))
**Fixed**
- We have fixed `jupytext --sync`, and the contents manager, to make sure that a simple `.py` file (not in the percent format) will not be treated as a paired file when the Jupytext configuration file has `formats="ipynb,py:percent"` ([#1418](https://github.com/mwouts/jupytext/issues/1418))
- A notebook can be moved in Jupyter even if that makes it unpaired ([#1414](https://github.com/mwouts/jupytext/issues/1414))
- The test against `jupyter-fs` now installs its `fs` extra dependency ([#1398](https://github.com/mwouts/jupytext/issues/1398))
- The `jupytext --sync` command now works correctly with symbolic links. Thanks to [mccullerlp](https://github.com/mccullerlp) for reporting ([#1407](https://github.com/mwouts/jupytext/issues/1407)) and addressing ([#1408](https://github.com/mwouts/jupytext/pull/1408)) the problem!
- Jupytext will look for `quarto.cmd` on Windows. Thanks to [mccullerlp](https://github.com/mccullerlp) for documenting the issue ([#1406](https://github.com/mwouts/jupytext/issues/1406), [#1409](https://github.com/mwouts/jupytext/pull/1409)).
- We have corrected the `jupytext --paired-paths` command: it will now take the Jupytext configuration file, if any, into account ([#1419](https://github.com/mwouts/jupytext/issues/1419))
1.17.2 (2025-06-01)
-------------------
**Fixed**
- The `--set-formats` argument takes precedence over pre-existing pairing information in the notebook ([#1386](https://github.com/mwouts/jupytext/issues/1386))
**Changed**
- We have removed Python 3.8 from the list of supported Python version, and from the CI, as it has reached its EOL
**Added**
- ROOT-flavored C++ notebooks can be paired to `.cpp` files - thanks to [Steven Gardiner](https://github.com/sjgardiner) for this contribution ([#1384](https://github.com/mwouts/jupytext/pull/1384))
1.17.1 (2025-04-26)
-------------------
**Fixed**
- Renaming files other than notebooks will not load their content ([#1376](https://github.com/mwouts/jupytext/issues/1376))
1.17.0 (2025-04-05)
-------------------
**Added**
- The MyST frontmatter found in MyST Markdown notebooks can be mapped to a YAML header at the top of the Jupyter notebook. This way MyST notebooks in either the `md:myst` or `ipynb` format can be used with MyST. Thanks to [Ian Carroll](https://github.com/itcarroll) for proposing and implementing this change ([#1314](https://github.com/mwouts/jupytext/issues/1314))
- The context menu has a "New Text Notebook" entry. Thanks to [Mahendra Paipuri](https://github.com/mahendrapaipuri) for this PR ([#1365](https://github.com/mwouts/jupytext/pull/1365))!
**Changed**
- Jupytext's default contents manager is now derived from the asynchronous AsyncLargeFileManager. Thanks to [Darshan Poudel](https://github.com/Darshan808) for making this finally happen ([#1328](https://github.com/mwouts/jupytext/pull/1328))!
- The [percent format](https://jupytext.readthedocs.io/en/latest/formats-scripts.html#the-percent-format) is now the default format for scripts. If you run `jupytext --to py notebook.ipynb` you now get a `py:percent` script (use `--to py:light` for the light format) [#1201](https://github.com/mwouts/jupytext/pull/1201)
- The `rst2md` conversion now works with `sphinx-gallery>=0.8`. Thanks to [Thomas J. Fan](https://github.com/thomasjpfan) for fixing this! ([#1334](https://github.com/mwouts/jupytext/pull/1334))
- We have updated the JupyterLab extension dependencies ([#1300](https://github.com/mwouts/jupytext/pull/1300), [#1355](https://github.com/mwouts/jupytext/pull/1355), [#1360](https://github.com/mwouts/jupytext/pull/1360)). Thanks to [Mahendra Paipuri](https://github.com/mahendrapaipuri) for these PRs!
**Fixed**
- We have added and fixed round trip tests on MyST Markdown notebooks ([#759](https://github.com/mwouts/jupytext/issues/759), [#789](https://github.com/mwouts/jupytext/issues/789), [#1267](https://github.com/mwouts/jupytext/issues/1267), [#1317](https://github.com/mwouts/jupytext/issues/1317))
- The `--quiet` option now works with the `--pipe` mode of Jupytext CLI ([#1305](https://github.com/mwouts/jupytext/issues/1305))
- Jupytext is now compatible with the cell toolbar extension - thanks to [Nicolas Brichet](https://github.com/brichet) for the fix! ([#1358](https://github.com/mwouts/jupytext/pull/1358))
- The project description on PyPI now uses absolute links ([#1287](https://github.com/mwouts/jupytext/issues/1287))
1.16.7 (2025-02-09)
-------------------
**Added**
- Logtalk notebooks are now supported ([#1308](https://github.com/mwouts/jupytext/pull/1308)) - thanks to [Paulo Moura](https://github.com/pmoura) for this contribution
**Fixed**
We have updated the dependencies of the JupyterLab extension to address [security issues](https://github.com/mwouts/jupytext/security/dependabot).
1.16.6 (2024-12-16)
-------------------
**Fixed**
- We fixed a "File Changed" warning when saving notebooks ([#1301](https://github.com/mwouts/jupytext/issues/1301))
**Changed**
- The original file name is easier to infer from the tmp file name in pre-commit hooks ([#1289](https://github.com/mwouts/jupytext/issues/1289)) - thanks to [Lunin Leonid](https://github.com/lrlunin) for making this change.
1.16.5 (2024-12-15)
-------------------
**Fixed**
- We have fixed a notebook corruption issue when using Jupytext with Jupyter-Collaboration ([#1124](https://github.com/mwouts/jupytext/issues/1124), [jupyter-collaboration 214](https://github.com/jupyterlab/jupyter-collaboration/issues/214)).
- We have added the `require_hash` argument on the Jupytext contents manager. The hash of a paired file is the concatenation of the hash of the text file and the hash for the `.ipynb` file ([#1165](https://github.com/mwouts/jupytext/issues/1165))
- The `rst2md` tests have been fixed by requiring `sphinx<8` ([#1266](https://github.com/mwouts/jupytext/issues/1266))
- Some dependencies of the JupyterLab extensions were updated ([#1272](https://github.com/mwouts/jupytext/issues/1272), [#1273](https://github.com/mwouts/jupytext/issues/1273), [#1280](https://github.com/mwouts/jupytext/issues/1280), [#1285](https://github.com/mwouts/jupytext/issues/1285), [#1290](https://github.com/mwouts/jupytext/issues/1290))
- The pre-commit hook is now compatible with log.showsignature=True ([#1281](https://github.com/mwouts/jupytext/issues/1281)). Thanks to [Justin Lecher](https://github.com/jlec) for this fix.
**Added**
- Jupytext is now tested with Python 3.13 ([#1242](https://github.com/mwouts/jupytext/issues/1242)). Thanks to [Jerry James](https://github.com/jamesjer) for the suggested fixes!
- The extension of a notebook piped into stdin will be taken in the notebook metadata ([#1282](https://github.com/mwouts/jupytext/issues/1282))
1.16.4 (2024-07-12)
-------------------
**Fixed**
- We use `asyncio.iscoroutinefunction` to determine whether the current contents manager is sync or async ([#1260](https://github.com/mwouts/jupytext/issues/1260))
1.16.3 (2024-07-09)
-------------------
**Fixed**
- We use `inspect` to determine whether the current contents manager derives from `AsyncContentsManager` (which is not
supported by Jupytext at the moment). This fixes a compatibility issue with `jupyter-fs==1.0.0` ([#1239](https://github.com/mwouts/jupytext/issues/1239)). Thanks to [Mahendra Paipuri](https://github.com/mahendrapaipuri) for this PR!
- We have fixed a typo when `build_jupytext_contents_manager_class` can't be imported ([#1162](https://github.com/mwouts/jupytext/issues/1162))
- A Python 3.13 deprecation warning was fixed [#1241](https://github.com/mwouts/jupytext/pull/1241) - thanks to [Jerry James](https://github.com/jamesjer)
- We have fixed a typo when `build_jupytext_contents_manager_class` can't be imported ([#1162](https://github.com/mwouts/jupytext/issues/1162))
- Some dependencies of the JupyterLab extensions were updated ([#1243](https://github.com/mwouts/jupytext/issues/1243), [#1245](https://github.com/mwouts/jupytext/issues/1245))
**Added**
- Lua notebooks are now supported ([#1252](https://github.com/mwouts/jupytext/pull/1252)) - thanks to [erentar](https://github.com/erentar) for this contribution
- Go notebooks are supported too ([#1244](https://github.com/mwouts/jupytext/issues/1244))! Many thanks to [Jan Pfeifer](https://github.com/janpfeifer), author of [GoNB](https://github.com/janpfeifer/gonb), and to [HaveF](https://github.com/HaveF) for their help on this topic.
- Empty prefixes are now allowed in Jupytext format when specified as a dictionary ([#1144](https://github.com/mwouts/jupytext/issues/1144))
**Changed**
- We've had to deactivate the tests on the Quarto format in the CI as the Quarto round trip
might now add a Markdown cell to the notebook ([#1255](https://github.com/mwouts/jupytext/issues/1255))
1.16.2 (2024-05-05)
-------------------
**Added**
- Added support for Xonsh notebooks ([#1213](https://github.com/mwouts/jupytext/pull/1213)) - thanks to [Jeffrey Odongo](https://github.com/jsquaredosquared) for this contribution
**Changed**
- By default, the JupyterLab extension for Jupytext is not included in the build (set `HATCH_BUILD_HOOKS_ENABLE=true` to include it). This simplifies the installation of Jupytext in pre-commit hooks ([#1210](https://github.com/mwouts/jupytext/issues/1210))
- Temporary text notebooks for the `--pipe` or `--check` commands are now created in the notebook directory ([#1206](https://github.com/mwouts/jupytext/issues/1206))
- Jupytext uses the standard library `tomllib` in Python 3.11, or `tomli` in Python 3.10 or older, to match JupyterLab's dependencies ([#1195](https://github.com/mwouts/jupytext/issues/1195))
- The dependencies of the JupyterLab extension were updated ([#1216](https://github.com/mwouts/jupytext/issues/1216), [#1218](https://github.com/mwouts/jupytext/issues/1218), [#1231](https://github.com/mwouts/jupytext/issues/1231))
- `jupytext --sync` will not update the timestamp of text notebooks if their content is unchanged ([#1215](https://github.com/mwouts/jupytext/issues/1215))
**Fixed**
- Jupytext is now tested with `pandoc>=3.0`. Please note that switching to `pandoc>=3.0` will add cell ids to your `pandoc:md` notebooks ([#1006](https://github.com/mwouts/jupytext/issues/1006))
1.16.1 (2024-01-13)
-------------------
**Changed**
- The CI has been updated. Thanks to [Christian Le](https://github.com/LecrisUT) for taking care of this! ([#1190](https://github.com/mwouts/jupytext/issues/1190), [#1204](https://github.com/mwouts/jupytext/issues/1204))
**Fixed**
- Fixed an issue about unpairing notebooks from the Jupytext Menu ([#1197](https://github.com/mwouts/jupytext/issues/1197))
- JupyterLab's dependency `follow-redirects` was updated from 1.15.3 to 1.15.4 ([#1203](https://github.com/mwouts/jupytext/issues/1203))
1.16.0 (2023-12-03)
-------------------
**Added**
- The Jupytext Menu is back! And text notebooks can be created directly from the launcher. This is an outstanding contribution by [Mahendra Paipuri](https://github.com/mahendrapaipuri) ([#1154](https://github.com/mwouts/jupytext/issues/1154), [#1163](https://github.com/mwouts/jupytext/issues/1163)). This requires JupyterLab 4.x or Jupyter Notebook 7.x.
**Changed**
- Jupytext is now configured with `pyproject.toml` and built with `hatch`. The layout has been reorganised to follow `src-layout` ([#1140](https://github.com/mwouts/jupytext/issues/1140)). This is another outstanding contribution by [Mahendra Paipuri](https://github.com/mahendrapaipuri).
- The tests are now part of the `sdist`. They have been reorganized into unit/functional/integration/external ([#1167](https://github.com/mwouts/jupytext/issues/1167), [#1173](https://github.com/mwouts/jupytext/issues/1173)).
- The legacy extension for Jupyter Notebook <=6 (the Jupytext Menu) has been removed.
**Fixed**
- The bibliography section in Rmd files does not become a code cell anymore ([#1161](https://github.com/mwouts/jupytext/issues/1161))
- Commented code in `active-py` cells is not uncommented anymore ([#1131](https://github.com/mwouts/jupytext/issues/1131))
- The test coverage has been restored ([#1167](https://github.com/mwouts/jupytext/issues/1167), [#1173](https://github.com/mwouts/jupytext/issues/1173))
- We test Jupytext against the pre-release version of JupyterLab, and other dependencies ([#1168](https://github.com/mwouts/jupytext/issues/1168))
1.15.2 (2023-09-16)
-------------------
**Added**
- The JupyterLab extension is now compatible with the [JupyterLab RISE](https://github.com/jupyterlab-contrib/rise) extension. Many thanks to [Frédéric Collonval](https://github.com/fcollonval) for his PR ([#1126](https://github.com/mwouts/jupytext/pull/1126))!
1.15.1 (2023-08-26)
-------------------
**Added**
- We have added a new command line interface `jupytext-config` that you can use to set Jupytext as the default viewer for text notebooks in JupyterLab and Jupyter Notebook 7. Thanks to [Thierry Parmentelat](https://github.com/parmentelat) for this contribution! ([#1094](https://github.com/mwouts/jupytext/pull/1094))
1.15.0 (2023-07-30)
-------------------
**Changed**
- This version comes with a version of the JupyterLab extension that is compatible with JupyterLab 4.x. Many thanks to [Thierry Parmentelat](https://github.com/parmentelat) for his PRs! ([#1092](https://github.com/mwouts/jupytext/pull/1092), [#1109](https://github.com/mwouts/jupytext/pull/1109))
- Pandoc 3.0 is now supported, thanks to [Raniere Silva](https://github.com/rgaiacs) for his PR ([#1099](https://github.com/mwouts/jupytext/pull/1099))
- We have reorganized the documentation and the README.md ([#1031](https://github.com/mwouts/jupytext/issues/1031), [#1073](https://github.com/mwouts/jupytext/issues/1073))
- Invalid `pyproject.toml` files will be ignored by Jupytext in Jupyter ([#1103](https://github.com/mwouts/jupytext/issues/1103))
- We have updated the pre-commit tools
1.14.7 (2023-06-29)
-------------------
**Changed**
- We have updated the GitHub workflows - thanks to Matthew Feickert and to Cristian Le for their help on this subject ([#1037](https://github.com/mwouts/jupytext/issues/1037))
- We have removed the upper bound on `markdown-it-py<3`. Now we test Jupytext with `markdown-it-py` in versions `2.x` and `3.x` on the CI ([#1075](https://github.com/mwouts/jupytext/issues/1075))
**Fixed**
- Notebooks with an empty YAML header work ([#1070](https://github.com/mwouts/jupytext/issues/1070))
- Double quote strings in R Markdown options can contain single quotes ([#1079](https://github.com/mwouts/jupytext/issues/1079))
- The necessary directories are created when paired notebooks are moved to a sub-folder ([#1059](https://github.com/mwouts/jupytext/issues/1059))
- Commented quotes are recognized as such ([#1060](https://github.com/mwouts/jupytext/issues/1060))
- Jupytext can use either `pkg_resources` or `packaging` to parse version numbers ([#1085](https://github.com/mwouts/jupytext/issues/1085))
1.14.6 (2023-06-04)
-------------------
**Changed**
- This version comes with a build requirement `jupyterlab>=3,<4`, as the Jupyterlab
extension for Jupytext is not compatible with JupyterLab 4 yet ([#1054](https://github.com/mwouts/jupytext/issues/1054))
- The JupyterLab extension was released to `npm` in version 1.3.9.
1.14.5 (2023-02-25)
-------------------
**Added**
- Added Stata as a supported language ([#1027](https://github.com/mwouts/jupytext/pull/1027)) - thanks to [Raffaele Mancuso](https://github.com/raffaem) for this contribution
- Added SAS as a supported language ([#1047](https://github.com/mwouts/jupytext/pull/1047)) - thanks to [lawrencet149](https://github.com/lawrencet149) for this contribution
- We have added a series of test to make sure that the main formats support cell tags ([#1024](https://github.com/mwouts/jupytext/issues/1024))
**Fixed**
- When a metadata key is not a valid identifier, a warning is emitted and the metadata is not saved to the text representation ([#1042](https://github.com/mwouts/jupytext/issues/1042))
- The CI was fixed by [Matthew Feickert](https://github.com/matthewfeickert) ([#1035](https://github.com/mwouts/jupytext/pull/1035))
- We now use `concurrency` to cancel previous runs on the same branch/PR ([#1037](https://github.com/mwouts/jupytext/issues/1037))
- We use both `codecov.notify.after_n_builds` and `comment.after_n_builds` to get only the final codecov comment
**Changed**
- Empty tags are not exported to the text notebook anymore ([#960](https://github.com/mwouts/jupytext/issues/960))
- We updated the `yarn.lock` file for the jupyter lab extension to address security vulnerabilities ([#1030](https://github.com/mwouts/jupytext/issues/1030), [#1036](https://github.com/mwouts/jupytext/issues/1036))
- In the pre-commit tests we now use `main` for the main branch
1.14.4 (2022-12-11)
-------------------
**Added**
- Added Wolfram Language as a supported language ([#1014](https://github.com/mwouts/jupytext/issues/1014)) - thanks to [Etienne Dechamps](https://github.com/dechamps) for this contribution
1.14.3 (2022-12-11)
-------------------
**Fixed**
- When the default contents manager is _async_ (i.e. `jupyter_server>=2.0.0`), the Jupyter server extension for Jupytext derives a contents manager from `LargeFileManager` instead, as async contents managers are not supported by Jupytext at the moment ([#1020](https://github.com/mwouts/jupytext/issues/1020))
- We have made adjustments on the CI as flake8 was moved to GitHub, and Python 3.6 is not available anymore on `ubuntu-latest`
1.14.2 (2022-11-12)
-------------------
**Fixed**
- The sample notebooks have been normalized with `nbformat.validator.normalize` ([#1002](https://github.com/mwouts/jupytext/issues/1002)).
- The warnings in the test suite that we cannot fix are filtered using a new `pytest.ini` file
- We updated the `yarn.lock` file for the jupyter lab extension to address security vulnerabilities ([#984](https://github.com/mwouts/jupytext/issues/984), [#1005](https://github.com/mwouts/jupytext/issues/1005), [#1011](https://github.com/mwouts/jupytext/issues/1011))
**Changed**
- The CI uses Python 3.9 rather than 3.7 when testing conda environments
**Added**
- Gnuplot is now supported ([#998](https://github.com/mwouts/jupytext/issues/998)) - thanks to [razimantv](https://github.com/razimantv) for this contribution
- We now test Jupytext against Python 3.6 to 3.11 on the CI
- We have added a test to document how to use the folder and prefix matching when pairing notebooks ([#974](https://github.com/mwouts/jupytext/issues/974))
1.14.1 (2022-07-29)
-------------------
**Fixed**
- The timestamp of a paired notebook is the timestamp of the most recent paired file. This fixes the warning "File Changed"
after reloading the notebook in Jupyter ([#978](https://github.com/mwouts/jupytext/issues/978)).
1.14.0 (2022-07-03)
-------------------
**Changed**
- The Jupytext configuration file has a new option `cm_config_log_level` that defaults to `info_if_changed`.
With that value, the contents manager will log a line regarding the configuration file used only when the
config file is not the same as the one previously used ([#959](https://github.com/mwouts/jupytext/issues/959)) -
many thanks to R.C. Thomas for suggesting this and thoughtfully testing the patch.
- Hidden configuration files like `.jupytext.toml` or `.jupytext.py` are now ignored by Jupytext's contents manager
when `allow_hidden=False` (that option was introduced in `jupyter_server==2.0.0a1`) ([#964](https://github.com/mwouts/jupytext/issues/964)).
- We have changed `jupytext --set-formats` to make it more similar to `jupytext --sync`. Now `--set-formats` will not
override existing paired files anymore ([#969](https://github.com/mwouts/jupytext/issues/969)).
**Added**
- We have added a test `test_pre_commit_hook_sync_with_no_config` that documents how to use the pre-commit hook without
a configuration file ([#967](https://github.com/mwouts/jupytext/issues/967))
1.13.8 (2022-04-04)
-------------------
**Fixed**
- Text-only notebooks are always trusted (as they don't include any output cells) ([#941](https://github.com/mwouts/jupytext/issues/941))
- We made sure that our tests also work in absence of a Python kernel ([#906](https://github.com/mwouts/jupytext/issues/906))
- The coverage of the `tests` folder has been restored at 100%
- Bash commands like `!{cmd}` are now correctly escaped in the `py:percent` format ([#938](https://github.com/mwouts/jupytext/issues/938))
**Added**
- Added Tcl as a supported language ([#930](https://github.com/mwouts/jupytext/issues/930)) - thanks to [shishitao](https://github.com/shishitao) for this contribution
- Added Maxima as a supported language ([#927](https://github.com/mwouts/jupytext/issues/927)) - thanks to [Alberto Lusiani](https://github.com/alusiani) for contributing a sample Maxima notebook.
**Changed**
- The Jupytext contents manager is derived from the `LargeFileManager` imported from `jupyter_server` rather than `notebook` ([#933](https://github.com/mwouts/jupytext/issues/933))
- Allow for markdown-it-py v2 ([#924](https://github.com/mwouts/jupytext/issues/924))
- We have updated the hooks used in the test pre-commits, to fix an issue on the CI ([#940](https://github.com/mwouts/jupytext/issues/940), [#942](https://github.com/mwouts/jupytext/issues/942))
- We updated the `yarn.lock` file for the jupyter lab extension to address security vulnerabilities ([#904](https://github.com/mwouts/jupytext/issues/904), [#925](https://github.com/mwouts/jupytext/issues/925), [#935](https://github.com/mwouts/jupytext/issues/935), [#939](https://github.com/mwouts/jupytext/issues/939))
1.13.7 (2022-02-09)
-------------------
**Fixed**
- The Jupytext CLI only suggest `--update` when the target is an .ipynb file ([#905](https://github.com/mwouts/jupytext/issues/905)) - thanks to [st--](https://github.com/st--) for this contribution
- We made sure that commands like `cat notebook.md | jupytext --execute` work ([#908](https://github.com/mwouts/jupytext/issues/908))
**Added**
- Added Haskell as supported language ([#909](https://github.com/mwouts/jupytext/issues/909)) - thanks to [codeweber](https://github.com/codeweber) for this contribution
**Changed**
- We have updated the pre-commit hooks and in particular we switched to the first stable version of `black==22.1.0`.
- We require `pandoc==2.16.2` for testing. The representation for code cells changed from ` ``` {.python}` to ` ``` python` in that version of Pandoc ([#906](https://github.com/mwouts/jupytext/issues/906)). We don't use `pandoc>=2.17` in tests at the moment because of the introduction of cell ids that cannot be filtered.
- Jupytext will not add anymore a UTF-8 encoding on Python scripts when the notebook contains non-ascii characters ([#907](https://github.com/mwouts/jupytext/issues/907))
- We have added `pyupgrade` to the pre-commit hooks used for developing Jupytext ([#907](https://github.com/mwouts/jupytext/issues/907))
1.13.6 (2022-01-11)
-------------------
**Fixed**
- The `text_representation` metadata of text notebooks is filtered from `.ipynb` files both in `jupytext.write` and in the contents manager for Jupyter ([#900](https://github.com/mwouts/jupytext/issues/900))
**Changed**
- Jupytext will not issue a warning when a format suffix starting with '.', '-' or '_' is passed to the `--to` option ([#901](https://github.com/mwouts/jupytext/issues/901))
1.13.5 (2021-12-27)
-------------------
**Fixed**
- Jupytext will not open a text notebook that is not UTF-8 ([#896](https://github.com/mwouts/jupytext/issues/896))
1.13.4 (2021-12-12)
-------------------
**Changed**
- The test suite filters the warnings that don't belong to Jupytext ([#823](https://github.com/mwouts/jupytext/issues/823))
**Fixed**
- The parsing of notebooks that don't have a YAML header (like `docs/formats.md`) was improved.
- The test suite works with `pytest-randomly` ([#838](https://github.com/mwouts/jupytext/issues/838))
1.13.3 (2021-12-04)
-------------------
**Changed**
- The "Jupytext Notebook" factory that lets the user configure the Notebook viewer as the default for text notebooks accepts more filetypes: "myst", "r-markdown" and "quarto" ([#803](https://github.com/mwouts/jupytext/issues/803))
- Empty MyST Markdown files are valid notebooks ([#883](https://github.com/mwouts/jupytext/issues/883))
- Jupytext also works with `markdown-it-py` v2.0 ([#885](https://github.com/mwouts/jupytext/issues/885))
1.13.2 (2021-11-30)
-------------------
**Changed**
- The extension for JupyterLab benefited from a series of improvements contributed by [Frédéric Collonval](https://github.com/fcollonval):
- A new "Jupytext Notebook" factory offers the option to open text notebooks directly with the notebook view ([#803](https://github.com/mwouts/jupytext/issues/803)). To use it, follow the instructions in the [documentation](https://github.com/mwouts/jupytext/blob/main/docs/index.md#Install).
- The ICommandPalette is optional, for compatibility with RISE within JupyterLab [RISE[#605](https://github.com/mwouts/jupytext/issues/605)](https://github.com/damianavila/RISE/pull/605)
- Added support for translation
- Branch `master` was renamed to `main` (links in the documentation were updated)
1.13.1 (2021-10-07)
-------------------
**Fixed**
- The magic commands in `py:percent` scripts with no explicit format information remain commented over a round trip ([#848](https://github.com/mwouts/jupytext/issues/848))
1.13.0 (2021-09-25)
-------------------
**Added**
- The Jupytext CLI has a new `--diff` command to show the differences between two notebooks (and if you want to see the changes in a file being updated by Jupytext, use `--show-changes`) ([#799](https://github.com/mwouts/jupytext/issues/799))
- Jupyter will show the diff between text and `ipynb` paired notebooks when it cannot open a paired notebook because the `ipynb` version is more recent. Also, if the inputs in the two files are identical then the notebook will open with no error ([#799](https://github.com/mwouts/jupytext/issues/799))
- The `py:percent` format will use raw strings when encoding Markdown cells as string, if they contain backslash characters ([#836](https://github.com/mwouts/jupytext/issues/836))
**Fixed**
- We have upgraded the jupyterlab extension dependencies and especially `ansi-regex` to fix a security vulnerability ([#857](https://github.com/mwouts/jupytext/issues/857))
**Changed**
- The Jupytext configuration file is reloaded only when a notebook is opened, saved, or when a different folder is explored ([#797](https://github.com/mwouts/jupytext/issues/797))
1.12.0 (2021-09-09)
-------------------
**Added**
- Jupytext supports Quarto notebooks (with `.qmd` extension) ([#837](https://github.com/mwouts/jupytext/issues/837))
- Jupytext can be configured through the `pyproject.toml` file. Thanks to Robin Brown for this contribution! ([#828](https://github.com/mwouts/jupytext/issues/828))
- Jupytext now supports OCaml files with `.ml` extension. Thanks to Quentin Fortier for getting this started ([#832](https://github.com/mwouts/jupytext/issues/832))
**Fixed**
- Added more test to make sure that notebooks can be trusted. In practice, notebooks could not be trusted in JupyterLab<3.0.13 because of the absence of cell ids ([#826](https://github.com/mwouts/jupytext/issues/826))
1.11.5 (2021-08-31)
-------------------
**Fixed**
- Fixed typos revealed by `codespell` - thanks to @hectormz for this contribution ([#829](https://github.com/mwouts/jupytext/issues/829))
- We updated the dependencies of the `jupyterlab-jupytext` extension to address several security issues ([#842](https://github.com/mwouts/jupytext/issues/842)) ([#843](https://github.com/mwouts/jupytext/issues/843))
- The Jupytext dev environment (`requirements-dev.txt`) now uses `jupyterlab==3.0.17` rather than `3.0.0` because of another security issue ([#839](https://github.com/mwouts/jupytext/issues/839))
1.11.4 (2021-07-14)
-------------------
**Changed**
- The documentation illustrates how the `cell_markers` option (and the other ones) can be set directly in the `jupytext.toml` config file ([#809](https://github.com/mwouts/jupytext/issues/809)).
- The dependency on `mdit-py-plugins` through `markdown-it-py[plugins]` was made explicit ([#814](https://github.com/mwouts/jupytext/issues/814))
**Fixed**
- System assigns of the form `var = !cmd` are commented out ([#816](https://github.com/mwouts/jupytext/issues/816))
- Fixed an `InconsistentPath` issue with notebooks paired with scripts in a folder. The prefix in the Jupytext formats always use /, while paths might use either / or \ ([#806](https://github.com/mwouts/jupytext/issues/806))
- Tests that cannot succeed are skipped when either the Jupytext folder is not a git repository, when `sphinx-gallery` is too recent, or when `pandoc` is not up-to-date ([#814](https://github.com/mwouts/jupytext/issues/814))
- Removed the mention of '--update' in 'jupytext --pipe' since outputs are preserved already
1.11.3 (2021-06-10)
-------------------
**Changed**
- Jupytext CLI has a new option `--use-source-timestamp` that sets the last modification time of the output file equal to that of the source file (this avoids having to change the timestamp of the source file) ([#784](https://github.com/mwouts/jupytext/issues/784))
- In the pre-commit mode, Jupytext now uses the commit timestamp to determine which file in the pair is the most recent ([#780](https://github.com/mwouts/jupytext/issues/780))
**Fixed**
- Dependencies of the JupyterLab extension have been upgraded to fix a security vulnerability ([#798](https://github.com/mwouts/jupytext/pull/798))
- The `--warn-only` option also applies to pipes. Use this if the pipe may fail, e.g. if you apply `black` on a possibly invalid script ([#781](https://github.com/mwouts/jupytext/issues/781))
- Variables assigned from a magic command are commented out in `py` scripts ([#781](https://github.com/mwouts/jupytext/issues/781))
- Fixed a round-trip issue on notebooks that have None/null in their metadata ([#792](https://github.com/mwouts/jupytext/issues/792))
1.11.2 (2021-05-02)
-------------------
**Changed**
- Jupytext's dependency markdown-it-py is now in v1 ([#769](https://github.com/mwouts/jupytext/issues/769))
- The optional argument `fmt` in `jupytext.reads` now has the default value `None` - thanks to Yuvi Panda ([#763](https://github.com/mwouts/jupytext/issues/763))
**Fixed**
- All text files are opened with an explicit `utf-8` encoding ([#770](https://github.com/mwouts/jupytext/issues/770))
- Previously `--pipe black` was not always putting two blank lines between functions. To fix that we load the internal Jupytext
cell metadata like `lines_to_next_cell` from the text file rather than ipynb ([#761](https://github.com/mwouts/jupytext/issues/761))
- The timestamp of the source file is not updated any more when the destination file is not in the pair ([#765](https://github.com/mwouts/jupytext/issues/765), [#767](https://github.com/mwouts/jupytext/issues/767))
**Added**
- A new test documents when the `ipython3` pygment lexer appears in MyST Markdown files ([#759](https://github.com/mwouts/jupytext/issues/759))
1.11.1 (2021-03-26)
-------------------
**Fixed**
- Format options stored in the notebook itself are now taken into account (Fixes [#757](https://github.com/mwouts/jupytext/issues/757))
1.11.0 (2021-03-18)
-------------------
**Fixed**
- The `jupytext.toml` config file can now be used together with the `jupytext` pre-commit hook ([#752](https://github.com/mwouts/jupytext/issues/752))
- The `notebook_extensions` option of the `jupytext.toml` file now works ([#746](https://github.com/mwouts/jupytext/issues/746))
**Changed**
- The options in `jupytext.toml` where renamed to match the `jupytext` metadata in the text notebooks. One should now use `formats` rather than `default_jupytext_formats` and `notebook_metadata_filter` rather than `default_notebook_metadata_filter` ([#753](https://github.com/mwouts/jupytext/issues/753))
1.10.3 (2021-03-07)
-------------------
**Fixed**
- We have updated `marked`, an indirect dependency of the `jupyterlab-jupytext` extension, to fix a moderate vulnerability ([#750](https://github.com/mwouts/jupytext/issues/750)).
- We use non-random cell ids in the tests to avoid test failures due to duplicate cell ids ([#747](https://github.com/mwouts/jupytext/issues/747))
1.10.2 (2021-02-17)
-------------------
**Fixed**
- We have adjusted the `MANIFEST.in` file to exclude the `node_modules` but still include the JupyterLab extension that was missing in the `.tar.gz` (and conda) package in v1.10.1. Many thanks to Martin Renou for providing the fix at ([#741](https://github.com/mwouts/jupytext/issues/741))
1.10.1 (2021-02-11)
-------------------
**Added**
- The recursive glob pattern `**/*.ipynb` is now supported by Jupytext - Thanks to Banst for this contribution ([#731](https://github.com/mwouts/jupytext/issues/731))
- Sage notebooks are supported. They can be converted to `.sage` and `.md` files and back. Thanks to Lars Franke for suggesting this! ([#727](https://github.com/mwouts/jupytext/issues/727))
- Jupytext is also accessible with `python -m jupytext`. Thanks to Matthew Brett for his PR! ([#739](https://github.com/mwouts/jupytext/issues/739))
**Changed**
- We have tested Jupytext with the new cell ids introduced in `nbformat>=5.1.0`. Cell ids are preserved by the `--sync` and `--update` command. So we removed the constraint on the version of `nbformat` ([#735](https://github.com/mwouts/jupytext/issues/735)).
**Fixed**
- We filtered out the `node_modules` folder from the `.tar.gz` package for Jupytext ([#730](https://github.com/mwouts/jupytext/issues/730))
1.10.0 (2021-02-04)
-------------------
**Added**
- Jupytext has a pre-commit hook! Many thanks to John Paton and Aaron Gokaslan for making this happen ([#698](https://github.com/mwouts/jupytext/issues/698))
- Jupytext CLI will not rewrite files that don't change ([#698](https://github.com/mwouts/jupytext/issues/698)).
- If you want to see the diff for changed files, use the new `--diff` option ([#722](https://github.com/mwouts/jupytext/issues/722))
- We have added `isort` and `autoflake8` to the `pre-commit` configuration file used for developing the Jupytext project ([#709](https://github.com/mwouts/jupytext/issues/709))
- We made sure that `py:percent` scripts end with exactly one blank line ([#682](https://github.com/mwouts/jupytext/issues/682))
- We checked that Jupytext works well with symbolic links to folders (not files!) ([#696](https://github.com/mwouts/jupytext/issues/696))
**Changed**
- Jupytext does not work properly with the new cell ids of the version 4.5 of `nbformat>=5.1.0` yet, so we added the requirement `nbformat<=5.0.8` ([#715](https://github.com/mwouts/jupytext/issues/715))
- Jupytext will issue an informative error or warning on notebooks in a version of nbformat that is not known to be supported ([#681](https://github.com/mwouts/jupytext/issues/681), [#715](https://github.com/mwouts/jupytext/issues/715))
**Fixed**
- Code cells that contain triple backticks (or more) are now encapsulated with four backticks (or more) in the Markdown and MyST Markdown formats. The version number for the Markdown format was increased to 1.3, and the version number for the MyST Markdown format was increased to 0.13 ([#712](https://github.com/mwouts/jupytext/issues/712))
- Indented magic commands are supported ([#694](https://github.com/mwouts/jupytext/issues/694))
1.9.1 (2021-01-06)
------------------
**Fixed**
- Include the lab extension that was missing in the conda package ([#703](https://github.com/mwouts/jupytext/pull/703)).
1.9.0 (2021-01-05)
------------------
**Changed**
- The Jupytext extension for JupyterLab is compatible with JupyterLab 3.0, thanks to Martin Renou's awesome contribution ([#683](https://github.com/mwouts/jupytext/pull/683)).
1.8.2 (2021-01-04)
------------------
**Changed**
- Jupytext 1.8.2 depends on `python>=3.6`. The last version of Jupytext explicitly tested with Python 2.7 and 3.5 was Jupytext 1.7.1 ([#697](https://github.com/mwouts/jupytext/issues/697)).
1.8.1 (2021-01-03)
------------------
**Changed**
- The dependency on `markdown-it-py` is conditional on `python>=3.6` ([#697](https://github.com/mwouts/jupytext/issues/697))
1.8.0 (2020-12-22)
------------------
**Changed**
- Removed support for Python 2.7 and 3.5, a preliminary step towards a JupyterLab 3.0-compatible extension ([#683](https://github.com/mwouts/jupytext/issues/683))
- The MyST Markdown format uses `markdown-it-py~=0.6.0` ([#692](https://github.com/mwouts/jupytext/issues/692))
1.7.1 (2020-11-16)
------------------
**Fixed**
- Text notebooks have the same format and mimetype as ipynb notebooks. This fixes the _File Load Error - content.indexOf is not a function_ error on text notebooks ([#659](https://github.com/mwouts/jupytext/issues/659))
1.7.0 (2020-11-14)
------------------
**Changed**
- Jupytext's contents manager uses the parent CM's `get` and `save` methods to read and save text files, and explicitly calls `jupytext.reads` and `jupytext.writes` to do the conversion. We don't use `mock` nor internal parent methods any more. Thanks to Max Klein for helping making this work! ([#634](https://github.com/mwouts/jupytext/issues/634), [#635](https://github.com/mwouts/jupytext/issues/635))
- Thanks to the above, Jupytext can work on top of contents manager that don't derive from `FileContentsManager`, and in particular it works with `jupyterfs` ([#618](https://github.com/mwouts/jupytext/issues/618))
- The documentation was reorganized. `README.md` was simplified and now includes many links to the documentation.
- The documentation now uses `myst_parser` rather than `recommonmark`. And we use `conda` on RTD ([#650](https://github.com/mwouts/jupytext/issues/650), [#652](https://github.com/mwouts/jupytext/issues/652))
- The `readf` and `writef` functions were dropped (they had been deprecated in favor of `read` and `write` in June 2019, v1.2.0)
- The description & dependencies of the JupyterLab extension were updated ([#654](https://github.com/mwouts/jupytext/issues/654))
- The `--set-kernel -` command, on a Python notebook, gives an explicit error when no kernel is not found that matches the current Python executable.
- All the GitHub workflow files were concatenated into a unique file, and we have added an `pypi-publish` step to automatically publish the package on PyPi when new releases are created.
- The `CHANGELOG.md` file was moved under `docs` to better expose the history of changes.
**Added**
- Configuration errors are reported in the console and/or in Jupyter ([#613](https://github.com/mwouts/jupytext/issues/613))
- Jupytext's Contents Manager internal errors are logged on the console, and trigger an HTTP Error 500 ([#638](https://github.com/mwouts/jupytext/issues/638))
- The GitHub actions run on both push events and pull requests, and duplicate jobs are skipped ([#605](https://github.com/mwouts/jupytext/issues/605))
- Jupytext has a `tox.ini` file, thanks to Chris Sewell ([#605](https://github.com/mwouts/jupytext/issues/605))
- Jupytext is tested against Python 3.9
- The `execution` cell metadata is now filtered by default ([#656](https://github.com/mwouts/jupytext/issues/656))
**Fixed**
- Optional dependency on `sphinx-gallery` frozen to version `~=0.7.0` ([#614](https://github.com/mwouts/jupytext/issues/614))
- Codecov/patch reports should be OK now ([#639](https://github.com/mwouts/jupytext/issues/639))
- Jupytext tests work on non-English locales ([#636](https://github.com/mwouts/jupytext/issues/636))
- Cell metadata that are already present in text notebook can be filtered out using a config file ([#656](https://github.com/mwouts/jupytext/issues/656))
- Optional cell attributes like attachments are preserved ([#671](https://github.com/mwouts/jupytext/issues/671))
1.6.0 (2020-09-01)
------------------
**Added**
- New option `hide_notebook_metadata` to encapsulate the notebook metadata in an HTML comment ([#527](https://github.com/mwouts/jupytext/issues/527))
- New option `root_level_metadata_as_raw_cell`. Set it to `False` if you don't want to see root level metadata
of R Markdown notebooks as a raw cell in Jupyter ([#415](https://github.com/mwouts/jupytext/issues/415))
- New option `doxygen_equation_markers` to translate Markdown equations into Doxygen equations ([#517](https://github.com/mwouts/jupytext/issues/517))
- New option `custom_cell_magics` to comment out cells starting with user-specific cell magics ([#513](https://github.com/mwouts/jupytext/issues/513))
- Documented how to use `isort` on notebooks ([#553](https://github.com/mwouts/jupytext/issues/553))
- `jupytext notebook.ipynb --to filename.py` will warn that `--to` is used in place of `--output`.
- `jupytext --set-formats filename.py` will suggest to use `--sync` instead of `--set-formats` ([#544](https://github.com/mwouts/jupytext/issues/544))
- Warn if 'Include Metadata' is off when saving text files in Jupyter ([#561](https://github.com/mwouts/jupytext/issues/561))
- Test that notebooks paired through a configuration file are left unmodified ([#598](https://github.com/mwouts/jupytext/issues/598))
- Test that metadata filters in the configuration files are taken into account when using `jupytext --to` ([#543](https://github.com/mwouts/jupytext/issues/543))
- New argument `--run-path` to execute the notebooks at the desired location ([#595](https://github.com/mwouts/jupytext/issues/595))
- Activated GitHub code scanning alerts
**Changed**
- Jupytext now depends on `markdown-it-py` (Python 3.6 and above) and always features the MyST-Markdown format,
thanks to Chris Sewell ([#591](https://github.com/mwouts/jupytext/issues/591))
- The `md:myst` and `md:pandoc` are always included in the Jupytext formats, and an informative runtime
error will occur if the required dependencies, resp. `markdown-it-py` and `pandoc`, are not installed. ([#556](https://github.com/mwouts/jupytext/issues/556))
- The `# %%` cell marker has the same indentation as the first line in the cell ([#562](https://github.com/mwouts/jupytext/issues/562))
- Jupytext is now installed from source on MyBinder to avoid cache issues ([#567](https://github.com/mwouts/jupytext/issues/567))
- The tests that execute a notebook are now skipped on Windows to avoid timeout issues ([#489](https://github.com/mwouts/jupytext/issues/489))
**Fixed**
- Only scripts can have an encoding comment, not Markdown or R Markdown files ([#576](https://github.com/mwouts/jupytext/issues/576))
- Spaces in `--pipe` commands are supported ([#562](https://github.com/mwouts/jupytext/issues/562))
- Bash commands starting with special characters are now correctly detected, thanks to Aaron Gokaslan ([#587](https://github.com/mwouts/jupytext/issues/587))
- MyST Markdown files are recognized as such even if MyST-Markdown is not available ([#556](https://github.com/mwouts/jupytext/issues/556))
- Build JupyterLab with `dev-build=False` and `minimize=False` on mybinder to avoid build errors
- Configured coverage targets in `codecov.yml`
1.5.2 (2020-07-21)
------------------
**Changed**
- The documentation uses the Alabaster theme
**Fixed**
- Preserve indentation when commenting out magic commands ([#437](https://github.com/mwouts/jupytext/issues/437))
- Fixed MyBinder - `jupytext.py` is not a configuration file ([#559](https://github.com/mwouts/jupytext/issues/559), [#567](https://github.com/mwouts/jupytext/issues/567))
1.5.1 (2020-07-05)
------------------
**Fixed**
- Added `toml` as a dependency ([#552](https://github.com/mwouts/jupytext/issues/552)).
- Filtered out `__pycache__` and `.pyc` files from the pip package.
- Fixed coverage upload by adding `coverage` as a dependency to the conda CI workflow.
- Fixed the conda CI / Python 2.7 job by explicitly installing `backports.functools_lru_cache` ([#554](https://github.com/mwouts/jupytext/issues/554)).
1.5.0 (2020-06-07)
------------------
**Added**
- Jupytext can use a local or global [configuration file](https://github.com/mwouts/jupytext/blob/main/docs/config.md) ([#508](https://github.com/mwouts/jupytext/issues/508))
- Jupytext can pair notebooks in trees. Use e.g. `notebooks///ipynb,scripts///py:percent` if you want to replicate the tree of notebooks under `notebooks` in a folder named `scripts` ([#424](https://github.com/mwouts/jupytext/issues/424))
- The extension for Jupyter Notebook has a _New Text Notebook_ menu that creates text-only notebooks ([#443](https://github.com/mwouts/jupytext/issues/443))
- Groovy and Java are now supported, thanks to Przemek Wesołek ([#500](https://github.com/mwouts/jupytext/issues/500))
- The Coconut language is also supported, thanks to Thurston Sexton ([#532](https://github.com/mwouts/jupytext/issues/532))
- Resource files with `.resource` extension from the Robot Framework are supported, thanks to Hiski Valli ([#535](https://github.com/mwouts/jupytext/issues/535))
- Jupytext is tested in `pip` and `conda` environments, on Linux, Mac OS and Windows, using Github actions ([#487](https://github.com/mwouts/jupytext/issues/487))
- Jupytext uses pre-commit checks and automatic reformatting with `pre-commit`, `black` and `flake8` ([#483](https://github.com/mwouts/jupytext/issues/483))
- Documentation improvements:
- Mention that the YAML header can be created with either `--set-kernel`, `--set-formats`, or both ([#485](https://github.com/mwouts/jupytext/issues/485))
- Mention that one should use double quotes, not single quotes, around `jupytext --check` commands like `"pytest {}"` on Windows ([#475](https://github.com/mwouts/jupytext/issues/475))
- Improved error message when a file is in a version that can't be read by Jupytext ([#531](https://github.com/mwouts/jupytext/issues/531))
**Fixed**
- Skip the `jupytext --execute` tests when the warning _Timeout waiting for IOPub output_ occurs, which is the case intermittently on Windows ([#489](https://github.com/mwouts/jupytext/issues/489))
- Fixed wrong paired paths when syncing with the --pre-commit flag ([#506](https://github.com/mwouts/jupytext/issues/506))
1.4.2 (2020-04-05)
------------------
**Added**
- Added an example with custom notebook metadata ([#469](https://github.com/mwouts/jupytext/issues/469))
- Added an example with custom cell tags ([#478](https://github.com/mwouts/jupytext/issues/478))
**Changed**
- The outputs from the `.ipynb` file are matched with the input cells from the text file with less strict rules. In this version, a search and replace on the text file will not remove the outputs any more ([#464](https://github.com/mwouts/jupytext/issues/464)).
- Update parsing of myst notebooks to new (markdown-it based) parser (please upgrade to `myst-parser` to version `~0.8`) ([#473](https://github.com/mwouts/jupytext/issues/473))
- Use `os.path.samefile` when searching for the kernel that corresponds to the current environment (`--set-kernel -`)
**Fixed**
- Fixed the CLI example for not commenting out magic commands: `--opt comment_magics=false`. In addition, most of the `jupytext` commands in `using-cli.md` are now tested! ([#465](https://github.com/mwouts/jupytext/issues/465))
- `jupytext.read` and `jupytext.write` now give more meaningful errors when the format information is incorrect ([#462](https://github.com/mwouts/jupytext/issues/462))
- Multiline comments starting or ending with quadruple quotes should not cause issues anymore ([#460](https://github.com/mwouts/jupytext/issues/460))
- Fixed active cells in the py:percent format ([#477](https://github.com/mwouts/jupytext/issues/477))
1.4.1 (2020-03-19)
------------------
**Added**
- Script of script (SoS) notebooks are now supported. Thanks to Thomas Pernet-coudrier for contributing the sample notebook ([#453](https://github.com/mwouts/jupytext/issues/453)).
- New MyST Markdown format (`md:myst`), developed by the [ExecutableBookProject](https://github.com/ExecutableBookProject) team. Read more about the MyST Markdown format in the [documentation](https://jupytext.readthedocs.io/en/latest/formats.html#myst-markdown). And many thanks to Chris Sewell for contributing the actual implementation! ([#447](https://github.com/mwouts/jupytext/issues/447) [#456](https://github.com/mwouts/jupytext/issues/456) [#458](https://github.com/mwouts/jupytext/issues/458))
**Fixed**
- When using `jupytext --pipe cmd`, the output of `cmd` should not appear in the terminal ([#432](https://github.com/mwouts/jupytext/issues/432))
1.4.0 (2020-03-09)
------------------
**Changed**
- The new jupyterlab extension (in version 1.2.0) is compatible with JupyterLab 2.0. Many thanks to Jean Helie! ([#449](https://github.com/mwouts/jupytext/issues/449))
- It is not compatible with JupyterLab 1.x anymore. If you wish, you can install manually the previous version of the extension with `jupyter labextension install jupyterlab-jupytext@1.1.1`.
**Fixed**
- Display the output/errors of command executed with `jupytext --pipe` or `jupytext --check` ([#432](https://github.com/mwouts/jupytext/issues/432))
1.3.5 (2020-03-08)
------------------
**Fixed**
- Removed leading slash in notebook paths ([#444](https://github.com/mwouts/jupytext/issues/444))
- Fixed `jupytext --set-formats` when using formats with prefix and/or suffix ([#450](https://github.com/mwouts/jupytext/issues/450))
1.3.4 (2020-02-18)
------------------
**Added**
- C# and F# Jupyter notebooks are now supported ([#427](https://github.com/mwouts/jupytext/issues/427), [#429](https://github.com/mwouts/jupytext/issues/429))
**Fixed**
- `jupytext --to script *.ipynb` now computes the script extension for each notebook ([#428](https://github.com/mwouts/jupytext/issues/428))
- Fix shebang handling for languages with non-# comments, by Jonas Bushart ([#434](https://github.com/mwouts/jupytext/issues/434))
- Indented bash commands are now commented out ([#437](https://github.com/mwouts/jupytext/issues/437))
- The main formats are documented in `jupytext --help` ([#426](https://github.com/mwouts/jupytext/issues/426), [#433](https://github.com/mwouts/jupytext/issues/433))
1.3.3 (2020-01-27)
------------------
**Added**
- Jupytext has a logo! Many thanks to Kyle Kelley for contributing the actual logo ([#423](https://github.com/mwouts/jupytext/issues/423)), and to Chris Holdgraf for suggesting this ([#260](https://github.com/mwouts/jupytext/issues/260)).
- Nested metadata filtering is now supported! You can use this to rid of `jupytext_version` if you wish ([#416](https://github.com/mwouts/jupytext/issues/416)).
**Fixed**
- Code cells in the Markdown format can contain triple backticks inside multiline strings ([#419](https://github.com/mwouts/jupytext/issues/419)).
- Changes in the YAML header when running `jupytext --test` on text files are ignored ([#414](https://github.com/mwouts/jupytext/issues/414)).
1.3.2 (2020-01-08)
------------------
**Fixed**
- The `--pre-commit` mode now ignores non-notebook files in the index ([#338](https://github.com/mwouts/jupytext/issues/338)).
- `nbformat_minor` is taken from the notebook with outputs When inputs and outputs are merged.
1.3.1 (2019-12-26)
------------------
**Added**
- New `nomarker` format in the Jupyter Notebook and JupyterLab extensions ([#397](https://github.com/mwouts/jupytext/issues/397))
**Changed**
- The `normarker` format replaces the one previously named `bare`.
**Fixed**
- Multiline strings are now accepted in the YAML header ([#404](https://github.com/mwouts/jupytext/issues/404)). Fix contributed by ZHUO Qiang ([#405](https://github.com/mwouts/jupytext/issues/405)).
- Fixed the instructions on how to use multiline comments for all Markdown cells in the py:percent format, by ZHUO Qiang ([#403](https://github.com/mwouts/jupytext/issues/403)).
- Added `cd` to the list of magic commands.
- Do not read paired files when `--set-formats` is being used (fixes [#399](https://github.com/mwouts/jupytext/issues/399)).
1.3.0 (2019-11-23)
------------------
See also [What's new in Jupytext 1.3?](https://gist.github.com/mwouts/724efe5e00654fc2145a4c916796e071)
**Added**
- Pairing a notebook to both `.md` and `.py` is now supported. Input cells are loaded from the most recent text representation ([#290](https://github.com/mwouts/jupytext/issues/290))
- Both the Jupyter Notebook and the JupyterLab extension for Jupytext were updated ([#290](https://github.com/mwouts/jupytext/issues/290))
- Raw cells are now encoded using HTML comments (`` and ``) in Markdown files ([#321](https://github.com/mwouts/jupytext/issues/321))
- Markdown cells can be delimited with any of ``, `` or `` ([#344](https://github.com/mwouts/jupytext/issues/344))
- Code blocks from Markdown files, when they don't have an explicit language, appear in Markdown cells in Jupyter ([#321](https://github.com/mwouts/jupytext/issues/321))
- Code blocks with an explicit language and a `.noeval` attribute are inactive in Jupyter ([#347](https://github.com/mwouts/jupytext/issues/347))
- Markdown and raw cells can be quoted with triple quotes in the `py:percent` format. And Markdown cells can start with just `# %% [md]` ([#305](https://github.com/mwouts/jupytext/issues/305))
- The light format uses `# + [markdown]` rather than the previous `cell_type` metadata to identify markdown cells with metadata ([#356](https://github.com/mwouts/jupytext/issues/356))
- Explicit Markdown cells in the light format `# + [markdown]` can use triple quotes ([#356](https://github.com/mwouts/jupytext/issues/356))
- IPython magic help commands like `float?` are classified as magics, and thus commented in Python scripts ([#297](https://github.com/mwouts/jupytext/issues/297))
- Cell metadata can be encoded as either `key=value` (the new default) or in JSON. An automatic option `cell_metadata_json` should help minimize the impact on existing files ([#344](https://github.com/mwouts/jupytext/issues/344))
- R Markdown hidden inputs, outputs, or cells are now mapped to the corresponding Jupyter Book tags by default ([#337](https://github.com/mwouts/jupytext/issues/337))
- The notebook metadata filter is automatically updated when extra keys are added to the YAML header ([#376](https://github.com/mwouts/jupytext/issues/376))
- The Jupyter Notebook extension for Jupytext is compatible with Jupyter Notebook 6.0 ([#346](https://github.com/mwouts/jupytext/issues/346))
- `jupytext notebook.py --to ipynb` updates the timestamp of `notebook.py` so that the paired notebook still works in Jupyter ([#335](https://github.com/mwouts/jupytext/issues/335), [#254](https://github.com/mwouts/jupytext/issues/254))
- `jupytext --check pytest notebook.ipynb` can be used to run test functions in a notebook ([#286](https://github.com/mwouts/jupytext/issues/286))
- `jupytext --check` and `jupytext --pipe` can run commands that only operate on files: when `{}` is found in the text of the command, `jupytext` saves the text representation of the notebook in a temp file, and replaces `{}` with the name of that file before executing the command. ([#286](https://github.com/mwouts/jupytext/issues/286))
- Documented how to sync notebooks in a pre-commit hook ([#338](https://github.com/mwouts/jupytext/issues/338))
- Added support for Rust/Evxcr, by Jonas Bushart ([#351](https://github.com/mwouts/jupytext/issues/351))
- Added support for [Robot Framework](https://robots-from-jupyter.github.io/), by Asko Soukka ([#378](https://github.com/mwouts/jupytext/issues/378))
- Added support for PowerShell ([#349](https://github.com/mwouts/jupytext/issues/349))
**Changed**
- Use `CHANGELOG.md` rather than `HISTORY.rst`
**Fixed**
- Commands like `cat = x` are not magic commands, so they are not commented any more ([#339](https://github.com/mwouts/jupytext/issues/339))
- Fixed an inconsistent round trip (code cell with `"cat"` being converted to a markdown cell) in the `py:light` format ([#339](https://github.com/mwouts/jupytext/issues/339))
- `jupytext --test textfile.ext` now really compares the text file to its round trip (rather than the corresponding notebook) ([#339](https://github.com/mwouts/jupytext/issues/339))
- Markdown cells that contain code are now preserved in a round trip through the Markdown and R Markdown formats ([#361](https://github.com/mwouts/jupytext/issues/361))
- Code cells with a `%%python3` cell magic are now preserved in a round trip through the Markdown format ([#365](https://github.com/mwouts/jupytext/issues/365))
- `jupytext --execute` runs the notebook in its folder ([#382](https://github.com/mwouts/jupytext/issues/382))
- Strings in the metadata of code cells are quoted in the Rmd representation. And we escape R code in chunk options with `#R_CODE#` ([#383](https://github.com/mwouts/jupytext/issues/383))
1.2.4 (2019-09-19)
------------------
**Added**
- The documentation includes a mention on how to set metadata filters at the command line ([#330](https://github.com/mwouts/jupytext/issues/330))
- Jupytext will not catch any error when the flag `--warn-only` is not set ([#327](https://github.com/mwouts/jupytext/issues/327))
**Fixed**
- Now the flag `--warn-only` catches every possible error ([#263](https://github.com/mwouts/jupytext/issues/263))
- `.md` and `.markdown` files are treated identically ([#325](https://github.com/mwouts/jupytext/issues/325))
- Fixed `--set-kernel` when using pipes ([#326](https://github.com/mwouts/jupytext/issues/326))
- Fixed utf-8 encoding on stdout on Python 2 ([#331](https://github.com/mwouts/jupytext/issues/331))
1.2.3 (2019-09-02)
------------------
**Fixed**
- Dependency on `setuptools` in `pandoc.py` made optional to fix the build of the conda package ([#310](https://github.com/mwouts/jupytext/issues/310), [#323](https://github.com/mwouts/jupytext/issues/323))
1.2.2 (2019-09-01)
------------------
**Added**
- Documentation includes a section on how to use Jupytext as a Python library ([#317](https://github.com/mwouts/jupytext/issues/317))
- Mention of the server extension in the documentation ([#304](https://github.com/mwouts/jupytext/issues/304))
- Text notebooks can be tested with `jupytext --execute notebook.md` ([#303](https://github.com/mwouts/jupytext/issues/303))
- The default value of `as_version` in `jupytext.read` is `nbformat.NO_CONVERT`, as for `nbformat.read`
- Jupytext tests are now included in sdist ([#310](https://github.com/mwouts/jupytext/issues/310))
**Fixed**
- Fixed the usability of the `fmt` argument in `jupytext.read` ([#312](https://github.com/mwouts/jupytext/issues/312))
- Fixed the download notebook error when `c.notebook_extensions` has a custom value ([#318](https://github.com/mwouts/jupytext/issues/318))
- String delimiters in commented text are now ignored ([#307](https://github.com/mwouts/jupytext/issues/307))
1.2.1 (2019-07-20)
------------------
**Added**
- Added documentation on how to use Jupytext with JupyterLab 0.35 ([#299](https://github.com/mwouts/jupytext/issues/299))
- and on using Jupytext with the pre-commit package manager ([#292](https://github.com/mwouts/jupytext/issues/292))
- The `read` and `write` functions are easier to use ([#292](https://github.com/mwouts/jupytext/issues/292))
**Fixed**
- Jupytext now ships the `jupyterlab-jupytext` extension in version 1.0.2. The version 1.0.1 erroneously introduces a `target_formats` metadata in the jupytext section, instead of `formats`, and works only after two clicks.
1.2.0 (2019-07-18)
------------------
**Added**
- New `--execute` option in Jupytext CLI ([#231](https://github.com/mwouts/jupytext/issues/231))
- The `--set-formats` option in Jupytext CLI also triggers `--sync`, allowing shorter commands.
- `jupytext`'s `read` and `write` functions can be used as drop-in replacements for `nbformat`'s ones ([#262](https://github.com/mwouts/jupytext/issues/262)).
- `jupytext --sync` will now skip unpaired notebooks ([#281](https://github.com/mwouts/jupytext/issues/281)).
- The JupyterLab extension was updated. It now works on text files ([#213](https://github.com/mwouts/jupytext/issues/213)) and has a new option to include
(or not) the metadata in the text representation of the notebook.
- Jupytext's contents manager class is derived dynamically from the default CM class for compatibility with
`jupyter_server` ([#270](https://github.com/mwouts/jupytext/issues/270))
- Removed dependency on `mock` and `testfixtures`, thanks to Jean-Sebastien Dieu ([#279](https://github.com/mwouts/jupytext/issues/279))
- Added support for `.markdown` extension ([#288](https://github.com/mwouts/jupytext/issues/288))
**Fixed**
- The `jupyterlab-jupytext` extension shipped with the python package is in version 1.0.1, and is compatible only
with JupyterLab >= 1.0. If you use an earlier version of JupyterLab, please install the version 0.19 of the extension
with `jupyter labextension install jupyterlab-jupytext@0.19` ([#276](https://github.com/mwouts/jupytext/issues/276), [#278](https://github.com/mwouts/jupytext/issues/278))
- Text files can be unpaired ([#289](https://github.com/mwouts/jupytext/issues/289))
1.1.7 (2019-06-23)
------------------
**Added**
- Added support for Scala notebook, by Tobias Frischholz ([#253](https://github.com/mwouts/jupytext/issues/253))
- Added a getting started notebook for jupytext (and Binder), by Chris Holdgraf ([#257](https://github.com/mwouts/jupytext/issues/257))
- The Markdown and R Markdown representations are now tested for all the languages
- The Jupytext notebook extension also works when the notebook is a text file ([#213](https://github.com/mwouts/jupytext/issues/213))
**Fixed**
- The Jupytext Menu in Jupyter Notebook is now compatible with `jupyter_nbextensions_configurator` ([#178](https://github.com/mwouts/jupytext/issues/178))
- Entries in the Jupytext menu are updated when the menu is hovered on ([#248](https://github.com/mwouts/jupytext/issues/248))
- Fixed link to `.md` files in the documentation ([#255](https://github.com/mwouts/jupytext/issues/255))
1.1.6 (2019-06-11)
------------------
**Added**
- Jupytext now supports Javascript and Typescript, thanks to Hatem Hosny ([#250](https://github.com/mwouts/jupytext/issues/250))
- Jupytext works with Python 3.8 as well
**Fixed**
- Fix global `auto` pairing ([#249](https://github.com/mwouts/jupytext/issues/249))
1.1.5 (2019-06-06)
------------------
**Fixed**
- Fixed implicit dependency on `jupyter_client` ([#245](https://github.com/mwouts/jupytext/issues/245))
1.1.4 (2019-06-05)
------------------
**Added**
- New argument `--set-kernel` in Jupytext command line ([#230](https://github.com/mwouts/jupytext/issues/230))
- Jupytext now accepts `--to script` or `--to auto` ([#240](https://github.com/mwouts/jupytext/issues/240))
- Jupytext now has a real Sphinx documentation on [readthedocs](https://jupytext.readthedocs.io/en/latest), thanks to Chris Holdgraf ([#237](https://github.com/mwouts/jupytext/issues/237))
**Fixed**
- Invalid notebooks may cause a warning, but not a fatal error ([#234](https://github.com/mwouts/jupytext/issues/234))
- Jupyter server extension leaves the contents manager unchanged if it is a sub-class of Jupytext's CM ([#236](https://github.com/mwouts/jupytext/issues/236))
- Fixed format inference when metadata is present but not format information ([#239](https://github.com/mwouts/jupytext/issues/239))
- Preserve executable and encoding information in scripts with metadata ([#241](https://github.com/mwouts/jupytext/issues/241))
1.1.3 (2019-05-22)
------------------
**Added**
- Support for IDL notebooks and .pro scripts ([#232](https://github.com/mwouts/jupytext/issues/232))
1.1.2 (2019-05-16)
------------------
**Added**
- Jupytext's content manager has a new `notebook_extensions` option ([#224](https://github.com/mwouts/jupytext/issues/224), [#183](https://github.com/mwouts/jupytext/issues/183))
- Cells can be made inactive in scripts with the `active-ipynb` cell tag ([#226](https://github.com/mwouts/jupytext/issues/226))
**Fixed**
- Directories ending in .jl (or .ipynb) are not notebooks ([#228](https://github.com/mwouts/jupytext/issues/228))
- Empty notebooks have no language ([#227](https://github.com/mwouts/jupytext/issues/227))
1.1.1 (2019-04-16)
------------------
**Added**
- Jupytext server extension leaves the contents manager unchanged when it is already a subclass of TextFileContentsManager ([#218](https://github.com/mwouts/jupytext/issues/218))
- The base class for TextFileContentsManager defaults to FileContentsManager when LargeFileManager is not available ([#217](https://github.com/mwouts/jupytext/issues/217))
1.1.0 (2019-04-14)
------------------
**Added**
- Markdown and R Markdown formats now support metadata ([#66](https://github.com/mwouts/jupytext/issues/66), [#111](https://github.com/mwouts/jupytext/issues/111), [#188](https://github.com/mwouts/jupytext/issues/188))
- The `light` format for Scripts can use custom cell markers, e.g. Vim or VScode/PyCharm folding markers ([#199](https://github.com/mwouts/jupytext/issues/199))
- Pandoc's Markdown format for Jupyter notebooks is available in Jupytext as `md:pandoc` ([#208](https://github.com/mwouts/jupytext/issues/208))
**Fixed**
- Jupytext's contents manager is now based on `LargeFileManager` to allow large file uploads ([#210](https://github.com/mwouts/jupytext/issues/210))
- YAML header parsed with yaml.safe_load rather than yaml.load ([#215](https://github.com/mwouts/jupytext/issues/215))
- IPython line magic can be split across lines ([#209](https://github.com/mwouts/jupytext/issues/209))
- `jupytext --to py` rather than `--to python` in the README ([#216](https://github.com/mwouts/jupytext/issues/216))
1.0.5 (2019-03-26)
------------------
**Fixed**
- Fix the error 'notebook file has changed on disk' when saving large notebooks ([#207](https://github.com/mwouts/jupytext/issues/207))
1.0.4 (2019-03-20)
------------------
**Added**
- Wildcard are now supported on Windows ([#202](https://github.com/mwouts/jupytext/issues/202))
- Trusted notebooks remain trusted when inputs cells are modified ([#203](https://github.com/mwouts/jupytext/issues/203))
**Fixed**
- Pre-commit mode adds the result of conversion to the commit ([#200](https://github.com/mwouts/jupytext/issues/200))
1.0.3 (2019-03-13)
------------------
**Added**
- Matlab and Octave notebooks and scripts are now supported ([#197](https://github.com/mwouts/jupytext/issues/197))
**Fixed**
- `notebook_metadata_filter = "all"` now works ([#196](https://github.com/mwouts/jupytext/issues/196))
- Default pairing in subfolders fixed in JupyterLab ([#180](https://github.com/mwouts/jupytext/issues/180))
1.0.2 (2019-02-27)
------------------
**Added**
- Rename notebooks in pairs in the tree view ([#190](https://github.com/mwouts/jupytext/issues/190))
- Associate `.scm` file extension with Scheme scripts ([#192](https://github.com/mwouts/jupytext/issues/192))
- Added support for Clojure, by bzinberg ([#193](https://github.com/mwouts/jupytext/issues/193))
**Fixed**
- Allow spaces between `?` or `!` and python or bash command ([#189](https://github.com/mwouts/jupytext/issues/189))
1.0.1 (2019-02-23)
------------------
**Fixed**
- Exclude tests in package deployment ([#184](https://github.com/mwouts/jupytext/issues/184))
- Jupytext's serverextension only runs selected init steps ([#185](https://github.com/mwouts/jupytext/issues/185))
- Added an additional test for magic arguments ([#111](https://github.com/mwouts/jupytext/issues/111))
1.0.0 (2019-02-19)
------------------
**Added**
- Jupytext now includes a Jupyter Notebook extension and a JupyterLab extension ([#86](https://github.com/mwouts/jupytext/issues/86)).
- Jupytext command line has more arguments: `--paired-paths` to list the paths for the paired representations of the notebook, and `--sync` to synchronise the content of all paired paths based on the most recent file ([#146](https://github.com/mwouts/jupytext/issues/146)). In addition, the `--from` argument is optional even when the notebook is read from stdin ([#148](https://github.com/mwouts/jupytext/issues/148)).
- The pairing information, and more generally the notebook metadata can be edited with the CLL, see the `--set-formats` and the `--update-metadata` arguments ([#141](https://github.com/mwouts/jupytext/issues/141)).
- Jupytext can `--pipe` the text representation of a notebook to external programs like `black` or `flake8` ([#154](https://github.com/mwouts/jupytext/issues/154), [#142](https://github.com/mwouts/jupytext/issues/142))
- The Python representation of notebooks containing PEP8 cells is now expected to be PEP8 compliant ([#154](https://github.com/mwouts/jupytext/issues/154)).
- Format specification allow prefix and suffix for path and file name ([#138](https://github.com/mwouts/jupytext/issues/138), [#142](https://github.com/mwouts/jupytext/issues/142)). Use `ipynb,prefix/suffix.py:percent` to pair the current notebook named `notebook.ipynb` to a script named `prefixnotebooksuffix.py`. Suffix and prefix can also be configured on the `ipynb` file, with the same syntax.
- Introducing a new `hydrogen` format for scripts, which derives from `percent`. In that format Jupyter magic commands are not commented ([#59](https://github.com/mwouts/jupytext/issues/59), [#126](https://github.com/mwouts/jupytext/issues/126), [#132](https://github.com/mwouts/jupytext/issues/132)).
- Introducing a new `bare` format for scripts, which derives from `light`. That format has no cell marker. Use a notebook metadata filter `{"jupytext": {"notebook_metadata_filter":"-all"}}` if you want no YAML header ([#152](https://github.com/mwouts/jupytext/issues/152)).
- The default format for R script is now `light`, as for the other languages.
- Added support for q/kdb+ notebooks ([#161](https://github.com/mwouts/jupytext/issues/161)).
- Python scripts or Markdown documents that have no Jupyter metadata receive a metadata filter that ensures that metadata is not exported back to the text representation ([#124](https://github.com/mwouts/jupytext/issues/124)).
- Metadata filters are represented as strings rather than dictionaries to make YAML headers shorter. Previous syntax from [#105](https://github.com/mwouts/jupytext/issues/105) is still supported. They were also renamed to `notebook_metadata_filter` and `cell_metadata_filter`.
- Markdown and RMarkdown formats have a new option `split_at_heading` to split Markdown cells at heading ([#130](https://github.com/mwouts/jupytext/issues/130))
**Fixed**
- Main language of scripts is inferred from script extension. Fixes a round trip conversion issue for Python notebooks with a Javascript cell.
- Non-Python scripts opened as notebooks in Jupyter are now correctly saved even when no matching kernel is found.
- Jupyter magic commands like `ls` are commented in the light and R markdown format ([#149](https://github.com/mwouts/jupytext/issues/149)).
- Cell starting with `%%html`, `%%latex` are now commented out in the `light`, `percent` and `Rmd` formats ([#179](https://github.com/mwouts/jupytext/issues/179)).
0.8.6 (2018-11-29)
------------------
**Added**
- The `language_info` section is not part of the default header any more. Language information is now taken from metadata `kernelspec.language`. ([#105](https://github.com/mwouts/jupytext/issues/105)).
- When opening a paired notebook, the active file is now the file that was originally opened ([#118](https://github.com/mwouts/jupytext/issues/118)). When saving a notebook, timestamps of all the alternative representations are tested to ensure that Jupyter's autosave does not override manual modifications.
- Jupyter magic commands are now commented per default in the `percent` format ([#126](https://github.com/mwouts/jupytext/issues/126), [#132](https://github.com/mwouts/jupytext/issues/132)). Version for the `percent` format increases from '1.1' to '1.2'. Set an option `comment_magics` to `false` either per notebook, or globally on Jupytext's contents manager, or on `jupytext`'s command line, if you prefer not to comment Jupyter magics.
- Jupytext command line has a pre-commit mode ([#121](https://github.com/mwouts/jupytext/issues/121)).
0.8.5 (2018-11-13)
------------------
**Added**
- `bash` scripts as notebooks ([#127](https://github.com/mwouts/jupytext/issues/127))
- R scripts with `.r` extension are supported ([#122](https://github.com/mwouts/jupytext/issues/122))
- Jupytext selects the first kernel that matches the language ([#120](https://github.com/mwouts/jupytext/issues/120))
0.8.4 (2018-10-29)
------------------
**Added**
- Notebook metadata is filtered - only the most common metadata are stored in the text representation ([#105](https://github.com/mwouts/jupytext/issues/105))
- New config option `freeze_metadata` on the content manager and on the command line interface (defaults to `False`). Use this option to avoid creating a YAML header or cell metadata if there was none initially. ([#110](https://github.com/mwouts/jupytext/issues/110))
- Language magic arguments are preserved in R Markdown, and also supported in `light` and `percent` scripts ([#111](https://github.com/mwouts/jupytext/issues/111), [#114](https://github.com/mwouts/jupytext/issues/114), [#115](https://github.com/mwouts/jupytext/issues/115))
- First markdown cell exported as a docstring when using the Sphinx format ([#107](https://github.com/mwouts/jupytext/issues/107))
0.8.3 (2018-10-19)
------------------
**Added**
- Frozen cells are supported in R Markdown, light and percent scripts ([#101](https://github.com/mwouts/jupytext/issues/101))
- Inactive cells extended to percent scripts ([#108](https://github.com/mwouts/jupytext/issues/108))
- `jupytext` gains a `--version` argument ([#103](https://github.com/mwouts/jupytext/issues/103))
- "ExecuteTime" cell metadata is not included in the text representation anymore ([#106](https://github.com/mwouts/jupytext/issues/106))
0.8.2 (2018-10-15)
------------------
**Added**
- Round trip conversion testing with `jupytext --test` was improved ([#99](https://github.com/mwouts/jupytext/issues/99))
- Round trip conversion tested on Jake Vanderplas' Python for Data Science Handbook.
**Fixed**
- Nested lists and dictionaries are now supported in notebook metadata
- Final empty code cell supported in Sphinx representation
0.8.1 (2018-10-11)
------------------
**Fixed**
- Sphinx format tested on `World population` notebook ([#97](https://github.com/mwouts/jupytext/issues/97))
- Mirror test made stronger on this occasion!
- Markdown representation recognize Julia, Scheme and C++ code cells as such
- Light representation of Scheme and C++ notebooks fixed ([#61](https://github.com/mwouts/jupytext/issues/61))
0.8.0 (2018-10-10)
------------------
**Added**
- All `jupytext` related metadata goes to a `jupytext` section ([#91](https://github.com/mwouts/jupytext/issues/91)). Please make sure your collaborators use the same version of Jupytext, as the new version can read previous metadata, but not the opposite.
- Notebooks extensions can be prefixed with any prefix of at most three chars ([#87](https://github.com/mwouts/jupytext/issues/87)).
- Export of the same notebook to multiple formats is now supported. To export to all python formats, plus `.ipynb` and `.md`, use `"jupytext": {"formats": "ipynb,pct.py:percent,lgt.py:light,spx.py:sphinx,md"},`.
- README includes a short section on how to extend `light` and `percent` formats to more languages ([#61](https://github.com/mwouts/jupytext/issues/61)).
- Jupytext's contents manager accepts the `auto` extension in `default_jupytext_formats` ([#93](https://github.com/mwouts/jupytext/issues/93)).
- All Jupyter magics are escaped in `light` scripts and R markdown documents. Escape magics in other formats with a `comment_magics` metadata (true or false), or with the contents manager `comment_magics` global flag ([#94](https://github.com/mwouts/jupytext/issues/94)).
**Fixed**
- Trusting notebooks made functional again.
- Command line `jupytext` returns a meaningful error when no argument is given.
- Fixed global pairing configuration ([#95](https://github.com/mwouts/jupytext/issues/95)).
0.7.2 (2018-10-01)
------------------
**Added**
- `light` and `percent` formats made available for scheme and cpp notebooks. Adding more formats is straightforward - just add a new entry to _SCRIPT_EXTENSIONS in languages.py, a sample notebook and a mirror test ([#61](https://github.com/mwouts/jupytext/issues/61))
- Format name is automatically appended to extension in `jupytext_formats` when notebook is loaded/saved.
**Fixed**
- Notebooks extensions can only be prefixed with `.nb` ([#87](https://github.com/mwouts/jupytext/issues/87))
0.7.1 (2018-09-24)
------------------
**Fixed**
- Markdown cells header in sphinx gallery format may have a space between first # and following.
0.7.0 (2018-09-23)
------------------
**Added**
- Header for cells in `percent` format is more robust: use `[markdown]` and `[raw]` to identify cell types. Cell type comes after the cell title. ([#59](https://github.com/mwouts/jupytext/issues/59))
- Jupytext can read and write notebooks as Hydrogen/VScode/Spyder/PyCharm compatible scripts (cells starting with `# %%`) ([#59](https://github.com/mwouts/jupytext/issues/59))
- Jupytext can read and write notebooks as Sphinx-gallery compatible scripts ([#80](https://github.com/mwouts/jupytext/issues/80))
- Metadata are supported for all cell types in light python and percent formats ([#66](https://github.com/mwouts/jupytext/issues/66)). Due to this, light python format version is now 1.3. Light python notebooks in versions 1.1 and 1.2 are still readable.
- Command line `jupytext` has a `from` argument, and now accepts notebook from the standard input.
**Fixed**
- Fix merging of input and output notebooks ([#83](https://github.com/mwouts/jupytext/issues/83))
- Removed extra new line on stdout in command line `jupytext` ([#84](https://github.com/mwouts/jupytext/issues/84))
0.6.5 (2018-09-13)
------------------
**Added**
- Code lines that start with a quotation mark in Jupyter are commented in the corresponding Python and Julia scripts ([#73](https://github.com/mwouts/jupytext/issues/73))
- Update pypy, add flake8 tests on Travis CI ([#74](https://github.com/mwouts/jupytext/issues/74))
**Fixed**
- Import notebook.transutils before notebook.services.contents.filemanager ([#75](https://github.com/mwouts/jupytext/issues/75))
0.6.4 (2018-09-12)
------------------
**Added**
- Jupytext will not load paired notebook when text representation is out of date ([#63](https://github.com/mwouts/jupytext/issues/63))
- Package tested against Python 3.7 ([#68](https://github.com/mwouts/jupytext/issues/68))
**Fixed**
- Allow unicode characters in notebook path ([#70](https://github.com/mwouts/jupytext/issues/70))
- Read README.md as unicode in `setup.py` ([#71](https://github.com/mwouts/jupytext/issues/71))
0.6.3 (2018-09-07)
------------------
**Added**
- Lighter cell markers for Python and Julia scripts ([#57](https://github.com/mwouts/jupytext/issues/57)). Corresponding file format version at 1.2. Scripts in previous version 1.1 can still be opened.
- New screenshots for the README.
**Fixed**
- Command line conversion tool `jupytext` fixed on Python 2.7 ([#46](https://github.com/mwouts/jupytext/issues/46))
0.6.2 (2018-09-05)
------------------
**Added**
- Initial support for Jupyter notebooks as Julia scripts ([#56](https://github.com/mwouts/jupytext/issues/56))
- Command line conversion tool `jupytext` has explicit `to` and `output` options ([#46](https://github.com/mwouts/jupytext/issues/46))
- Round trip test with `jupytext --test` improved ([#54](https://github.com/mwouts/jupytext/issues/54))
- Improved README ([#51](https://github.com/mwouts/jupytext/issues/51))
**Fixed**
- testfixtures now in requirements ([#55](https://github.com/mwouts/jupytext/issues/55))
- Empty code cells are now preserved ([#53](https://github.com/mwouts/jupytext/issues/53))
0.6.1 (2018-08-31)
------------------
**Added**
- Package and conversion script renamed from `nbrmd` to `jupytext`.
0.6.0 (2018-08-31)
------------------
**Added**
- Cell parsing and exporting done in two specialized classes. This is way easier to read. Pylint score at 9.9 !
- Python file format updated to 1.1: default end of cell for python scripts is one blank space. Two blank spaces are allowed as well. Now you can reformat code in Python IDE without breaking notebook cells ([#38](https://github.com/mwouts/jupytext/issues/38)).
- Added support for plain markdown files ([#40](https://github.com/mwouts/jupytext/issues/40), [#44](https://github.com/mwouts/jupytext/issues/44)).
- Demonstration notebooks more user friendly ([#45](https://github.com/mwouts/jupytext/issues/45)).
- Command line tool simpler to use ([#46](https://github.com/mwouts/jupytext/issues/46)).
- Start code patterns present in Jupyter cells are escaped.
- Default `nbrmd_format` is empty (mwouts/nbsrc/[#5](https://github.com/mwouts/jupytext/issues/5)): no Jupyter notebook is created on disk when the user opens a Python or R file and saves it from Jupyter, unless the users asks for it by setting `nbrmd_format`.
**Fixed**
- Fixed message in the `nbsrc` script ([#43](https://github.com/mwouts/jupytext/issues/43))
- Technical metadata don't appear any more in scripts unless required ([#42](https://github.com/mwouts/jupytext/issues/42))
- Code cells that are fully commented remain code cells after round trip ([#41](https://github.com/mwouts/jupytext/issues/41))
0.5.4 (2018-08-24)
------------------
**Added**
- R to Rmd conversion compares well to knitr::spin ([#26](https://github.com/mwouts/jupytext/issues/26))
- Increased coverage to 98%
0.5.3 (2018-08-22)
------------------
**Fixed**
- Read and write version to the same metadata ([#36](https://github.com/mwouts/jupytext/issues/36))
0.5.2 (2018-08-22)
------------------
**Added**
- Classical jupyter extensions (autoreload, rmagics) are also escaped ([#35](https://github.com/mwouts/jupytext/issues/35))
- Explicit file format version, set at 1.0, to avoid overriding ipynb files by accident ([#36](https://github.com/mwouts/jupytext/issues/36))
0.5.1 (2018-08-21)
------------------
**Fixed**
- Source only notebooks can be trusted.
0.5.0 (2018-08-21)
------------------
**Added**
- Jupyter magic commands escaped when exported ([#29](https://github.com/mwouts/jupytext/issues/29))
- 'endofcell' option for explicit (optional) end-of-cell marker ([#31](https://github.com/mwouts/jupytext/issues/31))
- 'active' cell option now supported for .py and .R export ([#30](https://github.com/mwouts/jupytext/issues/30))
- Raw cells now preserved when exported to .py or .R ([#32](https://github.com/mwouts/jupytext/issues/32))
- Extensions can be prefixed, like `.nb.py`, (mwouts/nbsrc[#5](https://github.com/mwouts/jupytext/issues/5))
- When a file with an extension not associated to 'ipynb' is opened and saved, no 'ipynb' file is created (mwouts/nbsrc[#5](https://github.com/mwouts/jupytext/issues/5))
- Extensions can now be a sequence of groups. For instance, `nbrmd_formats="ipynb,nb.py;script.ipynb,py"` will create an `ipynb` file when a `nb.py` is opened (and conversely), and a `script.ipynb` file when a `py` file is opened (mwouts/nbsrc[#5](https://github.com/mwouts/jupytext/issues/5))
- `nbsrc` script was moved to the `nbrmd` package. The `nbsrc` package now only contains the documentation (mwouts/nbsrc[#3](https://github.com/mwouts/jupytext/issues/3))
0.4.6 (2018-07-26)
------------------
- Ping pypi - previous version still not available
0.4.5 (2018-07-26)
------------------
**Fixed**
- Removed dependency of `setup.py` on `yaml`
0.4.4 (2018-07-26)
-------------------
**Fixed**
- Package republished with `python setup.py sdist bdist_wheel` to fix missing dependencies
0.4.3 (2018-07-26)
------------------
**Added**
- Multiline comments now supported [#25](https://github.com/mwouts/jupytext/issues/25)
- Readme refactored, notebook demos available on binder [#23](https://github.com/mwouts/jupytext/issues/23)
**Fixed**
- ContentsManager can be imported even if `notebook.transutils` is not available, for compatibility with older python distributions.
- Fixed missing cell metadata [#27](https://github.com/mwouts/jupytext/issues/27)
- Documentation tells how to avoid creating `.ipynb` files [#16](https://github.com/mwouts/jupytext/issues/16)
0.4.2 (2018-07-23)
------------------
**Added**
- Added test for R notebooks
- Added pylint badge, imports now in correct order
- New `active` cell metadata that allows cell activation only for desired extensions (currently available for Rmd and ipynb extensions only)
0.4.1 (2018-07-20)
------------------
**Fixed**
- Indented python code will not start a new cell [#20](https://github.com/mwouts/jupytext/issues/20)
- Fixed parsing of Rmd cell metadata [#21](https://github.com/mwouts/jupytext/issues/21)
0.4.0 (2018-07-18)
------------------
**Added**
- `.py` format for notebooks is lighter and pep8 compliant
**Fixed**
- Default nbrmd config not added to notebooks ([#17](https://github.com/mwouts/jupytext/issues/17))
- `nbrmd_formats` becomes a configurable traits ([#16](https://github.com/mwouts/jupytext/issues/16))
- Removed `nbrmd_sourceonly_format` metadata. Source notebook is current notebook when not `.ipynb`, otherwise the first notebook format in `nbrmd_formats` (not `.ipynb`) that is found on disk
0.3.0 (2018-07-17)
------------------
**Added**
- Introducing support for notebooks as python `.py` or R scripts `.R`
0.2.6 (2018-07-13)
------------------
**Added**
- Introduced `nbrmd_sourceonly_format` metadata
- Inputs are loaded from `.Rmd` file when a matching `.ipynb` file is opened.
**Fixed**
- Trusted notebooks remain trusted ([#12](https://github.com/mwouts/jupytext/issues/12))
0.2.5 (2018-07-11)
------------------
**Added**
- Outputs of existing `.ipynb` versions are combined with matching inputs of R markdown version, as suggested by @grst ([#12](https://github.com/mwouts/jupytext/issues/12))
**Fixed**
- Support for unicode text in python 2.7 ([#11](https://github.com/mwouts/jupytext/issues/11))
0.2.4 (2018-07-05)
------------------
**Added**
- nbrmd will always open notebooks, even if header of code cells are not terminated. Merge conflicts can thus be solved in Jupyter directly.
- New metadata 'main language' that preserves the notebook language.
**Fixed**
- dependencies included in `setup.py`
- pre_save_hook work with non-empty `notebook_dir` ([#9](https://github.com/mwouts/jupytext/issues/9))
0.2.3 (2018-06-28)
------------------
**Added**
- Screenshots in README
**Fixed**
- RMarkdown exporter for nbconvert fixed on non-recent python
- Tests compatible with other revisions of nbformat >= 4.0
- Tests compatible with older pytest versions
0.2.2 (2018-06-28)
-------------------
**Added**
- RMarkdown exporter for nbconvert
- Parsing of R options robust to parenthesis
- Jupyter cell tags are preserved
**Fixed**
- requirements.txt now included in pypi packages
0.2.1 (2018-06-24)
------------------
**Added**
- Support for editing markdown files in Jupyter
- New pre-save hook `update_selected_formats` that saves to formats in metadata 'nbrmd_formats'
- Rmd cell options directly mapped to cell metadata
**Fixed**
- ContentManager compatible with Python 2.7
0.2.0 (2018-06-21)
------------------
**Added**
- The package provides a `RmdFileContentsManager` for direct edit of R markdown files in Jupyter
- Notebook metadata and cell options are preserved
0.1.1 (2018-06-19)
------------------
**Added**
- `nbrmd` prints the result of conversion to stdout, unless flag `-i` is provided
- Notebooks with R code chunks are supported
0.1 (2018-06-18)
----------------
- Initial version with the `nbrmd` converter and Jupyter `pre_save_hook`
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2018-2026 Marc Wouts
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================

[](https://github.com/mwouts/jupytext/actions)
[](https://jupytext.readthedocs.io/en/latest/?badge=latest)
[](https://codecov.io/gh/mwouts/jupytext/branch/main)
[](https://github.com/mwouts/jupytext/blob/main/LICENSE)
[](https://github.com/psf/black)
[](docs/languages.md)
[](https://anaconda.org/conda-forge/jupytext/)
[](https://pypi.python.org/pypi/jupytext)
[](https://pypi.python.org/pypi/jupytext)
[](https://mybinder.org/v2/gh/mwouts/jupytext/main?urlpath=lab/tree/demo/get_started.ipynb)
[](https://mybinder.org/v2/gh/mwouts/jupytext/main?filepath=demo)
[](https://renkulab.io/projects/best-practices/jupytext/sessions/new?autostart=1)
[](https://www.youtube.com/watch?v=SDYdeVfMh48)
# Jupytext
Have you always wished Jupyter notebooks were plain text documents? Wished you could edit them in your favorite IDE? And get clear and meaningful diffs when doing version control? Then, Jupytext may well be the tool you're looking for!
## Text Notebooks
A Python notebook encoded in the `py:percent` [format](docs/formats-scripts.md#the-percent-format) has a `.py` extension and looks like this:
```
# %% [markdown]
# This is a markdown cell
# %%
def f(x):
return 3*x+1
```
Only the notebook inputs (and optionally, the metadata) are included. Text notebooks are well suited for version control. You can also edit or refactor them in an IDE - the `.py` notebook above is a regular Python file.
We recommend the `percent` format for notebooks that mostly contain code. The `percent` format is available for Julia, Python, R and many other [languages](docs/languages.md).
If your notebook is documentation-oriented, a [Markdown-based format](docs/formats-markdown.md) (text notebooks with a `.md` extension) might be more appropriate. Depending on what you plan to do with your notebook, you might prefer the Myst Markdown format, which interoperates very well with Jupyter Book, or Quarto Markdown, or even Pandoc Markdown.
## Installation
Install Jupytext in the Python environment that you use for Jupyter. Use either
pip install jupytext
or
conda install jupytext -c conda-forge
Then, restart your JupyterLab server, and make sure Jupytext is activated in Jupyter: `.py` and `.md` files have a Notebook icon, and you can open them as Notebooks with a right click in JupyterLab.

## Paired Notebooks
Text notebooks with a `.py` or `.md` extension are well suited for version control. They can be edited or authored conveniently in an IDE. You can open and run them as notebooks in JupyterLab with a right click. However, the notebook outputs are lost when the notebook is closed, as only the notebook inputs are saved in text notebooks.
A convenient alternative to text notebooks are [paired notebooks](docs/paired-notebooks.md). These are a set of two files, say `.ipynb` and `.py`, that contain the same notebook, but in different formats.
You can edit the `.py` version of the paired notebook, and get the edits back in Jupyter by selecting _reload notebook from disk_. The outputs will be reloaded from the `.ipynb` file, if it exists. The `.ipynb` version will be updated or recreated the next time you save the notebook in Jupyter.
💡 **Tip:** You can automate the notebook reloading by installing the [Jupyter Collaboration](docs/jupyter-collaboration.md) extension.
To pair a notebook in JupyterLab, use the command `Pair Notebook with percent Script` from the Command Palette:

To pair all the notebooks in a certain directory, create a [configuration file](docs/config.md) with this content:
```
# jupytext.toml at the root of your notebook directory
formats = "ipynb,py:percent"
```
## Command line
Jupytext is also available at the [command line](docs/using-cli.md). You can
- pair a notebook with `jupytext --set-formats ipynb,py:percent notebook.ipynb`
- synchronize the paired files with `jupytext --sync notebook.py` (the inputs are loaded from the most recent paired file)
- convert a notebook in one format to another with `jupytext --to ipynb notebook.py` (use `-o` if you want a specific output file)
- pipe a notebook to a linter with e.g. `jupytext --pipe black notebook.ipynb`
## Sample use cases
### Notebooks under version control
This is a quick how-to:
- Open your `.ipynb` notebook in Jupyter and [pair](docs/paired-notebooks.md) it to a `.py` notebook, using either the _pair_ command in JupyterLab, or a global [configuration file](docs/config.md)
- Save the notebook - this creates a `.py` notebook
- Add this `.py` notebook to version control
You might exclude `.ipynb` files from version control (unless you want to see the outputs versioned!). Jupytext will recreate the `.ipynb` files locally when the users open and save the `.py` notebooks.
### Collaborating on notebooks with Git
Collaborating on Jupyter notebooks through Git becomes as easy as collaborating on text files.
Assume that you have your `.py` notebooks under version control (see above). Then,
- Your collaborator pulls the `.py` notebook
- They open it _as a notebook_ in Jupyter (right-click in JupyterLab)
- At that stage the notebook has no outputs. They run the notebook and save it. Outputs are regenerated, and a local `.ipynb` file is created
- They edit the notebook, and push the updated `notebook.py` file. The diff is nothing else than a standard diff on a Python script.
- You pull the updated `notebook.py` script, and refresh your browser. The input cells are updated based on the new content of `notebook.py`. The outputs are reloaded from your local `.ipynb` file. Finally, the kernel variables are untouched, so you have the option to run only the modified cells to get the new outputs.
### Editing or refactoring a notebook in an IDE
Once your notebook is [paired](docs/paired-notebooks.md) with a `.py` file, you can easily edit or refactor the `.py` representation of the notebook in an IDE.
Once you are done editing the `.py` notebook, you will just have to _reload_ the notebook in Jupyter to get the latest edits there.
Note: It is simpler to close the `.ipynb` notebook in Jupyter when you edit the paired `.py` file. There is no obligation to do so; however, if you don't, you should be prepared to read carefully the pop-up messages. If Jupyter tries to save the notebook while the paired `.py` file has also been edited on disk since the last reload, a conflict will be detected and you will be asked to decide which version of the notebook (in memory or on disk) is the appropriate one. Alternatively, the [Jupyter Collaboration](docs/jupyter-collaboration.md) extension provides an autoreload feature which simplifies this.
## More resources
Read more about Jupytext in the [documentation](https://jupytext.readthedocs.io).
If you're new to Jupytext, you may want to start with the [FAQ](docs/faq.md) or with the [Tutorials](docs/tutorials.md).
There is also this short introduction to Jupytext: [](https://www.youtube.com/watch?v=SDYdeVfMh48).
================================================
FILE: binder/labconfig/default_setting_overrides.json
================================================
{
"@jupyterlab/docmanager-extension:plugin": {
"defaultViewers": {
"markdown": "Jupytext Notebook",
"myst": "Jupytext Notebook",
"r-markdown": "Jupytext Notebook",
"quarto": "Jupytext Notebook",
"julia": "Jupytext Notebook",
"python": "Jupytext Notebook",
"r": "Jupytext Notebook"
}
}
}
================================================
FILE: binder/postBuild
================================================
# Stop everything if one command fails
set -e
# Install from sources
# NB: If you want to use Jupytext on your binder, don't install it from source,
# just add "jupytext" to your "binder/requirements.txt" instead.
HATCH_BUILD_HOOKS_ENABLE=true pip install .
mkdir -p ${HOME}/.jupyter/labconfig
cp binder/labconfig/* ${HOME}/.jupyter/labconfig
# Create the notebook for our jupytext demo
jupytext demo/get_started.md --to ipynb --update-metadata '{"jupytext":null}'
# Remove the markdown representation
# (the demo starts with just the ipynb file)
rm demo/get_started.md
# Trust our World Population notebook
jupyter trust demo/World\ population.ipynb
# Install the bash kernel
python -m bash_kernel.install
================================================
FILE: binder/requirements.txt
================================================
jupyterlab>=4
notebook>=7
plotly
matplotlib
wordcloud
pandas
wbdata
bash_kernel
================================================
FILE: demo/Benchmarking Jupytext.py
================================================
# In this notebook, we benchmark the Jupytext formats for Jupyter notebooks against the base format
# Open this script as a notebook in Jupyter to run it and see the plots
import time
import copy
import pandas as pd
import plotly.graph_objects as go
from plotly.colors import DEFAULT_PLOTLY_COLORS
import nbformat
import jupytext
# The notebook to be tested
notebook = jupytext.read('World population.ipynb')
# Same notebook, with no outputs, for a fair comparison
notebook_no_outputs = copy.deepcopy(notebook)
for cell in notebook_no_outputs.cells:
cell.outputs = []
cell.execution_count = None
# +
JUPYTEXT_FORMATS = ['ipynb', 'md', 'py:light', 'py:percent', 'py:sphinx']
# Let's see if we have pandoc here
try:
jupytext.writes(notebook, fmt='md:pandoc')
JUPYTEXT_FORMATS.append('md:pandoc')
except jupytext.formats.JupytextFormatError as err:
print(str(err))
# Let's see if we have myst-parser installed here
try:
jupytext.writes(notebook, fmt='myst')
JUPYTEXT_FORMATS.append('myst')
except jupytext.formats.JupytextFormatError as err:
print(str(err))
# -
def sample_perf(nb, n=30):
samples = pd.DataFrame(
pd.np.NaN,
index=pd.MultiIndex.from_product(
(range(n), ['nbformat'] + JUPYTEXT_FORMATS), names=['sample', 'implementation']),
columns=pd.Index(['size', 'read', 'write'], name='measure'))
for i, fmt in samples.index:
t0 = time.time()
if fmt == 'nbformat':
text = nbformat.writes(nb)
else:
text = jupytext.writes(nb, fmt)
t1 = time.time()
samples.loc[(i, fmt), 'write'] = t1 - t0
samples.loc[(i, fmt), 'size'] = len(text)
t0 = time.time()
if fmt == 'nbformat':
nbformat.reads(text, as_version=4)
else:
jupytext.reads(text, fmt)
t1 = time.time()
samples.loc[(i, fmt), 'read'] = t1 - t0
return samples
def performance_plot(perf, title):
formats = ['nbformat'] + JUPYTEXT_FORMATS
mean = perf.groupby('implementation').mean().loc[formats]
std = perf.groupby('implementation').std().loc[formats]
data = [go.Bar(x=mean.index,
y=mean[col],
error_y=dict(
type='data',
array=std[col],
color=color,
thickness=0.5
) if col != 'size' else dict(),
name=col,
yaxis={'read': 'y1', 'write': 'y2', 'size': 'y3'}[col])
for col, color in zip(mean.columns, DEFAULT_PLOTLY_COLORS)]
layout = go.Layout(title=title,
xaxis=dict(title='Implementation', anchor='y3'),
yaxis=dict(domain=[0.7, 1], title='Read (secs)'),
yaxis2=dict(domain=[0.35, .65], title='Write (secs)'),
yaxis3=dict(domain=[0, .3], title='Size')
)
return go.Figure(data=data, layout=layout)
perf_no_outputs = sample_perf(notebook_no_outputs, 30)
performance_plot(perf_no_outputs, 'Benchmarking Jupytext on the World Population notebook (Outputs filtered)')
perf = sample_perf(notebook, 30)
performance_plot(perf, 'Benchmarking Jupytext on the World Population notebook (With outputs)')
================================================
FILE: demo/Jupytext's word cloud.py
================================================
# This is a notebook that I used to generate Jupytext's word cloud.
# To open this script as a notebook in JupyterLab, right-click on this file, and select _Open with/Notebook_.
from wordcloud import WordCloud
text = """
Jupytext
Notebook
JupyterLab
Git
GitHub
Version control
Markdown
R Markdown
Text
Scripts
Code
Notebook Template
Binder
Visual Studio Code
PyCharm
Atom
Spyder
Hydrogen
RStudio
Sphinx-Gallery
Documentation
black
pytest
autopep8
Metadata
Reproducible research
R
Julia
Python
Bash
Powershell
Scala
Scheme
Clojure
Matlab
Octave
C++
q/kdb+
IDL
TypeScript
Javascript
Scala
Rust
Robot Framework
"""
wordcloud = WordCloud(
random_state=1,
background_color='white',
width=1200, height=500
).generate_from_frequencies({word: 1 for word in text.splitlines()})
wordcloud.to_image()
wordcloud.to_file('../docs/jupytext_word_cloud.png')
================================================
FILE: demo/Tests in a notebook.md
================================================
# Testing a Jupyter notebook with pytest
In this notebook we describe how to test a notebook with `jupytext`.
## Writing assertions and tests in a notebook
Our notebook defines a function that we wish to test. Our function is simply
```python
def f(x, n=5):
return [x + i for i in range(n)]
```
We can test the assertion in Jupyter with simply
```python
assert f(5) == [5,6,7,8,9]
```
Since the assertion above works, we don't get any message. It's more interesting to see what happens when an assertion fails. Remove one element of the list above and change the assertion to, say,
assert f(5) == [5,6,8,9]
When we run the above in Jupyter, we get
```stderr
---------------------------------------------------------------------------
AssertionError Traceback (most recent call last)
in
----> 1 assert f(5) == [5,6,8,9]
AssertionError:
```
Now if we run the notebook with `jupytext --check pytest 'Tests in a notebook.md'`, we get a more detailed description of the issue, thanks to `pytest`'s rewriting of assertions:
```output
[jupytext] Reading Tests in a notebook.md
=========================== test session starts ===========================
platform win32 -- Python 3.7.5, pytest-5.2.2, py-1.8.0, pluggy-0.13.0
rootdir: C:\Users\Marco
collected 0 items / 1 errors
================================ ERRORS ===================================
_________ ERROR collecting Tests in a notebook vhs_lscr.py ________________
Tests in a notebook vhs_lscr.py:19: in
assert f(5) == [5,6,8,9]
E assert [5, 6, 7, 8, 9] == [5, 6, 8, 9]
E + where [5, 6, 7, 8, 9] = (5)
!!!!!!!!!!!!! Interrupted: 1 errors during collection !!!!!!!!!!!!!!!!!!!!!
=========================== 1 error in 0.09s ==============================
```
Once all of our assertions pass, we can move them to a test function. In Jupyter the function is not evaluated - only when we run `jupytext --check pytest` on the notebook, the function is actually executed.
```python
def test_f():
assert f(5) == [5,6,7,8,9]
```
## Going further
- [nbval](https://github.com/computationalmodelling/nbval) is a plugin for `pytest` that allows you to make sure that Jupyter notebooks run properly, and that their new outputs match the current ones. Use it as `pytest --nbval notebook.ipynb`.
- [ipytest](https://github.com/chmp/ipytest) defines a `%%run_pytest` cell magic that allows you to execute the tests in a cell directly in Jupyter.
================================================
FILE: demo/World population.Rmd
================================================
---
jupyter:
jupytext:
cell_markers: region,endregion
formats: ipynb,.pct.py:percent,.lgt.py:light,.spx.py:sphinx,md,Rmd,.pandoc.md:pandoc
text_representation:
extension: .Rmd
format_name: rmarkdown
format_version: '1.1'
jupytext_version: 1.1.0
kernelspec:
display_name: Python 3
language: python
name: python3
---
# A quick insight at world population
## Collecting population data
In the below we retrieve population data from the
[World Bank](http://www.worldbank.org/)
using the [wbdata](https://github.com/OliverSherouse/wbdata) python package
```{python}
import pandas as pd
import wbdata as wb
pd.options.display.max_rows = 6
pd.options.display.max_columns = 20
```
Corresponding indicator is found using search method - or, directly,
the World Bank site.
```{python}
wb.search_indicators('Population, total') # SP.POP.TOTL
# wb.search_indicators('area')
# => https://data.worldbank.org/indicator is easier to use
```
Now we download the population data
```{python}
indicators = {'SP.POP.TOTL': 'Population, total',
'AG.SRF.TOTL.K2': 'Surface area (sq. km)',
'AG.LND.TOTL.K2': 'Land area (sq. km)',
'AG.LND.ARBL.ZS': 'Arable land (% of land area)'}
data = wb.get_dataframe(indicators, convert_date=True).sort_index()
data
```
World is one of the countries
```{python}
data.loc['World']
```
Can we classify over continents?
```{python}
data.loc[(slice(None), '2017-01-01'), :]['Population, total'].dropna(
).sort_values().tail(60).index.get_level_values('country')
```
Extract zones manually (in order of increasing population)
```{python}
zones = ['North America', 'Middle East & North Africa',
'Latin America & Caribbean', 'Europe & Central Asia',
'Sub-Saharan Africa', 'South Asia',
'East Asia & Pacific'][::-1]
```
And extract population information (and check total is right)
```{python}
population = data.loc[zones]['Population, total'].swaplevel().unstack()
population = population[zones]
assert all(data.loc['World']['Population, total'] == population.sum(axis=1))
```
## Stacked area plot with matplotlib
```{python}
import matplotlib.pyplot as plt
```
```{python}
plt.clf()
plt.figure(figsize=(10, 5), dpi=100)
plt.stackplot(population.index, population.values.T / 1e9)
plt.legend(population.columns, loc='upper left')
plt.ylabel('Population count (B)')
plt.show()
```
## Stacked bar plot with plotly
```{python}
import plotly.offline as offline
import plotly.graph_objs as go
offline.init_notebook_mode()
```
```{python}
data = [go.Scatter(x=population.index, y=population[zone], name=zone, stackgroup='World')
for zone in zones]
fig = go.Figure(data=data,
layout=go.Layout(title='World population'))
offline.iplot(fig)
```
================================================
FILE: demo/World population.ipynb
================================================
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# A quick insight at world population\n",
"\n",
"## Collecting population data\n",
"\n",
"In the below we retrieve population data from the\n",
"[World Bank](http://www.worldbank.org/)\n",
"using the [wbdata](https://github.com/OliverSherouse/wbdata) python package"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"import pandas as pd\n",
"import wbdata as wb\n",
"\n",
"pd.options.display.max_rows = 6\n",
"pd.options.display.max_columns = 20"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Corresponding indicator is found using search method - or, directly,\n",
"the World Bank site."
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"SP.POP.TOTL\tPopulation, total\n"
]
}
],
"source": [
"wb.search_indicators('Population, total') # SP.POP.TOTL\n",
"# wb.search_indicators('area')\n",
"# => https://data.worldbank.org/indicator is easier to use"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now we download the population data"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"
\n",
"\n",
"
\n",
" \n",
"
\n",
"
\n",
"
\n",
"
Population, total
\n",
"
Surface area (sq. km)
\n",
"
Land area (sq. km)
\n",
"
Arable land (% of land area)
\n",
"
\n",
"
\n",
"
country
\n",
"
date
\n",
"
\n",
"
\n",
"
\n",
"
\n",
"
\n",
" \n",
" \n",
"
\n",
"
Afghanistan
\n",
"
1960-01-01
\n",
"
8996351.0
\n",
"
NaN
\n",
"
NaN
\n",
"
NaN
\n",
"
\n",
"
\n",
"
1961-01-01
\n",
"
9166764.0
\n",
"
652860.0
\n",
"
652860.0
\n",
"
11.717673
\n",
"
\n",
"
\n",
"
1962-01-01
\n",
"
9345868.0
\n",
"
652860.0
\n",
"
652860.0
\n",
"
11.794259
\n",
"
\n",
"
\n",
"
...
\n",
"
...
\n",
"
...
\n",
"
...
\n",
"
...
\n",
"
...
\n",
"
\n",
"
\n",
"
Zimbabwe
\n",
"
2015-01-01
\n",
"
15777451.0
\n",
"
390760.0
\n",
"
386850.0
\n",
"
10.339925
\n",
"
\n",
"
\n",
"
2016-01-01
\n",
"
16150362.0
\n",
"
390760.0
\n",
"
386850.0
\n",
"
NaN
\n",
"
\n",
"
\n",
"
2017-01-01
\n",
"
16529904.0
\n",
"
390760.0
\n",
"
386850.0
\n",
"
NaN
\n",
"
\n",
" \n",
"
\n",
"
15312 rows × 4 columns
\n",
"
"
],
"text/plain": [
" Population, total Surface area (sq. km) \\\n",
"country date \n",
"Afghanistan 1960-01-01 8996351.0 NaN \n",
" 1961-01-01 9166764.0 652860.0 \n",
" 1962-01-01 9345868.0 652860.0 \n",
"... ... ... \n",
"Zimbabwe 2015-01-01 15777451.0 390760.0 \n",
" 2016-01-01 16150362.0 390760.0 \n",
" 2017-01-01 16529904.0 390760.0 \n",
"\n",
" Land area (sq. km) Arable land (% of land area) \n",
"country date \n",
"Afghanistan 1960-01-01 NaN NaN \n",
" 1961-01-01 652860.0 11.717673 \n",
" 1962-01-01 652860.0 11.794259 \n",
"... ... ... \n",
"Zimbabwe 2015-01-01 386850.0 10.339925 \n",
" 2016-01-01 386850.0 NaN \n",
" 2017-01-01 386850.0 NaN \n",
"\n",
"[15312 rows x 4 columns]"
]
},
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"indicators = {'SP.POP.TOTL': 'Population, total',\n",
" 'AG.SRF.TOTL.K2': 'Surface area (sq. km)',\n",
" 'AG.LND.TOTL.K2': 'Land area (sq. km)',\n",
" 'AG.LND.ARBL.ZS': 'Arable land (% of land area)'}\n",
"data = wb.get_dataframe(indicators, convert_date=True).sort_index()\n",
"data"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"World is one of the countries"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"
\n",
"\n",
"
\n",
" \n",
"
\n",
"
\n",
"
Population, total
\n",
"
Surface area (sq. km)
\n",
"
Land area (sq. km)
\n",
"
Arable land (% of land area)
\n",
"
\n",
"
\n",
"
date
\n",
"
\n",
"
\n",
"
\n",
"
\n",
"
\n",
" \n",
" \n",
"
\n",
"
1960-01-01
\n",
"
3.032160e+09
\n",
"
NaN
\n",
"
NaN
\n",
"
NaN
\n",
"
\n",
"
\n",
"
1961-01-01
\n",
"
3.073369e+09
\n",
"
134043190.4
\n",
"
129721455.4
\n",
"
9.693086
\n",
"
\n",
"
\n",
"
1962-01-01
\n",
"
3.126510e+09
\n",
"
134043190.4
\n",
"
129721435.4
\n",
"
9.726105
\n",
"
\n",
"
\n",
"
...
\n",
"
...
\n",
"
...
\n",
"
...
\n",
"
...
\n",
"
\n",
"
\n",
"
2015-01-01
\n",
"
7.357559e+09
\n",
"
134325130.2
\n",
"
129732901.8
\n",
"
10.991288
\n",
"
\n",
"
\n",
"
2016-01-01
\n",
"
7.444157e+09
\n",
"
134325130.2
\n",
"
129733172.7
\n",
"
NaN
\n",
"
\n",
"
\n",
"
2017-01-01
\n",
"
7.530360e+09
\n",
"
134325130.2
\n",
"
129733172.7
\n",
"
NaN
\n",
"
\n",
" \n",
"
\n",
"
58 rows × 4 columns
\n",
"
"
],
"text/plain": [
" Population, total Surface area (sq. km) Land area (sq. km) \\\n",
"date \n",
"1960-01-01 3.032160e+09 NaN NaN \n",
"1961-01-01 3.073369e+09 134043190.4 129721455.4 \n",
"1962-01-01 3.126510e+09 134043190.4 129721435.4 \n",
"... ... ... ... \n",
"2015-01-01 7.357559e+09 134325130.2 129732901.8 \n",
"2016-01-01 7.444157e+09 134325130.2 129733172.7 \n",
"2017-01-01 7.530360e+09 134325130.2 129733172.7 \n",
"\n",
" Arable land (% of land area) \n",
"date \n",
"1960-01-01 NaN \n",
"1961-01-01 9.693086 \n",
"1962-01-01 9.726105 \n",
"... ... \n",
"2015-01-01 10.991288 \n",
"2016-01-01 NaN \n",
"2017-01-01 NaN \n",
"\n",
"[58 rows x 4 columns]"
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"data.loc['World']"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Can we classify over continents?"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"Index(['Iran, Islamic Rep.', 'Congo, Dem. Rep.', 'Germany', 'Vietnam',\n",
" 'Egypt, Arab Rep.', 'Central Europe and the Baltics', 'Philippines',\n",
" 'Ethiopia', 'Japan', 'Mexico', 'Russian Federation', 'Bangladesh',\n",
" 'Nigeria', 'Pakistan', 'Brazil', 'Indonesia', 'United States',\n",
" 'Euro area', 'North America',\n",
" 'Middle East & North Africa (IDA & IBRD countries)',\n",
" 'Middle East & North Africa (excluding high income)', 'Arab World',\n",
" 'Europe & Central Asia (excluding high income)',\n",
" 'Middle East & North Africa',\n",
" 'Europe & Central Asia (IDA & IBRD countries)',\n",
" 'Fragile and conflict affected situations', 'European Union',\n",
" 'IDA blend', 'Latin America & Caribbean (excluding high income)',\n",
" 'Latin America & the Caribbean (IDA & IBRD countries)',\n",
" 'Latin America & Caribbean', 'Low income',\n",
" 'Heavily indebted poor countries (HIPC)', 'Pre-demographic dividend',\n",
" 'Europe & Central Asia', 'Least developed countries: UN classification',\n",
" 'Sub-Saharan Africa (excluding high income)',\n",
" 'Sub-Saharan Africa (IDA & IBRD countries)', 'Sub-Saharan Africa',\n",
" 'IDA only', 'Post-demographic dividend', 'High income', 'OECD members',\n",
" 'India', 'China', 'IDA total', 'South Asia (IDA & IBRD)', 'South Asia',\n",
" 'East Asia & Pacific (IDA & IBRD countries)',\n",
" 'East Asia & Pacific (excluding high income)',\n",
" 'Late-demographic dividend', 'East Asia & Pacific',\n",
" 'Upper middle income', 'Lower middle income',\n",
" 'Early-demographic dividend', 'IBRD only', 'Middle income',\n",
" 'Low & middle income', 'IDA & IBRD total', 'World'],\n",
" dtype='object', name='country')"
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"data.loc[(slice(None), '2017-01-01'), :]['Population, total'].dropna(\n",
").sort_values().tail(60).index.get_level_values('country')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Extract zones manually (in order of increasing population)"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [],
"source": [
"zones = ['North America', 'Middle East & North Africa',\n",
" 'Latin America & Caribbean', 'Europe & Central Asia',\n",
" 'Sub-Saharan Africa', 'South Asia',\n",
" 'East Asia & Pacific'][::-1]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"And extract population information (and check total is right)"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [],
"source": [
"population = data.loc[zones]['Population, total'].swaplevel().unstack()\n",
"population = population[zones]\n",
"assert all(data.loc['World']['Population, total'] == population.sum(axis=1))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Stacked area plot with matplotlib"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [],
"source": [
"import matplotlib.pyplot as plt"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"
"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"plt.clf()\n",
"plt.figure(figsize=(10, 5), dpi=100)\n",
"plt.stackplot(population.index, population.values.T / 1e9)\n",
"plt.legend(population.columns, loc='upper left')\n",
"plt.ylabel('Population count (B)')\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Stacked bar plot with plotly"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
""
],
"text/vnd.plotly.v1+html": [
""
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"import plotly.offline as offline\n",
"import plotly.graph_objs as go\n",
"\n",
"offline.init_notebook_mode()"
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {},
"outputs": [
{
"data": {
"application/vnd.plotly.v1+json": {
"data": [
{
"name": "East Asia & Pacific",
"stackgroup": "World",
"type": "scatter",
"uid": "e5b08b9e-fcb8-4246-87db-4cb5b960bdf4",
"x": [
"1960-01-01",
"1961-01-01",
"1962-01-01",
"1963-01-01",
"1964-01-01",
"1965-01-01",
"1966-01-01",
"1967-01-01",
"1968-01-01",
"1969-01-01",
"1970-01-01",
"1971-01-01",
"1972-01-01",
"1973-01-01",
"1974-01-01",
"1975-01-01",
"1976-01-01",
"1977-01-01",
"1978-01-01",
"1979-01-01",
"1980-01-01",
"1981-01-01",
"1982-01-01",
"1983-01-01",
"1984-01-01",
"1985-01-01",
"1986-01-01",
"1987-01-01",
"1988-01-01",
"1989-01-01",
"1990-01-01",
"1991-01-01",
"1992-01-01",
"1993-01-01",
"1994-01-01",
"1995-01-01",
"1996-01-01",
"1997-01-01",
"1998-01-01",
"1999-01-01",
"2000-01-01",
"2001-01-01",
"2002-01-01",
"2003-01-01",
"2004-01-01",
"2005-01-01",
"2006-01-01",
"2007-01-01",
"2008-01-01",
"2009-01-01",
"2010-01-01",
"2011-01-01",
"2012-01-01",
"2013-01-01",
"2014-01-01",
"2015-01-01",
"2016-01-01",
"2017-01-01"
],
"y": [
1039944591,
1043546747,
1058028199,
1083802299,
1109204182,
1135650895,
1165517894,
1194424326,
1223721283,
1256501008,
1289258962,
1322942750,
1354794332,
1385052014,
1415128313,
1442241325,
1466464716,
1489361661,
1512159741,
1535391301,
1558178982,
1581806986,
1607731269,
1633628203,
1658253758,
1683448851,
1710171170,
1738276268,
1766656203,
1794408755,
1821481246,
1847530233,
1871898268,
1895356643,
1918771287,
1941918800,
1964635058,
1986799861,
2008140141,
2028095314,
2047150745,
2065521836,
2082953527,
2099545576,
2115557365,
2131363078,
2147029631,
2162104019,
2177421759,
2192352319,
2207154641,
2221934584,
2237082761,
2252311022,
2267745366,
2283108073,
2298726779,
2314364990
]
},
{
"name": "South Asia",
"stackgroup": "World",
"type": "scatter",
"uid": "5ccdb42f-dbf4-4abc-a1de-851c1114c31b",
"x": [
"1960-01-01",
"1961-01-01",
"1962-01-01",
"1963-01-01",
"1964-01-01",
"1965-01-01",
"1966-01-01",
"1967-01-01",
"1968-01-01",
"1969-01-01",
"1970-01-01",
"1971-01-01",
"1972-01-01",
"1973-01-01",
"1974-01-01",
"1975-01-01",
"1976-01-01",
"1977-01-01",
"1978-01-01",
"1979-01-01",
"1980-01-01",
"1981-01-01",
"1982-01-01",
"1983-01-01",
"1984-01-01",
"1985-01-01",
"1986-01-01",
"1987-01-01",
"1988-01-01",
"1989-01-01",
"1990-01-01",
"1991-01-01",
"1992-01-01",
"1993-01-01",
"1994-01-01",
"1995-01-01",
"1996-01-01",
"1997-01-01",
"1998-01-01",
"1999-01-01",
"2000-01-01",
"2001-01-01",
"2002-01-01",
"2003-01-01",
"2004-01-01",
"2005-01-01",
"2006-01-01",
"2007-01-01",
"2008-01-01",
"2009-01-01",
"2010-01-01",
"2011-01-01",
"2012-01-01",
"2013-01-01",
"2014-01-01",
"2015-01-01",
"2016-01-01",
"2017-01-01"
],
"y": [
571835666,
583894094,
596413939,
609391805,
622822615,
636701820,
651036352,
665826653,
681054882,
696697198,
712740919,
729173562,
746012374,
763310561,
781140577,
799553306,
818560436,
838142287,
858277856,
878933031,
900076467,
921696915,
943781613,
966293643,
989188965,
1012429641,
1035982524,
1059829211,
1083963380,
1108386444,
1133089464,
1158058109,
1183253534,
1208612942,
1234059205,
1259530819,
1284978193,
1310387887,
1335777637,
1361185289,
1386625845,
1412104373,
1437568227,
1462906674,
1487975237,
1512670560,
1536943534,
1560818860,
1584359049,
1607663899,
1630806784,
1653798614,
1676615491,
1699310450,
1721847786,
1744199944,
1766393714,
1788388852
]
},
{
"name": "Sub-Saharan Africa",
"stackgroup": "World",
"type": "scatter",
"uid": "d81b1f54-9dcc-475b-9517-2127417ea48f",
"x": [
"1960-01-01",
"1961-01-01",
"1962-01-01",
"1963-01-01",
"1964-01-01",
"1965-01-01",
"1966-01-01",
"1967-01-01",
"1968-01-01",
"1969-01-01",
"1970-01-01",
"1971-01-01",
"1972-01-01",
"1973-01-01",
"1974-01-01",
"1975-01-01",
"1976-01-01",
"1977-01-01",
"1978-01-01",
"1979-01-01",
"1980-01-01",
"1981-01-01",
"1982-01-01",
"1983-01-01",
"1984-01-01",
"1985-01-01",
"1986-01-01",
"1987-01-01",
"1988-01-01",
"1989-01-01",
"1990-01-01",
"1991-01-01",
"1992-01-01",
"1993-01-01",
"1994-01-01",
"1995-01-01",
"1996-01-01",
"1997-01-01",
"1998-01-01",
"1999-01-01",
"2000-01-01",
"2001-01-01",
"2002-01-01",
"2003-01-01",
"2004-01-01",
"2005-01-01",
"2006-01-01",
"2007-01-01",
"2008-01-01",
"2009-01-01",
"2010-01-01",
"2011-01-01",
"2012-01-01",
"2013-01-01",
"2014-01-01",
"2015-01-01",
"2016-01-01",
"2017-01-01"
],
"y": [
228586005,
234008580,
239647139,
245503266,
251576403,
257868609,
264386171,
271139271,
278138996,
285397999,
292929074,
300737023,
308831549,
317234127,
325972033,
335064879,
344522341,
354343159,
364515830,
375034981,
385885281,
397065556,
408577033,
420413518,
432573431,
445048059,
457835414,
470938971,
484357710,
498102752,
512177101,
526599014,
541365685,
556451898,
571828603,
587469624,
603385639,
619607956,
636182577,
653179863,
670649638,
688615995,
707099850,
726140617,
745788118,
766080507,
787035792,
808660166,
830965556,
853953657,
877628367,
901989926,
927039875,
952734072,
979017918,
1005850049,
1033212743,
1061107721
]
},
{
"name": "Europe & Central Asia",
"stackgroup": "World",
"type": "scatter",
"uid": "41d2b519-5c2e-4f44-a2bf-af5275db6ae1",
"x": [
"1960-01-01",
"1961-01-01",
"1962-01-01",
"1963-01-01",
"1964-01-01",
"1965-01-01",
"1966-01-01",
"1967-01-01",
"1968-01-01",
"1969-01-01",
"1970-01-01",
"1971-01-01",
"1972-01-01",
"1973-01-01",
"1974-01-01",
"1975-01-01",
"1976-01-01",
"1977-01-01",
"1978-01-01",
"1979-01-01",
"1980-01-01",
"1981-01-01",
"1982-01-01",
"1983-01-01",
"1984-01-01",
"1985-01-01",
"1986-01-01",
"1987-01-01",
"1988-01-01",
"1989-01-01",
"1990-01-01",
"1991-01-01",
"1992-01-01",
"1993-01-01",
"1994-01-01",
"1995-01-01",
"1996-01-01",
"1997-01-01",
"1998-01-01",
"1999-01-01",
"2000-01-01",
"2001-01-01",
"2002-01-01",
"2003-01-01",
"2004-01-01",
"2005-01-01",
"2006-01-01",
"2007-01-01",
"2008-01-01",
"2009-01-01",
"2010-01-01",
"2011-01-01",
"2012-01-01",
"2013-01-01",
"2014-01-01",
"2015-01-01",
"2016-01-01",
"2017-01-01"
],
"y": [
667246384,
674972972,
682938669,
690962675,
698905664,
706609007,
713341112,
719879789,
726161895,
732317863,
737948178,
743607439,
749815857,
755867961,
761701324,
767332578,
772838318,
778094845,
783298994,
788525199,
793937090,
799215272,
803972967,
808524728,
813281381,
818146882,
823155058,
828213790,
833315236,
838462813,
842848473,
846178276,
849656745,
852762016,
854723055,
856352860,
857652705,
859112733,
860236341,
861380108,
862304086,
863615632,
865196877,
867457664,
870030756,
872661616,
875343235,
878465990,
881965815,
885591814,
889016507,
891098854,
894679968,
898881448,
903123160,
907426233,
911686319,
915545801
]
},
{
"name": "Latin America & Caribbean",
"stackgroup": "World",
"type": "scatter",
"uid": "dd6bc133-ee5c-4ca8-8457-ddf476dc6ab2",
"x": [
"1960-01-01",
"1961-01-01",
"1962-01-01",
"1963-01-01",
"1964-01-01",
"1965-01-01",
"1966-01-01",
"1967-01-01",
"1968-01-01",
"1969-01-01",
"1970-01-01",
"1971-01-01",
"1972-01-01",
"1973-01-01",
"1974-01-01",
"1975-01-01",
"1976-01-01",
"1977-01-01",
"1978-01-01",
"1979-01-01",
"1980-01-01",
"1981-01-01",
"1982-01-01",
"1983-01-01",
"1984-01-01",
"1985-01-01",
"1986-01-01",
"1987-01-01",
"1988-01-01",
"1989-01-01",
"1990-01-01",
"1991-01-01",
"1992-01-01",
"1993-01-01",
"1994-01-01",
"1995-01-01",
"1996-01-01",
"1997-01-01",
"1998-01-01",
"1999-01-01",
"2000-01-01",
"2001-01-01",
"2002-01-01",
"2003-01-01",
"2004-01-01",
"2005-01-01",
"2006-01-01",
"2007-01-01",
"2008-01-01",
"2009-01-01",
"2010-01-01",
"2011-01-01",
"2012-01-01",
"2013-01-01",
"2014-01-01",
"2015-01-01",
"2016-01-01",
"2017-01-01"
],
"y": [
220434662,
226564469,
232897323,
239401268,
246016368,
252710310,
259468669,
266295880,
273209036,
280225795,
287361389,
294620137,
301984430,
309446959,
316987646,
324590837,
332247800,
339956419,
347735305,
355593111,
363543431,
371580994,
379697683,
387868173,
396059351,
404246768,
412413602,
420560827,
428701267,
436857131,
445044474,
453251622,
461466819,
469673465,
477832467,
485913138,
493920488,
501837820,
509664957,
517324344,
524829251,
532172709,
539372044,
546478662,
553563090,
560673962,
567821716,
574994397,
582179826,
589349327,
596478519,
603537118,
610547919,
617495658,
624335544,
631062657,
637663890,
644137666
]
},
{
"name": "Middle East & North Africa",
"stackgroup": "World",
"type": "scatter",
"uid": "ed1e8fae-72d2-495c-b82c-946c6c4a74bd",
"x": [
"1960-01-01",
"1961-01-01",
"1962-01-01",
"1963-01-01",
"1964-01-01",
"1965-01-01",
"1966-01-01",
"1967-01-01",
"1968-01-01",
"1969-01-01",
"1970-01-01",
"1971-01-01",
"1972-01-01",
"1973-01-01",
"1974-01-01",
"1975-01-01",
"1976-01-01",
"1977-01-01",
"1978-01-01",
"1979-01-01",
"1980-01-01",
"1981-01-01",
"1982-01-01",
"1983-01-01",
"1984-01-01",
"1985-01-01",
"1986-01-01",
"1987-01-01",
"1988-01-01",
"1989-01-01",
"1990-01-01",
"1991-01-01",
"1992-01-01",
"1993-01-01",
"1994-01-01",
"1995-01-01",
"1996-01-01",
"1997-01-01",
"1998-01-01",
"1999-01-01",
"2000-01-01",
"2001-01-01",
"2002-01-01",
"2003-01-01",
"2004-01-01",
"2005-01-01",
"2006-01-01",
"2007-01-01",
"2008-01-01",
"2009-01-01",
"2010-01-01",
"2011-01-01",
"2012-01-01",
"2013-01-01",
"2014-01-01",
"2015-01-01",
"2016-01-01",
"2017-01-01"
],
"y": [
105488678,
108374227,
111385940,
114471415,
117671617,
120973578,
124374455,
127947266,
131566224,
135279930,
139083819,
142950993,
146887303,
150999871,
155259901,
159722643,
164407528,
169318424,
174493349,
180000521,
185843847,
192015875,
198499602,
205229901,
212102793,
219095273,
226161475,
233285188,
240367135,
247283550,
255989130,
262659662,
267020622,
273204804,
279279333,
286917385,
292934005,
298982946,
305001541,
311053183,
317129227,
323196354,
329289435,
335522845,
342046777,
348956287,
356287693,
363996317,
371999662,
380192587,
388376106,
396573248,
404783020,
412953000,
421030035,
428974903,
436738031,
444322417
]
},
{
"name": "North America",
"stackgroup": "World",
"type": "scatter",
"uid": "1664abd5-63da-48bf-a458-41469dab41cb",
"x": [
"1960-01-01",
"1961-01-01",
"1962-01-01",
"1963-01-01",
"1964-01-01",
"1965-01-01",
"1966-01-01",
"1967-01-01",
"1968-01-01",
"1969-01-01",
"1970-01-01",
"1971-01-01",
"1972-01-01",
"1973-01-01",
"1974-01-01",
"1975-01-01",
"1976-01-01",
"1977-01-01",
"1978-01-01",
"1979-01-01",
"1980-01-01",
"1981-01-01",
"1982-01-01",
"1983-01-01",
"1984-01-01",
"1985-01-01",
"1986-01-01",
"1987-01-01",
"1988-01-01",
"1989-01-01",
"1990-01-01",
"1991-01-01",
"1992-01-01",
"1993-01-01",
"1994-01-01",
"1995-01-01",
"1996-01-01",
"1997-01-01",
"1998-01-01",
"1999-01-01",
"2000-01-01",
"2001-01-01",
"2002-01-01",
"2003-01-01",
"2004-01-01",
"2005-01-01",
"2006-01-01",
"2007-01-01",
"2008-01-01",
"2009-01-01",
"2010-01-01",
"2011-01-01",
"2012-01-01",
"2013-01-01",
"2014-01-01",
"2015-01-01",
"2016-01-01",
"2017-01-01"
],
"y": [
198624409,
202007500,
205198600,
208253700,
211262900,
214031100,
216659000,
219176000,
221503000,
223759000,
226431000,
229361135,
231943831,
234332208,
236681487,
239235000,
241606200,
244088400,
246674600,
249385800,
251872670,
254421050,
256921449,
259303930,
261583423,
263922898,
266394382,
268896849,
271452347,
274256841,
277473326,
281211703,
285092192,
288811320,
292297226,
295691746,
299126029,
302704697,
306162843,
309600485,
312993944,
316113359,
319050105,
321847258,
324864038,
327892753,
331014940,
334184023,
337405012,
340465736,
343408819,
346051624,
348808615,
351451876,
354223012,
356937591,
359735880,
362492702
]
}
],
"layout": {
"title": "World population"
}
},
"text/html": [
""
],
"text/vnd.plotly.v1+html": [
""
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"data = [go.Scatter(x=population.index, y=population[zone], name=zone, stackgroup='World')\n",
" for zone in zones]\n",
"fig = go.Figure(data=data,\n",
" layout=go.Layout(title='World population'))\n",
"offline.iplot(fig)"
]
}
],
"metadata": {
"jupytext": {
"cell_markers": "region,endregion",
"formats": "ipynb,.pct.py:percent,.lgt.py:light,.spx.py:sphinx,md,Rmd,.pandoc.md:pandoc"
},
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.7.3"
},
"toc": {
"base_numbering": 1,
"nav_menu": {},
"number_sections": true,
"sideBar": true,
"skip_h1_title": false,
"title_cell": "Table of Contents",
"title_sidebar": "Contents",
"toc_cell": false,
"toc_position": {},
"toc_section_display": true,
"toc_window_display": false
}
},
"nbformat": 4,
"nbformat_minor": 2
}
================================================
FILE: demo/World population.lgt.py
================================================
# ---
# jupyter:
# jupytext:
# cell_markers: region,endregion
# formats: ipynb,.pct.py:percent,.lgt.py:light,.spx.py:sphinx,md,Rmd,.pandoc.md:pandoc
# text_representation:
# extension: .py
# format_name: light
# format_version: '1.4'
# jupytext_version: 1.1.0
# kernelspec:
# display_name: Python 3
# language: python
# name: python3
# ---
# # A quick insight at world population
#
# ## Collecting population data
#
# In the below we retrieve population data from the
# [World Bank](http://www.worldbank.org/)
# using the [wbdata](https://github.com/OliverSherouse/wbdata) python package
# region
import pandas as pd
import wbdata as wb
pd.options.display.max_rows = 6
pd.options.display.max_columns = 20
# endregion
# Corresponding indicator is found using search method - or, directly,
# the World Bank site.
wb.search_indicators('Population, total') # SP.POP.TOTL
# wb.search_indicators('area')
# => https://data.worldbank.org/indicator is easier to use
# Now we download the population data
indicators = {'SP.POP.TOTL': 'Population, total',
'AG.SRF.TOTL.K2': 'Surface area (sq. km)',
'AG.LND.TOTL.K2': 'Land area (sq. km)',
'AG.LND.ARBL.ZS': 'Arable land (% of land area)'}
data = wb.get_dataframe(indicators, convert_date=True).sort_index()
data
# World is one of the countries
data.loc['World']
# Can we classify over continents?
data.loc[(slice(None), '2017-01-01'), :]['Population, total'].dropna(
).sort_values().tail(60).index.get_level_values('country')
# Extract zones manually (in order of increasing population)
zones = ['North America', 'Middle East & North Africa',
'Latin America & Caribbean', 'Europe & Central Asia',
'Sub-Saharan Africa', 'South Asia',
'East Asia & Pacific'][::-1]
# And extract population information (and check total is right)
population = data.loc[zones]['Population, total'].swaplevel().unstack()
population = population[zones]
assert all(data.loc['World']['Population, total'] == population.sum(axis=1))
# ## Stacked area plot with matplotlib
import matplotlib.pyplot as plt
plt.clf()
plt.figure(figsize=(10, 5), dpi=100)
plt.stackplot(population.index, population.values.T / 1e9)
plt.legend(population.columns, loc='upper left')
plt.ylabel('Population count (B)')
plt.show()
# ## Stacked bar plot with plotly
# region
import plotly.offline as offline
import plotly.graph_objs as go
offline.init_notebook_mode()
# endregion
data = [go.Scatter(x=population.index, y=population[zone], name=zone, stackgroup='World')
for zone in zones]
fig = go.Figure(data=data,
layout=go.Layout(title='World population'))
offline.iplot(fig)
================================================
FILE: demo/World population.md
================================================
---
jupyter:
jupytext:
cell_markers: region,endregion
formats: ipynb,.pct.py:percent,.lgt.py:light,.spx.py:sphinx,md,Rmd,.pandoc.md:pandoc
text_representation:
extension: .md
format_name: markdown
format_version: '1.1'
jupytext_version: 1.1.0
kernelspec:
display_name: Python 3
language: python
name: python3
---
# A quick insight at world population
## Collecting population data
In the below we retrieve population data from the
[World Bank](http://www.worldbank.org/)
using the [wbdata](https://github.com/OliverSherouse/wbdata) python package
```python
import pandas as pd
import wbdata as wb
pd.options.display.max_rows = 6
pd.options.display.max_columns = 20
```
Corresponding indicator is found using search method - or, directly,
the World Bank site.
```python
wb.search_indicators('Population, total') # SP.POP.TOTL
# wb.search_indicators('area')
# => https://data.worldbank.org/indicator is easier to use
```
Now we download the population data
```python
indicators = {'SP.POP.TOTL': 'Population, total',
'AG.SRF.TOTL.K2': 'Surface area (sq. km)',
'AG.LND.TOTL.K2': 'Land area (sq. km)',
'AG.LND.ARBL.ZS': 'Arable land (% of land area)'}
data = wb.get_dataframe(indicators, convert_date=True).sort_index()
data
```
World is one of the countries
```python
data.loc['World']
```
Can we classify over continents?
```python
data.loc[(slice(None), '2017-01-01'), :]['Population, total'].dropna(
).sort_values().tail(60).index.get_level_values('country')
```
Extract zones manually (in order of increasing population)
```python
zones = ['North America', 'Middle East & North Africa',
'Latin America & Caribbean', 'Europe & Central Asia',
'Sub-Saharan Africa', 'South Asia',
'East Asia & Pacific'][::-1]
```
And extract population information (and check total is right)
```python
population = data.loc[zones]['Population, total'].swaplevel().unstack()
population = population[zones]
assert all(data.loc['World']['Population, total'] == population.sum(axis=1))
```
## Stacked area plot with matplotlib
```python
import matplotlib.pyplot as plt
```
```python
plt.clf()
plt.figure(figsize=(10, 5), dpi=100)
plt.stackplot(population.index, population.values.T / 1e9)
plt.legend(population.columns, loc='upper left')
plt.ylabel('Population count (B)')
plt.show()
```
## Stacked bar plot with plotly
```python
import plotly.offline as offline
import plotly.graph_objs as go
offline.init_notebook_mode()
```
```python
data = [go.Scatter(x=population.index, y=population[zone], name=zone, stackgroup='World')
for zone in zones]
fig = go.Figure(data=data,
layout=go.Layout(title='World population'))
offline.iplot(fig)
```
================================================
FILE: demo/World population.myst.md
================================================
---
jupytext:
formats: ipynb,.pct.py:percent,.lgt.py:light,.spx.py:sphinx,md,Rmd,.pandoc.md:pandoc,.myst.md:myst
text_representation:
extension: '.md'
format_name: myst
format_version: '0.7'
jupytext_version: 1.4.0+dev
kernelspec:
display_name: Python 3
language: python
name: python3
---
# A quick insight at world population
## Collecting population data
In the below we retrieve population data from the
[World Bank](http://www.worldbank.org/)
using the [wbdata](https://github.com/OliverSherouse/wbdata) python package
```{code-cell} ipython3
import pandas as pd
import wbdata as wb
pd.options.display.max_rows = 6
pd.options.display.max_columns = 20
```
Corresponding indicator is found using search method - or, directly,
the World Bank site.
```{code-cell} ipython3
wb.search_indicators('Population, total') # SP.POP.TOTL
# wb.search_indicators('area')
# => https://data.worldbank.org/indicator is easier to use
```
Now we download the population data
```{code-cell} ipython3
indicators = {'SP.POP.TOTL': 'Population, total',
'AG.SRF.TOTL.K2': 'Surface area (sq. km)',
'AG.LND.TOTL.K2': 'Land area (sq. km)',
'AG.LND.ARBL.ZS': 'Arable land (% of land area)'}
data = wb.get_dataframe(indicators, convert_date=True).sort_index()
data
```
World is one of the countries
```{code-cell} ipython3
data.loc['World']
```
Can we classify over continents?
```{code-cell} ipython3
data.loc[(slice(None), '2017-01-01'), :]['Population, total'].dropna(
).sort_values().tail(60).index.get_level_values('country')
```
Extract zones manually (in order of increasing population)
```{code-cell} ipython3
zones = ['North America', 'Middle East & North Africa',
'Latin America & Caribbean', 'Europe & Central Asia',
'Sub-Saharan Africa', 'South Asia',
'East Asia & Pacific'][::-1]
```
And extract population information (and check total is right)
```{code-cell} ipython3
population = data.loc[zones]['Population, total'].swaplevel().unstack()
population = population[zones]
assert all(data.loc['World']['Population, total'] == population.sum(axis=1))
```
## Stacked area plot with matplotlib
```{code-cell} ipython3
import matplotlib.pyplot as plt
```
```{code-cell} ipython3
plt.clf()
plt.figure(figsize=(10, 5), dpi=100)
plt.stackplot(population.index, population.values.T / 1e9)
plt.legend(population.columns, loc='upper left')
plt.ylabel('Population count (B)')
plt.show()
```
## Stacked bar plot with plotly
+++
Stacked area plots (with cumulated values computed depending on
selected legends) are
[on their way](https://github.com/plotly/plotly.js/pull/2960) at Plotly. For
now we just do a stacked bar plot.
```{code-cell} ipython3
import plotly.offline as offline
import plotly.graph_objs as go
offline.init_notebook_mode()
```
```{code-cell} ipython3
bars = [go.Bar(x=population.index, y=population[zone], name=zone)
for zone in zones]
fig = go.Figure(data=bars,
layout=go.Layout(title='World population',
barmode='stack'))
offline.iplot(fig)
```
================================================
FILE: demo/World population.pandoc.md
================================================
---
jupyter:
jupytext:
cell_markers: 'region,endregion'
formats: 'ipynb,.pct.py:percent,.lgt.py:light,.spx.py:sphinx,md,Rmd,.pandoc.md:pandoc'
text_representation:
extension: '.md'
format_name: pandoc
format_version: '2.7.2'
jupytext_version: '1.1.0'
kernelspec:
display_name: Python 3
language: python
name: python3
nbformat: 4
nbformat_minor: 2
---
::: {.cell .markdown}
# A quick insight at world population
## Collecting population data
In the below we retrieve population data from the
[World Bank](http://www.worldbank.org/)
using the [wbdata](https://github.com/OliverSherouse/wbdata) python package
:::
::: {.cell .code}
``` {.python}
import pandas as pd
import wbdata as wb
pd.options.display.max_rows = 6
pd.options.display.max_columns = 20
```
:::
::: {.cell .markdown}
Corresponding indicator is found using search method - or, directly,
the World Bank site.
:::
::: {.cell .code}
``` {.python}
wb.search_indicators('Population, total') # SP.POP.TOTL
# wb.search_indicators('area')
# => https://data.worldbank.org/indicator is easier to use
```
:::
::: {.cell .markdown}
Now we download the population data
:::
::: {.cell .code}
``` {.python}
indicators = {'SP.POP.TOTL': 'Population, total',
'AG.SRF.TOTL.K2': 'Surface area (sq. km)',
'AG.LND.TOTL.K2': 'Land area (sq. km)',
'AG.LND.ARBL.ZS': 'Arable land (% of land area)'}
data = wb.get_dataframe(indicators, convert_date=True).sort_index()
data
```
:::
::: {.cell .markdown}
World is one of the countries
:::
::: {.cell .code}
``` {.python}
data.loc['World']
```
:::
::: {.cell .markdown}
Can we classify over continents?
:::
::: {.cell .code}
``` {.python}
data.loc[(slice(None), '2017-01-01'), :]['Population, total'].dropna(
).sort_values().tail(60).index.get_level_values('country')
```
:::
::: {.cell .markdown}
Extract zones manually (in order of increasing population)
:::
::: {.cell .code}
``` {.python}
zones = ['North America', 'Middle East & North Africa',
'Latin America & Caribbean', 'Europe & Central Asia',
'Sub-Saharan Africa', 'South Asia',
'East Asia & Pacific'][::-1]
```
:::
::: {.cell .markdown}
And extract population information (and check total is right)
:::
::: {.cell .code}
``` {.python}
population = data.loc[zones]['Population, total'].swaplevel().unstack()
population = population[zones]
assert all(data.loc['World']['Population, total'] == population.sum(axis=1))
```
:::
::: {.cell .markdown}
## Stacked area plot with matplotlib
:::
::: {.cell .code}
``` {.python}
import matplotlib.pyplot as plt
```
:::
::: {.cell .code}
``` {.python}
plt.clf()
plt.figure(figsize=(10, 5), dpi=100)
plt.stackplot(population.index, population.values.T / 1e9)
plt.legend(population.columns, loc='upper left')
plt.ylabel('Population count (B)')
plt.show()
```
:::
::: {.cell .markdown}
## Stacked bar plot with plotly
:::
::: {.cell .code}
``` {.python}
import plotly.offline as offline
import plotly.graph_objs as go
offline.init_notebook_mode()
```
:::
::: {.cell .code}
``` {.python}
data = [go.Scatter(x=population.index, y=population[zone], name=zone, stackgroup='World')
for zone in zones]
fig = go.Figure(data=data,
layout=go.Layout(title='World population'))
offline.iplot(fig)
```
:::
================================================
FILE: demo/World population.pct.py
================================================
# ---
# jupyter:
# jupytext:
# cell_markers: region,endregion
# formats: ipynb,.pct.py:percent,.lgt.py:light,.spx.py:sphinx,md,Rmd,.pandoc.md:pandoc
# text_representation:
# extension: .py
# format_name: percent
# format_version: '1.2'
# jupytext_version: 1.1.0
# kernelspec:
# display_name: Python 3
# language: python
# name: python3
# ---
# %% [markdown]
# # A quick insight at world population
#
# ## Collecting population data
#
# In the below we retrieve population data from the
# [World Bank](http://www.worldbank.org/)
# using the [wbdata](https://github.com/OliverSherouse/wbdata) python package
# %%
import pandas as pd
import wbdata as wb
pd.options.display.max_rows = 6
pd.options.display.max_columns = 20
# %% [markdown]
# Corresponding indicator is found using search method - or, directly,
# the World Bank site.
# %%
wb.search_indicators('Population, total') # SP.POP.TOTL
# wb.search_indicators('area')
# => https://data.worldbank.org/indicator is easier to use
# %% [markdown]
# Now we download the population data
# %%
indicators = {'SP.POP.TOTL': 'Population, total',
'AG.SRF.TOTL.K2': 'Surface area (sq. km)',
'AG.LND.TOTL.K2': 'Land area (sq. km)',
'AG.LND.ARBL.ZS': 'Arable land (% of land area)'}
data = wb.get_dataframe(indicators, convert_date=True).sort_index()
data
# %% [markdown]
# World is one of the countries
# %%
data.loc['World']
# %% [markdown]
# Can we classify over continents?
# %%
data.loc[(slice(None), '2017-01-01'), :]['Population, total'].dropna(
).sort_values().tail(60).index.get_level_values('country')
# %% [markdown]
# Extract zones manually (in order of increasing population)
# %%
zones = ['North America', 'Middle East & North Africa',
'Latin America & Caribbean', 'Europe & Central Asia',
'Sub-Saharan Africa', 'South Asia',
'East Asia & Pacific'][::-1]
# %% [markdown]
# And extract population information (and check total is right)
# %%
population = data.loc[zones]['Population, total'].swaplevel().unstack()
population = population[zones]
assert all(data.loc['World']['Population, total'] == population.sum(axis=1))
# %% [markdown]
# ## Stacked area plot with matplotlib
# %%
import matplotlib.pyplot as plt
# %%
plt.clf()
plt.figure(figsize=(10, 5), dpi=100)
plt.stackplot(population.index, population.values.T / 1e9)
plt.legend(population.columns, loc='upper left')
plt.ylabel('Population count (B)')
plt.show()
# %% [markdown]
# ## Stacked bar plot with plotly
# %%
import plotly.offline as offline
import plotly.graph_objs as go
offline.init_notebook_mode()
# %%
data = [go.Scatter(x=population.index, y=population[zone], name=zone, stackgroup='World')
for zone in zones]
fig = go.Figure(data=data,
layout=go.Layout(title='World population'))
offline.iplot(fig)
================================================
FILE: demo/World population.spx.py
================================================
# ---
# jupyter:
# jupytext:
# cell_markers: region,endregion
# formats: ipynb,.pct.py:percent,.lgt.py:light,.spx.py:sphinx,md,Rmd,.pandoc.md:pandoc
# text_representation:
# extension: .py
# format_name: sphinx
# format_version: '1.1'
# jupytext_version: 1.1.0
# kernelspec:
# display_name: Python 3
# language: python
# name: python3
# ---
"""
# A quick insight at world population
## Collecting population data
In the below we retrieve population data from the
[World Bank](http://www.worldbank.org/)
using the [wbdata](https://github.com/OliverSherouse/wbdata) python package
"""
import pandas as pd
import wbdata as wb
pd.options.display.max_rows = 6
pd.options.display.max_columns = 20
###############################################################################
# Corresponding indicator is found using search method - or, directly,
# the World Bank site.
wb.search_indicators('Population, total') # SP.POP.TOTL
# wb.search_indicators('area')
# => https://data.worldbank.org/indicator is easier to use
###############################################################################
# Now we download the population data
indicators = {'SP.POP.TOTL': 'Population, total',
'AG.SRF.TOTL.K2': 'Surface area (sq. km)',
'AG.LND.TOTL.K2': 'Land area (sq. km)',
'AG.LND.ARBL.ZS': 'Arable land (% of land area)'}
data = wb.get_dataframe(indicators, convert_date=True).sort_index()
data
###############################################################################
# World is one of the countries
data.loc['World']
###############################################################################
# Can we classify over continents?
data.loc[(slice(None), '2017-01-01'), :]['Population, total'].dropna(
).sort_values().tail(60).index.get_level_values('country')
###############################################################################
# Extract zones manually (in order of increasing population)
zones = ['North America', 'Middle East & North Africa',
'Latin America & Caribbean', 'Europe & Central Asia',
'Sub-Saharan Africa', 'South Asia',
'East Asia & Pacific'][::-1]
###############################################################################
# And extract population information (and check total is right)
population = data.loc[zones]['Population, total'].swaplevel().unstack()
population = population[zones]
assert all(data.loc['World']['Population, total'] == population.sum(axis=1))
###############################################################################
# ## Stacked area plot with matplotlib
import matplotlib.pyplot as plt
""
plt.clf()
plt.figure(figsize=(10, 5), dpi=100)
plt.stackplot(population.index, population.values.T / 1e9)
plt.legend(population.columns, loc='upper left')
plt.ylabel('Population count (B)')
plt.show()
###############################################################################
# ## Stacked bar plot with plotly
import plotly.offline as offline
import plotly.graph_objs as go
offline.init_notebook_mode()
""
data = [go.Scatter(x=population.index, y=population[zone], name=zone, stackgroup='World')
for zone in zones]
fig = go.Figure(data=data,
layout=go.Layout(title='World population'))
offline.iplot(fig)
================================================
FILE: demo/get_started.md
================================================
---
jupyter:
jupytext:
formats: ipynb,md
text_representation:
extension: .md
format_name: markdown
format_version: '1.1'
jupytext_version: 1.1.6
kernelspec:
display_name: Python 3
language: python
name: python3
---
# Getting started with Jupytext
This small notebook shows you how to activate Jupytext in the JupyterLab
environment. We'll show you a few things that you can do with Jupytext and
a bit of what happens under the hood.
**Note: to run this notebook locally, you need to first follow the Jupytext
installation instructions and activate the JupyterLab plugin. If you're on
Binder, it should already work.**
## Enabling Jupytext in a new notebook
This notebook is brand new - it hasn't had any special extra metadata added
to it.
If we want Jupytext to save files in multiple formats automatically,
we can use the JupyterLab **command palette** to do so.
* In the _View_ menu, click on _Activate Command Palette_
* Then type **`Jupytext`**. You should see a number of commands come up. Each
one tells Jupytext to save the notebook in a different
file format automatically.
* Select **Pair notebook with MyST Markdown**
That's it! If you have Jupytext installed, it will now save your notebook in
markdown format automatically when you save this `.ipynb` file
**in addition to** saving the `.ipynb` file itself.
After you've done this, save the notebook. You should now see a new file called
**`get_started.md`** in the same directory as this notebook.
## How does Jupytext know to do this?
Jupytext uses notebook-level metadata to keep track of what formats are paired
with a notebook. Below we'll print the metadata of this notebook so you can see
what the Jupytext metadata looks like.
```python
import nbformat as nbf
from IPython.display import JSON
notebook = nbf.read('./get_started.ipynb', nbf.NO_CONVERT)
JSON(notebook['metadata'])
```
As you select different formats from the command palette (following the instructions
above) and save the notebook, you'll see this metadata change.
## That's it!
Play around with different kinds of code and outputs to see how each is
converted into its corresponding text format. Here's a little Python code
to get you started:
```python
import numpy as np
import matplotlib.pyplot as plt
plt.scatter(*np.random.randn(2, 100), c=np.random.randn(100), s=np.random.rand(100)*100)
```
# Experiment with the demo notebook!
In the *`demo/`* folder for `jupytext` there is a notebook called **`World population.ipynb`**.
By default, saving the demo notebook will also create *many* possible Jupytext
outputs so you can see what each looks like and which you prefer.
================================================
FILE: demo/vscode/notebook.ipynb
================================================
{
"cells": [
{
"cell_type": "markdown",
"id": "fba07815",
"metadata": {},
"source": [
"This paired notebook can be edited either as\n",
"a Python script or as a Jupyter notebook in\n",
"VS Code. Thanks to the Jupytext Sync\n",
"extension, changes are automatically synced to\n",
"the other paired file(s) when you save.\n",
"\n",
"💡 Tip: don't forget to hit \"Save\" before\n",
"switching to the other editor. If you don't save\n",
"your changes, you will run into conflicting edits\n",
"as VS Code does not autoreload files that have\n",
"unsaved edits."
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "1e480a09",
"metadata": {},
"outputs": [],
"source": [
"import plotly.express as px"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "80da494e-465e-472b-b94b-cea9d4e49451",
"metadata": {},
"outputs": [
{
"data": {
"application/vnd.plotly.v1+json": {
"config": {
"plotlyServerURL": "https://plot.ly"
},
"data": [
{
"hovertemplate": "species=setosa sepal_width=%{x} sepal_length=%{y}",
"legendgroup": "setosa",
"marker": {
"color": "#636efa",
"symbol": "circle"
},
"mode": "markers",
"name": "setosa",
"orientation": "v",
"showlegend": true,
"type": "scatter",
"x": {
"bdata": "AAAAAAAADEAAAAAAAAAIQJqZmZmZmQlAzczMzMzMCEDNzMzMzMwMQDMzMzMzMw9AMzMzMzMzC0AzMzMzMzMLQDMzMzMzMwdAzczMzMzMCECamZmZmZkNQDMzMzMzMwtAAAAAAAAACEAAAAAAAAAIQAAAAAAAABBAmpmZmZmZEUAzMzMzMzMPQAAAAAAAAAxAZmZmZmZmDkBmZmZmZmYOQDMzMzMzMwtAmpmZmZmZDUDNzMzMzMwMQGZmZmZmZgpAMzMzMzMzC0AAAAAAAAAIQDMzMzMzMwtAAAAAAAAADEAzMzMzMzMLQJqZmZmZmQlAzczMzMzMCEAzMzMzMzMLQGZmZmZmZhBAzczMzMzMEEDNzMzMzMwIQJqZmZmZmQlAAAAAAAAADEDNzMzMzMwIQAAAAAAAAAhAMzMzMzMzC0AAAAAAAAAMQGZmZmZmZgJAmpmZmZmZCUAAAAAAAAAMQGZmZmZmZg5AAAAAAAAACEBmZmZmZmYOQJqZmZmZmQlAmpmZmZmZDUBmZmZmZmYKQA==",
"dtype": "f8"
},
"xaxis": "x",
"y": {
"bdata": "ZmZmZmZmFECamZmZmZkTQM3MzMzMzBJAZmZmZmZmEkAAAAAAAAAUQJqZmZmZmRVAZmZmZmZmEkAAAAAAAAAUQJqZmZmZmRFAmpmZmZmZE0CamZmZmZkVQDMzMzMzMxNAMzMzMzMzE0AzMzMzMzMRQDMzMzMzMxdAzczMzMzMFkCamZmZmZkVQGZmZmZmZhRAzczMzMzMFkBmZmZmZmYUQJqZmZmZmRVAZmZmZmZmFEBmZmZmZmYSQGZmZmZmZhRAMzMzMzMzE0AAAAAAAAAUQAAAAAAAABRAzczMzMzMFEDNzMzMzMwUQM3MzMzMzBJAMzMzMzMzE0CamZmZmZkVQM3MzMzMzBRAAAAAAAAAFkCamZmZmZkTQAAAAAAAABRAAAAAAAAAFkCamZmZmZkTQJqZmZmZmRFAZmZmZmZmFEAAAAAAAAAUQAAAAAAAABJAmpmZmZmZEUAAAAAAAAAUQGZmZmZmZhRAMzMzMzMzE0BmZmZmZmYUQGZmZmZmZhJAMzMzMzMzFUAAAAAAAAAUQA==",
"dtype": "f8"
},
"yaxis": "y"
},
{
"hovertemplate": "species=versicolor sepal_width=%{x} sepal_length=%{y}",
"legendgroup": "versicolor",
"marker": {
"color": "#EF553B",
"symbol": "circle"
},
"mode": "markers",
"name": "versicolor",
"orientation": "v",
"showlegend": true,
"type": "scatter",
"x": {
"bdata": "mpmZmZmZCUCamZmZmZkJQM3MzMzMzAhAZmZmZmZmAkBmZmZmZmYGQGZmZmZmZgZAZmZmZmZmCkAzMzMzMzMDQDMzMzMzMwdAmpmZmZmZBUAAAAAAAAAAQAAAAAAAAAhAmpmZmZmZAUAzMzMzMzMHQDMzMzMzMwdAzczMzMzMCEAAAAAAAAAIQJqZmZmZmQVAmpmZmZmZAUAAAAAAAAAEQJqZmZmZmQlAZmZmZmZmBkAAAAAAAAAEQGZmZmZmZgZAMzMzMzMzB0AAAAAAAAAIQGZmZmZmZgZAAAAAAAAACEAzMzMzMzMHQM3MzMzMzARAMzMzMzMzA0AzMzMzMzMDQJqZmZmZmQVAmpmZmZmZBUAAAAAAAAAIQDMzMzMzMwtAzczMzMzMCEBmZmZmZmYCQAAAAAAAAAhAAAAAAAAABEDNzMzMzMwEQAAAAAAAAAhAzczMzMzMBEBmZmZmZmYCQJqZmZmZmQVAAAAAAAAACEAzMzMzMzMHQDMzMzMzMwdAAAAAAAAABEBmZmZmZmYGQA==",
"dtype": "f8"
},
"xaxis": "x",
"y": {
"bdata": "AAAAAAAAHECamZmZmZkZQJqZmZmZmRtAAAAAAAAAFkAAAAAAAAAaQM3MzMzMzBZAMzMzMzMzGUCamZmZmZkTQGZmZmZmZhpAzczMzMzMFEAAAAAAAAAUQJqZmZmZmRdAAAAAAAAAGEBmZmZmZmYYQGZmZmZmZhZAzczMzMzMGkBmZmZmZmYWQDMzMzMzMxdAzczMzMzMGEBmZmZmZmYWQJqZmZmZmRdAZmZmZmZmGEAzMzMzMzMZQGZmZmZmZhhAmpmZmZmZGUBmZmZmZmYaQDMzMzMzMxtAzczMzMzMGkAAAAAAAAAYQM3MzMzMzBZAAAAAAAAAFkAAAAAAAAAWQDMzMzMzMxdAAAAAAAAAGECamZmZmZkVQAAAAAAAABhAzczMzMzMGkAzMzMzMzMZQGZmZmZmZhZAAAAAAAAAFkAAAAAAAAAWQGZmZmZmZhhAMzMzMzMzF0AAAAAAAAAUQGZmZmZmZhZAzczMzMzMFkDNzMzMzMwWQM3MzMzMzBhAZmZmZmZmFEDNzMzMzMwWQA==",
"dtype": "f8"
},
"yaxis": "y"
},
{
"hovertemplate": "species=virginica sepal_width=%{x} sepal_length=%{y}",
"legendgroup": "virginica",
"marker": {
"color": "#00cc96",
"symbol": "circle"
},
"mode": "markers",
"name": "virginica",
"orientation": "v",
"showlegend": true,
"type": "scatter",
"x": {
"bdata": "ZmZmZmZmCkCamZmZmZkFQAAAAAAAAAhAMzMzMzMzB0AAAAAAAAAIQAAAAAAAAAhAAAAAAAAABEAzMzMzMzMHQAAAAAAAAARAzczMzMzMDECamZmZmZkJQJqZmZmZmQVAAAAAAAAACEAAAAAAAAAEQGZmZmZmZgZAmpmZmZmZCUAAAAAAAAAIQGZmZmZmZg5AzczMzMzMBECamZmZmZkBQJqZmZmZmQlAZmZmZmZmBkBmZmZmZmYGQJqZmZmZmQVAZmZmZmZmCkCamZmZmZkJQGZmZmZmZgZAAAAAAAAACEBmZmZmZmYGQAAAAAAAAAhAZmZmZmZmBkBmZmZmZmYOQGZmZmZmZgZAZmZmZmZmBkDNzMzMzMwEQAAAAAAAAAhAMzMzMzMzC0DNzMzMzMwIQAAAAAAAAAhAzczMzMzMCEDNzMzMzMwIQM3MzMzMzAhAmpmZmZmZBUCamZmZmZkJQGZmZmZmZgpAAAAAAAAACEAAAAAAAAAEQAAAAAAAAAhAMzMzMzMzC0AAAAAAAAAIQA==",
"dtype": "f8"
},
"xaxis": "x",
"y": {
"bdata": "MzMzMzMzGUAzMzMzMzMXQGZmZmZmZhxAMzMzMzMzGUAAAAAAAAAaQGZmZmZmZh5AmpmZmZmZE0AzMzMzMzMdQM3MzMzMzBpAzczMzMzMHEAAAAAAAAAaQJqZmZmZmRlAMzMzMzMzG0DNzMzMzMwWQDMzMzMzMxdAmpmZmZmZGUAAAAAAAAAaQM3MzMzMzB5AzczMzMzMHkAAAAAAAAAYQJqZmZmZmRtAZmZmZmZmFkDNzMzMzMweQDMzMzMzMxlAzczMzMzMGkDNzMzMzMwcQM3MzMzMzBhAZmZmZmZmGECamZmZmZkZQM3MzMzMzBxAmpmZmZmZHUCamZmZmZkfQJqZmZmZmRlAMzMzMzMzGUBmZmZmZmYYQM3MzMzMzB5AMzMzMzMzGUCamZmZmZkZQAAAAAAAABhAmpmZmZmZG0DNzMzMzMwaQJqZmZmZmRtAMzMzMzMzF0AzMzMzMzMbQM3MzMzMzBpAzczMzMzMGkAzMzMzMzMZQAAAAAAAABpAzczMzMzMGECamZmZmZkXQA==",
"dtype": "f8"
},
"yaxis": "y"
}
],
"layout": {
"legend": {
"title": {
"text": "species"
},
"tracegroupgap": 0
},
"margin": {
"t": 60
},
"template": {
"data": {
"bar": [
{
"error_x": {
"color": "#2a3f5f"
},
"error_y": {
"color": "#2a3f5f"
},
"marker": {
"line": {
"color": "#E5ECF6",
"width": 0.5
},
"pattern": {
"fillmode": "overlay",
"size": 10,
"solidity": 0.2
}
},
"type": "bar"
}
],
"barpolar": [
{
"marker": {
"line": {
"color": "#E5ECF6",
"width": 0.5
},
"pattern": {
"fillmode": "overlay",
"size": 10,
"solidity": 0.2
}
},
"type": "barpolar"
}
],
"carpet": [
{
"aaxis": {
"endlinecolor": "#2a3f5f",
"gridcolor": "white",
"linecolor": "white",
"minorgridcolor": "white",
"startlinecolor": "#2a3f5f"
},
"baxis": {
"endlinecolor": "#2a3f5f",
"gridcolor": "white",
"linecolor": "white",
"minorgridcolor": "white",
"startlinecolor": "#2a3f5f"
},
"type": "carpet"
}
],
"choropleth": [
{
"colorbar": {
"outlinewidth": 0,
"ticks": ""
},
"type": "choropleth"
}
],
"contour": [
{
"colorbar": {
"outlinewidth": 0,
"ticks": ""
},
"colorscale": [
[
0,
"#0d0887"
],
[
0.1111111111111111,
"#46039f"
],
[
0.2222222222222222,
"#7201a8"
],
[
0.3333333333333333,
"#9c179e"
],
[
0.4444444444444444,
"#bd3786"
],
[
0.5555555555555556,
"#d8576b"
],
[
0.6666666666666666,
"#ed7953"
],
[
0.7777777777777778,
"#fb9f3a"
],
[
0.8888888888888888,
"#fdca26"
],
[
1,
"#f0f921"
]
],
"type": "contour"
}
],
"contourcarpet": [
{
"colorbar": {
"outlinewidth": 0,
"ticks": ""
},
"type": "contourcarpet"
}
],
"heatmap": [
{
"colorbar": {
"outlinewidth": 0,
"ticks": ""
},
"colorscale": [
[
0,
"#0d0887"
],
[
0.1111111111111111,
"#46039f"
],
[
0.2222222222222222,
"#7201a8"
],
[
0.3333333333333333,
"#9c179e"
],
[
0.4444444444444444,
"#bd3786"
],
[
0.5555555555555556,
"#d8576b"
],
[
0.6666666666666666,
"#ed7953"
],
[
0.7777777777777778,
"#fb9f3a"
],
[
0.8888888888888888,
"#fdca26"
],
[
1,
"#f0f921"
]
],
"type": "heatmap"
}
],
"histogram": [
{
"marker": {
"pattern": {
"fillmode": "overlay",
"size": 10,
"solidity": 0.2
}
},
"type": "histogram"
}
],
"histogram2d": [
{
"colorbar": {
"outlinewidth": 0,
"ticks": ""
},
"colorscale": [
[
0,
"#0d0887"
],
[
0.1111111111111111,
"#46039f"
],
[
0.2222222222222222,
"#7201a8"
],
[
0.3333333333333333,
"#9c179e"
],
[
0.4444444444444444,
"#bd3786"
],
[
0.5555555555555556,
"#d8576b"
],
[
0.6666666666666666,
"#ed7953"
],
[
0.7777777777777778,
"#fb9f3a"
],
[
0.8888888888888888,
"#fdca26"
],
[
1,
"#f0f921"
]
],
"type": "histogram2d"
}
],
"histogram2dcontour": [
{
"colorbar": {
"outlinewidth": 0,
"ticks": ""
},
"colorscale": [
[
0,
"#0d0887"
],
[
0.1111111111111111,
"#46039f"
],
[
0.2222222222222222,
"#7201a8"
],
[
0.3333333333333333,
"#9c179e"
],
[
0.4444444444444444,
"#bd3786"
],
[
0.5555555555555556,
"#d8576b"
],
[
0.6666666666666666,
"#ed7953"
],
[
0.7777777777777778,
"#fb9f3a"
],
[
0.8888888888888888,
"#fdca26"
],
[
1,
"#f0f921"
]
],
"type": "histogram2dcontour"
}
],
"mesh3d": [
{
"colorbar": {
"outlinewidth": 0,
"ticks": ""
},
"type": "mesh3d"
}
],
"parcoords": [
{
"line": {
"colorbar": {
"outlinewidth": 0,
"ticks": ""
}
},
"type": "parcoords"
}
],
"pie": [
{
"automargin": true,
"type": "pie"
}
],
"scatter": [
{
"fillpattern": {
"fillmode": "overlay",
"size": 10,
"solidity": 0.2
},
"type": "scatter"
}
],
"scatter3d": [
{
"line": {
"colorbar": {
"outlinewidth": 0,
"ticks": ""
}
},
"marker": {
"colorbar": {
"outlinewidth": 0,
"ticks": ""
}
},
"type": "scatter3d"
}
],
"scattercarpet": [
{
"marker": {
"colorbar": {
"outlinewidth": 0,
"ticks": ""
}
},
"type": "scattercarpet"
}
],
"scattergeo": [
{
"marker": {
"colorbar": {
"outlinewidth": 0,
"ticks": ""
}
},
"type": "scattergeo"
}
],
"scattergl": [
{
"marker": {
"colorbar": {
"outlinewidth": 0,
"ticks": ""
}
},
"type": "scattergl"
}
],
"scattermap": [
{
"marker": {
"colorbar": {
"outlinewidth": 0,
"ticks": ""
}
},
"type": "scattermap"
}
],
"scattermapbox": [
{
"marker": {
"colorbar": {
"outlinewidth": 0,
"ticks": ""
}
},
"type": "scattermapbox"
}
],
"scatterpolar": [
{
"marker": {
"colorbar": {
"outlinewidth": 0,
"ticks": ""
}
},
"type": "scatterpolar"
}
],
"scatterpolargl": [
{
"marker": {
"colorbar": {
"outlinewidth": 0,
"ticks": ""
}
},
"type": "scatterpolargl"
}
],
"scatterternary": [
{
"marker": {
"colorbar": {
"outlinewidth": 0,
"ticks": ""
}
},
"type": "scatterternary"
}
],
"surface": [
{
"colorbar": {
"outlinewidth": 0,
"ticks": ""
},
"colorscale": [
[
0,
"#0d0887"
],
[
0.1111111111111111,
"#46039f"
],
[
0.2222222222222222,
"#7201a8"
],
[
0.3333333333333333,
"#9c179e"
],
[
0.4444444444444444,
"#bd3786"
],
[
0.5555555555555556,
"#d8576b"
],
[
0.6666666666666666,
"#ed7953"
],
[
0.7777777777777778,
"#fb9f3a"
],
[
0.8888888888888888,
"#fdca26"
],
[
1,
"#f0f921"
]
],
"type": "surface"
}
],
"table": [
{
"cells": {
"fill": {
"color": "#EBF0F8"
},
"line": {
"color": "white"
}
},
"header": {
"fill": {
"color": "#C8D4E3"
},
"line": {
"color": "white"
}
},
"type": "table"
}
]
},
"layout": {
"annotationdefaults": {
"arrowcolor": "#2a3f5f",
"arrowhead": 0,
"arrowwidth": 1
},
"autotypenumbers": "strict",
"coloraxis": {
"colorbar": {
"outlinewidth": 0,
"ticks": ""
}
},
"colorscale": {
"diverging": [
[
0,
"#8e0152"
],
[
0.1,
"#c51b7d"
],
[
0.2,
"#de77ae"
],
[
0.3,
"#f1b6da"
],
[
0.4,
"#fde0ef"
],
[
0.5,
"#f7f7f7"
],
[
0.6,
"#e6f5d0"
],
[
0.7,
"#b8e186"
],
[
0.8,
"#7fbc41"
],
[
0.9,
"#4d9221"
],
[
1,
"#276419"
]
],
"sequential": [
[
0,
"#0d0887"
],
[
0.1111111111111111,
"#46039f"
],
[
0.2222222222222222,
"#7201a8"
],
[
0.3333333333333333,
"#9c179e"
],
[
0.4444444444444444,
"#bd3786"
],
[
0.5555555555555556,
"#d8576b"
],
[
0.6666666666666666,
"#ed7953"
],
[
0.7777777777777778,
"#fb9f3a"
],
[
0.8888888888888888,
"#fdca26"
],
[
1,
"#f0f921"
]
],
"sequentialminus": [
[
0,
"#0d0887"
],
[
0.1111111111111111,
"#46039f"
],
[
0.2222222222222222,
"#7201a8"
],
[
0.3333333333333333,
"#9c179e"
],
[
0.4444444444444444,
"#bd3786"
],
[
0.5555555555555556,
"#d8576b"
],
[
0.6666666666666666,
"#ed7953"
],
[
0.7777777777777778,
"#fb9f3a"
],
[
0.8888888888888888,
"#fdca26"
],
[
1,
"#f0f921"
]
]
},
"colorway": [
"#636efa",
"#EF553B",
"#00cc96",
"#ab63fa",
"#FFA15A",
"#19d3f3",
"#FF6692",
"#B6E880",
"#FF97FF",
"#FECB52"
],
"font": {
"color": "#2a3f5f"
},
"geo": {
"bgcolor": "white",
"lakecolor": "white",
"landcolor": "#E5ECF6",
"showlakes": true,
"showland": true,
"subunitcolor": "white"
},
"hoverlabel": {
"align": "left"
},
"hovermode": "closest",
"mapbox": {
"style": "light"
},
"paper_bgcolor": "white",
"plot_bgcolor": "#E5ECF6",
"polar": {
"angularaxis": {
"gridcolor": "white",
"linecolor": "white",
"ticks": ""
},
"bgcolor": "#E5ECF6",
"radialaxis": {
"gridcolor": "white",
"linecolor": "white",
"ticks": ""
}
},
"scene": {
"xaxis": {
"backgroundcolor": "#E5ECF6",
"gridcolor": "white",
"gridwidth": 2,
"linecolor": "white",
"showbackground": true,
"ticks": "",
"zerolinecolor": "white"
},
"yaxis": {
"backgroundcolor": "#E5ECF6",
"gridcolor": "white",
"gridwidth": 2,
"linecolor": "white",
"showbackground": true,
"ticks": "",
"zerolinecolor": "white"
},
"zaxis": {
"backgroundcolor": "#E5ECF6",
"gridcolor": "white",
"gridwidth": 2,
"linecolor": "white",
"showbackground": true,
"ticks": "",
"zerolinecolor": "white"
}
},
"shapedefaults": {
"line": {
"color": "#2a3f5f"
}
},
"ternary": {
"aaxis": {
"gridcolor": "white",
"linecolor": "white",
"ticks": ""
},
"baxis": {
"gridcolor": "white",
"linecolor": "white",
"ticks": ""
},
"bgcolor": "#E5ECF6",
"caxis": {
"gridcolor": "white",
"linecolor": "white",
"ticks": ""
}
},
"title": {
"x": 0.05
},
"xaxis": {
"automargin": true,
"gridcolor": "white",
"linecolor": "white",
"ticks": "",
"title": {
"standoff": 15
},
"zerolinecolor": "white",
"zerolinewidth": 2
},
"yaxis": {
"automargin": true,
"gridcolor": "white",
"linecolor": "white",
"ticks": "",
"title": {
"standoff": 15
},
"zerolinecolor": "white",
"zerolinewidth": 2
}
}
},
"xaxis": {
"anchor": "y",
"domain": [
0,
1
],
"title": {
"text": "sepal_width"
}
},
"yaxis": {
"anchor": "x",
"domain": [
0,
1
],
"title": {
"text": "sepal_length"
}
}
}
}
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"px.scatter(\n",
" px.data.iris(),\n",
" x=\"sepal_width\",\n",
" y=\"sepal_length\",\n",
" color=\"species\",\n",
")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "61adea94",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"jupytext": {
"formats": "ipynb,py:percent"
},
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.12.0"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
================================================
FILE: demo/vscode/notebook.py
================================================
# ---
# jupyter:
# jupytext:
# formats: ipynb,py:percent
# text_representation:
# extension: .py
# format_name: percent
# format_version: '1.3'
# jupytext_version: 1.18.0-dev
# kernelspec:
# display_name: Python 3
# language: python
# name: python3
# ---
# %% [markdown]
# This paired notebook can be edited either as
# a Python script or as a Jupyter notebook in
# VS Code. Thanks to the Jupytext Sync
# extension, changes are automatically synced to
# the other paired file(s) when you save.
#
# 💡 Tip: don't forget to hit "Save" before
# switching to the other editor. If you don't save
# your changes, you will run into conflicting edits
# as VS Code does not autoreload files that have
# unsaved edits.
# %%
import plotly.express as px
# %%
px.scatter(
px.data.iris(),
x="sepal_width",
y="sepal_length",
color="species",
)
# %%
================================================
FILE: docs/Makefile
================================================
# Minimal makefile for Sphinx documentation
#
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
SOURCEDIR = .
BUILDDIR = _build
# Put it first so that "make" without argument is like "make help".
help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
.PHONY: help Makefile
# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
================================================
FILE: docs/advanced-options.md
================================================
# Advanced options
## Metadata filtering
The metadata that is included in the text notebooks is governed by the two options `notebook_metadata_filter` and `cell_metadata_filter`. The default value for these options are
- `notebook_metadata_filter="kernelspec,jupytext"`, i.e. by default, only the Jupytext and the kernel metadata are included:
- `cell_metadata_filter=all,-autoscroll,-collapsed,-scrolled,-trusted,-ExecuteTime`, i.e. by default all the cell metadata are included, except those listed with a negative sign.
Suppose you want to keep all the notebook metadata but `widgets` and `varInspector` in the YAML header, and that for cell metadata, you want to allow `ExecuteTime` and `autoscroll`, but not `hide_output`. Then, you could either add
```python
notebook_metadata_filter="all,-widgets,-varInspector"
cell_metadata_filter="ExecuteTime,autoscroll,-hide_output"
```
to your [`jupytext.toml`](config.md) file, or set these options for one notebook using the [Jupytext CLI](using-cli.md) with
```
jupytext --opt notebook_metadata_filter=all,-widgets,-varInspector --opt cell_metadata_filter=ExecuteTime,autoscroll,-hide_output notebook.py
```
If you wanted no notebook or cell metadata at all in the text notebooks, you could add this to your [`jupytext.toml`](config.md) file:
```python
notebook_metadata_filter="-all"
cell_metadata_filter="-all"
```
It is possible to filter nested metadata - use a dot to represent the nested fields. For example, if you wanted to include the Jupytext metadata, but not the Jupytext version number, you can use:
```python
notebook_metadata_filter="-jupytext.text_representation.jupytext_version"
```
Finally, note that you can _hide_ the notebook metadata in an HTML comment in `.md` files - just set `hide_notebook_metadata=true` either at the command line or in the `jupytext.toml` file.
## Magic commands
In the `percent` and `light` script formats, magic commands (Jupyter commands prefixed by `%` or `%%`) are commented out in scripts. You can change this by using the `comment_magics` option, either in the `jupytext.toml` file or at the command line with `jupytext --opt`.
## Active and inactive cells
You might want to make some cell active only when the notebook is run in Jupyter, or active only when the `.py` file is interpreted by Python. To do so, add an `active-ipynb` tag to the cells that should only be executed in the `.ipynb` file, and an `active-py` tag to the cells that should be executed only in the Python script.
## More options
There are a couple more options available - please have a look at the `JupytextConfiguration` class in [config.py](https://github.com/mwouts/jupytext/blob/main/src/jupytext/config.py).
================================================
FILE: docs/changelog.md
================================================
```{include} ../CHANGELOG.md
```
================================================
FILE: docs/conf.py
================================================
# Configuration file for the Sphinx documentation builder.
#
# This file only contains a selection of the most common options. For a full
# list see the documentation:
# http://www.sphinx-doc.org/en/master/config
# -- Path setup --------------------------------------------------------------
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#
# import os
# import sys
# sys.path.insert(0, os.path.abspath('.'))
# -- Project information -----------------------------------------------------
project = "Jupytext"
copyright = "2018-2025, The Jupytext Team"
author = "The Jupytext Team"
# -- General configuration ---------------------------------------------------
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = ["sphinx_copybutton", "myst_parser"]
# Auto-generated header anchors
myst_heading_anchors = 3
html_context = {
"display_github": True, # Integrate GitHub
"github_user": "mwouts", # Username
"github_repo": "jupytext", # Repo name
"github_version": "main", # Version
"conf_py_path": "/docs/", # Path in the checkout to the docs root
}
# Add any paths that contain templates here, relative to this directory.
templates_path = ["_templates"]
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This pattern also affects html_static_path and html_extra_path.
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store", ".ipynb_checkpoints"]
# -- Options for HTML output -------------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
html_theme = "alabaster"
html_sidebars = {"**": ["about.html", "navigation.html", "searchbox.html"]}
html_theme_options = {
"github_button": True,
"github_banner": True,
"github_user": "mwouts",
"github_repo": "jupytext",
"github_type": "star",
"logo": "logo.svg",
"show_relbars": True,
}
pygments_style = "sphinx"
master_doc = "index" # Makes `index.md` the main file
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ["_static"]
# Output file base name for HTML help builder.
htmlhelp_basename = "jupytext"
================================================
FILE: docs/config.md
================================================
# Jupytext's configuration file
Jupytext can use either `jupytext.toml` or `pyproject.toml` as its configuration file.
## Global pairing
To [pair](paired-notebooks.md) all the notebooks in the current folder and subfolders, all you need to do is to create a `jupytext.toml` file with this content:
```
# Pair ipynb notebooks to py:percent text notebooks
formats = "ipynb,py:percent"
```
With the above configuration, saving `notebook.ipynb` (or `notebook.py`) in Jupyter will have the effect to update both `notebook.ipynb` and `notebook.py` on disk.
You can use other text formats like `md`, `md:myst`, `Rmd` or `qmd`. The percent format is available for many languages. Use `auto:percent` to infer the file extension from the programmation language used in the notebook.
You can also configure Jupytext in a `pyproject.toml` config file rather than `jupytext.toml`. In that case, a sample configuration would be:
```
[tool.jupytext]
formats = "ipynb,py:percent"
```
## Pairing in subfolders
If you want to store your `.ipynb` notebooks in a `notebooks` folder, and their `.py` representation in a `scripts` folder, you can use this `jupytext.toml` configuration:
```
[formats]
"notebooks/" = "ipynb"
"scripts/" = "py:percent"
```
or this `pyproject.toml` configuration:
```
[tool.jupytext.formats]
"notebooks/" = "ipynb"
"scripts/" = "py:percent"
```
The `notebook/` prefix above is matched with the top-most parent folder of the matching name, not above the Jupytext configuration file.
## Pairing with multiple format specifications
You can define different pairing configurations for specific subsets of notebooks by using a list of format dictionaries. This is useful when you want to apply different pairing rules to notebooks in different locations, such as generating documentation markdown files only for tutorial notebooks.
Since Jupytext v1.18.0, the `formats` option can be a list of format dictionaries, where the first matching format is used for each notebook.
Here's an example that pairs tutorial notebooks to markdown documentation files, and all other notebooks to Python scripts:
```toml
# jupytext.toml
# Tutorial notebooks get paired to markdown docs
[[formats]]
"notebooks/tutorials/" = "ipynb"
"docs/tutorials/" = "md"
"scripts/tutorials/" = "py:percent"
# Default pairing: all other notebooks are paired with Python scripts
[[formats]]
"notebooks/" = "ipynb"
"scripts/" = "py:percent"
```
With this configuration:
- Tutorial notebooks like `notebooks/tutorials/getting_started.ipynb` are paired with:
- `docs/tutorials/getting_started.md`
- `scripts/tutorials/getting_started.py`
- Regular notebooks like `notebooks/hello.ipynb` are paired with `scripts/hello.py`
You can define multiple format specifications for different subsets:
```toml
# Tutorial notebooks
[[formats]]
"notebooks/tutorials/" = "ipynb"
"docs/tutorials/" = "md"
# Example notebooks with MyST format
[[formats]]
"notebooks/examples/" = "ipynb"
"docs/examples/" = "md:myst"
# Default for all other notebooks
[[formats]]
"notebooks/" = "ipynb"
"scripts/" = "py:percent"
```
The first format specification that matches the notebook path is used. It's recommended to put more specific paths first and the default/catch-all formats last.
### Alternative syntaxes
You can also use a semicolon-separated string for a more compact notation:
```toml
# jupytext.toml
formats = "notebooks///ipynb,scripts///py:percent;ipynb,py:percent"
```
Or a TOML list with string format specifications:
```toml
# jupytext.toml
formats = [
"notebooks///ipynb,scripts///py:percent",
"ipynb,py:percent"
]
```
In `pyproject.toml`, the configuration would be:
```toml
[[tool.jupytext.formats]]
"notebooks/tutorials/" = "ipynb"
"docs/tutorials/" = "md"
[[tool.jupytext.formats]]
"notebooks/" = "ipynb"
"scripts/" = "py:percent"
```
## Global pairing vs individual pairing
Alternatively, notebooks can be paired individually using either the Jupytext commands in JupyterLab, or the command line interface:
```bash
jupytext --set-formats ipynb,py:percent notebook.ipynb
```
The individual pairing takes precedence over the global pairing. You can disable the global pairing for an individual notebook by setting formats to a single format:
```bash
jupytext --set-formats ipynb notebook.ipynb
```
Please note that, while Jupytext is Jupyter acts accordingly to both local or global Jupytext configuration files, the Jupyter commands in JupyterLab and the Jupytext menu in Jupyter only display the pairing information set in the notebooks itself and are not aware of the global configuration ([#177](https://github.com/mwouts/jupytext/issues/177)).
## Possible locations for the Jupytext configuration files
The Jupytext configuration file(s) should be either in the local or a parent directory, or in any directory listed in
```python
from jupytext.config import global_jupytext_configuration_directories
list(global_jupytext_configuration_directories())
```
which include `XDG_CONFIG_HOME` (defaults to `$HOME/.config`) and `XDG_CONFIG_DIR`.
The name for the configuration file can be any of `jupytext.config.JUPYTEXT_CONFIG_FILES`, i.e. `.jupytext` (in TOML),
`jupytext.toml`, `jupytext.yml`, `jupytext.yaml`, `jupytext.json` or `jupytext.py` (dot-files
like `.jupytext.toml` are accepted by the CLI version of Jupytext, but are not effective in Jupyter).
As mentionned above, you can also use your Python project's `pyproject.toml` file.
If you want to know, for a given directory, which configuration file is used by Jupytext, run this in a Python shell:
```python
from jupytext.config import find_jupytext_configuration_file
find_jupytext_configuration_file('.')
```
If you want to limit the search for a configuration file to a given parent directory, you can create an empty `.jupytext` configuration file in that directory. Alternatively, you can set the search boundaries with an environment variable `JUPYTEXT_CEILING_DIRECTORIES` - a colon-separated list of absolute paths.
If `JUPYTEXT_CEILING_DIRECTORIES` is defined, Jupytext will stop searching for configuration files when it meets one of these path. This can be helpful to avoid searching for configuration files on slow filesystems. It can also be useful if you don't want to use a global configuration - for instance, when running `pytest` on Jupytext, we use `JUPYTEXT_CEILING_DIRECTORIES="/tmp"`.
================================================
FILE: docs/contributing.md
================================================
# Contributing
Thanks for reading this! Contributions to this project are welcome, and there are many ways you can contribute
## Improve the documentation
You've spotted a typo, a paragraph that is not very clear, or an instruction that does not work? Please follow the _Fork me on Github_ link, edit the document, and submit a pull request.
## Report an issue
You have seen an issue with Jupytext, or you can't find your way in the documentation?
Please let us know, and provide enough information so that we can reproduce the problem.
## Propose enhancements
You want to submit an enhancement on Jupytext? Unless this is a small change, we usually prefer that you let us know beforehand: open an issue that describe the problem you want to solve.
## Add support for another language
A pull request for which you do not need to contact us in advance is the addition of a new language to Jupytext. In principle that should be easy - you would only have to:
- document the language extension and comment by adding one line to `_SCRIPT_EXTENSIONS` in `jupytext/languages.py`.
- add the language to `docs/languages.md`
- contribute a sample notebook in `tests/data/notebooks/inputs/ipynb_[language]`.
- run the tests suite (create a [development environment](developing.md), then execute `pytest` locally). The tests will generate various text representations corresponding to your notebook under `tests/data/notebooks/outputs/`. Please verify that these files are valid scripts, and include them in your PR.
================================================
FILE: docs/developing.md
================================================
# Developing Jupytext
## How to test development versions from GitHub
If you want to test a feature that has been integrated in `main` but not delivered yet to `pip` or `conda`, use
```
HATCH_BUILD_HOOKS_ENABLE=true pip install git+https://github.com/mwouts/jupytext.git
```
The above requires `node`. You can install it with e.g.
```
conda install 'nodejs>=20' -c conda-forge
```
Alternatively you can build only Jupytext core (e.g. skip the JupyterLab extension). To do so, remove `HATCH_BUILD_HOOKS_ENABLE=true` in the above.
Finally, if you want to test a development branch, use
```
HATCH_BUILD_HOOKS_ENABLE=true pip install git+https://github.com/mwouts/jupytext.git@branch
```
where `branch` is the name of the branch you want to test.
## Install and develop Jupytext locally
Most of Jupytext's code is written in Python. To develop the Python part of Jupytext, you should clone Jupytext, then create a dedicated Python environment with [Pixi](https://pixi.sh):
```
pixi shell
```
Install the `jupytext` package in development mode with
```
HATCH_BUILD_HOOKS_ENABLE=true pip install -e '.[dev]'
```
We use the [pre-commit](https://pre-commit.com) package to run pre-commit scripts like `black` and `ruff` on the code.
Install it with
```
pre-commit install
```
Tests are executed with `pytest`. You can run them in parallel with for instance
```
pytest -n 5
```
Some tests require a Jupyter kernel pointing to the current environment:
```
python -m ipykernel install --name jupytext-dev --user
```
## Jupytext's extension for JupyterLab
Our extension for JupyterLab adds a series of Jupytext commands to JupyterLab. The code is in `packages/labextension`. See the `README.md` there for instructions on how to develop that extension.
## Jupytext's documentation
You can build the HTML documentation locally with
```
rm -rf docs/_build
pixi run -e docs sphinx-build docs docs/_build
```
================================================
FILE: docs/doc-requirements.txt
================================================
sphinx
sphinx_copybutton
sphinx_rtd_theme
jupytext
myst_parser
================================================
FILE: docs/faq.md
================================================
# FAQ
## What is Jupytext?
Jupytext is a Python package that provides _two-way_ conversion between Jupyter notebooks and several other text-based formats like Markdown documents or scripts.
## Why would I want to convert my notebooks to text?
The text representation only contains the part of the notebook that you wrote (not the outputs). You get a cleaner diff history. Thanks to the _two-way_ conversion, you can also act on the text file and then propagate the changes to the original `.ipynb` file. Refactor your code or merge multiple contributions easily!
## How do I use Jupytext?
Open the notebook that you want to version control. _Pair_ the notebook to a script or a Markdown file using the [Jupytext Commands](install.md#jupytext-commands-in-jupyterlab) in JupyterLab.
Save the notebook, and you get two copies of the notebook: the original `*.ipynb` file, together with its paired text representation.
## Which Jupytext format do you recommend?
Notebooks that contain more text than code are best represented as Markdown documents. These are conveniently edited in IDEs and are also well rendered on GitHub.
Saving notebooks as scripts is an appropriate choice when you want to act on the code (refactor the code, import it in another script or notebook, etc). Use the `percent` format if you prefer to get explicit cell markers (compatible with VScode, PyCharm, Spyder, Hydrogen...). And if you prefer to get the minimal amount of cell markers, go for the `light` format.
## Can I see a sample of each format?
Go to [our demo folder](https://github.com/mwouts/jupytext/tree/main/demo) and see how our sample `World population` notebook is represented in each format.
## Can I edit the paired text file?
Yes! When you're done, reload the notebook in Jupyter. There, you will see the updated input cells combined with the matching output cells from the `.ipynb` file.
## Do I need to close my notebook in Jupyter?
Closing the notebook in Jupyter while you refactor it in another editor will help you avoid the message _Untitled.ipynb has changed on disk_. However, you don't really need to close the notebook.
You can simply use _Reload Notebook from disk_ to load the latest edits once you're done with the other editor. When you reload the notebook, the kernel variables are preserved (and the outputs too if the notebook is paired to an `.ipynb` file), so you can continue your work where you left it.
💡 **Tip:** You can automate the notebook reloading by installing the [Jupyter Collaboration](jupyter-collaboration.md) extension.
## How do paired notebooks work?
The `.ipynb` file contains the full notebook. The paired text file only contains the input cells and selected metadata. When the notebook is loaded by Jupyter, input cells are loaded from the text file, while the output cells and the filtered metadata are restored using the `.ipynb` file. When the notebook is saved in Jupyter, the two files are updated to match the current content of the notebook.
## Can I create a notebook from a text file?
Certainly. Open your pre-existing scripts or Markdown files as notebooks with the _Open as Notebook_ menu in JupyterLab.
Output cells appear in the browser when you execute the notebook, but they are not written to the disk when you save the notebook.
The output cells are lost when you reload the notebook - if you want to avoid this, just _pair_ the text file to an `.ipynb` file.
If you want to convert text formats to notebooks programmatically, use one of
```shell
jupytext --to ipynb *.md # convert all .md files to notebooks with no outputs
jupytext --to ipynb --execute *.md # convert all .md files to notebooks and execute them
jupytext --set-formats ipynb,md --execute *.md # convert all .md files to paired notebooks and execute them
```
Conversions the other way use a similar format
```shell
jupytext --to md *.ipynb # convert all .ipynb files to .md files
```
## I want a specific cell to be commented out in the paired script
That's possible! See how to [activate or deactivate cells](advanced-options.md#active-and-inactive-cells).
## Which files should I version control?
Unless you want to version the outputs, you should version *only the text representation*. The paired `.ipynb` file can safely be deleted. It will be recreated locally the next time you open the notebook (from the text file) and save it.
Note that if you version both the `.md` and `.ipynb` files, you can configure `git diff` to [ignore the diffs on the `.ipynb` files](https://github.com/mwouts/jupytext/issues/251).
## I have modified a text file, but git reports no diff for the paired `.ipynb` file
The synchronization between the two files happens when you reload and *save* the notebook in Jupyter, or when you explicitly run `jupytext --sync`. If you want to force the synchronization on every commit, you could use `jupytext` as a [pre-commit hook](using-pre-commit.md).
## Jupyter warns me that the file has changed on disk
By default, Jupyter tries to save your notebooks every 2 minutes. If you have edited the text representation in another editor, it will detect that and ask you if you want to either overwrite, or _reload_ the notebook from disk.
You should simply click on _Reload_.
Note you can deactivate Jupyter's autosave function with the _Autosave Document_ setting in JupyterLab (search for _autosave_ in the _advanced settings editor_).
## When I reload, Jupyter warns me that my notebook has unsaved changes
That happens if you have edited both the notebook and the paired text file at the same time... If you know which version you want to keep, save it and reload the other. If you want to compare and merge both versions, backup the text file (with e.g. `git stash`), save the notebook, and merge the updated paired file with the backup (with e.g. `git stash pop`). Then, refresh the notebook in Jupyter.
## Jupyter complains that the `.ipynb` file is more recent than the text representation
This happens if you have edited the `.ipynb` file outside of Jupyter. This is a safeguard to avoid overwriting the notebook with an outdated text file.
In this case, a manual action is requested. Remove the paired `.md` or `.py` file if it is outdated, otherwise, edit and save it to update the file timestamp.
## Can I use Jupytext with JupyterHub, Binder, Nteract, Colab, Saturn or Azure?
Jupytext is compatible with JupyterHub (execute `pip install jupytext --user` to install it in user mode) and with Binder (add `jupytext` to the project requirements).
If you use another editor than JupyterLab, you probably can't get Jupytext there. However, you can still use Jupytext at the command line to manually sync the two representations of the notebook:
```shell
jupytext --set-formats ipynb,py:light notebook.ipynb # Pair a notebook to a light script
jupytext --sync notebook.ipynb # Sync the two representations
```
## Can I re-write my git history to use text files instead of notebooks?
Indeed, you could substitute every `.ipynb` file in the project history with its Jupytext Markdown representation.
Technically this is available in just one command, which results in a complete rewrite of the history. Please experiment that in a branch, and think twice before pushing the result...
```shell
git filter-branch --tree-filter 'jupytext --to md */*.ipynb && rm -f */*.ipynb' HEAD
```
See the result and the cleaner diff history in the case of the [Python Data Science Handbook](https://github.com/mwouts/PythonDataScienceHandbook/tree/jupytext_no_ipynb).
================================================
FILE: docs/formats-markdown.md
================================================
# Notebooks as Markdown
## MyST Markdown
[MyST (Markedly Structured Text)][myst-parser] is a markdown flavor that "implements the best parts of reStructuredText". It provides a way to call Sphinx directives and roles from within Markdown,
using a *slight* extension of CommonMark markdown.
[MyST-NB][myst-nb] and [Jupyter Book][jupyter-book] builds on this markdown flavor, to offer direct conversion of Jupyter Notebooks into Sphinx documents.
Similar to the jupytext Markdown format, MyST Markdown uses code blocks to contain code cells.
The difference though, is that the metadata is contained in a YAML block:
````md
```{code-cell} ipython3
---
other:
more: true
tags: [hide-output, show-input]
---
print("Hallo!")
```
````
The `ipython3` here is purely optional, as an aide for syntax highlighting.
In the round-trip, it is copied from `notebook.metadata.language_info.pygments_lexer`.
Also, where possible the conversion will use the short-hand metadata format
(see the [MyST guide](https://myst-parser.readthedocs.io/en/latest/using/syntax.html#parameterizing-directives)):
````md
```{code-cell} ipython3
:tags: [hide-output, show-input]
print("Hallo!")
```
````
Raw cells are also represented in a similar fashion:
````md
```{raw-cell}
:raw_mimetype: text/html
Bold text
```
````
Markdown cells are not wrapped. If a markdown cell has metadata, or
directly proceeds another markdown cell, then a [block break] will be inserted
above it, with an (optional) single line JSON representation of the metadata:
```md
+++ {"slide": true}
This is a markdown cell with metadata
+++
This is a new markdown cell with no metadata
```
See for instance how our `World population.ipynb` notebook is [represented](https://github.com/mwouts/jupytext/blob/main/demo/World%20population.myst.md#) in the `myst` format.
**Note**: The `myst` format requires Python >= 3.6
**Tip**: You can use the [myst-highlight] VS Code extension to provide better syntax highlighting for this format.
[myst-parser]: https://myst-parser.readthedocs.io
[myst-nb]: https://myst-nb.readthedocs.io
[jupyter-book]: https://jupyterbook.org
[block break]: https://myst-parser.readthedocs.io/en/latest/using/syntax.html#block-breaks
[myst-highlight]: https://marketplace.visualstudio.com/items?itemName=ExecutableBookProject.myst-highlight
## Quarto
[Quarto](https://quarto.org/) is a scientific and technical publishing system built on Pandoc. If you have `quarto` installed, Jupytext lets you edit `.qmd` documents as notebooks in Jupyter, and pair `.ipynb` notebooks with `.qmd` notebooks.
The conversion from `.ipynb` to `.qmd` and back directly calls `quarto convert`, and consequently requires an installation of Quarto v0.2.134 or higher.
Note that the round trip of `.ipynb` to `.qmd` to `.ipynb` has the effect of concatenating consecutive Markdown cells and turning raw cells into Markdown cells (since `.qmd` files represent all content as either Markdown or code cells).
## R Markdown
[R Markdown](https://rmarkdown.rstudio.com/authoring_quick_tour.html) is [RStudio](https://www.rstudio.com/)'s format for notebooks, with support for R, Python, and many [other languages](https://bookdown.org/yihui/rmarkdown/language-engines.html).
Jupytext's implementation of R Markdown is very similar to that of the Markdown format. The major difference is on code cells, which use R Markdown's convention, i.e. the language and options are surrounded by curly brackets, and the cell metadata are encoded as R objects. For instance our cell with the `parameters` tags would be represented as:
```{python tags=c("parameters")}
param = 5
```
Python and R notebooks represented in the R Markdown format can run both in Jupyter and RStudio. Note that you can change the default Python environment in RStudio with `RETICULATE_PYTHON` in a `.Renviron` file, see [here](https://github.com/mwouts/jupytext/issues/267#issuecomment-506994930).
See how our `World population.ipynb` notebook in the [demo folder](https://github.com/mwouts/jupytext/tree/main/demo) is represented in [R Markdown](https://github.com/mwouts/jupytext/blob/main/demo/World%20population.Rmd).
## Jupytext Markdown
Jupytext can save notebooks as [Markdown](https://daringfireball.net/projects/markdown/syntax) documents. This format is well adapted to tutorials, books, or more generally notebooks that contain more text than code. Notebooks represented in this form are well rendered by most Markdown editors or renderers, including GitHub.
Like all the Jupytext formats, Jupytext Markdown notebooks start with an (optional) YAML header. This header is used to store selected notebook metadata like the kernel information, together with Jupytext's format and version information.
```
---
jupyter:
jupytext:
text_representation:
extension: .md
format_name: markdown
format_version: '1.1'
jupytext_version: 1.1.0
kernelspec:
display_name: Python 3
language: python
name: python3
---
```
You can add custom notebook metadata like `author` or `title` under the `jupyter:` section, it will be synchronized with the notebook metadata.
And if you wish to export more metadata from the notebook, have a look at the paragraph on [metadata filtering](advanced-options.md#metadata-filtering).
In the Markdown format, markdown cells are inserted verbatim and separated with two blank lines.
If you'd like that cell breaks also occurs on Markdown headers, add a `split_at_heading: true` entry in the `jupytext` section in the YAML header, or if you want that option to be the default for all Markdown documents in Jupyter, activate the option in the [`jupytext.toml` configuration file](config.md):
```
split_at_heading = true
```
Code cells are encoded using the classical triple backticks, followed by the notebook language. Cell metadata are appended after the language information, with a `key=value` syntax, where `value` is encoded in JSON format. For instance, in a Python notebook, a simple code cell with a `parameters` tag is represented as:
```python tags=["parameters"]
param = 5
```
Code snippets are turned into code cells in Jupyter as soon as they have an explicit language, when that language is supported in Jupyter. Thus, you have a code snippet that you don't want to execute in Jupyter, you can either
- remove the language information,
- or, start the code snippet with a triple tilde, e.g. `~~~python`, instead of ` ```python`
- or, add an `active="md"` cell metadata, or a `.noeval` attribute after the language information, e.g. ` ```python .noeval `
- or, surround the code snippet with explicit Markdown cell markers (see below).
Raw cells are delimited with HTML comments, and accept cell metadata in the same key=value format:
raw text
raw cell with metadata
Markdown cells can also have explicit markers: use one of `` or `` or `` and the corresponding `` counterpart. Note that the `` and `` cells markers are [foldable](https://code.visualstudio.com/docs/editor/codebasics#_folding) in VS Code, and that you can also insert a title there, e.g. ``. Cell metadata are accepted in the format `key="value"` (`"value"` being encoded in JSON) as for the other cell types.
For a concrete example, see how our `World population.ipynb` notebook in the [demo folder](https://github.com/mwouts/jupytext/tree/main/demo) is represented in [Markdown](https://github.com/mwouts/jupytext/blob/main/demo/World%20population.md#).
## Pandoc Markdown
Pandoc, the _Universal document converter_, can read and write Jupyter notebooks - see [Pandoc's documentation](https://pandoc.org/MANUAL.html#creating-jupyter-notebooks-with-pandoc).
In Pandoc Markdown, all cells are marked with pandoc divs (`:::`). The format is therefore slightly more verbose than the Jupytext Markdown format.
See for instance how our `World population.ipynb` notebook is [represented](https://github.com/mwouts/jupytext/blob/main/demo/World%20population.pandoc.md#) in the `md:pandoc` format.
If you wish to use that format, please install `pandoc` in version 2.7.2 or above, with e.g. `conda install pandoc -c conda-forge`.
================================================
FILE: docs/formats-scripts.md
================================================
# Notebooks as code
## The `percent` format
The `percent` format is a representation of Jupyter notebooks as scripts, in which all cells are explicitly delimited with a commented double percent sign `# %%`. The `percent` format is currently available for these [languages](https://github.com/mwouts/jupytext/blob/main/src/jupytext/languages.py).
The format was introduced by Spyder in 2013, and is now supported by many editors, including
- [Spyder IDE](https://docs.spyder-ide.org/editor.html#defining-code-cells),
- [Hydrogen](https://nteract.gitbooks.io/hydrogen/docs/Usage/NotebookFiles.html#notebook-export), a package for Atom,
- [VS
Code](https://code.visualstudio.com/docs/python/jupyter-support-py#_jupyter-code-cells),
- [Python Tools for Visual Studio](https://docs.microsoft.com/en-us/visualstudio/python/python-interactive-repl-in-visual-studio?view=vs-2019#work-with-code-cells),
- and [PyCharm Professional](https://www.jetbrains.com/help/pycharm/editing-jupyter-notebook-files.html#edit-content).
Our implementation of the `percent` format is as follows: cells can have
- a title
- a cell type (`markdown`, `md` or `raw`, omitted for code cells)
- and cell metadata
like in this example:
```python
# %% Optional title [cell type] key="value"
```
In the `percent` format, our previous example becomes:
```python
# %% [markdown]
# This is a multiline
# Markdown cell
# %% [markdown]
# Another Markdown cell
# %%
# This is a code cell
class A():
def one():
return 1
def two():
return 2
```
In the case of Python scripts, Markdown cells do accept multiline comments:
```python
# %% [markdown]
"""
This is a Markdown cell
that uses multiline comments
"""
```
By default Jupytext will use line comments when it converts your Jupyter notebooks for `percent` scripts. If you prefer to use multiline comments for all text cells, add a `{"jupytext": {"cell_markers": "\"\"\""}}` metadata to your notebook, either with the notebook metadata editor in Jupyter, or at the command line:
```bash
jupytext --update-metadata '{"jupytext": {"cell_markers": "\"\"\""}}' notebook.ipynb --to py:percent
```
If you want to use multiline comments for all your paired notebooks, you could also add
```python
cell_markers = '"""'
```
to your [`jupytext.toml` configuration file](config.md).
See how our `World population.ipynb` notebook is [represented](https://github.com/mwouts/jupytext/blob/main/demo/World%20population.pct.py) in the `percent` format.
## The `marimo` format
Since Jupytext v1.19, you can use the `py:marimo` format, in which text notebooks are converted to Jupyter notebooks, and back, using [Marimo](https://marimo.io/).
Our [implementation](https://github.com/mwouts/jupytext/blob/main/src/jupytext/marimo.py) calls Marimo's converter directly (this requires Marimo v1.16.3 or later).
Please note that:
- The format is available only for Python notebooks.
- Marimo will add a suffix to variables that are defined multiple times, to make them compatible with its reactive evaluation.
- Notebook and cell metadata (except tags) cannot be stored in the `py:marimo` file.
- As of Marimo 0.17.8, empty cells are removed during round trips.
You can determine whether a given notebook is stable over a Marimo round trip with
```
jupytext --test --to py:marimo your_notebook.ipynb
jupytext --test --to ipynb your_marimo_script.py
```
💡 If you notice unexpected changes, and can reproduce them with `marimo convert` and `marimo export ipynb --sort top-down`, report the issue on the Marimo [tracker](https://github.com/marimo-team/marimo/issues), and ping `@mwouts`. If you believe the issue is on Jupytext’s side, use the Jupytext [issue tracker](https://github.com/mwouts/jupytext/issues).
## The `light` format
The `light` format was introduced by Jupytext. That format can represent any script in one of these [languages](https://github.com/mwouts/jupytext/blob/main/src/jupytext/languages.py) as a Jupyter notebook.
When a script in the `light` format is converted to a notebook, Jupytext code paragraphs are turned into code cells, and comments that are not adjacent to code are converted to Markdown cells. Cell breaks occurs on blank lines outside of functions, classes or multiline comments.
For instance, in this example we have three cells:
```python
# This is a multiline
# Markdown cell
# Another Markdown cell
# This is a code cell
class A():
def one():
return 1
def two():
return 2
```
Code cells can contain multiple code paragraphs. In that case Jupytext uses an explicit start-of-cell delimiter that is, by default, `# +` (`// +` in C++, etc). The default end of cell delimiter is `# -`, and can be omitted when followed by another explicit start of cell marker, or the end of the file:
```python
# +
# A single code cell made of two paragraphs
a = 1
def f(x):
return x+a
```
Metadata can be associated to a given cell using a key/value representation:
```python
# + key="value"
# A code cell with metadata
# + [markdown] key="value"
# A Markdown cell with metadata
```
The `light` format can use custom cell markers instead of `# +` or `# -`. If you prefer to mark cells with VS Code/PyCharm (resp. Vim) folding markers, set `"cell_markers": "region,endregion"` (resp. `"{{{,}}}"`) in the jupytext section of the notebook metadata. If you want to configure this as a global default, add either
```python
cell_markers = "region,endregion" # Use VS Code/PyCharm region folding delimiters
```
or
```python
cell_markers = "{{{,}}}" # Use Vim region folding delimiters
```
to your [`jupytext.toml` configuration file](config.md#).
See how our `World population.ipynb` notebook is [represented](https://github.com/mwouts/jupytext/blob/main/demo/World%20population.lgt.py) in that format.
## The `nomarker` format
The `nomarker` format is a variation of the `light` format with no cell marker at all. Please note that this format does not provide round-trip consistency - code cells are split on code paragraphs. By default, the `nomarker` format still includes a YAML header - if you prefer to also remove the header, set `"notebook_metadata_filter": "-all"` in the jupytext section of your notebook metadata.
## Sphinx-gallery scripts
Another popular notebook-like format for Python scripts is the Sphinx-gallery [format](https://sphinx-gallery.github.io/stable/syntax.html). Scripts that contain at least two lines with more than twenty hash signs are classified as Sphinx-Gallery notebooks by Jupytext.
Comments in Sphinx-Gallery scripts are formatted using reStructuredText rather than markdown. They can be converted to markdown for a nicer display in Jupyter by adding a `sphinx_convert_rst2md = True` line to your Jupytext configuration file. Please note that this is a non-reversible transformation—use this only with Binder. Revert to the default value `sphinx_convert_rst2md = False` when you edit Sphinx-Gallery files with Jupytext.
Turn a GitHub repository containing Sphinx-Gallery scripts into a live notebook repository with [Binder](https://mybinder.org/) and Jupytext by adding only two files to the repo:
- `binder/requirements.txt`, a list of the required packages (including `jupytext`)
- a [`jupytext.toml` configuration file](config.md#) with the following contents:
```
preferred_jupytext_formats_read = "py:sphinx"
sphinx_convert_rst2md = true
```
Our sample notebook is also represented in `sphinx` format [here](https://github.com/mwouts/jupytext/blob/main/demo/World%20population.spx.py).
================================================
FILE: docs/index.md
================================================
```{include} ../README.md
:relative-docs: docs/
:start-after:
```
## Table of Contents
```{toctree}
:maxdepth: 1
install.md
text-notebooks.md
paired-notebooks.md
jupyterlab-extension.md
vs-code.md
jupyter-collaboration.md
config.md
advanced-options.md
formats-scripts.md
formats-markdown.md
languages.md
using-cli.md
using-pre-commit.md
faq.md
tutorials.md
contributing.md
developing.md
changelog.md
```
================================================
FILE: docs/install.md
================================================
# Installation
Installing Jupytext is as simple as
```bash
pip install jupytext
```
or
```bash
conda install jupytext -c conda-forge
```
You should run either one of these commands in the Python environment from which you launch JupyterLab. Once you have installed Jupytext, you need to restart Jupyter to be able to use Jupytext within Jupyter.
If the Python environment where the Jupyter server runs is read-only, for instance if your server is started using JupyterHub, you can still install Jupytext in user mode with:
```
/path_to_your_jupyter_environment/python -m pip install jupytext --user
```
Jupytext comes with a series of tools and plugin, which we will now briefly describe.
## Jupytext's contents manager
Jupytext provides a contents manager that let Jupyter open and save notebooks as text files.
Jupytext's contents manager is activated automatically by Jupytext's server extension. When you start either `jupyter lab` or `jupyter notebook`, you should see a line that looks like:
```bash
[I 10:28:31.646 LabApp] [Jupytext Server Extension] Changing NotebookApp.contents_manager_class from LargeFileManager to jupytext.TextFileContentsManager
```
If you don't see the message above after a fresh restart of your Jupyter server, please enable our server extension explicitly with
```
jupyter serverextension enable jupytext
```
When [`jupyter-fs>=1.0.0`](https://github.com/jpmorganchase/jupyter-fs) is being used along with `jupytext`, use `SyncMetaManager` as the contents manager for `jupyter-fs` as `jupytext` do not support async contents manager which is used in default `MetaManager` of `jupyter-fs`. The `jupyter-fs` config must be as follows:
```json
{
"ServerApp": {
"contents_manager_class": "jupyterfs.metamanager.SyncMetaManager",
}
}
```
so that `jupytext` will create its own contents manager derived from `SyncMetaManager`.
## Jupytext commands in JupyterLab
Jupytext comes with a frontend extension for JupyterLab which provides pairing commands (accessible with View / Activate Command Palette, or Ctrl+Shift+C):

The Jupytext extension for JupyterLab is bundled with Jupytext. It should be installed and enabled automatically. You can `enable` or `disable` it manually with either
```
jupyter labextension enable jupyterlab-jupytext
jupyter labextension disable jupyterlab-jupytext
```
From Jupytext 1.16.0 on, the version of the extension is compatible with JupyterLab 4.x only. If you wish to use Jupytext with JupyterLab 3.x or older, please
- install the `jupytext` package using `pip` or `conda`
- and then, install the last version of the `jupyterlab-jupytext` extension that is compatible with your version of JupyterLab, i.e.
```
jupyter labextension install jupyterlab-jupytext@1.3.11 # for JupyterLab 3.x
jupyter labextension install jupyterlab-jupytext@1.2.2 # for JupyterLab 2.x
jupyter labextension install jupyterlab-jupytext@1.1.1 # for JupyterLab 1.x
```
## Jupytext menu in Jupyter Notebook
In Jupyter Notebook 7 you can use the same pairing commands as in JupyterLab (see above).
In Jupyter Notebook **classic**, i.e. Jupyter Notebook 6.x and below, Jupytext used to provided an extension that added a Jupytext section in the File menu.

That extension is available only for `jupytext<1.16`, and is automatically installed. If need be, you can install and activate it manually with
```
jupyter nbextension install --py jupytext [--user]
jupyter nbextension enable --py jupytext [--user]
```
See also [Issue #1095](https://github.com/mwouts/jupytext/issues/1095) where we discuss how to
add a Jupytext menu to JupyterLab and Notebook 7.x.
## Jupytext's command line interface
Jupytext provides a `jupytext` command in the terminal that you can use to pair, synchronize or convert notebooks in different formats.
The CLI is documented [here](using-cli.md).
Run `jupytext --version` to check which version of Jupytext is installed.
## Jupytext as a Python library
Jupytext is also available as a Python library. The `jupytext` package exposes the same `read`, `write`, `reads` and `writes` functions than `nbformat`, meaning that you can read and write notebooks within Python like this:
```
import jupytext
# read a notebook from a file
nb = jupytext.read("notebook.py")
# or from a string
nb = jupytext.reads(text, fmt="md:myst")
# write a notebook to a file in the 'py:percent' format
jupytext.write(nb, "notebook.py", fmt="py:percent")
```
In the above, `nb` is an instance of an `nbformat` `NotebookNode`. The notebook format is documented in the [nbformat documentation](https://nbformat.readthedocs.io).
================================================
FILE: docs/jupyter-collaboration.md
================================================
# Jupyter Collaboration
[Jupyter Collaboration](https://github.com/jupyterlab/jupyter-collaboration) is an official Jupyter extension that enables real-time collaboration in JupyterLab.
## Autoreload Feature
Beyond its collaboration features, Jupyter Collaboration also provides automatic file reloading. When the extension is installed, JupyterLab auto-reloads any file that gets modified on disk. This way, you can edit your notebook and text files outside of Jupyter, and the changes appear in Jupyter automatically without having to manually reload the document.
Note that Jupyter Collaboration also comes with an auto-save feature. By [default](https://github.com/jupyterlab/jupyter-collaboration/blob/67453e04dad30978d42fdef07040ae94cabe2bf0/projects/jupyter-server-ydoc/jupyter_server_ydoc/app.py#L45-L84), notebooks and text documents are saved one second after your last change.
## Collaborating on text notebooks
Currently, the real-time collaboration feature can be used:
- in the notebook editor when opening `.ipynb` notebooks
- in the text editor when opening `.py` or `.md` files
However, it does not yet work on `.py` and `.md` files opened as notebooks. If you want to collaborate on text notebooks, we recommend that you collaborate on paired notebooks (see [#1432](https://github.com/mwouts/jupytext/issues/1432)).
## Known issues
While using Jupytext and Jupyter Collaboration together mostly works, we have noticed a few side effects. Please review the list of [known issues](https://github.com/mwouts/jupytext/issues?q=state%3Aopen%20label%3A%22jupyter-collaboration%22) and make sure that you are comfortable with these.
================================================
FILE: docs/jupyterlab-extension.md
================================================
# Frontend extension
Recent versions of Jupytext (`>=1.16.0`) ships frontend extension that enables users
to create text notebooks and pair notebooks from main menu of JupyterLab 4 and
Notebook 7. In addition, the frontend extension adds selected Jupytext text
notebook formats to launcher in the `Jupytext` section so that users can launch text
notebooks as they launch a regular notebook from JupyterLab launcher.
## Launcher icons
After installing Jupytext extension, users will have a new category in the launcher
called Jupytext as shown below:

Users can remove and/or add new formats to the Jupytext section _via_ `Settings>Jupytext`.

By (un)selecting different formats, users can filter the items in launcher and main menu. **Note** that users need to refresh the current browser tab when they modify the settings for them to take effect.
## Main menu
It is also possible to create new text notebooks and/or pair existing notebooks from main menu and dedicated Jupytext main menu, respectively.
Following screenshot shows the `New Text Notebook` submenu available in `File` menu.

Similarly, to pair existing notebooks, users can go to `Jupytext` menu on main menu as shown below:

All the options are greyed out in the above screenshot as there is no notebook widget currently active. Once the user opens a new notebook, the options will become available.
================================================
FILE: docs/languages.md
================================================
# Supported Languages
Jupytext works with notebooks in any of the following languages:
- Bash
- C#
- C++ (using either the xeus-cling or the ROOT kernel)
- Clojure
- Coconut
- F#
- Gnuplot
- Go
- Groovy
- Haskell
- IDL
- Java
- Javascript
- Julia
- Logtalk
- Lua
- Matlab
- OCaml
- Octave
- PowerShell
- Python
- q/kdb+
- R
- Robot Framework
- Rust/Evxcr
- Sage
- SAS
- Scala
- Scheme
- Script of Script
- Stata
- Tcl
- TypeScript
- Wolfram Language
- Note that Jupytext uses the non-standard `.wolfram` file extension for Wolfram Language files to avoid conflicts with Matlab.
- Xonsh
Extending Jupytext to more languages should be easy, see the sections on [contributing to](contributing.md) and [developing](developing.md) Jupytext.
================================================
FILE: docs/make.bat
================================================
@ECHO OFF
pushd %~dp0
REM Command file for Sphinx documentation
if "%SPHINXBUILD%" == "" (
set SPHINXBUILD=sphinx-build
)
set SOURCEDIR=.
set BUILDDIR=_build
if "%1" == "" goto help
%SPHINXBUILD% >NUL 2>NUL
if errorlevel 9009 (
echo.
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
echo.installed, then set the SPHINXBUILD environment variable to point
echo.to the full path of the 'sphinx-build' executable. Alternatively you
echo.may add the Sphinx directory to PATH.
echo.
echo.If you don't have Sphinx installed, grab it from
echo.http://sphinx-doc.org/
exit /b 1
)
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
goto end
:help
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
:end
popd
================================================
FILE: docs/paired-notebooks.md
================================================
# Paired notebooks
Both classical Jupyter Notebooks in the `.ipynb` format, and [text notebooks](text-notebooks.md), have great qualities. The former is a well established standard, contains all the notebook outputs, while the latter is lighter, easier to edit, and well suited for version control.
_Paired notebooks_ give you the best of both worlds.
They work as follows:
- When a paired notebook is opened, the inputs are loaded from the most recent file in the pair, while the outputs are loaded from the `.ipynb` file, if it exists
- When a paired notebook is saved, all the files in the pair are updated (or recreated) with the new notebook content.
This means, in particular, that you can put under version control only the `.py` or `.md` representation of the notebook. When your collaborators
check-out the updates on the text notebook, open it, then save it in Jupyter, the corresponding `.ipynb` file will be updated or even re-created if necessary.
## How to pair a notebook
In JupyterLab, pair your notebook to one or more text formats with the [Jupytext commands](install.md#jupytext-commands-in-jupyterlab):

These commands simply add a `"jupytext": {"formats": "ipynb,md"}` entry to the notebook metadata.
You can also configure the notebook pairing [globally](config.md) for all your notebooks.
## Can I edit a notebook simultaneously in Jupyter and in a text editor?
When saving a paired notebook, Jupyter updates both the `.ipynb` and its text representation. The text representation can be edited outside of Jupyter. When the notebook is refreshed in Jupyter, the input cells are read from the text file, and the output cells from the `.ipynb` file.
It is possible (and convenient) to leave the notebook open in Jupyter while you edit its text representation. However, you don't want the two editors to save the notebook simultaneously. To avoid this:
- deactivate Jupyter's autosave, and
- make sure you reload the notebook when you switch back from the editor to Jupyter.
In case you forgot to reload, and saved the Jupyter notebook while the text representation had changed, no worries: Jupyter will ask you which version you want to keep:

If that happens, simply select the version that has the most recent changes (or: make a copy of the `.py` notebook on disk, click on _overwrite_ in Jupyter, and reconcile manually the two `.py` files).
================================================
FILE: docs/text-notebooks.md
================================================
# Text notebooks
Jupytext can save Jupyter Notebooks as text files, with e.g. a `.py` or `.md`
extension. These text files only contain the inputs of your notebooks, as well
as [selected metadata](advanced-options.md#metadata-filtering).
Text notebooks are well suited for version control. They are standard text files
and you can easily edit or refactor them in the editor of your choice.
The outputs of the notebook are not stored on disk, unless you decide to
[pair](paired-notebooks.md) your text notebook to a regular `.ipynb` file.
## How to open a text notebook in JupyterLab
Once you have [installed](install.md) Jupytext, you can open and run `.py` and `.md` files as
notebooks.
### With a right click
Right click on the text notebook, then select _Open With_ → _Notebook_:

Notes:
* you can achieve the same result if you use _Open With_ → _Jupytext Notebook_
* to open links to `.md` files in notebooks with the Notebook editor, you will
need `jupyterlab>=3.6.0`.
### With a double click
Right clicking and the _Open With_ submenu allows you to choose among several
ways to open a file (several **viewers**, in JupyterLab jargon); and when you
double click instead, you open the file using **its default viewer**.
The default viewer for text notebooks is by default configured to be the
**Editor** (which means: text editor); if you'd prefer to have the text files
open as a notebook instead, you have the option to **redefine the default
viewer**, which is something defined for each document type.
Since version 1.15.1, `jupytext` comes with a helper command that allows you to
do this from the command line; and essentially you would just need to run
```bash
jupytext-config set-default-viewer
```
See also [the last section below](#more-on-default-viewers) for alternative
means to change and inspect the default viewers configuration
## How to open a text notebook in Jupyter notebook (nb7)
As of July 2023, Jupyter Notebook now comes as version 7.x - and is known in short as nb7
nb7 being built on top of JupyterLab, the principles described above apply as
well in this context; which means that
* you can always right-click a file and select *Open With* → *Notebook*;
* and if you have properly defined the default viewers as described above, you
can also double-click a file to open it as a notebook.
## How to open a text notebook in Jupyter Notebook (classic)
Previous releases of Jupyter Notebook, i.e. up to version 6, were known as notebook classic
By default, notebook classic opens scripts and Markdown documents as notebooks.
If you want to open them with the text editor, select the document and click on
_edit_:

## How to decide which extensions are notebooks
By default, Jupytext will classify documents with a `.py`, `.R`, `.jl`, `.md`,
`.Rmd`, `.qmd` extension (and more!) as notebooks. If you prefer to limit the
notebook type to certain extensions, you can add a `notebook_extensions` option
to your [Jupytext config file (`jupytext.toml`)](config.md) configuration file
with, for instance, the following value:
```
notebook_extensions = "ipynb,md,qmd,Rmd"
```
## More on default viewers
### `jupytext-config`
This command has more options than the one shown above; in particular:
* you can use `jupytext-config` to set only some of the default viewers; for
example, if you want to have your `.py` and `.md` files open as a notebook
when you double-click them e.g.
`jupytext-config set-default-viewer python markdown`
* you can use `jupytext-config` to inspect the current configuration, e.g.
`jupytext-config list-default-viewer`
* you can use `jupytext-config unset-default-viewer python` to remove some of the settings
Here's an example of a session, starting from the default config of JupyterLab
```bash
# starting from the default config of JupyterLab
$ jupytext-config list-default-viewer
# we add the default viewer for 2 doctypes
$ jupytext-config set-default-viewer python markdown
# we check what was done
$ jupytext-config list-default-viewer
python: Jupytext Notebook
markdown: Jupytext Notebook
# we can now remove the default viewer for markdown
$ jupytext-config unset-default-viewer markdown
# and check again
$ jupytext-config list-default-viewer
python: Jupytext Notebook
$
```
### From JupyterLab settings dialog
Alternatively to using `jupytext-config`, you can also find the configuration of the default viewers from JupyterLab interactively; to do so, go to _Settings_, _Advanced Settings Editor_, and in the JSON view for the `Document Manager` copy-paste the following settings (or the subset that matches your use case):
```json
{
"defaultViewers": {
"markdown": "Jupytext Notebook",
"myst": "Jupytext Notebook",
"r-markdown": "Jupytext Notebook",
"quarto": "Jupytext Notebook",
"julia": "Jupytext Notebook",
"python": "Jupytext Notebook",
"r": "Jupytext Notebook"
}
}
```
Here is a screencast of the steps to follow:

================================================
FILE: docs/tutorials.md
================================================
# Blog posts and talks
Over the course of the years I wrote a few different blog posts about Jupytext, about text notebooks, their integration with the most popular IDEs, etc. They might help you get started with Jupytext:
- Read the original [announcement](https://towardsdatascience.com/introducing-jupytext-9234fdff6c57) in _Towards Data Science_ (Sept. 2018),
- Watch the [PyParis talk](https://github.com/mwouts/jupytext_pyparis_2018/blob/master/README.md) (Nov. 2018),
- Read our article on [Jupytext and Papermill](https://medium.com/capital-fund-management/automated-reports-with-jupyter-notebooks-using-jupytext-and-papermill-619e60c37330) in _CFM Insights_ (Sept. 2019)
- See how you can edit [Jupyter Notebooks in VS Code or PyCharm](https://towardsdatascience.com/jupyter-notebooks-in-the-ide-visual-studio-code-versus-pycharm-5e72218eb3e8) with (or without!) Jupytext (Jan. 2020)
- Watch the [JupyterCon 2020 talk](https://github.com/mwouts/jupytext_jupytercon2020/blob/master/README.md) on Jupytext (Oct. 2020),
- or, try Jupytext online with [binder](https://mybinder.org/v2/gh/mwouts/jupytext/main?urlpath=lab/tree/demo/get_started.ipynb)!
================================================
FILE: docs/using-cli.md
================================================
# Jupytext CLI
## Command line conversion
Jupytext provides command line interface for converting notebooks between the different formats.
```bash
jupytext --to py notebook.ipynb # convert notebook.ipynb to a .py file
jupytext --to py:percent notebook.ipynb # convert notebook.ipynb to a .py file in the double percent format
jupytext --to py:percent --opt comment_magics=false notebook.ipynb # same as above + do not comment magic commands
jupytext --to markdown notebook.ipynb # convert notebook.ipynb to a .md file
jupytext --output script.py notebook.ipynb # convert notebook.ipynb to a script.py file
jupytext --to notebook notebook.py # convert notebook.py to an .ipynb file with no outputs
jupytext --update --to notebook notebook.py # update the input cells in the .ipynb file and preserve outputs and metadata
jupytext --to md --test notebook.ipynb # Test round trip conversion
jupytext --to md --output - notebook.ipynb # display the markdown version on screen
jupytext --from ipynb --to py:percent # read ipynb from stdin and write double percent script on stdout
```
Jupytext has a `--sync` mode that updates all the paired representations of a notebook based on timestamps:
```bash
jupytext --set-formats ipynb,py notebook.ipynb # Turn notebook.ipynb into a paired ipynb/py notebook
jupytext --sync notebook.ipynb # Update whichever of notebook.ipynb/notebook.py is outdated
```
You may also find useful to `--pipe` the text representation of a notebook into tools like `black`:
```bash
jupytext --sync --pipe black notebook.ipynb # read most recent version of notebook, reformat with black, save
```
To reorder the imports in your notebook, use
```bash
jupytext --pipe 'isort - --treat-comment-as-code "# %%" --float-to-top' notebook.ipynb
```
(remove the `--float-to-top` argument if you prefer to run `isort` per cell).
For programs that don't accept pipes, use `{}` as a placeholder for the name of a temporary file that will contain the text representation of the notebook. For instance, run `pytest` on your notebook with:
```bash
jupytext --check 'pytest {}' notebook.ipynb # export the notebook in format py:percent in a temp file, run pytest
```
Read more about running `pytest` on notebooks in our example [`Tests in a notebook.md`](https://github.com/mwouts/jupytext/blob/main/demo/Tests%20in%20a%20notebook.md#).
Note also that on Windows you need to use double quotes instead of single quotes and type e.g. `jupytext --check "pytest {}" notebook.ipynb`.
Execute `jupytext --help` to access the full documentation.
### Execute notebook cells
For convenience, when creating a notebook from text you can execute it:
```bash
jupytext --set-kernel - notebook.md # create a YAML header with kernel metadata matching the current python executable
jupytext --set-formats md:myst notebook.md # create a YAML header with an explicit jupytext format
jupytext --to notebook --execute notebook.md # convert notebook.md to an .ipynb file and run it
```
If you wanted to convert a collection of Markdown files to paired notebooks, and execute them in the current Python environment, you could run:
```bash
jupytext --set-formats ipynb,md --execute *.md
```
#### Advanced usage: error tolerance
If any notebook cell errors, execution will terminate and `jupytext` will not save the notebook. This can cause headaches as the details of any error would be encoded in the notebook, which would not have been saved. But there's an error-tolerant way to execute a notebook: `jupyter nbconvert` has a mode which will still save a notebook if a cell errors, producing something akin to what would happen if you ran all cells manually in Jupyter's notebook UI.
```bash
# First, convert script (py/sh/R/jl etc) -> notebook. May need additional args to define input format etc as above.
jupytext --to ipynb script.py
# Then, execute notebook in place and allowing cells to produce errors
jupyter nbconvert --to ipynb --inplace --execute --allow-errors script.ipynb
# One can also combine these to a single command using jupytext --pipe
jupytext --to ipynb --pipe-fmt ipynb \
--pipe 'jupyter nbconvert --to ipynb --execute --allow-errors --stdin --stdout' \
script.py
```
In each of the above, `jupyter nbconvert` could be replaced with any alternative tool to execute a jupyter notebook non-interactively, including [papermill](https://github.com/nteract/papermill) which would allow notebook parameterisation (see [@mwouts' post on the topic here](https://github.com/CFMTech/jupytext_papermill_post/blob/master/README.md)).
## Testing the round-trip conversion
Representing Jupyter notebooks as scripts requires a solid round trip conversion. You don't want your notebooks (nor your scripts) to change because you are converting them to the other form. Our test suite includes a few hundred tests to ensure that round trip conversion is safe.
You can test yourself that the round trip conversion preserves your Jupyter notebooks and scripts. Run for instance:
```bash
# Test the ipynb -> py:percent -> ipynb round trip conversion
jupytext --test notebook.ipynb --to py:percent
# Test the ipynb -> (py:percent + ipynb) -> ipynb (à la paired notebook) conversion
jupytext --test --update notebook.ipynb --to py:percent
```
Note that `jupytext --test` compares the resulting notebooks according to its expectations. If you wish to proceed to a strict comparison of the two notebooks, use `jupytext --test-strict`, and use the flag `-x` to report with more details on the first difference, if any.
## Cell and notebook metadata
When a scripts is converted to an `.ipynb` notebook, Jupytext will set empty notebook and cell [metadata filters](advanced-options.md) to avoid having notebook or cell metadata added back to the script. Remove these filters if you want to store Jupytext's settings, or the kernel information, in the text file.
Cell metadata are available in the `light` and `percent` formats, as well as in the MyST Markdown, R Markdown and Jupytext Markdown formats. R scripts in `spin` format support cell metadata for code cells only. Sphinx Gallery scripts in `sphinx` format do not support cell metadata.
A few cell metadata are not included in the text representation of the notebook, and only the most standard notebook metadata are exported - see the section on [metadata filters](advanced-options.md).
================================================
FILE: docs/using-pre-commit.md
================================================
# Pre-commit hook
Jupytext includes a hook for the [pre-commit](https://pre-commit.com/) framework.
## Do I need to use this hook?
You don't need Jupytext's pre-commit hook if you commit only the `.py` (or `.md`) representation of notebooks in your Git repository.
In that case, a possible pre-commit `.pre-commit-config.yaml` configuration that works well with `.py:percent` notebooks (and does not include a `jupytext` pre-commit hook!) is the following:
```yaml
repos:
- repo: https://github.com/pycqa/isort
rev: 5.11.2
hooks:
- id: isort
args: [--treat-comment-as-code, "# %%", --float-to-top]
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.0.275
hooks:
- id: ruff
args: [--fix, --exit-non-zero-on-fix]
- repo: https://github.com/psf/black
rev: 23.3.0
hooks:
- id: black
language_version: python3
```
## What is the point of having a `jupytext` pre-commit hook?
Jupyter keeps paired `.py` and `.ipynb` files in sync, but the synchronization happens only when you _save_ the notebook in Jupyter. If you edit the `.py` file manually, then the `.ipynb` file will be outdated until you reload and save the notebook in Jupyter, or execute `jupytext --sync`.
Jupytext's pre-commit hook can enforce this synchronization on commits:
```yaml
repos:
- repo: https://github.com/mwouts/jupytext
rev: v1.14.7 # CURRENT_TAG/COMMIT_HASH
hooks:
- id: jupytext
args: [--sync]
```
If you combine Jupytext with other pre-commit hooks, you must ensure that all hooks will pass on any files you generate. For example, if you have a hook for using `black` to format all your python code, then you should use Jupytext's `--pipe` option to also format newly generated Python scripts before writing them:
```yaml
repos:
- repo: https://github.com/mwouts/jupytext
rev: v1.14.7 # CURRENT_TAG/COMMIT_HASH
hooks:
- id: jupytext
args: [--sync, --pipe, black]
additional_dependencies:
- black==23.3.0 # Matches hook
- repo: https://github.com/psf/black
rev: 23.3.0
hooks:
- id: black
language_version: python3
```
Tested examples of how to use the pre-commit hook are available in our [tests](https://github.com/mwouts/jupytext/tree/main/tests/external/pre_commit) -
see for instance [test_pre_commit_1_sync_with_config.py](https://github.com/mwouts/jupytext/blob/main/tests/external/pre_commit/test_pre_commit_1_sync_with_config.py).
================================================
FILE: docs/vs-code.md
================================================
# VS Code
## The Jupytext Sync Extension
You can get the same pairing and synchronization functionality in VS Code as in Jupyter, thanks to the [Jupytext Sync](https://marketplace.visualstudio.com/items?itemName=caenrigen.jupytext-sync) extension for VS Code. The extension is developed by Victor Negîrneac on [GitHub](https://github.com/caenrigen/vscode-jupytext-sync) under a MIT license.
### Installation
Install the extension from the [VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=caenrigen.jupytext-sync) or search for "Jupytext Sync" in the VS Code Extensions view.
### Usage
With the Jupytext Sync extension active, open both the `.ipynb` and `.py` paired files in your editor. When you save changes to either file, they are automatically propagated to the other paired file(s).
💡 **Tip:** Save your changes before switching editors. If a file has unsaved edits, VS Code will not auto-reload it when the paired file is saved, until you either save (overwrite) or revert your changes.

### Configuration
The Jupytext Sync extension is aware of your `jupytext.toml` or `pyproject.toml` [configuration files](config.md), so you can use those to pair your notebooks or control which metadata you want in the text notebooks.
The extension also gives you the option to pair the notebooks individually. Read more about these features in the extension's [documentation](https://marketplace.visualstudio.com/items?itemName=caenrigen.jupytext-sync).
### Requirements
The Jupytext Sync extension requires Jupytext 1.17.3 or later to properly identify which files are paired. Additionally, the `jupytext --sync` command gained the ability to detect simultaneous modifications in Jupytext 1.18.0, so we recommend updating to the latest version if possible.
================================================
FILE: jupyterlab/.gitignore
================================================
.build
.cache
.coverage
.pytest_cache
.eggs
.tox
build
/dist
venv
jupytext.egg-info
future
*.pyc
gits*
.ipynb_checkpoints
jupyterlab_jupytext/labextension
# Ignore node_modules and yarn cache
node_modules/
.yarn
*.bundle.*
lib/
*.log
.eslintcache
.stylelintcache
*.egg-info/
.ipynb_checkpoints
*.tsbuildinfo
# Ignore playwright stuff
**/playwright-report
**/test-results
================================================
FILE: jupyterlab/.prettierignore
================================================
node_modules
**/node_modules
**/lib
**/labextension
**/package.json
**/venv
**/.venv
**/jupyter-config
================================================
FILE: jupyterlab/.yarnrc.yml
================================================
enableImmutableInstalls: false
nodeLinker: node-modules
================================================
FILE: jupyterlab/install.json
================================================
{
"packageManager": "python",
"packageName": "jupytext",
"uninstallInstructions": "Use your Python package manager (pip, conda, etc.) to uninstall the package jupytext"
}
================================================
FILE: jupyterlab/jupyter-config/jupyter_notebook_config.d/jupytext.json
================================================
{
"NotebookApp": {
"nbserver_extensions": {
"jupyterlab_jupytext": true
}
}
}
================================================
FILE: jupyterlab/jupyter-config/jupyter_server_config.d/jupytext.json
================================================
{
"ServerApp": {
"jpserver_extensions": {
"jupyterlab_jupytext": true
}
}
}
================================================
FILE: jupyterlab/jupyterlab_jupytext/__init__.py
================================================
"""Jupyter server and lab extension entry points"""
import asyncio
from jupytext.reraise import reraise
try:
from jupytext.async_contentsmanager import (
build_async_jupytext_contents_manager_class,
)
except ImportError as err:
build_async_jupytext_contents_manager_class = reraise(err)
try:
from jupytext.sync_contentsmanager import build_sync_jupytext_contents_manager_class
except ImportError as err:
build_sync_jupytext_contents_manager_class = reraise(err)
def load_jupyter_server_extension(app): # pragma: no cover
"""Use Jupytext's contents manager"""
if hasattr(app.contents_manager_class, "formats"):
app.log.info(
"[Jupytext Server Extension] NotebookApp.contents_manager_class is "
"(a subclass of) jupytext.TextFileContentsManager already - OK"
)
return
# The server extension call is too late!
# The contents manager was set at NotebookApp.init_configurables
base_class = app.contents_manager_class
asynchronous = asyncio.iscoroutinefunction(base_class.get)
app.log.info(
"[Jupytext Server Extension] Deriving "
+ ("an Async" if asynchronous else "a ")
+ "TextFileContentsManager from "
+ base_class.__name__
)
if asyncio.iscoroutinefunction(base_class.get):
app.contents_manager_class = build_async_jupytext_contents_manager_class(base_class)
else:
app.contents_manager_class = build_sync_jupytext_contents_manager_class(base_class)
try:
# And rerun selected init steps from https://github.com/jupyter/notebook/blob/
# 132f27306522b32fa667a6b208034cb7a04025c9/notebook/notebookapp.py#L1634-L1638
# app.init_configurables()
app.contents_manager = app.contents_manager_class(parent=app, log=app.log)
app.session_manager.contents_manager = app.contents_manager
# app.init_components()
# app.init_webapp()
app.web_app.settings["contents_manager"] = app.contents_manager
# app.init_terminals()
# app.init_signal()
except Exception:
app.log.error(
"""[Jupytext Server Extension] An error occurred. Please deactivate the server extension with
jupyter serverextension disable jupytext
and configure the contents manager manually by adding
c.NotebookApp.contents_manager_class = "jupytext.TextFileContentsManager"
to your .jupyter/jupyter_notebook_config.py file.
"""
)
raise
def _jupyter_labextension_paths():
return [{"src": "labextension", "dest": "jupyterlab-jupytext"}]
================================================
FILE: jupyterlab/lerna.json
================================================
{
"$schema": "node_modules/lerna/schemas/lerna-schema.json",
"version": "independent",
"npmClient": "yarn"
}
================================================
FILE: jupyterlab/package.json
================================================
{
"name": "jupyterlab-jupytext-extensions",
"version": "1.0.0",
"private": true,
"files": [],
"workspaces": [
"packages/*"
],
"scripts": {
"build": "lerna run --parallel build",
"build:prod": "lerna run --parallel build:prod",
"install-ext": "lerna run build:labextension:dev",
"clean:all": "lerna run --parallel clean:all",
"eslint": "eslint . --ext .ts,.tsx --fix",
"eslint:check": "eslint . --ext .ts,.tsx",
"prettier": "prettier --write \"**/*{.ts,.tsx,.js,.jsx,.css,.json,.md}\"",
"prettier:check": "prettier --list-different \"**/*{.ts,.tsx,.js,.jsx,.css,.json,.md}\"",
"stylelint": "jlpm stylelint:check --fix",
"stylelint:check": "stylelint --cache \"packages/*/style/**/*.css\"",
"stylelint:files": "stylelint --fix",
"lint": "jlpm && jlpm prettier && jlpm eslint && jlpm stylelint",
"lint:check": "jlpm prettier:check && jlpm eslint:check && jlpm stylelint:check",
"update-dependency": "update-dependency --lerna",
"watch": "lerna run --parallel watch"
},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "~6.13.2",
"@typescript-eslint/parser": "~6.13.2",
"eslint": "~8.55.0",
"eslint-config-prettier": "~9.1.0",
"eslint-plugin-prettier": "~5.0.1",
"lerna": "^7.1.4",
"prettier": "^3.5.3",
"stylelint": "^16.15.0",
"stylelint-config-prettier": "^9.0.4",
"stylelint-config-recommended": "^15.0.0",
"stylelint-config-standard": "^37.0.0",
"stylelint-prettier": "^5.0.3"
},
"resolutions": {
"tar": "7.5.3",
"glob": "10.5.0",
"systeminformation": "5.27.14"
},
"styleModule": "packages/*/style/index.js",
"eslintIgnore": [
"**/*.d.ts",
"dist",
"*node_modules*",
"coverage",
"tests",
"venv",
".venv"
],
"prettier": {
"singleQuote": true
},
"eslintConfig": {
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended",
"plugin:prettier/recommended"
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": "packages/**/tsconfig.json",
"sourceType": "module",
"tsconfigRootDir": "."
},
"plugins": [
"@typescript-eslint"
],
"rules": {
"@typescript-eslint/naming-convention": [
"error",
{
"selector": "interface",
"format": [
"PascalCase"
],
"custom": {
"regex": "^I[A-Z]",
"match": true
}
}
],
"@typescript-eslint/no-unused-vars": [
"warn",
{
"args": "none"
}
],
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-namespace": "off",
"@typescript-eslint/no-use-before-define": "off",
"@typescript-eslint/quotes": [
"error",
"single",
{
"avoidEscape": true,
"allowTemplateLiterals": false
}
],
"curly": [
"error",
"all"
],
"eqeqeq": "error",
"prefer-arrow-callback": "error"
}
},
"stylelint": {
"extends": [
"stylelint-config-recommended",
"stylelint-config-standard",
"stylelint-prettier/recommended"
],
"rules": {
"no-empty-source": null,
"selector-class-pattern": null,
"property-no-vendor-prefix": null,
"selector-no-vendor-prefix": null,
"value-no-vendor-prefix": null
}
}
}
================================================
FILE: jupyterlab/packages/jupyterlab-jupytext-extension/.gitignore
================================================
*.bundle.*
lib/
node_modules/
*.egg-info/
.ipynb_checkpoints
*.tgz
================================================
FILE: jupyterlab/packages/jupyterlab-jupytext-extension/CHANGELOG.md
================================================
# 1.4.6 (2025-10-18)
We have added support for pairing notebooks to Python scripts in the Marimo format
# 1.4.5 (2025-08-09)
The dependencies of the JupyterLab extension were updated to address [security issues](https://github.com/mwouts/jupytext/security/dependabot):
- [#1360](https://github.com/mwouts/jupytext/pull/1360)
- [#1410](https://github.com/mwouts/jupytext/pull/1410)
- [#1412](https://github.com/mwouts/jupytext/pull/1412)
# 1.4.4 (2025-04-05)
The context menu has a "New Text Notebook" entry. Thanks to [Mahendra Paipuri](https://github.com/mahendrapaipuri) for this PR ([#1365](https://github.com/mwouts/jupytext/pull/1365))!
We have updated the JupyterLab extension dependencies ([#1300](https://github.com/mwouts/jupytext/pull/1300), [#1355](https://github.com/mwouts/jupytext/pull/1355), [#1360](https://github.com/mwouts/jupytext/pull/1360)). Thanks to [Mahendra Paipuri](https://github.com/mahendrapaipuri) for these PRs!
The dependencies of the JupyterLab extension were updated to address [security issues](https://github.com/mwouts/jupytext/security/dependabot):
- [#1272](https://github.com/mwouts/jupytext/issues/1272)
- [#1273](https://github.com/mwouts/jupytext/issues/1273)
- [#1280](https://github.com/mwouts/jupytext/issues/1280)
- [#1285](https://github.com/mwouts/jupytext/issues/1285)
- [#1290](https://github.com/mwouts/jupytext/issues/1290)
- [#1304](https://github.com/mwouts/jupytext/pull/1304)
# 1.4.3 (2024-05-05)
- JupyterLab's dependency `ejs` was updated from 3.1.9 to 3.1.10 ([#1231](https://github.com/mwouts/jupytext/issues/1231))
- JupyterLab's dependency `follow-redirects` was updated from 1.15.4 to 1.15.6 ([#1218](https://github.com/mwouts/jupytext/issues/1218))
- JupyterLab's dependency `ip` was updated from 2.0.0 to 2.0.1 ([#1216](https://github.com/mwouts/jupytext/issues/1216))
# 1.4.2 (2024-01-13)
- Fixed an issue about unpairing notebooks from the Jupytext Menu ([#1197](https://github.com/mwouts/jupytext/issues/1197))
- JupyterLab's dependency `follow-redirects` was updated from 1.15.3 to 1.15.4 ([#1203](https://github.com/mwouts/jupytext/issues/1203))
# 1.4.1 (2023-11-27)
- The Jupytext Menu is back! And text notebooks can be created directly from the launcher. This is an outstanding contribution by [Mahendra Paipuri](https://github.com/mahendrapaipuri) ([#1154](https://github.com/mwouts/jupytext/issues/1154), [#1163](https://github.com/mwouts/jupytext/issues/1163)). This requires JupyterLab 4.x or Jupyter Notebook 7.x.
# 1.4.0 (2023-10-22)
- This version of the JupyterLab extension is fully compatible with (and requires) JupyterLab 4.x.
# 1.3.11 (2023-10-22)
- This version is the same as 1.3.10. It was re-published to include the README that was missing in 1.3.10.
# 1.3.10 (2023-10-22)
- The server extension for `jupytext` has been moved from core Jupytext to the (Python) `jupyterlab-jupytext` extension.
- The JupyterLab extension is now compatible with the [JupyterLab RISE](https://github.com/jupyterlab-contrib/rise) extension. Many thanks to [Frédéric Collonval](https://github.com/fcollonval) for his PR ([#1126](https://github.com/mwouts/jupytext/pull/1126))!
- This version of the JupyterLab extension is compatible with JupyterLab 4.x. Many thanks to [Thierry Parmentelat](https://github.com/parmentelat) for his PRs! ([#1092](https://github.com/mwouts/jupytext/pull/1092), [#1109](https://github.com/mwouts/jupytext/pull/1109))
# 1.3.9 (2022-06-02)
- We updated the `yarn.lock` file for the jupyter lab extension to address security vulnerabilities ([#904](https://github.com/mwouts/jupytext/issues/904), [#925](https://github.com/mwouts/jupytext/issues/925), [#935](https://github.com/mwouts/jupytext/issues/935), [#939](https://github.com/mwouts/jupytext/issues/939), [#984](https://github.com/mwouts/jupytext/issues/984), [#1005](https://github.com/mwouts/jupytext/issues/1005), [#1011](https://github.com/mwouts/jupytext/issues/1011), [#1030](https://github.com/mwouts/jupytext/issues/1030), [#1036](https://github.com/mwouts/jupytext/issues/1036), [#1052](https://github.com/mwouts/jupytext/pull/1052))
- This version is the last version that is compatible with `jupyterlab==3.x`.
# 1.3.8 (2021-12-03)
- The "Jupytext Notebook" factory that lets the user configure the Notebook viewer as the default for text notebooks accepts more filetypes: "myst", "r-markdown" and "quarto" ([#803](https://github.com/mwouts/jupytext/issues/803))
# 1.3.7 (2021-11-30)
The extension for JupyterLab benefited from a series of improvements contributed by [Frédéric Collonval](https://github.com/fcollonval):
- A new "Jupytext Notebook" factory offers the option to open text notebooks directly with the notebook view (#803). To use it, follow the instructions in the [documentation](https://github.com/mwouts/jupytext/blob/main/docs/index.md#Install).
- The ICommandPalette is optional, for compatibility with RISE within JupyterLab [RISE#605](https://github.com/damianavila/RISE/pull/605)
- Added support for translation.
We also upgraded the extension dependency and especially `json-schema` to address a security vulnerability.
# 1.3.6 (2021-09-23)
- We have upgraded the extension dependencies and especially `ansi-regex` to fix a security vulnerability (#857)
# 1.3.5 (2021-09-05)
- The extension can pair notebooks with `.qmd` files (Quarto format) (#837)
# 1.3.4 (2021-08-31)
- We have upgraded the extension dependencies and especially `tar` and `url-parse` to fix two security vulnerabilities (#842) (#843)
# 1.3.3 (2021-06-10)
- We have upgraded the extension dependencies and especially `ws` to fix a security vulnerability (#798)
# 1.3.2 (2021-05-21)
- We have upgraded the extension dependencies and especially `hosted-git-info` to fix a security vulnerability (#783)
# 1.3.1 (2021-03-07)
- We have updated `yarn.lock` to upgrade `marked` to `2.0` and fix a moderate vulnerability in the extension dependencies (#750)
# 1.3.0 (2021-01-05)
- The `jupyterlab-jupytext` extension is now distributed using `jupyter-packaging`, thanks to Martin Renou's awesome contribution (#683).
# 1.2.3 (2020-10-14)
- Remove duplicate `jupyterlab` entry in `package.json` (#654)
# 1.2.2 (2020-10-13)
- The description of the `jupyterlab-jupytext` extension was updated (#654)
- The explicit dependency on the `jupytext` Python package was documented in `package.json` (#654)
# 1.2.1 (2020-03-18)
- The extension can pair a notebook to the new MyST Markdown format, developed by the [ExecutableBookProject](https://github.com/ExecutableBookProject) team. Thanks to Chris Sewell for his PRs! (#447 #456 #458)
# 1.2.0 (2020-03-09)
- This version of the extension is compatible with JupyterLab 2.0. Many thanks to Jean Helie! (#449)
# 1.1.1 (2019-12-26)
- The `nomarker` format is available through the Jupytext commands (requires `jupytext>=1.3.1`).
# 1.1.0 (2019-11-03)
- Multiple pairings are supported (#290)
- The documentation includes the last version numbers for both Jupytext Python and for this extension (#311)
- Documentation says clearly that the extension is bundled with the Python package (#350)
# 1.0.2 (2019-07-18)
- Fixed an incorrect `target_format` entry inserted by the version 1.0.1 of the extension.
# 1.0.1 (2019-07-18)
- A click on a selected format toggle the pairing (#289)
- Use `JupyterFrontEnd` and `JupyterFrontEndPlugin` from `@jupyterlab/application` rather than `JupyterLab` and `JupyterLabPlugin` for compatibility with JupyterLab 1.0.
# 1.0.0 (2019-07-06)
- First extension compatible with JupyterLab 1.0
# 0.19.0 (2019-07-06)
- Last extension compatible with JupyterLab 0.35
# 0.1.0 (2018-10-02)
- Initial release of the extension
================================================
FILE: jupyterlab/packages/jupyterlab-jupytext-extension/README.md
================================================
# A JupyterLab extension for Jupytext
This extension adds a few [Jupytext](https://github.com/mwouts/jupytext) commands to the command palette. Use these to select the desired ipynb/text pairing for your notebook.
The latest version for this extension is [](https://badge.fury.io/js/jupyterlab-jupytext).
Most users do not need to install this extension, since it is already included in the latest [jupytext](https://github.com/mwouts/jupytext/), both on [](https://pypi.python.org/pypi/jupytext) and
[](https://anaconda.org/conda-forge/jupytext).

## Installation
Please install [Jupytext](https://github.com/mwouts/jupytext/blob/main/README.md#Install) first. As mentioned above, both the `pip` and `conda` packages do include the latest version of the JupyterLab extension, so in most cases you don't need to specifically install this `npm` package.
In case you're not using JupyterLab 4.x, you will have to install an older version of the extension that is compatible with your version. Please first install `jupytext` using `pip` or `conda`, and then downgrade the extension to a version compatible with your version of JupyterLab with:
```bash
jupyter labextension install jupyterlab-jupytext@1.3.11 # for JupyterLab 3.x
jupyter labextension install jupyterlab-jupytext@1.2.2 # for JupyterLab 2.x
jupyter labextension install jupyterlab-jupytext@1.1.1 # for JupyterLab 1.x
```
# How to develop this extension
For fine-grained access to the `jlpm` command and various build steps:
```bash
pip install -e '.[dev]'
cd jupyterlab/packages/jupyterlab-jupytext-extension
jlpm
jlpm install:extension # Symlink into `{sys.prefix}/share/jupyter/labextensions`
```
(see also the instructions at [developing.md](../../../docs/developing.md) on how to create a Python environment with a recent version of `nodejs`)
Watch the source directory and automatically rebuild the `lib` folder:
```bash
cd jupyterlab/packages/jupyterlab-jupytext-extension
# Watch the source directory in one terminal, automatically rebuilding when needed
jlpm watch
# Run JupyterLab in another terminal
jupyter lab
```
While running `jlpm watch`, every saved change to a `.ts` file will immediately be
built locally and available in your running Jupyter client. "Hard" refresh JupyterLab or Notebook
with CTRL-F5 or ⌘-F5 to load the change in your browser
(you may need to wait several seconds for the extension to be fully rebuilt).
Read more on this on the [JupyterLab documentation](https://jupyterlab.readthedocs.io/en/latest/extension/extension_dev.html#developing-a-prebuilt-extension).
# How to publish a new version of the extension on npm
Please note that the main purpose of updating the extension on [npm](https://www.npmjs.com) is to keep the npm documentation up-to-date, since the extension is made available within the Python package itself.
Make sure you have `nodejs>=18` installed, bump the version in `package.json`, and then:
```bash
cd jupyterlab/packages/jupyterlab-jupytext-extension
# Package the extension
npm pack
# Test the extension locally
jupyter labextension install jupyterlab-jupytext-xxx.tgz
# Publish the package on npm with
npm publish --access=public
```
================================================
FILE: jupyterlab/packages/jupyterlab-jupytext-extension/package.json
================================================
{
"name": "jupyterlab-jupytext",
"version": "1.4.6",
"description": "Save Jupyter Notebooks as Scripts or Markdown files that work well with version control & external text editors",
"keywords": [
"jupyter",
"jupytext",
"jupyterlab",
"jupyterlab-extension"
],
"homepage": "https://github.com/mwouts/jupytext/tree/main/jupyterlab/packages/jupyterlab-jupytext",
"bugs": {
"url": "https://github.com/mwouts/jupytext/issues"
},
"license": "MIT",
"author": "Marc Wouts",
"files": [
"lib/**/*.{d.ts,eot,gif,html,jpg,js,js.map,json,png,svg,woff2,ttf}",
"style/**/*.{css,eot,gif,html,jpg,json,png,svg,woff2,ttf}"
],
"main": "lib/index.js",
"types": "lib/index.d.ts",
"repository": {
"type": "git",
"url": "https://github.com/mwouts/jupytext.git"
},
"workspaces": [
"ui-tests"
],
"scripts": {
"build": "jlpm run build:lib && jlpm run build:labextension:dev && jlpm run copy:extensioncfgfile",
"build:labextension": "jupyter labextension build .",
"build:labextension:dev": "jupyter labextension build --development True .",
"build:lib": "tsc",
"build:prod": "jlpm run build:lib && jlpm run build:labextension && jlpm run copy:extensioncfgfile",
"copy:extensioncfgfile": "cp ../../install.json ../../jupyterlab_jupytext/labextension",
"clean": "jlpm run clean:lib",
"clean:all": "jlpm run clean:lib && jlpm run clean:labextension",
"clean:labextension": "rimraf ../../jupyterlab_jupytext/labextension",
"clean:lib": "rimraf lib tsconfig.tsbuildinfo",
"install:extension": "python ../../scripts/install_extension.py",
"watch": "run-p watch:src watch:labextension",
"watch:labextension": "jupyter labextension watch .",
"watch:src": "tsc -w"
},
"jupyterlab": {
"discovery": {
"server": {
"managers": [
"pip",
"conda"
],
"base": {
"name": "jupyterlab_jupytext"
}
}
},
"extension": true,
"schemaDir": "schema",
"outputDir": "../../jupyterlab_jupytext/labextension",
"sharedPackages": {
"jupyterlab-rise": {
"singleton": true
}
}
},
"dependencies": {
"@jupyterlab/application": "^4.4.9",
"@jupyterlab/apputils": "^4.4.9",
"@jupyterlab/codeeditor": "^4.4.9",
"@jupyterlab/docregistry": "^4.4.9",
"@jupyterlab/filebrowser": "^4.4.9",
"@jupyterlab/launcher": "^4.4.9",
"@jupyterlab/nbformat": "^4.4.9",
"@jupyterlab/notebook": "^4.4.9",
"@jupyterlab/rendermime": "^4.4.9",
"@jupyterlab/settingregistry": "^4.4.9",
"@jupyterlab/translation": "^4.4.9",
"@jupyterlab/ui-components": "^4.4.9",
"@lumino/commands": "^2.3.3",
"@lumino/coreutils": "^2.2.2",
"@lumino/disposable": "^2.1.5",
"buffer": "^6.0.3",
"jupyterlab-rise": "^0.43.1"
},
"devDependencies": {
"@jupyterlab/builder": "^4.4.9",
"npm-run-all": "^4.1.5",
"rimraf": "^6.0.1",
"typescript": "~5.9.3"
}
}
================================================
FILE: jupyterlab/packages/jupyterlab-jupytext-extension/schema/plugin.json
================================================
{
"title": "Jupytext",
"description": "List of Jupytext Text Notebook formats that will be added to launcher and jupytext menu.",
"properties": {
"auto:percent": {
"type": "boolean",
"title": "Text Notebook as Script in Percent Format",
"default": true
},
"auto:light": {
"type": "boolean",
"title": "Text Notebook as Script in Light Format",
"default": false
},
"auto:hydrogen": {
"type": "boolean",
"title": "Text Notebook as Script in Hydrogen Format",
"default": false
},
"auto:nomarker": {
"type": "boolean",
"title": "Text Notebook as Script in Nomarker Format",
"default": false
},
"py:marimo": {
"type": "boolean",
"title": "Text Notebook as Marimo Script",
"default": false
},
"md:myst": {
"type": "boolean",
"title": "MyST Markdown Notebook",
"default": true
},
"md": {
"type": "boolean",
"title": "Markdown Notebook",
"default": false
},
"Rmd": {
"type": "boolean",
"title": "R Markdown Notebook",
"default": false
},
"qmd": {
"type": "boolean",
"title": "Quarto Markdown Notebook",
"default": false
},
"NOTE": {
"description": "Refresh the current browser tab for the changes to take effect.",
"type": "null"
}
},
"additionalProperties": false,
"type": "object"
}
================================================
FILE: jupyterlab/packages/jupyterlab-jupytext-extension/src/commands.ts
================================================
import { showErrorMessage } from '@jupyterlab/apputils';
import { INotebookTracker } from '@jupyterlab/notebook';
import * as nbformat from '@jupyterlab/nbformat';
import { TranslationBundle } from '@jupyterlab/translation';
import {
LANGUAGE_INDEPENDENT_NOTEBOOK_EXTENSIONS,
JUPYTEXT_FORMATS,
IJupytextSection,
} from './tokens';
/**
* Get Jupytext format of current widget if it is a text notebook
*/
function getWidgetJupytextFormats(
notebookTracker: INotebookTracker,
): Array {
const model = notebookTracker.currentWidget.context.model;
const jupytext: IJupytextSection = (model as any).getMetadata('jupytext');
if (!jupytext) {
return [];
}
const formats: Array = jupytext.formats
? jupytext.formats.split(',')
: [];
return formats.filter((format) => {
return format !== '';
});
}
/**
* Get file extension of current notebook widget
*/
function getNotebookFileExtension(notebookTracker: INotebookTracker): string {
let notebookFileExtension: string | undefined =
notebookTracker.currentWidget.context.path.split('.').pop();
if (!notebookFileExtension) {
return '';
}
notebookFileExtension = LANGUAGE_INDEPENDENT_NOTEBOOK_EXTENSIONS.includes(
notebookFileExtension,
)
? notebookFileExtension
: 'auto';
return notebookFileExtension;
}
/**
* Get a list of all selected formats
*/
function getSelectedFormats(notebookTracker: INotebookTracker): Array {
if (!notebookTracker.currentWidget) {
return [];
}
let formats = getWidgetJupytextFormats(notebookTracker);
const model = notebookTracker.currentWidget.context.model;
const languageInfo = (model as any).getMetadata(
'language_info',
) as nbformat.ILanguageInfoMetadata;
if (languageInfo && languageInfo.file_extension) {
const scriptExt = languageInfo.file_extension.substring(1);
formats = formats.map((format) => {
// By default use percent format
if (format === scriptExt) {
return 'auto:percent';
}
// Replace language specific extension with auto
return format.replace(`${scriptExt}:`, 'auto:');
});
}
const notebookFileExtension = getNotebookFileExtension(notebookTracker);
if (!notebookFileExtension) {
return formats;
}
// Remove variant after : in format
const formatExtensions = formats.map((format) => {
return format.split(':')[0];
});
// If current notebook file extension in formats, return
if (formatExtensions.includes(notebookFileExtension)) {
return formats;
}
// When notebook loads for the first time, ipynb extension would not be
// in the formats. Here we add it and return formats
if (
LANGUAGE_INDEPENDENT_NOTEBOOK_EXTENSIONS.includes(notebookFileExtension)
) {
formats.push(notebookFileExtension);
} else {
const model = notebookTracker.currentWidget.context.model;
const jupytext: IJupytextSection = (model as any).getMetadata(
'jupytext',
) as IJupytextSection;
const formatName = jupytext
? jupytext?.text_representation?.formatName || 'percent'
: 'percent';
formats.push(`auto:${formatName}`);
}
return formats;
}
/**
* Toggle pair command
*/
export function isPairCommandToggled(
format: string,
notebookTracker: INotebookTracker,
): boolean {
if (!notebookTracker.currentWidget) {
return false;
}
// Get selected formats on current widget
const selectedFormats = getSelectedFormats(notebookTracker);
if (format === 'custom') {
for (const selectedFormat of selectedFormats) {
if (!JUPYTEXT_FORMATS.includes(selectedFormat)) {
return true;
}
}
return false;
}
return selectedFormats.includes(format);
}
/**
* Enable pair command
*/
export function isPairCommandEnabled(
format: string,
notebookTracker: INotebookTracker,
): boolean {
if (!notebookTracker.currentWidget) {
return false;
}
const notebookFileExtension: string | undefined =
notebookTracker.currentWidget.context.path.split('.').pop();
if (format === notebookFileExtension) {
return false;
}
// Get selected formats on current widget
const selectedFormats = getSelectedFormats(notebookTracker);
if (format === 'none') {
return selectedFormats.length > 1;
}
return true;
}
/**
* Execute pair command
*/
export function executePairCommand(
command: string,
format: string,
notebookTracker: INotebookTracker,
trans: TranslationBundle,
): void {
if (!notebookTracker.currentWidget) {
return;
}
const model = notebookTracker.currentWidget.context.model;
let jupytext: IJupytextSection = (model as any).getMetadata('jupytext') as
| IJupytextSection
| undefined;
// Get selected formats on current widget
let selectedFormats = getSelectedFormats(notebookTracker);
// Toggle the selected format
console.debug('Jupytext: executing command=' + command);
if (format === 'custom') {
showErrorMessage(
trans.__('Error'),
trans.__(
'Please edit the notebook metadata directly if you wish a custom configuration.',
),
);
return;
}
// Get current notebook widget extension
const notebookFileExtension = getNotebookFileExtension(notebookTracker);
// Toggle the selected format
const index = selectedFormats.indexOf(format);
if (format === 'none') {
// Only keep one format - one that matches the current extension
for (const selectedFormat of selectedFormats) {
if (selectedFormat.split(':')[0] === notebookFileExtension) {
selectedFormats = [selectedFormat];
break;
}
}
} else if (index !== -1) {
selectedFormats.splice(index, 1);
// The current file extension can't be unpaired
let extFound = false;
for (const selectedFormat of selectedFormats) {
if (selectedFormat.split(':')[0] === notebookFileExtension) {
extFound = true;
break;
}
}
if (!extFound) {
return;
}
} else {
// We can't have the same extension multiple times
const newFormats = [];
for (const selectedFormat of selectedFormats) {
if (selectedFormat.split(':')[0] !== format.split(':')[0]) {
newFormats.push(selectedFormat);
}
}
selectedFormats = newFormats;
selectedFormats.push(format);
}
if (selectedFormats.length === 1) {
if (notebookFileExtension !== 'auto') {
selectedFormats = [];
} else if (jupytext?.text_representation) {
jupytext.text_representation.formatName =
selectedFormats[0].split(':')[1];
selectedFormats = [];
}
}
if (selectedFormats.length === 0) {
// an older version was re-fetching the jupytext metadata here
// but this is not necessary, as the metadata is already available
if (!jupytext) {
return;
}
if (jupytext.formats) {
delete jupytext.formats;
}
if (Object.keys(jupytext).length === 0) {
(model as any).deleteMetadata('jupytext');
}
(model as any).setMetadata('jupytext', jupytext);
return;
}
// set the desired format
if (jupytext) {
jupytext.formats = selectedFormats.join();
} else {
jupytext = { formats: selectedFormats.join() };
}
(model as any).setMetadata('jupytext', jupytext);
}
/**
* Toggle metadata command
*/
export function isMetadataCommandToggled(
notebookTracker: INotebookTracker,
): boolean {
if (!notebookTracker.currentWidget) {
return false;
}
const model = notebookTracker.currentWidget.context.model;
const jupytextMetadata = (model as any).getMetadata('jupytext');
if (!jupytextMetadata) {
return false;
}
const jupytext: IJupytextSection =
jupytextMetadata as unknown as IJupytextSection;
if (jupytext.notebook_metadata_filter === '-all') {
return false;
}
return true;
}
/**
* Enable metadata command
*/
export function isMetadataCommandEnabled(
notebookTracker: INotebookTracker,
): boolean {
if (!notebookTracker.currentWidget) {
return false;
}
const model = notebookTracker.currentWidget.context.model;
const jupytextMetadata = (model as any).getMetadata('jupytext');
if (!jupytextMetadata) {
return false;
}
const jupytext: IJupytextSection =
jupytextMetadata as unknown as IJupytextSection;
if (jupytext.notebook_metadata_filter === undefined) {
return true;
}
if (jupytext.notebook_metadata_filter === '-all') {
return true;
}
return false;
}
/**
* Execute metadata command
*/
export function executeMetadataCommand(
notebookTracker: INotebookTracker,
): void {
console.debug('Jupytext: toggling YAML header');
if (!notebookTracker.currentWidget) {
return;
}
const model = notebookTracker.currentWidget.context.model;
const jupytextMetadata = (model as any).getMetadata('jupytext');
if (!jupytextMetadata) {
return;
}
const jupytext = ((jupytextMetadata as unknown) ?? {}) as IJupytextSection;
if (jupytext.notebook_metadata_filter) {
delete jupytext.notebook_metadata_filter;
if (jupytext.notebook_metadata_filter === '-all') {
delete jupytext.notebook_metadata_filter;
}
} else {
jupytext.notebook_metadata_filter = '-all';
if (jupytext.notebook_metadata_filter === undefined) {
jupytext.notebook_metadata_filter = '-all';
}
}
(model as any).setMetadata('jupytext', jupytext);
}
================================================
FILE: jupyterlab/packages/jupyterlab-jupytext-extension/src/factory.ts
================================================
import {
IToolbarWidgetRegistry,
createToolbarFactory,
} from '@jupyterlab/apputils';
import {
INotebookTracker,
NotebookPanel,
NotebookWidgetFactory,
} from '@jupyterlab/notebook';
import { ISettingRegistry } from '@jupyterlab/settingregistry';
import { DocumentRegistry } from '@jupyterlab/docregistry';
import { IRenderMimeRegistry } from '@jupyterlab/rendermime';
import { IEditorServices } from '@jupyterlab/codeeditor';
import { ITranslator, TranslationBundle } from '@jupyterlab/translation';
import { IDisposable } from '@lumino/disposable';
import { IRisePreviewFactory } from 'jupyterlab-rise';
import { FACTORY, FILE_TYPES } from './tokens';
export function createFactory(
kernelFileTypeNames: string[],
toolbarRegistry: IToolbarWidgetRegistry,
settingRegistry: ISettingRegistry,
docRegistry: DocumentRegistry,
notebookTracker: INotebookTracker,
notebookFactory: NotebookWidgetFactory.IFactory,
contentFactory: NotebookPanel.IContentFactory,
editorServices: IEditorServices,
rendermime: IRenderMimeRegistry,
translator: ITranslator,
trans: TranslationBundle,
riseFactory: IRisePreviewFactory | null,
) {
const allFileTypes = FILE_TYPES.concat(kernelFileTypeNames);
// primarily this block is copied/pasted from jlab4 code and specifically
// jupyterlab/packages/notebook-extension/src/index.ts
// inside the function `activateWidgetFactory` at line 1150 as of this writing
//
const toolbarFactory = createToolbarFactory(
toolbarRegistry,
settingRegistry,
'Notebook',
'@jupyterlab/notebook-extension:panel',
translator,
);
// Duplicate notebook factory to apply it on Jupytext notebooks
// Mirror: https://github.com/jupyterlab/jupyterlab/blob/8a8c3752564f37493d4eb6b4c59008027fa83880/packages/notebook-extension/src/index.ts#L860
const factory = new NotebookWidgetFactory({
name: FACTORY,
label: trans.__(FACTORY),
fileTypes: allFileTypes,
modelName: notebookFactory.modelName ?? 'notebook',
preferKernel: notebookFactory.preferKernel ?? true,
canStartKernel: notebookFactory.canStartKernel ?? true,
rendermime,
contentFactory,
editorConfig: notebookFactory.editorConfig,
notebookConfig: notebookFactory.notebookConfig,
mimeTypeService: editorServices.mimeTypeService,
toolbarFactory: toolbarFactory,
translator,
});
docRegistry.addWidgetFactory(factory);
// The list of extensions in the Jupytext Notebook factory.
const factoryExtensions: IDisposable[] = [];
const updateWidgetExtensions = () => {
// Dispose of all existing extensions.
factoryExtensions.forEach((extension) => extension.dispose());
// Add all the widgets extensions in the Notebook factory.
for (const extension of docRegistry.widgetExtensions('Notebook')) {
docRegistry.addWidgetExtension(FACTORY, extension);
}
};
// Listen for changes in Notebook factory extensions.
docRegistry.changed.connect((_, change) => {
if (change.type === 'widgetExtension' && change.name === 'Notebook') {
updateWidgetExtensions();
}
});
updateWidgetExtensions();
// Register widget created with the new factory in the notebook tracker
// This is required to activate notebook commands (and therefore shortcuts)
let id = 0; // The ID counter for notebook panels.
const ft = docRegistry.getFileType('notebook');
factory.widgetCreated.connect((sender, widget) => {
// If the notebook panel does not have an ID, assign it one.
widget.id = widget.id || `notebook-jupytext-${++id}`;
// Set up the title icon
widget.title.icon = ft?.icon;
widget.title.iconClass = ft?.iconClass ?? '';
widget.title.iconLabel = ft?.iconLabel ?? '';
// Notify the widget tracker if restore data needs to update.
widget.context.pathChanged.connect(() => {
// @ts-expect-error Trick using private API
void notebookTracker.save(widget);
});
// Add the notebook panel to the tracker.
// @ts-expect-error Trick using private API
void notebookTracker.add(widget);
});
// Add support for RISE slides
if (riseFactory) {
for (const fileType of allFileTypes) {
riseFactory.addFileType(fileType);
}
}
}
================================================
FILE: jupyterlab/packages/jupyterlab-jupytext-extension/src/index.ts
================================================
import {
JupyterFrontEnd,
JupyterFrontEndPlugin,
} from '@jupyterlab/application';
import {
ICommandPalette,
IToolbarWidgetRegistry,
showErrorMessage,
} from '@jupyterlab/apputils';
import { IDocumentManager } from '@jupyterlab/docmanager';
import { IEditorLanguageRegistry } from '@jupyterlab/codemirror';
import { Contents } from '@jupyterlab/services';
import { ILauncher } from '@jupyterlab/launcher';
import { ISettingRegistry } from '@jupyterlab/settingregistry';
import { IEditorServices } from '@jupyterlab/codeeditor';
import {
INotebookTracker,
INotebookWidgetFactory,
NotebookPanel,
NotebookWidgetFactory,
} from '@jupyterlab/notebook';
import { IRenderMimeRegistry } from '@jupyterlab/rendermime';
import { IDefaultFileBrowser } from '@jupyterlab/filebrowser';
import { IMainMenu } from '@jupyterlab/mainmenu';
import { ITranslator, nullTranslator } from '@jupyterlab/translation';
import { LabIcon } from '@jupyterlab/ui-components';
import { DisposableSet } from '@lumino/disposable';
import { Menu } from '@lumino/widgets';
import { JSONExt, ReadonlyJSONValue } from '@lumino/coreutils';
import { IRisePreviewFactory } from 'jupyterlab-rise';
import {
JUPYTEXT_EXTENSION_ID,
JUPYTEXT_PAIR_COMMANDS_FILETYPE_DATA,
JUPYTEXT_FORMATS,
TEXT_NOTEBOOKS_LAUNCHER_ICONS,
CommandIDs,
IFileTypeData,
JupytextIcon,
AUTO_LANGUAGE_FILETYPE_DATA,
} from './tokens';
import {
isPairCommandToggled,
isPairCommandEnabled,
executePairCommand,
isMetadataCommandToggled,
isMetadataCommandEnabled,
executeMetadataCommand,
} from './commands';
import { registerFileTypes } from './registry';
import { createFactory } from './factory';
import {
getAvailableKernelLanguages,
getAvailableCreateTextNotebookCommands,
createNewTextNotebook,
} from './utils';
/**
* Initialization data for the jupyterlab-jupytext extension.
*/
const extension: JupyterFrontEndPlugin = {
id: JUPYTEXT_EXTENSION_ID,
autoStart: true,
optional: [
ILauncher,
IMainMenu,
IDefaultFileBrowser,
ITranslator,
ICommandPalette,
IRisePreviewFactory,
],
requires: [
NotebookPanel.IContentFactory,
IEditorServices,
IDocumentManager,
IEditorLanguageRegistry,
IRenderMimeRegistry,
INotebookWidgetFactory,
INotebookTracker,
ISettingRegistry,
IToolbarWidgetRegistry,
],
activate: async (
app: JupyterFrontEnd,
contentFactory: NotebookPanel.IContentFactory,
editorServices: IEditorServices,
docManager: IDocumentManager,
languages: IEditorLanguageRegistry,
rendermime: IRenderMimeRegistry,
notebookFactory: NotebookWidgetFactory.IFactory,
notebookTracker: INotebookTracker,
settingRegistry: ISettingRegistry,
toolbarRegistry: IToolbarWidgetRegistry,
launcher: ILauncher | null,
mainmenu: IMainMenu | null,
defaultBrowser: IDefaultFileBrowser | null,
translator: ITranslator | null,
palette: ICommandPalette | null,
riseFactory: IRisePreviewFactory | null,
) => {
console.log('JupyterLab extension jupytext is activating...');
const trans = (translator ?? nullTranslator).load('jupytext');
// Load settings
const includeFormats = TEXT_NOTEBOOKS_LAUNCHER_ICONS;
if (settingRegistry) {
const settings = await settingRegistry.load(extension.id);
for (const format of JUPYTEXT_FORMATS) {
const addFormat = settings.get(format).composite as boolean;
if (addFormat && !includeFormats.includes(format)) {
includeFormats.push(format);
} else if (!addFormat && includeFormats.includes(format)) {
includeFormats.splice(includeFormats.indexOf(format), 1);
}
}
}
// Unpack necessary components
const { commands, serviceManager, docRegistry } = app;
// Initialise Jupytext create notebook submenu and add it to File menu
const jupytextCreateMenu = new Menu({ commands: app.commands });
jupytextCreateMenu.id = 'jp-mainmenu-jupytext-new-menu';
jupytextCreateMenu.title.label = trans.__('New Text Notebook');
mainmenu.fileMenu.addItem({
rank: 0.97,
type: 'submenu',
submenu: jupytextCreateMenu,
});
// Add create text notebook submenu to context menu
// Rank 53 as 52 is used for classic notebook
// https://github.com/jupyterlab/jupyterlab/blob/4d34bbbea2afc7385169d92bf7bc0c9e0face3a9/packages/notebook-extension/schema/tracker.json#L363-L369
app.contextMenu.addItem({
submenu: jupytextCreateMenu,
type: 'submenu',
selector: '.jp-DirListing-content',
rank: 53,
});
// Initialise Jupytext menu and add it to main menu
const jupytextMenu = new Menu({ commands: app.commands });
mainmenu.fileMenu.addItem({
rank: 0.98,
type: 'submenu',
submenu: jupytextMenu,
});
jupytextMenu.id = 'jp-mainmenu-jupytext-menu';
jupytextMenu.title.label = trans.__('Jupytext');
// Get all Jupytext formats
let rank = 0;
const separatorIndex: number[] = [];
JUPYTEXT_PAIR_COMMANDS_FILETYPE_DATA.forEach(
(value: IFileTypeData[], key: string) => {
value.map((fileType: IFileTypeData) => {
const format = key;
const command = `jupytext:pair-nb-with-${format}`;
commands.addCommand(command, {
label: (args) => {
if (args.isPalette) {
return (
(fileType.paletteLabel as string) ?? trans.__('Pair notebook')
);
}
return (fileType.caption as string) ?? trans.__('Pair notebook');
},
caption: trans.__(fileType.caption),
icon: (args) => {
if (args.isPalette) {
return undefined;
} else {
return fileType.iconName
? LabIcon.resolve({
icon: fileType.iconName as string,
})
: undefined;
}
},
isToggled: () => {
return isPairCommandToggled(format, notebookTracker);
},
isEnabled: () => {
return isPairCommandEnabled(format, notebookTracker);
},
execute: () => {
return executePairCommand(
command,
format,
notebookTracker,
trans,
);
},
});
console.debug(
'Registering pairing command=' + command + ' with rank=' + rank,
);
palette?.addItem({
command,
args: { isPalette: true },
rank: rank + 1,
category: 'Jupytext',
});
// Add to jupytext pair menu
jupytextMenu.addItem({
command: command,
});
if (fileType.separator) {
separatorIndex.push(rank);
}
rank += 1;
});
},
);
// Add separators in jupytext pair menu
separatorIndex.map((index, idx) => {
jupytextMenu.insertItem(index + idx + 1, {
type: 'separator',
});
});
// Metadata in text representation
commands.addCommand(CommandIDs.metadata, {
label: trans.__('Include Metadata'),
icon: (args) => {
if (args.isPalette) {
return undefined;
} else {
return JupytextIcon;
}
},
isToggled: () => {
return isMetadataCommandToggled(notebookTracker);
},
isEnabled: () => {
return isMetadataCommandEnabled(notebookTracker);
},
execute: () => {
return executeMetadataCommand(notebookTracker);
},
});
palette?.addItem({
command: CommandIDs.metadata,
args: { isPalette: true },
rank: 98,
category: 'Jupytext',
});
jupytextMenu.addItem({
type: 'separator',
});
jupytextMenu.addItem({
command: CommandIDs.metadata,
});
jupytextMenu.addItem({
type: 'separator',
});
// Register Jupytext FAQ command
commands.addCommand(CommandIDs.faq, {
label: trans.__('Jupytext FAQ'),
icon: (args) => {
if (args.isPalette) {
return undefined;
} else {
return JupytextIcon;
}
},
execute: () => {
window.open('https://jupytext.readthedocs.io/en/latest/faq.html');
},
});
palette?.addItem({
command: CommandIDs.faq,
args: { isPalette: true },
rank: 99,
category: 'Jupytext',
});
jupytextMenu.addItem({
command: CommandIDs.faq,
});
// Register Jupytext Reference
commands.addCommand(CommandIDs.reference, {
label: trans.__('Jupytext Reference'),
icon: (args) => {
if (args.isPalette) {
return undefined;
} else {
return JupytextIcon;
}
},
execute: () => {
window.open('https://jupytext.readthedocs.io/en/latest/');
},
});
palette?.addItem({
command: CommandIDs.reference,
args: { isPalette: true },
rank: 100,
category: 'Jupytext',
});
jupytextMenu.addItem({
command: CommandIDs.reference,
});
// Register a new command to create untitled file. This snippet is taken
// from docManager package https://github.com/jupyterlab/jupyterlab/blob/c106f0a19110efad7c5e1b136144985819e21100/packages/docmanager-extension/src/index.tsx#L679-L680
// We are "duplicating" it as base command adds extension to the options
// only if it is of type file. But we are interested in creating notebook type
// files with different Jupytext supported extensions. So we create a dedicated
// create new text notebook command here and make sure we pass extension in options
commands.addCommand(CommandIDs.newUntitled, {
execute: async (args) => {
const errorTitle = (args['error'] as string) || trans.__('Error');
const path =
typeof args['path'] === 'undefined' ? '' : (args['path'] as string);
const options: Partial = {
type: args['type'] as Contents.ContentType,
path,
};
// Ensure we pass extension to command always
options.ext = (args['ext'] as string) || '.txt';
return docManager.services.contents
.newUntitled(options)
.catch((error) => showErrorMessage(errorTitle, error));
},
label: (args) =>
(args['label'] as string) || `New ${args['type'] as string}`,
});
// We dont need to add this command to palettte as it is a utility one
// which does not have direct usage
// palette?.addItem({
// command: CommandIDs.newUntitled,
// rank: 50,
// category: 'Jupytext',
// });
// Get a map of available kernel languages in current widget
const availableKernelLanguages = await getAvailableKernelLanguages(
languages,
docRegistry,
serviceManager,
);
// Get a map of all create text notebook commands
const createTextNotebookCommands =
await getAvailableCreateTextNotebookCommands(
includeFormats,
availableKernelLanguages,
);
// Register Jupytext text notebooks file types
registerFileTypes(
availableKernelLanguages,
[
...[...JUPYTEXT_PAIR_COMMANDS_FILETYPE_DATA.values()].flat(),
...[...AUTO_LANGUAGE_FILETYPE_DATA.values()].flat(),
],
docRegistry,
trans,
);
// Get all kernel file types to add to Jupytext factory
const kernelLanguageNames: string[] = [];
for (const kernelLanguage of availableKernelLanguages.keys()) {
kernelLanguageNames.push(kernelLanguage);
}
// Create a factory for Jupytext
createFactory(
kernelLanguageNames,
toolbarRegistry,
settingRegistry,
docRegistry,
notebookTracker,
notebookFactory,
contentFactory,
editorServices,
rendermime,
translator,
trans,
riseFactory,
);
// Register all the commands that create text notebooks with different formats
// Nicked from notebook-extension package in JupyterLab
// https://github.com/jupyterlab/jupyterlab/blob/c106f0a19110efad7c5e1b136144985819e21100/packages/notebook-extension/src/index.ts#L1902-L1965
createTextNotebookCommands.forEach((fileTypes: IFileTypeData[], _) => {
fileTypes.map((fileType: IFileTypeData) => {
const format = fileType.fileExt;
const command = `jupytext:create-new-text-notebook-${format}`;
const iconName = fileType.iconName;
const kernelIcon = fileType.kernelIcon;
commands.addCommand(command, {
label: (args) => {
if (args['isLauncher']) {
return trans.__(fileType.launcherLabel);
}
return trans.__(fileType.paletteLabel);
},
caption: trans.__(fileType.caption),
icon: (args) => {
if (args.isPalette) {
return undefined;
} else {
if (iconName) {
return LabIcon.resolve({
icon: iconName as string,
});
}
if (kernelIcon) {
return kernelIcon;
}
return LabIcon.resolve({
icon: 'ui-components:kernel',
});
}
},
execute: (args) => {
const cwd =
(args['cwd'] as string) || (defaultBrowser?.model.path ?? '');
const kernelId = (args['kernelId'] as string) || '';
const kernelName = (args['kernelName'] as string) || '';
return createNewTextNotebook(
cwd,
kernelId,
kernelName,
format,
commands,
);
},
});
console.debug(
'Registering create new text notebook command=' +
command +
' with rank=' +
rank,
);
palette?.addItem({
command,
args: { isPalette: true },
rank: rank,
category: 'Jupytext',
});
if (includeFormats.includes(format)) {
jupytextCreateMenu.addItem({
command: command,
args: { isMainMenu: true },
});
// Add separator after each kernel type
if (fileType.separator) {
jupytextCreateMenu.addItem({
type: 'separator',
});
}
}
// Add a launcher item if the launcher is available.
if (launcher && includeFormats.includes(format)) {
void serviceManager.ready.then(() => {
let disposables: DisposableSet | null = null;
const onSpecsChanged = () => {
if (disposables) {
disposables.dispose();
disposables = null;
}
const specs = serviceManager.kernelspecs.specs;
if (!specs) {
return;
}
disposables = new DisposableSet();
const kernelIconUrl =
specs.kernelspecs[fileType.kernelName]?.resources['logo-svg'] ||
specs.kernelspecs[fileType.kernelName]?.resources['logo-64x64'];
disposables.add(
launcher.add({
command: command,
args: { isLauncher: true, kernelName: fileType.kernelName },
category: trans.__('Jupytext'),
rank: rank++,
kernelIconUrl,
metadata: {
kernel: JSONExt.deepCopy(
specs.kernelspecs[fileType.kernelName]?.metadata || {},
) as ReadonlyJSONValue,
},
}),
);
};
onSpecsChanged();
serviceManager.kernelspecs.specsChanged.connect(onSpecsChanged);
});
}
// Increment rank
rank += 1;
});
});
},
};
export default extension;
================================================
FILE: jupyterlab/packages/jupyterlab-jupytext-extension/src/registry.ts
================================================
import { DocumentRegistry } from '@jupyterlab/docregistry';
import { TranslationBundle } from '@jupyterlab/translation';
import { markdownIcon, LabIcon, fileIcon } from '@jupyterlab/ui-components';
import { IFileTypeData } from './tokens';
export function registerFileTypes(
availableKernelLanguages: Map,
jupytextFormatsWithFileTypeData: IFileTypeData[],
docRegistry: DocumentRegistry,
trans: TranslationBundle,
) {
const mystExtensions = ['myst', 'mystnb', 'mnb'];
const rmdExtensions = ['Rmd'];
const quartoExtensions = ['qmd'];
const extensionsWithFactory = [
...mystExtensions,
...rmdExtensions,
...quartoExtensions,
];
// Add a catch-all file type to overrride icon which by default is derive from model type.
// Because jupytext changes the type of all files it can handle to Notebook, we need to
// override it. We exclude file types which have dedicated icons (handled later).
const excludedExtensions = [
'ipynb',
...jupytextFormatsWithFileTypeData.map((f) => f.fileExt),
...extensionsWithFactory,
].join('|');
docRegistry.addFileType({
name: 'jupytext-notebook-file',
contentType: 'notebook',
pattern: `^(?!.*\\.(${excludedExtensions})$).*$`,
icon: fileIcon,
});
// Handle file types with dedicated icons
for (const format of jupytextFormatsWithFileTypeData) {
if (!extensionsWithFactory.includes(format.fileExt)) {
docRegistry.addFileType({
name: `${format.fileExt}-jupytext-notebook`,
contentType: 'notebook',
// `pattern` field gives it precedence over other file type information when resolving the icon
pattern: `\\.${format.fileExt}$`,
extensions: [`.${format.fileExt}`],
icon: format.iconName
? LabIcon.resolve({ icon: format.iconName })
: format.kernelIcon,
});
}
}
// Add kernel file types to registry
availableKernelLanguages.forEach(
(kernelFileTypes: IFileTypeData[], kernelLanguage: string) => {
kernelFileTypes.map((kernelFileType) => {
docRegistry.addFileType({
name: kernelLanguage,
contentType: 'notebook',
pattern: `\\.${kernelFileType.fileExt}$`,
displayName: trans.__(
kernelFileType.paletteLabel.split('New')[1].trim(),
),
extensions: [`.${kernelFileType.fileExt}`],
icon: kernelFileType.iconName
? LabIcon.resolve({ icon: kernelFileType.iconName })
: kernelFileType.kernelIcon,
});
});
},
);
const markdownNotebookIcon = markdownIcon.bindprops({
boxSizing: 'border-box',
border: '2px solid var(--jp-notebook-icon-color)',
borderRadius: '2px',
});
// Add markdown file types to registry - these will be open with Notebook by default
docRegistry.addFileType(
{
name: 'myst',
contentType: 'notebook',
displayName: trans.__('MyST Markdown Notebook'),
extensions: mystExtensions.map((ext) => '.' + ext),
icon: markdownNotebookIcon,
},
['Notebook'],
);
docRegistry.addFileType(
{
name: 'r-markdown',
contentType: 'notebook',
displayName: trans.__('R Markdown Notebook'),
// Extension file are transformed to lower case...
extensions: rmdExtensions.map((ext) => '.' + ext),
icon: markdownNotebookIcon,
},
['Notebook'],
);
docRegistry.addFileType(
{
name: 'quarto',
contentType: 'notebook',
displayName: trans.__('Quarto Notebook'),
extensions: quartoExtensions.map((ext) => '.' + ext),
pattern: '\\.qmd$',
icon: markdownNotebookIcon,
},
['Notebook'],
);
}
================================================
FILE: jupyterlab/packages/jupyterlab-jupytext-extension/src/svg.d.ts
================================================
declare module '*.svg' {
const image: string;
export default image;
}
================================================
FILE: jupyterlab/packages/jupyterlab-jupytext-extension/src/tokens.ts
================================================
import { ServerConnection } from '@jupyterlab/services';
import { LabIcon } from '@jupyterlab/ui-components';
import jupytextSvgstr from '../style/icons/logo.svg';
/**
* Jupytext extension ID
*/
export const JUPYTEXT_EXTENSION_ID = 'jupyterlab-jupytext:plugin';
/**
* Jupytext widget factory
*/
export const FACTORY = 'Jupytext Notebook';
/**
* Supported file formats.
*/
export const LANGUAGE_INDEPENDENT_NOTEBOOK_EXTENSIONS = [
'ipynb',
'md',
'Rmd',
'qmd',
];
/**
* Supported file types names.
*/
export const FILE_TYPES = [
'markdown',
'myst',
'r-markdown',
'quarto',
'julia',
'python',
'r',
];
/**
* The short namespace for commands, etc.
*/
export const NS = 'jupytext';
/**
* Command IDs of Jupytext
*/
export namespace CommandIDs {
export const metadata = `${NS}:metadata`;
export const reference = `${NS}:reference`;
export const faq = `${NS}:faq`;
export const newUntitled = `${NS}:new-untitled-text-notebook`;
}
/**
* Jupytext logo icon
*/
export const JupytextIcon = new LabIcon({
name: `${NS}:icon:logo`,
svgstr: jupytextSvgstr,
});
/**
* Current Jupyter server settings
*/
export const SERVER_SETTINGS = ServerConnection.makeSettings();
/**
* Supported Jupytext pairings along with metadata.
*/
export const JUPYTEXT_PAIR_COMMANDS_FILETYPE_DATA = new Map<
string,
IFileTypeData[]
>([
[
'ipynb',
[
{
fileExt: 'ipynb',
paletteLabel: 'Pair with ipynb',
caption: 'Pair Notebook with ipynb document',
iconName: 'ui-components:notebook',
separator: true,
},
],
],
[
'auto:percent',
[
{
fileExt: 'auto:percent',
paletteLabel: 'Pair with percent script',
caption: 'Pair Notebook with Percent Format',
iconName: 'ui-components:text-editor',
},
],
],
[
'auto:light',
[
{
fileExt: 'auto:light',
paletteLabel: 'Pair with light script',
caption: 'Pair Notebook with Light Format',
iconName: 'ui-components:text-editor',
},
],
],
[
'auto:nomarker',
[
{
fileExt: 'auto:nomarker',
paletteLabel: 'Pair with nomarker script',
caption: 'Pair Notebook with Nomarker Format',
iconName: 'ui-components:text-editor',
separator: true,
},
],
],
[
'py:marimo',
[
{
fileExt: 'py:marimo',
paletteLabel: 'Pair with Marimo Python script',
caption: 'Pair Notebook with Marimo Python script',
iconName: 'ui-components:text-editor',
separator: true,
},
],
],
[
'md',
[
{
fileExt: 'md',
paletteLabel: 'Pair with md',
caption: 'Pair Notebook with Markdown',
iconName: 'ui-components:markdown',
},
],
],
[
'md:myst',
[
{
fileExt: 'md:myst',
paletteLabel: 'Pair with myst md',
caption: 'Pair Notebook with MyST Markdown',
iconName: 'ui-components:markdown',
separator: true,
},
],
],
[
'Rmd',
[
{
fileExt: 'Rmd',
paletteLabel: 'Pair with Rmd',
caption: 'Pair Notebook with R Markdown',
iconName: 'ui-components:markdown',
},
],
],
[
'qmd',
[
{
fileExt: 'qmd',
paletteLabel: 'Pair with qmd',
caption: 'Pair Notebook with Quarto (qmd)',
iconName: 'ui-components:markdown',
separator: true,
},
],
],
[
'custom',
[
{
fileExt: 'custom',
paletteLabel: 'Custom pair',
caption: 'Custom Pairing',
iconName: 'ui-components:text-editor',
},
],
],
[
'none',
[
{
fileExt: 'none',
paletteLabel: 'Unpair',
caption: 'Unpair Current Notebook',
},
],
],
]);
/**
* Supported kernels file types metadata
*/
export const AUTO_LANGUAGE_FILETYPE_DATA = new Map([
[
'python',
[
{
fileExt: 'py',
paletteLabel: 'New Python Text Notebook',
caption: 'Create a new Python Text Notebook',
iconName: 'ui-components:python',
launcherLabel: 'Python',
kernelName: 'python3',
},
],
],
[
'julia',
[
{
fileExt: 'jl',
paletteLabel: 'New Julia Text Notebook',
caption: 'Create a new Julia Text Notebook',
iconName: 'ui-components:julia',
launcherLabel: 'Julia',
kernelName: 'julia',
},
],
],
[
'R',
[
{
fileExt: 'R',
paletteLabel: 'New R Text Notebook',
caption: 'Create a new R Text Notebook',
iconName: 'ui-components:r-kernel',
launcherLabel: 'R',
kernelName: 'ir',
},
],
],
]);
/**
* Supported Jupytext create new text notebooks file types
*/
export const JUPYTEXT_CREATE_TEXT_NOTEBOOK_FILETYPE_DATA = new Map<
string,
IFileTypeData[]
>([
[
'auto:percent',
[
{
fileExt: 'auto:percent',
paletteLabel: 'Percent Format',
caption: 'Percent Format',
launcherLabel: 'Percent Format',
},
],
],
[
'auto:light',
[
{
fileExt: 'auto:light',
paletteLabel: 'Light Format',
caption: 'Light Format',
launcherLabel: 'Light Format',
},
],
],
[
'auto:nomarker',
[
{
fileExt: 'auto:nomarker',
paletteLabel: 'Nomarker Format',
caption: 'Nomarker Format',
launcherLabel: 'Nomarker Format',
},
],
],
[
'py:marimo',
[
{
fileExt: 'py:marimo',
paletteLabel: 'Marimo Format',
caption: 'Marimo Format',
launcherLabel: 'Marimo Format',
},
],
],
[
'md',
[
{
fileExt: 'md',
paletteLabel: 'New Markdown Text Notebook',
caption: 'Create a new Markdown Text Notebook',
iconName: 'ui-components:markdown',
launcherLabel: 'Markdown',
},
],
],
[
'md:myst',
[
{
fileExt: 'md:myst',
paletteLabel: 'New MyST Markdown Text Notebook',
caption: 'Create a new MyST Markdown Text Notebook',
iconName: 'ui-components:markdown',
launcherLabel: 'MyST Markdown',
},
],
],
[
'Rmd',
[
{
fileExt: 'Rmd',
paletteLabel: 'New R Markdown Text Notebook',
caption: 'Create a new R Markdown Text Notebook',
iconName: 'ui-components:markdown',
launcherLabel: 'R Markdown',
},
],
],
[
'qmd',
[
{
fileExt: 'qmd',
paletteLabel: 'New Quarto Markdown Text Notebook',
caption: 'Create a new Quarto Markdown Text Notebook',
iconName: 'ui-components:markdown',
launcherLabel: 'Quarto Markdown',
},
],
],
]);
/**
* Supported Jupytext format extensions bar custom and none
*/
export const JUPYTEXT_FORMATS = Array.from(
JUPYTEXT_PAIR_COMMANDS_FILETYPE_DATA.keys(),
)
.map((format) => {
return format;
})
.filter((format) => {
return !['custom', 'none'].includes(format);
});
/**
* List of formats that would be added to launcher icons
*/
export const TEXT_NOTEBOOKS_LAUNCHER_ICONS = JUPYTEXT_FORMATS.filter(
(format) => {
return !['ipynb', 'auto:nomarker', 'qmd', 'custom', 'none'].includes(
format,
);
},
);
/**
* An interface for file type metadata
*/
export interface IFileTypeData {
fileExt: string;
paletteLabel: string;
caption: string;
iconName?: string;
kernelIcon?: LabIcon;
launcherLabel?: string;
kernelName?: string;
separator?: boolean;
}
/**
* An interface for Jupytext representation
*/
export interface IJupytextRepresentation {
formatName: string;
extension: string;
}
/**
* An interface for Jupytext metadata
*/
export interface IJupytextSection {
formats?: string;
notebook_metadata_filter?: string;
cell_metadata_filer?: string;
text_representation?: IJupytextRepresentation;
}
================================================
FILE: jupyterlab/packages/jupyterlab-jupytext-extension/src/utils.ts
================================================
import { IEditorLanguageRegistry } from '@jupyterlab/codemirror';
import {
ServiceManager,
ServerConnection,
KernelSpec,
} from '@jupyterlab/services';
import { LabIcon } from '@jupyterlab/ui-components';
import { URLExt } from '@jupyterlab/coreutils';
import { DocumentRegistry, IDocumentWidget } from '@jupyterlab/docregistry';
import { CommandRegistry } from '@lumino/commands';
import { Buffer } from 'buffer';
import {
NS,
SERVER_SETTINGS,
AUTO_LANGUAGE_FILETYPE_DATA,
JUPYTEXT_CREATE_TEXT_NOTEBOOK_FILETYPE_DATA,
FACTORY,
CommandIDs,
IFileTypeData,
} from './tokens';
/**
* Get kernel icon SVG string
*/
async function getKernelIconBase64String(
kernelIconUrl: string,
): Promise {
// Seems like URL prefix is already included in kernelIconUrl. We need to strip
// it off as baseUrl will already has url prefix.
kernelIconUrl = `/kernelspecs${kernelIconUrl.split('kernelspecs')[1]}`;
const url = URLExt.join(SERVER_SETTINGS.baseUrl, kernelIconUrl);
const response = await ServerConnection.makeRequest(url, {}, SERVER_SETTINGS);
const blob = await response.arrayBuffer();
const contentType = response.headers.get('content-type');
return `data:${contentType};base64,${Buffer.from(blob).toString('base64')}`;
}
/**
* Make a SVG string from base64 image
*/
function base64ToSvgStr(width: number, imageBase64: string): string {
return ``.replace(/\n/g, ' ');
}
/**
* Get kernel Icon
*/
async function getKernelIcon(
specModel: KernelSpec.ISpecModel,
fileType: DocumentRegistry.IFileType | undefined,
): Promise {
// First check for logo-svg
if (specModel.resources['logo-svg']) {
const svgStr = await getKernelIconBase64String(
specModel.resources['logo-svg'],
);
return new LabIcon({
name: `${NS}:icon:${specModel.name}`,
svgstr: svgStr,
});
}
// Else check if 64x64 kernel icon is available
if (specModel.resources['logo-64x64']) {
const iconBase64String = await getKernelIconBase64String(
specModel.resources['logo-64x64'],
);
return new LabIcon({
name: `${NS}:icon:${specModel.name}`,
svgstr: base64ToSvgStr(64, iconBase64String),
});
}
// Finally check for 32x32 kernel icon
if (specModel.resources['logo-32x32']) {
const iconBase64String = await getKernelIconBase64String(
specModel.resources['logo-32x32'],
);
return new LabIcon({
name: `${NS}:icon:${specModel.name}`,
svgstr: base64ToSvgStr(32, iconBase64String),
});
}
if (fileType && fileType.icon) {
return fileType.icon;
}
// If not found, make a generic kernel icon
return LabIcon.resolve({
icon: 'ui-components:kernel',
});
}
/**
* Get all available kernel languages so that we replace auto format
* with these language file extensions
*/
export async function getAvailableKernelLanguages(
languages: IEditorLanguageRegistry,
docRegistry: DocumentRegistry,
serviceManager: ServiceManager.IManager,
): Promise