Skip to content

Commit 3192d49

Browse files
Update
1 parent aaad28b commit 3192d49

7 files changed

Lines changed: 139 additions & 115 deletions

File tree

README.md

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ A FastAPI service for running PolicyEngine microsimulations with Supabase backen
66

77
- RESTful API for creating and managing simulations
88
- Supabase for database and storage
9-
- Redis + Celery for background task processing
9+
- Redis caching and Celery for background task processing
1010
- SQLModel for type-safe database models
1111
- Terraform deployment to AWS
1212

@@ -184,16 +184,15 @@ ruff check --fix .
184184

185185
### Database migrations
186186

187-
Create a new migration:
187+
Schema migrations are managed through Supabase SQL migrations:
188188

189189
```bash
190-
alembic revision --autogenerate -m "description"
191-
```
192-
193-
Apply migrations:
190+
# Create a new migration
191+
supabase migration new migration_name
194192

195-
```bash
196-
alembic upgrade head
193+
# Migrations are applied automatically on supabase start
194+
# Or manually push to production
195+
supabase db push
197196
```
198197

199198
### Supabase management
@@ -227,8 +226,7 @@ policyengine-api-v2/
227226
│ ├── services/ # Database, storage, initialization
228227
│ ├── tasks/ # Celery tasks
229228
│ └── main.py # FastAPI application
230-
├── supabase/ # Supabase configuration
231-
├── migrations/ # Alembic migrations
229+
├── supabase/ # Supabase configuration and migrations
232230
├── tests/ # Test suite
233231
├── docker-compose.yml # Docker services (API, worker, Redis)
234232
└── pyproject.toml # Dependencies

docs/supabase-setup.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -132,14 +132,14 @@ for f in files:
132132

133133
## Database schema
134134

135-
Schema is managed via Alembic migrations. The SQLModel models automatically generate the schema:
135+
Schema is managed through Supabase SQL migrations. The SQLModel models define the schema, which you can export:
136136

137137
```bash
138-
# Create migration
139-
alembic revision --autogenerate -m "add new table"
138+
# Create a new migration file
139+
supabase migration new migration_name
140140

141-
# Apply migrations
142-
alembic upgrade head
141+
# Edit the SQL file in supabase/migrations/ to define schema changes
142+
# Migrations are applied automatically on 'supabase start'
143143
```
144144

145145
## Useful commands

src/policyengine_api/api/parameters.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from uuid import UUID
33

44
from fastapi import APIRouter, Depends, HTTPException
5+
from fastapi_cache.decorator import cache
56
from sqlmodel import Session, select
67

