Skip to content

Commit 8525bc6

Browse files
SimplyLizclaude
andcommitted
Add README and fix Windows compatibility
- Add project README with setup, CLI reference, and architecture overview - Fix launch.py: Windows venv path, netstat/taskkill for port cleanup, npm.cmd resolution, guard SIGTERM behind hasattr - Replace asyncio.get_event_loop().time() with time.monotonic() in ensembl, ncbi, and uniprot rate-limiting (cross-platform, not deprecated) - Use asyncio.get_running_loop() in CellForge simulation route Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 5c6537d commit 8525bc6

6 files changed

Lines changed: 196 additions & 17 deletions

File tree

README.md

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
# BioLab
2+
3+
Unified bioinformatics platform for gene analysis, evidence synthesis, and whole-cell simulation.
4+
5+
BioLab combines genomic data ingestion, LLM-powered functional prediction, and multi-scale cell simulation into a single interactive environment. Load a genome, analyze genes against public databases, synthesize function hypotheses with AI, and simulate cellular behavior — all from one interface.
6+
7+
## Features
8+
9+
- **Multi-genome management** — load genomes from GenBank, NCBI accessions, Ensembl, or FASTA files
10+
- **Gene analysis & evidence synthesis** — aggregate functional evidence from BRENDA, SABIO-RK, KEGG, and Datanator
11+
- **LLM-powered predictions** — synthesize gene function hypotheses using Claude, OpenAI, or local Ollama models
12+
- **Cell simulation** — multi-timescale ODE engine covering metabolism, gene expression, growth, mutation, and epigenetics
13+
- **CellForge** — advanced whole-cell simulation with thermodynamic constraints, stochastic processes (Gillespie SSA), and FBA
14+
- **Population dynamics** — population-level growth, division, and genetic drift simulation
15+
- **Interactive frontend** — React/TypeScript UI with genome browser, petri dish visualization, knockout lab, and real-time charts
16+
- **CLI & API** — full Typer CLI and FastAPI REST/WebSocket API
17+
18+
## Requirements
19+
20+
- Python 3.11+
21+
- Node.js 18+ (for frontend)
22+
- Rust toolchain (optional, for native CellForge engine)
23+
24+
## Quick Start
25+
26+
```bash
27+
# Clone
28+
git clone https://github.com/SimplyLiz/BioLab.git
29+
cd BioLab
30+
31+
# Python setup
32+
python -m venv .venv
33+
source .venv/bin/activate # Linux/macOS
34+
# .venv\Scripts\activate # Windows
35+
pip install -e ".[dev,llm]"
36+
37+
# Frontend setup
38+
cd frontend && npm install && cd ..
39+
40+
# Initialize database
41+
biolab init
42+
43+
# Launch both backend and frontend
44+
python launch.py
45+
```
46+
47+
Backend runs on `http://localhost:8000`, frontend on `http://localhost:5173`.
48+
49+
## Configuration
50+
51+
Set via environment variables or a `.env` file:
52+
53+
| Variable | Description | Default |
54+
|---|---|---|
55+
| `ANTHROPIC_API_KEY` | Claude API key for LLM synthesis ||
56+
| `OPENAI_API_KEY` | OpenAI API key (alternative provider) ||
57+
| `LLM_PROVIDER` | `anthropic`, `openai`, or `ollama` | `anthropic` |
58+
| `LLM_MODEL` | Model name | `claude-sonnet-4-5-20250929` |
59+
| `DATABASE_URL` | SQLAlchemy database URL | `sqlite:///biolab.db` |
60+
| `NCBI_API_KEY` | NCBI E-utilities key (10 req/s vs 3) ||
61+
| `NCBI_EMAIL` | Required by NCBI for identification ||
62+
| `BIOLAB_BACKEND_PORT` | Backend port | `8000` |
63+
| `BIOLAB_FRONTEND_PORT` | Frontend port | `5173` |
64+
65+
## CLI
66+
67+
```
68+
biolab genes Gene operations (list, import, search)
69+
biolab analyze Deep gene analysis & LLM synthesis
70+
biolab evidence Evidence source management
71+
biolab synthesize LLM function synthesis
72+
biolab pipeline Multi-phase evidence pipeline
73+
biolab validate Validation and quality checks
74+
biolab cellforge CellForge whole-cell simulation engine
75+
biolab init Initialize database
76+
```
77+
78+
### CellForge subcommands
79+
80+
```
81+
biolab cellforge annotate <fasta> Run genome annotation pipeline
82+
biolab cellforge run <config.json> Run a whole-cell simulation
83+
biolab cellforge serve Start the CellForge API server
84+
biolab cellforge benchmark Run performance benchmarks
85+
biolab cellforge info Show version and dependency info
86+
```
87+
88+
## Docker
89+
90+
```bash
91+
docker-compose up
92+
```
93+
94+
This starts the backend (port 8000) and Redis cache (port 6379).
95+
96+
For GPU-accelerated workloads:
97+
98+
```bash
99+
docker build -f docker/Dockerfile.gpu -t biolab-gpu .
100+
```
101+
102+
## Project Structure
103+
104+
```
105+
src/biolab/
106+
api/ REST API (FastAPI)
107+
cli/ CLI commands (Typer)
108+
services/ Business logic (LLM, ETL, import)
109+
ingestion/ GenBank/FASTA parsing
110+
simulation/ Core simulation engine
111+
cellforge/ Advanced whole-cell simulation
112+
core/ Simulation kernel
113+
processes/ Biological processes (transcription, translation, etc.)
114+
constraints/ Thermodynamic & energy constraints
115+
annotation/ Genome annotation pipeline
116+
api/ CellForge REST API
117+
contrib/ Plugin modules (DNASyn evidence pipeline)
118+
db/ Database models & migrations
119+
120+
frontend/
121+
src/pages/ React page components
122+
src/components/ UI components (genome browser, petri dish, charts)
123+
src/hooks/ Data fetching & state hooks
124+
src/stores/ Zustand state management
125+
126+
crates/
127+
cellforge-engine/ Rust native simulation engine (optional)
128+
```
129+
130+
## Optional Dependencies
131+
132+
Install extras for additional capabilities:
133+
134+
```bash
135+
pip install -e ".[cellforge]" # CellForge (COBRApy, GillesPy2, Redis, Zarr)
136+
pip install -e ".[ml]" # ML models (PyTorch, Transformers)
137+
pip install -e ".[validation]" # Validation (libRoadRunner, matplotlib)
138+
pip install -e ".[dashboard]" # Streamlit dashboard
139+
pip install -e ".[postgres]" # PostgreSQL support
140+
```
141+
142+
## Development
143+
144+
```bash
145+
pip install -e ".[dev]"
146+
pytest # Run tests
147+
ruff check src/ # Lint
148+
mypy src/ # Type check
149+
```
150+
151+
## License
152+
153+
All rights reserved.

