Skip to content

Commit

Permalink
feat: Add FWI API route (#84)
Browse files Browse the repository at this point in the history
* feat: Update app configuration

* feat: Add get_fwi helper

* feat: Add app config

* feat: Update api schemas

* feat: Add FWI route to api

* feat: Exclude risk route

* feat: Add pyrorisks conda-lock for rasterio

* refactor: Update API settings

* chore: Update dependencies

* chore: Remove build directory

* chore: Add conda to dockerfile for rasterio

* chore: Update docker-compose config

* chore: Update dependencies and configuration

* chore: Add docker image push workflow

* chore: Update FWI script workflow

* chore: Update doc-deployment action

* fix: Bump setup-python action to v4

* fix: Fix type hints
  • Loading branch information
jsakv authored Aug 6, 2024
1 parent 1b07dc2 commit 1ea8697
Show file tree
Hide file tree
Showing 19 changed files with 12,786 additions and 357 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/doc-deploy.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ jobs:
with:
persist-credentials: false
- name: Set up Python 3.10.5
uses: actions/setup-python@v1
uses: actions/setup-python@v4
with:
python-version: 3.10.5
python-version: 3.10

- name: Install dependencies
run: poetry install
Expand Down
35 changes: 35 additions & 0 deletions .github/workflows/push.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
name: push
on:
push:
branches: [ main ]

env:
IMAGE_NAME: pyro-risks
DOCKERHUB_USER: ${{ secrets.DOCKERHUB_LOGIN }}

jobs:
docker:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build docker
run: docker compose build -t $DOCKERHUB_USER/$IMAGE_NAME:latest
- name: Login to DockerHub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_LOGIN }}
password: ${{ secrets.DOCKERHUB_PW }}
- name: Push to hub
run: docker push $DOCKERHUB_USER/$IMAGE_NAME:latest
- name: Login to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Push to container registry
run: |
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:latest $IMAGE_ID:latest
docker push $IMAGE_ID:latest
10 changes: 8 additions & 2 deletions .github/workflows/scripts.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,18 @@ jobs:
with:
python-version: 3.10.5

- uses: s-weigand/setup-conda@v1
with:
activate-conda: false
- run: conda install conda-lock
- run: conda-lock install -n pyro-risks pyrorisks.conda-lock.yml
- run: conda activate pyro-risks

- name: Install Poetry
uses: snok/install-poetry@v1
with:
version: 1.8.1
virtualenvs-create: true
virtualenvs-in-project: true
virtualenvs-create: false

- name: Install dependencies
run: poetry install
Expand Down
37 changes: 30 additions & 7 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,19 +1,42 @@
FROM python:3.10-buster

WORKDIR /app

RUN wget --quiet https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh -O /tmp/miniconda.sh && \
/bin/bash /tmp/miniconda.sh -b -p /opt/conda && \
rm /tmp/miniconda.sh

ENV PATH=/opt/conda/bin:$PATH

RUN conda install -c conda-forge conda-lock

# Install and activate pyrorisks environment

COPY pyrorisks.conda-lock.yml pyrorisks.conda-lock.yml

RUN conda-lock install --name pyrorisks pyrorisks.conda-lock.yml && conda clean -a

ENV CONDA_DEFAULT_ENV=pyrorisks
ENV PATH /opt/conda/envs/${CONDA_DEFAULT_ENV}/bin:$PATH
RUN echo "conda activate ${CONDA_DEFAULT_ENV}" >> ~/.bashrc

# Install poetry
RUN pip install poetry==1.8.1

# Set environment variables for poetry
ENV POETRY_NO_INTERACTION=1 \
POETRY_VIRTUALENVS_IN_PROJECT=1 \
POETRY_VIRTUALENVS_CREATE=1 \
POETRY_VIRTUALENVS_CREATE=0 \
POETRY_CACHE_DIR=/tmp/poetry_cache \
VIRTUAL_ENV=/app/.venv \
PATH="/app/.venv/bin:$PATH"

WORKDIR /app
VIRTUAL_ENV=/opt/conda/envs/${CONDA_DEFAULT_ENV} \
PATH="/opt/conda/envs/${CONDA_DEFAULT_ENV}/bin:$PATH" \
PYTHONPATH="/opt/conda/envs/${CONDA_DEFAULT_ENV}/lib/python3.10/site-packages:${PYTHONPATH}"

COPY pyrorisks ./pyrorisks
COPY app ./app
COPY build ./build
COPY pyrorisks ./pyrorisks
COPY pyproject.toml poetry.lock README.md ./

# Install pyrorisks package in pyrorisks conda environment
RUN poetry install

CMD ["bash"]
28 changes: 28 additions & 0 deletions app/api/routes/fwi.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Copyright (C) 2021-2022, Pyronear.

# This program is licensed under the Apache License version 2.
# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.

from typing import Dict, Any
from fastapi import APIRouter, Depends
from fastapi import HTTPException, status
from app.api.schemas import ScoreQueryParams, Score
from pyrorisks.platform_fwi.get_fwi_effis_score import get_fwi as _get_fwi


router = APIRouter()


