Skip to content
Open
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
122 changes: 122 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
name: CI

on:
push:
pull_request:
workflow_dispatch:

jobs:
smoke:
runs-on: ubuntu-latest

services:
mongodb:
image: mongo:latest
options: >-
--health-cmd mongosh
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 27017:27017
env:
MONGO_INITDB_ROOT_USERNAME: admin
MONGO_INITDB_ROOT_PASSWORD: mongodb

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"

- name: Set up Node
uses: actions/setup-node@v4
with:
node-version: "20"

- name: Repo smoke checks
shell: bash
run: |
set -euo pipefail

echo "Validating repository structure and basic runnable signals"

has_signal=0

if find . -maxdepth 4 -type f -name "package.json" | grep -q .; then
has_signal=1
while IFS= read -r pkg; do
[ -z "$pkg" ] && continue
node -e "const fs=require('fs'); JSON.parse(fs.readFileSync(process.argv[1],'utf8'));" "$pkg"
done < <(find . -maxdepth 4 -type f -name "package.json")
fi

if find . -maxdepth 4 -type f \( -name "pyproject.toml" -o -name "requirements.txt" -o -name "setup.py" -o -name "manage.py" \) | grep -q .; then
has_signal=1
fi

if find . -maxdepth 4 -type f \( -name "app.py" -o -name "main.py" -o -name "wsgi.py" -o -name "asgi.py" \) | grep -q .; then
has_signal=1
fi

if find . -maxdepth 4 -type f \( -name "pom.xml" -o -name "build.gradle" -o -name "build.gradle.kts" -o -name "gradlew" \) | grep -q .; then
has_signal=1
fi

if find . -maxdepth 4 -type f -name "go.mod" | grep -q .; then
has_signal=1
fi

if find . -maxdepth 4 -type f -name "Cargo.toml" | grep -q .; then
has_signal=1
fi

if find . -maxdepth 4 -type f \( -name "*.csproj" -o -name "*.sln" \) | grep -q .; then
has_signal=1
fi

if find . -maxdepth 4 -type f \( -name "Dockerfile" -o -name "docker-compose.yml" -o -name "docker-compose.yaml" \) | grep -q .; then
has_signal=1
fi

if find . -maxdepth 4 -type f -name "Makefile" | grep -q .; then
has_signal=1
fi

if [ "$has_signal" -ne 1 ]; then
echo "No runnable/build signals found in repository"
exit 1
fi

echo "Running Python syntax smoke check"
python_files="$(find . -type f -name '*.py' -not -path './.git/*' 2>/dev/null || true)"
if [ -n "$python_files" ]; then
while IFS= read -r f; do
[ -z "$f" ] && continue
python -m py_compile "$f"
done <<< "$python_files"
fi

echo "Smoke checks passed"

- name: Run repository runtime smoke test
shell: bash
run: |
set -euo pipefail
if [ -f tests/test_runtime.py ]; then
python tests/test_runtime.py
else
echo "No runtime smoke test file found"
exit 1
fi

- name: Install integration test dependencies
run: pip install pytest pymongo

- name: Run integration tests
env:
MONGODB_URI: mongodb://admin:mongodb@localhost:27017/
run: pytest tests/test_integration.py -v
2 changes: 1 addition & 1 deletion backend/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ authors = [
classifiers = ["Private :: Do Not Upload"]
dependencies = [
"fastapi[all] ~= 0.112.2",
"motor[srv] ~= 3.5.1",
"pymongo[srv] ~= 4.13.1",
"beanie == 1.26.0",
]

Expand Down
6 changes: 3 additions & 3 deletions backend/scripts/odm_comparison.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import asyncio
import os
from motor.motor_asyncio import AsyncIOMotorClient
from pymongo import AsyncMongoClient

from pprint import pprint

Expand All @@ -24,7 +24,7 @@ class ToDoList(Document):
class Settings:
name = COLLECTION_NAME

client = AsyncIOMotorClient(MONGODB_URI)
client = AsyncMongoClient(MONGODB_URI)
await init_beanie(
database=client.get_default_database(), document_models=[ToDoList]
)
Expand Down Expand Up @@ -60,7 +60,7 @@ def from_doc(doc) -> "ToDoList":
items=[Item.from_doc(item) for item in doc["items"]],
)

