SVG Polish
A fast, lossless, type-safe SVG optimizer for Python.
svg_polish shrinks SVG files — strips editor metadata, collapses redundant attributes, dedups gradients, optimises path data and transforms — while guaranteeing that the output renders identically to the input. The library is fully typed (py.typed), thread-safe, secure-by-default against XML attacks, and ships with a single short API: optimize().
Why SVG Polish
| Before | After |
|---|---|
style="fill:#ff0000;stroke:#000000" |
fill="#f00" |
| Inkscape / Sketch / Adobe namespaces | Removed |
| Verbose path commands | Collapsed and rounded |
| Duplicate gradients | Deduplicated |
| Hostile XXE / billion-laughs payload | Rejected before parsing |
Install
Requires Python 3.10+.
pip install svg-polish
The install is pure Python and depends only on defusedxml. A
svg-polish[fast] extra is reserved for the v1.x lxml-backed XML
engine (~3–5× faster on large files); v1.0 ships only the
defusedxml.minidom backend.
Quick start
Python
from svg_polish import optimize
optimized = optimize('<svg xmlns="http://www.w3.org/2000/svg">…</svg>')
That's the whole pitch. Read from disk, write to disk:
from pathlib import Path
from svg_polish import optimize, optimize_path
with open("input.svg", encoding="utf-8") as f:
svg = f.read()
optimized = optimize(svg)
with open("output.svg", "w", encoding="utf-8") as f:
f.write(optimized)
# or, in one line via the path-aware helper:
Path("output.svg").write_text(optimize_path("input.svg"), encoding="utf-8")
Pass OptimizeOptions for tuning:
from svg_polish import optimize, OptimizeOptions
opts = OptimizeOptions(
digits=3,
shorten_ids=True,
enable_viewboxing=True,
strip_comments=True,
)
optimized = optimize(svg, opts)
For metrics, use optimize_with_stats:
from svg_polish import optimize_with_stats
result = optimize_with_stats(svg)
print(f"saved {result.saved_bytes} B ({result.saved_ratio:.1%}) in {result.duration_ms:.1f} ms")
For async web frameworks:
from svg_polish import optimize_async
async def handler(svg: str) -> str:
return await optimize_async(svg)
Command line
svg-polish -i input.svg -o output.svg
cat input.svg | svg-polish > output.svg
svg-polish -i input.svg -o output.svgz # gzip-compressed output
Aggressive settings:
svg-polish -i input.svg -o output.svg \
--enable-viewboxing \
--enable-id-stripping \
--enable-comment-stripping \
--shorten-ids \
--indent=none
Run svg-polish --help for the full flag list.
Public API
| Symbol | Purpose |
|---|---|
optimize(svg, options=None) |
Canonical entry point. Alias of optimize_string. |
optimize_string(svg, options=None) |
str/bytes in, str out. |
optimize_bytes(svg, options=None) |
bytes in, UTF-8 bytes out. |
optimize_path(path, options=None) |
Read from a filesystem path. |
optimize_async(svg, options=None) |
await-able wrapper via asyncio.to_thread. |
optimize_with_stats(svg, options=None) |
Returns an OptimizeResult with metrics. |
OptimizeOptions |
Frozen dataclass — the only configuration shape. |
OptimizeResult |
Optimised SVG + stats + duration. |
ScourStats |
Per-pass counters. |
Exceptions: SvgPolishError (base) → SvgParseError, SvgPathSyntaxError, SvgTransformSyntaxError, SvgOptimizeError, SvgSecurityError, InvalidOptionError. See docs/api.md for the full reference.
What it does
- Removes editor metadata (Inkscape, Sodipodi, Illustrator, Sketch).
- Strips default attribute values and empty attributes.
- Converts colors to the shortest equivalent form.
- Deduplicates
<linearGradient>/<radialGradient>definitions. - Collapses
<g>wrappers and merges sibling groups. - Optimises
<path>ddata (relative coords, h/v/s shortcuts, segment merging). - Optimises
transform,patternTransform,gradientTransform. - Reduces numeric precision to a configurable digit count.
- Optionally shortens IDs, strips comments, converts to viewBox, embeds rasters.
- Custom serialiser produces tight, deterministic output.
All passes are lossless by default — see docs/performance.md for the opt-in decimal_engine="float" mode that trades reproducibility for ~3-5× faster numeric arithmetic.
Security
Inputs are parsed through defusedxml, so XML external entity attacks, billion-laughs, and external DTD fetches are rejected before they touch the optimiser. Inputs over 100 MB are refused by default. See SECURITY.md for the threat model and docs/security.md for usage patterns.
Documentation
docs/api.md— Python API reference.docs/architecture.md— module layout, layering, pipeline.docs/performance.md— tuning guide and benchmarks.docs/security.md— usage patterns for untrusted input.docs/cli.md— command-line reference.docs/configuration.md—OptimizeOptionsfield-by-field.
Development
git clone https://github.com/g-battaglia/svg_polish.git
cd svg_polish
uv sync
uv run poe test # full test suite (~660 tests)
uv run poe test-cov # with coverage HTML report
uv run poe lint # ruff lint
uv run poe format # ruff format
uv run poe typecheck # mypy strict
uv run poe check # all of the above
uv run poe bench # save a performance baseline
uv run poe bench-compare # compare current run against the baseline
Origin
svg_polish is a fork of Scour, originally created by Jeff Schiller and Louis Simard in 2010, later maintained by Tobias Oberstein and Patrick Storz. Upstream Scour has been dormant since August 2021. This v1.0 release is a ground-up modernisation:
- Python 3.10+ only; no
six, no Python 2 compatibility shims. - Single typed public surface (
OptimizeOptions,OptimizeResult,optimize_*). - Modular passes (one file per transformation) instead of a 4 700-line monolith.
- Thread-safe
Decimalprecision contexts. - Secure-by-default XML parsing with
defusedxml. - 100% line coverage,
mypy --strictclean,ruffclean.
License
Apache License 2.0 — see LICENSE.
svg_regex.py is derived from code by Enthought, Inc. (BSD 3-Clause). Full attribution in NOTICE.
Metadata
Release files for svg-polish 1.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| svg_polish-1.0.0.tar.gz | 210.0 kB | Without provenance | Details for svg_polish-1.0.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| svg_polish-1.0.0-py3-none-any.whl | Python 3 | none | any | Details for svg_polish-1.0.0-py3-none-any.whl |
Total release size: 332.1 kB
Release files / svg_polish-1.0.0.tar.gz
| Download URL | svg_polish-1.0.0.tar.gz |
|---|---|
| Size | 210.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
56df761f7f0beb7c41060616e7eb5ddc990ce78e4a3323f04d62790ab431273d
|
|
BLAKE2b-256 checksum How to use checksums |
7dc5e7987aa81660268e7cc64db5360ca28ef6e729cd225a28443d9da246a2ec
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.11.7 {"installer":{"name":"uv","version":"0.11.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / svg_polish-1.0.0-py3-none-any.whl
| Download URL | svg_polish-1.0.0-py3-none-any.whl |
|---|---|
| Size | 122.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
a513b3f584b64b293c2535d0231eb8bb27e977f193f8f1e5f59cdf7fc59ae61d
|
|
BLAKE2b-256 checksum How to use checksums |
593e3de48cd6c5c0c94924fb28127bd392ba99175f8a3f03bc43d9eb694bd61c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.11.7 {"installer":{"name":"uv","version":"0.11.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Security for svg-polish 1.0.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.
No provenance found
None of the files in this release were uploaded with provenance metadata.
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.