vsdxkit
Create, edit and analyse Microsoft Visio .vsdx files with Python. Visio is not required at runtime.
0.x API notice. 0.7 is the last release of the inherited API. 1.0 renames
VisioFiletoDocumentandContainertoSwimlaneDiagram, splitsConnectinto an internalConnectionRecordand a publicConnector, and drops the context manager: opening closes the archive before it returns, andsave()is the only write. The 1.0 design lists every change, and a migration guide lands atdocs/migration-1.0.rstwith the first breaking release. Once there is a release on PyPI again, pinvsdxkit<1to stay on the 0.x names; see Installation for what the index holds today.
The distribution is named vsdxkit. The import remains vsdx, so existing code keeps working.
vsdxkit adds shape creation, Visio-faithful connectors, connector re-anchoring, cross-functional flowchart swimlanes, stricter package handling, current Python tooling and typed public APIs. It began as a fork of dave-howard/vsdx and is now developed as its own project; see Provenance and licence.
What it does
- Opens, queries and edits existing
.vsdxfiles without Microsoft Visio. - Finds shapes by ID, text, regular expression or Shape Data.
- Creates common flowchart shapes from a bundled palette.
- Creates dynamic or connection-point glue with straight, right-angle or curved routing.
- Re-anchors either end of an existing connector.
- Reads and extends Visio cross-functional flowchart swimlanes.
- Copies shapes and pages while rewriting package-local IDs and importing masters.
- Renders data into Visio templates with Jinja.
- Saves to a new file or safely replaces the source file in place.
The implementation edits the XML parts inside the Open Packaging Convention archive. It does not drive the Visio user interface. Generated connector and swimlane files are checked against Microsoft Visio through COM before a connector or swimlane change is considered done. That check is manual; no workflow runs Visio. See When a change needs Visio.
Installation
As of 2026-09-13 there is no release on PyPI. The first release was
withdrawn after a security defect was found in it and cannot be republished, so
pip install vsdxkit finds no versions until the next one lands. The
PyPI project page shows the current state.
Install from GitHub in the meantime:
python -m pip install "vsdxkit @ git+https://github.com/firmfooting/vsdxkit.git"
That tracks main, so pin a commit if you need a reproducible install. Once
there is a release on the index, install it with pip install vsdxkit.
For development:
git clone https://github.com/firmfooting/vsdxkit.git
cd vsdxkit
uv sync --locked
Python 3.10–3.14 is supported on Linux, Windows and macOS. Add --group docs to
that sync if you also want to build the documentation; Sphinx needs Python 3.12
or later.
Open, edit and save
Use the context manager to close the package cleanly. Saving is explicit.
from vsdx import VisioFile
with VisioFile("diagram.vsdx") as vis:
page = vis.pages[0]
shape = page.find_shape_by_text("Shape to remove")
if shape is not None:
shape.text = "Renamed shape"
vis.save_vsdx("edited.vsdx")
Call save_vsdx() without a filename to replace the source file in place:
with VisioFile("diagram.vsdx") as vis:
vis.pages[0].name = "Current state"
vis.save_vsdx()
Create shapes and connectors
Shape coordinates are in Visio page units, normally inches. x and y identify the shape centre.
from vsdx import VisioFile
with VisioFile("diagram.vsdx") as vis:
page = vis.pages[0]
start = vis.create_shape(
page, "PALETTE_START_END", 2.0, 6.0, text="Start"
)
work = vis.create_shape(
page, "PALETTE_PROCESS", 6.0, 6.0, text="Do the thing"
)
decision = vis.create_shape(
page, "PALETTE_DECISION", 10.0, 6.0, text="OK?"
)
page.connect_shapes(start, work)
page.connect_shapes(work, decision, route="rightangle")
vis.save_vsdx("flow.vsdx")
Bundled palette names are:
PALETTE_PROCESSPALETTE_DECISIONPALETTE_START_ENDPALETTE_PARALLELOGRAMPALETTE_DATABASE
Connector route combines glue and routing behaviour:
| Value | Meaning |
|---|---|
dynamic |
Dynamic shape glue. This is the default. |
point |
Glue to zero-based connection points selected with from_cp and to_cp. |
straight |
Dynamic glue with straight routing. |
rightangle |
Dynamic glue with right-angle routing. |
curved |
Dynamic glue with curved routing. |
| `point | curved` |
Re-anchor a connector
Pass only the end that should move. A None endpoint keeps the current shape.
connector = page.find_shape_by_id("9")
new_target = page.find_shape_by_text("Store")
if connector is not None and new_target is not None:
page.reanchor_connector(connector, to_shape=new_target)
Deleting a shape through page.delete_shape(shape) also removes incident connectors and their Connect records.
Work with swimlanes
Swimlane operations require an existing Visio cross-functional flowchart (CFF) page. add_swimlane() clones the current top lane and updates the CFF container geometry.
with VisioFile("cross-functional-flow.vsdx") as vis:
page = vis.pages[0]
container = page.get_container()
if container is None:
raise ValueError("The page is not a Visio CFF diagram")
review_lane = page.add_swimlane("Review")
check = vis.create_shape(
page, "PALETTE_PROCESS", 6.0, 2.0, text="Check"
)
page.add_shape_to_lane(check, review_lane)
assert container.lane_of(check) is not None
vis.save_vsdx("with-review-lane.vsdx")
Visio CFF membership is geometric. Shapes are associated with the lane whose vertical band contains their centre; there is no separate membership field to write.
Render a Jinja template
Jinja expressions can be stored in shape text and rendered into a new file:
with VisioFile("template.vsdx") as vis:
vis.jinja_render_vsdx(
context={"project": "Ward refurbishment", "owner": "Facilities"}
)
vis.save_vsdx("rendered.vsdx")
The package also supports its existing group-shape loop and showif conventions. See docs/templating.rst and the tests/test_jinja*.py cases for the exact template structure.
Limits
- The library starts from an existing
.vsdx; it does not create a complete Visio document package from nothing. .vsdmfiles can be read and saved, but only back to a.vsdmdestination. The package kind is decided by the content type ofvisio/document.xml, not by the filename, sosave_vsdx()refuses a.vsdxdestination for a macro-enabled package and a.vsdmdestination for one that is not — either would produce a file whose extension and[Content_Types].xmldisagree, which Visio reports as corrupt. Stripping macros to convert a.vsdminto a.vsdxis not supported. A destination with no extension, or with an unrelated one, gets the matching Visio extension appended.- Swimlane creation works on existing Visio CFF diagrams. It does not convert an ordinary page into a CFF diagram.
- Visio may recalculate layout when a generated file opens. The library writes the glue and route cells but does not reproduce Visio's entire layout engine.
- Loading enforces package expansion limits before any archive member is read: at most 512 members, 64 MiB per member, 256 MiB total uncompressed, and a 100:1 compression ratio, plus rejection of duplicate and path-unsafe member names. A hostile or accidental archive is refused with
vsdx.PackageLimitErrorinstead of exhausting process memory. The defaults suit documents from unknown sources; trusted callers can relax the caps withVisioFile(filename, limits=PackageLimits(...))orlimits_path="vsdx.limits.json"(same keys, JSON object).
Development and verification
uv run --no-sync python -m pytest tests -q
uv run --no-sync ruff check vsdx tests tools
uv run --no-sync ruff format --check vsdx tests tools
uv run --no-sync pyrefly check vsdx --min-severity warn --output-format min-text
uv run --no-sync sphinx-build -W --keep-going -b html docs docs/_build/html
uv run --no-sync python -m build
The package is held at pyrefly's strict preset. CI tests Python 3.10–3.14 on Linux and Windows, and 3.10 and 3.14 on macOS. Connector and swimlane changes also run through tools/visio_check.ps1, which opens generated files in an invisible Microsoft Visio instance and reports package repair or automation errors. That step is manual, on a maintainer's Windows machine; GitHub's runners have no Visio.
Documentation
The Sphinx source is in docs/. Build it locally with:
uv sync --locked --group docs
uv run --no-sync python -m sphinx -W --keep-going -b html docs docs/_build/html
Sphinx is pinned in the docs dependency group, which requires Python 3.12 or
later. The library itself still supports 3.10, so run the docs build on a 3.12+
interpreter.
Provenance and licence
vsdxkit descends from dave-howard/vsdx, originally written by Dave Howard and released under the BSD 3-Clause licence. That work is the foundation this library is built on, and its copyright notice is retained in LICENSE alongside our own.
vsdxkit is now developed independently: it is not a downstream of that project and does not track it. The vsdx import namespace is kept so existing code continues to work, and the licence remains BSD 3-Clause.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file vsdxkit-0.7.1.tar.gz.
File metadata
- Download URL: vsdxkit-0.7.1.tar.gz
- Upload date:
- Size: 613.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ff70ff50a204fb409c03761e4a81afbdae7ad6be78e31f63a30fd89cc1a74d40
|
|
| MD5 |
b5f006ea5696b289e1b3605aac3c4654
|
|
| BLAKE2b-256 |
21b97d484bcfa7d040aaf952d117ed8ba0d1819c725b7aac4d4db6ab913c79bf
|
Provenance
The following attestation bundles were made for vsdxkit-0.7.1.tar.gz:
Publisher:
publish.yml on firmfooting/vsdxkit
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
vsdxkit-0.7.1.tar.gz -
Subject digest:
ff70ff50a204fb409c03761e4a81afbdae7ad6be78e31f63a30fd89cc1a74d40 - Sigstore transparency entry: 2817751712
- Sigstore integration time:
-
Permalink:
firmfooting/vsdxkit@83bda7ad0cf5fa3edc120f2859874c5a29e38044 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/firmfooting
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@83bda7ad0cf5fa3edc120f2859874c5a29e38044 -
Trigger Event:
push
-
Statement type:
File details
Details for the file vsdxkit-0.7.1-py3-none-any.whl.
File metadata
- Download URL: vsdxkit-0.7.1-py3-none-any.whl
- Upload date:
- Size: 98.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7b973d2241314867afd08ebc01b5be3411bdac499b51c3c8c3c0c4fd2d71a5f2
|
|
| MD5 |
4f8a25524770b1679f6ea03ec5a748b4
|
|
| BLAKE2b-256 |
1ae51472d0a635194e06c3ccb88c825e3ea5e03cc82ecbdb82b7dd58483663da
|
Provenance
The following attestation bundles were made for vsdxkit-0.7.1-py3-none-any.whl:
Publisher:
publish.yml on firmfooting/vsdxkit
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
vsdxkit-0.7.1-py3-none-any.whl -
Subject digest:
7b973d2241314867afd08ebc01b5be3411bdac499b51c3c8c3c0c4fd2d71a5f2 - Sigstore transparency entry: 2817751752
- Sigstore integration time:
-
Permalink:
firmfooting/vsdxkit@83bda7ad0cf5fa3edc120f2859874c5a29e38044 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/firmfooting
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@83bda7ad0cf5fa3edc120f2859874c5a29e38044 -
Trigger Event:
push
-
Statement type: