scigantic-bindingdb 0.2.2__tar.gz → 0.2.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {scigantic_bindingdb-0.2.2/src/scigantic_bindingdb.egg-info → scigantic_bindingdb-0.2.4}/PKG-INFO +2 -2
- {scigantic_bindingdb-0.2.2 → scigantic_bindingdb-0.2.4}/README.md +1 -1
- {scigantic_bindingdb-0.2.2 → scigantic_bindingdb-0.2.4}/pyproject.toml +1 -1
- {scigantic_bindingdb-0.2.2 → scigantic_bindingdb-0.2.4}/src/scigantic_bindingdb/cache.py +29 -5
- {scigantic_bindingdb-0.2.2 → scigantic_bindingdb-0.2.4}/src/scigantic_bindingdb/chembl_bridge.py +12 -6
- {scigantic_bindingdb-0.2.2 → scigantic_bindingdb-0.2.4}/src/scigantic_bindingdb/dti_pairs.py +7 -2
- {scigantic_bindingdb-0.2.2 → scigantic_bindingdb-0.2.4/src/scigantic_bindingdb.egg-info}/PKG-INFO +2 -2
- {scigantic_bindingdb-0.2.2 → scigantic_bindingdb-0.2.4}/tests/test_cache.py +44 -0
- {scigantic_bindingdb-0.2.2 → scigantic_bindingdb-0.2.4}/tests/test_chembl_bridge.py +24 -0
- {scigantic_bindingdb-0.2.2 → scigantic_bindingdb-0.2.4}/tests/test_dti_pairs.py +24 -0
- {scigantic_bindingdb-0.2.2 → scigantic_bindingdb-0.2.4}/LICENSE +0 -0
- {scigantic_bindingdb-0.2.2 → scigantic_bindingdb-0.2.4}/setup.cfg +0 -0
- {scigantic_bindingdb-0.2.2 → scigantic_bindingdb-0.2.4}/src/scigantic_bindingdb/__init__.py +0 -0
- {scigantic_bindingdb-0.2.2 → scigantic_bindingdb-0.2.4}/src/scigantic_bindingdb/__main__.py +0 -0
- {scigantic_bindingdb-0.2.2 → scigantic_bindingdb-0.2.4}/src/scigantic_bindingdb/_constants.py +0 -0
- {scigantic_bindingdb-0.2.2 → scigantic_bindingdb-0.2.4}/src/scigantic_bindingdb/cli.py +0 -0
- {scigantic_bindingdb-0.2.2 → scigantic_bindingdb-0.2.4}/src/scigantic_bindingdb/connection.py +0 -0
- {scigantic_bindingdb-0.2.2 → scigantic_bindingdb-0.2.4}/src/scigantic_bindingdb/measurements.py +0 -0
- {scigantic_bindingdb-0.2.2 → scigantic_bindingdb-0.2.4}/src/scigantic_bindingdb/py.typed +0 -0
- {scigantic_bindingdb-0.2.2 → scigantic_bindingdb-0.2.4}/src/scigantic_bindingdb/releases.py +0 -0
- {scigantic_bindingdb-0.2.2 → scigantic_bindingdb-0.2.4}/src/scigantic_bindingdb.egg-info/SOURCES.txt +0 -0
- {scigantic_bindingdb-0.2.2 → scigantic_bindingdb-0.2.4}/src/scigantic_bindingdb.egg-info/dependency_links.txt +0 -0
- {scigantic_bindingdb-0.2.2 → scigantic_bindingdb-0.2.4}/src/scigantic_bindingdb.egg-info/entry_points.txt +0 -0
- {scigantic_bindingdb-0.2.2 → scigantic_bindingdb-0.2.4}/src/scigantic_bindingdb.egg-info/requires.txt +0 -0
- {scigantic_bindingdb-0.2.2 → scigantic_bindingdb-0.2.4}/src/scigantic_bindingdb.egg-info/top_level.txt +0 -0
- {scigantic_bindingdb-0.2.2 → scigantic_bindingdb-0.2.4}/tests/test_cli.py +0 -0
- {scigantic_bindingdb-0.2.2 → scigantic_bindingdb-0.2.4}/tests/test_connection.py +0 -0
- {scigantic_bindingdb-0.2.2 → scigantic_bindingdb-0.2.4}/tests/test_measurements.py +0 -0
- {scigantic_bindingdb-0.2.2 → scigantic_bindingdb-0.2.4}/tests/test_releases.py +0 -0
{scigantic_bindingdb-0.2.2/src/scigantic_bindingdb.egg-info → scigantic_bindingdb-0.2.4}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: scigantic-bindingdb
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.4
|
|
4
4
|
Summary: 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.
|
|
5
5
|
Author: Scigantic
|
|
6
6
|
License: MIT-0
|
|
@@ -136,7 +136,7 @@ bindingdb.enable_cache()
|
|
|
136
136
|
df = bindingdb.chembl_bridge() # downloads the bridge table once, then reads from disk
|
|
137
137
|
```
|
|
138
138
|
|
|
139
|
-
`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.
|
|
139
|
+
`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.
|
|
140
140
|
|
|
141
141
|
`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(...)`.
|
|
142
142
|
|
|
@@ -105,7 +105,7 @@ bindingdb.enable_cache()
|
|
|
105
105
|
df = bindingdb.chembl_bridge() # downloads the bridge table once, then reads from disk
|
|
106
106
|
```
|
|
107
107
|
|
|
108
|
-
`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.
|
|
108
|
+
`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.
|
|
109
109
|
|
|
110
110
|
`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(...)`.
|
|
111
111
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "scigantic-bindingdb"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.4"
|
|
8
8
|
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."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -23,6 +23,7 @@ from __future__ import annotations
|
|
|
23
23
|
|
|
24
24
|
import os
|
|
25
25
|
import sys
|
|
26
|
+
import threading
|
|
26
27
|
import urllib.request
|
|
27
28
|
import uuid
|
|
28
29
|
from pathlib import Path
|
|
@@ -34,6 +35,24 @@ _cache_dir: Path | None = None
|
|
|
34
35
|
|
|
35
36
|
_CHUNK_BYTES = 1024 * 1024
|
|
36
37
|
|
|
38
|
+
# One lock per key, created on first use. Guards resolve()'s
|
|
39
|
+
# check-then-download against concurrent callers asking for the same key
|
|
40
|
+
# at once: without this, N threads racing the first resolve() of a key
|
|
41
|
+
# each see it missing and each download it in full, rather than one
|
|
42
|
+
# downloading while the rest wait and reuse the result. Verified directly:
|
|
43
|
+
# 16 threads calling chembl_bridge() concurrently on an empty cache
|
|
44
|
+
# triggered 16 separate downloads of the same file before this fix.
|
|
45
|
+
_resolve_locks: dict[str, threading.Lock] = {}
|
|
46
|
+
_resolve_locks_guard = threading.Lock()
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _lock_for(key: str) -> threading.Lock:
|
|
50
|
+
lock = _resolve_locks.get(key)
|
|
51
|
+
if lock is not None:
|
|
52
|
+
return lock
|
|
53
|
+
with _resolve_locks_guard:
|
|
54
|
+
return _resolve_locks.setdefault(key, threading.Lock())
|
|
55
|
+
|
|
37
56
|
|
|
38
57
|
def _default_cache_dir() -> Path:
|
|
39
58
|
if sys.platform == "win32":
|
|
@@ -114,7 +133,9 @@ def resolve(key: str) -> str:
|
|
|
114
133
|
`key` is a path relative to the bucket root, e.g.
|
|
115
134
|
"202608/derived/bindingdb_chembl_bridge.parquet". Downloads to the
|
|
116
135
|
cache on first access; later calls for the same key reuse the local
|
|
117
|
-
file without touching the network.
|
|
136
|
+
file without touching the network. Concurrent callers asking for the
|
|
137
|
+
same key while it's still downloading wait for that download rather
|
|
138
|
+
than each starting their own.
|
|
118
139
|
"""
|
|
119
140
|
if not _enabled:
|
|
120
141
|
return f"s3://{BUCKET}/{key}"
|
|
@@ -124,8 +145,11 @@ def resolve(key: str) -> str:
|
|
|
124
145
|
if local_path.exists():
|
|
125
146
|
return str(local_path)
|
|
126
147
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
148
|
+
with _lock_for(key):
|
|
149
|
+
if local_path.exists(): # another thread finished it while we waited
|
|
150
|
+
return str(local_path)
|
|
151
|
+
local_path.parent.mkdir(parents=True, exist_ok=True)
|
|
152
|
+
url = f"https://{BUCKET}.s3.{REGION}.amazonaws.com/{key}"
|
|
153
|
+
print(f"scigantic-bindingdb: caching {key} ...", file=sys.stderr, flush=True)
|
|
154
|
+
_atomic_download(url, local_path)
|
|
131
155
|
return str(local_path)
|
{scigantic_bindingdb-0.2.2 → scigantic_bindingdb-0.2.4}/src/scigantic_bindingdb/chembl_bridge.py
RENAMED
|
@@ -49,10 +49,6 @@ def chembl_bridge(
|
|
|
49
49
|
con = connect(release)
|
|
50
50
|
try:
|
|
51
51
|
bridge_path = _resolve(f"{release}/derived/bindingdb_chembl_bridge.parquet")
|
|
52
|
-
con.execute(
|
|
53
|
-
"CREATE OR REPLACE VIEW chembl_bridge AS "
|
|
54
|
-
f"SELECT * FROM read_parquet('{bridge_path}')"
|
|
55
|
-
)
|
|
56
52
|
|
|
57
53
|
where: list[str] = []
|
|
58
54
|
params: list[str | int] = []
|
|
@@ -61,12 +57,22 @@ def chembl_bridge(
|
|
|
61
57
|
params.append(reactant_set_id)
|
|
62
58
|
clause = f"WHERE {' AND '.join(where)}" if where else ""
|
|
63
59
|
|
|
60
|
+
# read_parquet(bridge_path) inline rather than a named
|
|
61
|
+
# CREATE VIEW: connect() now hands out a cursor on a base
|
|
62
|
+
# connection shared across calls (see connection.py), and a
|
|
63
|
+
# named view is catalog state on that shared connection.
|
|
64
|
+
# Concurrent calls each doing CREATE OR REPLACE VIEW on the same
|
|
65
|
+
# name raced each other's DDL transaction: reproduced directly,
|
|
66
|
+
# concurrent chembl_bridge()/dti_pairs() calls from a thread pool
|
|
67
|
+
# mostly failed with DuckDB's "Catalog write-write conflict".
|
|
68
|
+
# Referencing the parquet file directly in the query has no
|
|
69
|
+
# catalog side effect at all, so there's nothing to conflict on.
|
|
64
70
|
if with_names:
|
|
65
71
|
mol_dict = f"s3://{CHEMBL_BUCKET}/{chembl_release}/parquet/molecule_dictionary.parquet"
|
|
66
72
|
sql = f"""
|
|
67
73
|
SELECT b.reactant_set_id, b.chembl_molregno, b.chembl_id, b.match_method,
|
|
68
74
|
m.ligand_smiles, d.pref_name AS chembl_pref_name
|
|
69
|
-
FROM
|
|
75
|
+
FROM read_parquet('{bridge_path}') b
|
|
70
76
|
JOIN measurements m ON m.reactant_set_id = b.reactant_set_id
|
|
71
77
|
LEFT JOIN read_parquet('{mol_dict}') d ON d.molregno = b.chembl_molregno
|
|
72
78
|
{clause}
|
|
@@ -75,7 +81,7 @@ def chembl_bridge(
|
|
|
75
81
|
sql = f"""
|
|
76
82
|
SELECT b.reactant_set_id, b.chembl_molregno, b.chembl_id, b.match_method,
|
|
77
83
|
m.ligand_smiles
|
|
78
|
-
FROM
|
|
84
|
+
FROM read_parquet('{bridge_path}') b
|
|
79
85
|
JOIN measurements m ON m.reactant_set_id = b.reactant_set_id
|
|
80
86
|
{clause}
|
|
81
87
|
"""
|
{scigantic_bindingdb-0.2.2 → scigantic_bindingdb-0.2.4}/src/scigantic_bindingdb/dti_pairs.py
RENAMED
|
@@ -54,7 +54,6 @@ def dti_pairs(
|
|
|
54
54
|
con = connect(release)
|
|
55
55
|
try:
|
|
56
56
|
path = _resolve(f"{release}/derived/dti_pairs.parquet")
|
|
57
|
-
con.execute(f"CREATE OR REPLACE VIEW dti_pairs AS SELECT * FROM read_parquet('{path}')")
|
|
58
57
|
|
|
59
58
|
where: list[str] = []
|
|
60
59
|
params: list[str | int] = []
|
|
@@ -68,7 +67,13 @@ def dti_pairs(
|
|
|
68
67
|
where.append("n_chains_declared = 1")
|
|
69
68
|
clause = f"WHERE {' AND '.join(where)}" if where else ""
|
|
70
69
|
|
|
71
|
-
|
|
70
|
+
# read_parquet(path) inline rather than a named CREATE VIEW: see
|
|
71
|
+
# chembl_bridge.py's comment on the same change. connect() now
|
|
72
|
+
# returns a cursor on a base connection shared across calls, and a
|
|
73
|
+
# named view is catalog state on that shared connection --
|
|
74
|
+
# concurrent calls racing CREATE OR REPLACE VIEW on the same name
|
|
75
|
+
# hit DuckDB's "Catalog write-write conflict", reproduced directly.
|
|
76
|
+
sql = f"SELECT * FROM read_parquet('{path}') {clause} ORDER BY p_affinity DESC"
|
|
72
77
|
if limit is not None:
|
|
73
78
|
sql += " LIMIT ?"
|
|
74
79
|
params.append(int(limit))
|
{scigantic_bindingdb-0.2.2 → scigantic_bindingdb-0.2.4/src/scigantic_bindingdb.egg-info}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: scigantic-bindingdb
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.4
|
|
4
4
|
Summary: 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.
|
|
5
5
|
Author: Scigantic
|
|
6
6
|
License: MIT-0
|
|
@@ -136,7 +136,7 @@ bindingdb.enable_cache()
|
|
|
136
136
|
df = bindingdb.chembl_bridge() # downloads the bridge table once, then reads from disk
|
|
137
137
|
```
|
|
138
138
|
|
|
139
|
-
`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.
|
|
139
|
+
`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.
|
|
140
140
|
|
|
141
141
|
`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(...)`.
|
|
142
142
|
|
|
@@ -127,6 +127,50 @@ def test_concurrent_downloads_of_the_same_key_never_raise(tmp_path):
|
|
|
127
127
|
assert list(local_path.parent.glob("key.parquet.*.part")) == []
|
|
128
128
|
|
|
129
129
|
|
|
130
|
+
def test_concurrent_first_resolve_of_the_same_key_downloads_once(tmp_path):
|
|
131
|
+
# Regression test for a real inefficiency: resolve()'s check-then-
|
|
132
|
+
# download wasn't coordinated across threads, so N threads racing the
|
|
133
|
+
# first resolve() of a key each saw it missing and each downloaded it
|
|
134
|
+
# in full. Verified directly before this was fixed with a per-key
|
|
135
|
+
# lock: 16 threads calling resolve() concurrently on an empty cache
|
|
136
|
+
# triggered 16 separate downloads of the same file instead of one.
|
|
137
|
+
#
|
|
138
|
+
# mock.patch is entered/exited only once here, by the main thread,
|
|
139
|
+
# before the pool starts; the worker threads only read the already-
|
|
140
|
+
# patched attribute, so this doesn't hit the same-target mock.patch
|
|
141
|
+
# race that test_concurrent_downloads_of_the_same_key_never_raise's
|
|
142
|
+
# docstring describes for patching from multiple threads at once.
|
|
143
|
+
body = b"cached-body"
|
|
144
|
+
|
|
145
|
+
class FakeConnection:
|
|
146
|
+
def __enter__(self):
|
|
147
|
+
return self
|
|
148
|
+
|
|
149
|
+
def __exit__(self, *exc_info):
|
|
150
|
+
return False
|
|
151
|
+
|
|
152
|
+
def read(self, *_args):
|
|
153
|
+
if getattr(self, "_served", False):
|
|
154
|
+
return b""
|
|
155
|
+
self._served = True
|
|
156
|
+
return body
|
|
157
|
+
|
|
158
|
+
bindingdb.enable_cache(cache_dir=str(tmp_path))
|
|
159
|
+
try:
|
|
160
|
+
with mock.patch(
|
|
161
|
+
"urllib.request.urlopen", side_effect=lambda *a, **k: FakeConnection()
|
|
162
|
+
) as urlopen:
|
|
163
|
+
with ThreadPoolExecutor(max_workers=16) as pool:
|
|
164
|
+
paths = list(pool.map(lambda _i: resolve("shared/once.parquet"), range(16)))
|
|
165
|
+
|
|
166
|
+
assert len(set(paths)) == 1
|
|
167
|
+
assert urlopen.call_count == 1 # only one thread actually downloaded
|
|
168
|
+
final = tmp_path / "shared" / "once.parquet"
|
|
169
|
+
assert final.read_bytes() == body
|
|
170
|
+
finally:
|
|
171
|
+
bindingdb.disable_cache()
|
|
172
|
+
|
|
173
|
+
|
|
130
174
|
def test_cached_bridge_matches_uncached(tmp_path):
|
|
131
175
|
# The one integration-level check: caching actually gets used by a real
|
|
132
176
|
# function, not just by resolve() directly, and returns the same data.
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
2
|
+
|
|
1
3
|
import scigantic_bindingdb as bindingdb
|
|
2
4
|
|
|
3
5
|
# A gefitinib measurement, verified against the live mirror to resolve
|
|
@@ -29,3 +31,25 @@ def test_bridge_total_row_count():
|
|
|
29
31
|
"'s3://scigantic-bindingdb/202608/derived/bindingdb_chembl_bridge.parquet')"
|
|
30
32
|
)
|
|
31
33
|
assert total["n"].iloc[0] == 2272063
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def test_concurrent_calls_do_not_conflict_on_catalog():
|
|
37
|
+
# Regression test for a real bug introduced by connect()'s move to a
|
|
38
|
+
# shared base connection (see connection.py): chembl_bridge() used to
|
|
39
|
+
# register its result as a named CREATE OR REPLACE VIEW, which is
|
|
40
|
+
# catalog-mutating DDL. Two calls racing that DDL on the same shared
|
|
41
|
+
# connection from different threads mostly failed with DuckDB's
|
|
42
|
+
# "Catalog write-write conflict", reproduced directly (46/48 calls
|
|
43
|
+
# failed in a 24-thread stress run) before this was fixed by
|
|
44
|
+
# referencing the parquet file inline instead of via a named view.
|
|
45
|
+
def call(i):
|
|
46
|
+
return bindingdb.chembl_bridge(
|
|
47
|
+
reactant_set_id=_GEFITINIB_REACTANT_SET_ID, with_names=(i % 2 == 0)
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
with ThreadPoolExecutor(max_workers=16) as pool:
|
|
51
|
+
results = list(pool.map(call, range(16)))
|
|
52
|
+
|
|
53
|
+
for df in results:
|
|
54
|
+
assert len(df) == 1
|
|
55
|
+
assert df["chembl_id"].iloc[0] == "CHEMBL939"
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import math
|
|
2
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
2
3
|
|
|
3
4
|
import scigantic_bindingdb as bindingdb
|
|
4
5
|
|
|
@@ -69,3 +70,26 @@ def test_uniprot_filter():
|
|
|
69
70
|
def test_sorted_most_potent_first():
|
|
70
71
|
df = bindingdb.dti_pairs(endpoint="ki", uniprot_id="P00533")
|
|
71
72
|
assert df["p_affinity"].is_monotonic_decreasing
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def test_concurrent_calls_do_not_conflict_on_catalog():
|
|
76
|
+
# Regression test for a real bug introduced by connect()'s move to a
|
|
77
|
+
# shared base connection (see connection.py): dti_pairs() used to
|
|
78
|
+
# register its result as a named CREATE OR REPLACE VIEW, which is
|
|
79
|
+
# catalog-mutating DDL. Two calls racing that DDL on the same shared
|
|
80
|
+
# connection from different threads mostly failed with DuckDB's
|
|
81
|
+
# "Catalog write-write conflict" (reproduced directly before this was
|
|
82
|
+
# fixed by referencing the parquet file inline instead of via a named
|
|
83
|
+
# view; see chembl_bridge.py's equivalent test).
|
|
84
|
+
endpoints = ("ki", "ic50", "kd", "ec50")
|
|
85
|
+
|
|
86
|
+
def call(i):
|
|
87
|
+
endpoint = endpoints[i % len(endpoints)]
|
|
88
|
+
return endpoint, bindingdb.dti_pairs(endpoint=endpoint, limit=50)
|
|
89
|
+
|
|
90
|
+
with ThreadPoolExecutor(max_workers=16) as pool:
|
|
91
|
+
results = list(pool.map(call, range(16)))
|
|
92
|
+
|
|
93
|
+
for endpoint, df in results:
|
|
94
|
+
assert len(df) == 50
|
|
95
|
+
assert (df["endpoint"] == endpoint).all()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{scigantic_bindingdb-0.2.2 → scigantic_bindingdb-0.2.4}/src/scigantic_bindingdb/_constants.py
RENAMED
|
File without changes
|
|
File without changes
|
{scigantic_bindingdb-0.2.2 → scigantic_bindingdb-0.2.4}/src/scigantic_bindingdb/connection.py
RENAMED
|
File without changes
|
{scigantic_bindingdb-0.2.2 → scigantic_bindingdb-0.2.4}/src/scigantic_bindingdb/measurements.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{scigantic_bindingdb-0.2.2 → scigantic_bindingdb-0.2.4}/src/scigantic_bindingdb.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|