Skip to main content

scigantic-bindingdb

CI PyPI PyPI - Python Version License

Query BindingDB directly from a public S3 mirror with DuckDB.

import scigantic_bindingdb as bindingdb

df = bindingdb.query("""
    SELECT reactant_set_id, ligand_smiles, ki_nm_value
    FROM measurements
    WHERE ki_nm_value IS NOT NULL
    LIMIT 5
""")

That query runs against s3://scigantic-bindingdb over DuckDB's httpfs extension. Nothing is downloaded first, and there's no local database file sitting on disk afterward.

Installation

$ pip install scigantic-bindingdb

Repeat calls reuse the connection

measurements(), chembl_bridge(), dti_pairs() and query() each open a connection to run one query. Under the hood that's a cursor() on a shared, lazily-created base connection per release, not a fresh duckdb.connect() every time: the first call in a process pays for INSTALL/LOAD httpfs, the S3 secret, and registering the five core views, and every later call for that release skips straight to the query.

Measured, not asserted, on 2026-08-29: 8 repeat calls to measurements(uniprot_id="P00533", endpoint="ki") in a loop, before vs after this was fixed:

measured
8 calls, fresh connection each time 318s total, ~39.8s/call
8 calls, shared base connection + cursor() 5.3s total, ~0.66s/call (~60x)

connect() still returns something you can close(), and closing it only closes your cursor; the base connection stays open for the next call to reuse. A single DuckDB connection isn't safe for concurrent execute() calls from multiple threads, so each call gets its own cursor rather than the base connection itself, which is what makes this safe from a thread pool too.

What's different about BindingDB

Every row is one binding measurement (Ki, IC50, Kd or EC50) between one ligand and one protein target, rather than ChEMBL's assay-centric bioactivity record. BindingDB ships no relational database, just a flat TSV; the mirror normalizes it into measurements (one row per measurement), target_chains (one row per protein chain of the target, since BindingDB's raw format repeats a column block once per chain in a multimer) and target_chain_names.

Measurements, filtered the way that avoids the two sharp edges

df = bindingdb.measurements(uniprot_id="P00533", endpoint="ki")  # EGFR

Two things this does that a raw query on measurements doesn't do for you:

Censored values stay out unless you ask for them. Ki/IC50/Kd/EC50 are occasionally reported as >X or <X rather than an exact value, the same idea as ChEMBL's standard_relation. exact_only=True, the default, keeps only rows where that endpoint's qualifier is =:

df = bindingdb.measurements(uniprot_id="P00533", endpoint="ic50", exact_only=False)  # include censored bounds too

Target filtering doesn't fan out on multichain complexes. uniprot_id matches against target_chains with an EXISTS check, not a JOIN, so a target whose accession appears on more than one chain of the same complex still returns each measurement once.

bindingdb.query() still reaches the raw tables directly for anything this leaves out.

Cross-referencing ChEMBL

BindingDB ingests ChEMBL as one of its own curated source feeds (51.3% of measurements in the 202608 release), and ChEMBL separately absorbs some BindingDB patent-derived bioactivity data, so the two archives are not independent corpora. derived/bindingdb_chembl_bridge.parquet, built once at mirror time, joins measurements to scigantic-chembl by BindingDB's own chembl_id column where present (authoritative) and falls back to an exact InChIKey match where it's missing:

df = bindingdb.chembl_bridge(reactant_set_id="50000001")

with_names=True (the default) also reaches into the live scigantic-chembl mirror for the matched compound's ChEMBL preferred name, a second public-bucket read over the same connection: not a mount-level dependency between the two archives, just a query-time join across two buckets that are both public and read-only here.

Drug-target-interaction pairs

BindingDB is the dataset most DTI/proteochemometric tooling (like DeepPurpose) is built around, specifically because it ships a full protein sequence alongside every affinity measurement, something ChEMBL's bioactivity tables don't do as directly. derived/dti_pairs.parquet is BindingDB reshaped into the (ligand, target, affinity) triples a model trains on, done once rather than re-derived by every caller:

df = bindingdb.dti_pairs(endpoint="ki", single_chain_only=True)
  reactant_set_id  ligand_smiles       target_sequence  uniprot_id  endpoint  affinity_nm  p_affinity
           764556  Cc1ncoc1-c1nnc...   MASLSQLSSHLN...      P35462        ki         1.74    8.759451

Only exact measurements (never a censored >X/<X bound treated as a real label), and p_affinity is already computed as -log10(affinity_nm * 1e-9), the same transform as ChEMBL's pchembl_value. 2,589,053 pairs across the four endpoints in the 202608 release, 1,163,672 distinct ligands, 9,219 distinct UniProt targets.

