Skip to content

Commit

Permalink
Merge pull request #15 from kiwix/dev
Browse files Browse the repository at this point in the history
Improve basic repo architecture
  • Loading branch information
elfkuzco authored Jun 7, 2024
2 parents d8c7b37 + fd75ae1 commit b58f624
Show file tree
Hide file tree
Showing 37 changed files with 405 additions and 39 deletions.
31 changes: 31 additions & 0 deletions .github/workflows/backend-Publish.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
name: Publish backend Docker image

on:
push:
paths:
- 'backend/**'
branches:
- main

jobs:

publish-backend:
runs-on: ubuntu-22.04
steps:
- name: Retrieve source code
uses: actions/checkout@v4

- name: Build and publish Docker Image
uses: openzim/docker-publish-action@v10
with:
image-name: openzim/mirrors-qa-backend
tag-pattern: /^v([0-9.]+)$/
latest-on-tag: true
restrict-to: openzim/mirrors-qa
context: backend
registries: ghcr.io
credentials:
GHCRIO_USERNAME=${{ secrets.GHCR_USERNAME }}
GHCRIO_TOKEN=${{ secrets.GHCR_TOKEN }}
repo_description: auto
repo_overview: auto
4 changes: 3 additions & 1 deletion .github/workflows/backend-QA.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@ name: Backend QA
on:
pull_request:
push:
paths:
- 'backend/**'
branches:
- main

jobs:

check-qa:
runs-on: ubuntu-22.04

steps:
- uses: actions/checkout@v3

Expand Down
4 changes: 3 additions & 1 deletion .github/workflows/worker-QA.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@ name: Worker QA
on:
pull_request:
push:
paths:
- 'worker/**'
branches:
- main

jobs:

check-qa:
runs-on: ubuntu-22.04

steps:
- uses: actions/checkout@v3

Expand Down
32 changes: 32 additions & 0 deletions .github/workflows/worker-manager-Publish.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
name: Publish worker-manager Docker image

on:
push:
paths:
- 'worker/**'
branches:
- main

jobs:

publish-worker-manager:
runs-on: ubuntu-22.04
steps:
- name: Retrieve source code
uses: actions/checkout@v4

- name: Build and publish Docker Image
uses: openzim/docker-publish-action@v10
with:
image-name: openzim/mirrors-qa-worker-manager
latest-on-tag: true
tag-pattern: /^v([0-9.]+)$/
restrict-to: openzim/mirrors-qa
context: worker
dockerfile: manager.Dockerfile
registries: ghcr.io
credentials:
GHCRIO_USERNAME=${{ secrets.GHCR_USERNAME }}
GHCRIO_TOKEN=${{ secrets.GHCR_TOKEN }}
repo_description: auto
repo_overview: auto
14 changes: 14 additions & 0 deletions backend/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
FROM python:3.11-slim
LABEL org.opencontainers.image.source=https://github.com/kiwix/mirrors-qa
# Copy code
COPY src /src/src
# Copy pyproject.toml and its dependencies
COPY pyproject.toml README.md /src/

# Install + cleanup
RUN pip install --no-cache-dir /src \
&& rm -rf /src

EXPOSE 80

CMD ["uvicorn", "mirrors_qa_backend.main:app", "--host", "0.0.0.0", "--port", "80"]
21 changes: 9 additions & 12 deletions backend/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "hatchling.build"

