d2-python
Build D2 diagrams with Python code.
D2 is a language for writing diagrams as text. d2-python lets you build that text from code, so you can draw diagrams from data, loops and functions. You describe shapes and connections as typed Python objects, and the library writes the D2 and renders it to SVG, PNG, PDF or ASCII.
This project is a maintained hard fork of MrBlenny/py-d2. See Coming from py-d2 if you used it.
Install
Add d2-python to your project with uv. The bin extra also installs the official D2 binary, so d2.render works without installing D2 yourself:
uv add 'd2-python[bin]'
Run a single script without a project:
uv run --with 'd2-python[bin]' diagram.py
Run the D2 command itself without installing anything, or install it once so d2 is on your PATH:
uvx d2-bin --version
uv tool install d2-bin
With pip, use pip install 'd2-python[bin]'. If you already have the d2 command on your PATH, you can leave out the bin extra.
Quick start
import d2
diagram = d2.Diagram(
shapes=[
d2.Shape(name="api", label="API"),
d2.Shape(name="db", label="Postgres", shape=d2.ShapeType.cylinder),
],
connections=[d2.Connection("api", "db", "reads and writes")],
)
print(diagram) # the D2 text
open("graph.svg", "wb").write(d2.render(diagram))
print(d2.render(diagram, "txt").decode()) # ASCII art
print(diagram) prints this D2 text:
api: API
db: Postgres {
shape: cylinder
}
api -> db: reads and writes
The ASCII render looks like this:
┌──────┐
│ API │
│ │
└──────┘
│
reads and writes
│
▼
.-‾‾‾‾-.
│╲-____-╱│
│ │
│Postgres│
│ │
╲-____-╱
Building diagrams
Every feature below has a runnable script in examples/.
Shapes take a type, an icon, a tooltip and a link:
db = d2.Shape(
name="db",
shape=d2.ShapeType.cylinder,
icon="https://icons.terrastruct.com/essentials%2F117-database.svg",
tooltip="Primary database",
link="https://example.com/runbook",
)
A shape can hold other shapes. Connect nested shapes with a dotted path:
backend = d2.Shape(
name="backend", shapes=[d2.Shape(name="api"), d2.Shape(name="worker")]
)
d2.Connection("backend.api", "backend.worker")
Styles set colors, borders, fonts and effects:
d2.Style(fill="#dbeafe", stroke="navy", stroke_width=2, bold=True, shadow=True)
Connections take a direction, a label, a style and arrowheads, including crow's foot ends for data models:
d2.Connection(
"orders",
"users",
"belongs to",
direction=d2.Direction.TO,
style=d2.Style(stroke="gray", animated=True),
source_arrowhead=d2.Arrowhead(d2.ArrowheadShape.CF_MANY),
target_arrowhead=d2.Arrowhead(d2.ArrowheadShape.CF_ONE_REQUIRED),
)
A label can be Markdown, LaTeX or code:
d2.Shape(name="notes", label=d2.Text("# Notes\nSome **bold** text", "md"))
Other features:
- SQL tables with constraints, and UML class shapes with fields and methods.
- Grids, with
grid_rows,grid_columnsand gaps. - Reusable style classes, set on the diagram and used by name.
- Layers, scenarios and steps, which are extra boards in one diagram.
- Diagram settings through
d2.Config, such as the layout engine, theme and sketch mode. - Variables with
${name}substitution, and legends. - Label and icon positions, and fixed
topandleftpositions. - Comments at the top of the file.
Rendering
d2.render(diagram, format) runs d2 and returns the result as bytes. It uses the binary from d2-bin when that is installed, and otherwise the d2 command on your PATH. You can also pass D2 text as a string.
| Option | Values |
|---|---|
format |
"svg" (default), "png", "pdf", "gif", "pptx" or "txt" for ASCII |
layout |
"dagre", "elk" or "tala" |
theme, dark_theme |
a D2 theme id |
target |
the board to render: the root by default, or a path such as "layers.detail" |
| Others | sketch, pad, scale, center, omit_version, no_xml_tag, salt, animate_interval, ascii_mode, timeout |
If d2 fails, render raises d2.D2Error with d2's message.
Checking and formatting D2
d2.validate runs d2 validate and raises d2.D2Error when the source is wrong. d2.fmt returns the source formatted by d2 fmt. Both take a Diagram or a string, so you can use them to check D2 text that a person or a language model wrote.
d2.validate("a -> b")
print(d2.fmt("a->b"))
validate checks the syntax and meaning of the source. It does not lay the diagram out, so a few errors only show when you render.
How values are checked
The library checks values when you create an object, so a mistake fails in your code instead of in d2.
- Names and labels that D2 cannot read as written are quoted for you, e.g. a name with a dot or a D2 keyword such as
shape. Connection endpoints are written as given, so they can be dotted paths. - A color can be a CSS color name, a 3 or 6 digit hex code with or without
#, a gradient or a${variable}. You can pass a string or ad2.Color. The library writes hex codes in lowercase with#and in quotes, because a bare#starts a comment in D2. Any other value raisesValueError. - Numbers that D2 limits are checked, e.g.
opacitymust be from 0 to 1.
Coming from py-d2
Import d2 instead of py_d2, and drop the D2 prefix from class names, e.g. d2.Shape instead of py_d2.D2Shape. The pull requests that were open on py-d2 are merged here, and most of its open issues are fixed. Issues and pull requests are welcome on this repository.
Development
You need uv.
git clone git@github.com:h0rv/d2-python.git && cd d2-python
uv sync --extra bin
uv run pytest
uv run ruff check
uv run ruff format
uv run ty check
To redraw the banner, run uv run python docs/banner.py.
License
d2-python is MIT licensed. The d2-bin package contains the unmodified D2 binary, which is licensed under the MPL-2.0 by Terrastruct.
Metadata
Release files for d2-python 2.0.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| d2_python-2.0.1.tar.gz | 16.5 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| d2_python-2.0.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 36.5 kB
Release files / d2_python-2.0.1.tar.gz
| Download URL | d2_python-2.0.1.tar.gz |
|---|---|
| Size | 16.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
5c20c963e67a1ec46fa04ba9d212866c009665a8fc0ece75d921a577b53a7128
|
|
BLAKE2b-256 checksum How to use checksums |
383a0c1dd61883473266ce1e218b9f3b33e7fd2f25820c7870a4de82dff4d1a9
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.13.5 {"installer":{"name":"uv","version":"0.13.5","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / d2_python-2.0.1-py3-none-any.whl
| Download URL | d2_python-2.0.1-py3-none-any.whl |
|---|---|
| Size | 20.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
5aa6989d26c3a6e61892381db2d470520730a74683489a922ed5aae04e228b00
|
|
BLAKE2b-256 checksum How to use checksums |
eeee0e806d168d8951b39788e892496fac627bd937e7cb7f7599880053719ae3
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.13.5 {"installer":{"name":"uv","version":"0.13.5","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Security for d2-python 2.0.1
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.