From fff25f6a772fa49ef69a141d777ef6f0380349e7 Mon Sep 17 00:00:00 2001 From: namingbe Date: Mon, 6 Apr 2026 05:04:18 +0200 Subject: [PATCH 1/2] wire nsv-rust for dumps/loads --- MANIFEST.in | 4 +- nsv/core.py | 8 + pyproject.toml | 3 + rust/Cargo.lock | 313 +++++++++++++++++++++++++++++++++++++++ rust/Cargo.toml | 12 ++ rust/src/lib.rs | 35 +++++ setup.py | 9 ++ tests/test_edge_cases.py | 5 +- 8 files changed, 387 insertions(+), 2 deletions(-) create mode 100644 pyproject.toml create mode 100644 rust/Cargo.lock create mode 100644 rust/Cargo.toml create mode 100644 rust/src/lib.rs diff --git a/MANIFEST.in b/MANIFEST.in index 3ee42b4..1f98841 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,3 +1,5 @@ include README.md include LICENSE -recursive-include nsv *.py \ No newline at end of file +recursive-include nsv *.py +include rust/Cargo.toml +recursive-include rust/src * diff --git a/nsv/core.py b/nsv/core.py index 7dc6214..0912726 100644 --- a/nsv/core.py +++ b/nsv/core.py @@ -35,3 +35,11 @@ def dumps(data: Iterable[Iterable[str]]) -> str: lines.append(Writer.escape(cell)) lines.append('') return ''.join(f'{line}\n' for line in lines) + +# Try to use fast Rust implementation if available +try: + from ._rust import loads as _loads_rs, dumps as _dumps_rs + loads = _loads_rs + dumps = _dumps_rs +except ImportError: + pass diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..0de2d7c --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["setuptools>=61.0", "setuptools-rust>=1.0"] +build-backend = "setuptools.build_meta" diff --git a/rust/Cargo.lock b/rust/Cargo.lock new file mode 100644 index 0000000..207afff --- /dev/null +++ b/rust/Cargo.lock @@ -0,0 +1,313 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "_rust" +version = "0.1.0" +dependencies = [ + "nsv", + "pyo3", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "bitflags" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "indoc" +version = "2.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706" +dependencies = [ + "rustversion", +] + +[[package]] +name = "libc" +version = "0.2.184" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "nsv" +version = "0.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fca6b22f417de46dc8f501eafa3a1db1122ab7c9abe02e00e6cf598efbe73bc9" +dependencies = [ + "memchr", + "rayon", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "portable-atomic" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "pyo3" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53bdbb96d49157e65d45cc287af5f32ffadd5f4761438b527b055fb0d4bb8233" +dependencies = [ + "cfg-if", + "indoc", + "libc", + "memoffset", + "parking_lot", + "portable-atomic", + "pyo3-build-config", + "pyo3-ffi", + "pyo3-macros", + "unindent", +] + +[[package]] +name = "pyo3-build-config" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "deaa5745de3f5231ce10517a1f5dd97d53e5a2fd77aa6b5842292085831d48d7" +dependencies = [ + "once_cell", + "target-lexicon", +] + +[[package]] +name = "pyo3-ffi" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b42531d03e08d4ef1f6e85a2ed422eb678b8cd62b762e53891c05faf0d4afa" +dependencies = [ + "libc", + "pyo3-build-config", +] + +[[package]] +name = "pyo3-macros" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7305c720fa01b8055ec95e484a6eca7a83c841267f0dd5280f0c8b8551d2c158" +dependencies = [ + "proc-macro2", + "pyo3-macros-backend", + "quote", + "syn", +] + +[[package]] +name = "pyo3-macros-backend" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c7e9b68bb9c3149c5b0cade5d07f953d6d125eb4337723c4ccdb665f1f96185" +dependencies = [ + "heck", + "proc-macro2", + "pyo3-build-config", + "quote", + "syn", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rayon" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "target-lexicon" +version = "0.12.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unindent" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7264e107f553ccae879d21fbea1d6724ac785e8c3bfc762137959b5802826ef3" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" diff --git a/rust/Cargo.toml b/rust/Cargo.toml new file mode 100644 index 0000000..6da9d42 --- /dev/null +++ b/rust/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "_rust" +version = "0.1.0" +edition = "2021" + +[lib] +name = "_rust" +crate-type = ["cdylib"] + +[dependencies] +pyo3 = { version = "0.20", features = ["extension-module"] } +nsv = "0.0.12" diff --git a/rust/src/lib.rs b/rust/src/lib.rs new file mode 100644 index 0000000..e95d01e --- /dev/null +++ b/rust/src/lib.rs @@ -0,0 +1,35 @@ +use pyo3::prelude::*; +use pyo3::types::PyList; + +/// Parse NSV string into a list of lists of str +#[pyfunction] +fn loads(py: Python, s: &str) -> PyResult { + let data = nsv::decode(s); + + // Convert Vec> to Python list of lists + let result = PyList::empty(py); + for row in data { + let py_row = PyList::empty(py); + for cell in row { + py_row.append(cell)?; + } + result.append(py_row)?; + } + + Ok(result.into()) +} + +/// Serialize data to NSV string +#[pyfunction] +fn dumps(data: Vec>) -> PyResult { + Ok(nsv::encode(&data)) +} + +/// A Python module implemented in Rust. +#[pymodule] +fn _rust(_py: Python, m: &PyModule) -> PyResult<()> { + m.add_function(wrap_pyfunction!(loads, m)?)?; + m.add_function(wrap_pyfunction!(dumps, m)?)?; + Ok(()) +} + diff --git a/setup.py b/setup.py index 949a996..7be517e 100644 --- a/setup.py +++ b/setup.py @@ -1,5 +1,11 @@ from setuptools import setup, find_packages +try: + from setuptools_rust import Binding, RustExtension + rust_extensions = [RustExtension("nsv._rust", path="rust/Cargo.toml", binding=Binding.PyO3, optional=True)] +except ImportError: + rust_extensions = [] + setup( name="nsv", version="0.2.2", @@ -36,4 +42,7 @@ extras_require={ "pandas": ["pandas"], }, + rust_extensions=rust_extensions, + setup_requires=["setuptools-rust>=1.0"] if rust_extensions else [], + zip_safe=False, ) diff --git a/tests/test_edge_cases.py b/tests/test_edge_cases.py index 64ce60d..16f5e15 100644 --- a/tests/test_edge_cases.py +++ b/tests/test_edge_cases.py @@ -6,7 +6,10 @@ class TestEdgeCases(unittest.TestCase): def test_long_strings(self): """Test handling of long string values.""" - long_string = ''.join(chr(x) for x in range(11, 0x110000)) + # Skip surrogates (0xD800-0xDFFF): not valid UTF-8, not a use case for NSV. + long_string = ''.join( + chr(x) for x in range(11, 0x110000) if not (0xD800 <= x <= 0xDFFF) + ) data = [ ["normal", long_string], [long_string, "normal"] From 756e5dd64acfd95c4395ab225272754123bcf948 Mon Sep 17 00:00:00 2001 From: namingbe Date: Mon, 6 Apr 2026 05:07:34 +0200 Subject: [PATCH 2/2] bruh --- .github/workflows/tests.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 0ad3df6..f8547d9 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -2,7 +2,6 @@ name: Tests on: push: - branches: [ master, claude/* ] pull_request: branches: [ master ]