Skip to main content

A Polars expression plugin that validates peptide sequences at Rust speed

Project description

polars-is-peptide

A Polars expression plugin that answers one question quickly: is this string a valid peptide sequence?

A string is a peptide when every character is one of the 20 canonical amino acids — no spaces, no ambiguity codes, no punctuation, nothing else. The check is a byte scan in Rust, so it runs at about 25M rows/s: roughly 9× a per-row Python regex loop and 2× Polars' own regex engine (benchmark).

import polars as pl
from polars_is_peptide import is_peptide

df = pl.DataFrame({"seq": ["MKWVTFISLL", "MKW VTF", "MKWVTFISLLX", "mkwvtfisll", None]})

df.with_columns(valid=is_peptide("seq"))
┌─────────────┬───────┐
│ seq         ┆ valid │
│ ---         ┆ ---   │
│ str         ┆ bool  │
╞═════════════╪═══════╡
│ MKWVTFISLL  ┆ true  │   all canonical
│ MKW VTF     ┆ false │   space
│ MKWVTFISLLX ┆ false │   X is an ambiguity code, not an amino acid
│ mkwvtfisll  ┆ false │   lowercase is opt-in
│ null        ┆ null  │   missing is unknown, not invalid
└─────────────┴───────┘

Install

Not on PyPI yet — build it from source. You'll need a Rust toolchain, plus uv; Python 3.14 and Polars come along for the ride.

git clone <this repo> && cd polars_is_peptide
uv sync            # fetches Python 3.14, builds the Rust extension, installs it
uv run pytest      # 46 tests

uv sync installs a uv-managed standalone Python 3.14 (pinned in .python-version) into uv's own cache. Your system Python is not touched.

Usage

is_peptide takes a column name or an expression, and returns a boolean expression. Use it anywhere an expression goes — select, with_columns, filter, lazy or eager:

df.filter(is_peptide("seq"))                     # keep only the valid rows
df.with_columns(valid=is_peptide(pl.col("seq"))) # flag them instead
lf.filter(is_peptide("seq")).collect()           # works lazily too

There is also a .peptide namespace, if you prefer method chaining:

df.with_columns(valid=pl.col("seq").peptide.is_valid())

The alphabet

By default, only these are valid — the 20 canonical amino acids, uppercase:

A C D E F G H I K L M N P Q R S T V W Y

Four keyword arguments widen that. Each is off by default and additive, so the default behaviour can never silently start accepting something it didn't before:

Argument Default Effect
allow_lowercase False Accept lowercase residues too ("acdef"). Useful for soft-masked FASTA.
allow_ambiguous False Additionally accept B (Asx), J (Leu/Ile), X (any), Z (Glx).
allow_extended False Additionally accept O (pyrrolysine) and U (selenocysteine).
min_length 1 Sequences shorter than this are invalid. 0 accepts "".
df.with_columns(
    strict=is_peptide("seq"),
    permissive=is_peptide("seq", allow_lowercase=True, allow_ambiguous=True),
    real_peptides=is_peptide("seq", min_length=5),
)

The alphabets are exported, so you don't have to retype them:

from polars_is_peptide import AMINO_ACIDS, AMBIGUOUS_CODES, EXTENDED_RESIDUES

Two behaviours worth knowing

Nulls stay null. They do not become false. A missing sequence is unknown, not invalid, and the difference matters at the point you filter:

df.filter(is_peptide("seq"))    # drops null rows (null is not true)
df.filter(~is_peptide("seq"))   # ALSO drops them (NOT null is null)

A null row falls through both filters. That's deliberate — you should decide what a missing sequence means for your pipeline, rather than have the plugin quietly file it under "invalid". If you do want nulls treated as invalid, say so explicitly:

df.filter(~is_peptide("seq").fill_null(False))   # rejected, nulls included

The empty string is false, because a peptide has at least one residue. Pass min_length=0 for the vacuous-truth reading.

Non-ASCII input is rejected rather than raising: validation is a byte scan against a 256-entry table with no entry set at or above 0x80, so no byte of a multi-byte character can be mistaken for a residue.

Performance

1,000,000 sequences averaging 50 residues (50.4M residues total), on this machine:

Implementation Time Throughput
polars-is-peptide 0.039 s 25.6M rows/s
Polars native str.contains regex 0.091 s 11.0M rows/s 2.3× slower
Python re.match per row 0.341 s 2.9M rows/s 8.7× slower
Python set-membership per row 1.366 s 0.7M rows/s 34.9× slower

All four agree on all million rows. Reproduce with uv run python tmp/demo.py.

The speed comes from compiling the options into a [bool; 256] lookup table once per call, which turns per-character validation into a single indexed load, then scanning each row's bytes with early exit on the first invalid one. No allocation per row, and no decoding — the table's shape is what makes the byte scan UTF-8 safe.

Development

uv sync                      # build the Rust extension + install dev deps
uv run pytest                # test
uv run maturin develop -r    # rebuild in release mode after editing Rust
cargo check --lib            # fast Rust-only feedback loop

Layout: the Rust kernel is src/alphabet.rs (the lookup table) and src/expressions.rs (the Polars expression); the Python API is python/polars_is_peptide/init.py. tmp/demo.py is a runnable walkthrough, and PLAN.md records the design decisions and why they went the way they did.

One pinning note, because it will bite whoever upgrades next: pyo3 is held at 0.28 because that's what pyo3-polars 0.27 links against. Bumping pyo3 on its own fails the build with a links = "python" conflict. Bump the two together, or neither.

Releasing

Wheels are built and published by CI on a version tag, using PyPI Trusted Publishing — there is no API token stored in the repo or in GitHub secrets.

