Skip to content

Commit b95be9a

Browse files
authored
Merge pull request #802 from Sadeequ/moxxi
Subsection and carriage fix
2 parents a3fa884 + 15480d2 commit b95be9a

8 files changed

Lines changed: 1347 additions & 101 deletions

File tree

.github/workflows/pytest.yml

Lines changed: 99 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ name: CI
22

33
on:
44
push:
5-
branches: [ "main", "master" ]
5+
branches: ["main", "master"]
66
pull_request:
7-
branches: [ "main", "master" ]
7+
branches: ["main", "master"]
88

99
jobs:
1010
test:
@@ -13,114 +13,117 @@ jobs:
1313
# below short-circuits otherwise so the matrix completes cleanly on
1414
# standard GitHub-hosted runners). `requirements-cpu.txt` is used when
1515
# present so CPU CI doesn't pull heavy CUDA-bound wheels.
16-
name: pytest (${{ matrix.flavor }}, py${{ matrix.python-version }})
16+
name: pytest (${ matrix.flavor }, py${{ matrix.python-version }}, torch${{ matrix.torch-version }})
1717
runs-on: ubuntu-latest
1818
strategy:
1919
fail-fast: false
2020
matrix:
21-
python-version: ["3.10", "3.11"]
21+
python-version: ["3.10", "3.11", "3.12"]
2222
flavor: ["cpu"]
23+
torch-version: ["2.0", "2.1", "2.2"]
24+
exclude:
25+
- python-version: "3.12"
26+
torch-version: "2.0"
2327
include:
2428
- python-version: "3.11"
2529
flavor: "gpu"
30+
torch-version: "2.1"
2631

2732
continue-on-error: ${{ matrix.flavor == 'gpu' }}
2833

2934
steps:
30-
- uses: actions/checkout@v4
31-
32-
- name: Set up Python ${{ matrix.python-version }}
33-
uses: actions/setup-python@v5
34-
with:
35-
python-version: ${{ matrix.python-version }}
36-
# Issue #203: cache pip wheels keyed on all requirements files so the
37-
# cache is invalidated whenever any dependency changes, but reused
38-
# across runs when nothing has changed — cuts install time by ~60-80%.
39-
cache: pip
40-
cache-dependency-path: |
41-
requirements*.txt
42-
43-
# Issue #203: expose pip's wheel cache dir so the built-in setup-python
44-
# cache restores pre-compiled wheels and skips compilation on cache hits.
45-
- name: Install dependencies (${{ matrix.flavor }})
46-
env:
47-
PIP_CACHE_DIR: ~/.cache/pip
48-
run: |
49-
python -m pip install --upgrade pip
50-
pip install pytest pytest-asyncio pytest-xdist
51-
if [ "${{ matrix.flavor }}" = "cpu" ] && [ -f requirements-cpu.txt ]; then
52-
pip install -r requirements-cpu.txt
53-
elif [ -f requirements.txt ]; then
54-
pip install -r requirements.txt
55-
fi
56-
# Install the package itself so `import astroml` resolves in tests.
57-
pip install -e . --no-deps
58-
59-
- name: Run pytest (CPU)
60-
if: matrix.flavor == 'cpu'
61-
# Issue #204: -p no:randomly prevents non-deterministic ordering;
62-
# --forked (if available) isolates shared-state flakiness in test_dedupe.
63-
# Issue #338: Run property-based tests with coverage reporting.
64-
run: pytest -v -m "not gpu" -p no:randomly --tb=short --cov=astroml --cov-report=xml --cov-report=term
65-
66-
- name: Upload coverage to Codecov
67-
if: matrix.flavor == 'cpu'
68-
uses: codecov/codecov-action@v4
69-
with:
70-
token: ${{ secrets.CODECOV_TOKEN }}
71-
files: ./coverage.xml
72-
flags: py${{ matrix.python-version }}
73-
fail_ci_if_error: false
74-
75-
- name: Run pytest (GPU)
76-
if: matrix.flavor == 'gpu'
77-
run: |
78-
# The GPU subset is gated by pytest's `gpu` marker. We additionally
79-
# short-circuit when CUDA isn't reachable so the job stays green on
80-
# CPU-only runners until self-hosted GPU runners come online.
81-
python - <<'PY'
82-
import sys
83-
try:
84-
import torch
85-
except ImportError:
86-
print("torch not installed; skipping GPU pytest")
87-
sys.exit(0)
88-
if not torch.cuda.is_available():
89-
print("CUDA not available on this runner; GPU job no-op")
90-
sys.exit(0)
91-
import subprocess
92-
subprocess.check_call(["pytest", "-v", "-m", "gpu"])
93-
PY
94-
95-
# ── Issue #244: API integration tests ────────────────────────────────────────
35+
- uses: actions/checkout@v4
36+
37+
- name: Set up Python ${{ matrix.python-version }}
38+
uses: actions/setup-python@v5
39+
with:
40+
python-version: ${{ matrix.python-version }}
41+
cache: pip
42+
cache-dependency-path: |
43+
requirements*.txt
44+
45+
- name: Install dependencies (${ matrix.flavor }})
46+
env:
47+
PIP_CACHE_DIR: ~/.cache/pip
48+
run: |
49+
python -m pip install --upgrade pip
50+
pip install pytest pytest-asyncio pytest-xdist
51+
52+
# Install torch first to pin the version, then install remaining deps
53+
if [ "${{ matrix.flavor }}" = "cpu" ]; then
54+
pip install torch==${{ matrix.torch-version }}.* --index-url https://download.pytorch.org/whl/cpu
55+
else
56+
pip install torch==${{ matrix.torch-version }}.*
57+
fi
58+
59+
if [ "${{ matrix.flavor }}" = "cpu" ] && [ -f requirements-cpu.txt ]; then
60+
pip install -r requirements-cpu.txt
61+
elif [ -f requirements.txt ]; then
62+
pip install -r requirements.txt
63+
fi
64+
pip install -e . --no-deps
65+
66+
- name: Verify torch installation
67+
run: |
68+
python -c "import torch; print(f'torch {torch.__version__} installed; CUDA available: {torch.cuda.is_available()}')"
69+
70+
- name: Run pytest (CPU)
71+
if: matrix.flavor == 'cpu'
72+
run: pytest -v -m "not gpu" -p no:randomly --tb=short --cov=astroml --cov-report=xml --cov-report=term
73+
74+
- name: Upload coverage to Codecov
75+
if: matrix.flavor == 'cpu' && matrix.python-version == '3.11' && matrix.torch-version == '2.1'
76+
uses: codecov/codecov-action@v4
77+
with:
78+
token: ${{ secrets.CODECOV_TOKEN }}
79+
files: ./coverage.xml
80+
flags: py${{ matrix.python-version }}-torch${{ matrix.torch-version }}
81+
fail_ci_if_error: false
82+
83+
- name: Run pytest (GPU)
84+
if: matrix.flavor == 'gpu'
85+
run: |
86+
python - <<'PY'
87+
import sys
88+
try:
89+
import torch
90+
except ImportError:
91+
print("torch not installed; skipping GPU pytest")
92+
sys.exit(0)
93+
if not torch.cuda.is_available():
94+
print("CUDA not available on this runner; GPU job no-op")
95+
sys.exit(0)
96+
import subprocess
97+
subprocess.check_call(["pytest", "-v", "-m", "gpu"])
98+
PY
99+
96100
test-api:
97101
name: API integration tests (py3.11)
98102
runs-on: ubuntu-latest
99103

