Skip to main content

Alluvial diagrams

alluvial is a Python package for drawing alluvial diagrams with matplotlib: diagrams of how the modules (communities) of a network change, split and merge across time steps, layers or methods. Each diagram is made of ordinary matplotlib artists on your own axes, ready for a paper figure as vector PDF or SVG.

Alluvial diagram of synthetic citation networks, 2001 to 2007, in which neurology, psychology and part of molecular & cell biology merge into neuroscience

Documentation: https://mapequation.github.io/alluvial-py

  • Native Infomap support. Pass the results of infomap.run (or infomap.Infomap objects that have run), or Infomap's output files (.tree, .ftree, .stree, .clu, .json), and go from data to a paper figure in a few lines. Sub-modules, state, memory and multilayer networks work too.
  • Any partition. Plain dicts that map nodes to modules, and pandas DataFrames with one row per node, need no Infomap at all.
  • The mapequation.org layout. The layout is a faithful port of the engine behind the mapequation.org alluvial web app, so module order, sizes, margins, streamlines and colors match it, and alluvial.save_diagram writes files the web app opens for editing by hand.
  • Made for papers. Font sizes in points from rcParams, labels fitted to the axes, highlighted groups with a legend, custom module names and order, and the web app's color schemes.

See CHANGELOG.md for what changed in each release.

Install

pip install alluvial

Requires Python >= 3.11 and matplotlib >= 3.7. Results of infomap.run and infomap.Infomap objects work whenever infomap >= 2.14 is installed; pip install "alluvial[infomap]" is only a shortcut that also installs a compatible infomap. Files written by any infomap version (.tree, .ftree, .clu, *_states.*, .json) read without it.

Quick start

Map each node to its module, one dict per network. Nodes are matched across networks by name, and modules are sized by their number of nodes:

import alluvial

# Who works in which team, two years running.
teams_2020 = {"Ann": 1, "Bo": 1, "Cy": 1, "Di": 1, "Ed": 2, "Flo": 2, "Gus": 2, "Hal": 3, "Ivy": 3}
teams_2021 = {"Ann": 1, "Bo": 1, "Cy": 1, "Di": 2, "Ed": 2, "Flo": 2, "Gus": 2, "Hal": 2, "Ivy": 3}

ax = alluvial.plot([teams_2020, teams_2021], names=["2020", "2021"])
ax.figure.savefig("alluvial.png", dpi=150)

Alluvial diagram of two partitions of nine people into teams

examples/quickstart.py makes the figure above. A module can also be a path in a module tree, such as "1:2" (sub-module 2 of module 1). alluvial.partition(modules, flows) weights the nodes by flow, and a DataFrame with the columns network, node, module and optionally flow is a whole diagram:

import pandas as pd

import alluvial

df = pd.DataFrame(
    {
        "network": ["2020", "2020", "2020", "2021", "2021", "2021"],
        "node": ["Ann", "Bo", "Cy", "Ann", "Bo", "Cy"],
        "module": [1, 1, 2, 1, 2, 2],
        "flow": [0.5, 0.3, 0.2, 0.5, 0.3, 0.2],
    }
)
ax = alluvial.plot(df)  # or alluvial.from_dataframe(df, network=..., node=..., module=..., flow=...)
nf = alluvial.partition({"Ann": 1, "Bo": 1, "Cy": "2:1"}, flows={"Ann": 3, "Bo": 2, "Cy": 1}, name="2022")

From Infomap

Pass the Result of infomap.run, or the files Infomap writes (an infomap.Infomap that has run works too):

import infomap

import alluvial

links_2020 = [(1, 2), (1, 3), (2, 3), (3, 4), (4, 5), (4, 6), (5, 6)]
links_2021 = [(1, 2), (1, 3), (2, 3), (3, 5), (3, 6), (4, 5), (4, 6), (5, 6), (4, 7), (7, 8), (8, 4)]
results = [infomap.run(links, seed=1, two_level=True) for links in (links_2020, links_2021)]
ax = alluvial.plot(results, names=["2020", "2021"])

# Or Infomap's output files, here bundled with the tests (journals are matched by name):
science = [f"tests/data/science{year}_2y.stree" for year in (2001, 2003, 2005, 2007)]
ax = alluvial.plot(science, identifier="name", names=[2001, 2003, 2005, 2007])

