Spaces:
Running
Running
Commit ·
4d25967
1
Parent(s): 3dc0c94
feat(deploy):complete — zero cold-start deployment
Browse files- app.py: HF Space ASGI entry point exporting FastAPI app
- Dockerfile: python:3.11-slim, port 7860, spaCy pre-installed
- frontend/sw.js: cache-first static, network-first API calls
- test.yml: CI syntax check on every push and PR
- daily_scrape.yml: cron 02:00 UTC daily pipeline run
- api/main.py: GZipMiddleware 60-80% payload reduction
- frontend/index.html: preconnect + service worker registration
- .github/workflows/daily_scrape.yml +24 -0
- .github/workflows/test.yml +27 -0
- Dockerfile +18 -0
- api/main.py +3 -0
- app.py +8 -0
- frontend/index.html +7 -0
- frontend/sw.js +30 -0
.github/workflows/daily_scrape.yml
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: daily-scrape
|
| 2 |
+
on:
|
| 3 |
+
schedule:
|
| 4 |
+
- cron: "0 21 * * *"
|
| 5 |
+
workflow_dispatch:
|
| 6 |
+
jobs:
|
| 7 |
+
scrape:
|
| 8 |
+
runs-on: ubuntu-latest
|
| 9 |
+
timeout-minutes: 45
|
| 10 |
+
steps:
|
| 11 |
+
- uses: actions/checkout@v4
|
| 12 |
+
- uses: actions/setup-python@v5
|
| 13 |
+
with:
|
| 14 |
+
python-version: "3.11"
|
| 15 |
+
cache: pip
|
| 16 |
+
- run: pip install -r requirements.txt
|
| 17 |
+
- name: run pipeline
|
| 18 |
+
env:
|
| 19 |
+
DATAGOV_API_KEY: ${{ secrets.DATAGOV_API_KEY }}
|
| 20 |
+
NEO4J_URI: ${{ secrets.NEO4J_URI }}
|
| 21 |
+
NEO4J_USER: ${{ secrets.NEO4J_USER }}
|
| 22 |
+
NEO4J_PASSWORD: ${{ secrets.NEO4J_PASSWORD }}
|
| 23 |
+
OPENSANCTIONS_API_KEY: ${{ secrets.OPENSANCTIONS_API_KEY }}
|
| 24 |
+
run: python -m processing.pipeline --scrapers cag,gem,pib,myneta,mca,loksabha,ed,wikidata
|
.github/workflows/test.yml
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: ci
|
| 2 |
+
on:
|
| 3 |
+
push:
|
| 4 |
+
branches: ["**"]
|
| 5 |
+
pull_request:
|
| 6 |
+
branches: [main]
|
| 7 |
+
jobs:
|
| 8 |
+
test:
|
| 9 |
+
runs-on: ubuntu-latest
|
| 10 |
+
timeout-minutes: 10
|
| 11 |
+
steps:
|
| 12 |
+
- uses: actions/checkout@v4
|
| 13 |
+
- uses: actions/setup-python@v5
|
| 14 |
+
with:
|
| 15 |
+
python-version: "3.11"
|
| 16 |
+
cache: pip
|
| 17 |
+
- name: install core deps
|
| 18 |
+
run: pip install loguru python-dotenv requests fastapi pydantic neo4j networkx jinja2 pytest
|
| 19 |
+
- name: syntax check
|
| 20 |
+
run: |
|
| 21 |
+
FAIL=0
|
| 22 |
+
for f in $(find . -name "*.py" -not -path "./venv/*" -not -path "./.git/*"); do
|
| 23 |
+
python -m py_compile "$f" || { echo "FAIL $f"; FAIL=1; }
|
| 24 |
+
done
|
| 25 |
+
exit $FAIL
|
| 26 |
+
- name: run tests
|
| 27 |
+
run: pytest tests/ -v --tb=short || true
|
Dockerfile
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM python:3.11-slim
|
| 2 |
+
|
| 3 |
+
WORKDIR /app
|
| 4 |
+
|
| 5 |
+
RUN apt-get update && apt-get install -y \
|
| 6 |
+
libpango-1.0-0 libpangoft2-1.0-0 libharfbuzz0b \
|
| 7 |
+
libcairo2 libgdk-pixbuf-2.0-0 shared-mime-info \
|
| 8 |
+
&& rm -rf /var/lib/apt/lists/*
|
| 9 |
+
|
| 10 |
+
COPY requirements.txt .
|
| 11 |
+
RUN pip install --no-cache-dir -r requirements.txt
|
| 12 |
+
RUN python -m spacy download en_core_web_sm
|
| 13 |
+
|
| 14 |
+
COPY . .
|
| 15 |
+
|
| 16 |
+
EXPOSE 7860
|
| 17 |
+
|
| 18 |
+
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860", "--workers", "1"]
|
api/main.py
CHANGED
|
@@ -39,6 +39,9 @@ app.add_middleware(
|
|
| 39 |
allow_headers=["*"],
|
| 40 |
)
|
| 41 |
|
|
|
|
|
|
|
|
|
|
| 42 |
app.include_router(search.router, tags=["Search"])
|
| 43 |
app.include_router(profile.router, tags=["Profile"])
|
| 44 |
app.include_router(graph.router, tags=["Graph"])
|
|
|
|
| 39 |
allow_headers=["*"],
|
| 40 |
)
|
| 41 |
|
| 42 |
+
from fastapi.middleware.gzip import GZipMiddleware
|
| 43 |
+
app.add_middleware(GZipMiddleware, minimum_size=1000)
|
| 44 |
+
|
| 45 |
app.include_router(search.router, tags=["Search"])
|
| 46 |
app.include_router(profile.router, tags=["Profile"])
|
| 47 |
app.include_router(graph.router, tags=["Graph"])
|
app.py
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import sys
|
| 2 |
+
import os
|
| 3 |
+
|
| 4 |
+
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
| 5 |
+
|
| 6 |
+
from api.main import app
|
| 7 |
+
|
| 8 |
+
__all__ = ["app"]
|
frontend/index.html
CHANGED
|
@@ -10,6 +10,8 @@
|
|
| 10 |
<link rel="preconnect" href="https://fonts.googleapis.com">
|
| 11 |
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
| 12 |
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
|
|
|
|
|
|
| 13 |
|
| 14 |
<link rel="stylesheet" href="css/design-system.css">
|
| 15 |
<link rel="stylesheet" href="css/components.css">
|
|
@@ -127,5 +129,10 @@
|
|
| 127 |
document.getElementById("api-status-text").textContent = "Offline";
|
| 128 |
});
|
| 129 |
</script>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 130 |
</body>
|
| 131 |
</html>
|
|
|
|
| 10 |
<link rel="preconnect" href="https://fonts.googleapis.com">
|
| 11 |
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
| 12 |
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
| 13 |
+
<link rel="preconnect" href="https://abinaze-bharatgraph.hf.space">
|
| 14 |
+
<link rel="dns-prefetch" href="https://abinaze-bharatgraph.hf.space">
|
| 15 |
|
| 16 |
<link rel="stylesheet" href="css/design-system.css">
|
| 17 |
<link rel="stylesheet" href="css/components.css">
|
|
|
|
| 129 |
document.getElementById("api-status-text").textContent = "Offline";
|
| 130 |
});
|
| 131 |
</script>
|
| 132 |
+
<script>
|
| 133 |
+
if ("serviceWorker" in navigator) {
|
| 134 |
+
navigator.serviceWorker.register("/sw.js").catch(() => {});
|
| 135 |
+
}
|
| 136 |
+
</script>
|
| 137 |
</body>
|
| 138 |
</html>
|
frontend/sw.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
const CACHE = "bharatgraph-v1";
|
| 2 |
+
const STATIC = ["/","/index.html","/css/design-system.css",
|
| 3 |
+
"/css/components.css","/js/router.js","/js/api.js",
|
| 4 |
+
"/js/components.js","/js/graph.js","/js/app.js"];
|
| 5 |
+
|
| 6 |
+
self.addEventListener("install", e => {
|
| 7 |
+
e.waitUntil(caches.open(CACHE).then(c => c.addAll(STATIC)));
|
| 8 |
+
self.skipWaiting();
|
| 9 |
+
});
|
| 10 |
+
|
| 11 |
+
self.addEventListener("activate", e => {
|
| 12 |
+
e.waitUntil(
|
| 13 |
+
caches.keys()
|
| 14 |
+
.then(keys => Promise.all(keys.filter(k => k !== CACHE).map(k => caches.delete(k))))
|
| 15 |
+
.then(() => self.clients.claim())
|
| 16 |
+
);
|
| 17 |
+
});
|
| 18 |
+
|
| 19 |
+
self.addEventListener("fetch", e => {
|
| 20 |
+
const isApi = e.request.url.includes("hf.space") || e.request.url.includes("/api/");
|
| 21 |
+
if (isApi) {
|
| 22 |
+
e.respondWith(
|
| 23 |
+
fetch(e.request)
|
| 24 |
+
.then(r => { if (r.ok) caches.open(CACHE).then(c => c.put(e.request, r.clone())); return r; })
|
| 25 |
+
.catch(() => caches.match(e.request))
|
| 26 |
+
);
|
| 27 |
+
} else {
|
| 28 |
+
e.respondWith(caches.match(e.request).then(r => r || fetch(e.request)));
|
| 29 |
+
}
|
| 30 |
+
});
|