100104
steps:
101-
- uses: actions/checkout@v4
102-
103-
- name: Set up Python 3.11
104-
uses: actions/setup-python@v5
105-
with:
106-
python-version: "3.11"
107-
cache: pip
108-
cache-dependency-path: |
109-
requirements*.txt
110-
111-
- name: Install dependencies
112-
env:
113-
PIP_CACHE_DIR: ~/.cache/pip
114-
run: |
115-
python -m pip install --upgrade pip
116-
pip install pytest pytest-asyncio pytest-xdist httpx fastapi sqlalchemy
117-
if [ -f requirements-cpu.txt ]; then
118-
pip install -r requirements-cpu.txt
119-
elif [ -f requirements.txt ]; then
120-
pip install -r requirements.txt
121-
fi
122-
pip install -e . --no-deps
123-
124-
- name: Run API integration tests
125-
# Uses SQLite in-memory via conftest.py — no Postgres needed in CI.
126-
run: pytest api/tests/ -v --tb=short -p no:randomly
105+
- uses: actions/checkout@v4
106+
107+
- name: Set up Python 3.11
108+
uses: actions/setup-python@v5
109+
with:
110+
python-version: "3.11"
111+
cache: pip
112+
cache-dependency-path: |
113+
requirements*.txt
114+
115+
- name: Install dependencies
116+
env:
117+
PIP_CACHE_DIR: ~/.cache/pip
118+
run: |
119+
python -m pip install --upgrade pip
120+
pip install pytest pytest-asyncio pytest-xdist httpx fastapi sqlalchemy
121+
if [ -f requirements-cpu.txt ]; then
122+
pip install -r requirements-cpu.txt
123+
elif [ -f requirements.txt ]; then
124+
pip install -r requirements.txt
125+
fi
126+
pip install -e . --no-deps
127+
128+
- name: Run API integration tests
129+
run: pytest api/tests/ -v --tb=short -p no:randomly

