wenruifan commited on
Commit
8f7e54a
·
1 Parent(s): c15d304

Use indexed node lookup sidecar

Browse files
.env.example CHANGED
@@ -4,6 +4,7 @@ MUSPROT_DB_FILENAME=MuSProt.db
4
 
5
  # Optional mounted-volume overrides
6
  # MUSPROT_DB_PATH=/data/MuSProt.db
 
7
  # MUSPROT_SUMMARY_PATH=/data/musprot_summary.json
8
  # MUSPROT_DOCS_PATH=/data/MuSProt_documentation.md
9
  # MUSPROT_PLOTS_DIR=/data/plots
 
4
 
5
  # Optional mounted-volume overrides
6
  # MUSPROT_DB_PATH=/data/MuSProt.db
7
+ # MUSPROT_NODE_DB_PATH=/data/MuSProt-node-lookup.db
8
  # MUSPROT_SUMMARY_PATH=/data/musprot_summary.json
9
  # MUSPROT_DOCS_PATH=/data/MuSProt_documentation.md
10
  # MUSPROT_PLOTS_DIR=/data/plots
README.md CHANGED
@@ -37,6 +37,7 @@ Recommended Dataset repository files:
37
 
38
  ```text
39
  MuSProt.db
 
40
  musprot_summary.json
41
  MuSProt_documentation.md
42
  plots/
@@ -46,6 +47,7 @@ Optional Space variables:
46
 
47
  ```text
48
  MUSPROT_DB_PATH=/data/MuSProt.db
 
49
  MUSPROT_SUMMARY_PATH=/data/musprot_summary.json
50
  MUSPROT_DOCS_PATH=/data/MuSProt_documentation.md
51
  MUSPROT_PLOTS_DIR=/data/plots
 
37
 
38
  ```text
39
  MuSProt.db
40
+ MuSProt-node-lookup.db
41
  musprot_summary.json
42
  MuSProt_documentation.md
43
  plots/
 
47
 
48
  ```text
49
  MUSPROT_DB_PATH=/data/MuSProt.db
50
+ MUSPROT_NODE_DB_PATH=/data/MuSProt-node-lookup.db
51
  MUSPROT_SUMMARY_PATH=/data/musprot_summary.json
52
  MUSPROT_DOCS_PATH=/data/MuSProt_documentation.md
53
  MUSPROT_PLOTS_DIR=/data/plots
backend/app/protein/config.py CHANGED
@@ -66,6 +66,20 @@ def get_summary_path() -> Path | None:
66
  )
67
 
68
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
69
  def get_docs_path() -> Path | None:
70
  """Return the dataset documentation path when available."""
71
  explicit_path = os.getenv("MUSPROT_DOCS_PATH")
 
66
  )
67
 
68
 
69
+ def get_node_database_path() -> Path:
70
+ """Return an optional indexed node lookup sidecar, falling back to the main DB."""
71
+ explicit_path = os.getenv("MUSPROT_NODE_DB_PATH")
72
+ if explicit_path:
73
+ path = Path(explicit_path).expanduser().resolve()
74
+ if path.exists():
75
+ return path
76
+
77
+ return (
78
+ _existing_path(DEFAULT_VOLUME_DIR / "MuSProt-node-lookup.db")
79
+ or get_database_path()
80
+ ).resolve()
81
+
82
+
83
  def get_docs_path() -> Path | None:
84
  """Return the dataset documentation path when available."""
85
  explicit_path = os.getenv("MUSPROT_DOCS_PATH")
backend/app/protein/tsv_loader.py CHANGED
@@ -8,10 +8,11 @@ key the first occurrence is kept.
8
  import ast
9
  import sqlite3
10
  import logging
 
11
  from functools import lru_cache
12
  from typing import Dict, List, Optional, Tuple, Any
13
 
14
- from app.protein.config import get_database_path
15
  from app.protein.database import connect_readonly
16
 
17
  logger = logging.getLogger(__name__)
@@ -81,7 +82,7 @@ def reset_index() -> None:
81
  @lru_cache(maxsize=4096)
82
  def _lookup_node_row(pdb_id: str, auth_asym_id: str) -> Optional[_Row]:
83
  """Read one node row without blocking on a full-table in-memory index."""
84
- conn = connect_readonly(get_database_path())
85
  try:
86
  conn.row_factory = sqlite3.Row
