Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,14 @@ TEMPORAL_VALIDATION_POLL_SECONDS=2
TEMPORAL_VALIDATION_MAX_AGE=300
TEMPORAL_VALIDATION_LEASE_SECONDS=120

# Inference container only
INFERENCE_API_URL='http://inference:5051'
INFERENCE_API_TOKEN=
INFERENCE_API_TIMEOUT=10
INFERENCE_WORKERS=1
TRIANGULATION_RELAXATION_SECONDS=1800
TRIANGULATION_MIN_APEX_DISTANCE_KM=0.1

# Production-only
ACME_EMAIL=
BACKEND_HOST=
Expand Down
2 changes: 1 addition & 1 deletion .github/verify_deps_sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

import yaml

DOCKERFILES = ["src/Dockerfile", "src/Dockerfile.test"]
DOCKERFILES = ["src/Dockerfile", "src/Dockerfile.inference", "src/Dockerfile.test"]
PRECOMMIT_CONFIG = ".pre-commit-config.yaml"
PYPROJECTS = ["./pyproject.toml", "./client/pyproject.toml"]
TRACKED_DEPS = ("uv", "ruff", "ty", "prek")
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/builds.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ jobs:
POSTGRES_USER: dummy_pg_user
POSTGRES_PASSWORD: dummy_pg_pwd
POSTGRES_DB: dummy_pg_db
INFERENCE_API_TOKEN: dummy-inference-token
run: |
docker compose up -d --build --wait
docker compose logs
Expand Down
51 changes: 42 additions & 9 deletions .github/workflows/push.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ on:

env:
BACKEND_IMAGE_NAME: alert-api
INFERENCE_IMAGE_NAME: alert-api-inference
DOCKERHUB_USER: ${{ secrets.DOCKERHUB_LOGIN }}
PYTHON_VERSION: "3.11"
UV_VERSION: "0.11.14"
Expand All @@ -31,14 +32,18 @@ jobs:
echo "IMAGE_TAG=latest" >> $GITHUB_ENV
fi
- name: Build docker
run: docker build -f src/Dockerfile . -t $DOCKERHUB_USER/$BACKEND_IMAGE_NAME:$IMAGE_TAG
run: |
docker build -f src/Dockerfile . -t $DOCKERHUB_USER/$BACKEND_IMAGE_NAME:$IMAGE_TAG
docker build -f src/Dockerfile.inference . -t $DOCKERHUB_USER/$INFERENCE_IMAGE_NAME:$IMAGE_TAG
- name: Login to DockerHub
uses: docker/login-action@v4
with:
username: ${{ secrets.DOCKERHUB_LOGIN }}
password: ${{ secrets.DOCKERHUB_PW }}
- name: Push to hub
run: docker push $DOCKERHUB_USER/$BACKEND_IMAGE_NAME:$IMAGE_TAG
run: |
docker push $DOCKERHUB_USER/$BACKEND_IMAGE_NAME:$IMAGE_TAG
docker push $DOCKERHUB_USER/$INFERENCE_IMAGE_NAME:$IMAGE_TAG
- name: Login to GHCR
uses: docker/login-action@v4
with:
Expand All @@ -47,29 +52,57 @@ jobs:
password: ${{ secrets.GITHUB_TOKEN }}
- name: Push to container registry
run: |
IMAGE_ID=ghcr.io/${{ github.repository_owner }}/$BACKEND_IMAGE_NAME
IMAGE_ID=$(echo $IMAGE_ID | tr '[A-Z]' '[a-z]')
docker tag $DOCKERHUB_USER/$BACKEND_IMAGE_NAME:$IMAGE_TAG $IMAGE_ID:$IMAGE_TAG
docker push $IMAGE_ID:$IMAGE_TAG
for IMAGE_NAME in $BACKEND_IMAGE_NAME $INFERENCE_IMAGE_NAME; do
IMAGE_ID=ghcr.io/${{ github.repository_owner }}/$IMAGE_NAME
IMAGE_ID=$(echo $IMAGE_ID | tr '[A-Z]' '[a-z]')
docker tag $DOCKERHUB_USER/$IMAGE_NAME:$IMAGE_TAG $IMAGE_ID:$IMAGE_TAG
docker push $IMAGE_ID:$IMAGE_TAG
done

