darkfrostx commited on
Commit
ae887ef
·
verified ·
1 Parent(s): 6ec747b

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +246 -155
app.py CHANGED
@@ -1,155 +1,246 @@
1
- runtime error
2
- Exit code: 1. Reason: cio.run(self.serve(sockets=sockets))
3
- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
4
- File "/usr/local/lib/python3.11/asyncio/runners.py", line 190, in run
5
- return runner.run(main)
6
- ^^^^^^^^^^^^^^^^
7
- File "/usr/local/lib/python3.11/asyncio/runners.py", line 118, in run
8
- return self._loop.run_until_complete(task)
9
- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
10
- File "/usr/local/lib/python3.11/asyncio/base_events.py", line 654, in run_until_complete
11
- return future.result()
12
- ^^^^^^^^^^^^^^^
13
- File "/usr/local/lib/python3.11/site-packages/uvicorn/server.py", line 71, in serve
14
- await self._serve(sockets)
15
- File "/usr/local/lib/python3.11/site-packages/uvicorn/server.py", line 78, in _serve
16
- config.load()
17
- File "/usr/local/lib/python3.11/site-packages/uvicorn/config.py", line 436, in load
18
- self.loaded_app = import_from_string(self.app)
19
- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
20
- File "/usr/local/lib/python3.11/site-packages/uvicorn/importer.py", line 19, in import_from_string
21
- module = importlib.import_module(module_str)
22
- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
23
- File "/usr/local/lib/python3.11/importlib/__init__.py", line 126, in import_module
24
- return _bootstrap._gcd_import(name[level:], package, level)
25
- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
26
- File "<frozen importlib._bootstrap>", line 1204, in _gcd_import
27
- File "<frozen importlib._bootstrap>", line 1176, in _find_and_load
28
- File "<frozen importlib._bootstrap>", line 1147, in _find_and_load_unlocked
29
- File "<frozen importlib._bootstrap>", line 690, in _load_unlocked
30
- File "<frozen importlib._bootstrap_external>", line 936, in exec_module
31
- File "<frozen importlib._bootstrap_external>", line 1074, in get_code
32
- File "<frozen importlib._bootstrap_external>", line 1004, in source_to_code
33
- File "<frozen importlib._bootstrap>", line 241, in _call_with_frames_removed
34
- File "/app/app.py", line 1
35
- <PASTE YOUR WHOLE app.py CONTENT HERE EXACTLY>
36
- ^
37
- SyntaxError: invalid syntax
38
- Container logs:
39
-
40
- ===== Application Startup at 2025-09-06 03:54:41 =====
41
-
42
- Traceback (most recent call last):
43
- File "/usr/local/bin/uvicorn", line 8, in <module>
44
- sys.exit(main())
45
- ^^^^^^
46
- File "/usr/local/lib/python3.11/site-packages/click/core.py", line 1442, in __call__
47
- return self.main(*args, **kwargs)
48
- ^^^^^^^^^^^^^^^^^^^^^^^^^^
49
- File "/usr/local/lib/python3.11/site-packages/click/core.py", line 1363, in main
50
- rv = self.invoke(ctx)
51
- ^^^^^^^^^^^^^^^^
52
- File "/usr/local/lib/python3.11/site-packages/click/core.py", line 1226, in invoke
53
- return ctx.invoke(self.callback, **ctx.params)
54
- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
55
- File "/usr/local/lib/python3.11/site-packages/click/core.py", line 794, in invoke
56
- return callback(*args, **kwargs)
57
- ^^^^^^^^^^^^^^^^^^^^^^^^^
58
- File "/usr/local/lib/python3.11/site-packages/uvicorn/main.py", line 413, in main
59
- run(
60
- File "/usr/local/lib/python3.11/site-packages/uvicorn/main.py", line 580, in run
61
- server.run()
62
- File "/usr/local/lib/python3.11/site-packages/uvicorn/server.py", line 67, in run
63
- return asyncio.run(self.serve(sockets=sockets))
64
- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
65
- File "/usr/local/lib/python3.11/asyncio/runners.py", line 190, in run
66
- return runner.run(main)
67
- ^^^^^^^^^^^^^^^^
68
- File "/usr/local/lib/python3.11/asyncio/runners.py", line 118, in run
69
- return self._loop.run_until_complete(task)
70
- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
71
- File "/usr/local/lib/python3.11/asyncio/base_events.py", line 654, in run_until_complete
72
- return future.result()
73
- ^^^^^^^^^^^^^^^
74
- File "/usr/local/lib/python3.11/site-packages/uvicorn/server.py", line 71, in serve
75
- await self._serve(sockets)
76
- File "/usr/local/lib/python3.11/site-packages/uvicorn/server.py", line 78, in _serve
77
- config.load()
78
- File "/usr/local/lib/python3.11/site-packages/uvicorn/config.py", line 436, in load
79
- self.loaded_app = import_from_string(self.app)
80
- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
81
- File "/usr/local/lib/python3.11/site-packages/uvicorn/importer.py", line 19, in import_from_string
82
- module = importlib.import_module(module_str)
83
- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
84
- File "/usr/local/lib/python3.11/importlib/__init__.py", line 126, in import_module
85
- return _bootstrap._gcd_import(name[level:], package, level)
86
- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
87
- File "<frozen importlib._bootstrap>", line 1204, in _gcd_import
88
- File "<frozen importlib._bootstrap>", line 1176, in _find_and_load
89
- File "<frozen importlib._bootstrap>", line 1147, in _find_and_load_unlocked
90
- File "<frozen importlib._bootstrap>", line 690, in _load_unlocked
91
- File "<frozen importlib._bootstrap_external>", line 936, in exec_module
92
- File "<frozen importlib._bootstrap_external>", line 1074, in get_code
93
- File "<frozen importlib._bootstrap_external>", line 1004, in source_to_code
94
- File "<frozen importlib._bootstrap>", line 241, in _call_with_frames_removed
95
- File "/app/app.py", line 1
96
- <PASTE YOUR WHOLE app.py CONTENT HERE EXACTLY>
97
- ^
98
- SyntaxError: invalid syntax
99
- Traceback (most recent call last):
100
- File "/usr/local/bin/uvicorn", line 8, in <module>
101
- sys.exit(main())
102
- ^^^^^^
103
- File "/usr/local/lib/python3.11/site-packages/click/core.py", line 1442, in __call__
104
- return self.main(*args, **kwargs)
105
- ^^^^^^^^^^^^^^^^^^^^^^^^^^
106
- File "/usr/local/lib/python3.11/site-packages/click/core.py", line 1363, in main
107
- rv = self.invoke(ctx)
108
- ^^^^^^^^^^^^^^^^
109
- File "/usr/local/lib/python3.11/site-packages/click/core.py", line 1226, in invoke
110
- return ctx.invoke(self.callback, **ctx.params)
111
- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
112
- File "/usr/local/lib/python3.11/site-packages/click/core.py", line 794, in invoke
113
- return callback(*args, **kwargs)
114
- ^^^^^^^^^^^^^^^^^^^^^^^^^
115
- File "/usr/local/lib/python3.11/site-packages/uvicorn/main.py", line 413, in main
116
- run(
117
- File "/usr/local/lib/python3.11/site-packages/uvicorn/main.py", line 580, in run
118
- server.run()
119
- File "/usr/local/lib/python3.11/site-packages/uvicorn/server.py", line 67, in run
120
- return asyncio.run(self.serve(sockets=sockets))
121
- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
122
- File "/usr/local/lib/python3.11/asyncio/runners.py", line 190, in run
123
- return runner.run(main)
124
- ^^^^^^^^^^^^^^^^
125
- File "/usr/local/lib/python3.11/asyncio/runners.py", line 118, in run
126
- return self._loop.run_until_complete(task)
127
- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
128
- File "/usr/local/lib/python3.11/asyncio/base_events.py", line 654, in run_until_complete
129
- return future.result()
130
- ^^^^^^^^^^^^^^^
131
- File "/usr/local/lib/python3.11/site-packages/uvicorn/server.py", line 71, in serve
132
- await self._serve(sockets)
133
- File "/usr/local/lib/python3.11/site-packages/uvicorn/server.py", line 78, in _serve
134
- config.load()
135
- File "/usr/local/lib/python3.11/site-packages/uvicorn/config.py", line 436, in load
136
- self.loaded_app = import_from_string(self.app)
137
- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
138
- File "/usr/local/lib/python3.11/site-packages/uvicorn/importer.py", line 19, in import_from_string
139
- module = importlib.import_module(module_str)
140
- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
141
- File "/usr/local/lib/python3.11/importlib/__init__.py", line 126, in import_module
142
- return _bootstrap._gcd_import(name[level:], package, level)
143
- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
144
- File "<frozen importlib._bootstrap>", line 1204, in _gcd_import
145
- File "<frozen importlib._bootstrap>", line 1176, in _find_and_load
146
- File "<frozen importlib._bootstrap>", line 1147, in _find_and_load_unlocked
147
- File "<frozen importlib._bootstrap>", line 690, in _load_unlocked
148
- File "<frozen importlib._bootstrap_external>", line 936, in exec_module
149
- File "<frozen importlib._bootstrap_external>", line 1074, in get_code
150
- File "<frozen importlib._bootstrap_external>", line 1004, in source_to_code
151
- File "<frozen importlib._bootstrap>", line 241, in _call_with_frames_removed
152
- File "/app/app.py", line 1
153
- <PASTE YOUR WHOLE app.py CONTENT HERE EXACTLY>
154
- ^
155
- SyntaxError: invalid syntax
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI, Query
2
+ from fastapi.middleware.cors import CORSMiddleware
3
+ import httpx, asyncio, time, os, hashlib, json
4
+ from typing import Dict, Any, Tuple, Optional, List
5
+ from fastapi.responses import RedirectResponse, JSONResponse
6
+
7
+
8
+ APP_NAME = "neuro-mechanism-backend"
9
+ CALLER_ID = "neuro-mech-backend-demo" # shows up in STRING logs
10
+
11
+ app = FastAPI(title=APP_NAME)
12
+
13
+ @app.get("/", include_in_schema=False)
14
+ def root():
15
+ # Nice landing: send people to the interactive docs
16
+ return RedirectResponse(url="/docs")
17
+
18
+ @app.get("/health", include_in_schema=False)
19
+ def health():
20
+ return {"ok": True, "app": "neuro-mechanism-backend"}
21
+
22
+ @app.get("/endpoints", include_in_schema=False)
23
+ def endpoints():
24
+ return JSONResponse({
25
+ "GET": [
26
+ "/mechanism_graph?receptor=HTR2A&symptom=apathy",
27
+ "/heuristics/regions_from_string?receptor=HTR2A&limit=40",
28
+ "/lit/eupmc?query=HTR2A%20AND%20apathy&pageSize=5",
29
+ "/string/network?identifiers=HTR2A&species=9606",
30
+ "/gpcrdb/protein?entry=htr2a_human",
31
+ "/uniprot/search?query=HTR2A&size=5",
32
+ "/rxnav/rxcui?name=fluoxetine",
33
+ "/pubchem/compound_by_name?name=fluoxetine",
34
+ "/trials/search?q=HTR2A&pageSize=5",
35
+ "/health", "/docs"
36
+ ]
37
+ })
38
+
39
+ app.add_middleware(
40
+ CORSMiddleware,
41
+ allow_origins=["*"], allow_credentials=True,
42
+ allow_methods=["*"], allow_headers=["*"]
43
+ )
44
+
45
+ UA = {"User-Agent": f"{APP_NAME}/1.1 (HF Space)"}
46
+
47
+ # ----------------- NEW: tiny in-memory TTL cache -----------------
48
+ class TTLCache:
49
+ def __init__(self, max_items=512):
50
+ self.store: Dict[str, Tuple[float, Any]] = {}
51
+ self.max_items = max_items
52
+ self._lock = asyncio.Lock()
53
+
54
+ def _mk(self, url: str, params: Optional[dict]) -> str:
55
+ key = url + "?" + (json.dumps(params, sort_keys=True) if params else "")
56
+ return hashlib.sha1(key.encode()).hexdigest()
57
+
58
+ async def get(self, url: str, params: Optional[dict], ttl: float):
59
+ k = self._mk(url, params)
60
+ async with self._lock:
61
+ item = self.store.get(k)
62
+ if item and (time.time() < item[0]):
63
+ return item[1]
64
+ async with httpx.AsyncClient(headers=UA, timeout=30) as client:
65
+ r = await client.get(url, params=params)
66
+ r.raise_for_status()
67
+ data = r.json()
68
+ async with self._lock:
69
+ if len(self.store) > self.max_items:
70
+ # drop an arbitrary item (good enough for a tiny Space)
71
+ self.store.pop(next(iter(self.store)))
72
+ self.store[k] = (time.time() + ttl, data)
73
+ return data
74
+
75
+ CACHE = TTLCache()
76
+
77
+ # ----------------- polite throttling for STRING ------------------
78
+ _last_string_call = 0.0
79
+ async def throttle_string():
80
+ """Be nice to STRING; ~1 req/sec as a courtesy."""
81
+ global _last_string_call
82
+ now = time.time()
83
+ wait = 1.05 - (now - _last_string_call)
84
+ if wait > 0:
85
+ await asyncio.sleep(wait)
86
+ _last_string_call = time.time()
87
+
88
+ # ----------------- Helpers -----------------
89
+ async def get_json_cached(url: str, params: dict, ttl: int):
90
+ return await CACHE.get(url, params, ttl)
91
+
92
+ # ----------------- Existing endpoints ---------
93
+ @app.get("/lit/eupmc")
94
+ async def europe_pmc_search(query: str, pageSize: int = 5):
95
+ url = "https://www.ebi.ac.uk/europepmc/webservices/rest/search"
96
+ params = {"query": query, "format": "json", "pageSize": pageSize}
97
+ return await get_json_cached(url, params, ttl=600)
98
+
99
+ @app.get("/lit/pubmed_esearch")
100
+ async def pubmed_esearch(term: str, retmax: int = 10):
101
+ url = "https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi"
102
+ params = {"db":"pubmed","term":term,"retmode":"json","retmax":retmax}
103
+ return await get_json_cached(url, params, ttl=600)
104
+
105
+ @app.get("/trials/search")
106
+ async def ctgov_v2_studies(q: str, pageSize: int = 5):
107
+ url = "https://clinicaltrials.gov/api/v2/studies"
108
+ params = {"query.term": q, "pageSize": pageSize}
109
+ return await get_json_cached(url, params, ttl=900)
110
+
111
+ @app.get("/rxnav/rxcui")
112
+ async def rxnav_rxcui(name: str):
113
+ url = "https://rxnav.nlm.nih.gov/REST/rxcui.json"
114
+ params = {"name": name}
115
+ return await get_json_cached(url, params, ttl=86400)
116
+
117
+ @app.get("/openfda/ae")
118
+ async def openfda_adverse_events(drug: str, limit: int = 5):
119
+ url = "https://api.fda.gov/drug/event.json"
120
+ params = {"search": f'patient.drug.medicinalproduct:"{drug}"', "limit": limit}
121
+ return await get_json_cached(url, params, ttl=3600)
122
+
123
+ @app.get("/pubchem/compound_by_name")
124
+ async def pubchem_by_name(name: str):
125
+ url = f"https://pubchem.ncbi.nlm.nih.gov/rest/pug/compound/name/{name}/JSON"
126
+ return await get_json_cached(url, None, ttl=86400)
127
+
128
+ @app.get("/uniprot/search")
129
+ async def uniprot_search(query: str, size: int = 5):
130
+ url = "https://rest.uniprot.org/uniprotkb/search"
131
+ params = {"query": query, "format": "json", "size": size}
132
+ return await get_json_cached(url, params, ttl=86400)
133
+
134
+ @app.get("/gpcrdb/protein")
135
+ async def gpcrdb_protein(entry: str):
136
+ url = f"https://gpcrdb.org/services/protein/{entry}"
137
+ return await get_json_cached(url, None, ttl=86400)
138
+
139
+ @app.get("/string/network")
140
+ async def string_network(identifiers: str, species: int = 9606, limit: int = 50):
141
+ await throttle_string()
142
+ url = "https://string-db.org/api/json/network"
143
+ params = {"identifiers": identifiers, "species": species, "caller_identity": CALLER_ID, "limit": limit}
144
+ return await get_json_cached(url, params, ttl=3600)
145
+
146
+ # ----------------- STRING region heuristic -----------------
147
+ REGION_TERMS_DEFAULT = [
148
+ "prefrontal cortex","anterior cingulate cortex","mPFC","ACC","nucleus accumbens","ventral striatum",
149
+ "dorsal striatum","caudate","putamen","amygdala","hippocampus","thalamus","hypothalamus",
150
+ "insula","VTA","substantia nigra","cerebellum"
151
+ ]
152
+
153
+ async def eupmc_hitcount(q: str) -> int:
154
+ url = "https://www.ebi.ac.uk/europepmc/webservices/rest/search"
155
+ params = {"query": q, "format": "json", "pageSize": 0}
156
+ data = await get_json_cached(url, params, ttl=3600)
157
+ return int(data.get("hitCount", 0))
158
+
159
+ def collect_gene_symbols_from_string(edges: List[dict], focus: str) -> List[str]:
160
+ genes = set()
161
+ f = focus.upper()
162
+ for e in edges:
163
+ for k in ("preferredName_A","preferredName_B"):
164
+ g = e.get(k)
165
+ if g and g.upper() != f:
166
+ genes.add(g)
167
+ return list(genes)
168
+
169
+ @app.get("/heuristics/regions_from_string")
170
+ async def regions_from_string(
171
+ receptor: str = Query(..., description="e.g., HTR2A"),
172
+ species: int = 9606,
173
+ limit: int = 40,
174
+ regions: Optional[str] = Query(None, description="comma-separated region terms; default common regions")
175
+ ):
176
+ """
177
+ Heuristic: rank brain regions by (STRING-weighted) literature co-occurrence.
178
+ """
179
+ # 1) STRING neighbors
180
+ edges = await string_network(receptor, species=species, limit=limit)
181
+ neighbors = collect_gene_symbols_from_string(edges, receptor)
182
+
183
+ # precompute STRING confidence per neighbor
184
+ conf: Dict[str, float] = {}
185
+ for e in edges:
186
+ a, b, score = e.get("preferredName_A"), e.get("preferredName_B"), float(e.get("score", 0))
187
+ if a and a.upper() != receptor.upper():
188
+ conf[a] = max(conf.get(a, 0.0), score)
189
+ if b and b.upper() != receptor.upper():
190
+ conf[b] = max(conf.get(b, 0.0), score)
191
+
192
+ region_list = [r.strip() for r in (regions.split(",") if regions else REGION_TERMS_DEFAULT) if r.strip()]
193
+ # 2) Europe PMC hitCount per region
194
+ gene_clause = " OR ".join([receptor] + neighbors[:25]) # cap size
195
+ tasks = []
196
+ for region in region_list:
197
+ q = f'("{region}") AND ({gene_clause})'
198
+ tasks.append(eupmc_hitcount(q))
199
+ counts = await asyncio.gather(*tasks)
200
+
201
+ # 3) Weighting
202
+ import math
203
+ mean_conf = sum(conf.values())/max(len(conf),1)
204
+ results = []
205
+ for region, hc in zip(region_list, counts):
206
+ score = (math.log10(hc+1.0)) * (mean_conf if conf else 0.2)
207
+ results.append({"region": region, "hits": hc, "weighted_score": round(score, 4)})
208
+
209
+ results.sort(key=lambda x: x["weighted_score"], reverse=True)
210
+ return {
211
+ "focus": receptor,
212
+ "neighbors_considered": neighbors[:25],
213
+ "regions_ranked": results,
214
+ "notes": "Exploratory heuristic using STRING neighbors + Europe PMC co-occurrence."
215
+ }
216
+
217
+ # ----------------- Aggregator (adds region heuristic) --------
218
+ @app.get("/mechanism_graph")
219
+ async def mechanism_graph(
220
+ receptor: str = Query(..., description="e.g., HTR2A"),
221
+ species: int = 9606,
222
+ symptom: str = "apathy"
223
+ ):
224
+ gpcr_entry = f"{receptor.lower()}_human" if not receptor.lower().endswith("_human") else receptor.lower()
225
+
226
+ # cache-powered parallel fetches
227
+ gpcr = get_json_cached(f"https://gpcrdb.org/services/protein/{gpcr_entry}", None, ttl=86400)
228
+ string_net = get_json_cached("https://string-db.org/api/json/network",
229
+ {"identifiers": receptor, "species": species, "caller_identity": CALLER_ID, "limit": 50},
230
+ ttl=3600)
231
+ lit = get_json_cached("https://www.ebi.ac.uk/europepmc/webservices/rest/search",
232
+ {"query": f"{receptor} AND {symptom}", "format": "json", "pageSize": 10},
233
+ ttl=600)
234
+ # call our local async fn without FastAPI wrapper
235
+ region_scores = regions_from_string.__wrapped__(receptor=receptor, species=species, limit=40, regions=None)
236
+
237
+ gpcr_r, string_r, lit_r, regions_r = await asyncio.gather(gpcr, string_net, lit, region_scores)
238
+
239
+ return {
240
+ "receptor": receptor,
241
+ "gpcrdb": gpcr_r,
242
+ "string": string_r,
243
+ "literature": lit_r,
244
+ "region_scores": regions_r,
245
+ "notes": "Mechanism aggregator with cache + STRING→region heuristic"
246
+ }