@router.get(
path="/",
response_model=Score,
summary="Provide European Forest Fire Information System (EFFIS) Fire Weather Index (FWI) categories.",
)
async def get_fwi(query: ScoreQueryParams = Depends()) -> Dict[str, Any]:
results = _get_fwi(longitude=query.longitude, latitude=query.latitude, crs=query.crs)
if results is None:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Fire Weather Index (FWI) for longitude {query.longitude} and latitude {query.latitude} was not found.",
)
return results
21 changes: 20 additions & 1 deletion app/api/schemas.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright (C) 2021-2022, Pyronear.
# Copyright (C) 2020-2024, Pyronear.

# This program is licensed under the Apache License version 2.
# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0.txt> for full license details.
Expand All @@ -11,3 +11,22 @@ class RegionRisk(BaseModel):
geocode: str = Field(..., examples=["01"])
score: float = Field(..., gt=0, lt=1, examples=[0.5])
explainability: Optional[str] = Field(None, examples=["weather"])


class ScoreQueryParams(BaseModel):
longitude: float = Field(..., gt=-90.0, lt=90.0)
latitude: float = Field(..., gt=-180.0, lt=180.0)
crs: str = Field(
default="EPSG:4326",
examples=["EPSG:4326"],
description="Coordinate Reference System (CRS), Default to World Geodetic System CRS (EPSG:4326 / WGS84).",
)


class Score(BaseModel):
longitude: float = Field(..., gt=-90.0, lt=90.0, examples=[2.638828])
latitude: float = Field(..., gt=-180.0, lt=180.0, examples=[48.391842])
crs: str = Field(..., examples=["EPSG:4326"], description="Coordinate Reference System (CRS).")
score: str = Field(..., examples=["fwi"], description="Score name.")
value: float = Field(..., examples=[2, 1], description="Score value.")
date: str = Field(..., examples=["2024-01-01"], description="Date in %Y-%m-%d format")
21 changes: 0 additions & 21 deletions app/config.py

This file was deleted.

Empty file added app/core/__init__.py
Empty file.
34 changes: 34 additions & 0 deletions app/core/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Copyright (C) 2021-2024, Pyronear.

# This program is licensed under the Apache License 2.0.
# See LICENSE or go to <https://www.apache.org/licenses/LICENSE-2.0> for full license details.

from pydantic_settings import BaseSettings, SettingsConfigDict
from pydantic import field_validator
from typing import Optional

__all__ = ["settings"]


class Settings(BaseSettings):
model_config = SettingsConfigDict(env_file="./../../.env")

PROJECT_NAME: str = "Pyrorisks"
PROJECT_DESCRIPTION: str = "API for wildfire risk estimation"
LOGO_URL: str = "https://pyronear.org/img/logo_letters.png"
VERSION: str = "0.1.0"
DEBUG: bool = False

@field_validator("DEBUG", mode="before")
@classmethod
def transform_debug(cls, value: str) -> bool:
return value != "False"

S3_BUCKET_NAME: Optional[str] = None
S3_ACCESS_KEY: Optional[str] = None
S3_SECRET_KEY: Optional[str] = None
S3_REGION: Optional[str] = None
S3_ENDPOINT_URL: Optional[str] = None


settings = Settings() # type: ignore[call-arg]
22 changes: 11 additions & 11 deletions app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,19 @@
from fastapi import FastAPI, Request
from fastapi.openapi.utils import get_openapi

from app import config as cfg
from app.api.routes import risk
from app.core.config import settings
from app.api.routes import fwi


app = FastAPI(
title=cfg.PROJECT_NAME,
description=cfg.PROJECT_DESCRIPTION,
debug=cfg.DEBUG,
version=cfg.VERSION,
title=settings.PROJECT_NAME,
description=settings.PROJECT_DESCRIPTION,
debug=settings.DEBUG,
version=settings.VERSION,
)

# Routing
app.include_router(risk.router, prefix="/risk", tags=["risk"])
app.include_router(fwi.router, prefix="/fwi", tags=["fwi"])


# Middleware
Expand All @@ -37,12 +37,12 @@ def custom_openapi():
if app.openapi_schema:
return app.openapi_schema
openapi_schema = get_openapi(
title=cfg.PROJECT_NAME,
version=cfg.VERSION,
description=cfg.PROJECT_DESCRIPTION,
title=settings.PROJECT_NAME,
version=settings.VERSION,
description=settings.PROJECT_DESCRIPTION,
routes=app.routes,
)
openapi_schema["info"]["x-logo"] = {"url": cfg.LOGO_URL}
openapi_schema["info"]["x-logo"] = {"url": settings.LOGO_URL}
app.openapi_schema = openapi_schema
return app.openapi_schema

Expand Down
Binary file not shown.
11 changes: 7 additions & 4 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
version: '3.7'

services:
web:
build: .
Expand All @@ -9,5 +7,10 @@ services:
ports:
- ${PORT}:8000
environment:
- CDS_UID=${CDS_UID}
- CDS_API_KEY=${CDS_API_KEY}
- DEBUG=${DEBUG}
- S3_BUCKET_NAME=${S3_BUCKET_NAME}
- S3_ACCESS_KEY=${S3_ACCESS_KEY}
- S3_SECRET_KEY=${S3_SECRET_KEY}
- S3_REGION=${S3_REGION}
- S3_ENDPOINT_URL=${S3_ENDPOINT_URL}
platform: "linux/amd64"
Loading

0 comments on commit 1ea8697

Please sign in to comment.