diff --git a/docs/automotive-adas/index.rst b/docs/automotive-adas/index.rst
index d226e3b..dbba8f9 100644
--- a/docs/automotive-adas/index.rst
+++ b/docs/automotive-adas/index.rst
@@ -36,6 +36,7 @@
sys_4_sys_integation_tests
sys_5_sys_quali_test
sys_5_sys_quali_test_results
+ sysml/index
V-model and service connectors
diff --git a/docs/automotive-adas/sys_3_sys_arch.rst b/docs/automotive-adas/sys_3_sys_arch.rst
index 0a37b95..2985a7e 100644
--- a/docs/automotive-adas/sys_3_sys_arch.rst
+++ b/docs/automotive-adas/sys_3_sys_arch.rst
@@ -207,6 +207,11 @@ SYS.3 Architecture Design
Design the system architecture for traffic sign recognition, including camera
capture, sign classification, and distribution of detected speed limits to vehicle control functions.
+ The component view (PlantUML) and the SysML Block Definition Diagram below
+ describe the same architecture from two notations. The BDD is a static
+ SVG pre-rendered from a ``.gaphor`` model — see :ref:`SysML_Demo` for
+ the workflow.
+
.. uml::
@startuml
@@ -229,3 +234,10 @@ SYS.3 Architecture Design
TrafficSignRecognition --> SignInterpreter
SignInterpreter --> VehicleControl
@enduml
+
+ .. figure:: sysml/adas-tsr-bdd__tsr-block-definition.svg
+ :align: center
+ :alt: SysML Block Definition Diagram for the Traffic Sign Recognition system
+
+ SysML BDD: ``TrafficSignRecognition`` decomposed into ``FrontCamera``,
+ ``SignInterpreter`` and ``VehicleControl`` parts.
diff --git a/docs/automotive-adas/sysml/adas-tsr-bdd.gaphor b/docs/automotive-adas/sysml/adas-tsr-bdd.gaphor
new file mode 100644
index 0000000..a0f21f2
--- /dev/null
+++ b/docs/automotive-adas/sysml/adas-tsr-bdd.gaphor
@@ -0,0 +1,243 @@
+
+
+
+
+
+ADAS
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+TrafficSignRecognition
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+FrontCamera
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+SignInterpreter
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+VehicleControl
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+composite
+
+
+camera
+
+
+
+
+
+
+
+
+
+
+composite
+
+
+interpreter
+
+
+
+
+
+
+
+
+
+
+composite
+
+
+control
+
+
+
+
+
+
+
+
+
+
+
+
+
+TSR Block Definition
+
+
+
+
+
+
+
+
+
+
+
+
+(1.0, 0.0, 0.0, 1.0, 200.0, 40.0)
+
+
+(0.0, 0.0)
+
+
+240.0
+
+
+160.0
+
+
+
+
+
+1
+
+
+
+
+
+
+
+(1.0, 0.0, 0.0, 1.0, 40.0, 280.0)
+
+
+(0.0, 0.0)
+
+
+160.0
+
+
+70.0
+
+
+
+
+
+
+
+
+
+
+(1.0, 0.0, 0.0, 1.0, 240.0, 280.0)
+
+
+(0.0, 0.0)
+
+
+160.0
+
+
+70.0
+
+
+
+
+
+
+
+
+
+
+(1.0, 0.0, 0.0, 1.0, 440.0, 280.0)
+
+
+(0.0, 0.0)
+
+
+160.0
+
+
+70.0
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/docs/automotive-adas/sysml/adas-tsr-bdd__tsr-block-definition.svg b/docs/automotive-adas/sysml/adas-tsr-bdd__tsr-block-definition.svg
new file mode 100644
index 0000000..a40696d
--- /dev/null
+++ b/docs/automotive-adas/sysml/adas-tsr-bdd__tsr-block-definition.svg
@@ -0,0 +1,431 @@
+
+
diff --git a/docs/automotive-adas/sysml/author_adas_bdd.py b/docs/automotive-adas/sysml/author_adas_bdd.py
new file mode 100644
index 0000000..02522f1
--- /dev/null
+++ b/docs/automotive-adas/sysml/author_adas_bdd.py
@@ -0,0 +1,100 @@
+"""Programmatically author the ADAS Traffic Sign Recognition BDD.
+
+This script regenerates ``adas-tsr-bdd.gaphor`` from scratch using Gaphor's
+Python API. The model can then be opened and tweaked in the Gaphor GUI, and
+re-exported to SVG via :mod:`render_sysml`. Re-run after structural changes:
+
+.. code:: console
+
+ uv run python docs/automotive-adas/sysml/author_adas_bdd.py
+ uv run python docs/automotive-adas/sysml/render_sysml.py
+
+The generated file is committed alongside the rendered SVG; the doc build
+itself does **not** depend on Gaphor or Cairo (it just embeds the SVG via
+``.. figure::``).
+"""
+
+from __future__ import annotations
+
+from pathlib import Path
+
+from gaphor.core.modeling import ElementFactory
+from gaphor.storage import storage
+from gaphor.SysML import sysml
+from gaphor.SysML.blocks import BlockItem
+from gaphor.UML import uml
+
+OUTPUT = Path(__file__).with_name("adas-tsr-bdd.gaphor")
+
+
+def _make_block(ef: ElementFactory, package, name: str):
+ block = ef.create(sysml.Block)
+ block.name = name
+ block.package = package
+ return block
+
+
+def _make_part(ef: ElementFactory, parent, name: str, type_block):
+ prop = ef.create(uml.Property)
+ prop.name = name
+ prop.aggregation = "composite"
+ prop.type = type_block
+ prop.class_ = parent
+ return prop
+
+
+def _place_block(diag, block, x: float, y: float, width: float = 180, height: float = 80, *, show_parts: bool = False):
+ item = diag.create(BlockItem, subject=block)
+ item.matrix.translate(x, y)
+ item.width = width
+ item.height = height
+ item.show_parts = 1 if show_parts else 0
+ return item
+
+
+def build() -> ElementFactory:
+ ef = ElementFactory()
+
+ pkg = ef.create(uml.Package)
+ pkg.name = "ADAS"
+
+ tsr = _make_block(ef, pkg, "TrafficSignRecognition")
+ cam = _make_block(ef, pkg, "FrontCamera")
+ interpreter = _make_block(ef, pkg, "SignInterpreter")
+ veh_ctl = _make_block(ef, pkg, "VehicleControl")
+
+ # Parts on the parent block (rendered automatically by Gaphor inside a
+ # "parts" compartment when ``show_parts`` is on).
+ _make_part(ef, tsr, "camera", cam)
+ _make_part(ef, tsr, "interpreter", interpreter)
+ _make_part(ef, tsr, "control", veh_ctl)
+
+ diag = ef.create(sysml.BlockDefinitionDiagram)
+ diag.name = "TSR Block Definition"
+ diag.element = pkg
+
+ # Parent block at top centre, parts compartment turned on so the named
+ # parts render inside the block instead of as separate items that would
+ # otherwise overlap the parent label.
+ _place_block(diag, tsr, x=200, y=40, width=240, height=160, show_parts=True)
+
+ # Sub-block definitions in a row below.
+ spacing = 40
+ sub_w, sub_h = 160, 70
+ row_y = 280
+ start_x = 200 + (240 - (3 * sub_w + 2 * spacing)) / 2
+ for i, sub in enumerate((cam, interpreter, veh_ctl)):
+ _place_block(diag, sub, x=start_x + i * (sub_w + spacing), y=row_y, width=sub_w, height=sub_h)
+
+ return ef
+
+
+def main() -> None:
+ ef = build()
+ with OUTPUT.open("w", encoding="utf-8") as fp:
+ storage.save(fp, ef)
+ print(f"Wrote {OUTPUT} ({OUTPUT.stat().st_size} bytes)")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/docs/automotive-adas/sysml/index.rst b/docs/automotive-adas/sysml/index.rst
new file mode 100644
index 0000000..bbd1dc2
--- /dev/null
+++ b/docs/automotive-adas/sysml/index.rst
@@ -0,0 +1,105 @@
+{% set page="index.rst" %}
+{% include "demo_page_header.rst" with context %}
+
+.. _SysML_Demo:
+
+🧩 SysML diagrams alongside sphinx-needs items
+===============================================
+
+This page demonstrates how SysML diagrams can be embedded inside
+Sphinx-Needs items the same way PlantUML diagrams already are. The doc
+build itself stays free of any modeling-tool / Cairo / GTK
+dependencies — diagrams are **pre-rendered to SVG** and committed into
+the repo, then included via plain ``.. figure::`` directives.
+
+The example model
+-----------------
+
+The Block Definition Diagram below is rendered from
+``adas-tsr-bdd.gaphor`` and shows the same Traffic Sign Recognition
+architecture that appears in :need:`ARCH_007` as a PlantUML component
+view. It is committed as ``adas-tsr-bdd__tsr-block-definition.svg``.
+
+.. figure:: adas-tsr-bdd__tsr-block-definition.svg
+ :align: center
+ :alt: SysML BDD: TrafficSignRecognition with FrontCamera, SignInterpreter, VehicleControl
+
+ ``TrafficSignRecognition`` (top, with parts compartment) decomposed
+ into ``FrontCamera``, ``SignInterpreter`` and ``VehicleControl``
+ (bottom row) — pre-rendered SVG, no build-time tooling.
+
+A second example uses Gaphor's stock SysML car BDD, dropped into the
+docs tree unchanged:
+
+.. figure:: sysml-car__main.svg
+ :align: center
+ :alt: SysML BDD of a Car with Wheel and Engine parts and axle ports
+
+ ``Car`` block with ``rear: Wheel[2]``, ``: Engine`` parts and two
+ ``axle`` proxy ports — Gaphor's ``examples/sysml-car.gaphor``
+ committed as-is.
+
+Workflow
+--------
+
+The recommended workflow has two stages — **author once / re-render**
+(needs Gaphor + Cairo) and **build docs** (no extra deps):
+
+1. **Edit the model.** Author / tweak the diagram in the Gaphor desktop
+ GUI (https://gaphor.org/), or programmatically via Gaphor's Python
+ API. The script :file:`author_adas_bdd.py` in this directory shows
+ the API path for the TSR BDD.
+
+2. **Re-render to SVG** on a developer machine that has the Cairo /
+ GTK system libraries installed:
+
+ .. code:: console
+
+ uv sync --extra render
+ uv run python docs/automotive-adas/sysml/render_sysml.py
+
+ Every ``.gaphor`` file under this directory gets rendered into one
+ SVG per diagram, named ``__.svg``.
+
+3. **Commit** the regenerated SVG (and the ``.gaphor`` source).
+
+4. **Build docs** with the normal toolchain — no Gaphor, no Cairo, no
+ GTK on Read-the-Docs or in CI.
+
+Using existing SysML 1.5 XMI
+-----------------------------
+
+A common ask is *"we already have SysML 1.5 XMI files from
+Cameo / Papyrus / MagicDraw / Rhapsody — can we drop them in?"* The
+practical answer is: **render them once in your existing tool, commit
+the resulting SVG, and skip the conversion entirely**.
+
+- All major SysML authoring tools (Cameo Systems Modeler, Eclipse
+ Papyrus, IBM Rhapsody, Sparx Enterprise Architect, MagicDraw) export
+ individual diagrams as SVG or PNG out of the box.
+- The ``.gaphor`` format is **not** XMI — it is a Gaphor-native XML
+ schema with embedded layout. Converting XMI → ``.gaphor`` requires a
+ custom translator (Gaphor's own XMI export was removed in 3.1.0,
+ `gaphor/gaphor#3444 `__).
+- Treating the SysML diagram as **just an image asset** sidesteps the
+ format-translation problem and works for every SysML tool the
+ customer might already own.
+
+If a project still wants the ``.gaphor``-as-code path (text diff, easy
+PR review, `author_adas_bdd.py` style), Gaphor's GUI also imports a
+limited subset of UML XMI 2.5 — see the Gaphor docs for the current
+state.
+
+Trade-offs to be aware of
+-------------------------
+
+- The pre-render step is **manual**: edit a model → run the render
+ script → commit. Mitigations: a ``make render`` target, a CI job
+ that regenerates SVGs when ``.gaphor`` files change and pushes back,
+ or a pre-commit hook.
+- The diagram is now **a binary-ish asset in the repo** (SVG is text,
+ but auto-laid-out XML diffs poorly). The ``.gaphor`` source is the
+ reviewable artifact; the SVG is the build output.
+- For a customer with hundreds of SysML 1.5 XMI diagrams, scripting a
+ bulk export (e.g., a Cameo macro or a Papyrus headless build) is the
+ next step beyond this prototype.
diff --git a/docs/automotive-adas/sysml/render_sysml.py b/docs/automotive-adas/sysml/render_sysml.py
new file mode 100644
index 0000000..2a653ef
--- /dev/null
+++ b/docs/automotive-adas/sysml/render_sysml.py
@@ -0,0 +1,63 @@
+"""Render every ``.gaphor`` file in this directory to a sibling ``.svg``.
+
+This is a one-shot rendering step run from a developer / CI machine that
+has Gaphor (and therefore Cairo + PyGObject) installed. The resulting SVG
+is committed into the repo and embedded by the docs via plain
+``.. figure::`` directives, so the documentation build itself has zero
+dependency on Cairo / GTK.
+
+Usage:
+
+.. code:: console
+
+ uv run --extra render python docs/automotive-adas/sysml/render_sysml.py
+
+Each ``.gaphor`` file may contain multiple diagrams. Every diagram is
+rendered as ``__.svg``.
+"""
+
+from __future__ import annotations
+
+import re
+import sys
+from pathlib import Path
+
+from gaphor.core.modeling import Diagram, ElementFactory
+from gaphor.diagram.export import save_svg
+from gaphor.services.modelinglanguage import ModelingLanguageService
+from gaphor.storage import storage
+
+HERE = Path(__file__).resolve().parent
+
+
+def _slugify(name: str) -> str:
+ return re.sub(r"[^a-z0-9]+", "-", name.lower()).strip("-") or "diagram"
+
+
+def _render(model_path: Path) -> list[Path]:
+ ef = ElementFactory()
+ ml = ModelingLanguageService()
+ with model_path.open(encoding="utf-8") as fp:
+ storage.load(fp, ef, ml)
+
+ written: list[Path] = []
+ for diagram in ef.select(lambda e: isinstance(e, Diagram)):
+ out = model_path.with_name(f"{model_path.stem}__{_slugify(diagram.name)}.svg")
+ save_svg(out, diagram)
+ written.append(out)
+ return written
+
+
+def main() -> int:
+ sources = sorted(HERE.glob("*.gaphor"))
+ if not sources:
+ print(f"No .gaphor files found under {HERE}", file=sys.stderr)
+ return 1
+ for src in sources:
+ for out in _render(src):
+ print(f"Rendered {src.name} -> {out.name} ({out.stat().st_size} bytes)")
+ return 0
+
+
+if __name__ == "__main__":
+ raise SystemExit(main())
diff --git a/docs/automotive-adas/sysml/sysml-car.gaphor b/docs/automotive-adas/sysml/sysml-car.gaphor
new file mode 100644
index 0000000..b93cf6d
--- /dev/null
+++ b/docs/automotive-adas/sysml/sysml-car.gaphor
@@ -0,0 +1,373 @@
+
+
+
+
+New model
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+main
+
+
+
+
+
+
+
+
+* {
+ background-color: transparent;
+ color: black;
+ font-family: sans;
+ font-size: 14;
+ highlight-color: rgba(0, 0, 255, 0.4);
+ line-width: 2;
+ padding: 0;
+}
+
+diagram {
+ background-color: white;
+ line-style: normal;
+ /* line-style: sloppy 0.3; */
+}
+
+
+
+
+
+Car
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+rear: Wheel[2]
+
+
+
+
+
+
+
+
+
+composite
+
+
+
+
+
+
+
+
+
+
+: Engine
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+axle
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+axle
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/docs/automotive-adas/sysml/sysml-car__main.svg b/docs/automotive-adas/sysml/sysml-car__main.svg
new file mode 100644
index 0000000..f1e4c10
--- /dev/null
+++ b/docs/automotive-adas/sysml/sysml-car__main.svg
@@ -0,0 +1,232 @@
+
+
diff --git a/docs/conf.py b/docs/conf.py
index a7c4de7..eea981b 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -6,6 +6,15 @@
import shutil
import sys
+# Defensive: if a developer also has Gaphor (or any other PyGObject-using
+# package) installed in the same venv, matplotlib auto-picks the
+# "gtk4agg" backend and the build hangs at "writing output... [4%]"
+# trying to open a GDK surface on a headless host. Force the headless
+# Agg backend before any Sphinx extension imports matplotlib.
+import matplotlib
+
+matplotlib.use("Agg")
+
import jinja2
# We need to make Python aware of our project source code, which is stored outside `/docs`,
@@ -41,6 +50,10 @@
"ubt_sphinx",
]
+# SysML / Gaphor diagrams are pre-rendered to SVG and embedded with
+# plain ``.. figure::`` — see docs/automotive-adas/sysml/index.rst. The
+# doc build therefore needs no Gaphor / PyGObject / Cairo runtime.
+
ubtrace_organization = "useblocks"
ubtrace_project = "sphinx-needs-demo"
ubtrace_version = "main"
diff --git a/pyproject.toml b/pyproject.toml
index 542d170..0c79067 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -15,11 +15,26 @@ dependencies = [
"sphinx-test-reports>=1.3.2", # pin as RTD consumes this file, not uv.lock
"furo>=2024.8.6",
"sphinx-preview>=0.1.2",
- "ubt-sphinx==0.7.1"
+ "ubt-sphinx==0.7.1",
]
readme = "README.md"
requires-python = ">= 3.12"
+# Optional extra used only when re-rendering the SysML / Gaphor diagrams to
+# SVG. Gaphor pulls in PyGObject + pycairo, which need the Cairo / GTK
+# girepository system libraries to build, so we keep them out of the doc
+# build path. Run:
+#
+# uv sync --extra render
+# uv run python docs/automotive-adas/sysml/render_sysml.py
+#
+# on a developer machine that has those libraries installed, then commit
+# the regenerated SVGs.
+[project.optional-dependencies]
+render = [
+ "gaphor>=3.3.2",
+]
+
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
diff --git a/uv.lock b/uv.lock
index 6bd4dec..9fbefe8 100644
--- a/uv.lock
+++ b/uv.lock
@@ -67,6 +67,18 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/1a/39/47f9197bdd44df24d67ac8893641e16f386c984a0619ef2ee4c51fbbc019/beautifulsoup4-4.14.3-py3-none-any.whl", hash = "sha256:0918bfe44902e6ad8d57732ba310582e98da931428d231a5ecb9e7c703a735bb", size = 107721, upload-time = "2025-11-30T15:08:24.087Z" },
]
+[[package]]
+name = "better-exceptions"
+version = "0.3.3"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "colorama", marker = "sys_platform == 'win32'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/26/d8/30b745b965765c08ee132fd590fca46c31296e8f1a606de0c53cc6b5a68f/better_exceptions-0.3.3.tar.gz", hash = "sha256:e4e6bc18444d5f04e6e894b10381e5e921d3d544240418162c7db57e9eb3453b", size = 30156, upload-time = "2021-01-29T16:48:54.914Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/ed/50/abf6850135f1e95d321a525d0a36e05255a039b3fc118b7d88413e8a8207/better_exceptions-0.3.3-py3-none-any.whl", hash = "sha256:9c70b1c61d5a179b84cd2c9d62c3324b667d74286207343645ed4306fdaad976", size = 11857, upload-time = "2021-01-29T16:48:53.642Z" },
+]
+
[[package]]
name = "brotli"
version = "1.2.0"
@@ -449,6 +461,46 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/8f/d7/9322c609343d929e75e7e5e6255e614fcc67572cfd083959cdef3b7aad79/docutils-0.21.2-py3-none-any.whl", hash = "sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2", size = 587408, upload-time = "2024-04-23T18:57:14.835Z" },
]
+[[package]]
+name = "dulwich"
+version = "1.2.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "urllib3" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/72/0f/46df53e30b03cc8fee9d1bbd7ca624b4d1b579ce2e4efeaa1cb712d119b0/dulwich-1.2.1.tar.gz", hash = "sha256:ba43bfb3a7cad40d9607170561e8c3be42e7083b4b57af89a5f54e01577ff791", size = 1223320, upload-time = "2026-04-29T15:12:19.674Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/2c/c7/a9417106607171af98fe4fedc03693fa406fe74e3eec6b73438c1621dec4/dulwich-1.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:23560643c1cc85737c87985761666a59b35b06a1cbb06db5ba642fa35c67be93", size = 1328929, upload-time = "2026-04-29T15:11:28.388Z" },
+ { url = "https://files.pythonhosted.org/packages/37/b8/83c156e349656de8a7bbc504d94fd318a847fce575caa33b27ffabad7b52/dulwich-1.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:da2fdcc9e4f178b2b4ec1194d8e17d111d572f111fdaa7c6c5a3f46bbe686eaf", size = 1313906, upload-time = "2026-04-29T15:11:30.345Z" },
+ { url = "https://files.pythonhosted.org/packages/03/db/b2706c46f0776a838d13850a70426b1d72268a937cab783c88cbf726c0ba/dulwich-1.2.1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:d0ddda4e165ea14c70e1d0531aa97e527332f1133ea0c65eebaa5afb8786029e", size = 1396200, upload-time = "2026-04-29T15:11:32.166Z" },
+ { url = "https://files.pythonhosted.org/packages/92/87/17e1c3e6846c0eb31da3e5d5f876a91fabe0fa47fc8033d4780b30101723/dulwich-1.2.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:9d0ea88273a7ee6fd3b1d75e231cc6fc614774e19bdd7c1de5df274b4b492dde", size = 1414087, upload-time = "2026-04-29T15:11:33.826Z" },
+ { url = "https://files.pythonhosted.org/packages/64/95/bbafb223fe9e26633f6c143e93d52ae849fe0a246716e0d1ce573be46dd4/dulwich-1.2.1-cp312-cp312-win32.whl", hash = "sha256:5e875729df04991732959f52e29c1de96d00eaf62feddfc60f0b2b08c6e38870", size = 998474, upload-time = "2026-04-29T15:11:35.664Z" },
+ { url = "https://files.pythonhosted.org/packages/ab/9c/34b5d72a1b411d5433a403be2885e87113a122f69a2dcdb4100a298e4fb5/dulwich-1.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:6349b475a1b17b224191a4801687a2486574c9526e00835db2ba9ed081124b11", size = 1013707, upload-time = "2026-04-29T15:11:37.207Z" },
+ { url = "https://files.pythonhosted.org/packages/72/34/c842f2d092a277fee3e465403d525ccff8c2a01862a58431ac45729d0675/dulwich-1.2.1-cp313-cp313-android_21_arm64_v8a.whl", hash = "sha256:3a95dd2649ef3c6e59095d5bd3de08470b3ba908dc41343f5823666a10a326f8", size = 1464426, upload-time = "2026-04-29T15:11:39.219Z" },
+ { url = "https://files.pythonhosted.org/packages/fb/3c/500e3aace2c2c20cbd317218053bbc368ab3e44e183d5eb03038a9fcabe1/dulwich-1.2.1-cp313-cp313-android_21_x86_64.whl", hash = "sha256:11a51dba8454b4c64bf242a918ca4c4300097f7cec84a13164845e638a26f9de", size = 1451868, upload-time = "2026-04-29T15:11:40.903Z" },
+ { url = "https://files.pythonhosted.org/packages/ec/ed/257cedc41ef184c1ac0591bc03009ed4afc1fad4f06c5609cf5d34e9bbe7/dulwich-1.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:121d2d181407cd7bb051922dc3bc00841ca0959b8299880529525db93dfbdca9", size = 1327229, upload-time = "2026-04-29T15:11:42.475Z" },
+ { url = "https://files.pythonhosted.org/packages/fa/2a/520f8aceab83cd54d529c9735d8a091d832da5090716537110642cd55510/dulwich-1.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:96846ef600378739a64e347412c22d5f87f1b67d68526c633b465589335e9387", size = 1312813, upload-time = "2026-04-29T15:11:44.034Z" },
+ { url = "https://files.pythonhosted.org/packages/08/04/f28a380f1aaef5fecf9fb6b4dd261eeb988ab1543211494f647845a7de75/dulwich-1.2.1-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:3412eca24fd79d5413ac7003f8f29b2e01008d7c9d6fce159f77602f70058aea", size = 1396372, upload-time = "2026-04-29T15:11:45.697Z" },
+ { url = "https://files.pythonhosted.org/packages/c6/16/2eac51723d07eb1e4077f2bf0a6034af415bb82be45f4987120fa95de84d/dulwich-1.2.1-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:0b1181f4ae225bbde373fc16279c4e61fdf04aedd30ec21fab387be642567453", size = 1413924, upload-time = "2026-04-29T15:11:47.302Z" },
+ { url = "https://files.pythonhosted.org/packages/a2/14/e0ebb351b1b293f0824150d949ffaab8d15a0e89feb08ec2907d2bf59f1c/dulwich-1.2.1-cp313-cp313-win32.whl", hash = "sha256:e2f14d20aea48dd1d48714d637c70399b140825d930b4f5aa6fbb62199429740", size = 998185, upload-time = "2026-04-29T15:11:49.285Z" },
+ { url = "https://files.pythonhosted.org/packages/de/f4/b17cd27f3cefb8062dc08313d96ee1859673c1b3ec6b2fd2f6528a04dba3/dulwich-1.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:bad94416de9a76ad36dc19f0e65a829b89fc845282bdcc8c43285a4addc1ad2e", size = 1013429, upload-time = "2026-04-29T15:11:51.146Z" },
+ { url = "https://files.pythonhosted.org/packages/0d/39/39515e92676caea97cb2bfa63e37cf9cc579d10421243aa087d0a1d4e67e/dulwich-1.2.1-cp314-cp314-android_24_arm64_v8a.whl", hash = "sha256:74b01505b8e2ba81a715213101c790e0794f5c0b8021f8ce1100131a1e8cfa55", size = 1463937, upload-time = "2026-04-29T15:11:52.682Z" },
+ { url = "https://files.pythonhosted.org/packages/5a/62/30abf318e048f053a5609227952be0773e76203618197e530a4453da161b/dulwich-1.2.1-cp314-cp314-android_24_x86_64.whl", hash = "sha256:011d37afa0922b500c938d6417317e3e6d29d33a6fbaf12b3696fe2a216aa170", size = 1450968, upload-time = "2026-04-29T15:11:54.577Z" },
+ { url = "https://files.pythonhosted.org/packages/6a/a1/de61a118b2df06ba9b87d1751cfa2d2110a0b596a1e46eaed4a18d9c72c7/dulwich-1.2.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:96c160d68173ae55f68bf734e7fddae04ea025f3541996b0b5f28dc2025b42dc", size = 1328562, upload-time = "2026-04-29T15:11:56.157Z" },
+ { url = "https://files.pythonhosted.org/packages/2a/3f/bbb212960d8f1419ba2b4bb5c9d5b4745c4cd9d21f4fb0b58abf302b1e51/dulwich-1.2.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:5a0245dccd69fba9dfe2ed88eff9212bad3e04107bc41676fffbc7f706428cdb", size = 1313625, upload-time = "2026-04-29T15:11:57.982Z" },
+ { url = "https://files.pythonhosted.org/packages/25/af/0c9612199d33a22499be405b610e3a4215183e041ce066ec0cc21ebf017c/dulwich-1.2.1-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:674b21a9025c00a00495fa03a9bb5f2da67a13ee513c74f16bd91c56678d0a77", size = 1396126, upload-time = "2026-04-29T15:11:59.397Z" },
+ { url = "https://files.pythonhosted.org/packages/de/d3/a1217ebacff0d57a7da39f56d31ef7efd076890eb89ef4d23016e8cf8dda/dulwich-1.2.1-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:fd774825510350b614121bfa84d95c6eb08f7e93c0fe7faf8760423a43dbad8b", size = 1413818, upload-time = "2026-04-29T15:12:01.215Z" },
+ { url = "https://files.pythonhosted.org/packages/0b/61/aa3ace71b694b3aff9da14205881760856f00bad189af12530b0c7ebfba7/dulwich-1.2.1-cp314-cp314-win32.whl", hash = "sha256:cf3aa983ca4907c96a0b98b357a3cac7381192786495522a65944da1b284bc02", size = 1005464, upload-time = "2026-04-29T15:12:02.93Z" },
+ { url = "https://files.pythonhosted.org/packages/10/b9/69e3efa49603664f4a84d19847c999f9afb60f8b77889aa9ce189f3c94d0/dulwich-1.2.1-cp314-cp314-win_amd64.whl", hash = "sha256:fb855620d04d0c286058db941c106cfc60bc1e4882ea20b6dd499f3668a1afbe", size = 1021562, upload-time = "2026-04-29T15:12:04.747Z" },
+ { url = "https://files.pythonhosted.org/packages/c1/97/73f1f90d0b1361cb05498838736eed253b8cb84bcdc302abd1662a74a907/dulwich-1.2.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:f944b742962d9933e60863f577491743328b287251ae57a1e7cd84c289acfc23", size = 1327227, upload-time = "2026-04-29T15:12:07.309Z" },
+ { url = "https://files.pythonhosted.org/packages/d8/c6/1c3eb585deedd6c3f7ab0286b6d1d2e6404abfd8f8bcc62a371532a55997/dulwich-1.2.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:4198f79ff04b07f6f6dc81047041f341b35e774cf0207f496b35f34dd2e1bb9c", size = 1312021, upload-time = "2026-04-29T15:12:09.507Z" },
+ { url = "https://files.pythonhosted.org/packages/31/59/2e5405851a0bdfe6f6ea947210ddb6f0784727fa6126dd27152efc1d8b32/dulwich-1.2.1-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:41dfe1b1cb0f0202102e90f9f47f54458f27da6b746939a6ed85362b68e3e3a5", size = 1393001, upload-time = "2026-04-29T15:12:11.165Z" },
+ { url = "https://files.pythonhosted.org/packages/3f/15/78d2fefa8bf05168b11c61903d9c8eff10844b1d5cd0d9511d44e58599c2/dulwich-1.2.1-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:a65527eda5f6a463168c6338d781b05d325ba85dc05c6f8217ddd8454911bb53", size = 1414765, upload-time = "2026-04-29T15:12:13.098Z" },
+ { url = "https://files.pythonhosted.org/packages/11/4a/d9a45e4ab08088053d6571e80e27e1b18fb2d4213d34e6e231e2840441ec/dulwich-1.2.1-cp314-cp314t-win32.whl", hash = "sha256:04215befa00c82accda97d0e3ce760d9344a67f081d5b92ade8ad00007fb38d8", size = 1003379, upload-time = "2026-04-29T15:12:14.765Z" },
+ { url = "https://files.pythonhosted.org/packages/2a/9c/8f5b9f142cabc47f881155b33330110290e501ffcae85745b01ce19b56dd/dulwich-1.2.1-cp314-cp314t-win_amd64.whl", hash = "sha256:3183b7db09282842459fbeb37e77e498c8339e6db4d0cb6b5d3685c97ce79dac", size = 1020397, upload-time = "2026-04-29T15:12:16.373Z" },
+ { url = "https://files.pythonhosted.org/packages/08/55/f5470846eb8bdbf204f1c1b47c3d25f542fe4a0134f027c672498fb6e0d3/dulwich-1.2.1-py3-none-any.whl", hash = "sha256:1961e0b6c0b1f2920f4ab05821652d8eb12f19ddb5a4c167c385902391c08dd3", size = 674611, upload-time = "2026-04-29T15:12:18.177Z" },
+]
+
[[package]]
name = "execnet"
version = "2.1.2"
@@ -522,6 +574,52 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/f4/b2/50e9b292b5cac13e9e81272c7171301abc753a60460d21505b606e15cf21/furo-2025.12.19-py3-none-any.whl", hash = "sha256:bb0ead5309f9500130665a26bee87693c41ce4dbdff864dbfb6b0dae4673d24f", size = 339262, upload-time = "2025-12-19T17:34:38.905Z" },
]
+[[package]]
+name = "gaphas"
+version = "5.1.2"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "pycairo" },
+ { name = "pygobject" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/7d/cf/2699e0beff737dd2d51e5390cdf44cf243ae2d75130ffa5029102e6f3995/gaphas-5.1.2.tar.gz", hash = "sha256:5dd6b05931f3856a96b623269b50187231b4abf7b9849f48f7eec52898563696", size = 59989, upload-time = "2025-12-27T12:06:09.078Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/71/c2/03e1d69be2fe316250a3e2116a9ff811313b074d069162da9cbff5026c62/gaphas-5.1.2-py3-none-any.whl", hash = "sha256:e2f5fcbab3d8278eacfb13529551608ecfdc45ac580fd8f22dff5964efb5b1d0", size = 73708, upload-time = "2025-12-27T12:06:07.629Z" },
+]
+
+[[package]]
+name = "gaphor"
+version = "3.3.2"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "babel" },
+ { name = "better-exceptions" },
+ { name = "defusedxml" },
+ { name = "dulwich" },
+ { name = "gaphas" },
+ { name = "generic" },
+ { name = "jedi" },
+ { name = "pillow" },
+ { name = "pycairo" },
+ { name = "pydot" },
+ { name = "pygobject" },
+ { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" },
+ { name = "tinycss2" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/2e/9f/33c39e6c936a167a299f99a1ac87b0a487974ffd8b8eb33398e20b5566f9/gaphor-3.3.2.tar.gz", hash = "sha256:dc4cad4df94e861584923091e5eab45fc13e4c84d3bd0bd8606b0f2b3ba2bfba", size = 800828, upload-time = "2026-05-02T19:28:42.231Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/84/44/8d54ef9e7674b91d3d41ed8e9ab2d7a2d5ef7d7f72d3a2120bb6f428b508/gaphor-3.3.2-py3-none-any.whl", hash = "sha256:6a8a84bbce429677af664c9857d9433ea66e782015d7a53f679f7e7c4bf208d0", size = 1070767, upload-time = "2026-05-02T19:28:40.014Z" },
+]
+
+[[package]]
+name = "generic"
+version = "1.1.7"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/52/a6/b1d3ed364b780137f1fc5e85737f42d2d8c6fda5b254c03cb916b7b7715e/generic-1.1.7.tar.gz", hash = "sha256:d14dae6628542eb084434588d350756c88f2f30c7e2348ad5fcfa01fbdb7ceed", size = 8225, upload-time = "2026-03-09T14:39:18.035Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/68/6d/459ded13938c2f2dadf71e107f78001d30b8466fda3acac27a55934b246e/generic-1.1.7-py3-none-any.whl", hash = "sha256:9048a52debd50e6d7671bb44826567a9c81d2e8302253179adfb8054ab415077", size = 9815, upload-time = "2026-03-09T14:39:16.301Z" },
+]
+
[[package]]
name = "gitignore-parser"
version = "0.1.13"
@@ -564,6 +662,18 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" },
]
+[[package]]
+name = "jedi"
+version = "0.19.2"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "parso" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/72/3a/79a912fbd4d8dd6fbb02bf69afd3bb72cf0c729bb3063c6f4498603db17a/jedi-0.19.2.tar.gz", hash = "sha256:4770dc3de41bde3966b02eb84fbcf557fb33cce26ad23da12c742fb50ecb11f0", size = 1231287, upload-time = "2024-11-11T01:41:42.873Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl", hash = "sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9", size = 1572278, upload-time = "2024-11-11T01:41:40.175Z" },
+]
+
[[package]]
name = "jinja2"
version = "3.1.6"
@@ -1024,6 +1134,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", size = 100195, upload-time = "2026-04-24T20:15:22.081Z" },
]
+[[package]]
+name = "parso"
+version = "0.8.7"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/30/4b/90c937815137d43ce71ba043cd3566221e9df6b9c805f24b5d138c9d40a7/parso-0.8.7.tar.gz", hash = "sha256:eaaac4c9fdd5e9e8852dc778d2d7405897ec510f2a298071453e5e3a07914bb1", size = 401824, upload-time = "2026-05-01T23:13:02.138Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/99/5d/8268b644392ee874ee82a635cd0df1773de230bde356c38de28e298392cc/parso-0.8.7-py2.py3-none-any.whl", hash = "sha256:a8926eb2a1b915486941fdbd31e86a4baf88fe8c210f25f2f35ecec5b574ca1c", size = 107025, upload-time = "2026-05-01T23:12:58.867Z" },
+]
+
[[package]]
name = "pillow"
version = "12.2.0"
@@ -1158,6 +1277,25 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/8c/c7/7bb2e321574b10df20cbde462a94e2b71d05f9bbda251ef27d104668306a/psutil-7.2.2-cp37-abi3-win_arm64.whl", hash = "sha256:8c233660f575a5a89e6d4cb65d9f938126312bca76d8fe087b947b3a1aaac9ee", size = 134617, upload-time = "2026-01-28T18:15:36.514Z" },
]
+[[package]]
+name = "pycairo"
+version = "1.29.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/22/d9/1728840a22a4ef8a8f479b9156aa2943cd98c3907accd3849fb0d5f82bfd/pycairo-1.29.0.tar.gz", hash = "sha256:f3f7fde97325cae80224c09f12564ef58d0d0f655da0e3b040f5807bd5bd3142", size = 665871, upload-time = "2025-11-11T19:13:01.584Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/f6/28/6363087b9e60af031398a6ee5c248639eefc6cc742884fa2789411b1f73b/pycairo-1.29.0-cp312-cp312-win32.whl", hash = "sha256:91bcd7b5835764c616a615d9948a9afea29237b34d2ed013526807c3d79bb1d0", size = 751486, upload-time = "2025-11-11T19:11:54.451Z" },
+ { url = "https://files.pythonhosted.org/packages/3a/d2/d146f1dd4ef81007686ac52231dd8f15ad54cf0aa432adaefc825475f286/pycairo-1.29.0-cp312-cp312-win_amd64.whl", hash = "sha256:3f01c3b5e49ef9411fff6bc7db1e765f542dc1c9cfed4542958a5afa3a8b8e76", size = 845383, upload-time = "2025-11-11T19:12:01.551Z" },
+ { url = "https://files.pythonhosted.org/packages/01/16/6e6f33bb79ec4a527c9e633915c16dc55a60be26b31118dbd0d5859e8c51/pycairo-1.29.0-cp312-cp312-win_arm64.whl", hash = "sha256:eafe3d2076f3533535ad4a361fa0754e0ee66b90e548a3a0f558fed00b1248f2", size = 694518, upload-time = "2025-11-11T19:12:06.561Z" },
+ { url = "https://files.pythonhosted.org/packages/f0/21/3f477dc318dd4e84a5ae6301e67284199d7e5a2384f3063714041086b65d/pycairo-1.29.0-cp313-cp313-win32.whl", hash = "sha256:3eb382a4141591807073274522f7aecab9e8fa2f14feafd11ac03a13a58141d7", size = 750949, upload-time = "2025-11-11T19:12:12.198Z" },
+ { url = "https://files.pythonhosted.org/packages/43/34/7d27a333c558d6ac16dbc12a35061d389735e99e494ee4effa4ec6d99bed/pycairo-1.29.0-cp313-cp313-win_amd64.whl", hash = "sha256:91114e4b3fbf4287c2b0788f83e1f566ce031bda49cf1c3c3c19c3e986e95c38", size = 844149, upload-time = "2025-11-11T19:12:19.171Z" },
+ { url = "https://files.pythonhosted.org/packages/15/43/e782131e23df69e5c8e631a016ed84f94bbc4981bf6411079f57af730a23/pycairo-1.29.0-cp313-cp313-win_arm64.whl", hash = "sha256:09b7f69a5ff6881e151354ea092137b97b0b1f0b2ab4eb81c92a02cc4a08e335", size = 693595, upload-time = "2025-11-11T19:12:23.445Z" },
+ { url = "https://files.pythonhosted.org/packages/2d/fa/87eaeeb9d53344c769839d7b2854db7ff2cd596211e00dd1b702eeb1838f/pycairo-1.29.0-cp314-cp314-win32.whl", hash = "sha256:69e2a7968a3fbb839736257bae153f547bca787113cc8d21e9e08ca4526e0b6b", size = 767198, upload-time = "2025-11-11T19:12:42.336Z" },
+ { url = "https://files.pythonhosted.org/packages/3c/90/3564d0f64d0a00926ab863dc3c4a129b1065133128e96900772e1c4421f8/pycairo-1.29.0-cp314-cp314-win_amd64.whl", hash = "sha256:e91243437a21cc4c67c401eff4433eadc45745275fa3ade1a0d877e50ffb90da", size = 871579, upload-time = "2025-11-11T19:12:48.982Z" },
+ { url = "https://files.pythonhosted.org/packages/5e/91/93632b6ba12ad69c61991e3208bde88486fdfc152be8cfdd13444e9bc650/pycairo-1.29.0-cp314-cp314-win_arm64.whl", hash = "sha256:b72200ea0e5f73ae4c788cd2028a750062221385eb0e6d8f1ecc714d0b4fdf82", size = 719537, upload-time = "2025-11-11T19:12:55.016Z" },
+ { url = "https://files.pythonhosted.org/packages/93/23/37053c039f8d3b9b5017af9bc64d27b680c48a898d48b72e6d6583cf0155/pycairo-1.29.0-cp314-cp314t-win_amd64.whl", hash = "sha256:5e45fce6185f553e79e4ef1722b8e98e6cde9900dbc48cb2637a9ccba86f627a", size = 874015, upload-time = "2025-11-11T19:12:28.47Z" },
+ { url = "https://files.pythonhosted.org/packages/d7/54/123f6239685f5f3f2edc123f1e38d2eefacebee18cf3c532d2f4bd51d0ef/pycairo-1.29.0-cp314-cp314t-win_arm64.whl", hash = "sha256:caba0837a4b40d47c8dfb0f24cccc12c7831e3dd450837f2a356c75f21ce5a15", size = 721404, upload-time = "2025-11-11T19:12:36.919Z" },
+]
+
[[package]]
name = "pycparser"
version = "3.0"
@@ -1167,6 +1305,18 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" },
]
+[[package]]
+name = "pydot"
+version = "4.0.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "pyparsing" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/50/35/b17cb89ff865484c6a20ef46bf9d95a5f07328292578de0b295f4a6beec2/pydot-4.0.1.tar.gz", hash = "sha256:c2148f681c4a33e08bf0e26a9e5f8e4099a82e0e2a068098f32ce86577364ad5", size = 162594, upload-time = "2025-06-17T20:09:56.454Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/7e/32/a7125fb28c4261a627f999d5fb4afff25b523800faed2c30979949d6facd/pydot-4.0.1-py3-none-any.whl", hash = "sha256:869c0efadd2708c0be1f916eb669f3d664ca684bc57ffb7ecc08e70d5e93fee6", size = 37087, upload-time = "2025-06-17T20:09:55.25Z" },
+]
+
[[package]]
name = "pydyf"
version = "0.12.1"
@@ -1185,6 +1335,44 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" },
]
+[[package]]
+name = "pygobject"
+version = "3.56.2"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "pycairo" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/a2/80/09247a2be28af2c2240132a0af6c1005a2b1d089242b13a2cd782d2de8d7/pygobject-3.56.2.tar.gz", hash = "sha256:b816098969544081de9eecedb94ad6ac59c77e4d571fe7051f18bebcec074313", size = 1409059, upload-time = "2026-03-25T16:14:04.008Z" }
+
+[[package]]
+name = "pyobjc-core"
+version = "12.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/b8/b6/d5612eb40be4fd5ef88c259339e6313f46ba67577a95d86c3470b951fce0/pyobjc_core-12.1.tar.gz", hash = "sha256:2bb3903f5387f72422145e1466b3ac3f7f0ef2e9960afa9bcd8961c5cbf8bd21", size = 1000532, upload-time = "2025-11-14T10:08:28.292Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/64/5a/6b15e499de73050f4a2c88fff664ae154307d25dc04da8fb38998a428358/pyobjc_core-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:818bcc6723561f207e5b5453efe9703f34bc8781d11ce9b8be286bb415eb4962", size = 678335, upload-time = "2025-11-14T09:32:20.107Z" },
+ { url = "https://files.pythonhosted.org/packages/f4/d2/29e5e536adc07bc3d33dd09f3f7cf844bf7b4981820dc2a91dd810f3c782/pyobjc_core-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:01c0cf500596f03e21c23aef9b5f326b9fb1f8f118cf0d8b66749b6cf4cbb37a", size = 677370, upload-time = "2025-11-14T09:33:05.273Z" },
+ { url = "https://files.pythonhosted.org/packages/1b/f0/4b4ed8924cd04e425f2a07269943018d43949afad1c348c3ed4d9d032787/pyobjc_core-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:177aaca84bb369a483e4961186704f64b2697708046745f8167e818d968c88fc", size = 719586, upload-time = "2025-11-14T09:33:53.302Z" },
+ { url = "https://files.pythonhosted.org/packages/25/98/9f4ed07162de69603144ff480be35cd021808faa7f730d082b92f7ebf2b5/pyobjc_core-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:844515f5d86395b979d02152576e7dee9cc679acc0b32dc626ef5bda315eaa43", size = 670164, upload-time = "2025-11-14T09:34:37.458Z" },
+ { url = "https://files.pythonhosted.org/packages/62/50/dc076965c96c7f0de25c0a32b7f8aa98133ed244deaeeacfc758783f1f30/pyobjc_core-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:453b191df1a4b80e756445b935491b974714456ae2cbae816840bd96f86db882", size = 712204, upload-time = "2025-11-14T09:35:24.148Z" },
+]
+
+[[package]]
+name = "pyobjc-framework-cocoa"
+version = "12.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "pyobjc-core" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/02/a3/16ca9a15e77c061a9250afbae2eae26f2e1579eb8ca9462ae2d2c71e1169/pyobjc_framework_cocoa-12.1.tar.gz", hash = "sha256:5556c87db95711b985d5efdaaf01c917ddd41d148b1e52a0c66b1a2e2c5c1640", size = 2772191, upload-time = "2025-11-14T10:13:02.069Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/95/bf/ee4f27ec3920d5c6fc63c63e797c5b2cc4e20fe439217085d01ea5b63856/pyobjc_framework_cocoa-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:547c182837214b7ec4796dac5aee3aa25abc665757b75d7f44f83c994bcb0858", size = 384590, upload-time = "2025-11-14T09:41:17.336Z" },
+ { url = "https://files.pythonhosted.org/packages/ad/31/0c2e734165abb46215797bd830c4bdcb780b699854b15f2b6240515edcc6/pyobjc_framework_cocoa-12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:5a3dcd491cacc2f5a197142b3c556d8aafa3963011110102a093349017705118", size = 384689, upload-time = "2025-11-14T09:41:41.478Z" },
+ { url = "https://files.pythonhosted.org/packages/23/3b/b9f61be7b9f9b4e0a6db18b3c35c4c4d589f2d04e963e2174d38c6555a92/pyobjc_framework_cocoa-12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:914b74328c22d8ca261d78c23ef2befc29776e0b85555973927b338c5734ca44", size = 388843, upload-time = "2025-11-14T09:42:05.719Z" },
+ { url = "https://files.pythonhosted.org/packages/59/bb/f777cc9e775fc7dae77b569254570fe46eb842516b3e4fe383ab49eab598/pyobjc_framework_cocoa-12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:03342a60fc0015bcdf9b93ac0b4f457d3938e9ef761b28df9564c91a14f0129a", size = 384932, upload-time = "2025-11-14T09:42:29.771Z" },
+ { url = "https://files.pythonhosted.org/packages/58/27/b457b7b37089cad692c8aada90119162dfb4c4a16f513b79a8b2b022b33b/pyobjc_framework_cocoa-12.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:6ba1dc1bfa4da42d04e93d2363491275fb2e2be5c20790e561c8a9e09b8cf2cc", size = 388970, upload-time = "2025-11-14T09:42:53.964Z" },
+]
+
[[package]]
name = "pyparsing"
version = "3.3.2"
@@ -1682,9 +1870,15 @@ dependencies = [
{ name = "ubt-sphinx" },
]
+[package.optional-dependencies]
+render = [
+ { name = "gaphor" },
+]
+
[package.metadata]
requires-dist = [
{ name = "furo", specifier = ">=2024.8.6" },
+ { name = "gaphor", marker = "extra == 'render'", specifier = ">=3.3.2" },
{ name = "sphinx", extras = ["test"], specifier = ">=8.2.3" },
{ name = "sphinx-codelinks", specifier = ">=1.2.0" },
{ name = "sphinx-design", specifier = ">=0.6.1" },
@@ -1695,6 +1889,7 @@ requires-dist = [
{ name = "sphinxcontrib-plantuml", specifier = ">=0.30" },
{ name = "ubt-sphinx", specifier = "==0.7.1" },
]
+provides-extras = ["render"]
[[package]]
name = "sphinx-preview"