├── .gitignore ├── DESCRIPTION.md ├── LICENSE ├── README.md ├── quarto ├── __init__.py ├── metadata.py ├── quarto.py ├── render.py └── theme.py └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 98 | __pypackages__/ 99 | 100 | # Celery stuff 101 | celerybeat-schedule 102 | celerybeat.pid 103 | 104 | # SageMath parsed files 105 | *.sage.py 106 | 107 | # Environments 108 | .env 109 | .venv 110 | env/ 111 | venv/ 112 | ENV/ 113 | env.bak/ 114 | venv.bak/ 115 | 116 | # Spyder project settings 117 | .spyderproject 118 | .spyproject 119 | 120 | # Rope project settings 121 | .ropeproject 122 | 123 | # mkdocs documentation 124 | /site 125 | 126 | # mypy 127 | .mypy_cache/ 128 | .dmypy.json 129 | dmypy.json 130 | 131 | # Pyre type checker 132 | .pyre/ 133 | 134 | # pytype static type analyzer 135 | .pytype/ 136 | 137 | # Cython debug symbols 138 | cython_debug/ 139 | -------------------------------------------------------------------------------- /DESCRIPTION.md: -------------------------------------------------------------------------------- 1 | # Quarto 2 | 3 | Python interface to Quarto, an academic, scientific, and technical publishing system built on [Pandoc](https://pandoc.org). 4 | 5 | In addition to the core capabilities of Pandoc, Quarto includes: 6 | 7 | 1. Support for embedding output from Julia, Python, and R via integration with [Jupyter](https://jupyter.org/) and [knitr](https://yihui.org/knitr/) . 8 | 2. A project system for rendering groups of documents at once. 9 | 3. Flexible ways to specify rendering options, including project-wide options and per-format options. 10 | 4. Cross references for figures, tables, equations, sections, listings, proofs, and more. 11 | 5. Sophisticated layout for panels of figures, tables, and other content. 12 | 6. Automatic installation of required LaTeX packages when rendering PDF output. 13 | 14 | The overall design of Quarto is influenced heavily by [R Markdown](https://rmarkdown.rstudio.com/), however unlike R Markdown the architecture is language agnostic. In it's current iteration, Quarto can render plain markdown, Rmd documents, and Jupyter notebooks. 15 | 16 | For additional documentation, see the [Quarto Wiki](https://github.com/quarto-dev/quarto-cli/wiki). 17 | 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020-2025 Posit Software, PBC 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # quarto-python 2 | 3 | Python interface to the Quarto CLI (https://quarto-dev/quarto-cli). 4 | 5 | 6 | 7 | ## theme helpers 8 | 9 | `quarto-python` contains theme helpers to do basic background/foreground theming of plots and tables 10 | 11 | The `theme_colors_*` functions take background and foreground colors: 12 | 13 | | parameter | description | 14 | | --------- | ----------- | 15 | | `bg` | background color | 16 | | `fg` | foreground color | 17 | 18 | The `theme_brand_*` functions take a [brand.yml](https://posit-dev.github.io/brand-yml/) file: 19 | 20 | | parameter | description | 21 | | --------- | ----------- | 22 | | `brand_yml` | path to **brand.yml** file | 23 | 24 | Available theme helpers: 25 | 26 | | colors | brand | 27 | | --------- | ----------- | 28 | | `theme_colors_altair` | `theme_brand_altair` | 29 | | `theme_colors_bokeh` | `theme_brand_bokeh` | 30 | | `theme_colors_great_tables` | `theme_brand_great_tables` | 31 | | `theme_colors_matplotlib` | `theme_brand_matplotlib` | 32 | | `theme_colors_plotnine` | `theme_brand_plotnine` | 33 | | `theme_colors_plotly` | `theme_brand_plotly` | 34 | | `theme_colors_pygal` | `theme_brand_pygal` | 35 | | `theme_colors_seaborn` | `theme_brand_seaborn` | 36 | 37 | The usage of the returned object or function depends on the package. See this repo for usage examples: 38 | 39 | https://github.com/quarto-dev/quarto-examples/tree/main/renderings -------------------------------------------------------------------------------- /quarto/__init__.py: -------------------------------------------------------------------------------- 1 | # __init__.py 2 | 3 | # Version of the quarto package 4 | __version__ = "0.1.0" 5 | 6 | from quarto.quarto import path 7 | from quarto.render import render 8 | from quarto.metadata import metadata 9 | from quarto.theme import theme_colors_altair, theme_brand_altair, \ 10 | theme_colors_bokeh, theme_brand_bokeh, \ 11 | theme_colors_great_tables, theme_brand_great_tables, \ 12 | theme_colors_matplotlib, theme_brand_matplotlib, \ 13 | theme_colors_plotnine, theme_brand_plotnine, \ 14 | theme_colors_plotly, theme_brand_plotly, \ 15 | theme_colors_pygal, theme_brand_pygal, \ 16 | theme_colors_seaborn, theme_brand_seaborn 17 | -------------------------------------------------------------------------------- /quarto/metadata.py: -------------------------------------------------------------------------------- 1 | 2 | import sys 3 | import json 4 | import subprocess 5 | 6 | from quarto.quarto import find_quarto 7 | 8 | def metadata(input): 9 | args = ["metadata", input, "--json"] 10 | metadata_json = subprocess.check_output([find_quarto()] + args) 11 | return json.loads(metadata_json) 12 | 13 | 14 | -------------------------------------------------------------------------------- /quarto/quarto.py: -------------------------------------------------------------------------------- 1 | 2 | import os 3 | import sys 4 | import shutil 5 | import subprocess 6 | 7 | def path(): 8 | path_env = os.getenv("QUARTO_PATH") 9 | if path_env is None: 10 | return shutil.which("quarto") 11 | else: 12 | return path_env 13 | 14 | 15 | def find_quarto(): 16 | quarto = path() 17 | if quarto is None: 18 | raise FileNotFoundError('Unable to find quarto command line tools.') 19 | return quarto 20 | -------------------------------------------------------------------------------- /quarto/render.py: -------------------------------------------------------------------------------- 1 | 2 | import os 3 | import sys 4 | import yaml 5 | import tempfile 6 | import subprocess 7 | 8 | from quarto.quarto import find_quarto 9 | 10 | def render(input, 11 | output_format = None, 12 | output_file = None, 13 | execute = True, 14 | execute_params = None, 15 | execute_dir = None, 16 | cache = None, 17 | cache_refresh = False, 18 | kernel_keepalive = None, 19 | kernel_restart = False, 20 | debug = False, 21 | quiet = False, 22 | pandoc_args = None): 23 | 24 | # params file to remove after render 25 | params_file = None 26 | 27 | # build args 28 | args = ["render", input] 29 | 30 | if output_format is not None: 31 | args.extend(["--to", output_format]) 32 | 33 | if output_file is not None: 34 | args.extend(["--output", output_file]) 35 | 36 | if execute is not None: 37 | if execute is True: 38 | args.append("--execute") 39 | elif execute is False: 40 | args.append("--no-execute") 41 | 42 | if execute_params is not None: 43 | params_file = tempfile.NamedTemporaryFile(mode = 'w', 44 | prefix="quarto-params", 45 | suffix=".yml", 46 | delete=False) 47 | yaml.dump(execute_params, params_file) 48 | params_file.close() 49 | args.extend(["--execute-params", params_file.name]) 50 | 51 | if execute_dir is not None: 52 | args.extend(["--execute-dir", execute_dir]) 53 | 54 | if cache is not None: 55 | if cache is True: 56 | args.append("--cache") 57 | elif cache is False: 58 | args.append("--no-cache") 59 | 60 | if cache_refresh is True: 61 | args.append("--cache-refresh") 62 | 63 | if kernel_keepalive is not None: 64 | args.extend(["--kernel-keepalive", str(kernel_keepalive)]) 65 | 66 | if kernel_restart is True: 67 | args.append("--kernel-restart") 68 | 69 | if debug is True: 70 | args.append("--debug") 71 | 72 | if quiet is True: 73 | args.append("--quiet") 74 | 75 | # run process 76 | try: 77 | process = subprocess.Popen([find_quarto()] + args) 78 | process.wait() 79 | finally: 80 | if params_file is not None: 81 | os.remove(params_file.name) 82 | 83 | -------------------------------------------------------------------------------- /quarto/theme.py: -------------------------------------------------------------------------------- 1 | from functools import partial 2 | 3 | 4 | def theme_colors_altair(bg, fg): 5 | return { 6 | "config": { 7 | "view": {"stroke": "transparent"}, 8 | "axis": { 9 | "domainColor": fg, 10 | "labelColor": fg, 11 | "titleColor": fg, 12 | }, 13 | "legend": { 14 | "labelColor": fg, 15 | "titleColor": fg, 16 | }, 17 | "background": bg, # Background color 18 | } 19 | } 20 | 21 | 22 | def theme_brand_altair(brand_yml): 23 | from brand_yml import Brand 24 | 25 | brand = Brand.from_yaml(brand_yml) 26 | bg = brand.color.background 27 | fg = brand.color.foreground 28 | return partial(theme_colors_altair, bg, fg) 29 | 30 | 31 | # background fill is incomplete 32 | def theme_colors_bokeh(bg, fg): 33 | from bokeh.io import curdoc 34 | from bokeh.themes import Theme 35 | 36 | curdoc().theme = Theme( 37 | json={ 38 | "attrs": { 39 | "figure": { 40 | "background_fill_color": bg, # just plot area 41 | }, 42 | "Title": { 43 | "background_fill_color": bg, 44 | "text_color": fg, 45 | }, 46 | "Axis": { 47 | "background_fill_color": bg, 48 | "axis_label_text_color": fg, 49 | "major_label_text_color": fg, 50 | }, 51 | } 52 | } 53 | ) 54 | 55 | 56 | def theme_brand_bokeh(brand_yml): 57 | from brand_yml import Brand 58 | 59 | brand = Brand.from_yaml(brand_yml) 60 | fg = brand.color.foreground 61 | bg = brand.color.background 62 | return partial(theme_colors_bokeh, bg, fg) 63 | 64 | 65 | def theme_colors_great_tables(bg, fg): 66 | return {"table_background_color": bg, "table_font_color": fg} 67 | 68 | 69 | def theme_brand_great_tables(brand_yml): 70 | from brand_yml import Brand 71 | 72 | brand = Brand.from_yaml(brand_yml) 73 | fg = brand.color.foreground 74 | bg = brand.color.background 75 | return theme_colors_great_tables(bg, fg) 76 | 77 | 78 | def theme_colors_matplotlib(bg, fg, primary): 79 | import matplotlib as mpl 80 | from cycler import cycler 81 | 82 | mpl.rcParams["axes.facecolor"] = bg 83 | mpl.rcParams["axes.edgecolor"] = fg 84 | mpl.rcParams["axes.labelcolor"] = fg 85 | mpl.rcParams["axes.titlecolor"] = fg 86 | mpl.rcParams["figure.facecolor"] = bg 87 | mpl.rcParams["figure.edgecolor"] = fg 88 | mpl.rcParams["text.color"] = fg 89 | mpl.rcParams["xtick.color"] = fg 90 | mpl.rcParams["ytick.color"] = fg 91 | if primary: 92 | mpl.rcParams["axes.prop_cycle"] = cycler("color", [primary]) 93 | 94 | 95 | def theme_brand_matplotlib(brand_yml): 96 | from brand_yml import Brand 97 | 98 | brand = Brand.from_yaml(brand_yml) 99 | return partial( 100 | theme_colors_matplotlib, 101 | brand.color.background, 102 | brand.color.foreground, 103 | brand.color.primary, 104 | ) 105 | 106 | 107 | def theme_colors_plotnine(bg, fg): 108 | from plotnine import theme, element_rect, element_text 109 | 110 | return theme( 111 | plot_background=element_rect(fill=bg, size=0), text=element_text(color=fg) 112 | ) 113 | 114 | 115 | def theme_brand_plotnine(brand_yml): 116 | from brand_yml import Brand 117 | 118 | brand = Brand.from_yaml(brand_yml) 119 | return theme_colors_plotnine(brand.color.background, brand.color.foreground) 120 | 121 | 122 | def theme_colors_plotly(bg, fg): 123 | import plotly.graph_objects as go 124 | 125 | return go.layout.Template( 126 | { 127 | "layout": { 128 | "paper_bgcolor": bg, 129 | "plot_bgcolor": bg, 130 | "font_color": fg, 131 | } 132 | } 133 | ) 134 | 135 | 136 | def theme_brand_plotly(brand_yml): 137 | from brand_yml import Brand 138 | 139 | brand = Brand.from_yaml(brand_yml) 140 | return theme_colors_plotly(brand.color.background, brand.color.foreground) 141 | 142 | 143 | def theme_colors_pygal(_bg, fg, primary, secondary): 144 | from pygal.style import Style 145 | 146 | return Style( 147 | background="transparent", 148 | plot_background="transparent", 149 | foreground=fg, 150 | foreground_strong=primary, 151 | foreground_subtle=secondary or "#630C0D", 152 | opacity=".6", 153 | opacity_hover=".9", 154 | transition="400ms ease-in", 155 | colors=("#E853A0", "#E8537A", "#E95355", "#E87653", "#E89B53"), 156 | ) 157 | 158 | 159 | def theme_brand_pygal(brand_yml): 160 | from brand_yml import Brand 161 | 162 | brand = Brand.from_yaml(brand_yml) 163 | return theme_colors_pygal( 164 | brand.color.background, 165 | brand.color.foreground, 166 | brand.color.primary, 167 | brand.color.secondary, 168 | ) 169 | 170 | 171 | def theme_colors_seaborn(bg, fg): 172 | # seaborn accepts matplotlib rcparams 173 | return { 174 | "axes.facecolor": bg, 175 | "axes.edgecolor": fg, 176 | "axes.labelcolor": fg, 177 | "axes.titlecolor": fg, 178 | "figure.facecolor": bg, 179 | "figure.edgecolor": fg, 180 | "text.color": fg, 181 | "xtick.color": fg, 182 | "ytick.color": fg, 183 | "grid.color": fg, 184 | } 185 | 186 | 187 | def theme_brand_seaborn(brand_yml): 188 | from brand_yml import Brand 189 | 190 | brand = Brand.from_yaml(brand_yml) 191 | return theme_colors_seaborn(brand.color.background, brand.color.foreground) 192 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import setuptools 2 | 3 | with open("DESCRIPTION.md", "r", encoding="utf-8") as fh: 4 | long_description = fh.read() 5 | 6 | setuptools.setup( 7 | name="quarto", 8 | version="0.2.0", 9 | author="JJ Allaire", 10 | author_email="jj@rstudio.com", 11 | description="Python Interface to 'Quarto' Markdown Publishing System", 12 | long_description=long_description, 13 | long_description_content_type="text/markdown", 14 | url="https://github.com/quarto-dev/quarto-python", 15 | packages=setuptools.find_packages(), 16 | install_requires=["jupyter_core", "nbformat", "nbclient", "ipykernel", "pyyaml"], 17 | classifiers=[ 18 | "Programming Language :: Python :: 3", 19 | "License :: OSI Approved :: MIT License", 20 | "Operating System :: OS Independent", 21 | ], 22 | python_requires='>=3.6', 23 | ) --------------------------------------------------------------------------------