Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Changelog

All notable changes to this project are documented here. Versions correspond to PyPI releases.

## 0.2.5 - 2026-08-31

- `query()` now stamps `df.attrs["bindingdb_release"]` with the release actually used, matching scigantic-chembl's own `query()`, which already does this for `chembl_release`.
- Added a "Data license" section to the README: BindingDB's underlying data is a per-row mix of CC BY 3.0 (BindingDB's own curated rows) and CC BY-SA 3.0 (rows imported from ChEMBL, keyed by the `curation_source` column), separate from and not superseded by this package's own MIT-0 code license. `chembl_bridge()` results specifically carry ChEMBL's share-alike terms on their ChEMBL-matched columns regardless of the underlying measurement's own source.
- Fixed repo discoverability: added GitHub topics and a homepage link (was previously unset).
- Added upper bounds to the `duckdb` and `pandas` dependency constraints, matching scigantic-chembl's bounds for the same libraries.
- Added `CHANGELOG.md`.
- Added `pytest-cov` as a local, non-CI-gating coverage option.

## 0.2.4 - 2026-08-29

- Coordinate concurrent first downloads of the same cache key: `resolve()`'s check-then-download is now guarded by a per-key lock, so concurrent callers asking for an uncached key wait for one download instead of each starting their own (#3).

## 0.2.3 - 2026-08-29

- Fixed a `chembl_bridge()`/`dti_pairs()` conflict under concurrent calls: both used a named `CREATE OR REPLACE VIEW` on the shared base connection, which raced on DuckDB's catalog under concurrent calls (#2).

## 0.2.2 - 2026-08-29

- `connect()` now reuses the DuckDB base connection across calls instead of rebuilding it every time.
- Fixed a cache download race: the temp filename used during download was deterministic (derived only from the cache key), so two threads racing to fill the same key could collide and raise `FileNotFoundError` on `os.replace()` (#1).

## 0.2.1 - 2026-08-28

- Validate `dti_pairs()`'s `endpoint` argument, matching `measurements()`'s existing validation.

## 0.2.0 - 2026-08-26

- Added `dti_pairs()`, a ready drug-target-interaction training table.

## 0.1.0 - 2026-08-26

- Initial release: query BindingDB from the public S3 mirror with DuckDB.
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,4 +132,10 @@ $ scigantic-bindingdb query "SELECT count(*) FROM measurements" --release 202608

## License

MIT-0. See [LICENSE](LICENSE).
MIT-0. See [LICENSE](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](https://www.bindingdb.org/rwd/bind/info.jsp) its own staff-curated rows under [CC BY 3.0](https://creativecommons.org/licenses/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](https://creativecommons.org/licenses/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.
15 changes: 11 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "scigantic-bindingdb"
version = "0.2.4"
version = "0.2.5"
description = "Query BindingDB directly from a public S3 mirror with DuckDB, including a ChEMBL cross-reference bridge table and a ready drug-target-interaction training table."
readme = "README.md"
requires-python = ">=3.10"
Expand All @@ -25,12 +25,12 @@ classifiers = [
]

dependencies = [
"duckdb>=0.10",
"pandas>=1.5",
"duckdb>=0.10,<2",
"pandas>=1.5,<4",
]

[project.optional-dependencies]
dev = ["pytest>=7", "mypy>=1.10", "pandas-stubs"]
dev = ["pytest>=7", "pytest-cov>=5", "mypy>=1.10", "pandas-stubs"]

[project.urls]
Homepage = "https://scigantic.com"
Expand All @@ -49,5 +49,12 @@ scigantic_bindingdb = ["py.typed"]
[tool.pytest.ini_options]
testpaths = ["tests"]

[tool.coverage.run]
source = ["scigantic_bindingdb"]
# Not wired into CI as a gate: this suite runs real queries against the live
# S3 mirror, so a coverage threshold would be one more thing that can fail
# for reasons unrelated to a change. `pytest --cov` is available locally for
# anyone who wants the number.

[tool.mypy]
strict = true
7 changes: 5 additions & 2 deletions src/scigantic_bindingdb/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,8 +123,11 @@ def query(sql: str, release: str | None = None) -> "pd.DataFrame":
For several queries against the same release, call connect() once and
reuse it instead, to avoid opening and closing a cursor per query.
"""
con = connect(release)
resolved_release = release or latest()
con = connect(resolved_release)
try:
return con.execute(sql).df()
df = con.execute(sql).df()
df.attrs["bindingdb_release"] = resolved_release
return df
finally:
con.close()
13 changes: 13 additions & 0 deletions tests/test_connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,19 @@ def test_query_returns_dataframe_with_expected_columns():
assert len(df) == 5


def test_query_records_bindingdb_release_in_attrs():
# Matches scigantic_chembl's query(), which stamps df.attrs["chembl_release"]
# the same way: the release actually used should travel with the result,
# not just be implicit in whatever the caller happened to pass.
df = bindingdb.query("SELECT 1", release="202608")
assert df.attrs["bindingdb_release"] == "202608"


def test_query_omitted_release_records_resolved_release_in_attrs():
df = bindingdb.query("SELECT 1")
assert df.attrs["bindingdb_release"] == bindingdb.latest()


def test_connect_registers_all_five_core_tables():
con = bindingdb.connect()
try:
Expand Down
Loading