Diagram as Code for prototyping cloud system architectures.
```pythonexec="true" html="true"
frombase64importb64encodefromcontextlibimportsuppressfromdiagramsimportDiagramfromdiagrams.k8s.clusterconfigimportHPAfromdiagrams.k8s.computeimportDeployment,Pod,ReplicaSetfromdiagrams.k8s.networkimportIngress,Service# 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.withsuppress(FileNotFoundError):withDiagram("Exposed Pod with 3 Replicas",show=False)asdiagram:diagram.render=lambda:Nonenet=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()# Wrapping the image in a div prevents it from being wrapped in a paragraph,# which would add unnecessary space around it.print(f'<div><img src="data:image/png;base64, {png}"/></div>')```
A modern diagram scripting language that turns text to diagrams.
```pythonexec="true" html="true"
importsubprocessdiagram="""direction: rightBefore and after becoming friends: { 2007: Office chatter in 2007 { shape: sequence_diagram alice: Alice bob: Bobby awkward small talk: { alice -> bob: uhm, hi bob -> alice: oh, hello icebreaker attempt: { alice -> bob: what did you have for lunch? } unfortunate outcome: { bob -> alice: that's personal } } } 2012: Office chatter in 2012 { shape: sequence_diagram alice: Alice bob: Bobby alice -> bob: Want to play with ChatGPT? bob -> alice: Yes! bob -> alice.play: Write a play... alice.play -> bob.play: about 2 friends... bob.play -> alice.play: who find love... alice.play -> bob.play: in a sequence diagram } 2007 -> 2012: Five\nyears\nlater}"""# We simply run `d2` in a subprocess, passing it our diagram as input and capturing its output to print it.svg=subprocess.check_output(["d2","-","-"],input=diagram,stderr=subprocess.DEVNULL,text=True)print(svg)```
```pythonexec="1" html="1"
# https://matplotlib.org/stable/gallery/lines_bars_and_markers/scatter_demo2.htmlfromioimportStringIOimportmatplotlib.cbookascbookimportmatplotlib.pyplotaspltimportnumpyasnp# 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")["price_data"]price_data=price_data[-250:]# get the most recent 250 trading daysdelta1=np.diff(price_data["adj_close"])/price_data["adj_close"][:-1]# Marker size in units of points^2volume=(15*price_data["volume"][:-2]/price_data["volume"][0])**2close=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())```
```bashexec="1" result="mermaid"
# Change the direction of the graph from top-down to left-right,# and remove local version identifiers from our own package.
pipdeptree-pmarkdown-exec--mermaid2>/dev/null|sed-E's/\.dev.+"\]$/"]/;s/\+d.*"\]$/"]/'```
pydeps uses Graphviz under the hood to generate graphs. In this example we add links to the code reference in related nodes. Try clicking on the markdown_exec nodes!
```pythonexec="true" html="true"
frompydepsimportcli,colors,dot,py2depgraphfrompydeps.pydepsimportdepgraph_to_dotsrcfrompydeps.targetimportTarget# 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.cli.verbose=cli._not_verboseoptions=cli.parse_args(["src/markdown_exec","--noshow"])colors.START_COLOR=options["start_color"]target=Target(options["fname"])withtarget.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",)formoduleinmodules: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)```
```pythonexec="true" html="true"
importosfromrich.consoleimportConsolefromrich.paddingimportPaddingfromrich.syntaximportSyntax# Here we hardcode the code snippet we want to render,# but we could instead include it from somewhere else using the `pymdownx.snippets` extension# (https://facelessuser.github.io/pymdown-extensions/extensions/snippets/)# or by reading it dynamically from Python.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"""# We prevent Rich from actually writing to the terminal.withopen(os.devnull,"w")asdevnull: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")# Wrapping the SVG in a div prevents it from being wrapped in a paragraph,# which would add unnecessary space around it.print(f"<div>{svg}</div>")```
```pythonexec="true" html="true"
fromioimportStringIOimportpytermguiasptgcode=""" 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"""terminal=ptg.Terminal(stream=StringIO(),size=(80,16))ptg.set_global_terminal(terminal)withterminal.record()asrecorder:recorder.write(ptg.tim.parse(ptg.highlight_python(code)))svg=recorder.export_svg(inline_styles=True)# Wrapping the SVG in a div prevents it from being wrapped in a paragraph,# which would add unnecessary space around it.print(f"<div>{svg}</div>")```
Tip
There's a PyTermGUI-dedicated MkDocs plugin that allows to generate SVGs on-the-fly: Termage. It is implemented using regular expressions in the on_markdown event of MkDocs, so is probably less robust than our actual SuperFence implementation here, but also allows for less verbose source to generate the SVG snippets.
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.
```pythonexec="true" html="true"
fromtempfileimportNamedTemporaryFilefromchalkimportDiagram,triangle,unit_xfromcolourimportColorpapaya=Color("#ff9700")defsierpinski(n:int,size:int)->Diagram:ifn<=1:returntriangle(size)else:smaller=sierpinski(n-1,size/2)returnsmaller.above(smaller.beside(smaller,unit_x).center_xy())d=sierpinski(5,4).fill_color(papaya)# Chalk doesn't provide an easy method to get a string directly,# so we use a temporary file.withNamedTemporaryFile("w+")astmpfile:d.render_svg(tmpfile.name,height=256)tmpfile.seek(0)svg=tmpfile.read()print(svg)```
Programmatically generate SVG (vector) images, animations, and interactive Jupyter widgets.
```pythonexec="true" html="true"
importdrawsvgasdrawd=draw.Drawing(200,200,origin='center')# Animate the position and color of circlec=draw.Circle(0,0,20,fill='red')# See for supported attributes:# https://developer.mozilla.org/en-US/docs/Web/SVG/Element/animatec.append_anim(draw.Animate('cy','6s','-80;80;-80',repeatCount='indefinite'))c.append_anim(draw.Animate('cx','6s','0;80;0;-80;0',repeatCount='indefinite'))c.append_anim(draw.Animate('fill','6s','red;green;blue;yellow',calc_mode='discrete',repeatCount='indefinite'))d.append(c)# Animate a black circle around an ellipseellipse=draw.Path()ellipse.M(-90,0)ellipse.A(90,40,360,True,True,90,0)# Ellipse pathellipse.A(90,40,360,True,True,-90,0)ellipse.Z()c2=draw.Circle(0,0,10)# See for supported attributes:# https://developer.mozilla.org/en-US/docs/Web/SVG/Element/animate_motionc2.append_anim(draw.AnimateMotion(ellipse,'3s',repeatCount='indefinite'))# See for supported attributes:# https://developer.mozilla.org/en-US/docs/Web/SVG/Element/animate_transformc2.append_anim(draw.AnimateTransform('scale','3s','1,2;2,1;1,2;2,1;1,2',repeatCount='indefinite'))d.append(c2)print(d.as_svg())```
This example displays a file-tree of the current project, in which you can descend thanks to Material for MkDocs' code annotations. It uses a recursive Python function which accept a code block session name as parameter 🤯:
If you know a 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.
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.
-d,--duties-file: Python file where the duties are defined.(default: duties.py)
-l,--list: List the available duties.
-h,--helpDUTY: 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.
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.