client = AsyncIOMotorClient(MONGODB_URI)
client = AsyncMongoClient(MONGODB_URI)
collection = client.get_default_database().get_collection(COLLECTION_NAME)
pprint(
ToDoList.from_doc(doc)
Expand Down
6 changes: 3 additions & 3 deletions backend/src/todo/dal_beanie.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from bson import ObjectId
from motor.motor_asyncio import AsyncIOMotorDatabase
from pymongo.asynchronous.database import AsyncDatabase
from pymongo import ReturnDocument
from beanie import Document, init_beanie

Expand Down Expand Up @@ -38,12 +38,12 @@ class Settings:
items: list[ToDoListItem]


async def get_instance(database: AsyncIOMotorDatabase) -> "ToDoDALBeanie":
async def get_instance(database: AsyncDatabase) -> "ToDoDALBeanie":
return await ToDoDALBeanie(database)


class ToDoDALBeanie:
def __init__(self, database: AsyncIOMotorDatabase):
def __init__(self, database: AsyncDatabase):
self._database = database

def __await__(self):
Expand Down
6 changes: 3 additions & 3 deletions backend/src/todo/dal_motor.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from bson import ObjectId
from motor.motor_asyncio import AsyncIOMotorDatabase
from pymongo.asynchronous.database import AsyncDatabase
from pymongo import ReturnDocument

from pydantic import BaseModel, Field
Expand Down Expand Up @@ -49,12 +49,12 @@ def from_doc(doc) -> "ToDoList":
)


async def get_instance(database: AsyncIOMotorDatabase):
async def get_instance(database: AsyncDatabase):
return ToDoDALMotor(database)


class ToDoDALMotor:
def __init__(self, database: AsyncIOMotorDatabase):
def __init__(self, database: AsyncDatabase):
self._todo_collection = database.get_collection("todo_lists")

async def list_todo_lists(self, session=None):
Expand Down
4 changes: 2 additions & 2 deletions backend/src/todo/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import os

from fastapi import FastAPI, status
from motor.motor_asyncio import AsyncIOMotorClient
from pymongo import AsyncMongoClient
from pydantic import BaseModel

from .dal_beanie import get_instance, ListSummary, ToDoList
Expand All @@ -17,7 +17,7 @@
@asynccontextmanager
async def lifespan(app: FastAPI):
# Startup:
client = AsyncIOMotorClient(MONGODB_URI, appName="sample-app-python-farm-tutorial")
client = AsyncMongoClient(MONGODB_URI, appName="sample-app-python-farm-tutorial")
database = client.get_default_database()

# Ensure the database is available:
Expand Down
6 changes: 3 additions & 3 deletions backend/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@

import pytest
import pytest_asyncio
from motor.motor_asyncio import AsyncIOMotorClient
from pymongo import AsyncMongoClient


@pytest_asyncio.fixture(scope="session")
async def motor_client():
client = AsyncIOMotorClient(os.environ["MONGODB_URI"])
client = AsyncMongoClient(os.environ["MONGODB_URI"])
pong = await client.local.command("ping")
assert int(pong["ok"]) == 1
yield client
Expand All @@ -25,7 +25,7 @@ def app_db(motor_client):


@pytest_asyncio.fixture(scope="session")
async def rollback_session(motor_client: AsyncIOMotorClient):
async def rollback_session(motor_client: AsyncMongoClient):
"""
This fixture provides a session that will be aborted at the end of the test, to clean up any written data.
"""
Expand Down
Empty file added tests/__init__.py
Empty file.
Loading
Loading