abinazebinoy commited on
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 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
+ });