Skip to content

Commit 5eafa06

Browse files
committed
Implemented ruff linting and format check in CI + basic sanity test using pytest to test the CI workflow
1 parent 00a14cf commit 5eafa06

File tree

12 files changed

+95
-36
lines changed

12 files changed

+95
-36
lines changed

.github/workflows/ci.yml

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,20 @@ jobs:
3131
python -m pip install --upgrade pip
3232
pip install -r backend/requirements.txt
3333
34+
# Lint with Ruff
35+
- name: Lint with Ruff
36+
run: |
37+
ruff check backend/app backend/tests
38+
39+
# Ruff formating check
40+
- name: Ruff format check
41+
run: |
42+
ruff format --check backend/app backend/tests
43+
3444
# Run tests
35-
# - name: Run tests
36-
# run: |
37-
# pytest backend/tests/
45+
- name: Run tests
46+
run: |
47+
pytest backend/tests/
3848
3949
# Log in to Github Container Registry
4050
- name: Log in to GHCR

.gitignore

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,18 @@ htmlcov/
4343
*.log
4444
logs/
4545

46+
# Alembic
47+
alembic/__pycache__/
48+
alembic/versions/__pycache__/
49+
50+
# Monitoring
51+
monitoring/grafana/data/
52+
monitoring/grafana/provisioning/
53+
*.rrd
54+
4655
# Docker
4756
docker-compose.override.yml
57+
*.pid
4858

4959
# OS
5060
Thumbs.db

backend/alembic/env.py

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,9 @@
11
from logging.config import fileConfig
22

3-
from sqlalchemy import engine_from_config
4-
from sqlalchemy import pool
3+
from alembic import context
54
from app.core.config import DATABASE_URL
6-
from sqlalchemy import create_engine
7-
8-
95
from app.db import Base
10-
from app.models import event
11-
12-
from alembic import context
6+
from sqlalchemy import create_engine, pool
137

148
# this is the Alembic Config object, which provides
159
# access to the values within the .ini file in use.
@@ -77,6 +71,7 @@ def run_migrations_offline() -> None:
7771
# with context.begin_transaction():
7872
# context.run_migrations()
7973

74+
8075
def run_migrations_online() -> None:
8176
sync_url = DATABASE_URL.replace("+asyncpg", "")
8277

@@ -95,7 +90,6 @@ def run_migrations_online() -> None:
9590
context.run_migrations()
9691

9792

98-
9993
if context.is_offline_mode():
10094
run_migrations_offline()
10195
else:

backend/app/api/routes/events.py

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
1-
from fastapi import APIRouter, status
2-
from app.models.event import EventIn
3-
from app.db import database
4-
from app.core.logging import get_logger
51
import json
62

3+
from app.core.logging import get_logger
4+
from app.db import database
5+
from app.models.event import EventIn
6+
from fastapi import APIRouter, status
7+
78
router = APIRouter(prefix="/events", tags=["events"])
89
logger = get_logger("eventrelay.events")
910

11+
1012
@router.post("", status_code=status.HTTP_202_ACCEPTED)
1113
async def ingest_event(event: EventIn):
1214
query = """
@@ -16,9 +18,9 @@ async def ingest_event(event: EventIn):
1618
"""
1719
# Convert payload dict to JSON string for JSONB column
1820
values = event.dict()
19-
values['payload'] = json.dumps(values['payload'])
20-
21-
event_id = await database.execute(query = query, values=values)
21+
values["payload"] = json.dumps(values["payload"])
22+
23+
event_id = await database.execute(query=query, values=values)
2224

2325
logger.info(f"Stored event id={event_id} source={event.source} type={event.type}")
2426

@@ -27,12 +29,13 @@ async def ingest_event(event: EventIn):
2729
"id": event_id,
2830
}
2931

32+
3033
@router.get("")
3134
async def get_all_events():
3235
query = "SELECT * FROM events"
3336

34-
events = await database.fetch_all(query = query)
37+
events = await database.fetch_all(query=query)
3538

