matplotvite 0.6.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- matplotvite-0.6.0/.github/dependabot.yml +21 -0
- matplotvite-0.6.0/.github/requirements-ci.txt +13 -0
- matplotvite-0.6.0/.github/workflows/ci.yml +366 -0
- matplotvite-0.6.0/.github/workflows/pages.yml +84 -0
- matplotvite-0.6.0/.github/workflows/publish.yml +146 -0
- matplotvite-0.6.0/.gitignore +21 -0
- matplotvite-0.6.0/.jules/bolt.md +5 -0
- matplotvite-0.6.0/.jules/sentinel.md +6 -0
- matplotvite-0.6.0/Cargo.lock +1778 -0
- matplotvite-0.6.0/Cargo.toml +36 -0
- matplotvite-0.6.0/LICENSE +12 -0
- matplotvite-0.6.0/LICENSE-APACHE +201 -0
- matplotvite-0.6.0/LICENSE-MIT +21 -0
- matplotvite-0.6.0/PKG-INFO +477 -0
- matplotvite-0.6.0/README.md +443 -0
- matplotvite-0.6.0/ROADMAP.md +306 -0
- matplotvite-0.6.0/TRACE.md +222 -0
- matplotvite-0.6.0/architecture.md +219 -0
- matplotvite-0.6.0/assets/fonts/DejaVuSans-Bold.ttf +0 -0
- matplotvite-0.6.0/assets/fonts/DejaVuSans.ttf +0 -0
- matplotvite-0.6.0/assets/fonts/Geist-Regular.ttf +0 -0
- matplotvite-0.6.0/assets/fonts/GeistMono-Regular.ttf +0 -0
- matplotvite-0.6.0/assets/fonts/GeistSerif-Regular.ttf +0 -0
- matplotvite-0.6.0/assets/fonts/LICENSE_DEJAVU.txt +78 -0
- matplotvite-0.6.0/assets/fonts/LICENSE_GEIST_OFL.txt +93 -0
- matplotvite-0.6.0/benches/criterion_baseline.json +25 -0
- matplotvite-0.6.0/benches/render.rs +250 -0
- matplotvite-0.6.0/examples/__init__.py +1 -0
- matplotvite-0.6.0/examples/samples.py +890 -0
- matplotvite-0.6.0/prek.toml +38 -0
- matplotvite-0.6.0/pyproject.toml +59 -0
- matplotvite-0.6.0/python/matplotvite/__init__.py +8013 -0
- matplotvite-0.6.0/python/matplotvite/__init__.pyi +377 -0
- matplotvite-0.6.0/python/matplotvite/_cmaps.py +3657 -0
- matplotvite-0.6.0/python/matplotvite/_colors.py +152 -0
- matplotvite-0.6.0/python/matplotvite/mpl_toolkits/__init__.py +5 -0
- matplotvite-0.6.0/python/matplotvite/mpl_toolkits/axes_grid1/__init__.py +5 -0
- matplotvite-0.6.0/python/matplotvite/mpl_toolkits/axes_grid1/inset_locator.py +165 -0
- matplotvite-0.6.0/python/matplotvite/py.typed +0 -0
- matplotvite-0.6.0/review.md +260 -0
- matplotvite-0.6.0/src/bench.rs +34 -0
- matplotvite-0.6.0/src/cmap_data.rs +21193 -0
- matplotvite-0.6.0/src/color.rs +81 -0
- matplotvite-0.6.0/src/draw.rs +3162 -0
- matplotvite-0.6.0/src/export.rs +709 -0
- matplotvite-0.6.0/src/fonts.rs +105 -0
- matplotvite-0.6.0/src/image.rs +210 -0
- matplotvite-0.6.0/src/layout.rs +328 -0
- matplotvite-0.6.0/src/lib.rs +279 -0
- matplotvite-0.6.0/src/model.rs +646 -0
- matplotvite-0.6.0/src/raster.rs +343 -0
- matplotvite-0.6.0/src/scale.rs +211 -0
- matplotvite-0.6.0/tests/conftest.py +26 -0
- matplotvite-0.6.0/tests/png_utils.py +50 -0
- matplotvite-0.6.0/tests/test_benchmark.py +143 -0
- matplotvite-0.6.0/tests/test_commit_message.py +38 -0
- matplotvite-0.6.0/tests/test_gallery_smoke.py +71 -0
- matplotvite-0.6.0/tests/test_gallery_summary.py +43 -0
- matplotvite-0.6.0/tests/test_harvest_matplotlib.py +433 -0
- matplotvite-0.6.0/tests/test_harvest_seaborn.py +79 -0
- matplotvite-0.6.0/tests/test_plt.py +3794 -0
- matplotvite-0.6.0/tests/test_samples.py +148 -0
- matplotvite-0.6.0/tests/test_visual_compare.py +92 -0
- matplotvite-0.6.0/tests/test_wheel.py +83 -0
- matplotvite-0.6.0/tools/benchmark.py +622 -0
- matplotvite-0.6.0/tools/benchmark_baseline.json +212 -0
- matplotvite-0.6.0/tools/benchmark_guard.py +86 -0
- matplotvite-0.6.0/tools/benchmarks_data.json +212 -0
- matplotvite-0.6.0/tools/build_pages.py +1978 -0
- matplotvite-0.6.0/tools/check_commit_message.py +45 -0
- matplotvite-0.6.0/tools/criterion_guard.py +85 -0
- matplotvite-0.6.0/tools/gallery_summary.py +71 -0
- matplotvite-0.6.0/tools/generate_colormaps.py +111 -0
- matplotvite-0.6.0/tools/harvest_matplotlib.py +904 -0
- matplotvite-0.6.0/tools/harvest_seaborn.py +206 -0
- matplotvite-0.6.0/tools/security_audit.py +55 -0
- matplotvite-0.6.0/tools/trace_baseline.json +364 -0
- matplotvite-0.6.0/tools/trace_unsupported.py +209 -0
- matplotvite-0.6.0/tools/visual_compare.py +263 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# PDF output assembles svg2pdf/usvg chunks by hand. When either crate changes,
|
|
2
|
+
# run the tight-PDF tests in tests/test_plt.py before merging the update.
|
|
3
|
+
version: 2
|
|
4
|
+
updates:
|
|
5
|
+
- package-ecosystem: "cargo"
|
|
6
|
+
directory: "/"
|
|
7
|
+
schedule:
|
|
8
|
+
interval: "weekly"
|
|
9
|
+
ignore:
|
|
10
|
+
# svg2pdf pins pdf-writer's major version; update them together.
|
|
11
|
+
- dependency-name: "pdf-writer"
|
|
12
|
+
|
|
13
|
+
- package-ecosystem: "pip"
|
|
14
|
+
directory: "/"
|
|
15
|
+
schedule:
|
|
16
|
+
interval: "weekly"
|
|
17
|
+
|
|
18
|
+
- package-ecosystem: "github-actions"
|
|
19
|
+
directory: "/"
|
|
20
|
+
schedule:
|
|
21
|
+
interval: "weekly"
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Constraints for CI's ad-hoc pip installs. Keep these exact-version pins in
|
|
2
|
+
# sync with the supported Python matrix (3.9 through 3.14).
|
|
3
|
+
maturin==1.16.0
|
|
4
|
+
numpy==2.0.2; python_version < "3.12"
|
|
5
|
+
numpy==2.5.4; python_version >= "3.12"
|
|
6
|
+
pandas==2.2.3; python_version < "3.12"
|
|
7
|
+
pandas==3.1.1; python_version >= "3.12"
|
|
8
|
+
pytest==8.4.2
|
|
9
|
+
matplotlib==3.11.2
|
|
10
|
+
bandit==1.9.4
|
|
11
|
+
pip-audit==2.10.1
|
|
12
|
+
pypdfium2==4.30.0
|
|
13
|
+
cairosvg==2.7.1
|
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
pull_request:
|
|
6
|
+
|
|
7
|
+
permissions:
|
|
8
|
+
contents: read
|
|
9
|
+
|
|
10
|
+
concurrency:
|
|
11
|
+
group: ${{ github.workflow }}-${{ github.ref }}
|
|
12
|
+
cancel-in-progress: true
|
|
13
|
+
|
|
14
|
+
jobs:
|
|
15
|
+
prek:
|
|
16
|
+
name: prek checks
|
|
17
|
+
runs-on: ubuntu-latest
|
|
18
|
+
steps:
|
|
19
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
20
|
+
- uses: dtolnay/rust-toolchain@683b588c487a69d01f6c6ddf40f036ff682cc2c8 # master
|
|
21
|
+
with:
|
|
22
|
+
toolchain: stable
|
|
23
|
+
components: rustfmt, clippy
|
|
24
|
+
- uses: Swatinem/rust-cache@d1527c8f67ef9af357290475d4af4b0fab8f12e0 # v2.9.3
|
|
25
|
+
- uses: j178/prek-action@44924fe9ef4a6f88253c4b6b1bf11c689d3ce53d # v3.1.0
|
|
26
|
+
|
|
27
|
+
lint:
|
|
28
|
+
name: python lint
|
|
29
|
+
runs-on: ubuntu-latest
|
|
30
|
+
steps:
|
|
31
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
32
|
+
- uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
|
|
33
|
+
with:
|
|
34
|
+
python-version: "3.13"
|
|
35
|
+
- run: pip install ruff==0.17.4
|
|
36
|
+
- run: ruff check python tests tools examples
|
|
37
|
+
|
|
38
|
+
security:
|
|
39
|
+
name: security scan
|
|
40
|
+
runs-on: ubuntu-latest
|
|
41
|
+
steps:
|
|
42
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
43
|
+
- uses: dtolnay/rust-toolchain@683b588c487a69d01f6c6ddf40f036ff682cc2c8 # master
|
|
44
|
+
with:
|
|
45
|
+
toolchain: stable
|
|
46
|
+
- uses: taiki-e/install-action@3a3daafd3b7f23b8f65d9f5ba2db203fb59b79e2 # v2.90.2
|
|
47
|
+
with:
|
|
48
|
+
tool: cargo-audit
|
|
49
|
+
- uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
|
|
50
|
+
with:
|
|
51
|
+
python-version: "3.13"
|
|
52
|
+
- name: Run Bandit static analysis
|
|
53
|
+
run: |
|
|
54
|
+
python -m pip install -c .github/requirements-ci.txt "bandit>=1.9" numpy
|
|
55
|
+
python -m bandit -q -r python/matplotvite
|
|
56
|
+
|
|
57
|
+
- name: Run dependency and static security checks
|
|
58
|
+
run: |
|
|
59
|
+
python -m pip install -c .github/requirements-ci.txt "pip-audit>=2.9"
|
|
60
|
+
python tools/security_audit.py
|
|
61
|
+
|
|
62
|
+
test:
|
|
63
|
+
name: test (python ${{ matrix.python }})
|
|
64
|
+
runs-on: ${{ matrix.os }}
|
|
65
|
+
strategy:
|
|
66
|
+
fail-fast: false
|
|
67
|
+
matrix:
|
|
68
|
+
os: [ubuntu-latest]
|
|
69
|
+
python: ["3.10", "3.11", "3.12", "3.13", "3.14"]
|
|
70
|
+
include:
|
|
71
|
+
# ubuntu-latest (26.04) has no python-versions build for 3.9 yet, so the
|
|
72
|
+
# oldest supported interpreter runs on 22.04 instead.
|
|
73
|
+
- os: ubuntu-22.04
|
|
74
|
+
python: "3.9"
|
|
75
|
+
steps:
|
|
76
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
77
|
+
- uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
|
|
78
|
+
with:
|
|
79
|
+
python-version: ${{ matrix.python }}
|
|
80
|
+
- uses: dtolnay/rust-toolchain@683b588c487a69d01f6c6ddf40f036ff682cc2c8 # master
|
|
81
|
+
with:
|
|
82
|
+
toolchain: stable
|
|
83
|
+
- uses: Swatinem/rust-cache@d1527c8f67ef9af357290475d4af4b0fab8f12e0 # v2.9.3
|
|
84
|
+
- name: Build & Test
|
|
85
|
+
run: |
|
|
86
|
+
python -m venv .venv
|
|
87
|
+
. .venv/bin/activate
|
|
88
|
+
pip install -c .github/requirements-ci.txt maturin numpy pandas pytest
|
|
89
|
+
maturin develop --release
|
|
90
|
+
pytest -q
|
|
91
|
+
|
|
92
|
+
wheels:
|
|
93
|
+
name: wheel (${{ matrix.name }})
|
|
94
|
+
runs-on: ${{ matrix.os }}
|
|
95
|
+
strategy:
|
|
96
|
+
fail-fast: false
|
|
97
|
+
matrix:
|
|
98
|
+
include:
|
|
99
|
+
- name: linux-x86_64
|
|
100
|
+
os: ubuntu-latest
|
|
101
|
+
target: x86_64-unknown-linux-gnu
|
|
102
|
+
manylinux: auto
|
|
103
|
+
interpreter: python3.13
|
|
104
|
+
- name: linux-aarch64
|
|
105
|
+
os: ubuntu-24.04-arm
|
|
106
|
+
target: aarch64-unknown-linux-gnu
|
|
107
|
+
manylinux: auto
|
|
108
|
+
interpreter: python3.13
|
|
109
|
+
- name: macos-arm64
|
|
110
|
+
os: macos-latest
|
|
111
|
+
target: aarch64-apple-darwin
|
|
112
|
+
interpreter: python
|
|
113
|
+
- name: macos-x86_64
|
|
114
|
+
os: macos-15-intel
|
|
115
|
+
target: x86_64-apple-darwin
|
|
116
|
+
interpreter: python
|
|
117
|
+
- name: windows-x86_64
|
|
118
|
+
os: windows-latest
|
|
119
|
+
target: x86_64-pc-windows-msvc
|
|
120
|
+
interpreter: python
|
|
121
|
+
- name: windows-arm64
|
|
122
|
+
os: windows-11-arm
|
|
123
|
+
target: aarch64-pc-windows-msvc
|
|
124
|
+
interpreter: python
|
|
125
|
+
steps:
|
|
126
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
127
|
+
- uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
|
|
128
|
+
with:
|
|
129
|
+
python-version: "3.13"
|
|
130
|
+
- uses: PyO3/maturin-action@e83996d129638aa358a18fbd1dfb82f0b0fb5d3b # v1.51.0
|
|
131
|
+
with:
|
|
132
|
+
target: ${{ matrix.target }}
|
|
133
|
+
manylinux: ${{ matrix.manylinux }}
|
|
134
|
+
# The manylinux container does not see setup-python's interpreter, so
|
|
135
|
+
# select the matching container Python explicitly. The wheel is built
|
|
136
|
+
# against the stable ABI (cp39-abi3) and works on every version above 3.9.
|
|
137
|
+
args: --release --out dist --interpreter ${{ matrix.interpreter }}
|
|
138
|
+
- name: Build the sdist
|
|
139
|
+
# One matrix entry builds the source distribution: the tarball is
|
|
140
|
+
# platform independent, so every platform would only duplicate it.
|
|
141
|
+
if: matrix.name == 'linux-x86_64'
|
|
142
|
+
uses: PyO3/maturin-action@e83996d129638aa358a18fbd1dfb82f0b0fb5d3b # v1.51.0
|
|
143
|
+
with:
|
|
144
|
+
command: sdist
|
|
145
|
+
args: --out dist
|
|
146
|
+
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
|
147
|
+
with:
|
|
148
|
+
name: wheel-${{ matrix.name }}
|
|
149
|
+
path: dist/*.whl
|
|
150
|
+
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
|
151
|
+
if: matrix.name == 'linux-x86_64'
|
|
152
|
+
with:
|
|
153
|
+
name: sdist
|
|
154
|
+
path: dist/*.tar.gz
|
|
155
|
+
|
|
156
|
+
wheel-tests:
|
|
157
|
+
name: wheel test (py${{ matrix.python }}, ${{ matrix.os }})
|
|
158
|
+
needs: wheels
|
|
159
|
+
runs-on: ${{ matrix.os }}
|
|
160
|
+
# The wheel is stable-ABI, so the per-interpreter coverage comes from the
|
|
161
|
+
# Linux matrix. Hosted macOS runners are scarce: asking for one per Python
|
|
162
|
+
# version regularly left jobs queued until GitHub cancelled them ~15 minutes
|
|
163
|
+
# later, so macOS checks the newest interpreter only. No timeout setting
|
|
164
|
+
# covers that queue wait; reducing the number of macOS jobs is the fix.
|
|
165
|
+
timeout-minutes: 30
|
|
166
|
+
strategy:
|
|
167
|
+
fail-fast: false
|
|
168
|
+
matrix:
|
|
169
|
+
os: [ubuntu-latest, macos-latest, windows-latest]
|
|
170
|
+
python: ["3.10", "3.11", "3.12", "3.13", "3.14"]
|
|
171
|
+
exclude:
|
|
172
|
+
# Python 3.9 and 3.10 are no longer available on current hosted
|
|
173
|
+
# macOS/Windows images; the stable-ABI wheel still supports them.
|
|
174
|
+
- os: macos-latest
|
|
175
|
+
python: "3.10"
|
|
176
|
+
- os: macos-latest
|
|
177
|
+
python: "3.11"
|
|
178
|
+
- os: macos-latest
|
|
179
|
+
python: "3.12"
|
|
180
|
+
- os: macos-latest
|
|
181
|
+
python: "3.13"
|
|
182
|
+
- os: windows-latest
|
|
183
|
+
python: "3.10"
|
|
184
|
+
include:
|
|
185
|
+
- os: ubuntu-22.04
|
|
186
|
+
python: "3.9"
|
|
187
|
+
steps:
|
|
188
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
189
|
+
- uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
|
|
190
|
+
with:
|
|
191
|
+
python-version: ${{ matrix.python }}
|
|
192
|
+
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
|
|
193
|
+
with:
|
|
194
|
+
pattern: wheel-*
|
|
195
|
+
path: dist
|
|
196
|
+
merge-multiple: true
|
|
197
|
+
- name: Install the built wheel
|
|
198
|
+
run: |
|
|
199
|
+
python -m pip install -c .github/requirements-ci.txt numpy pandas pytest
|
|
200
|
+
python -m pip install --no-index --find-links dist matplotvite
|
|
201
|
+
- name: Run the test suite against the installed wheel
|
|
202
|
+
# The repo root is not on sys.path ahead of site-packages, so these
|
|
203
|
+
# imports resolve to the installed wheel rather than the source tree.
|
|
204
|
+
# Keep this the same suite the `test` job runs, so a wheel-only break
|
|
205
|
+
# cannot hide behind a `maturin develop` build.
|
|
206
|
+
run: python -m pytest -q
|
|
207
|
+
|
|
208
|
+
sdist-wheel:
|
|
209
|
+
name: wheel from sdist
|
|
210
|
+
needs: wheels
|
|
211
|
+
runs-on: ubuntu-latest
|
|
212
|
+
steps:
|
|
213
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
214
|
+
- uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
|
|
215
|
+
with:
|
|
216
|
+
python-version: "3.13"
|
|
217
|
+
- uses: dtolnay/rust-toolchain@683b588c487a69d01f6c6ddf40f036ff682cc2c8 # master
|
|
218
|
+
with:
|
|
219
|
+
toolchain: stable
|
|
220
|
+
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
|
|
221
|
+
with:
|
|
222
|
+
name: sdist
|
|
223
|
+
path: dist
|
|
224
|
+
- name: Build a wheel from the sdist
|
|
225
|
+
# Same PEP 517 path a user gets from `pip install matplotvite` on a
|
|
226
|
+
# platform without a wheel, using the pinned maturin instead of an
|
|
227
|
+
# isolated resolution.
|
|
228
|
+
run: |
|
|
229
|
+
python -m pip install -c .github/requirements-ci.txt maturin
|
|
230
|
+
python -m pip wheel --no-deps --no-build-isolation --wheel-dir wheel dist/*.tar.gz
|
|
231
|
+
- name: Check the wheel built from the sdist
|
|
232
|
+
run: |
|
|
233
|
+
python - <<'PY'
|
|
234
|
+
import glob
|
|
235
|
+
import pathlib
|
|
236
|
+
|
|
237
|
+
wheels = glob.glob("wheel/*.whl")
|
|
238
|
+
assert len(wheels) == 1, wheels
|
|
239
|
+
# The sdist must keep the abi3 configuration, or every interpreter
|
|
240
|
+
# above 3.9 would lose its wheel.
|
|
241
|
+
assert "cp39-abi3" in pathlib.Path(wheels[0]).name, wheels[0]
|
|
242
|
+
print(f"built {wheels[0]}")
|
|
243
|
+
PY
|
|
244
|
+
- name: Install and smoke-test the sdist wheel
|
|
245
|
+
run: |
|
|
246
|
+
python -m pip install -c .github/requirements-ci.txt numpy pytest
|
|
247
|
+
python -m pip install --no-deps wheel/*.whl
|
|
248
|
+
python -m pytest -q tests/test_wheel.py
|
|
249
|
+
|
|
250
|
+
benchmarks:
|
|
251
|
+
name: benchmark regression guard
|
|
252
|
+
runs-on: ubuntu-latest
|
|
253
|
+
# Performance budgets are advisory: benchmark noise should never block CI.
|
|
254
|
+
continue-on-error: true
|
|
255
|
+
steps:
|
|
256
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
257
|
+
- uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
|
|
258
|
+
with:
|
|
259
|
+
python-version: "3.13"
|
|
260
|
+
- uses: dtolnay/rust-toolchain@683b588c487a69d01f6c6ddf40f036ff682cc2c8 # master
|
|
261
|
+
with:
|
|
262
|
+
toolchain: stable
|
|
263
|
+
- uses: Swatinem/rust-cache@d1527c8f67ef9af357290475d4af4b0fab8f12e0 # v2.9.3
|
|
264
|
+
- name: Run benchmarks and report the regression budget
|
|
265
|
+
env:
|
|
266
|
+
MPLBACKEND: Agg
|
|
267
|
+
run: |
|
|
268
|
+
python -m venv .venv
|
|
269
|
+
. .venv/bin/activate
|
|
270
|
+
pip install -c .github/requirements-ci.txt maturin numpy "matplotlib==3.11.2"
|
|
271
|
+
maturin develop --release
|
|
272
|
+
python tools/benchmark.py --quick --out dist/benchmarks.json
|
|
273
|
+
python tools/benchmark_guard.py --warn-only dist/benchmarks.json tools/benchmark_baseline.json
|
|
274
|
+
python tools/visual_compare.py dist/visual --json dist/visual_diff.json
|
|
275
|
+
- name: Upload the visual mismatch metrics
|
|
276
|
+
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
|
277
|
+
with:
|
|
278
|
+
name: visual-diff-${{ github.sha }}
|
|
279
|
+
path: |
|
|
280
|
+
dist/visual
|
|
281
|
+
dist/visual_diff.json
|
|
282
|
+
retention-days: 90
|
|
283
|
+
|
|
284
|
+
galleries:
|
|
285
|
+
name: comparison galleries
|
|
286
|
+
runs-on: ubuntu-latest
|
|
287
|
+
steps:
|
|
288
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
289
|
+
- uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
|
|
290
|
+
with:
|
|
291
|
+
python-version: "3.13"
|
|
292
|
+
- uses: dtolnay/rust-toolchain@683b588c487a69d01f6c6ddf40f036ff682cc2c8 # master
|
|
293
|
+
with:
|
|
294
|
+
toolchain: stable
|
|
295
|
+
- uses: Swatinem/rust-cache@d1527c8f67ef9af357290475d4af4b0fab8f12e0 # v2.9.3
|
|
296
|
+
- name: Build
|
|
297
|
+
run: |
|
|
298
|
+
python -m venv .venv
|
|
299
|
+
. .venv/bin/activate
|
|
300
|
+
pip install -c .github/requirements-ci.txt maturin numpy "matplotlib==3.11.2" pypdfium2 cairosvg
|
|
301
|
+
maturin develop --release
|
|
302
|
+
- name: Fetch the harvested sources
|
|
303
|
+
run: |
|
|
304
|
+
pip download --no-binary :all: --no-deps "matplotlib==3.11.2" -d dist/sdist
|
|
305
|
+
mkdir -p dist/mpl-sdist
|
|
306
|
+
tar -xzf dist/sdist/matplotlib-3.11.2.tar.gz -C dist/mpl-sdist
|
|
307
|
+
git clone --depth 1 --filter=blob:none --sparse -b v3.11.2 \
|
|
308
|
+
https://github.com/matplotlib/matplotlib dist/mpl-src
|
|
309
|
+
git -C dist/mpl-src sparse-checkout set galleries/examples
|
|
310
|
+
git clone --depth 1 --filter=blob:none --sparse -b v0.13.2 \
|
|
311
|
+
https://github.com/mwaskom/seaborn dist/sns-src
|
|
312
|
+
git -C dist/sns-src sparse-checkout set examples
|
|
313
|
+
- name: Regenerate the galleries
|
|
314
|
+
env:
|
|
315
|
+
MPLBACKEND: Agg
|
|
316
|
+
run: |
|
|
317
|
+
. .venv/bin/activate
|
|
318
|
+
python tools/harvest_matplotlib.py --out dist/mpl-corpus
|
|
319
|
+
python tools/harvest_matplotlib.py --examples dist/mpl-src --out dist/mpl-examples
|
|
320
|
+
python tools/harvest_seaborn.py --out dist/seaborn-gallery
|
|
321
|
+
- name: Summarise the galleries
|
|
322
|
+
run: |
|
|
323
|
+
. .venv/bin/activate
|
|
324
|
+
python tools/gallery_summary.py --out dist >> "$GITHUB_STEP_SUMMARY"
|
|
325
|
+
|
|
326
|
+
criterion:
|
|
327
|
+
name: criterion output benchmarks
|
|
328
|
+
runs-on: ubuntu-latest
|
|
329
|
+
steps:
|
|
330
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
331
|
+
- uses: dtolnay/rust-toolchain@683b588c487a69d01f6c6ddf40f036ff682cc2c8 # master
|
|
332
|
+
with:
|
|
333
|
+
toolchain: stable
|
|
334
|
+
- uses: Swatinem/rust-cache@d1527c8f67ef9af357290475d4af4b0fab8f12e0 # v2.9.3
|
|
335
|
+
- name: Run the output-stage Criterion suite
|
|
336
|
+
run: cargo bench --bench render -- --quick
|
|
337
|
+
- name: Report the Criterion budget
|
|
338
|
+
run: python tools/criterion_guard.py --warn-only target/criterion benches/criterion_baseline.json
|
|
339
|
+
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
|
340
|
+
with:
|
|
341
|
+
name: criterion-${{ github.sha }}
|
|
342
|
+
path: target/criterion
|
|
343
|
+
retention-days: 90
|
|
344
|
+
|
|
345
|
+
trace:
|
|
346
|
+
name: coverage trace
|
|
347
|
+
runs-on: ubuntu-latest
|
|
348
|
+
steps:
|
|
349
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
350
|
+
- uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
|
|
351
|
+
with:
|
|
352
|
+
python-version: "3.13"
|
|
353
|
+
- uses: dtolnay/rust-toolchain@683b588c487a69d01f6c6ddf40f036ff682cc2c8 # master
|
|
354
|
+
with:
|
|
355
|
+
toolchain: stable
|
|
356
|
+
- uses: Swatinem/rust-cache@d1527c8f67ef9af357290475d4af4b0fab8f12e0 # v2.9.3
|
|
357
|
+
- name: Check coverage against baseline
|
|
358
|
+
env:
|
|
359
|
+
MPLBACKEND: Agg
|
|
360
|
+
run: |
|
|
361
|
+
python -m venv .venv
|
|
362
|
+
. .venv/bin/activate
|
|
363
|
+
pip install -c .github/requirements-ci.txt maturin numpy "matplotlib==3.11.2"
|
|
364
|
+
maturin develop --release
|
|
365
|
+
TRACE_DIR=$(python -c "import matplotlib, pathlib; print(pathlib.Path(matplotlib.__file__).parent / 'tests')")
|
|
366
|
+
python tools/trace_unsupported.py --check "$TRACE_DIR"
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
name: Pages
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: read
|
|
10
|
+
|
|
11
|
+
concurrency:
|
|
12
|
+
group: pages
|
|
13
|
+
cancel-in-progress: true
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
build:
|
|
17
|
+
name: Build Example Gallery
|
|
18
|
+
runs-on: ubuntu-latest
|
|
19
|
+
steps:
|
|
20
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
21
|
+
|
|
22
|
+
- uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
|
|
23
|
+
with:
|
|
24
|
+
python-version: "3.12"
|
|
25
|
+
|
|
26
|
+
- uses: dtolnay/rust-toolchain@683b588c487a69d01f6c6ddf40f036ff682cc2c8 # master
|
|
27
|
+
with:
|
|
28
|
+
toolchain: stable
|
|
29
|
+
|
|
30
|
+
- uses: Swatinem/rust-cache@d1527c8f67ef9af357290475d4af4b0fab8f12e0 # v2.9.3
|
|
31
|
+
|
|
32
|
+
- name: Build matplotvite
|
|
33
|
+
run: |
|
|
34
|
+
python -m venv .venv
|
|
35
|
+
. .venv/bin/activate
|
|
36
|
+
pip install -c .github/requirements-ci.txt maturin numpy pandas "matplotlib==3.11.2" pypdfium2 cairosvg
|
|
37
|
+
maturin develop --release
|
|
38
|
+
|
|
39
|
+
- name: Fetch the harvested sources
|
|
40
|
+
run: |
|
|
41
|
+
. .venv/bin/activate
|
|
42
|
+
mkdir -p /tmp/mpl-sdist /tmp/sdist
|
|
43
|
+
pip download --no-binary :all: --no-deps "matplotlib==3.11.2" -d /tmp/sdist
|
|
44
|
+
tar -xzf /tmp/sdist/matplotlib-3.11.2.tar.gz -C /tmp/mpl-sdist
|
|
45
|
+
git clone --depth 1 --filter=blob:none --sparse -b v3.11.2 \
|
|
46
|
+
https://github.com/matplotlib/matplotlib /tmp/mpl-src
|
|
47
|
+
git -C /tmp/mpl-src sparse-checkout set galleries/examples
|
|
48
|
+
git clone --depth 1 --filter=blob:none --sparse -b v0.13.2 \
|
|
49
|
+
https://github.com/mwaskom/seaborn /tmp/sns-src
|
|
50
|
+
git -C /tmp/sns-src sparse-checkout set examples
|
|
51
|
+
|
|
52
|
+
- name: Generate comparison galleries
|
|
53
|
+
env:
|
|
54
|
+
MPLBACKEND: Agg
|
|
55
|
+
run: |
|
|
56
|
+
. .venv/bin/activate
|
|
57
|
+
python tools/harvest_matplotlib.py --source /tmp/mpl-sdist --out dist/mpl-corpus
|
|
58
|
+
python tools/harvest_matplotlib.py --examples /tmp/mpl-src --out dist/mpl-examples
|
|
59
|
+
python tools/harvest_seaborn.py --source /tmp/sns-src --out dist/seaborn-gallery
|
|
60
|
+
python tools/visual_compare.py dist/visual-svg --format svg
|
|
61
|
+
# Built after the galleries so the coverage section can summarise them.
|
|
62
|
+
python tools/build_pages.py --out dist
|
|
63
|
+
|
|
64
|
+
- name: Setup Pages
|
|
65
|
+
uses: actions/configure-pages@45bfe0192ca1faeb007ade9deae92b16b8254a0d # v6.0.0
|
|
66
|
+
|
|
67
|
+
- name: Upload artifact
|
|
68
|
+
uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0
|
|
69
|
+
with:
|
|
70
|
+
path: dist
|
|
71
|
+
|
|
72
|
+
deploy:
|
|
73
|
+
name: Deploy to GitHub Pages
|
|
74
|
+
needs: build
|
|
75
|
+
runs-on: ubuntu-latest
|
|
76
|
+
permissions:
|
|
77
|
+
pages: write
|
|
78
|
+
id-token: write
|
|
79
|
+
environment:
|
|
80
|
+
name: github-pages
|
|
81
|
+
url: ${{ steps.deployment.outputs.page_url }}
|
|
82
|
+
steps:
|
|
83
|
+
- id: deployment
|
|
84
|
+
uses: actions/deploy-pages@368f82528645a54fb793d4d04e342629a3f51346 # v5.0.1
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
name: Publish
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- "v*"
|
|
7
|
+
# Re-run an interrupted upload for an existing tag without tagging again.
|
|
8
|
+
workflow_dispatch:
|
|
9
|
+
|
|
10
|
+
permissions:
|
|
11
|
+
contents: read
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
check-version:
|
|
15
|
+
name: tag matches package version
|
|
16
|
+
runs-on: ubuntu-latest
|
|
17
|
+
steps:
|
|
18
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
19
|
+
- uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
|
|
20
|
+
with:
|
|
21
|
+
python-version: "3.13"
|
|
22
|
+
- name: Compare the tag with the version in pyproject.toml, Cargo.toml and Cargo.lock
|
|
23
|
+
# PyPI rejects a duplicate version, so a mistyped tag would build every
|
|
24
|
+
# wheel and only fail at the upload. Stop before the matrix starts, and
|
|
25
|
+
# make sure the three manifests agree so the published metadata is not
|
|
26
|
+
# split between different versions.
|
|
27
|
+
if: github.ref_type == 'tag'
|
|
28
|
+
run: |
|
|
29
|
+
python - <<'PY'
|
|
30
|
+
import os
|
|
31
|
+
import pathlib
|
|
32
|
+
import sys
|
|
33
|
+
import tomllib
|
|
34
|
+
|
|
35
|
+
def load(path):
|
|
36
|
+
return tomllib.loads(pathlib.Path(path).read_text())
|
|
37
|
+
|
|
38
|
+
tag = os.environ["GITHUB_REF_NAME"]
|
|
39
|
+
wanted = tag[1:] if tag.startswith("v") else tag
|
|
40
|
+
cargo = load("Cargo.toml")["package"]
|
|
41
|
+
locked = next(
|
|
42
|
+
(item["version"] for item in load("Cargo.lock")["package"] if item["name"] == cargo["name"]),
|
|
43
|
+
None,
|
|
44
|
+
)
|
|
45
|
+
if locked is None:
|
|
46
|
+
sys.exit(f"{cargo['name']} is missing from Cargo.lock")
|
|
47
|
+
versions = {
|
|
48
|
+
"pyproject.toml": load("pyproject.toml")["project"]["version"],
|
|
49
|
+
"Cargo.toml": cargo["version"],
|
|
50
|
+
"Cargo.lock": locked,
|
|
51
|
+
}
|
|
52
|
+
mismatched = {path: version for path, version in versions.items() if version != wanted}
|
|
53
|
+
if mismatched:
|
|
54
|
+
found = ", ".join(f"{path} says {version}" for path, version in mismatched.items())
|
|
55
|
+
sys.exit(f"tag {tag} does not match: {found}")
|
|
56
|
+
print(f"tag {tag} matches " + ", ".join(f"{path} {version}" for path, version in versions.items()))
|
|
57
|
+
PY
|
|
58
|
+
|
|
59
|
+
wheels:
|
|
60
|
+
name: wheel (${{ matrix.name }})
|
|
61
|
+
needs: check-version
|
|
62
|
+
runs-on: ${{ matrix.os }}
|
|
63
|
+
strategy:
|
|
64
|
+
fail-fast: false
|
|
65
|
+
matrix:
|
|
66
|
+
include:
|
|
67
|
+
- name: linux-x86_64
|
|
68
|
+
os: ubuntu-latest
|
|
69
|
+
target: x86_64-unknown-linux-gnu
|
|
70
|
+
manylinux: auto
|
|
71
|
+
interpreter: python3.13
|
|
72
|
+
- name: linux-aarch64
|
|
73
|
+
os: ubuntu-24.04-arm
|
|
74
|
+
target: aarch64-unknown-linux-gnu
|
|
75
|
+
manylinux: auto
|
|
76
|
+
interpreter: python3.13
|
|
77
|
+
- name: macos-arm64
|
|
78
|
+
os: macos-latest
|
|
79
|
+
target: aarch64-apple-darwin
|
|
80
|
+
interpreter: python
|
|
81
|
+
- name: macos-x86_64
|
|
82
|
+
os: macos-15-intel
|
|
83
|
+
target: x86_64-apple-darwin
|
|
84
|
+
interpreter: python
|
|
85
|
+
- name: windows-x86_64
|
|
86
|
+
os: windows-latest
|
|
87
|
+
target: x86_64-pc-windows-msvc
|
|
88
|
+
interpreter: python
|
|
89
|
+
- name: windows-arm64
|
|
90
|
+
os: windows-11-arm
|
|
91
|
+
target: aarch64-pc-windows-msvc
|
|
92
|
+
interpreter: python
|
|
93
|
+
steps:
|
|
94
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
95
|
+
- uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
|
|
96
|
+
with:
|
|
97
|
+
python-version: "3.13"
|
|
98
|
+
- uses: PyO3/maturin-action@e83996d129638aa358a18fbd1dfb82f0b0fb5d3b # v1.51.0
|
|
99
|
+
with:
|
|
100
|
+
target: ${{ matrix.target }}
|
|
101
|
+
manylinux: ${{ matrix.manylinux }}
|
|
102
|
+
# Same stable-ABI wheel as CI: one cp39-abi3 build serves every
|
|
103
|
+
# interpreter above 3.9, so the matrix only varies by platform.
|
|
104
|
+
args: --release --out dist --interpreter ${{ matrix.interpreter }}
|
|
105
|
+
- name: Build the sdist
|
|
106
|
+
# The tarball is platform independent, so exactly one job builds it.
|
|
107
|
+
if: matrix.name == 'linux-x86_64'
|
|
108
|
+
uses: PyO3/maturin-action@e83996d129638aa358a18fbd1dfb82f0b0fb5d3b # v1.51.0
|
|
109
|
+
with:
|
|
110
|
+
command: sdist
|
|
111
|
+
args: --out dist
|
|
112
|
+
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
|
113
|
+
with:
|
|
114
|
+
name: wheel-${{ matrix.name }}
|
|
115
|
+
path: dist/*.whl
|
|
116
|
+
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
|
117
|
+
if: matrix.name == 'linux-x86_64'
|
|
118
|
+
with:
|
|
119
|
+
name: sdist
|
|
120
|
+
path: dist/*.tar.gz
|
|
121
|
+
|
|
122
|
+
publish:
|
|
123
|
+
name: Publish to PyPI
|
|
124
|
+
needs: wheels
|
|
125
|
+
runs-on: ubuntu-latest
|
|
126
|
+
# Must match the environment name registered in the PyPI trusted publisher.
|
|
127
|
+
environment: pypi
|
|
128
|
+
permissions:
|
|
129
|
+
# PyPI trusted publishing exchanges this OIDC token for a short-lived key.
|
|
130
|
+
id-token: write
|
|
131
|
+
steps:
|
|
132
|
+
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
|
|
133
|
+
with:
|
|
134
|
+
pattern: wheel-*
|
|
135
|
+
path: dist
|
|
136
|
+
merge-multiple: true
|
|
137
|
+
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
|
|
138
|
+
with:
|
|
139
|
+
name: sdist
|
|
140
|
+
path: dist
|
|
141
|
+
- name: Upload the wheels and sdist to PyPI
|
|
142
|
+
uses: PyO3/maturin-action@e83996d129638aa358a18fbd1dfb82f0b0fb5d3b # v1.51.0
|
|
143
|
+
with:
|
|
144
|
+
command: upload
|
|
145
|
+
# Re-runs after a partial upload keep the files PyPI already has.
|
|
146
|
+
args: --skip-existing --non-interactive dist/*
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# Bolt's Journal - Critical Performance Learnings
|
|
2
|
+
|
|
3
|
+
## 2026-03-30 - Python-side Data Extent Calculation Bottleneck on Dense Data
|
|
4
|
+
**Learning:** Even though rendering itself is written in compiled Rust, Python-side figure preparation can become the dominant bottleneck when plotting dense datasets (e.g. 50k–100k points). Specifically, `Axes._extent_x` and `_extent_y` previously flattened all coordinate arrays into a giant Python list using `list.extend(np.asarray(arr).ravel())` before feeding it to `_span(xs)`, which converted the list back into a NumPy array. For 100k points, constructing and boxing 100,000 Python float objects twice (for X and Y limits) took ~24 ms, whereas the entire Rust rendering pass took ~12 ms.
|
|
5
|
+
**Action:** When computing extents across heterogeneous graphical primitives in Python, accumulate running `(min, max)` tuples directly using vectorized `fin = arr[np.isfinite(arr)]; (min(ext[0], fin.min()), max(ext[1], fin.max()))` per array rather than concatenating arrays into a Python list. This cut 100k line plot time by more than half (speedup vs matplotlib jumped from 5.0× to 10.2× on PNG and 5.0× to 27.9× on SVG).
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# Sentinel's Journal - Critical Security Learnings
|
|
2
|
+
|
|
3
|
+
## 2026-03-30 - Unbounded Canvas Dimensions Causing Out-Of-Memory / Integer Overflows
|
|
4
|
+
**Vulnerability:** Figure size (`figsize`) and resolution (`dpi`) were accepted without positive-finite validation or maximum bounds checking in both Python and Rust layers. Unchecked parameters allowed non-positive or excessive numbers, leading to integer overflow conversions or multi-terabyte buffer allocations in Rust rendering engines that crashed or aborted the host process (Denial of Service).
|
|
5
|
+
**Learning:** PyO3 bindings converting Python floats directly into integer pixel counts (`(round(w * dpi), round(h * dpi))`) without range validation can cause `OverflowError` on negative/infinite inputs and immediate OOM aborts on huge values in native rasterizers (`tiny-skia` / `resvg`).
|
|
6
|
+
**Prevention:** Validate dimension inputs at the Python API boundary (`_validate_dimension`) ensuring positive, finite numbers and reasonable upper bounds on pixel dimensions (32,768px) and total pixel count (2^28 px), and mirror the bounds checking defensively in Rust before memory allocations.
|