[project]
name = "mirrors_qa_backend"
requires-python = ">=3.12,<3.13"
requires-python = ">=3.11,<3.13"
description = "mirrors-qa Backend API"
readme = "README.md"
authors = [
Expand All @@ -21,7 +21,7 @@ dependencies = [
license = {text = "GPL-3.0-or-later"}
classifiers = [
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.11",
"License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)",
]

Expand Down Expand Up @@ -54,19 +54,16 @@ dev = [
"mirrors_qa_backend[check]",
]

[project.scripts]
mirrors-qa-backend = "backend:entrypoint"

[tool.hatch.version]
path = "src/backend/__about__.py"
path = "src/mirrors_qa_backend/__about__.py"

[tool.hatch.build]
exclude = [
"/.github",
]

[tool.hatch.build.targets.wheel]
packages = ["src/backend"]
packages = ["src/mirrors_qa_backend"]

[tool.hatch.envs.default]
features = ["dev"]
Expand Down Expand Up @@ -188,7 +185,7 @@ unfixable = [
]

[tool.ruff.lint.isort]
known-first-party = ["backend"]
known-first-party = ["mirrors_qa_backend"]

[tool.ruff.lint.flake8-bugbear]
# add exceptions to B008 for fastapi.
Expand All @@ -207,15 +204,15 @@ testpaths = ["tests"]
pythonpath = [".", "src"]

[tool.coverage.paths]
backend = ["src/backend"]
mirrors_qa_backend = ["src/mirrors_qa_backend"]
tests = ["tests"]

[tool.coverage.run]
source_pkgs = ["backend"]
source_pkgs = ["mirrors_qa_backend"]
branch = true
parallel = true
omit = [
"src/backend/__about__.py",
"src/mirrors_qa_backend/__about__.py",
]

[tool.coverage.report]
Expand All @@ -229,6 +226,6 @@ exclude_lines = [
include = ["src", "tests", "tasks.py"]
exclude = [".env/**", ".venv/**"]
extraPaths = ["src"]
pythonVersion = "3.12"
pythonVersion = "3.11"
typeCheckingMode="strict"
disableBytesTypePromotions = true
5 changes: 0 additions & 5 deletions backend/src/backend/__init__.py

This file was deleted.

File renamed without changes.
10 changes: 10 additions & 0 deletions backend/src/mirrors_qa_backend/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import logging
import os

logger = logging.getLogger("backend")

if not logger.hasHandlers():
logger.setLevel(logging.DEBUG if bool(os.getenv("DEBUG")) else logging.INFO)
handler = logging.StreamHandler()
handler.setFormatter(logging.Formatter("[%(asctime)s: %(levelname)s] %(message)s"))
logger.addHandler(handler)
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

[alembic]
# path to migration scripts
script_location = src/backend/migrations
script_location = migrations

# template used to generate migration file names; The default value is %%(rev)s_%%(slug)s
# Uncomment the line below if you want the files to be prepended with date and time
Expand Down Expand Up @@ -36,10 +36,10 @@ prepend_sys_path = .
# sourceless = false

# version location specification; This defaults
# to src/backend/migirations/versions. When using multiple version
# to migirations/versions. When using multiple version
# directories, initial revisions must be specified with --version-path.
# The path separator used here should be the separator specified by "version_path_separator" below.
# version_locations = %(here)s/bar:%(here)s/bat:src/backend/migirations/versions
# version_locations = %(here)s/bar:%(here)s/bat:migirations/versions

# version path separator; As mentioned above, this is the character used to split
# version_locations. The default within new alembic.ini files is "os", which uses os.pathsep.
Expand Down
46 changes: 46 additions & 0 deletions backend/src/mirrors_qa_backend/db/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import subprocess
from collections.abc import Generator
from pathlib import Path

from sqlalchemy import SelectBase, create_engine, func, select
from sqlalchemy.orm import Session as OrmSession
from sqlalchemy.orm import sessionmaker

from mirrors_qa_backend import logger
from mirrors_qa_backend.db.models import Mirror
from mirrors_qa_backend.settings import Settings

Session = sessionmaker(
bind=create_engine(url=Settings.database_url, echo=False),
expire_on_commit=False,
)


def gen_dbsession() -> Generator[OrmSession, None, None]:
"""FastAPI's Depends() compatible helper to provide a begin DB Session"""
with Session.begin() as session:
yield session


def upgrade_db_schema():
"""Checks if Alembic schema has been applied to the DB"""
src_dir = Path(__file__).parent.parent
logger.info(f"Upgrading database schema with config in {src_dir}")
subprocess.check_output(args=["alembic", "upgrade", "head"], cwd=src_dir)


def count_from_stmt(session: OrmSession, stmt: SelectBase) -> int:
"""Count all records returned by any statement `stmt` passed as parameter"""
return session.execute(
select(func.count()).select_from(stmt.subquery())
).scalar_one()


def initialize_mirrors() -> None:
with Session.begin() as session:
count = count_from_stmt(session, select(Mirror))
if count == 0:
logger.info("No mirrors exist in database.")
# TODO: update mirrors from https://download.kiwix.org/mirrors.html
else:
logger.info(f"Found {count} mirrors in database.")
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
)
from sqlalchemy.sql.schema import MetaData

from backend.enums import StatusEnum
from mirrors_qa_backend.enums import StatusEnum


class Base(MappedAsDataclass, DeclarativeBase):
Expand Down
File renamed without changes.
23 changes: 23 additions & 0 deletions backend/src/mirrors_qa_backend/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
from contextlib import asynccontextmanager

from fastapi import FastAPI

from mirrors_qa_backend import db
from mirrors_qa_backend.routes import auth, tests


@asynccontextmanager
async def lifespan(_: FastAPI):
db.upgrade_db_schema()
db.initialize_mirrors()
yield


def create_app(*, debug: bool = True):
app = FastAPI(debug=debug, docs_url="/", lifespan=lifespan)
app.include_router(router=tests.router)
app.include_router(router=auth.router)
return app


app = create_app()
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from alembic import context
from sqlalchemy import create_engine

from backend.db.models import Base
from mirrors_qa_backend.db.models import Base

# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.
Expand Down
8 changes: 8 additions & 0 deletions backend/src/mirrors_qa_backend/routes/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from typing import Annotated

from fastapi import Depends
from sqlalchemy.orm import Session

from mirrors_qa_backend.db import gen_dbsession

DbSession = Annotated[Session, Depends(gen_dbsession)]
9 changes: 9 additions & 0 deletions backend/src/mirrors_qa_backend/routes/auth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from fastapi import APIRouter, Response, status
from fastapi.responses import JSONResponse

router = APIRouter(prefix="/auth", tags=["auth"])


@router.post("/authenticate")
def authenticate_user() -> Response:
return JSONResponse(content={"token": "token"}, status_code=status.HTTP_200_OK)
57 changes: 57 additions & 0 deletions backend/src/mirrors_qa_backend/routes/tests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
from fastapi import APIRouter, Response, status
from fastapi.responses import JSONResponse

router = APIRouter(prefix="/tests", tags=["tests"])


@router.get(
"",
status_code=status.HTTP_200_OK,
responses={
status.HTTP_200_OK: {"description": "Returns the list of tests."},
},
)
def list_tests() -> Response:
return JSONResponse(
content={
"tests": [],
"metadata": {
"currentPage": None,
"pageSize": 10,
"firstPage": None,
"lastPage": None,
"nextPage": None,
"totalRecords": 20,
},
},
status_code=status.HTTP_200_OK,
)


@router.get(
"/{test_id}",
status_code=status.HTTP_200_OK,
responses={
status.HTTP_200_OK: {"description": "Returns the details of a test."},
status.HTTP_404_NOT_FOUND: {"description": "Test with id does not exist."},
},
)
def get_test(test_id: str) -> Response:
return JSONResponse(
content={"id": test_id},
status_code=status.HTTP_200_OK,
)


@router.patch(
"/{test_id}",
status_code=status.HTTP_200_OK,
responses={
status.HTTP_200_OK: {"description": "Update the details of a test."},
},
)
def update_test(test_id: str) -> Response:
return JSONResponse(
content={"id": test_id},
status_code=status.HTTP_200_OK,
)
Loading

0 comments on commit b58f624

Please sign in to comment.