87
  row = conn.execute(
@@ -93,7 +94,10 @@ def _lookup_node_row(pdb_id: str, auth_asym_id: str) -> Optional[_Row]:
93
  ).fetchone()
94
  if row is None:
95
  return None
96
- return {key: row[key] or "" for key in row.keys()}
 
 
 
97
  finally:
98
  conn.close()
99
 
 
8
  import ast
9
  import sqlite3
10
  import logging
11
+ import zlib
12
  from functools import lru_cache
13
  from typing import Dict, List, Optional, Tuple, Any
14
 
15
+ from app.protein.config import get_database_path, get_node_database_path
16
  from app.protein.database import connect_readonly
17
 
18
  logger = logging.getLogger(__name__)
 
82
  @lru_cache(maxsize=4096)
83
  def _lookup_node_row(pdb_id: str, auth_asym_id: str) -> Optional[_Row]:
84
  """Read one node row without blocking on a full-table in-memory index."""
85
+ conn = connect_readonly(get_node_database_path())
86
  try:
87
  conn.row_factory = sqlite3.Row
88
  row = conn.execute(
 
94
  ).fetchone()
95
  if row is None:
96
  return None
97
+ return {
98
+ key: zlib.decompress(value).decode("utf-8") if isinstance(value, bytes) else value or ""
99
+ for key, value in ((key, row[key]) for key in row.keys())
100
+ }
101
  finally:
102
  conn.close()
103
 
backend/scripts/generate_node_lookup.py ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Generate an indexed read-only node lookup sidecar from MuSProt.db."""
2
+ from __future__ import annotations
3
+
4
+ import argparse
5
+ import sqlite3
6
+ import zlib
7
+ from pathlib import Path
8
+
9
+
10
+ NODE_COLUMNS = (
11
+ "pdb_id", "auth_asym_id", "base_label", "sequence", "sequence_length",
12
+ "CATH_ID", "cath_superfamily", "Rosetta", "FoldX", "EvoEF2", "RM", "RM+",
13
+ "ranked_functions", "state_id", "experimental_method", "pH", "temp_K",
14
+ )
15
+
16
+
17
+ def main() -> None:
18
+ parser = argparse.ArgumentParser()
19
+ parser.add_argument("source", type=Path)
20
+ parser.add_argument("-o", "--output", type=Path, default=Path("MuSProt-node-lookup.db"))
21
+ args = parser.parse_args()
22
+
23
+ output = args.output.resolve()
24
+ output.unlink(missing_ok=True)
25
+
26
+ conn = sqlite3.connect(output)
27
+ try:
28
+ conn.execute("PRAGMA journal_mode = OFF")
29
+ conn.execute("PRAGMA synchronous = OFF")
30
+ conn.execute(
31
+ 'CREATE TABLE node (pdb_id, auth_asym_id, base_label, sequence BLOB,'
32
+ ' sequence_length, CATH_ID, cath_superfamily, Rosetta, FoldX, EvoEF2,'
33
+ ' RM, "RM+", ranked_functions BLOB, state_id, experimental_method, pH, temp_K)'
34
+ )
35
+ source = sqlite3.connect(f"file:{args.source.resolve()}?mode=ro", uri=True)
36
+ try:
37
+ select_columns = ", ".join(f'"{column}"' for column in NODE_COLUMNS)
38
+ rows = source.execute(f"SELECT {select_columns} FROM node")
39
+ placeholders = ", ".join("?" for _ in NODE_COLUMNS)
40
+ batch = []
41
+ for row in rows:
42
+ row = list(row)
43
+ for index in (3, 12):
44
+ row[index] = zlib.compress((row[index] or "").encode("utf-8"), 1)
45
+ batch.append(row)
46
+ if len(batch) == 1000:
47
+ conn.executemany(f"INSERT INTO node VALUES ({placeholders})", batch)
48
+ batch.clear()
49
+ if batch:
50
+ conn.executemany(f"INSERT INTO node VALUES ({placeholders})", batch)
51
+ finally:
52
+ source.close()
53
+ conn.execute(
54
+ "CREATE INDEX idx_node_chain "
55
+ "ON node(LOWER(pdb_id), UPPER(auth_asym_id))"
56
+ )
57
+ conn.execute("ANALYZE")
58
+ conn.commit()
59
+ finally:
60
+ conn.close()
61
+
62
+ print(f"Created {output} ({output.stat().st_size:,} bytes)")
63
+
64
+
65
+ if __name__ == "__main__":
66
+ main()