Fast CSS inlining written in Rust
Reason this release was yanked:
security vulnerability in HTML serialization
Project description
css_inline
Blazing-fast CSS inlining for Python implemented with Mozilla's Servo project components.
Features:
- Removing
style
tags after inlining; - Resolving external stylesheets (including local files);
- Control if
style
tags should be processed; - Out-of-document CSS to inline;
- Inlining multiple documents in parallel (via Rust-level threads)
The project supports CSS Syntax Level 3.
Installation
To install css_inline
via pip
run the following command:
pip install css_inline
Pre-compiled wheels for most popular platforms are provided. If your platform is not in the support table below, you will need a Rust compiler to build this package from source. The minimum supported Rust version is 1.60.
Usage
To inline CSS in an HTML document:
import css_inline
HTML = """<html>
<head>
<title>Test</title>
<style>h1 { color:blue; }</style>
</head>
<body>
<h1>Big Text</h1>
</body>
</html>"""
inlined = css_inline.inline(HTML)
# HTML becomes this:
#
# <html>
# <head>
# <title>Test</title>
# <style>h1 { color:blue; }</style>
# </head>
# <body>
# <h1 style="color:blue;">Big Text</h1>
# </body>
# </html>
If you want to inline many HTML documents, you can utilize inline_many
that processes the input in parallel.
import css_inline
css_inline.inline_many(["<...>", "<...>"])
inline_many
will spawn threads on the Rust level; thus, you can expect it's running faster than css_inline.inline
via Python's multiprocessing
or threading
modules.
For customization options use the CSSInliner
class:
import css_inline
inliner = css_inline.CSSInliner(remove_style_tags=True)
inliner.inline("...")
If you'd like to skip CSS inlining for an HTML tag, add data-css-inline="ignore"
attribute to it:
<head>
<title>Test</title>
<style>h1 { color:blue; }</style>
</head>
<body>
<!-- The tag below won't receive additional styles -->
<h1 data-css-inline="ignore">Big Text</h1>
</body>
</html>
This attribute also allows you to skip link
and style
tags:
<head>
<title>Test</title>
<!-- Styles below are ignored -->
<style data-css-inline="ignore">h1 { color:blue; }</style>
</head>
<body>
<h1>Big Text</h1>
</body>
</html>
If you'd like to load stylesheets from your filesystem, use the file://
scheme:
import css_inline
# styles/email is relative to the current directory
inliner = css_inline.CSSInliner(base_url="file://styles/email/")
inliner.inline("...")
Standards support & restrictions
css-inline
is built on top of cssparser and relies on its behavior for CSS parsing.
Notably:
- Only HTML 5, XHTML is not supported;
- Only CSS 3;
- Only UTF-8 for string representation. Other document encodings are not yet supported.
If you'd like to work around some XHTML compatibility issues like closing empty tags (<hr>
vs. <hr/>
), you can use the following snippet that involves lxml
:
import css_inline
from lxml import html, etree
document = "..." # Your HTML document
inlined = css_inline.inline(document)
tree = html.fromstring(inlined)
inlined = etree.tostring(tree).decode(encoding="utf-8")
Performance
Due to the usage of efficient tooling from Mozilla's Servo project (html5ever
, rust-cssparser
and others) this
library has excellent performance characteristics. In comparison with other Python projects, it is usually >10x faster than the nearest alternative.
For inlining CSS in the html document from the Usage
section above there is the following breakdown in the benchmarks:
css_inline 0.9.0
- 13.58 uspremailer 3.10.0
- 310.06 us (x22.83)toronado 0.1.0
- 1.43 ms (x105.32)inlinestyler 0.2.5
- 2.11 ms (x155.46)pynliner 0.8.0
- 2.51 ms (x184.97)
Realistic email 1:
css_inline 0.9.0
- 266.53 uspremailer 3.10.0
- 2.78 ms (x10.47)toronado 0.1.0
- 31.20 ms (x117.09)inlinestyler 0.2.5
- 52.64 ms (x197.52)pynliner 0.8.0
- 101.14 ms (x379.47)
Realistic email 2:
css_inline 0.9.0
- 237.23 uspremailer 3.10.0
- 4.07 ms (x17.24)toronado 0.1.0
-Error: Pseudo-elements are not supported
inlinestyler 0.2.5
- 33.87 ms (x142.81)pynliner 0.8.0
-Error: No match was found
You can take a look at the benchmarks' code at benches/bench.py
file.
The results above were measured with stable rustc 1.69.0
, Python 3.11.0
, Linux x86_64
on i8700K, and 32GB RAM.
Comparison with other libraries
Besides performance, css-inline
differs from other Python libraries for CSS inlining.
- Generally supports more CSS features than other libraries (for example,
toronado
andpynliner
do not support pseudo-elements); - It has fewer configuration options and not as flexible as
premailer
; - Works on fewer platforms than LXML-based libraries (
premailer
,inlinestyler
,toronado
, and optionallypynliner
); - Does not have debug logs yet;
- Supports only HTML 5.
Python support
css_inline
supports CPython 3.7, 3.8, 3.9, 3.10, 3.11 and PyPy 3.7, 3.8, 3.9.
Extra materials
If you want to know how this library was created & how it works internally, you could take a look at these articles:
License
The code in this project is licensed under MIT license.
By contributing to css_inline
, you agree that your contributions
will be licensed under its MIT license.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distributions
Hashes for css_inline-0.9.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 1f92a87f49e0a6ea3caf7b60bf7898dcc1efbb8b622f5e9892237a05779513b9 |
|
MD5 | c5f740379718a6e0dbd2cfccb47e6262 |
|
BLAKE2b-256 | 250a487837d118a8be6aac32c992fb227697acbb5fd96c90417f6f4300e4319e |
Hashes for css_inline-0.9.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 38158bccb45732c196913dbd03c84a4f241e35f3f264d883a1f48c239bfca7dd |
|
MD5 | 3cbaff5c40d357bea143b5cff9b98199 |
|
BLAKE2b-256 | ca1d08057f1aec367a203da9e2fbae42c18015897a19ce9b6d48c809b906eb0f |
Hashes for css_inline-0.9.0-pp39-pypy39_pp73-macosx_10_7_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 77449d65330e7d5f7b24dfa4c2379001da4df69d41340096e8ef8f86ebc3fb99 |
|
MD5 | 1567c21aeb40c7d285b6c8e4265195b8 |
|
BLAKE2b-256 | db79cfd48e854dff32c2440cf5f38095c69e9c2307b259b795a8cde82688b745 |
Hashes for css_inline-0.9.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | c73ed7871cecaa8e5a65c529f6208db571739e8295d6596682c32ba2e043fd08 |
|
MD5 | 0c2e87a06648acf16d3a7d891fbb3ef9 |
|
BLAKE2b-256 | 1e8559a1aed1164bbd36b960db05d6984c197e6fd9aaaf9f6290ff3aa678d02a |
Hashes for css_inline-0.9.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | b1be1b03d436a55cb501c3c115e8f3c0ef85b49bf0007723dab75f97f93d8e62 |
|
MD5 | b6aa8f5bd8d8717638ec55f80caf5bf1 |
|
BLAKE2b-256 | 10e96a19fb52a7cdae81aa6a0f290cc6c9475a3cac78119ea872d436517a2d43 |
Hashes for css_inline-0.9.0-pp38-pypy38_pp73-macosx_10_7_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 211c9fc46f7c4f17fafc46eb43b2054593ca8392ea369df8b50b59f9c4064473 |
|
MD5 | b10eec595d56fb25f873a2225c270d6a |
|
BLAKE2b-256 | ba682a35045b39d4f129061b2a0ca24a16d83f75ca6dd98d884b8c1bcaa098ec |
Hashes for css_inline-0.9.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | d8848c74a8fe82db238d31df012beb805d3ddfbccf3f1c90aaa84d25273fe03f |
|
MD5 | 16ce00d0b41007bd610d7a5f4f2f6269 |
|
BLAKE2b-256 | 298bd2610d1e67dd798e87ab726f5b11f463e7b0e8fc65f360fe923de22ccbfe |
Hashes for css_inline-0.9.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 19604b20d7904c44786a9dd9dbd6e831e348ca8d139f325a090a0f478186a3e0 |
|
MD5 | fe3e9a3be0686016c73bb15cf984d6ef |
|
BLAKE2b-256 | 86bdacb27d74465fcbe9704a153da51ea76aa7f6d7168209f4baf30c2880e006 |
Hashes for css_inline-0.9.0-pp37-pypy37_pp73-macosx_10_7_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 515ba927e04fe25e7fc80934c7ee297e431b5232cf8768ffe0e06ed66f3013af |
|
MD5 | c5a0f17522a7caaf27d30f422b9c7e3a |
|
BLAKE2b-256 | 952d8fab07078f398d28dd19016a99fb7d9d825d31e994b6abec198b6c45e663 |
Hashes for css_inline-0.9.0-cp37-abi3-win_amd64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 14f5ccbf1eb87cca4795c011018f020c0b525cb1bb304a4080e04e108f9aefca |
|
MD5 | 7898cd798d15ef63a2cc15a30ef6eb80 |
|
BLAKE2b-256 | 7f5b5bd70b90bea2f4203c9d14f8bba223d3f6540ee5b96e5a833b92f63f6f11 |
Hashes for css_inline-0.9.0-cp37-abi3-win32.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 8779c1654300305d36bbf382ca5cb335d828df7fe9efe06e66a4a2f0d998bdf8 |
|
MD5 | 83723341f02cfc93379a06cfa0ba0960 |
|
BLAKE2b-256 | 7e353ed828150255a2e7f32af83bd1b39374cde984e79d81dfb16fb5b8af4b7e |
Hashes for css_inline-0.9.0-cp37-abi3-musllinux_1_2_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 9e6a5ff4ac4e76357690ff5b73b1601566d126a52342f5fb92ba22aecab3453e |
|
MD5 | 8ed62ff5118538d140b26eafb10df3ff |
|
BLAKE2b-256 | b7899f73f9a34ebe9f8b280a2b7d9c04630d72b8336c84a33f5330c182b2057b |
Hashes for css_inline-0.9.0-cp37-abi3-musllinux_1_2_armv7l.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 1da3366d8cbff7359989a72f8d6b265045909c40e17484194ca65cc5cefe13f9 |
|
MD5 | 5fe2c5b157715bad3a2c17c14a41441f |
|
BLAKE2b-256 | bf821b7cd4c719f20242f6689cc454536d795da6c5dfb0e0fff2321c3c236aa2 |
Hashes for css_inline-0.9.0-cp37-abi3-musllinux_1_2_aarch64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 0c796cb6171c5ea71b52f613abeeb78969bad3d0719af4d27cf6db9434eaff8f |
|
MD5 | ea41b02d92348f7d49c09c2f2a01f42d |
|
BLAKE2b-256 | 5067aac32293e9d843ffb998a9871277fbfb0c29d5996510aae702d95e457f06 |
Hashes for css_inline-0.9.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 66656b012cb7f0f6dcf9a0d1d2a0787695326fb28cd5237c08087e6c31837da3 |
|
MD5 | d6b8f4a2f4e38b803e30736dc27207b7 |
|
BLAKE2b-256 | 51e7f6c1aa46d3f7a88d2301358b192be43326fae0036be8476731c3b892fc93 |
Hashes for css_inline-0.9.0-cp37-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 3c25f2ca4586e20a53db701590fb33a96fd773741d4559474fa18e0ed34f4a49 |
|
MD5 | 7326ce1faf0a121764377ce3268665f5 |
|
BLAKE2b-256 | 468647bc5b0ca4072ba760b7c029757956360f3e350f97f1df29ed0c562c584c |
Hashes for css_inline-0.9.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 09eeb2b8da52f6c6a1bce15a7aac46110472e8a88bf5adcc900b6be9509bbab2 |
|
MD5 | 78fc90d9b6be12ba2151163d25aed821 |
|
BLAKE2b-256 | 2f68b200a1df560f1ed9d6f0b8b09bc44741e73f6d11655d14a82b4540a63b07 |
Hashes for css_inline-0.9.0-cp37-abi3-manylinux_2_12_i686.manylinux2010_i686.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 9b835fb01f9570cc55200794ea9dae77a437ba8e3ba5e64d937e1d3700b99c55 |
|
MD5 | e5432f0cc3ab13551d7bbd7c81851a6c |
|
BLAKE2b-256 | 428d7cbfef4496adab7d38c41ca158f42fa6c2ca6bac2452d478ce11a51f7ac8 |
Hashes for css_inline-0.9.0-cp37-abi3-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | a46acd54351a9b2c7bc33e269fdc588f647c059eb2acc60b6a12f497c547e45d |
|
MD5 | a7891d4cf623b984c71cd157e955a4e0 |
|
BLAKE2b-256 | 60e230fb318df3210edf8048dfa2d5672ebf681ac9fc4f0da0cb7ab0781b17f7 |
Hashes for css_inline-0.9.0-cp37-abi3-macosx_10_7_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 49cf75dd3facca412232511727c90105f674eb4d25ffc414797d85d8fdbb43c9 |
|
MD5 | 46cfe4f68c709052474ddb151ed9c122 |
|
BLAKE2b-256 | 1ea4c3edee4edf2d856b1aa0b6cd2b7befc0c860e945fe6847ea661b27bef1c9 |