Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/api_ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ jobs:
- name: Install API test dependencies
run: |
python -m pip install --upgrade pip
pip install fastapi sqlalchemy psycopg2-binary pytest httpx
pip install -r services/api/requirements-dev.txt

- name: Run API pytest suite
working-directory: services/api
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,4 @@ pnpm-debug.log*
# python
__pycache__/
*.pyc
.pytest_cache/
25 changes: 24 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,30 @@ Open the "Run and Debug" menu (Ctrl + Shift + D), select the "Development Server
*Note: You may need to perform `cd ./services/frontend/` and `npm install` before running with VSCode*

## API
WIP
The API can be launched either via Docker or locally.

### Docker
```
docker-compose up api
```

### Local
```
cd ./services/api/
python -m venv .venv
```
Activate the venv, then install dependencies:
```
pip install -r requirements-dev.txt
```
Run the API:
```
uvicorn main:app --host 0.0.0.0 --port 8000
```

### Requirements
- `services/api/requirements.txt` contains runtime dependencies for the API service.
- `services/api/requirements-dev.txt` contains developer/test dependencies and includes `requirements.txt`.

## API Testing
Place all test scripts into the tests folder
Expand Down
23 changes: 19 additions & 4 deletions docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ services:
volumes:
- pgdata:/var/lib/postgresql
- ./services/postgres/init:/docker-entrypoint-initdb.d
networks:
- backend_net

# Test database
postgres-test:
Expand All @@ -26,6 +28,8 @@ services:
volumes:
- pgdata_test:/var/lib/postgresql
- ./services/postgres/init:/docker-entrypoint-initdb.d
networks:
- backend_net

# TODO: MinIO
# minio:
Expand All @@ -34,7 +38,19 @@ services:
# rabbitmq:

# TODO: FastAPI
# api:
api:
build:
context: ./services/api
dockerfile: Dockerfile
container_name: mlsec-api
environment:
DATABASE_URL: postgresql://postgres:password123@postgres:5432/mlsec
ports:
- "8000:8000"
networks:
- backend_net
depends_on:
- postgres

# TODO: Celery Worker
# worker:
Expand All @@ -54,9 +70,8 @@ services:
- "4321:4321"
networks:
- backend_net
# TODO: Make depend on API once ready
# depends_on:
# - api
depends_on:
- api

# TODO: Adminer (Nice to have, not necessary)
# adminer:
Expand Down
20 changes: 20 additions & 0 deletions services/api/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
FROM python:3.11-slim

ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1

WORKDIR /app

RUN python -m pip install --upgrade pip

COPY requirements.txt /app/requirements.txt
RUN pip install --no-cache-dir -r /app/requirements.txt

COPY . /app

RUN useradd --create-home --uid 10001 appuser
USER appuser

EXPOSE 8000

CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
22 changes: 0 additions & 22 deletions services/api/app/core/database.py

This file was deleted.

7 changes: 0 additions & 7 deletions services/api/app/main.py

This file was deleted.

43 changes: 43 additions & 0 deletions services/api/core/database.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import os
from functools import lru_cache

from sqlalchemy import create_engine, text # type: ignore
from sqlalchemy.engine import Engine
from sqlalchemy.orm import declarative_base, sessionmaker

from core.settings import get_settings

# TODO: Change this to .env for resolving password, port, etc.
DEFAULT_DATABASE_URL = "postgresql://postgres:password123@postgres:5432/mlsec"


def get_database_url() -> str:
settings = get_settings()
return settings.database_url or os.getenv("DATABASE_URL", DEFAULT_DATABASE_URL)


@lru_cache(maxsize=1)
def get_engine() -> Engine:
return create_engine(get_database_url(), pool_pre_ping=True)


SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=get_engine())

Base = declarative_base()


def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()


def ping_db() -> bool:
try:
with get_engine().connect() as conn:
conn.execute(text("SELECT 1"))
return True
except Exception:
return False
19 changes: 19 additions & 0 deletions services/api/core/settings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from __future__ import annotations

from functools import lru_cache

from pydantic_settings import BaseSettings, SettingsConfigDict


class Settings(BaseSettings):
model_config = SettingsConfigDict(env_prefix="", extra="ignore")

env: str = "dev"
log_level: str = "INFO"

database_url: str | None = None


@lru_cache(maxsize=1)
def get_settings() -> Settings:
return Settings()
22 changes: 22 additions & 0 deletions services/api/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import logging

from fastapi import FastAPI

from core.settings import get_settings
from routers.health import router as health_router


def create_app() -> FastAPI:
settings = get_settings()
logging.basicConfig(level=getattr(logging, settings.log_level.upper(), logging.INFO))

app = FastAPI(
title="MLSEC Platform API",
version="0.1.0",
)

app.include_router(health_router)
return app


app = create_app()
4 changes: 4 additions & 0 deletions services/api/requirements-dev.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
-r requirements.txt

pytest>=8,<10
httpx>=0.27,<1
5 changes: 5 additions & 0 deletions services/api/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
fastapi>=0.110,<1
uvicorn[standard]>=0.27,<1
pydantic-settings>=2.2,<3
SQLAlchemy>=2.0,<3
psycopg2-binary>=2.9,<3
8 changes: 8 additions & 0 deletions services/api/routers/health.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from fastapi import APIRouter # type: ignore

router = APIRouter(tags=["health"])


@router.get("/health")
def health() -> dict[str, str]:
return {"status": "ok"}
4 changes: 2 additions & 2 deletions services/api/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
from sqlalchemy.orm import sessionmaker
from fastapi.testclient import TestClient

from app.main import app
from app.core.database import Base, get_db
from main import app
from core.database import Base, get_db


TEST_DB_URL = "postgresql://postgres:password123@localhost:5433/mlsec_test"
Expand Down