launch.py

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,23 @@
22
"""Launch BioLab backend + frontend in one shot. Ctrl-C kills both."""
33

44
import os
5+
import platform
6+
import shutil
57
import signal
68
import subprocess
79
import sys
810
import time
911

1012
ROOT = os.path.dirname(os.path.abspath(__file__))
1113
FRONTEND_DIR = os.path.join(ROOT, "frontend")
12-
VENV_PYTHON = os.path.join(ROOT, ".venv", "bin", "python")
14+
15+
IS_WINDOWS = platform.system() == "Windows"
16+
17+
# Resolve venv python — Scripts/python.exe on Windows, bin/python elsewhere
18+
if IS_WINDOWS:
19+
VENV_PYTHON = os.path.join(ROOT, ".venv", "Scripts", "python.exe")
20+
else:
21+
VENV_PYTHON = os.path.join(ROOT, ".venv", "bin", "python")
1322

1423
BACKEND_PORT = int(os.environ.get("BIOLAB_BACKEND_PORT", "8000"))
1524
FRONTEND_PORT = int(os.environ.get("BIOLAB_FRONTEND_PORT", "5173"))
@@ -20,11 +29,24 @@
2029
def kill_port(port: int) -> None:
2130
"""Kill any process currently holding a port."""
2231
try:
23-
out = subprocess.check_output(["lsof", "-ti", f":{port}"], text=True).strip()
24-
for pid in out.splitlines():
25-
os.kill(int(pid), signal.SIGKILL)
26-
time.sleep(0.5)
27-
except (subprocess.CalledProcessError, ValueError):
32+
if IS_WINDOWS:
33+
out = subprocess.check_output(
34+
["netstat", "-ano"], text=True, stderr=subprocess.DEVNULL,
35+
)
36+
for line in out.splitlines():
37+
if f":{port}" in line and "LISTENING" in line:
38+
pid = line.strip().split()[-1]
39+
subprocess.run(
40+
["taskkill", "/F", "/PID", pid],
41+
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
42+
)
43+
time.sleep(0.5)
44+
else:
45+
out = subprocess.check_output(["lsof", "-ti", f":{port}"], text=True).strip()
46+
for pid in out.splitlines():
47+
os.kill(int(pid), signal.SIGKILL)
48+
time.sleep(0.5)
49+
except (subprocess.CalledProcessError, ValueError, OSError):
2850
pass
2951