deploy-dev:
if: github.ref == 'refs/heads/main'
needs: docker
runs-on: ubuntu-latest
steps:
- uses: appleboy/ssh-action@v1.2.5
- uses: actions/checkout@v7
- name: Verify inference deployment secret
env:
INFERENCE_API_TOKEN: ${{ secrets.INFERENCE_API_TOKEN }}
run: test -n "$INFERENCE_API_TOKEN"
- name: Upload Compose definition
uses: appleboy/scp-action@v1.0.0
with:
host: ${{ secrets.SSH_DEV_HOST }}
username: ${{ secrets.SSH_DEV_USERNAME }}
key: ${{ secrets.SSH_DEPLOY_DEV }}
source: docker-compose.yml
target: "."
overwrite: true
- name: Deploy inference, then backend
uses: appleboy/ssh-action@v1.2.5
env:
INFERENCE_API_TOKEN: ${{ secrets.INFERENCE_API_TOKEN }}
with:
host: ${{ secrets.SSH_DEV_HOST }}
username: ${{ secrets.SSH_DEV_USERNAME }}
key: ${{ secrets.SSH_DEPLOY_DEV }}
envs: INFERENCE_API_TOKEN
script: |
touch .env
grep -v '^INFERENCE_API_TOKEN=' .env > .env.next || true
printf 'INFERENCE_API_TOKEN=%s\n' "$INFERENCE_API_TOKEN" >> .env.next
chmod 600 .env.next
mv .env.next .env
docker compose config -q
# Ensure we have max disk space
docker rmi -f $(docker images -f "dangling=true" -q)
docker volume rm -f $(docker volume ls -f "dangling=true" -q)
# Update the service
docker compose pull backend
docker compose stop backend && docker compose up -d --wait
docker compose pull inference backend
docker compose up -d --wait inference
docker compose stop backend && docker compose up -d --wait backend
# Check update
docker inspect -f '{{ .Created }}' $(docker compose images -q inference)
docker inspect -f '{{ .Created }}' $(docker compose images -q backend)
# Clean up
docker rmi -f $(docker images -f "dangling=true" -q)
Expand Down
6 changes: 5 additions & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ jobs:
- uses: astral-sh/setup-uv@v7
with:
version: ${{ env.UV_VERSION }}
- name: Run inference tests
run: PYTHONPATH=src uv run --group inference --group test pytest --cov=src/inference --cov-config=/dev/null --cov-report xml:coverage-inference.xml inference/tests
- name: Run backend tests
env:
SUPERADMIN_LOGIN: dummy_login
Expand All @@ -30,9 +32,11 @@ jobs:
POSTGRES_USER: dummy_pg_user
POSTGRES_PASSWORD: dummy_pg_pwd
POSTGRES_DB: dummy_pg_db
INFERENCE_API_TOKEN: dummy-inference-token
run: |
UV_GROUPS="server test" docker compose -f docker-compose.dev.yml up -d --build --wait
docker compose -f docker-compose.dev.yml exec -T backend pytest --cov=app --cov-report xml tests/
docker compose -f docker-compose.dev.yml exec -T backend python -c "import importlib.util; packages = ('geopy', 'networkx', 'numpy', 'pandas', 'pyproj', 'shapely', 'timezonefinder'); found = [package for package in packages if importlib.util.find_spec(package)]; assert not found, found"
docker compose -f docker-compose.dev.yml cp backend:/app/coverage.xml ./coverage-src.xml
- name: Run Telegram end-to-end check
env:
Expand All @@ -50,7 +54,7 @@ jobs:
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v7
with:
files: ./coverage-src.xml
files: ./coverage-src.xml,./coverage-inference.xml
token: ${{ secrets.CODECOV_TOKEN }}
fail_ci_if_error: true

Expand Down
10 changes: 10 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,16 @@ Let's install the different libraries:
make venv
```

#### Dependency boundary

The `server` dependency group is for database, storage, and web API code. CPU-heavy or native geospatial dependencies belong to the `inference` group and `src/inference`; the inference container must not receive database, storage, or JWT credentials or join the database network.

Run inference tests independently with:

```shell
PYTHONPATH=src uv run --group inference --group test pytest inference/tests
```

#### Pre-commit hooks
Let's make your life easier by formatting & fixing lint on each commit:
```shell
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ ruff-check: ruff-lint ruff-format
ruff-fix: ruff-lint-fix ruff-format-fix

typing-check: $(PYPROJECT)
uv run --group server --group client --group quality ty check src/app client/pyroclient
uv run --group server --group inference --group client --group quality ty check src/app src/inference client/pyroclient

deps-check: .github/verify_deps_sync.py
uv lock --check
Expand Down
38 changes: 38 additions & 0 deletions docker-compose.dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ services:
interval: 10s
timeout: 3s
retries: 3
networks:
- data

# ref link: https://github.com/localstack/localstack/blob/master/docker-compose.yml
localstack:
Expand All @@ -30,6 +32,29 @@ services:
interval: 10s
timeout: 5s
retries: 10
networks:
- data