78
from policyengine_api.models import Parameter, ParameterCreate, ParameterRead
@@ -23,9 +24,14 @@ def create_parameter(
2324

2425

2526
@router.get("/", response_model=List[ParameterRead])
26-
def list_parameters(session: Session = Depends(get_session)):
27-
"""List all parameters."""
28-
parameters = session.exec(select(Parameter)).all()
27+
@cache(expire=3600) # Cache for 1 hour
28+
def list_parameters(
29+
skip: int = 0, limit: int = 100, session: Session = Depends(get_session)
30+
):
31+
"""List all parameters with pagination, sorted by name."""
32+
parameters = session.exec(
33+
select(Parameter).order_by(Parameter.name).offset(skip).limit(limit)
34+
).all()
2935
return parameters
3036

3137

src/policyengine_api/api/variables.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from uuid import UUID
33

44
from fastapi import APIRouter, Depends, HTTPException
5+
from fastapi_cache.decorator import cache
56
from sqlmodel import Session, select
67

78
from policyengine_api.models import Variable, VariableCreate, VariableRead
@@ -21,9 +22,14 @@ def create_variable(variable: VariableCreate, session: Session = Depends(get_ses
2122

2223

2324
@router.get("/", response_model=List[VariableRead])
24-
def list_variables(session: Session = Depends(get_session)):
25-
"""List all variables."""
26-
variables = session.exec(select(Variable)).all()
25+
@cache(expire=3600) # Cache for 1 hour
26+
def list_variables(
27+
skip: int = 0, limit: int = 100, session: Session = Depends(get_session)
28+
):
29+
"""List all variables with pagination, sorted by name."""
30+
variables = session.exec(
31+
select(Variable).order_by(Variable.name).offset(skip).limit(limit)
32+
).all()
2733
return variables
2834

2935

src/policyengine_api/main.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
from contextlib import asynccontextmanager
22

33
from fastapi import FastAPI
4+
from fastapi_cache import FastAPICache
5+
from fastapi_cache.backends.redis import RedisBackend
6+
from redis import asyncio as aioredis
47
from rich.console import Console
58
import logfire
69

@@ -16,10 +19,16 @@
1619

1720
@asynccontextmanager
1821
async def lifespan(app: FastAPI):
19-
"""Initialize database on startup."""
22+
"""Initialize database and cache on startup."""
2023
console.print("[bold green]Initializing database...[/bold green]")
2124
init_db()
2225
console.print("[bold green]Database initialized[/bold green]")
26+
27+
console.print("[bold green]Initializing cache...[/bold green]")
28+
redis = aioredis.from_url(settings.redis_url, encoding="utf8", decode_responses=True)
29+
FastAPICache.init(RedisBackend(redis), prefix="fastapi-cache")
30+
console.print("[bold green]Cache initialized[/bold green]")
31+
2332
yield
2433

2534

terraform/main.tf

Lines changed: 90 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -138,84 +138,84 @@ resource "aws_ecs_cluster" "main" {
138138
}
139139
}
140140

141-
# Application Load Balancer
142-
resource "aws_security_group" "alb" {
143-
name = "${var.project_name}-alb-sg"
144-
description = "Security group for Application Load Balancer"
145-
vpc_id = aws_vpc.main.id
146-
147-
ingress {
148-
from_port = 80
149-
to_port = 80
150-
protocol = "tcp"
151-
cidr_blocks = ["0.0.0.0/0"]
152-
}
153-
154-
ingress {
155-
from_port = 443
156-
to_port = 443
157-
protocol = "tcp"
158-
cidr_blocks = ["0.0.0.0/0"]
159-
}
160-
161-
egress {
162-
from_port = 0
163-
to_port = 0
164-
protocol = "-1"
165-
cidr_blocks = ["0.0.0.0/0"]
166-
}
167-
168-
tags = {
169-
Name = "${var.project_name}-alb-sg"
170-
}
171-
}
172-
173-
resource "aws_lb" "main" {
174-
name = "${var.project_name}-alb"
175-
internal = false
176-
load_balancer_type = "application"
177-
security_groups = [aws_security_group.alb.id]
178-
subnets = aws_subnet.public[*].id
179-
180-
tags = {
181-
Name = "${var.project_name}-alb"
182-
}
183-
}
184-
185-
resource "aws_lb_target_group" "api" {
186-
name = "${var.project_name}-api-tg"
187-
port = 8000
188-
protocol = "HTTP"
189-
vpc_id = aws_vpc.main.id
190-
target_type = "ip"
191-
192-
health_check {
193-
enabled = true
194-
healthy_threshold = 2
195-
interval = 30
196-
matcher = "200"
197-
path = "/health"
198-
port = "traffic-port"
199-
protocol = "HTTP"
200-
timeout = 5
201-
unhealthy_threshold = 2
202-
}
203-
204-
tags = {
205-
Name = "${var.project_name}-api-tg"
206-
}
207-
}
208-
209-
resource "aws_lb_listener" "api" {
210-
load_balancer_arn = aws_lb.main.arn
211-
port = "80"
212-
protocol = "HTTP"
213-
214-
default_action {
215-
type = "forward"
216-
target_group_arn = aws_lb_target_group.api.arn
217-
}
218-
}
141+
# Application Load Balancer (COMMENTED OUT - Re-enable when AWS Support approves ALB access)
142+
# resource "aws_security_group" "alb" {
143+
# name = "${var.project_name}-alb-sg"
144+
# description = "Security group for Application Load Balancer"
145+
# vpc_id = aws_vpc.main.id
146+
#
147+
# ingress {
148+
# from_port = 80
149+
# to_port = 80
150+
# protocol = "tcp"
151+
# cidr_blocks = ["0.0.0.0/0"]
152+
# }
153+
#
154+
# ingress {
155+
# from_port = 443
156+
# to_port = 443
157+
# protocol = "tcp"
158+
# cidr_blocks = ["0.0.0.0/0"]
159+
# }
160+
#
161+
# egress {
162+
# from_port = 0
163+
# to_port = 0
164+
# protocol = "-1"
165+
# cidr_blocks = ["0.0.0.0/0"]
166+
# }
167+
#
168+
# tags = {
169+
# Name = "${var.project_name}-alb-sg"
170+
# }
171+
# }
172+
#
173+
# resource "aws_lb" "main" {
174+
# name = "${var.project_name}-alb"
175+
# internal = false
176+
# load_balancer_type = "application"
177+
# security_groups = [aws_security_group.alb.id]
178+
# subnets = aws_subnet.public[*].id
179+
#
180+
# tags = {
181+
# Name = "${var.project_name}-alb"
182+
# }
183+
# }
184+
#
185+
# resource "aws_lb_target_group" "api" {
186+
# name = "${var.project_name}-api-tg"
187+
# port = 8000
188+
# protocol = "HTTP"
189+
# vpc_id = aws_vpc.main.id
190+
# target_type = "ip"
191+
#
192+
# health_check {
193+
# enabled = true
194+
# healthy_threshold = 2
195+
# interval = 30
196+
# matcher = "200"
197+
# path = "/health"
198+
# port = "traffic-port"
199+
# protocol = "HTTP"
200+
# timeout = 5
201+
# unhealthy_threshold = 2
202+
# }
203+
#
204+
# tags = {
205+
# Name = "${var.project_name}-api-tg"
206+
# }
207+
# }
208+
#
209+
# resource "aws_lb_listener" "api" {
210+
# load_balancer_arn = aws_lb.main.arn
211+
# port = "80"
212+
# protocol = "HTTP"
213+
#
214+
# default_action {
215+
# type = "forward"
216+
# target_group_arn = aws_lb_target_group.api.arn
217+
# }
218+
# }
219219

220220
# Security group for ECS tasks
221221
resource "aws_security_group" "ecs" {
@@ -224,10 +224,10 @@ resource "aws_security_group" "ecs" {
224224
vpc_id = aws_vpc.main.id
225225

226226
ingress {
227-
from_port = 8000
228-
to_port = 8000
229-
protocol = "tcp"
230-
security_groups = [aws_security_group.alb.id]
227+
from_port = 8000
228+
to_port = 8000
229+
protocol = "tcp"
230+
cidr_blocks = ["0.0.0.0/0"] # Allow direct access from internet (temporary until ALB is enabled)
231231
}
232232

233233
egress {
@@ -456,13 +456,14 @@ resource "aws_ecs_service" "api" {
456456
assign_public_ip = true
457457
}
458458

459-
load_balancer {
460-
target_group_arn = aws_lb_target_group.api.arn
461-
container_name = "api"
462-
container_port = 8000
463-
}
464-
465-
depends_on = [aws_lb_listener.api]
459+
# Load balancer configuration commented out until ALB access is enabled
460+
# load_balancer {
461+
# target_group_arn = aws_lb_target_group.api.arn
462+
# container_name = "api"
463+
# container_port = 8000
464+
# }
465+
#
466+
# depends_on = [aws_lb_listener.api]
466467

467468
tags = {
468469
Name = "${var.project_name}-api-service"

terraform/outputs.tf

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,14 @@ output "redis_endpoint" {
88
value = aws_elasticache_cluster.redis.cache_nodes[0].address
99
}
1010

11-
output "load_balancer_url" {
12-
description = "Load balancer URL for API"
13-
value = "http://${aws_lb.main.dns_name}"
14-
}
11+
# output "load_balancer_url" {
12+
# description = "Load balancer URL for API"
13+
# value = "http://${aws_lb.main.dns_name}"
14+
# }
15+
#
16+
# Note: Without ALB, you'll need to get the task's public IP manually:
17+
# aws ecs list-tasks --cluster <cluster_name> --service-name <api_service_name>
18+
# aws ecs describe-tasks --cluster <cluster_name> --tasks <task_arn>
1519

1620
output "ecs_cluster_name" {
1721
description = "ECS cluster name"

0 commit comments

Comments
 (0)