# bump the version in pyproject.toml AND Cargo.toml, add a CHANGELOG entry, then:
git tag v0.1.0 && git push origin v0.1.0

That builds Linux (x86_64, aarch64), macOS (x86_64, arm64) and Windows (x64) wheels plus an sdist, runs the test suite against each built wheel, and publishes to PyPI. The matrix is deliberately narrower than maturin's default, which also emits armv7, s390x, ppc64le and 32-bit targets — Polars publishes wheels for none of those, so our own dependency wouldn't resolve there.

Because the wheels are abi3-py314, one wheel per platform covers 3.14 and every later 3.x. Nothing needs rebuilding when 3.15 lands.

Licence

MIT.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

polars_is_peptide-0.1.0.tar.gz (38.6 kB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

polars_is_peptide-0.1.0-cp314-abi3-win_amd64.whl (4.2 MB view details)

Uploaded CPython 3.14+Windows x86-64

polars_is_peptide-0.1.0-cp314-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (3.6 MB view details)

Uploaded CPython 3.14+manylinux: glibc 2.17+ x86-64

polars_is_peptide-0.1.0-cp314-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (3.2 MB view details)

Uploaded CPython 3.14+manylinux: glibc 2.17+ ARM64

polars_is_peptide-0.1.0-cp314-abi3-macosx_11_0_arm64.whl (3.2 MB view details)

Uploaded CPython 3.14+macOS 11.0+ ARM64

polars_is_peptide-0.1.0-cp314-abi3-macosx_10_12_x86_64.whl (3.6 MB view details)

Uploaded CPython 3.14+macOS 10.12+ x86-64

File details

Details for the file polars_is_peptide-0.1.0.tar.gz.

File metadata

  • Download URL: polars_is_peptide-0.1.0.tar.gz
  • Upload date:
  • Size: 38.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for polars_is_peptide-0.1.0.tar.gz
Algorithm Hash digest
SHA256 f53bae6ae4bb63428fb635f2b3c326c37b79b3c8057dc38af15aadc751552d48
MD5 28796b9b2695f33c76a75a847bd6c4a6
BLAKE2b-256 06452b7ec58db38ac4d45bb28c773382ba296babd0f5c22ff8d544e1e6dff0fa

See more details on using hashes here.

Provenance

The following attestation bundles were made for polars_is_peptide-0.1.0.tar.gz:

Publisher: CI.yml on drchristhorpe/polars-is-peptide

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file polars_is_peptide-0.1.0-cp314-abi3-win_amd64.whl.

File metadata

File hashes

Hashes for polars_is_peptide-0.1.0-cp314-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 0353c75fe6a518c6f343dc6d6841f149e6397d13d5bea6f1a2ad47245eb93679
MD5 434ed9bc7f5af312e0c7c71a3ac989c5
BLAKE2b-256 c45fb111d5fc327929e9de86834e0b381412438adf91c0fee09bb0652f5dc829

See more details on using hashes here.

Provenance

The following attestation bundles were made for polars_is_peptide-0.1.0-cp314-abi3-win_amd64.whl:

Publisher: CI.yml on drchristhorpe/polars-is-peptide

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file polars_is_peptide-0.1.0-cp314-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for polars_is_peptide-0.1.0-cp314-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 488ff25a70e6d20fe12f623eedfdb3314ac93b5436718118bd81f7853edcd9fc
MD5 177bbef00170b3cf7cb5be5d94551f55
BLAKE2b-256 f53d254fc1d39c9c8d999e9cf116e33542092b10ee48c54eb9ad5797829dd791

See more details on using hashes here.

Provenance

The following attestation bundles were made for polars_is_peptide-0.1.0-cp314-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:

Publisher: CI.yml on drchristhorpe/polars-is-peptide

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file polars_is_peptide-0.1.0-cp314-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.

File metadata

File hashes

Hashes for polars_is_peptide-0.1.0-cp314-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 505a3c74e4e21e25fe4f43616cabdf2c025e4784e4584638499458abbd25fd25
MD5 33e287efe8773d1992b877cb3c676d84
BLAKE2b-256 b791cdf1ab8ec3efe7f9a02cf45a60efcf9bf23c5e4494199c2860afd3032081

See more details on using hashes here.

Provenance

The following attestation bundles were made for polars_is_peptide-0.1.0-cp314-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl:

Publisher: CI.yml on drchristhorpe/polars-is-peptide

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file polars_is_peptide-0.1.0-cp314-abi3-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for polars_is_peptide-0.1.0-cp314-abi3-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 136e51491f0615cd5c985c25c49c41c18d5322ba481828d2e9ee3a42cc3ee9d7
MD5 6943209fae2b4943eb3110c56b46c924
BLAKE2b-256 f064edf4f17e61a4daae66e31372a4c152dfcfc91a9fa98a04feaed472528844

See more details on using hashes here.

Provenance

The following attestation bundles were made for polars_is_peptide-0.1.0-cp314-abi3-macosx_11_0_arm64.whl:

Publisher: CI.yml on drchristhorpe/polars-is-peptide

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file polars_is_peptide-0.1.0-cp314-abi3-macosx_10_12_x86_64.whl.

File metadata

File hashes

Hashes for polars_is_peptide-0.1.0-cp314-abi3-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 25388ec0e2f47fde380293a9631473d94f965fb4bab31acbc798b86a0ae5c6b8
MD5 905c9411a4e088e6e59c26ad0077fb1d
BLAKE2b-256 2589e7a46b031a184acd44852539a52e4343f5244aa76864b71615a96bf846fe

See more details on using hashes here.

Provenance

The following attestation bundles were made for polars_is_peptide-0.1.0-cp314-abi3-macosx_10_12_x86_64.whl:

Publisher: CI.yml on drchristhorpe/polars-is-peptide

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page