feat: Edit Distance Wrapper Service (Text and Graphs) #3
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: edit-distance-service | |
| on: | |
| push: | |
| branches: ["master"] | |
| paths: | |
| - "services/edit-distance-service/**" | |
| pull_request: | |
| branches: ["master"] | |
| paths: | |
| - "services/edit-distance-service/**" | |
| defaults: | |
| run: | |
| working-directory: services/edit-distance-service | |
| jobs: | |
| lint: | |
| name: Lint | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Setup Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.12" | |
| - name: Cache dependencies | |
| uses: actions/cache@v4 | |
| with: | |
| path: ~/.cache/pip | |
| key: ${{ runner.os }}-pip-${{ hashFiles('services/edit-distance-service/pyproject.toml') }} | |
| restore-keys: | | |
| ${{ runner.os }}-pip- | |
| - name: Install dependencies | |
| run: | | |
| pip install --upgrade pip | |
| pip install -e ".[dev]" | |
| - name: Ruff linting | |
| run: make lint | |
| test: | |
| name: Test | |
| runs-on: ubuntu-latest | |
| needs: lint | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Setup Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.12" | |
| - name: Cache dependencies | |
| uses: actions/cache@v4 | |
| with: | |
| path: ~/.cache/pip | |
| key: ${{ runner.os }}-pip-${{ hashFiles('services/edit-distance-service/pyproject.toml') }} | |
| restore-keys: | | |
| ${{ runner.os }}-pip- | |
| - name: Install dependencies | |
| run: | | |
| pip install --upgrade pip | |
| pip install -e ".[dev]" | |
| - name: Run tests | |
| run: pytest -v --tb=short | |
| - name: Run smoke tests | |
| run: | | |
| set -e | |
| # Start server in background | |
| uvicorn src.main:app --host 0.0.0.0 --port 8000 & | |
| SERVER_PID=$! | |
| # Wait for server to be ready | |
| for i in $(seq 1 30); do | |
| if curl -s http://localhost:8000/health | grep -q '"status":"ok"'; then | |
| echo "Server is ready" | |
| break | |
| fi | |
| echo "Waiting for server... ($i/30)" | |
| sleep 1 | |
| done | |
| # Smoke test: health endpoint | |
| echo "=== Health check ===" | |
| curl -sf http://localhost:8000/health | python -c "import sys,json; d=json.load(sys.stdin); assert d['status']=='ok', f'Health failed: {d}'" | |
| echo "Health: OK" | |
| # Smoke test: text algorithms discovery | |
| echo "=== Text algorithms ===" | |
| curl -sf http://localhost:8000/v1/text/algorithms | python -c "import sys,json; d=json.load(sys.stdin); assert len(d)>0, 'No algorithms'; print(f'Found {len(d)} text algorithms')" | |
| # Smoke test: GED algorithms discovery | |
| echo "=== GED algorithms ===" | |
| curl -sf http://localhost:8000/v1/graphs/algorithms | python -c "import sys,json; d=json.load(sys.stdin); assert len(d)>0, 'No GED algorithms'; print(f'Found {len(d)} GED algorithms')" | |
| # Smoke test: Levenshtein compute | |
| echo "=== Levenshtein compute ===" | |
| curl -sf -X POST http://localhost:8000/v1/text/distance \ | |
| -H 'Content-Type: application/json' \ | |
| -d '{"algorithm":"levenshtein","params":{},"inputs":[{"id":"p1","a":"kitten","b":"sitting"}]}' \ | |
| | python -c "import sys,json; d=json.load(sys.stdin); assert d['algorithm']=='levenshtein'; assert len(d['results'])==1; print('Levenshtein: OK')" | |
| # Smoke test: GED compute | |
| echo "=== GED compute ===" | |
| curl -sf -X POST http://localhost:8000/v1/graphs/distance \ | |
| -H 'Content-Type: application/json' \ | |
| -d '{"algorithm":"ged_astar","params":{"mode":"exact","timeout_ms":5000},"graphs":[{"id":"p1","g1":{"nodes":[{"id":"A"},{"id":"B"}],"edges":[{"source":"A","target":"B"}]},"g2":{"nodes":[{"id":"A"},{"id":"B"},{"id":"C"}],"edges":[{"source":"A","target":"B"},{"source":"B","target":"C"}]}}]}' \ | |
| | python -c "import sys,json; d=json.load(sys.stdin); assert d['status']=='completed'; assert len(d['results'])==1; print('GED compute: OK')" | |
| echo "=== All smoke tests passed ===" | |
| # Stop the server | |
| kill $SERVER_PID 2>/dev/null || true | |
| wait $SERVER_PID 2>/dev/null || true | |
| generate-openapi: | |
| name: Generate OpenAPI Spec | |
| runs-on: ubuntu-latest | |
| needs: test | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Setup Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.12" | |
| - name: Cache dependencies | |
| uses: actions/cache@v4 | |
| with: | |
| path: ~/.cache/pip | |
| key: ${{ runner.os }}-pip-${{ hashFiles('services/edit-distance-service/pyproject.toml') }} | |
| restore-keys: | | |
| ${{ runner.os }}-pip- | |
| - name: Install dependencies | |
| run: | | |
| pip install --upgrade pip | |
| pip install -e ".[dev]" | |
| - name: Generate OpenAPI spec | |
| run: make generate-openapi | |
| - name: Upload OpenAPI spec | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: openapi-spec-edit-distance-service | |
| path: services/edit-distance-service/edit-distance-service.openapi.json | |
| build: | |
| name: Build & Push Docker Image | |
| runs-on: ubuntu-latest | |
| needs: generate-openapi | |
| if: github.event_name == 'push' && github.ref == 'refs/heads/master' | |
| permissions: | |
| contents: read | |
| packages: write | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Log in to GitHub Container Registry | |
| uses: docker/login-action@v3 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Extract Docker metadata | |
| id: meta | |
| uses: docker/metadata-action@v5 | |
| with: | |
| images: ghcr.io/${{ github.repository_owner }}/edit-distance-service | |
| tags: | | |
| type=sha,prefix=sha- | |
| type=raw,value=latest | |
| - name: Build and push Docker image | |
| uses: docker/build-push-action@v6 | |
| with: | |
| context: services/edit-distance-service | |
| push: true | |
| tags: ${{ steps.meta.outputs.tags }} | |
| labels: ${{ steps.meta.outputs.labels }} |