Graphworks
A Python module for efficient graph theoretic programming
Quick Start
pip install graphworks
from graphworks import Graph, to_text
from graphworks.algorithms import find_path, is_connected
graph = Graph("roads").add_edge("A", "B", weight=2).add_edge("B", "C", label="toll")
print(graph.neighbors("B")) # ['A', 'C'] — undirected edges work both ways
print(find_path(graph, "C", "A")) # ['C', 'B', 'A']
print(is_connected(graph)) # True
print(to_text(graph))
Graphs can also be loaded from JSON (Graph(input_file=...) / Graph(input_graph=...)); a
neighbor may be a bare name or an object carrying edge data:
{
"label": "roads",
"directed": false,
"weighted": true,
"vertices": { "A": { "label": "Alpha" } },
"graph": { "A": ["B", { "target": "C", "weight": 2.5, "label": "toll" }], "B": [], "C": [] }
}
save_to_json writes this same format, so weights, labels, and attributes round-trip.
API conventions
Graphis the only class; algorithms (graphworks.algorithms) and exporters (graphworks.export) are free functions that take aGraph.- A vertex name that is not in the graph raises
KeyError(graph[v],neighbors, and every algorithm). Thedict.get-stylegraph.vertex(name)andgraph.edge(u, v)returnNone. - Predicates are vacuously true on an empty graph; metrics that are undefined there
(
min_degree,max_degree,diameter) raiseValueError. - Algorithms whose precondition fails raise
ValueErrorrather than returning a wrong answer (topological_sorton a cyclic graph,find_circuiton a non-Eulerian graph,diameteron a disconnected graph). - Degree counts every incident edge (in + out for directed graphs; a self-loop counts twice).
Optional extras:
pip install graphworks[matrix] # numpy adjacency matrix support
pip install graphworks[viz] # graphviz export
pip install graphworks[docs] # generate documentation
Development
Requirements
- Python 3.13+
- uv (>= 0.10.12)
Setup
uv sync --extra all
Running Tests
# Run all tests (includes coverage; fails under 90%)
uv run pytest
# Run a single test file
uv run pytest tests/test_graph.py
# Run a single test by name
uv run pytest tests/test_graph.py -k "test_method_name"
Linting and Formatting
# Lint
uv run ruff check --fix src/ tests/
# Format
uv run black src/ tests/
uv run isort src/ tests/
# Type checking
uv run ty check
# Code complexity
uv run xenon --max-average=A --max-modules=B --max-absolute=B src/
# Run all pre-commit hooks
prek run --all-files
Publishing
Version is managed automatically via git tags using hatchling-vcs.
- Tag a commit:
git tag -a X.Y.Z -m 'release message' - Push the tag:
git push --tags - The GitHub Actions workflow will build and publish to PyPI automatically.
Roadmap
Tier 1: Data model
-
Vertexclass with name, optional label, and immutable attribute mapping -
Edgedataclass with source, target, directed flag, optional weight, label, and attributes - Both classes are frozen (immutable) with identity-based equality and hashing
Tier 2: Graph refactor
- Internal storage uses
dict[str, Vertex]for vertex lookup - Adjacency structure uses
dict[str, dict[str, Edge]]for O(1) edge access - Edge weights, labels, and attributes survive union, complement (vertices), and JSON/DOT export
-
add_edgesupportsweightandlabelkeyword arguments -
edge()lookup method for direct O(1) edge retrieval -
remove_vertex()/remove_edge()(chainable; removing a vertex drops its incident edges) -
copy()(independent structure, shared immutableVertex/Edgeobjects) -
predecessors()for directed graphs (O(V); same asneighbors()when undirected)
Tier 3: Lossless conversions
-
to_adjacency_matrix()/from_adjacency_matrix()round-trips that preserve vertex names via an index mapping -
complementpreserves original vertex names and thedirectedflag -
to_edge_list()/from_edge_list()conversions - Weighted JSON format (
{"target": ..., "weight": ...}neighbor objects, seeg4.json)
Tier 4: Algorithms
- Unweighted shortest path via BFS (
find_pathreturns a path, not the shortest;diameteralready computes BFS distances internally) - Dijkstra's shortest path (weighted)
- Prim's / Kruskal's minimum spanning tree
- Strongly connected components (Tarjan or Kosaraju) and
is_strongly_connected
Tier 5: Export and CLI
- JSON export (
save_to_json, lossless round-trip) - Graphviz DOT export (
save_to_dot, with vertex/edge labels and weights) - Text tree and 2-D ASCII rendering (
to_text,to_ascii) - Rich-based demo script (
uv run demo) - CLI application for common graph operations
- Rich rendering integration for interactive graph display
Tier 6: Cross-cutting quality
- Iterative traversals:
find_path,find_all_paths, andtopological_sortare recursive and raiseRecursionErroron a ~3,000-vertex chain - Input validation hardening:
- Reject non-list neighbor values in JSON (
{"A": "B"}is silently read as the characters of"B") - Raise when more than one of
input_file/input_graph/input_matrixis given (extras are silently ignored)
- Reject non-list neighbor values in JSON (
- Read-only graph view (instead of locking for thread safety)
- Performance benchmarks
- Reverse adjacency index so
predecessors()is O(in-degree) instead of O(V); then switchis_connected,find_circuit, andfind_isolated_verticesto it (they currently rebuild in-edges by hand, which is faster than calling the O(V)predecessors()per vertex). Every add/remove path andcopy()must maintain the index. - Run docstring examples as tests (
--doctest-modules) so examples cannot go stale
Tier 7: Docs and releases
-
CHANGELOG.md(linked from the PyPI metadata but missing), starting with the 0.10.0 breaking changes - Hosted API documentation (Sphinx deps exist in the
[docs]extra, but there is nodocs/source and the Read the Docs site does not exist)
Metadata
Release files for graphworks 0.11.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 | |
|---|---|---|---|
| graphworks-0.11.1.tar.gz | 36.0 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| graphworks-0.11.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 80.7 kB
Release files / graphworks-0.11.1.tar.gz
| Download URL | graphworks-0.11.1.tar.gz |
|---|---|
| Size | 36.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
b33e3ef8834e4dfd5344aa163e07d61f395b76f8be6ed5964557f9ce5a54af92
|
|
BLAKE2b-256 checksum How to use checksums |
14a612ce0c194c08dedce3878ddd26401983dba360ef52fd862c22b96fdec046
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
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.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 16, 2026.
Transparency logRelease files / graphworks-0.11.1-py3-none-any.whl
| Download URL | graphworks-0.11.1-py3-none-any.whl |
|---|---|
| Size | 44.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
7744412bf9eb08099e2476617f27c74168dd970b49da622eaf8f51f596867e64
|
|
BLAKE2b-256 checksum How to use checksums |
5950f7d910df73c698c8e9faab6cba192b7a4e224cc5a4ff02fe77a8981c9d8c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
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.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 16, 2026.
Transparency log