Networks go from left to right, and each one can be:

  • a path to an Infomap output file (.tree, .ftree, .stree, .clu or -o json .json; the format comes from the suffix),
  • the Result of infomap.run, or an infomap.Infomap that has run (state and memory networks give state nodes, labeled by physical names),
  • an Infomap JSON dict (json.load of a -o json file),
  • a dict that maps nodes to modules (or alluvial.partition(modules, flows)), or a DataFrame of several networks,
  • an alluvial.NetworkFile from alluvial.read(...).

docs/tutorial.ipynb walks through a complete analysis: seeded Infomap runs on a small temporal network, the colorings, sub-modules, highlight groups with a legend, multilayer (state) networks, reading Infomap files, a two-panel PDF figure and restyling single artists. docs/capabilities.ipynb covers every feature, each with checks.

Capabilities

  • Inputs: Infomap's .tree, .ftree, .stree and .clu files (also the *_states.* versions), Infomap JSON (a -o json file or its dict), the Result of infomap.run and infomap.Infomap objects (state, memory and multilayer networks, with split_layers for one network per layer), a .zip or directory of network files (the web app's upload), plain {node: module} dicts and alluvial.partition, long-format pandas DataFrames (alluvial.from_dataframe), and alluvial.NetworkFiles from alluvial.read.
  • Layout: the web app's layout options (height, module_width, streamline_fraction, streamline_threshold, flow_threshold, margin_exponent, zero_margins, vertical_align, module_size, sort_modules_by), sub-modules (module_level, expand), custom module_names and module_order, and bipartite networks (show_bipartite_nodes).
  • Colors: by module id, by matching modules across networks (similarity), by node, by layer, by physical node or by the module hierarchy; the web app's 65 color schemes (with c3's tone and offset) or your own colors; highlight groups of nodes (a mapping or a function) with a legend.
  • Drawing: plot/draw on any matplotlib axes, with fonts from rcParams, labels fitted at draw time, hierarchy shadows or outlines, palettes and colormaps, gradient streamlines, rasterizing, and every artist available for restyling (get_artists); vector PDF and SVG output.
  • Geometry: layout returns frozen dataclasses (no matplotlib needed) with to_dict() for other renderers.
  • Save and load: save_diagram writes the web app's "Download diagram" files (a zip or loose Infomap-JSON files), which the web app opens for hand editing and read/layout load back with options, colors, order and names restored.

docs/capabilities.ipynb is a tested tour of all of these: every step asserts what it shows, and the test suite runs it.

Usage

The snippets below use the datasets bundled in tests/data of a source checkout; run them from its root.

Figures for papers

Pass your own axes and the diagram fills it. Shapes scale with the axes, while every font size, gap and margin is in points, so text prints at exactly the size you ask for. Labels and limits are fitted again at each draw, which keeps layout="constrained", bbox_inches="tight" and later resizing working. Labels never leave the axes. Long names are truncated with an ellipsis, and where the labels of neighboring modules would collide, the smaller module's label loses lines or is hidden.

import matplotlib.pyplot as plt

import alluvial

science = [f"tests/data/science{year}_2y.stree" for year in (2001, 2003, 2005, 2007)]
planted = [f"tests/data/planted_t{i}.ftree" for i in range(3)]

with plt.rc_context({"font.family": "serif", "font.size": 8, "pdf.fonttype": 42}):  # match the paper
    fig, (a, b) = plt.subplots(1, 2, figsize=(7, 2.6), width_ratios=[1.4, 1], layout="constrained")
    alluvial.plot(science, ax=a, names=[2001, 2003, 2005, 2007], max_label_lines=3)
    alluvial.plot(planted, ax=b, names=["$t_0$", "$t_1$", "$t_2$"], palette="tab10")
    a.set_title("(a)", loc="left")
    fig.savefig("figure.pdf")

A two-panel double-column figure

examples/paper_figure.py makes the figure above. The PDF stays vector: a two-color streamline is a 256 × 1 gradient strip clipped to its outline (gradient=False makes them solid). For very large diagrams, rasterized=True rasterizes the shapes and keeps the text as vector.

Sub-modules

