Metadata-Version: 2.4
Name: d2-python
Version: 2.0.0
Summary: Build D2 diagrams with Python code.
Keywords: d2-python,d2,py-d2,d2-lang,d2-python,declarative-diagrams,diagram-language,terrastruct
Author: David Revay, Robby
Author-email: David Revay <daverevay@gmail.com>, Robby <h0rv@users.noreply.github.com>
License-Expression: MIT
License-File: LICENSE
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Programming Language :: Python :: 3.15
Classifier: Programming Language :: Python :: Free Threading :: 3 - Stable
Classifier: Typing :: Typed
Requires-Dist: typing-extensions>=4.5 ; python_full_version < '3.12'
Requires-Dist: d2-bin==0.9.* ; extra == 'bin'
Requires-Python: >=3.11
Project-URL: Repository, https://github.com/h0rv/d2-python
Project-URL: Documentation, https://github.com/h0rv/d2-python/blob/main/README.md
Project-URL: Bug Tracker, https://github.com/h0rv/d2-python/issues
Provides-Extra: bin
Description-Content-Type: text/markdown

# d2-python

![d2-python banner](docs/banner.svg)

Build [D2](https://d2lang.com) 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](https://github.com/MrBlenny/py-d2). See [Coming from py-d2](#coming-from-py-d2) if you used it.

## Install

Add d2-python to your project with [uv](https://docs.astral.sh/uv/). The `bin` extra also installs the official D2 binary, so `d2.render` works without installing D2 yourself:

```bash
uv add 'd2-python[bin]'
```

Run a single script without a project:

```bash
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:

```bash
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

```python
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:

```d2
api: API
db: Postgres {
  shape: cylinder
}
api -> db: reads and writes
```

The ASCII render looks like this:

```text
    ┌──────┐
    │ API  │
    │      │
    └──────┘
        │
reads and writes
        │
        ▼
    .-‾‾‾‾-.
   │╲-____-╱│
   │        │
   │Postgres│
   │        │
    ╲-____-╱
```

## Building diagrams

Every feature below has a runnable script in [examples/](examples/README.md).

Shapes take a type, an icon, a tooltip and a link:

```python
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:

```python
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:

```python
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:

```python
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:

```python
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_columns` and 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 `top` and `left` positions.
- 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.

```python
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 a `d2.Color`. The library writes hex codes in lowercase with `#` and in quotes, because a bare `#` starts a comment in D2. Any other value raises `ValueError`.
- Numbers that D2 limits are checked, e.g. `opacity` must 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](https://docs.astral.sh/uv/).

```bash
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.
