Upload app.py
Browse files
app.py
CHANGED
|
@@ -44,7 +44,6 @@ source_extension_counts: dict[str, dict[str, int]] = {}
|
|
| 44 |
word_index: dict[str, set[int]] = {}
|
| 45 |
TOKEN_RE = re.compile(r"[a-z0-9]+|[\u4e00-\u9fff\u3400-\u4dbf]+")
|
| 46 |
TOKENIZER_VERSION = "cjk-bigram-boundary-fts5-v6-snippet-anchors"
|
| 47 |
-
APP_BUILD_VERSION = "2026-07-28-search-zip-v2"
|
| 48 |
LITERAL_PREFIX = "\0literal:"
|
| 49 |
API_CACHE_TTL_SECONDS = 120
|
| 50 |
SEARCH_CACHE_TTL_SECONDS = 300
|
|
@@ -446,10 +445,7 @@ class FulltextDatabases:
|
|
| 446 |
payload["snippet"] += "..."
|
| 447 |
snippets[doc_id_by_number[int(number)]] = payload
|
| 448 |
anchored_numbers.add(int(number))
|
| 449 |
-
except sqlite3.OperationalError
|
| 450 |
-
if timings is not None:
|
| 451 |
-
timings["snippet_anchor_error"] = 1
|
| 452 |
-
print(f"snippet_anchor_sql_failed={type(exc).__name__}:{exc}", flush=True)
|
| 453 |
break
|
| 454 |
numbers = [number for number in numbers if number not in anchored_numbers]
|
| 455 |
for offset in range(0, len(numbers), 500):
|
|
@@ -976,16 +972,6 @@ app = FastAPI(title="VOMEBOOK Search", version="1.0", lifespan=lifespan)
|
|
| 976 |
app.add_middleware(GZipMiddleware, minimum_size=500)
|
| 977 |
app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"])
|
| 978 |
|
| 979 |
-
@app.middleware("http")
|
| 980 |
-
async def timing_headers(request: Request, call_next):
|
| 981 |
-
started = time.perf_counter()
|
| 982 |
-
response = await call_next(request)
|
| 983 |
-
app_timing = f'app;dur={(time.perf_counter() - started) * 1000:.2f}'
|
| 984 |
-
existing = response.headers.get("Server-Timing")
|
| 985 |
-
response.headers["Server-Timing"] = f"{existing}, {app_timing}" if existing else app_timing
|
| 986 |
-
response.headers["X-App-Version"] = APP_BUILD_VERSION
|
| 987 |
-
return response
|
| 988 |
-
|
| 989 |
class SearchRequest(BaseModel):
|
| 990 |
q: str = Field(default="", max_length=256)
|
| 991 |
sources: Optional[list[str]] = None
|
|
@@ -1114,35 +1100,16 @@ def run_search(body: SearchRequest, sources_filter=None, timings=None):
|
|
| 1114 |
payload["index_generation"] = fulltext_generation
|
| 1115 |
return payload
|
| 1116 |
|
| 1117 |
-
def format_server_timing(timings: dict) -> str:
|
| 1118 |
-
values = [f'cache;desc="{timings.get("cache", "unknown")}";dur={timings.get("cache_lookup", 0):.2f}']
|
| 1119 |
-
for name in ("sqlite", "metadata", "filter", "rank", "sort", "summaries", "snippet_anchor_sql", "snippet_anchor_error", "snippet_fallback_sql", "snippet_payload", "snippets", "decorate", "engine", "json"):
|
| 1120 |
-
if name in timings:
|
| 1121 |
-
values.append(f"{name};dur={timings[name]:.2f}")
|
| 1122 |
-
return ", ".join(values)
|
| 1123 |
-
|
| 1124 |
@app.post("/api/search")
|
| 1125 |
|
| 1126 |
def api_search(body: SearchRequest):
|
| 1127 |
-
|
| 1128 |
-
payload = run_search(body, timings=timings)
|
| 1129 |
-
json_started = time.perf_counter()
|
| 1130 |
-
response = JSONResponse(payload)
|
| 1131 |
-
timings["json"] = (time.perf_counter() - json_started) * 1000
|
| 1132 |
-
response.headers["Server-Timing"] = format_server_timing(timings)
|
| 1133 |
-
return response
|
| 1134 |
@app.post("/api/search/{source_slug}")
|
| 1135 |
|
| 1136 |
def api_search_source(source_slug: str, body: SearchRequest):
|
| 1137 |
if source_slug not in source_counts:
|
| 1138 |
return JSONResponse({"error": "source not found", "results": [], "total": 0}, status_code=404)
|
| 1139 |
-
|
| 1140 |
-
payload = run_search(body, [source_slug], timings)
|
| 1141 |
-
json_started = time.perf_counter()
|
| 1142 |
-
response = JSONResponse(payload)
|
| 1143 |
-
timings["json"] = (time.perf_counter() - json_started) * 1000
|
| 1144 |
-
response.headers["Server-Timing"] = format_server_timing(timings)
|
| 1145 |
-
return response
|
| 1146 |
|
| 1147 |
@app.get("/api/ping")
|
| 1148 |
def api_ping():
|
|
|
|
| 44 |
word_index: dict[str, set[int]] = {}
|
| 45 |
TOKEN_RE = re.compile(r"[a-z0-9]+|[\u4e00-\u9fff\u3400-\u4dbf]+")
|
| 46 |
TOKENIZER_VERSION = "cjk-bigram-boundary-fts5-v6-snippet-anchors"
|
|
|
|
| 47 |
LITERAL_PREFIX = "\0literal:"
|
| 48 |
API_CACHE_TTL_SECONDS = 120
|
| 49 |
SEARCH_CACHE_TTL_SECONDS = 300
|
|
|
|
| 445 |
payload["snippet"] += "..."
|
| 446 |
snippets[doc_id_by_number[int(number)]] = payload
|
| 447 |
anchored_numbers.add(int(number))
|
| 448 |
+
except sqlite3.OperationalError:
|
|
|
|
|
|
|
|
|
|
| 449 |
break
|
| 450 |
numbers = [number for number in numbers if number not in anchored_numbers]
|
| 451 |
for offset in range(0, len(numbers), 500):
|
|
|
|
| 972 |
app.add_middleware(GZipMiddleware, minimum_size=500)
|
| 973 |
app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"])
|
| 974 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 975 |
class SearchRequest(BaseModel):
|
| 976 |
q: str = Field(default="", max_length=256)
|
| 977 |
sources: Optional[list[str]] = None
|
|
|
|
| 1100 |
payload["index_generation"] = fulltext_generation
|
| 1101 |
return payload
|
| 1102 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1103 |
@app.post("/api/search")
|
| 1104 |
|
| 1105 |
def api_search(body: SearchRequest):
|
| 1106 |
+
return JSONResponse(run_search(body))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1107 |
@app.post("/api/search/{source_slug}")
|
| 1108 |
|
| 1109 |
def api_search_source(source_slug: str, body: SearchRequest):
|
| 1110 |
if source_slug not in source_counts:
|
| 1111 |
return JSONResponse({"error": "source not found", "results": [], "total": 0}, status_code=404)
|
| 1112 |
+
return JSONResponse(run_search(body, [source_slug]))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1113 |
|
| 1114 |
@app.get("/api/ping")
|
| 1115 |
def api_ping():
|