Skip to content

Commit 52bbc6a

Browse files
committed
setup project
1 parent db38212 commit 52bbc6a

14 files changed

Lines changed: 254 additions & 0 deletions

File tree

app/api/execute.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import redis.asyncio as redis
2+
from fastapi import APIRouter
3+
from app.core.config import settings
4+
from app.schemas.execution import ExecutionRequest
5+
6+
router = APIRouter(prefix="/execute")
7+
r = redis.from_url(settings.REDIS_URL)
8+
9+
10+
@router.post("")
11+
async def enqueue_exec(req: ExecutionRequest):
12+
await r.lpush("exec_queue", req.model_dump_json())
13+
return {"status": "queued"}

app/api/snippets.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
from fastapi import APIRouter, Depends
2+
from sqlalchemy.ext.asyncio import AsyncSession
3+
from sqlalchemy import select
4+
from app.db.session import SessionLocal
5+
from app.db.models import Snippet, SnippetVersion
6+
from app.schemas.snippet import SnippetCreate, SnippetOut
7+
8+
router = APIRouter(prefix="/snippets")
9+
10+
11+
async def get_db():
12+
async with SessionLocal() as s:
13+
yield s
14+
15+
16+
@router.post("", response_model=SnippetOut)
17+
async def create_snippet(data: SnippetCreate, db: AsyncSession = Depends(get_db)):
18+
snippet = Snippet(title=data.title)
19+
db.add(snippet)
20+
await db.flush()
21+
22+
version = SnippetVersion(snippet_id=snippet.id, code=data.code, version=1)
23+
db.add(version)
24+
await db.commit()
25+
return snippet
26+
27+
28+
@router.get("/{snippet_id}", response_model=SnippetOut)
29+
async def get_snippet(snippet_id: int, db: AsyncSession = Depends(get_db)):
30+
q = await db.execute(select(Snippet).where(Snippet.id == snippet_id))
31+
return q.scalar_one()

app/core/config.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
from pydantic import BaseModel
2+
3+
4+
class Settings(BaseModel):
5+
DB_URL: str = "mysql+aiomysql://app:app@mariadb:3306/code"
6+
REDIS_URL: str = "redis://redis:6379/0"
7+
SANDBOX_CONTAINER: str = "code-sandbox"
8+
EXEC_TIMEOUT: int = 5
9+
MAX_OUTPUT: int = 20000
10+
11+
12+
settings = Settings()

app/db/models.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
2+
from sqlalchemy import Text, String, Integer, DateTime, ForeignKey
3+
from datetime import datetime
4+
5+
6+
class Base(DeclarativeBase):
7+
pass
8+
9+
10+
class Snippet(Base):
11+
__tablename__ = "snippets"
12+
13+
id: Mapped[int] = mapped_column(primary_key=True)
14+
title: Mapped[str] = mapped_column(String(200))
15+
created_at: Mapped[datetime] = mapped_column(default=datetime.utcnow)
16+
17+
18+
class SnippetVersion(Base):
19+
__tablename__ = "snippet_versions"
20+
21+
id: Mapped[int] = mapped_column(primary_key=True)
22+
snippet_id: Mapped[int] = mapped_column(ForeignKey("snippets.id"))
23+
code: Mapped[str] = mapped_column(Text)
24+
version: Mapped[int]
25+
26+
27+
class Execution(Base):
28+
__tablename__ = "executions"
29+
30+
id: Mapped[int] = mapped_column(primary_key=True)
31+
snippet_version_id: Mapped[int] = mapped_column(ForeignKey("snippet_versions.id"))
32+
output: Mapped[str] = mapped_column(Text)
33+
status: Mapped[str] = mapped_column(String(50))
34+
created_at: Mapped[datetime] = mapped_column(default=datetime.utcnow)

app/db/session.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker
2+
from app.core.config import settings
3+
4+
engine = create_async_engine(settings.DB_URL, echo=False)
5+
SessionLocal = async_sessionmaker(engine, expire_on_commit=False)

app/docker-compose.yml

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
services:
2+
mariadb:
3+
image: mariadb:11
4+
environment:
5+
MYSQL_ROOT_PASSWORD: root
6+
MYSQL_DATABASE: code
7+
MYSQL_USER: app
8+
MYSQL_PASSWORD: app
9+
ports:
10+
- "3306:3306"
11+
12+
redis:
13+
image: redis:7
14+
ports:
15+
- "6379:6379"
16+
17+
sandbox:
18+
build: ./sandbox
19+
container_name: code-sandbox
20+
networks: [default]
21+
22+
api:
23+
build: .
24+
command: uvicorn app.main:app --host 0.0.0.0 --port 8000
25+
volumes: [".:/app"]
26+
ports:
27+
- "8000:8000"
28+
depends_on: [mariadb, redis, sandbox]
29+
30+
worker:
31+
build: .
32+
command: python -m app.worker.worker
33+
volumes: [".:/app"]
34+
depends_on: [redis, mariadb, sandbox]

app/main.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
from fastapi import FastAPI
2+
from app.api import snippets, execute
3+
4+
app = FastAPI(title="Code Snippet Execution Platform")
5+
6+
app.include_router(snippets.router)
7+
app.include_router(execute.router)

app/schemas/execution.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
from pydantic import BaseModel
2+
3+
4+
class ExecutionRequest(BaseModel):
5+
snippet_version_id: int
6+
7+
8+
class ExecutionOut(BaseModel):
9+
id: int
10+
status: str
11+
output: str
12+
13+
class Config:
14+
from_attributes = True

app/schemas/snippet.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
from pydantic import BaseModel
2+
3+
4+
class SnippetCreate(BaseModel):
5+
title: str
6+
code: str
7+
8+
9+
class SnippetOut(BaseModel):
10+
id: int
11+
title: str
12+
13+
class Config:
14+
from_attributes = True

app/services/executor.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import docker
2+
from app.core.config import settings
3+
4+
client = docker.from_env()
5+
6+
7+
def run_code(code: str) -> str:
8+
container = client.containers.get(settings.SANDBOX_CONTAINER)
9+
10+
exec_id = client.api.exec_create(
11+
container.id,
12+
cmd=["python", "/runner.py"],
13+
stdin=True,
14+
tty=False,
15+
)
16+
17+
output = client.api.exec_start(exec_id, stdin=True, socket=False, demux=False, stream=False, detach=False, tty=False, input=code.encode())
18+
return output.decode(errors="ignore")[: settings.MAX_OUTPUT]

0 commit comments

Comments
 (0)