Multichain targets are represented by chain 1's sequence only, standard practice for DTI benchmarks. Pass single_chain_only=True to drop the 5.7% of rows where that simplifies an actual multi-protein complex, if single-chain purity matters for your model. See derived/DTI_README.md in the mirror for the exact filters applied.

Working offline

Off by default, since zero setup is the whole point. Turn it on to run the same queries repeatedly without re-fetching from S3:

import scigantic_bindingdb as bindingdb

bindingdb.enable_cache()
df = bindingdb.chembl_bridge()  # downloads the bridge table once, then reads from disk

chembl_bridge() and dti_pairs() each need exactly one derived file, so caching downloads that one file to ~/.cache/scigantic-bindingdb (override with enable_cache(cache_dir=...) or the SCIGANTIC_BINDINGDB_CACHE environment variable) and reuses it after that. Concurrent callers racing the first download of the same file wait for it rather than each downloading their own copy.

connect(), query() and measurements() don't participate in this: connect() registers five core tables as views the first time a release is used, so caching them there would mean the first call for any release eagerly downloads everything regardless of what the query actually touches. Cache one table yourself if you want it locally: bindingdb.cache_resolve("202608/parquet/measurements.parquet") downloads it and returns the local path, usable directly in read_parquet(...).

What's mirrored

bindingdb.releases()
release raw tables ChEMBL bridge DTI pairs
202608 yes yes yes

This table isn't hardcoded. releases() reads a small manifest published alongside each mirror run. If it can't be reached, calls fall back to the snapshot shipped with whatever version you have installed and print a warning, rather than failing outright.

Not mirrored yet: BindingDB's 3D SDF structures and precomputed similarity/substructure search (no fingerprint corpus has been built for this archive). bindingdb.query() still reaches every raw table the mirror carries.

Command line

$ scigantic-bindingdb info
$ scigantic-bindingdb query "SELECT count(*) FROM measurements" --release 202608

License

MIT-0. See LICENSE. This covers the code in this package only.

Data license

BindingDB's underlying data is not under one license: it's a row-by-row mix, determined by each measurement's curation_source column. BindingDB licenses its own staff-curated rows under CC BY 3.0, but rows imported from ChEMBL (curation_source = 'ChEMBL', 51.3% of measurements in the 202608 release) carry ChEMBL's own CC BY-SA 3.0 terms instead, share-alike included. Check curation_source before redistributing a subset of measurements or dti_pairs() to know which terms apply to those specific rows.

chembl_bridge() results specifically warrant care regardless of a matched measurement's own curation_source: every row includes chembl_id/chembl_molregno matched from ChEMBL's own molecule_dictionary, and with the default with_names=True, ChEMBL's own pref_name. Those columns carry ChEMBL's CC BY-SA 3.0 terms, independent of whichever license applies to the rest of that row.

Download files

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

Source Distribution

scigantic_bindingdb-0.2.5.tar.gz (26.3 kB view details)

Uploaded Source

Built Distribution

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

scigantic_bindingdb-0.2.5-py3-none-any.whl (20.9 kB view details)

Uploaded Python 3

File details

Details for the file scigantic_bindingdb-0.2.5.tar.gz.

File metadata

  • Download URL: scigantic_bindingdb-0.2.5.tar.gz
  • Upload date:
  • Size: 26.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.11.16

File hashes

Hashes for scigantic_bindingdb-0.2.5.tar.gz
Algorithm Hash digest
SHA256 015883dffcdecdf4be04f20d0f7771ea2016ec193c5d4d640e891e9067505f3c
MD5 3ec2b6e60ef1af7a2f3c91c6603730e4
BLAKE2b-256 0d3c7b5aaa78d8a236fb3374a99c6b67043c6f7081d47a0d9af5cea9aaeeb907

See more details on using hashes here.

File details

Details for the file scigantic_bindingdb-0.2.5-py3-none-any.whl.

File metadata

File hashes

Hashes for scigantic_bindingdb-0.2.5-py3-none-any.whl
Algorithm Hash digest
SHA256 8a80a7152cca45cf68b41adbecb6c22beb132db2d67ff0eee0255635a792af27
MD5 b5b6c831fbb656b49947beac23f91dd7
BLAKE2b-256 a05c43265ed807acda47db6eba221551aeeb027d526e28b196f4746642361b3c

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.2.5 This release

2 files

0.2.4

2 files

0.2.3

2 files

0.2.2

2 files

0.2.1

2 files

0.2.0

2 files

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