Gallery¤
Welcome to our gallery of examples!
Diagrams (cloud/system architecture)¤
Diagrams offers a nice way of building diagrams. It also bundles a number of images used to illustrate objects and concepts so you can build good-looking diagrams. By default, Diagrams tries to write the result on disk, so we prevent that by patching its render
method, and by ignoring the FileNotFoundError
that ensues. Then we use its internal dot
object and its pipe
method to store the diagram in a variable, as base64 encoded PNG data. Finally we output an HTML image with the base64 data. Using SVG is not possible here since Diagrams embeds actual, smaller PNG files in the result, files which are not automatically added to the final site.
```python exec="true" html="true"
from base64 import b64encode
from contextlib import suppress
from diagrams import Diagram
from diagrams.k8s.clusterconfig import HPA
from diagrams.k8s.compute import Deployment, Pod, ReplicaSet
from diagrams.k8s.network import Ingress, Service
with suppress(FileNotFoundError):
with Diagram("Exposed Pod with 3 Replicas", show=False) as diagram:
diagram.render = lambda: None
net = Ingress("domain.com") >> Service("svc")
net >> [Pod("pod1"), Pod("pod2"), Pod("pod3")] << ReplicaSet("rs") << Deployment("dp") << HPA("hpa")
png = b64encode(diagram.dot.pipe(format="png")).decode()
print(f'<img src="data:image/png;base64, {png}"/>')
```
Python dependency tree¤
pipdeptree is able to output a Mermaid diagram of your Python dependency tree. In this example we change the direction of the graph from top-down to left-right, and remove local version identifiers from our own package.
flowchart TD
classDef missing stroke-dasharray: 5
editables["editables\n0.5"]
markdown-exec["markdown-exec\n1.7.0.1.0.2"]
markdown["Markdown\n3.5.2"]
pymdown-extensions["pymdown-extensions\n10.7.1"]
pyyaml["PyYAML\n6.0.1"]
markdown-exec -- ">=9" --> pymdown-extensions
markdown-exec -- "any" --> editables
pymdown-extensions -- ">=3.5" --> markdown
pymdown-extensions -- "any" --> pyyaml
```bash exec="1" result="mermaid"
pipdeptree -p markdown-exec --mermaid 2>/dev/null |
sed -E 's/\.dev.+"\]$/"]/;s/\+d.*"\]$/"]/'
```
Another example with more dependencies:
flowchart LR
classDef missing stroke-dasharray: 5
click_0["click\n8.1.7"]
colorama["colorama\n0.4.6"]
ghp-import["ghp-import\n2.1.0"]
griffe["griffe\n0.42.0.1.2.0"]
jinja2["Jinja2\n3.1.3"]
markdown["Markdown\n3.5.2"]
markupsafe["MarkupSafe\n2.1.5"]
mergedeep["mergedeep\n1.3.4"]
mkdocs-autorefs["mkdocs-autorefs\n1.0.1"]
mkdocs["mkdocs\n1.5.3"]
mkdocstrings-python["mkdocstrings-python\n1.9.0.1.6.0"]
mkdocstrings["mkdocstrings\n0.24.1"]
packaging["packaging\n24.0"]
pathspec["pathspec\n0.12.1"]
platformdirs["platformdirs\n4.2.0"]
pymdown-extensions["pymdown-extensions\n10.7.1"]
python-dateutil["python-dateutil\n2.9.0.post0"]
pyyaml-env-tag["pyyaml_env_tag\n0.1"]
pyyaml["PyYAML\n6.0.1"]
six["six\n1.16.0"]
watchdog["watchdog\n4.0.0"]
ghp-import -- ">=2.8.1" --> python-dateutil
griffe -- ">=0.4" --> colorama
jinja2 -- ">=2.0" --> markupsafe
mkdocs -- ">=0.1" --> pyyaml-env-tag
mkdocs -- ">=0.11.1" --> pathspec
mkdocs -- ">=1.0" --> ghp-import
mkdocs -- ">=1.3.4" --> mergedeep
mkdocs -- ">=2.0" --> watchdog
mkdocs -- ">=2.0.1" --> markupsafe
mkdocs -- ">=2.11.1" --> jinja2
mkdocs -- ">=2.2.0" --> platformdirs
mkdocs -- ">=20.5" --> packaging
mkdocs -- ">=3.2.1" --> markdown
mkdocs -- ">=5.1" --> pyyaml
mkdocs -- ">=7.0" --> click_0
mkdocs-autorefs -- ">=1.1" --> mkdocs
mkdocs-autorefs -- ">=2.0.1" --> markupsafe
mkdocs-autorefs -- ">=3.3" --> markdown
mkdocstrings -- ">=0.3.1" --> mkdocs-autorefs
mkdocstrings -- ">=1.1" --> markupsafe
mkdocstrings -- ">=1.4" --> mkdocs
mkdocstrings -- ">=2.11.1" --> jinja2
mkdocstrings -- ">=2.2.0" --> platformdirs
mkdocstrings -- ">=3.3" --> markdown
mkdocstrings -- ">=6.3" --> pymdown-extensions
mkdocstrings -- ">=7.0" --> click_0
mkdocstrings-python -- ">=0.20" --> mkdocstrings
mkdocstrings-python -- ">=0.40" --> griffe
mkdocstrings-python -- ">=3.3,<3.6" --> markdown
pymdown-extensions -- ">=3.5" --> markdown
pymdown-extensions -- "any" --> pyyaml
python-dateutil -- ">=1.5" --> six
pyyaml-env-tag -- "any" --> pyyaml
```bash exec="1" result="mermaid"
pipdeptree -p mkdocstrings-python --mermaid 2>/dev/null |
sed 's/flowchart TD/flowchart LR/'
```
Python modules inter-dependencies¤
This example uses pydeps to build a graph of interdependencies of your project's modules. Data is built and stored in a pydeps data structure, then translated to dot
source, then rendered to SVG with Graphviz. In this example we also add links to the code reference in related nodes. Try clicking on the markdown_exec
nodes!
Note
pydeps wasn't designed to be used in such a programatic way, so the code is a bit convoluted, but you could make a function of it, put it in an importable script/module, and reuse it cleanly in your executed code blocks.
```python exec="true" html="true"
from pydeps import cli, colors, dot, py2depgraph
from pydeps.pydeps import depgraph_to_dotsrc
from pydeps.target import Target
cli.verbose = cli._not_verbose
options = cli.parse_args(["src/markdown_exec", "--noshow"])
colors.START_COLOR = options["start_color"]
target = Target(options["fname"])
with target.chdir_work():
dep_graph = py2depgraph.py2dep(target, **options)
dot_src = depgraph_to_dotsrc(target, dep_graph, **options)
svg = dot.call_graphviz_dot(dot_src, "svg").decode()
svg = "".join(svg.splitlines()[6:])
svg = svg.replace('fill="white"', 'fill="transparent"')
reference = "../reference"
modules = (
"markdown_exec",
"markdown_exec.formatters",
"markdown_exec.formatters.base",
"markdown_exec.formatters.bash",
"markdown_exec.formatters.console",
"markdown_exec.formatters.markdown",
"markdown_exec.formatters.pycon",
"markdown_exec.formatters.pyodide",
"markdown_exec.formatters.python",
"markdown_exec.formatters.sh",
"markdown_exec.formatters.tree",
"markdown_exec.logger",
"markdown_exec.mkdocs_plugin",
"markdown_exec.processors",
"markdown_exec.rendering",
)
for module in modules:
svg_title = module.replace(".", "_")
title_tag = f"<title>{svg_title}</title>"
href = f"{reference}/{module.replace('.', '/')}/"
svg = svg.replace(title_tag, f'<a href="{href}"><title>{module}</title>')
svg = svg.replace("</text></g>", "</text></a></g>")
print(svg)
```
Code snippets¤
Rich allows to export syntax-highlighted code as SVG. Here we hardcode the code snippet we want to render, but we could instead include it from somewhere else using the pymdownx.snippets
extension or by reading it dynamically from Python. We also prevent Rich from actually writing to the terminal.
```python exec="true" html="true"
import os
from rich.console import Console
from rich.padding import Padding
from rich.syntax import Syntax
code = """
from contextlib import asynccontextmanager
import httpx
class BookClient(httpx.AsyncClient):
async def get_book(self, book_id: int) -> str:
response = await self.get(f"/books/{book_id}")
return response.text
@asynccontextmanager
async def book_client(*args, **kwargs):
async with BookClient(*args, **kwargs) as client:
yield client
"""
with open(os.devnull, "w") as devnull:
console = Console(record=True, width=65, file=devnull, markup=False)
renderable = Syntax(code, "python", theme="material")
renderable = Padding(renderable, (0,), expand=False)
console.print(renderable, markup=False)
svg = console.export_svg(title="async context manager")
print(svg)
```
Terminal output with colors¤
If you installed Markdown Exec with the ansi
extra (pip install markdown-exec[ansi]
), the ANSI colors in the output of shell commands will be translated to HTML/CSS, allowing to render them naturally in your documentation pages. For this to happen, use the result="ansi"
option.
40m 41m 42m 43m 44m 45m 46m 47m
m xYz xYz xYz xYz xYz xYz xYz xYz xYz
1m xYz xYz xYz xYz xYz xYz xYz xYz xYz
30m xYz xYz xYz xYz xYz xYz xYz xYz xYz
1;30m xYz xYz xYz xYz xYz xYz xYz xYz xYz
31m xYz xYz xYz xYz xYz xYz xYz xYz xYz
1;31m xYz xYz xYz xYz xYz xYz xYz xYz xYz
32m xYz xYz xYz xYz xYz xYz xYz xYz xYz
1;32m xYz xYz xYz xYz xYz xYz xYz xYz xYz
33m xYz xYz xYz xYz xYz xYz xYz xYz xYz
1;33m xYz xYz xYz xYz xYz xYz xYz xYz xYz
34m xYz xYz xYz xYz xYz xYz xYz xYz xYz
1;34m xYz xYz xYz xYz xYz xYz xYz xYz xYz
35m xYz xYz xYz xYz xYz xYz xYz xYz xYz
1;35m xYz xYz xYz xYz xYz xYz xYz xYz xYz
36m xYz xYz xYz xYz xYz xYz xYz xYz xYz
1;36m xYz xYz xYz xYz xYz xYz xYz xYz xYz
37m xYz xYz xYz xYz xYz xYz xYz xYz xYz
1;37m xYz xYz xYz xYz xYz xYz xYz xYz xYz
```bash exec="true" result="ansi"
#!/bin/bash
# credits to https://github.com/42picky/42picky.github.io
text="xYz" # Some test text
echo -e "\n 40m 41m 42m 43m 44m 45m 46m 47m"
for FGs in ' m' ' 1m' ' 30m' '1;30m' ' 31m' '1;31m' ' 32m' \
'1;32m' ' 33m' '1;33m' ' 34m' '1;34m' ' 35m' '1;35m' \
' 36m' '1;36m' ' 37m' '1;37m'; do
FG=${FGs// /}
echo -en " $FGs \033[$FG ${text} "
for BG in 40m 41m 42m 43m 44m 45m 46m 47m; do
echo -en "$EINS \033[$FG\033[${BG} ${text} \033[0m"
done
echo
done
echo
```
As an alternative, we can use Rich again to render the output of a command in a terminal, with colors. This example is taken directly from the documentation of the Griffe project.
$ griffe check griffe -ssrc -b0.24.0 -a0.23.0
src/griffe/loader.py:156: GriffeLoader.resolve_aliases(only_exported): Parameter kind was changed: positional or keyword -> keyword-only
src/griffe/loader.py:156: GriffeLoader.resolve_aliases(only_exported): Parameter default was changed: True -> None
src/griffe/loader.py:156: GriffeLoader.resolve_aliases(only_known_modules): Parameter kind was changed: positional or keyword -> keyword-only
src/griffe/loader.py:156: GriffeLoader.resolve_aliases(only_known_modules): Parameter default was changed: True -> None
src/griffe/loader.py:156: GriffeLoader.resolve_aliases(max_iterations): Parameter kind was changed: positional or keyword -> keyword-only
src/griffe/loader.py:308: GriffeLoader.resolve_module_aliases(only_exported): Parameter was removed
src/griffe/loader.py:308: GriffeLoader.resolve_module_aliases(only_known_modules): Parameter was removed
src/griffe/git.py:39: tmp_worktree(commit): Parameter was removed
src/griffe/git.py:39: tmp_worktree(repo): Positional parameter was moved: position: from 2 to 1 (-1)
src/griffe/git.py:75: load_git(commit): Parameter was removed
src/griffe/git.py:75: load_git(repo): Parameter kind was changed: positional or keyword -> keyword-only
src/griffe/git.py:75: load_git(submodules): Parameter kind was changed: positional or keyword -> keyword-only
src/griffe/git.py:75: load_git(try_relative_path): Parameter was removed
src/griffe/git.py:75: load_git(extensions): Parameter kind was changed: positional or keyword -> keyword-only
src/griffe/git.py:75: load_git(search_paths): Parameter kind was changed: positional or keyword -> keyword-only
src/griffe/git.py:75: load_git(docstring_parser): Parameter kind was changed: positional or keyword -> keyword-only
src/griffe/git.py:75: load_git(docstring_options): Parameter kind was changed: positional or keyword -> keyword-only
src/griffe/git.py:75: load_git(lines_collection): Parameter kind was changed: positional or keyword -> keyword-only
src/griffe/git.py:75: load_git(modules_collection): Parameter kind was changed: positional or keyword -> keyword-only
src/griffe/git.py:75: load_git(allow_inspection): Parameter kind was changed: positional or keyword -> keyword-only
```python exec="true" html="true"
import os
from rich.console import Console
report = """$ griffe check griffe -ssrc -b0.24.0 -a0.23.0
[bold]src/griffe/loader.py[/]:156: GriffeLoader.resolve_aliases([#7faeff]only_exported[/]): [#afaf72]Parameter kind was changed[/]: positional or keyword -> keyword-only
[bold]src/griffe/loader.py[/]:156: GriffeLoader.resolve_aliases([#7faeff]only_exported[/]): [#afaf72]Parameter default was changed[/]: True -> None
[bold]src/griffe/loader.py[/]:156: GriffeLoader.resolve_aliases([#7faeff]only_known_modules[/]): [#afaf72]Parameter kind was changed[/]: positional or keyword -> keyword-only
[bold]src/griffe/loader.py[/]:156: GriffeLoader.resolve_aliases([#7faeff]only_known_modules[/]): [#afaf72]Parameter default was changed[/]: True -> None
[bold]src/griffe/loader.py[/]:156: GriffeLoader.resolve_aliases([#7faeff]max_iterations[/]): [#afaf72]Parameter kind was changed[/]: positional or keyword -> keyword-only
[bold]src/griffe/loader.py[/]:308: GriffeLoader.resolve_module_aliases([#7faeff]only_exported[/]): [#afaf72]Parameter was removed[/]
[bold]src/griffe/loader.py[/]:308: GriffeLoader.resolve_module_aliases([#7faeff]only_known_modules[/]): [#afaf72]Parameter was removed[/]
[bold]src/griffe/git.py[/]:39: tmp_worktree([#7faeff]commit[/]): [#afaf72]Parameter was removed[/]
[bold]src/griffe/git.py[/]:39: tmp_worktree([#7faeff]repo[/]): [#afaf72]Positional parameter was moved[/]: position: from 2 to 1 (-1)
[bold]src/griffe/git.py[/]:75: load_git([#7faeff]commit[/]): [#afaf72]Parameter was removed[/]
[bold]src/griffe/git.py[/]:75: load_git([#7faeff]repo[/]): [#afaf72]Parameter kind was changed[/]: positional or keyword -> keyword-only
[bold]src/griffe/git.py[/]:75: load_git([#7faeff]submodules[/]): [#afaf72]Parameter kind was changed[/]: positional or keyword -> keyword-only
[bold]src/griffe/git.py[/]:75: load_git([#7faeff]try_relative_path[/]): [#afaf72]Parameter was removed[/]
[bold]src/griffe/git.py[/]:75: load_git([#7faeff]extensions[/]): [#afaf72]Parameter kind was changed[/]: positional or keyword -> keyword-only
[bold]src/griffe/git.py[/]:75: load_git([#7faeff]search_paths[/]): [#afaf72]Parameter kind was changed[/]: positional or keyword -> keyword-only
[bold]src/griffe/git.py[/]:75: load_git([#7faeff]docstring_parser[/]): [#afaf72]Parameter kind was changed[/]: positional or keyword -> keyword-only
[bold]src/griffe/git.py[/]:75: load_git([#7faeff]docstring_options[/]): [#afaf72]Parameter kind was changed[/]: positional or keyword -> keyword-only
[bold]src/griffe/git.py[/]:75: load_git([#7faeff]lines_collection[/]): [#afaf72]Parameter kind was changed[/]: positional or keyword -> keyword-only
[bold]src/griffe/git.py[/]:75: load_git([#7faeff]modules_collection[/]): [#afaf72]Parameter kind was changed[/]: positional or keyword -> keyword-only
[bold]src/griffe/git.py[/]:75: load_git([#7faeff]allow_inspection[/]): [#afaf72]Parameter kind was changed[/]: positional or keyword -> keyword-only
"""
with open(os.devnull, "w") as devnull:
console = Console(record=True, width=150, file=devnull)
console.print(report, markup=True, highlight=False)
print(console.export_html(inline_styles=True, code_format="<pre><code>{code}</code></pre>"))
```
TUI screenshots¤
Textual allows to build Terminal User Interfaces (TUIs). In this example we generate the SVG image of a terminal interface.
```python exec="1" html="true"
from textual.app import App, ComposeResult
from textual.widgets import Static
from textual._doc import take_svg_screenshot
class TextApp(App):
CSS = """
Screen {
background: darkblue;
color: white;
layout: vertical;
}
Static {
height: auto;
padding: 2;
border: heavy white;
background: #ffffff 30%;
content-align: center middle;
}
"""
def compose(self) -> ComposeResult:
yield Static("Hello")
yield Static("[b]World![/b]")
print(take_svg_screenshot(app=TextApp(), terminal_size=(80, 24)))
```
Charts and Plots¤
With Matplotlib:
```python exec="1" html="1"
# https://matplotlib.org/stable/gallery/lines_bars_and_markers/scatter_demo2.html
from io import StringIO
import matplotlib.cbook as cbook
import matplotlib.pyplot as plt
import numpy as np
# Load a numpy record array from yahoo csv data with fields date, open, close,
# volume, adj_close from the mpl-data/example directory. The record array
# stores the date as an np.datetime64 with a day unit ('D') in the date column.
price_data = cbook.get_sample_data("goog.npz", np_load=True)["price_data"].view(np.recarray)
price_data = price_data[-250:] # get the most recent 250 trading days
delta1 = np.diff(price_data.adj_close) / price_data.adj_close[:-1]
# Marker size in units of points^2
volume = (15 * price_data.volume[:-2] / price_data.volume[0]) ** 2
close = 0.003 * price_data.close[:-2] / 0.003 * price_data.open[:-2]
fig, ax = plt.subplots()
ax.scatter(delta1[:-1], delta1[1:], c=close, s=volume, alpha=0.5)
ax.set_xlabel(r"$\Delta_i$", fontsize=15)
ax.set_ylabel(r"$\Delta_{i+1}$", fontsize=15)
ax.set_title("Volume and percent change")
ax.grid(True)
fig.tight_layout()
buffer = StringIO()
plt.savefig(buffer, format="svg")
print(buffer.getvalue())
```
Python module output¤
This example uses Python's runpy
module to run another Python module. This other module's output is captured by temporarily patching sys.stdout
with a text buffer.
Usage: mkdocs [OPTIONS] COMMAND [ARGS]...
MkDocs - Project documentation with Markdown.
Options:
-V, --version Show the version and exit.
-q, --quiet Silence warnings
-v, --verbose Enable verbose output
--color / --no-color Force enable or disable color and wrapping for the
output. Default is auto-detect.
-h, --help Show this message and exit.
Commands:
build Build the MkDocs documentation
get-deps Show required PyPI packages inferred from plugins in mkdocs.yml
gh-deploy Deploy your documentation to GitHub Pages
new Create a new MkDocs project
serve Run the builtin development server
```python exec="true"
import sys
import warnings
from contextlib import suppress
from io import StringIO
from runpy import run_module
old_argv = list(sys.argv)
sys.argv = ["mkdocs"]
old_stdout = sys.stdout
sys.stdout = StringIO()
warnings.filterwarnings("ignore", category=RuntimeWarning)
with suppress(SystemExit):
run_module("mkdocs", run_name="__main__")
output = sys.stdout.getvalue()
sys.stdout = old_stdout
sys.argv = old_argv
print(f"```\n{output}\n```")
```
Python CLI documentation¤
Argparse help message (code block)¤
Instead of blindly running a module with runpy
to get its help message, if you know the project is using argparse
to build its command line interface, and if it exposes its parser, then you can get the help message directly from the parser.
usage: duty [GLOBAL_OPTS...] [DUTY [DUTY_OPTS...] [DUTY_PARAMS...]...]
A simple task runner.
positional arguments:
remainder
Global options:
-d DUTIES_FILE, --duties-file DUTIES_FILE
Python file where the duties are defined.
-l, --list List the available duties.
-h [DUTY ...], --help [DUTY ...]
Show this help message and exit. Pass duties names to
print their help.
-V, --version show program's version number and exit
--debug-info Print debug information.
-c {stdout,stderr,both,none}, --capture {stdout,stderr,both,none}
Which output to capture. Colors are supported with
'both' only, unless the command has a 'force color'
option.
-f {pretty,tap}, --fmt {pretty,tap}, --format {pretty,tap}
Output format. Pass your own Jinja2 template as a
string with '-f custom=TEMPLATE'. Available variables:
command, title (command or title passed with -t), code
(exit status), success (boolean), failure (boolean),
number (command number passed with -n), output
(command output), nofail (boolean), quiet (boolean),
silent (boolean). Available filters: indent
(textwrap.indent).
-y, --pty Enable the use of a pseudo-terminal. PTY doesn't allow
programs to use standard input.
-Y, --no-pty Disable the use of a pseudo-terminal. PTY doesn't
allow programs to use standard input.
-p, --progress Print progress while running a command.
-P, --no-progress Don't print progress while running a command.
-q, --quiet Don't print the command output, even if it failed.
-Q, --no-quiet Print the command output when it fails.
-s, --silent Don't print anything.
-S, --no-silent Print output as usual.
-z, --zero, --nofail Don't fail. Always return a success (0) exit code.
-Z, --no-zero, --strict
Return the original exit code.
```python exec="true"
from duty.cli import get_parser
parser = get_parser()
print(f"```\n{parser.format_help()}\n```")
```
Argparse parser documentation¤
In this example, we inspect the argparse
parser to build better-looking Markdown/HTML contents. We simply use the description and iterate on options, but more complex stuff is possible of course.
duty¤
A simple task runner.
Options:
-d
,--duties-file
: Python file where the duties are defined.(default: duties.py)-l
,--list
: List the available duties.-h
,--help
DUTY
: Show this help message and exit. Pass duties names to print their help.-V
,--version
: show program's version number and exit--debug-info
: Print debug information.-c
,--capture
: Which output to capture. Colors are supported with 'both' only, unless the command has a 'force color' option.-f
,--fmt
,--format
: Output format. Pass your own Jinja2 template as a string with '-f custom=TEMPLATE'. Available variables: command, title (command or title passed with -t), code (exit status), success (boolean), failure (boolean), number (command number passed with -n), output (command output), nofail (boolean), quiet (boolean), silent (boolean). Available filters: indent (textwrap.indent).-y
,--pty
: Enable the use of a pseudo-terminal. PTY doesn't allow programs to use standard input.-Y
,--no-pty
: Disable the use of a pseudo-terminal. PTY doesn't allow programs to use standard input.-p
,--progress
: Print progress while running a command.-P
,--no-progress
: Don't print progress while running a command.-q
,--quiet
: Don't print the command output, even if it failed.-Q
,--no-quiet
: Print the command output when it fails.-s
,--silent
: Don't print anything.-S
,--no-silent
: Print output as usual.-z
,--zero
,--nofail
: Don't fail. Always return a success (0) exit code.-Z
,--no-zero
,--strict
: Return the original exit code.
```python exec="true" updatetoc="no"
import argparse
from duty.cli import get_parser
parser = get_parser()
lines = []
lines.append(f"## duty")
if parser.description:
lines.append(parser.description)
lines.append("\nOptions:\n")
for action in parser._actions:
opts = [f"`{opt}`" for opt in action.option_strings]
if not opts:
continue
line = "- " + ",".join(opts)
if action.metavar:
line += f" `{action.metavar}`"
line += f": {action.help}"
if action.default and action.default != argparse.SUPPRESS:
line += f"(default: {action.default})"
lines.append(line)
print("\n".join(lines))
```