23 Commits

Author SHA1 Message Date
Gitea CI
36a548eee6 Bump version: 0.1.1 → 0.1.2 2025-11-25 09:06:45 +00:00
5a1c19ccbe Merge pull request 'dev [release-patch]' (#6) from dev into main
Reviewed-on: #6
2025-11-25 09:05:53 +00:00
36f5dd14b5 check
All checks were successful
PR tests / build-and-smoke (pull_request) Successful in 1m4s
/ build (pull_request) Successful in 16m56s
2025-11-25 10:03:13 +01:00
811313d1ef fix: add uvicorn dependency
All checks were successful
PR tests / build-and-smoke (pull_request) Successful in 1m43s
feat: add test_api file
2025-11-25 09:59:24 +01:00
c44dc5a61c test new validation test
All checks were successful
PR tests / build-and-smoke (pull_request) Successful in 54s
2025-11-25 09:52:51 +01:00
c9f139d29c re-add port declaration
All checks were successful
PR tests / build-and-smoke (pull_request) Successful in 47s
2025-11-25 09:49:07 +01:00
6ef463458f use python instead of uv
All checks were successful
PR tests / build-and-smoke (pull_request) Successful in 1m19s
2025-11-25 09:46:46 +01:00
7a74ead335 chore: new service check using signature
Some checks failed
PR tests / build-and-smoke (pull_request) Failing after 1m19s
2025-11-25 09:41:11 +01:00
6f38dd482e chore: only import catalogue if needed
Some checks failed
PR tests / build-and-smoke (pull_request) Failing after 1m20s
2025-11-25 09:23:29 +01:00
e1da6085ed test new workflow
Some checks failed
PR tests / build-and-smoke (pull_request) Failing after 22s
2025-11-25 09:20:15 +01:00
af1ee0ce71 test: lazy initiation of catalogue
Some checks failed
PR tests / build-and-smoke (pull_request) Failing after 2m5s
2025-11-25 09:15:54 +01:00
0e7c45182a chore: add uv, generate requirements.txt for tests
Some checks failed
PR tests / build-and-smoke (pull_request) Failing after 54s
2025-11-25 09:12:51 +01:00
c21afa0776 chore: add custom pypi url
Some checks failed
PR tests / build-and-smoke (pull_request) Failing after 32s
2025-11-25 09:10:03 +01:00
df527b30da feat: add pr test workflow 2025-11-25 09:09:44 +01:00
0cf88113be maintenance: add .venv to dockerignore 2025-11-25 09:03:29 +01:00
59c90ca471 Merge pull request 'chore: replace apk with apt [release-patch]' (#5) from dev into main
Reviewed-on: #5
2025-11-25 08:00:20 +00:00
0d9ffb8539 chore: replace apk with apt
Some checks failed
/ build (pull_request) Failing after 12m43s
2025-11-25 08:59:46 +01:00
1e7d45ec49 Merge pull request 'test: inspired by other Dockerfile [release-patch]' (#4) from dev into main
Reviewed-on: #4
2025-11-25 07:51:22 +00:00
2ee575aff4 test: inspired by other Dockerfile
Some checks failed
/ build (pull_request) Failing after 11m19s
2025-11-25 08:50:07 +01:00
69e463c9a4 Merge pull request 'test: add path, switch to uv [release-patch]' (#3) from dev into main
Reviewed-on: #3
2025-11-25 07:39:30 +00:00
b1cdeffb4c Update pyproject.toml 2025-11-25 07:39:24 +00:00
e2cd6d7ba3 Update pyproject.toml 2025-11-25 07:38:30 +00:00
6837d0b4b3 test: add path, switch to uv
Some checks failed
/ build (pull_request) Failing after 11m20s
2025-11-25 08:35:46 +01:00
6 changed files with 115 additions and 49 deletions

View File

@@ -13,3 +13,4 @@ dist/
.gitignore .gitignore
.env .env
*.sock *.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

@@ -1,43 +1,13 @@
FROM python:3.13-slim AS builder FROM python:3.13.9-slim
# Prevent Python from writing .pyc files and enable unbuffered stdout/stderr RUN apt update
ENV PYTHONDONTWRITEBYTECODE=1 \ RUN apt upgrade -y
PYTHONUNBUFFERED=1 \
PIP_DISABLE_PIP_VERSION_CHECK=1 \
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1
WORKDIR /app WORKDIR /app
# Install build deps required to build wheels COPY requirements.txt .
RUN apt-get update \ RUN pip install --no-cache-dir -r requirements.txt --extra-index-url https://git.theprivateserver.de/api/packages/PHB/pypi/simple/
&& apt-get install -y --no-install-recommends build-essential gcc \
&& rm -rf /var/lib/apt/lists/*
# Upgrade packaging tools and install runtime dependencies into a separate prefix COPY . .
COPY requirements.txt ./
RUN pip install --upgrade pip setuptools wheel \
&& pip install --prefix=/install --no-cache-dir \
--extra-index-url https://git.theprivateserver.de/api/packages/PHB/pypi/simple/ \
-r requirements.txt \
&& rm -rf /root/.cache/pip
# Copy application source (only used to include app files in final image) ENTRYPOINT [ "python", "api_service.py" ]
COPY . /app
FROM python:3.13-slim
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
API_PORT=8001
WORKDIR /app
# Copy installed packages from the builder image into the final image
COPY --from=builder /install /usr/local
# Copy application sources
COPY --from=builder /app /app
EXPOSE 8001
# Run using uvicorn; the app is defined in `api_service.py` as `app`
CMD ["uvicorn", "api_service:app", "--host", "0.0.0.0", "--port", "8001"]

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 This can run independently to support the PHP application
""" """
import os import os
import re 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 import FastAPI, Query
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse from fastapi.responses import JSONResponse
@@ -22,15 +21,25 @@ app.add_middleware(
allow_headers=["*"], allow_headers=["*"],
) )
# Initialize catalogue for signature validation # Catalogue is expensive to initialize at import time; instantiate lazily
cat = catalogue.Catalogue() 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") @app.get("/api/validate-signature")
async def validate_signature(signature: str = Query(...)): async def validate_signature(signature: str = Query(...)):
"""Validate a book signature and return total pages""" """Validate a book signature and return total pages"""
try: 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: if book_result and hasattr(book_result, "pages") and book_result.pages:
# Try to extract numeric page count # Try to extract numeric page count
pages_str = str(book_result.pages) pages_str = str(book_result.pages)
@@ -39,7 +48,7 @@ async def validate_signature(signature: str = Query(...)):
if match: if match:
total_pages = int(match.group(1)) total_pages = int(match.group(1))
return JSONResponse( return JSONResponse(
{"valid": True, "total_pages": total_pages, "signature": signature} {"valid": True, "total_pages": total_pages, "signature": signature},
) )
return JSONResponse( return JSONResponse(
@@ -47,13 +56,13 @@ async def validate_signature(signature: str = Query(...)):
"valid": False, "valid": False,
"error": "Signatur nicht gefunden oder keine Seitenzahl verfügbar", "error": "Signatur nicht gefunden oder keine Seitenzahl verfügbar",
"signature": signature, "signature": signature,
} },
) )
except Exception as e: except Exception as e:
return JSONResponse( return JSONResponse(
{ {
"valid": False, "valid": False,
"error": f"Fehler bei der Validierung: {str(e)}", "error": f"Fehler bei der Validierung: {e!s}",
"signature": signature, "signature": signature,
}, },
status_code=500, status_code=500,

View File

@@ -1,20 +1,21 @@
[project] [project]
name = "semapform-api" name = "semapform-api"
version = "0.1.1" version = "0.1.2"
description = "Add your description here" description = "Add your description here"
readme = "README.md" readme = "README.md"
requires-python = ">=3.13" requires-python = ">=3.13"
dependencies = [ dependencies = [
"bibapi[catalogue]>=0.0.5", "bibapi>=0.0.5",
"fastapi>=0.122.0", "fastapi>=0.122.0",
"pip>=25.3", "pip>=25.3",
"uvicorn>=0.38.0",
] ]
[[tool.uv.index]] [[tool.uv.index]]
name = "gitea" name = "gitea"
url = "https://git.theprivateserver.de/api/packages/PHB/pypi/simple/" url = "https://git.theprivateserver.de/api/packages/PHB/pypi/simple/"
[tool.bumpversion] [tool.bumpversion]
current_version = "0.1.1" current_version = "0.1.2"
parse = "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)" parse = "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)"
serialize = ["{major}.{minor}.{patch}"] serialize = ["{major}.{minor}.{patch}"]
search = "{current_version}" search = "{current_version}"

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"))