3052

@@ -46,14 +68,14 @@ def shutdown(*_: object) -> None:
4668

4769
def main() -> None:
4870
signal.signal(signal.SIGINT, shutdown)
49-
signal.signal(signal.SIGTERM, shutdown)
71+
if hasattr(signal, "SIGTERM"):
72+
signal.signal(signal.SIGTERM, shutdown)
5073

5174
# Free ports
5275
kill_port(BACKEND_PORT)
5376
kill_port(FRONTEND_PORT)
5477

5578
# Backend — uvicorn with app factory
56-
# Use venv python if available, otherwise fall back to current interpreter
5779
python = VENV_PYTHON if os.path.exists(VENV_PYTHON) else sys.executable
5880
print(f"\033[36m[BioLab]\033[0m Starting backend on :{BACKEND_PORT}")
5981
backend = subprocess.Popen(
@@ -66,10 +88,11 @@ def main() -> None:
6688
)
6789
procs.append(backend)
6890

69-
# Frontend
91+
# Frontend — resolve npm executable (npm.cmd on Windows)
92+
npm = shutil.which("npm") or ("npm.cmd" if IS_WINDOWS else "npm")
7093
print(f"\033[32m[BioLab]\033[0m Starting frontend on :{FRONTEND_PORT}")
7194
frontend = subprocess.Popen(
72-
["npm", "run", "dev", "--", "--port", str(FRONTEND_PORT)],
95+
[npm, "run", "dev", "--", "--port", str(FRONTEND_PORT)],
7396
cwd=FRONTEND_DIR,
7497
)
7598
procs.append(frontend)

src/biolab/cellforge/api/routes/simulation.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ async def start_simulation(simulation_id: str) -> dict[str, str]:
8686

8787
async def _run_sim() -> None:
8888
try:
89-
loop = asyncio.get_event_loop()
89+
loop = asyncio.get_running_loop()
9090
await loop.run_in_executor(None, sim.run)
9191
entry["status"] = "completed"
9292
except Exception:

src/biolab/services/ensembl.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from __future__ import annotations
88

99
import asyncio
10+
import time
1011
from typing import Any
1112

1213
import httpx
@@ -21,11 +22,11 @@
2122

2223
async def _throttle():
2324
global _last_request
24-
now = asyncio.get_event_loop().time()
25+
now = time.monotonic()
2526
wait = _THROTTLE - (now - _last_request)
2627
if wait > 0:
2728
await asyncio.sleep(wait)
28-
_last_request = asyncio.get_event_loop().time()
29+
_last_request = time.monotonic()
2930

3031

3132
# ---------------------------------------------------------------------------

src/biolab/services/ncbi.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from __future__ import annotations
88

99
import asyncio
10+
import time
1011
import xml.etree.ElementTree as ET
1112
from typing import Any
1213

@@ -21,11 +22,11 @@
2122

2223
async def _throttle():
2324
global _last_request
24-
now = asyncio.get_event_loop().time()
25+
now = time.monotonic()
2526
wait = _THROTTLE - (now - _last_request)
2627
if wait > 0:
2728
await asyncio.sleep(wait)
28-
_last_request = asyncio.get_event_loop().time()
29+
_last_request = time.monotonic()
2930

3031

3132
def _params(**kwargs) -> dict:

src/biolab/services/uniprot.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from __future__ import annotations
77

88
import asyncio
9+
import time
910
from typing import Any
1011

1112
import httpx
@@ -19,11 +20,11 @@
1920

2021
async def _throttle():
2122
global _last_request
22-
now = asyncio.get_event_loop().time()
23+
now = time.monotonic()
2324
wait = _THROTTLE - (now - _last_request)
2425
if wait > 0:
2526
await asyncio.sleep(wait)
26-
_last_request = asyncio.get_event_loop().time()
27+
_last_request = time.monotonic()
2728

2829

2930
async def search_by_gene(

0 commit comments

Comments
 (0)