summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Lord <davidism@gmail.com>2023-01-26 07:18:50 -0800
committerDavid Lord <davidism@gmail.com>2023-01-26 07:18:50 -0800
commit45dbe44a36a2abe1b0b62b8ff9e32653fe91fb83 (patch)
tree8469681beac3314ed3d660fdb6e3dd9f5a1ced95
parent00fea0a771ed63169933684283862e50ff3497aa (diff)
downloadmarkupsafe-rust.tar.gz
initial rust implementationrust
-rw-r--r--.gitignore1
-rw-r--r--MANIFEST.in2
-rw-r--r--bench.py39
-rw-r--r--bench/bench_basic.py5
-rw-r--r--bench/bench_largestring.py6
-rw-r--r--bench/bench_long_empty_string.py6
-rw-r--r--bench/bench_long_suffix.py6
-rw-r--r--bench/bench_short_empty_string.py5
-rw-r--r--bench/runbench.py38
-rw-r--r--pyproject.toml2
-rw-r--r--setup.py91
-rw-r--r--src/markupsafe/_rust_speedups.pyi9
-rw-r--r--src/rust/Cargo.lock273
-rw-r--r--src/rust/Cargo.toml12
-rw-r--r--src/rust/src/lib.rs54
-rw-r--r--tests/conftest.py2
16 files changed, 409 insertions, 142 deletions
diff --git a/.gitignore b/.gitignore
index b48a303..b6ce35d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -15,3 +15,4 @@ __pycache__/
/htmlcov/
/docs/_build/
/.mypy_cache/
+/src/rust/target/
diff --git a/MANIFEST.in b/MANIFEST.in
index 7dfa3f6..35d1a31 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -6,4 +6,6 @@ prune docs/_build
graft tests
include src/markupsafe/py.typed
include src/markupsafe/*.pyi
+graft src/rust
+prune src/rust/target
global-exclude *.pyc
diff --git a/bench.py b/bench.py
new file mode 100644
index 0000000..1a1f9bc
--- /dev/null
+++ b/bench.py
@@ -0,0 +1,39 @@
+import pyperf
+
+runner = pyperf.Runner()
+
+for name in ("native", "rust_speedups"): # ("native", "speedups", "rust_speedups"):
+ runner.timeit(
+ f"short escape {name}",
+ setup=f"from markupsafe._{name} import escape",
+ stmt='escape("<strong>Hello, World!</strong>")',
+ )
+ # runner.timeit(
+ # f"long escape {name}",
+ # setup=(
+ # f"from markupsafe._{name} import escape;\n"
+ # "s = \"Hello, World!\" * 1000"
+ # ),
+ # stmt="escape(s)",
+ # )
+ # runner.timeit(
+ # f"short empty {name}",
+ # setup=f"from markupsafe._{name} import escape",
+ # stmt="escape(\"Hello, World!\")",
+ # )
+ # runner.timeit(
+ # f"long empty {name}",
+ # setup=(
+ # f"from markupsafe._{name} import escape;\n"
+ # "s = \"<strong>Hello, World!</strong>\" * 1000"
+ # ),
+ # stmt="escape(s)",
+ # )
+ # runner.timeit(
+ # f"long suffix {name}",
+ # setup=(
+ # f"from markupsafe._{name} import escape;\n"
+ # "s = \"<strong>Hello, World!</strong>\" + \"x\" * 100000"
+ # ),
+ # stmt="escape(s)",
+ # )
diff --git a/bench/bench_basic.py b/bench/bench_basic.py
deleted file mode 100644
index b67eda4..0000000
--- a/bench/bench_basic.py
+++ /dev/null
@@ -1,5 +0,0 @@
-from markupsafe import escape
-
-
-def run():
- escape("<strong>Hello World!</strong>")
diff --git a/bench/bench_largestring.py b/bench/bench_largestring.py
deleted file mode 100644
index 9ced67f..0000000
--- a/bench/bench_largestring.py
+++ /dev/null
@@ -1,6 +0,0 @@
-from markupsafe import escape
-
-
-def run():
- string = "<strong>Hello World!</strong>" * 1000
- escape(string)
diff --git a/bench/bench_long_empty_string.py b/bench/bench_long_empty_string.py
deleted file mode 100644
index ad2480c..0000000
--- a/bench/bench_long_empty_string.py
+++ /dev/null
@@ -1,6 +0,0 @@
-from markupsafe import escape
-
-
-def run():
- string = "Hello World!" * 1000
- escape(string)
diff --git a/bench/bench_long_suffix.py b/bench/bench_long_suffix.py
deleted file mode 100644
index 35f38da..0000000
--- a/bench/bench_long_suffix.py
+++ /dev/null
@@ -1,6 +0,0 @@
-from markupsafe import escape
-
-
-def run():
- string = "<strong>Hello World!</strong>" + "x" * 100000
- escape(string)
diff --git a/bench/bench_short_empty_string.py b/bench/bench_short_empty_string.py
deleted file mode 100644
index 0664a6f..0000000
--- a/bench/bench_short_empty_string.py
+++ /dev/null
@@ -1,5 +0,0 @@
-from markupsafe import escape
-
-
-def run():
- escape("Hello World!")
diff --git a/bench/runbench.py b/bench/runbench.py
deleted file mode 100644
index f20cd49..0000000
--- a/bench/runbench.py
+++ /dev/null
@@ -1,38 +0,0 @@
-import os
-import re
-import sys
-from subprocess import Popen
-
-_filename_re = re.compile(r"^bench_(.*?)\.py$")
-bench_directory = os.path.abspath(os.path.dirname(__file__))
-
-
-def list_benchmarks():
- result = []
- for name in os.listdir(bench_directory):
- match = _filename_re.match(name)
- if match is not None:
- result.append(match.group(1))
- result.sort(key=lambda x: (x.startswith("logging_"), x.lower()))
- return result
-
-
-def run_bench(name):
- print(name)
- Popen(
- [sys.executable, "-m", "timeit", "-s", f"from bench_{name} import run", "run()"]
- ).wait()
-
-
-def main():
- print("=" * 80)
- print("Running benchmark for MarkupSafe")
- print("-" * 80)
- os.chdir(bench_directory)
- for bench in list_benchmarks():
- run_bench(bench)
- print("-" * 80)
-
-
-if __name__ == "__main__":
- main()
diff --git a/pyproject.toml b/pyproject.toml
index 5fdf079..9244e1e 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -28,7 +28,7 @@ Twitter = "https://twitter.com/PalletsTeam"
Chat = "https://discord.gg/pallets"
[build-system]
-requires = ["setuptools"]
+requires = ["setuptools", "setuptools-rust"]
build-backend = "setuptools.build_meta"
[tool.setuptools.dynamic]
diff --git a/setup.py b/setup.py
index 7208cdd..e0ebfed 100644
--- a/setup.py
+++ b/setup.py
@@ -1,84 +1,27 @@
import os
import platform
-import sys
-from distutils.errors import CCompilerError
-from distutils.errors import DistutilsExecError
-from distutils.errors import DistutilsPlatformError
from setuptools import Extension
from setuptools import setup
-from setuptools.command.build_ext import build_ext
+from setuptools_rust import RustExtension
-ext_modules = [Extension("markupsafe._speedups", ["src/markupsafe/_speedups.c"])]
-
-
-class BuildFailed(Exception):
- pass
-
-
-class ve_build_ext(build_ext):
- """This class allows C extension building to fail."""
-
- def run(self):
- try:
- build_ext.run(self)
- except DistutilsPlatformError as e:
- raise BuildFailed() from e
-
- def build_extension(self, ext):
- try:
- build_ext.build_extension(self, ext)
- except (CCompilerError, DistutilsExecError, DistutilsPlatformError) as e:
- raise BuildFailed() from e
- except ValueError as e:
- # this can happen on Windows 64 bit, see Python issue 7511
- if "'path'" in str(sys.exc_info()[1]): # works with Python 2 and 3
- raise BuildFailed() from e
- raise
-
-
-def run_setup(with_binary):
- setup(
- cmdclass={"build_ext": ve_build_ext},
- ext_modules=ext_modules if with_binary else [],
- )
-
-
-def show_message(*lines):
- print("=" * 74)
- for line in lines:
- print(line)
- print("=" * 74)
-
-
-supports_speedups = platform.python_implementation() not in {
+if platform.python_implementation() not in {
"PyPy",
"Jython",
"GraalVM",
-}
-
-if os.environ.get("CIBUILDWHEEL", "0") == "1" and supports_speedups:
- run_setup(True)
-elif supports_speedups:
- try:
- run_setup(True)
- except BuildFailed:
- show_message(
- "WARNING: The C extension could not be compiled, speedups"
- " are not enabled.",
- "Failure information, if any, is above.",
- "Retrying the build without the C extension now.",
- )
- run_setup(False)
- show_message(
- "WARNING: The C extension could not be compiled, speedups"
- " are not enabled.",
- "Plain-Python build succeeded.",
- )
-else:
- run_setup(False)
- show_message(
- "WARNING: C extensions are not supported on this Python"
- " platform, speedups are not enabled.",
- "Plain-Python build succeeded.",
+}:
+ local = os.environ.get("CIBUIDWHEEL", "0") != "1"
+ setup(
+ ext_modules=[
+ Extension(
+ "markupsafe._speedups", ["src/markupsafe/_speedups.c"], optional=local
+ )
+ ],
+ rust_extensions=[
+ RustExtension(
+ "markupsafe._rust_speedups", "src/rust/Cargo.toml", optional=local
+ )
+ ],
)
+else:
+ setup()
diff --git a/src/markupsafe/_rust_speedups.pyi b/src/markupsafe/_rust_speedups.pyi
new file mode 100644
index 0000000..f673240
--- /dev/null
+++ b/src/markupsafe/_rust_speedups.pyi
@@ -0,0 +1,9 @@
+from typing import Any
+from typing import Optional
+
+from . import Markup
+
+def escape(s: Any) -> Markup: ...
+def escape_silent(s: Optional[Any]) -> Markup: ...
+def soft_str(s: Any) -> str: ...
+def soft_unicode(s: Any) -> str: ...
diff --git a/src/rust/Cargo.lock b/src/rust/Cargo.lock
new file mode 100644
index 0000000..4b91064
--- /dev/null
+++ b/src/rust/Cargo.lock
@@ -0,0 +1,273 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "autocfg"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
+
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "indoc"
+version = "1.0.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da2d6f23ffea9d7e76c53eee25dfb67bcd8fde7f1198b0855350698c9f07c780"
+
+[[package]]
+name = "libc"
+version = "0.2.139"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79"
+
+[[package]]
+name = "lock_api"
+version = "0.4.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df"
+dependencies = [
+ "autocfg",
+ "scopeguard",
+]
+
+[[package]]
+name = "markupsafe-rust"
+version = "0.1.0"
+dependencies = [
+ "pyo3",
+]
+
+[[package]]
+name = "memoffset"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d61c719bcfbcf5d62b3a09efa6088de8c54bc0bfcd3ea7ae39fcc186108b8de1"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.17.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66"
+
+[[package]]
+name = "parking_lot"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
+dependencies = [
+ "lock_api",
+ "parking_lot_core",
+]
+
+[[package]]
+name = "parking_lot_core"
+version = "0.9.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba1ef8814b5c993410bb3adfad7a5ed269563e4a2f90c41f5d85be7fb47133bf"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "redox_syscall",
+ "smallvec",
+ "windows-sys",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.50"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6ef7d57beacfaf2d8aee5937dab7b7f28de3cb8b1828479bb5de2a7106f2bae2"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "pyo3"
+version = "0.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ccd4149c8c3975099622b4e1962dac27565cf5663b76452c3e2b66e0b6824277"
+dependencies = [
+ "cfg-if",
+ "indoc",
+ "libc",
+ "memoffset",
+ "parking_lot",
+ "pyo3-build-config",
+ "pyo3-ffi",
+ "pyo3-macros",
+ "unindent",
+]
+
+[[package]]
+name = "pyo3-build-config"
+version = "0.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9cd09fe469834db21ee60e0051030339e5d361293d8cb5ec02facf7fdcf52dbf"
+dependencies = [
+ "once_cell",
+ "target-lexicon",
+]
+
+[[package]]
+name = "pyo3-ffi"
+version = "0.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c427c9a96b9c5b12156dbc11f76b14f49e9aae8905ca783ea87c249044ef137"
+dependencies = [
+ "libc",
+ "pyo3-build-config",
+]
+
+[[package]]
+name = "pyo3-macros"
+version = "0.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "16b822bbba9d60630a44d2109bc410489bb2f439b33e3a14ddeb8a40b378a7c4"
+dependencies = [
+ "proc-macro2",
+ "pyo3-macros-backend",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "pyo3-macros-backend"
+version = "0.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "84ae898104f7c99db06231160770f3e40dad6eb9021daddc0fedfa3e41dff10a"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.2.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
+dependencies = [
+ "bitflags",
+]
+
+[[package]]
+name = "scopeguard"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
+
+[[package]]
+name = "smallvec"
+version = "1.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
+
+[[package]]
+name = "syn"
+version = "1.0.107"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "target-lexicon"
+version = "0.12.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9410d0f6853b1d94f0e519fb95df60f29d2c1eff2d921ffdf01a4c8a3b54f12d"
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc"
+
+[[package]]
+name = "unindent"
+version = "0.1.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e1766d682d402817b5ac4490b3c3002d91dfa0d22812f341609f97b08757359c"
+
+[[package]]
+name = "windows-sys"
+version = "0.42.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7"
+dependencies = [
+ "windows_aarch64_gnullvm",
+ "windows_aarch64_msvc",
+ "windows_i686_gnu",
+ "windows_i686_msvc",
+ "windows_x86_64_gnu",
+ "windows_x86_64_gnullvm",
+ "windows_x86_64_msvc",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.42.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.42.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.42.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.42.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.42.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.42.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.42.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd"
diff --git a/src/rust/Cargo.toml b/src/rust/Cargo.toml
new file mode 100644
index 0000000..6e1a4fd
--- /dev/null
+++ b/src/rust/Cargo.toml
@@ -0,0 +1,12 @@
+[package]
+name = "markupsafe-rust"
+version = "0.1.0"
+edition = "2021"
+publish = false
+
+[dependencies]
+pyo3 = "0.18.0"
+
+[lib]
+name = "_rust_speedups"
+crate-type = ["cdylib"]
diff --git a/src/rust/src/lib.rs b/src/rust/src/lib.rs
new file mode 100644
index 0000000..281280e
--- /dev/null
+++ b/src/rust/src/lib.rs
@@ -0,0 +1,54 @@
+use pyo3::prelude::*;
+use pyo3::types::{PyBool, PyFloat, PyLong, PyString};
+use pyo3::{intern, PyTypeInfo};
+
+fn escape_str(s: &str) -> String {
+ s.replace('&', "&amp;")
+ .replace('>', "&gt;")
+ .replace('<', "&lt;")
+ .replace('\'', "&#39;")
+ .replace('"', "&#34;")
+}
+
+#[pyfunction]
+fn escape<'p>(py: Python<'p>, s: &'p PyAny) -> PyResult<&'p PyAny> {
+ let cls = PyModule::import(py, "markupsafe")?.getattr("Markup")?;
+
+ if PyLong::is_exact_type_of(s) || PyFloat::is_exact_type_of(s) || PyBool::is_exact_type_of(s) {
+ cls.call1((s,))
+ } else if s.hasattr(intern!(py, "__html__"))? {
+ cls.call1((s.call_method0("__html__")?,))
+ } else if PyString::is_exact_type_of(s) {
+ cls.call1((escape_str(s.extract()?),))
+ } else {
+ cls.call1((escape_str(s.str()?.extract()?),))
+ }
+}
+
+#[pyfunction]
+fn escape_silent<'p>(py: Python<'p>, s: &'p PyAny) -> PyResult<&'p PyAny> {
+ if s.is_none() {
+ let cls = PyModule::import(py, "markupsafe")?.getattr("Markup")?;
+ cls.call0()
+ } else {
+ escape(py, s)
+ }
+}
+
+#[pyfunction]
+fn soft_str(s: &PyAny) -> &PyAny {
+ if PyString::is_type_of(s) {
+ s
+ } else {
+ s.str().unwrap()
+ }
+}
+
+#[pymodule]
+#[pyo3(name = "_rust_speedups")]
+fn speedups(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
+ m.add_function(wrap_pyfunction!(escape, m)?)?;
+ m.add_function(wrap_pyfunction!(escape_silent, m)?)?;
+ m.add_function(wrap_pyfunction!(soft_str, m)?)?;
+ Ok(())
+}
diff --git a/tests/conftest.py b/tests/conftest.py
index d040ea8..f05285d 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -3,7 +3,7 @@ import pytest
from markupsafe import _native
try:
- from markupsafe import _speedups
+ from markupsafe import _rust_speedups as _speedups
except ImportError:
_speedups = None # type: ignore