Infomap finds hierarchies of modules. module_level=2 shows the sub-modules of every visible module that has them (one value, or one per network), exactly as clicking modules in the web app does. expand=[(network, module_id), ...] expands single modules first. Gray boxes offset behind the sub-modules mark their parent module, like the web app's hierarchy shadows; hierarchy="outline" draws dotted boxes around them instead, and hierarchy=False nothing.

import alluvial

networks = [f"tests/data/multilevel_t{i}.tree" for i in range(3)]
ax = alluvial.plot(networks, module_level=2)
geom = alluvial.layout(networks, expand=[(1, "1"), (1, "1:1")])  # network index (or id), module path

Top modules and module level 2

examples/hierarchy.py makes the figure above. Each ModuleGeometry has its level, and NetworkGeometry.outlines holds the parent-module boxes.

Highlight groups

highlight colors chosen nodes wherever they go and leaves everything else gray. Pass {label: node identifiers} or a function of an alluvial.NodeInfo that returns a label or None. legend=True (or alluvial.legend(ax)) adds a legend above the diagram, and the label lines take the color of their node.

import matplotlib.pyplot as plt

import alluvial

science = [f"tests/data/science{year}_2y.stree" for year in (2001, 2003, 2005, 2007)]
fields = {
    "Biology": ["J BIOL CHEM", "CELL", "EMBO J", "GENE DEV", "MOL CELL BIOL", "P NATL ACAD SCI USA"],
    "Physics": ["PHYS REV LETT", "PHYS REV B", "ASTROPHYS J", "APPL PHYS LETT", "J CHEM PHYS"],
}
fig, ax = plt.subplots(figsize=(3.4, 2.8), layout="constrained")
alluvial.plot(
    science, ax=ax, identifier="name", highlight=fields, legend=True, names=[2001, 2003, 2005, 2007]
)

# A function of an alluvial.NodeInfo works too; it returns a label or None:
geom = alluvial.layout(
    science, identifier="name", highlight=lambda node: "J ..." if node.name.startswith("J ") else None
)

Highlighted journals with a legend

examples/highlight.py makes the figure above. Identifiers are matched like the networks' nodes (identifier=), and numbers are allowed ({"a": [1, 2, 3]}). highlight_colors sets the colors (a list, or {label: color}). highlight replaces colors. The coloring is the web app's own "color by metadata" operation, including one quirk: in a network without hidden modules, the last module can miss its color; layout then warns.

Module names and order

module_names labels modules with names of your choice instead of their largest nodes, and module_order stacks a network's modules in a given order. Both are web-app operations (the module name field and "move up/down"), so the web app can show the same diagram.

import alluvial

science = [f"tests/data/science{year}_2y.stree" for year in (2001, 2003, 2005, 2007)]
geom = alluvial.layout(
    science,
    identifier="name",
    module_names={(0, "1"): "Life sciences", (0, "2"): "Physics", (3, "1"): "Life sciences"},
    module_order={0: ["2", ...], 3: ["2", "4", "3", "5", "1"]},  # bottom to top
)
# A function of an alluvial.ModuleInfo names every module; it returns a name or None:
geom = alluvial.layout(
    science,
    identifier="name",
    module_names=lambda m: "Physics" if "PHYS REV LETT" in m.node_names[:5] else None,
)

Four years of a citation network, modules named by field in a fixed order

examples/names_order.py makes the figure above.

  • module_names takes {(network, module_id): name} (network index, id, or a name given with names= as a string; module ids as shown, e.g. "1:2" after module_level=2) or a function. As in the web app, names are drawn on the first and last network (ModuleGeometry.name holds them for every network).
  • module_order takes {network: [module_id, ...]}, bottom to top. The unlisted visible modules keep their relative order above the listed ones; ... stands for them, so ["2", ...] puts module 2 at the bottom, [..., "2"] at the top. Hidden modules (below flow_threshold) cannot be placed. The order is applied after colors/highlight, so sort_modules_by sets the starting order.
  • Unknown networks or modules, a module listed twice and hidden modules raise a ValueError that names them.

Multilayer networks

split_layers=True draws a multilayer partition as one network per layer, matching nodes by their physical id, as the web app does when it expands a multilayer file. Pass one flag per network to split only some.

import alluvial

ax = alluvial.plot(["tests/data/mlayer_states.tree"], split_layers=True)
layers = alluvial.read("tests/data/mlayer_states.tree", split_layers=True)  # one NetworkFile per layer

