Skip to content

Commit cf1cc81

Browse files
authored
Merge pull request #1 from Varnasr/claude/build-bridgestack-InqNE
Build out FastAPI application for OpenStacks API layer
2 parents b7932de + 491d12e commit cf1cc81

30 files changed

Lines changed: 910 additions & 33 deletions

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
*.db
2+
__pycache__/
3+
.pytest_cache/

Dockerfile

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
FROM python:3.12-slim
2+
3+
WORKDIR /app
4+
5+
COPY requirements.txt .
6+
RUN pip install --no-cache-dir -r requirements.txt
7+
8+
COPY . .
9+
10+
EXPOSE 8000
11+
12+
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]

README.md

Lines changed: 148 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -4,63 +4,178 @@
44

55
[![Part of OpenStacks](https://img.shields.io/badge/Part%20of-OpenStacks-blue)](https://openstacks.dev)
66
[![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE)
7-
[![Status: Early Stage](https://img.shields.io/badge/Status-Early%20Stage-orange)]()
7+
[![Python 3.11+](https://img.shields.io/badge/Python-3.11%2B-blue)]()
8+
[![FastAPI](https://img.shields.io/badge/FastAPI-0.104%2B-009688)]()
89

910
> The API layer for OpenStacks — connecting database to frontend.
1011
1112
---
1213

13-
## Status
14+
## Quick Start
1415

15-
**This repository is in early development.** The architecture and goals are documented below, but the FastAPI application has not yet been implemented. Contributions are welcome to help build this out.
16+
```bash
17+
# Clone and install
18+
git clone https://github.com/Varnasr/BridgeStack.git
19+
cd BridgeStack
20+
pip install -r requirements.txt
1621

17-
## Vision
22+
# Run the API
23+
uvicorn app.main:app --reload
1824

19-
BridgeStack will provide a REST API layer connecting [RootStack](https://github.com/Varnasr/RootStack) (database) to [ViewStack](https://github.com/Varnasr/ViewStack) (frontend):
25+
# Open docs
26+
# http://localhost:8000/docs
27+
```
28+
29+
### With Docker
2030

21-
- **FastAPI application** with auto-generated API documentation
22-
- **Data models** mapped to RootStack schemas
23-
- **RESTful endpoints** for querying development sector data
24-
- **Authentication** for write operations
31+
```bash
32+
docker compose up
33+
```
2534

26-
### Planned Architecture
35+
## Architecture
2736

2837
```
29-
RootStack (Database) → BridgeStack (API) → ViewStack (Frontend)
38+
RootStack (SQLite) → BridgeStack (FastAPI) → ViewStack / EquityStack / FieldStack
3039
```
3140

32-
### Planned Structure
41+
BridgeStack serves as the middleware connecting [RootStack](https://github.com/Varnasr/RootStack) data to all consumer Stacks:
42+
43+
| Stack | Role | Connection |
44+
|-------|------|------------|
45+
| [RootStack](https://github.com/Varnasr/RootStack) | SQLite schemas & seed data | Data source |
46+
| **BridgeStack** (this repo) | REST API (FastAPI) | You are here |
47+
| [ViewStack](https://github.com/Varnasr/ViewStack) | Frontend dashboards | Consumes API |
48+
| [EquityStack](https://github.com/Varnasr/EquityStack) | Python analysis workflows | Consumes API |
49+
| [FieldStack](https://github.com/Varnasr/FieldStack) | R fieldwork tools | Consumes API |
50+
| [InsightStack](https://github.com/Varnasr/InsightStack) | MEL tools | Consumes API |
51+
| [SignalStack](https://github.com/Varnasr/SignalStack) | Curated content | Consumes API |
52+
53+
## API Endpoints
54+
55+
All endpoints are prefixed with `/api/v1`.
56+
57+
### Geography
58+
59+
| Method | Endpoint | Description |
60+
|--------|----------|-------------|
61+
| GET | `/geography/states` | List all states (filter: `?region=`) |
62+
| GET | `/geography/states/{id}` | State detail with districts |
63+
| GET | `/geography/districts` | List districts (filter: `?state_id=`, `?tier=`) |
64+
| GET | `/geography/districts/{id}` | District detail |
65+
66+
### Sectors
67+
68+
| Method | Endpoint | Description |
69+
|--------|----------|-------------|
70+
| GET | `/sectors/` | List all development sectors |
71+
| GET | `/sectors/{id}` | Sector detail |
72+
73+
### Indicators
74+
75+
| Method | Endpoint | Description |
76+
|--------|----------|-------------|
77+
| GET | `/indicators/` | List indicators (filter: `?sector_id=`, `?source=`) |
78+
| GET | `/indicators/{id}` | Indicator detail with values |
79+
| GET | `/indicators/values/` | Query data points (filter: `?indicator_id=`, `?state_id=`, `?year=`) |
80+
81+
### Policies
82+
83+
| Method | Endpoint | Description |
84+
|--------|----------|-------------|
85+
| GET | `/policies/schemes` | List schemes (filter: `?sector_id=`, `?status=`, `?level=`) |
86+
| GET | `/policies/schemes/{id}` | Scheme detail with budgets & coverage |
87+
| GET | `/policies/budgets` | Budget data (filter: `?scheme_id=`, `?fiscal_year=`) |
88+
| GET | `/policies/coverage` | Coverage data (filter: `?scheme_id=`, `?state_id=`) |
89+
90+
### Tools
91+
92+
| Method | Endpoint | Description |
93+
|--------|----------|-------------|
94+
| GET | `/tools/` | List tools (filter: `?stack=`, `?language=`, `?tool_type=`, `?difficulty=`) |
95+
| GET | `/tools/{id}` | Tool detail |
96+
97+
### Health
98+
99+
| Method | Endpoint | Description |
100+
|--------|----------|-------------|
101+
| GET | `/` | API info and ecosystem map |
102+
| GET | `/health` | Health check |
103+
104+
## Project Structure
33105

34106
```
35107
BridgeStack/
36108
├── app/
37-
│ ├── main.py # FastAPI application entry point
38-
│ ├── routes/ # API endpoint definitions
39-
│ ├── models/ # SQLAlchemy/Pydantic models
40-
│ ├── schemas/ # Request/response schemas
41-
│ └── core/ # Config, database connection, auth
42-
├── tests/ # API tests
43-
├── requirements.txt # Python dependencies
44-
└── docker-compose.yml # Local development setup
109+
│ ├── main.py # FastAPI app entry point
110+
│ ├── core/
111+
│ │ ├── config.py # Settings (env-configurable)
112+
│ │ └── database.py # SQLite/SQLAlchemy setup
113+
│ ├── models/ # SQLAlchemy ORM models
114+
│ │ ├── geography.py # States, Districts
115+
│ │ ├── sectors.py # Development sectors
116+
│ │ ├── indicators.py # Indicators & values
117+
│ │ ├── policies.py # Schemes, budgets, coverage
118+
│ │ └── tools.py # OpenStacks tool catalog
119+
│ ├── schemas/ # Pydantic response schemas
120+
│ │ ├── geography.py
121+
│ │ ├── sectors.py
122+
│ │ ├── indicators.py
123+
│ │ ├── policies.py
124+
│ │ └── tools.py
125+
│ └── routes/ # API route handlers
126+
│ ├── geography.py
127+
│ ├── sectors.py
128+
│ ├── indicators.py
129+
│ ├── policies.py
130+
│ └── tools.py
131+
├── tests/
132+
│ └── test_api.py # 14 endpoint tests
133+
├── requirements.txt
134+
├── Dockerfile
135+
└── docker-compose.yml
45136
```
46137

47-
## How to Contribute
138+
## Configuration
139+
140+
Environment variables (prefix `BRIDGE_`):
141+
142+
| Variable | Default | Description |
143+
|----------|---------|-------------|
144+
| `BRIDGE_DATABASE_URL` | `sqlite:///./rootstack.db` | Database connection string |
145+
| `BRIDGE_DEBUG` | `false` | Enable debug mode |
146+
| `BRIDGE_CORS_ORIGINS` | `["*"]` | Allowed CORS origins |
147+
148+
## Using with RootStack
149+
150+
To populate the database, clone and run [RootStack](https://github.com/Varnasr/RootStack) setup, then point BridgeStack at the generated SQLite file:
48151

49-
This is a great repo to contribute to if you have experience with:
50-
- FastAPI or similar Python web frameworks
51-
- REST API design
52-
- SQLAlchemy and database integrations
53-
- API testing (pytest, httpx)
152+
```bash
153+
# In RootStack directory
154+
bash scripts/setup.sh
54155

55-
See the [OpenStacks hub](https://github.com/Varnasr/OpenStacks-for-Change) for ecosystem-wide contribution guidelines.
156+
# Copy the database to BridgeStack
157+
cp rootstack.db ../BridgeStack/
158+
159+
# Start the API
160+
cd ../BridgeStack
161+
uvicorn app.main:app --reload
162+
```
163+
164+
## Running Tests
165+
166+
```bash
167+
pytest tests/ -v
168+
```
169+
170+
## How to Contribute
56171

57-
## How It Connects
172+
See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines. Areas where contributions are welcome:
58173

59-
| Stack | Role | Link |
60-
|-------|------|------|
61-
| [RootStack](https://github.com/Varnasr/RootStack) | Database schemas & seed data | Provides data to BridgeStack |
62-
| **BridgeStack** (this repo) | API backend (FastAPI) | You are here |
63-
| [ViewStack](https://github.com/Varnasr/ViewStack) | Frontend UI | Consumes BridgeStack API |
174+
- Additional query endpoints and aggregations
175+
- Authentication for write operations
176+
- Pagination and rate limiting
177+
- WebSocket support for real-time data
178+
- PostgreSQL adapter
64179

65180
## License
66181

app/__init__.py

Whitespace-only changes.

app/core/__init__.py

Whitespace-only changes.

app/core/config.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
from pydantic_settings import BaseSettings
2+
3+
4+
class Settings(BaseSettings):
5+
app_name: str = "BridgeStack API"
6+
app_version: str = "0.2.0"
7+
app_description: str = "API backend bridging OpenStacks data layers"
8+
database_url: str = "sqlite:///./rootstack.db"
9+
debug: bool = False
10+
cors_origins: list[str] = ["*"]
11+
12+
model_config = {"env_prefix": "BRIDGE_"}
13+
14+
15+
settings = Settings()

app/core/database.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
from sqlalchemy import create_engine, event
2+
from sqlalchemy.orm import DeclarativeBase, sessionmaker
3+
4+
from app.core.config import settings
5+
6+
engine = create_engine(
7+
settings.database_url,
8+
connect_args={"check_same_thread": False},
9+
)
10+
11+
12+
@event.listens_for(engine, "connect")
13+
def _enable_foreign_keys(dbapi_conn, connection_record):
14+
cursor = dbapi_conn.cursor()
15+
cursor.execute("PRAGMA foreign_keys=ON")
16+
cursor.close()
17+
18+
19+
SessionLocal = sessionmaker(bind=engine)
20+
21+
22+
class Base(DeclarativeBase):
23+
pass
24+
25+
26+
def get_db():
27+
db = SessionLocal()
28+
try:
29+
yield db
30+
finally:
31+
db.close()

app/main.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
from fastapi import FastAPI
2+
from fastapi.middleware.cors import CORSMiddleware
3+
4+
from app.core.config import settings
5+
from app.core.database import Base, engine
6+
from app.routes import geography, indicators, policies, sectors, tools
7+
8+
Base.metadata.create_all(bind=engine)
9+
10+
app = FastAPI(
11+
title=settings.app_name,
12+
version=settings.app_version,
13+
description=settings.app_description,
14+
docs_url="/docs",
15+
redoc_url="/redoc",
16+
)
17+
18+
app.add_middleware(
19+
CORSMiddleware,
20+
allow_origins=settings.cors_origins,
21+
allow_credentials=True,
22+
allow_methods=["*"],
23+
allow_headers=["*"],
24+
)
25+
26+
app.include_router(geography.router, prefix="/api/v1")
27+
app.include_router(sectors.router, prefix="/api/v1")
28+
app.include_router(indicators.router, prefix="/api/v1")
29+
app.include_router(policies.router, prefix="/api/v1")
30+
app.include_router(tools.router, prefix="/api/v1")
31+
32+
33+
@app.get("/", tags=["Health"])
34+
def root():
35+
return {
36+
"name": settings.app_name,
37+
"version": settings.app_version,
38+
"docs": "/docs",
39+
"stacks": {
40+
"data": "RootStack",
41+
"api": "BridgeStack",
42+
"frontend": "ViewStack",
43+
"analysis": "EquityStack",
44+
"fieldwork": "FieldStack",
45+
"mel": "InsightStack",
46+
"content": "SignalStack",
47+
},
48+
}
49+
50+
51+
@app.get("/health", tags=["Health"])
52+
def health_check():
53+
return {"status": "healthy"}

app/models/__init__.py

Whitespace-only changes.

app/models/geography.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
from sqlalchemy import Column, Float, ForeignKey, Integer, Text
2+
from sqlalchemy.orm import relationship
3+
4+
from app.core.database import Base
5+
6+
7+
class State(Base):
8+
__tablename__ = "states"
9+
10+
state_id = Column(Text, primary_key=True)
11+
state_name = Column(Text, nullable=False)
12+
region = Column(Text)
13+
state_type = Column(Text)
14+
capital = Column(Text)
15+
area_sq_km = Column(Float)
16+
census_2011_pop = Column(Integer)
17+
18+
districts = relationship("District", back_populates="state")
19+
20+
21+
class District(Base):
22+
__tablename__ = "districts"
23+
24+
district_id = Column(Text, primary_key=True)
25+
district_name = Column(Text, nullable=False)
26+
state_id = Column(Text, ForeignKey("states.state_id"))
27+
tier = Column(Text)
28+
census_2011_pop = Column(Integer)
29+
area_sq_km = Column(Float)
30+
latitude = Column(Float)
31+
longitude = Column(Float)
32+
33+
state = relationship("State", back_populates="districts")

0 commit comments

Comments
 (0)