pydot
pydot is a pure-Python object model and parser for Graphviz's DOT graph language. It can build directed or undirected graphs from nodes, edges, attributes, subgraphs, and clusters; parse one or more graphs from DOT text or files; edit the result; serialize raw DOT; and ask separately installed Graphviz programs to lay out and render PNG, SVG, PDF, or other formats. It also serves as the DOT conversion bridge used by NetworkX.
pydot is a good Python layer for DOT parsing, generation, and NetworkX interchange, especially when a Graphviz subprocess is acceptable. Do not mistake the easy pip install for a self-contained renderer or use it where graph algorithms, interactive UI, strict attribute validation, or predictable low latency are the real requirement.
Use it if
- You need to generate Graphviz diagrams from Python data without manually quoting and assembling DOT text
- You need to read, inspect, or rewrite existing DOT files using Python objects
- You want static Graphviz layouts such as dot, neato, fdp, sfdp, circo, or twopi from a Python workflow
- You already use NetworkX for graph algorithms and need DOT import, export, or Graphviz rendering
- You need shortest paths, centrality, connected components, graph matching, or other algorithms: pydot stores and serializes graph structure but does not provide an algorithm library
- You cannot install operating-system packages: pip installs pydot and pyparsing, but PNG, SVG, PDF, and layout output still require a separate Graphviz executable on PATH
- You need interactive browser visualization with pan, filtering, live updates, and click behavior: pydot and Graphviz produce static files or bytes, not a front-end visualization runtime
- You need a compact, strongly typed domain model: pydot mirrors DOT's string attributes and permissive syntax, so misspelled attribute names often reach Graphviz instead of failing in Python
- You process very large or latency-sensitive graphs: Python object construction, pyparsing, temporary DOT serialization, a Graphviz subprocess, and Graphviz layout cost all sit on the request path
Setup reality
pip install pydot installs pyparsing and no compiled Python extension. Rendering is a separate installation: install Graphviz with the operating system package manager and confirm dot -V works in the same environment, container, service account, and PATH that runs Python. A notebook seeing dot does not prove a systemd service or minimal container will. Raw serialization with to_string or write_raw works without Graphviz; create_png, create_svg, write_png, write_pdf, create_dot, and write_dot invoke a Graphviz program and can fail because the executable is missing, the selected layout program is unavailable, DOT is invalid, fonts are absent, or an output directory is unwritable. Containers need both the graphviz package and any fonts required for consistent labels. pydot 4.0.1 metadata allows Python 3.8+, but the README only promises support for Python 3.9 and newer, so Python 3.8 users are outside the maintainer's guaranteed support even if installation succeeds. DOT parsing returns a list because one input can contain multiple graphs; indexing [0] without checking silently assumes exactly one. Names and attributes are DOT strings, not validated Python enums. Quoting, HTML-like labels, ports, record shapes, strict graphs, and reserved words follow DOT rules, and Graphviz may normalize the serialized representation. The layout engines have different strengths and options: dot is hierarchical, while neato and sfdp are force-directed. A graph that looks good with one can be unreadable with another. Rendering runs an external process and may create temporary files, so add timeouts or job isolation around user-triggered or very large diagrams rather than blocking a web request indefinitely. DEBUG logging can include complete graph contents and sensitive labels, which the README warns about explicitly. Use pydot for graph interchange and rendering, NetworkX for algorithms, and keep Graphviz versions pinned when reproducible layout matters.
Patterns
Build a directed graphcreate-directed-graph
import pydot
graph = pydot.Dot('pipeline', graph_type='digraph', rankdir='LR')
graph.add_node(pydot.Node('fetch', label='Fetch data', shape='box'))
graph.add_node(pydot.Node('store', label='Store result', shape='cylinder'))
graph.add_edge(pydot.Edge('fetch', 'store', label='records'))Attributes are DOT strings passed through to Graphviz; pydot does not validate most names or allowed values.
Build an undirected graphcreate-undirected-graph
graph = pydot.Dot('network', graph_type='graph')
graph.add_edge(pydot.Edge('alice', 'bob'))
graph.add_edge(pydot.Edge('bob', 'carol'))Use graph for undirected edges and digraph for directed edges; changing only visual arrow attributes does not change graph semantics.
Apply default node and edge stylesset-default-attributes
graph.set_node_defaults(
shape='box',
style='rounded,filled',
fillcolor='#f3f4f6',
fontname='Inter',
)
graph.set_edge_defaults(color='#6b7280', arrowsize='0.8')Graphviz must be able to find the named font on the rendering host or it will substitute another font and change layout.
Group nodes in a Graphviz clustergroup-cluster
backend = pydot.Cluster('backend', label='Backend', color='lightgrey')
backend.add_node(pydot.Node('api'))
backend.add_node(pydot.Node('database'))
graph.add_subgraph(backend)
graph.add_edge(pydot.Edge('api', 'database'))Cluster names conventionally start with cluster. Clusters influence Graphviz layout but are not a general nested-graph algorithm structure.
Parse graphs from a DOT stringparse-dot-string
dot = 'digraph G { api -> worker; worker -> database; }'
graphs = pydot.graph_from_dot_data(dot)
if len(graphs) != 1:
raise ValueError(f'expected one graph, got {len(graphs)}')
graph = graphs[0]The parser returns a list because DOT input may contain multiple top-level graphs.
Load a DOT fileparse-dot-file
graphs = pydot.graph_from_dot_file('architecture.dot', encoding='utf-8')
if not graphs:
raise ValueError('no graph found')
graph = graphs[0]Parsing uses pyparsing and does not require Graphviz; rendering the result still does.
Find and edit a named nodeedit-existing-node
matches = graph.get_node('database')
if not matches:
raise KeyError('database')
node = matches[0]
node.set_shape('cylinder')
node.set_fillcolor('#dbeafe')
node.set_style('filled')get_node returns a list because DOT can contain repeated declarations of the same node name.
Inspect edge endpoints and attributesinspect-edges
for edge in graph.get_edges():
source = edge.get_source()
destination = edge.get_destination()
label = edge.get_label()
print(source, destination, label)Values may retain DOT quoting. Do not assume every getter returns an unquoted application identifier.
Serialize DOT without running Graphvizwrite-raw-dot
dot_text = graph.to_string()
graph.write_raw('pipeline.dot')Raw serialization is fast and needs no Graphviz executable, but it does not compute layout positions.
Render a PNG with the dot layout enginerender-png
graph.write_png('pipeline.png', prog='dot')This starts the external Graphviz dot program. Install Graphviz separately and ensure dot is on the service process PATH.
Render SVG into memorycreate-svg-bytes
svg_bytes = graph.create_svg(prog='dot')
with open('pipeline.svg', 'wb') as output:
output.write(svg_bytes)create_svg returns bytes and still invokes Graphviz; it is not the same as pydot's raw DOT serialization.
Convert between NetworkX and pydotconvert-networkx
import networkx as nx
nx_graph = nx.DiGraph()
nx_graph.add_edge('api', 'worker', weight=3)
pydot_graph = nx.drawing.nx_pydot.to_pydot(nx_graph)
round_trip = nx.drawing.nx_pydot.from_pydot(pydot_graph)DOT attributes are commonly strings, so numeric or application-specific types may need conversion after a round trip.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| graphviz | PyPI | You only need a small official-style Python builder and renderer for DOT and do not need pydot's parser or NetworkX bridge |
| pygraphviz | PyPI | You want tighter access to the Graphviz C library and accept a more difficult native build and system-library dependency |
| networkx | PyPI | Your primary need is graph algorithms and analysis, with pydot or pygraphviz used only for interchange and layout |