inference:
build:
context: .
dockerfile: ./src/Dockerfile.inference
args:
UV_GROUPS: ${INFERENCE_UV_GROUPS:-inference}
expose:
- 5051
environment:
- INFERENCE_API_TOKEN=${INFERENCE_API_TOKEN:-development-inference-token}
- INFERENCE_WORKERS=1
- TRIANGULATION_RELAXATION_SECONDS=${TRIANGULATION_RELAXATION_SECONDS:-1800}
- TRIANGULATION_MIN_APEX_DISTANCE_KM=${TRIANGULATION_MIN_APEX_DISTANCE_KM:-0.1}
healthcheck:
test: ["CMD-SHELL", "curl --fail http://localhost:5051/status"]
interval: 10s
timeout: 3s
retries: 3
networks:
- compute

backend:
build:
Expand All @@ -42,6 +67,8 @@ services:
condition: service_healthy
localstack:
condition: service_healthy
inference:
condition: service_healthy
ports:
- "5050:5050"
environment:
Expand All @@ -59,6 +86,9 @@ services:
- S3_REGION=us-east-1
- SLACK_HOOK=${SLACK_HOOK}
- PLATFORM_URL=${PLATFORM_URL:-https://platform.pyronear.org}
- INFERENCE_API_URL=http://inference:5051
- INFERENCE_API_TOKEN=${INFERENCE_API_TOKEN:-development-inference-token}
- INFERENCE_API_TIMEOUT=${INFERENCE_API_TIMEOUT:-10}
volumes:
- ./src/:/app/
command: "sh -c 'alembic upgrade head && python app/db.py && uvicorn app.main:app --reload --host 0.0.0.0 --port 5050 --proxy-headers'"
Expand All @@ -68,3 +98,11 @@ services:
interval: 10s
timeout: 3s
retries: 3
networks:
- data
- compute

networks:
data:
compute:
internal: true
38 changes: 38 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ services:
interval: 10s
timeout: 3s
retries: 3
networks:
- data

# ref link: https://github.com/localstack/localstack/blob/master/docker-compose.yml
localstack:
Expand All @@ -35,6 +37,29 @@ services:
interval: 10s
timeout: 5s
retries: 10
networks:
- data

inference:
image: ghcr.io/pyronear/alert-api-inference:latest
build:
context: .
dockerfile: ./src/Dockerfile.inference
expose:
- 5051
environment:
- INFERENCE_API_TOKEN=${INFERENCE_API_TOKEN:?INFERENCE_API_TOKEN must be set}
- INFERENCE_WORKERS=${INFERENCE_WORKERS:-1}
- TRIANGULATION_RELAXATION_SECONDS=${TRIANGULATION_RELAXATION_SECONDS:-1800}
- TRIANGULATION_MIN_APEX_DISTANCE_KM=${TRIANGULATION_MIN_APEX_DISTANCE_KM:-0.1}
restart: always
healthcheck:
test: ["CMD-SHELL", "curl --fail http://localhost:5051/status"]
interval: 10s
timeout: 3s
retries: 3
networks:
- compute

backend:
image: ghcr.io/pyronear/alert-api:latest
Expand All @@ -48,6 +73,8 @@ services:
condition: service_healthy
localstack:
condition: service_healthy
inference:
condition: service_healthy
ports:
- "5050:5050"
environment:
Expand Down Expand Up @@ -78,6 +105,9 @@ services:
- TEMPORAL_VALIDATION_POLL_SECONDS=${TEMPORAL_VALIDATION_POLL_SECONDS:-2}
- TEMPORAL_VALIDATION_MAX_AGE=${TEMPORAL_VALIDATION_MAX_AGE:-300}
- TEMPORAL_VALIDATION_LEASE_SECONDS=${TEMPORAL_VALIDATION_LEASE_SECONDS:-120}
- INFERENCE_API_URL=http://inference:5051
- INFERENCE_API_TOKEN=${INFERENCE_API_TOKEN:?INFERENCE_API_TOKEN must be set}
- INFERENCE_API_TIMEOUT=${INFERENCE_API_TIMEOUT:-10}
volumes:
- ./src/:/app/
command: "sh -c 'alembic upgrade head && python app/db.py && uvicorn app.main:app --reload --host 0.0.0.0 --port 5050 --proxy-headers'"
Expand All @@ -87,7 +117,15 @@ services:
interval: 10s
timeout: 3s
retries: 3
networks:
- data
- compute

volumes:
postgres_data:
localstack_data:

networks:
data:
compute:
internal: true
Loading