Skip to main content

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().

PyPI Python Coverage License


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> d data (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

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 Decimal precision contexts.
  • Secure-by-default XML parsing with defusedxml.
  • 100% line coverage, mypy --strict clean, ruff clean.

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)

Source distribution for svg-polish 1.0.0
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)

Table of built distributions (wheels) for svg-polish 1.0.0
File Interpreter ABI Platform Actions
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.

Release history Release notifications | RSS feed

This release

1.0.0 This release

2 release files No provenance

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