36-
logger.info(f"Retrieved all events")
39+
logger.info("Retrieved all events")
3740

38-
return events
41+
return events

backend/app/core/config.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
from dotenv import load_dotenv
21
import os
32
from pathlib import Path
43

backend/app/core/logging.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,22 @@
1-
import logging
21
import json
3-
from datetime import datetime, UTC
2+
import logging
3+
from datetime import UTC, datetime
4+
45

56
class JsonFormatter(logging.Formatter):
67
def format(self, record):
78
log = {
89
"level": record.levelname,
910
"message": record.getMessage(),
10-
"time": datetime.now(UTC).isoformat()
11+
"time": datetime.now(UTC).isoformat(),
1112
}
1213

1314
if hasattr(record, "extra"):
1415
log.update(record.extra)
1516

1617
return json.dumps(log)
17-
18+
19+
1820
def get_logger(name: str) -> logging.Logger:
1921
logger = logging.getLogger(name)
2022
logger.setLevel(logging.INFO)
@@ -25,4 +27,4 @@ def get_logger(name: str) -> logging.Logger:
2527
if not logger.handlers:
2628
logger.addHandler(handler)
2729

28-
return logger;
30+
return logger

backend/app/db.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
from sqlalchemy.orm import declarative_base
21
from databases import Database
2+
from sqlalchemy.orm import declarative_base
3+
34
from app.core.config import DATABASE_URL
45

56
database = Database(DATABASE_URL)
67

7-
Base = declarative_base()
8+
Base = declarative_base()

backend/app/main.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,23 @@
11
from fastapi import FastAPI
2+
23
from app.api.routes import events
34
from app.db import database
45

5-
66
app = FastAPI(title="EventRelay")
77

88
app.include_router(events.router)
99

10+
1011
@app.get("/health")
1112
def health():
1213
return {"status": "ok"}
1314

15+
1416
@app.on_event("startup")
1517
async def startup():
1618
await database.connect()
1719

20+
1821
@app.on_event("shutdown")
1922
async def shutdown():
20-
await database.disconnect()
23+
await database.disconnect()

backend/app/models/event.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
1-
from sqlalchemy import Column, Integer, String, JSON, DateTime, func
2-
from pydantic import BaseModel, Field
31
from typing import Dict
2+
43
from app.db import Base
4+
from pydantic import BaseModel, Field
5+
from sqlalchemy import JSON, Column, DateTime, Integer, String, func
6+
57

68
# pyndantic model (api inp)
79
class EventIn(BaseModel):
810
source: str = Field(..., example="auth-service")
911
type: str = Field(..., example="user.login")
1012
payload: Dict = Field(..., example={"user_id": 123})
1113

14+
1215
# sqlalchemy (db schema)
1316
class Event(Base):
1417
__tablename__ = "events"
@@ -17,4 +20,4 @@ class Event(Base):
1720
source = Column(String, nullable=False)
1821
type = Column(String, nullable=False)
1922
payload = Column(JSON, nullable=False)
20-
created_at = Column(DateTime(timezone=True), server_default=func.now())
23+
created_at = Column(DateTime(timezone=True), server_default=func.now())

backend/pyproject.toml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
[tool.ruff]
2+
# Where Ruff should look
3+
src = ["app"]
4+
5+
# Python version you target
6+
target-version = "py311"
7+
8+
# Keep output readable in CI
9+
line-length = 100
10+
11+
# Only enable high-signal rules
12+
lint.select = [
13+
"F", # Pyflakes (real bugs)
14+
"E", # Basic syntax errors
15+
"W", # Warnings
16+
"I", # Import sorting
17+
]
18+
19+
# Ignore noisy / non-critical rules
20+
lint.ignore = [
21+
"E501", # Line length (we'll format later)
22+
]
23+
24+
# Never lint generated or external code
25+
exclude = [
26+
"alembic/versions",
27+
"venv",
28+
]

0 commit comments

Comments
 (0)