Merge pull request 'dev [release-patch]' (#6) from dev into main

Reviewed-on: #6
This commit was merged in pull request #6.
This commit is contained in:
2025-11-25 09:05:53 +00:00
6 changed files with 110 additions and 11 deletions

View File

@@ -13,3 +13,4 @@ dist/
.gitignore
.env
*.sock
.venv

View File

@@ -0,0 +1,77 @@
name: PR tests
on:
pull_request:
types: [opened, synchronize, edited, reopened]
jobs:
build-and-smoke:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Install uv
uses: astral-sh/setup-uv@v5
- name: Set up Python
run: uv python install
with:
python-version-file: "pyproject.toml"
- name: Install the project dependencies
run: |
uv sync --all-groups
uv add pip
uv export --format requirements.txt -o requirements.txt
# uv run python -m pip install --upgrade pip
# uv run python -m pip install -r requirements.txt
- name: Build image
run: |
docker build -t semapform-api:test-pr .
- name: Start container (background)
run: |
# do NOT bind the container port to the host to avoid port conflicts on the runner
docker run -d --name semapform-test semapform-api:test-pr sleep infinity
- name: Start server in container and smoke test HTTP (in-container)
run: |
set -x
# start the server inside the container (detached)
docker exec -d semapform-test python api_service.py || true
# show container status to aid debugging
docker ps -a --filter name=semapform-test || true
# perform a readiness loop (try container-local /health) using small execs
echo "waiting for service to become ready inside container"
set -e
READY=0
for i in $(seq 1 20); do
echo "ready attempt $i"
if docker exec semapform-test python -c 'import urllib.request,sys; urllib.request.urlopen("http://127.0.0.1:8001/health", timeout=1); print("ok")' ; then
READY=1
break
fi
sleep 1
done
if [ "$READY" -ne 1 ]; then
echo "service did not become ready"
docker logs semapform-test --tail 200 || true
exit 1
fi
# Run the repository smoke-test script inside the container and surface its output
echo "running test_api.py inside container"
docker exec semapform-test python test_api.py || true
# dump the last 200 lines of logs so this step always displays useful output
docker logs semapform-test --tail 200 || true
- name: Cleanup container
if: always()
run: |
docker rm -f semapform-test || true

View File

@@ -6,7 +6,7 @@ RUN apt upgrade -y
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
RUN pip install --no-cache-dir -r requirements.txt --extra-index-url https://git.theprivateserver.de/api/packages/PHB/pypi/simple/
COPY . .

View File

@@ -1,12 +1,11 @@
"""
Lightweight Python API service for signature validation
"""Lightweight Python API service for signature validation
This can run independently to support the PHP application
"""
import os
import re
from bibapi import catalogue
# Avoid importing heavy modules at top-level to keep `import api_service` lightweight
from fastapi import FastAPI, Query
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
@@ -22,15 +21,25 @@ app.add_middleware(
allow_headers=["*"],
)
# Initialize catalogue for signature validation
cat = catalogue.Catalogue()
# Catalogue is expensive to initialize at import time; instantiate lazily
cat = None
def _get_catalogue():
global cat
if cat is None:
# import inside function to avoid expensive work during module import
from bibapi import catalogue as _catalogue
cat = _catalogue.Catalogue()
return cat
@app.get("/api/validate-signature")
async def validate_signature(signature: str = Query(...)):
"""Validate a book signature and return total pages"""
try:
book_result = cat.get_book_with_data(signature)
book_result = _get_catalogue().get_book_with_data(signature)
if book_result and hasattr(book_result, "pages") and book_result.pages:
# Try to extract numeric page count
pages_str = str(book_result.pages)
@@ -39,7 +48,7 @@ async def validate_signature(signature: str = Query(...)):
if match:
total_pages = int(match.group(1))
return JSONResponse(
{"valid": True, "total_pages": total_pages, "signature": signature}
{"valid": True, "total_pages": total_pages, "signature": signature},
)
return JSONResponse(
@@ -47,13 +56,13 @@ async def validate_signature(signature: str = Query(...)):
"valid": False,
"error": "Signatur nicht gefunden oder keine Seitenzahl verfügbar",
"signature": signature,
}
},
)
except Exception as e:
return JSONResponse(
{
"valid": False,
"error": f"Fehler bei der Validierung: {str(e)}",
"error": f"Fehler bei der Validierung: {e!s}",
"signature": signature,
},
status_code=500,

View File

@@ -4,7 +4,11 @@ version = "0.1.1"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.13"
dependencies = ["bibapi>=0.0.5", "fastapi>=0.122.0"]
dependencies = [
"bibapi>=0.0.5",
"fastapi>=0.122.0",
"uvicorn>=0.38.0",
]
[[tool.uv.index]]
name = "gitea"
url = "https://git.theprivateserver.de/api/packages/PHB/pypi/simple/"

8
test_api.py Normal file
View File

@@ -0,0 +1,8 @@
# test api endpoint with a signature, print the response
import urllib.request
response = urllib.request.urlopen(
"http://localhost:8001/api/validate-signature?signature=ST%20250%20U42%20%2815%29",
)
print(response.read().decode("utf-8"))