A four-layer partition, one network per layer

examples/multilayer.py makes the figure above from an Infomap run on a multilayer network. colors="layer" and colors="physical_id" color multilayer and state networks by layer and by physical node.

Colors

colors chooses what gets a color ("module_id", the default, "similarity", "nodes", "layer", "physical_id", "hierarchy" or None) and scheme the colors: one of the web app's 65 schemes by name (alluvial.SCHEMES) or a list of colors. The C3 schemes ("C3 Oklch", the default, "C3 Sinebow", "C3 Turbo", "C3 Rainbow", "C3 Viridis") are computed by c3-colors, the Python port of the web app's c3, and take a tone and an offset.

import alluvial

networks = [f"tests/data/multilevel_t{i}.tree" for i in range(3)]
geom = alluvial.layout(networks, scheme="C3 Oklch", tone="pastel", offset=120)
geom = alluvial.layout(networks, colors="hierarchy", module_level=2, scheme="Viridis", tone="deep")
geom = alluvial.layout(
    networks, colors="nodes", color_network=2
)  # every node by its module in the last network
  • tone is one of c3's tones ("vivid", the default, "bright", "strong", "deep", "dark", "soft", "pastel", "muted"). offset turns the hue source by that many degrees (None: c3's default, 245 for "C3 Oklch" and 0 for the others). The other schemes are fixed lists, which tone and offset leave unchanged.
  • colors="hierarchy" colors the modules by their place in the module hierarchy (c3's hierarchy()): sub-modules get colors close to their parent's, and the same module path gets the same color in every network. The C3 schemes and the interpolated schemes (diverging, sequential and cyclical, such as "Viridis" and "Spectral") are its hue source, with the tone and offset; any other scheme, a color list included, gives its seed colors and ignores the tone.
  • save_diagram stores the scheme, tone and offset, and layout of the saved files restores them, as the web app does. A tone or offset you pass wins, and passing another scheme drops the saved offset.
  • Known difference: c3-colors colors a module with many sub-modules (about 40 or more) slightly differently from the web app's c3 with colors="hierarchy" (a few sibling colors swap). It waits for a c3-colors release; the reference fixtures and the differential fuzzer find no other color difference.

The web app: save and load

save_diagram lays out the networks like layout and saves the result for the web app, where it can be fine-tuned by hand. It takes the networks and the same keywords as layout, not an AlluvialGeometry: the geometry holds only shapes, while the saved file needs the engine's networks (node paths, identifiers, expansion levels). The layout is deterministic, so the same arguments give the same diagram.

import alluvial

networks = [f"tests/data/multilevel_t{i}.tree" for i in range(3)]
# The web app's "Download diagram" format (drop the zip on its "Load networks" dialog):
alluvial.save_diagram(networks, "diagram.zip", module_level=2, scheme="Paired")
files = alluvial.read("diagram.zip")  # one NetworkFile per saved network, in saved order
geom = alluvial.layout(files)  # the same diagram: colors, levels, options, order and names
  • save_diagram writes "alluvial network files" (formatVersion 1), the format of the web app's "Download diagram": one Infomap-JSON file per network with the palette, each node's color and module level, and an alluvial block with the options, the color scheme with its c3 tone and offset, the module names and the custom module order. It writes a zip by default (loose=True writes the files to a directory). alluvial.read of the zip, of the directory or of a list of the files gives one NetworkFile per file in saved order, and layout restores the diagram from them (keywords you pass win over the stored values; restore_state=False ignores the block). The files are also plain Infomap JSON.
  • The 2019 saved-diagram format (one JSON file with networks, written by the web app before 2022) is no longer read: alluvial.read raises a ValueError. alluvial 0.4 still reads it.
  • JSON input with a top-level colors list (or colors: true with per-node color) is colored as in the web app, and layout then keeps those colors unless colors= is given. Schemes, tones and offsets: see "Colors" above.

API

import alluvial

networks = [f"tests/data/planted_t{i}.ftree" for i in range(3)]
geom = alluvial.layout(
    networks,
    names=None,
    ids=None,
    identifier="id",
    split_layers=False,
    colors="module_id",  # "similarity", "nodes", "layer", "physical_id", "hierarchy" or None
    color_network=None,  # with colors="nodes": the network whose modules color the nodes; default the first
    module_level=1,
    expand=None,
    highlight=None,
    highlight_colors=None,
    scheme=None,  # a web-app scheme name, e.g. "Paired", or a list of colors; default "C3 Oklch"
    tone=None,  # a c3 tone of the C3 schemes and colors="hierarchy", e.g. "pastel"; default "vivid"
    offset=None,  # degrees around the c3 hue source of the first color; default c3's
    restore_state=True,  # restore what a saved diagram stores (options, scheme, tone, offset, orders, names)
    module_order=None,  # {network: [module_id, ...]}, bottom to top
    module_names=None,  # {(network, module_id): name} or a function of an alluvial.ModuleInfo
    height=600,
    module_width=80,
    streamline_fraction=2,
    streamline_threshold=1,
    flow_threshold=5e-3,
    margin_exponent=4,
    zero_margins=False,
    vertical_align="bottom",  # or "justify"
    module_size="flow",
    sort_modules_by="flow",
)
ax = alluvial.draw(
    geom,
    ax=None,
    palette=None,
    default_color=None,
    module_labels=True,
    network_labels=True,
    fontsize=None,
    network_fontsize=None,  # points or "small"/"medium"/...
    max_label_lines=None,
    max_label_width="auto",
    avoid_label_overlap=True,
    label_pad=3,
    streamline_alpha=0.8,
    gradient=True,
    edgecolor="face",
    linewidth=0.3,
    rasterized=False,
    hierarchy=True,  # "shadow", "outline" or False
    legend=False,
    label_colors="auto",
    module_kw=None,
    streamline_kw=None,
    hierarchy_kw=None,
    text_kw=None,
    label_kw=None,
    network_label_kw=None,
)
art = alluvial.get_artists(ax)  # .modules, .streamlines, .outlines, .module_labels, .network_labels, .legend
  • plot(networks, ax=None, **kwargs) passes each keyword to draw or layout, whichever takes it. An unknown keyword raises TypeError.
  • colors: "module_id" (default: the same module id gets the same color in every network), "similarity" (the web app's "paint all by similarity"), "nodes" (color nodes by their module in one network, color_network, by default the first), "layer", "physical_id" (multilayer and state networks), or None (the input colors: gray, except for JSON with colors, where None is the default).
  • alluvial.save_diagram(networks, path, *, loose=False, **layout_kwargs) saves the diagram for the web app as a zip (or loose files; see above).
  • identifier: match nodes across networks by "id" (state id if present, else node id) or by "name".
  • Network ids default to the file stem (or net0, net1, ... for objects). Colliding ids are renamed out, out_2, ...; ids=[...] sets them.
  • The steps run in the web app's store in a fixed order: build, expand, module_level, then colors or highlight. Each step is the store's own operation, so the geometry is the web app's to the last bit.
  • palette: a list of colors, or a matplotlib colormap or its name. It defaults to the web app's palette. A qualitative map ("tab10") is used as is, and a continuous one is sampled. For grayscale-safe prints, use a map whose lightness changes monotonically, such as "cividis" or "viridis". Unhighlighted modules take default_color, and insignificant groups are drawn brighter.
  • Font sizes follow rcParams["font.size"] by default: module labels are "small" and network names "medium". text_kw styles all text, while label_kw and network_label_kw style one kind only. module_kw, streamline_kw and hierarchy_kw go to the shapes. Module labels are drawn literally, with no mathtext. Network names may use $...$.
  • label_colors="auto" colors label lines by their node's highlight color only for highlight diagrams; True always does (as the web app), False never.
  • max_label_width="auto" caps each label column at a quarter of the axes width. Pass a number in points, or None to disable truncation. avoid_label_overlap=False and linewidth=0 reproduce the web app exactly.
  • alluvial.legend(ax, other=None, **kwargs) adds (or replaces) the highlight legend; other="other" adds a gray entry, and loc=/bbox_to_anchor= place it like any matplotlib legend.
  • Every artist has a gid, such as module:<network>:<module>:<group>, streamline:<id> or outline:<network>:<path>, which SVG output keeps as the element id.
  • Without ax=, draw makes its own figure sized by content: taller with more modules, wider with more networks and longer labels.
  • draw and plot never call plt.show() and never change global state. They return the axes.
  • Common mistakes get a clear error or warning: an Infomap object that has not run, a missing file, passing networks to draw (use plot), state networks next to ordinary networks under identifier="id" (state ids would be compared with node ids), highlight identifiers that match no node, a module_level deeper than a network's module tree, and networks whose node flows are all zero (an empty diagram). Warnings point at your own line, also through plot.

See docs/dev/design.md for the binding design decisions.

Development

uv sync --extra dev   # .venv with the package (editable) and the dev tools, from uv.lock; any Python >= 3.11
.venv/bin/pytest -q

The dev extra installs pytest, infomap and ruff (.venv/bin/ruff check src tests). infomap 2.15.1 installs from a cp314 wheel, so no --system-site-packages fallback is needed.

Layout:

  • src/alluvial/: layout.py (public layout, LayoutOptions, NodeInfo), geometry.py (frozen geometry contract), io/ (readers, NetworkFile, split_layers), engine/ (port of the TS engine; internal), mpl.py (draw, plot, legend, get_artists), _palette.py, tree_path.py, _jscompat.py (JS number formatting and arithmetic semantics).
  • layout, geometry, io and engine never import matplotlib (enforced by tests/test_no_mpl_import.py).
  • docs/dev/port-notes/: detailed porting specs for each part of the TS engine.

Test fixtures

tests/fixtures/*.json (205 scenarios, plus 77 in v3/ and 15 in v4/ with save/load round trips) are the reference geometry, produced by running the real TypeScript engine headlessly in Node on the datasets in tests/data/. Fixtures with a features list exercise v2 features. To regenerate or verify them (needs Node >= 22 and a checkout of the web app with node_modules; set ALLUVIAL_TS_ROOT if it is not at ../web/alluvial):

node tools/ts-fixtures/generate.mjs           # rewrite tests/fixtures
node tools/ts-fixtures/generate.mjs --check   # regenerate to a temp dir and byte-compare

See tools/ts-fixtures/README.md for the fixture format and the scenario list.

The port follows the TS engine at the commit in tests/fixtures/provenance.txt (ts_repo_head, now 60fa1bf). To sync with a newer TS master, list the engine commits to port, port them, then regenerate and review:

git -C ../web/alluvial log <ts_repo_head>..master -- packages/alluvial-diagram/src
ALLUVIAL_TS_ROOT=../web/alluvial node tools/ts-fixtures/generate.mjs --check   # exit 0: nothing changed
ALLUVIAL_TS_ROOT=../web/alluvial node tools/ts-fixtures/generate.mjs           # regenerate, then review the diff

License

MIT. The layout algorithm is ported from the alluvial web app by Anton Holmgren (MIT); see LICENSE.

Metadata

Release files for alluvial 0.7.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for alluvial 0.7.0
File Size Uploaded Provenance
alluvial-0.7.0.tar.gz 6.5 MB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for alluvial 0.7.0
File Interpreter ABI Platform
alluvial-0.7.0-py3-none-any.whl Python 3 none any Details

Total release size: 6.7 MB

Release files / alluvial-0.7.0.tar.gz

Download URL alluvial-0.7.0.tar.gz
Size 6.5 MB
Tags Source
SHA-256 checksum
How to use checksums
09d81f4336c2a0727ce251101519cc2c4e41fe839d1a67e53663c8fb0e083afe
BLAKE2b-256 checksum
How to use checksums
42d846992b66240ee6e47cf700eb345935787d8e54d7306f0710bfed2eb48cab
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

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 23, 2026.

Transparency log

Release files / alluvial-0.7.0-py3-none-any.whl

Download URL alluvial-0.7.0-py3-none-any.whl
Size 161.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
8c3c2f7a910a55c88cdcfa7487f41e0968e8d10bbf1b6902ee43831d319d786f
BLAKE2b-256 checksum
How to use checksums
ecede247ee8a0f94aeb6b96786dde4de9c6cab27981a30d996cf55fd935ba38d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

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 23, 2026.

Transparency log

Security for alluvial 0.7.0

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.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for alluvial-0.7.0.tar.gz

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 23, 2026.

Transparency log

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.

Release history Release notifications | RSS feed

This release

0.7.0 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page