.github/workflows/release.yml

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
name: Release Images
2+
3+
on:
4+
push:
5+
tags: ["v*.*.*"]
6+
7+
env:
8+
REGISTRY: ghcr.io
9+
IMAGE_NAME: ${{ github.repository }}
10+
11+
jobs:
12+
release:
13+
name: build-and-push-release
14+
runs-on: ubuntu-latest
15+
permissions:
16+
contents: read
17+
packages: write
18+
id-token: write
19+
attestations: write
20+
strategy:
21+
fail-fast: false
22+
matrix:
23+
stage:
24+
- production
25+
- ingestion
26+
- feature-store
27+
steps:
28+
- uses: actions/checkout@v4
29+
30+
- name: Extract version metadata
31+
id: meta
32+
run: |
33+
TAG=${GITHUB_REF#refs/tags/}
34+
VERSION=${TAG#v}
35+
MAJOR=$(echo "$VERSION" | cut -d. -f1)
36+
MINOR=$(echo "$VERSION" | cut -d. -f2)
37+
PATCH=$(echo "$VERSION" | cut -d. -f3)
38+
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
39+
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
40+
echo "major=$MAJOR" >> "$GITHUB_OUTPUT"
41+
echo "minor=$MINOR" >> "$GITHUB_OUTPUT"
42+
echo "major_minor=$MAJOR.$MINOR" >> "$GITHUB_OUTPUT"
43+
44+
- name: Set up Docker Buildx
45+
uses: docker/setup-buildx-action@v3
46+
47+
- name: Log in to GHCR
48+
uses: docker/login-action@v3
49+
with:
50+
registry: ${{ env.REGISTRY }}
51+
username: ${{ github.actor }}
52+
password: ${{ secrets.GITHUB_TOKEN }}
53+
54+
- name: Docker meta
55+
id: docker_meta
56+
uses: docker/metadata-action@v5
57+
with:
58+
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
59+
tags: |
60+
type=semver,pattern={{version}}
61+
type=semver,pattern={{major}}.{{minor}}
62+
type=semver,pattern={{major}}
63+
type=sha
64+
type=raw,value=${{ matrix.stage }}-{{version}}
65+
type=raw,value=${{ matrix.stage }}-{{major}}.{{minor}}
66+
type=raw,value=${{ matrix.stage }}-{{major}}
67+
type=raw,value=${{ matrix.stage }}-latest
68+
69+
- name: Build and push ${{ matrix.stage }} image
70+
id: build
71+
uses: docker/build-push-action@v5
72+
with:
73+
context: .
74+
file: ./Dockerfile
75+
target: ${{ matrix.stage }}
76+
platforms: linux/amd64,linux/arm64
77+
push: true
78+
tags: ${{ steps.docker_meta.outputs.tags }}
79+
labels: ${{ steps.docker_meta.outputs.labels }}
80+
cache-from: type=gha
81+
cache-to: type=gha,mode=max
82+
build-args: |
83+
VERSION=${{ steps.meta.outputs.version }}
84+
85+
- name: Attest build provenance
86+
uses: actions/attest-build-provenance@v1
87+
with:
88+
subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
89+
subject-digest: ${{ steps.build.outputs.digest }}
90+
push-to-registry: true
91+
92+
create-release:
93+
name: create-github-release
94+
needs: release
95+
runs-on: ubuntu-latest
96+
permissions:
97+
contents: write
98+
steps:
99+
- uses: actions/checkout@v4
100+
101+
- name: Extract version
102+
id: meta
103+
run: |
104+
TAG=${GITHUB_REF#refs/tags/}
105+
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
106+
107+
- name: Generate release notes
108+
id: notes
109+
run: |
110+
cat <<'EOF' > /tmp/release_notes.md
111+
## Docker Images
112+
113+
Images published to GHCR for tag `${{ steps.meta.outputs.tag }}`:
114+
115+
- `${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.tag }}`
116+
- `${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:production-${{ steps.meta.outputs.tag }}`
117+
- `${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:ingestion-${{ steps.meta.outputs.tag }}`
118+
- `${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:feature-store-${{ steps.meta.outputs.tag }}`
119+
120+
### Usage
121+
122+
```bash
123+
docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.tag }}
124+
```
125+
126+
### Architectures
127+
128+
- `linux/amd64`
129+
- `linux/arm64`
130+
EOF
131+
132+
- name: Create GitHub Release
133+
uses: softprops/action-gh-release@v2
134+
with:
135+
tag_name: ${{ steps.meta.outputs.tag }}
136+
name: Release ${{ steps.meta.outputs.tag }}
137+
body_path: /tmp/release_notes.md
138+
generate_release_notes: true

api/app.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
from strawberry.fastapi import GraphQLRouter
3131

3232
from api.audit_middleware import AuditLoggingMiddleware
33+
from api.auth.hardening import PublicRateLimitMiddleware, SecurityHeadersMiddleware
3334
from api.auth.middleware import AuthMiddleware
3435
from api.config import settings
3536
from api.database import get_async_session_factory
@@ -249,6 +250,8 @@ async def lifespan(application: FastAPI) -> AsyncGenerator[None, None]:
249250
app.add_exception_handler(HTTPException, http_exception_handler)
250251
app.add_exception_handler(RequestValidationError, request_validation_exception_handler)
251252
app.add_exception_handler(Exception, unhandled_exception_handler)
253+
app.add_middleware(SecurityHeadersMiddleware)
254+
app.add_middleware(PublicRateLimitMiddleware, requests_per_minute=30, burst_size=10)
252255
app.add_middleware(VersionMiddleware)
253256
app.add_middleware(AuthMiddleware)
254257
app.add_middleware(ValidationMiddleware)

0 commit comments

Comments
 (0)