Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Solution #9

Open
wants to merge 16 commits into
base: main
Choose a base branch
from
Open
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
65 changes: 65 additions & 0 deletions .github/workflows/cd.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
name: 'Continuous Delivery'

on:
push:
branches:
- main
- release/*
pull_request:
branches:
- main
- release/*

jobs:
build:
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v2

- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: '3.9'

- name: Cache dependencies
uses: actions/cache@v2
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements*.txt') }}
restore-keys: |
${{ runner.os }}-pip-

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install -r requirements-dev.txt
pip install -r requirements-test.txt

- name: Set up Google Cloud SDK
uses: google-github-actions/auth@v1
with:
credentials_json: ${{ secrets.GCP_CREDENTIALS }}

- name: Configure Docker
run: gcloud auth configure-docker

- name: Setup gcloud CLI
uses: google-github-actions/setup-gcloud@v1
with:
version: '390.0.0'
service_account_key: ${{ secrets.GCP_CREDENTIALS }}

- name: Download Model from GCS
run: gsutil cp gs://delay-models/source/${{ secrets.MODEL_VERSION }}.pkl delay_model.pkl

- name: Submit Build
run: gcloud builds submit --region ${{ secrets.GCP_REGION }} --tag ${{ secrets.GCP_REGION }}-docker.pkg.dev/${{ secrets.GCP_PROJECT_ID }}/challenge/${{ secrets.GCP_IMAGE_NAME }}:latest

- name: Deploy to Cloud Run
run: gcloud run deploy ${{ secrets.GCP_IMAGE_NAME }} --image ${{ secrets.GCP_REGION }}-docker.pkg.dev/${{ secrets.GCP_PROJECT_ID }}/challenge/${{ secrets.GCP_IMAGE_NAME }}:latest --allow-unauthenticated --region ${{ secrets.GCP_REGION }}

- name: Run Stress Test
run: make stress-test
45 changes: 45 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
name: 'Continuous Integration'

on:
push:
branches:
- main
- develop
pull_request:
branches:
- main
- develop

jobs:
build:
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v2

- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: '3.9'

- name: Cache dependencies
uses: actions/cache@v2
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements*.txt') }}
restore-keys: |
${{ runner.os }}-pip-

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install -r requirements-dev.txt
pip install -r requirements-test.txt

- name: Run model tests
run: make model-test

- name: Run API tests
run: make api-test
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Byte-compiled / optimized / DLL files
__pycache__/

# Unit test / coverage reports
.coverage
reports/
24 changes: 21 additions & 3 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,3 +1,21 @@
# syntax=docker/dockerfile:1.2
FROM python:latest
# put you docker configuration here
FROM python:3.9-slim

# Set the working directory in the container
WORKDIR /app

# Copy the requirements files into the container
COPY requirements.txt requirements.txt
COPY requirements-dev.txt requirements-dev.txt

# Install the required Python packages
RUN pip install -r requirements.txt
RUN pip install -r requirements-dev.txt

# Copy all files from the current directory to the working directory in the container
COPY . .

# Expose port 8080 of the container to external network
EXPOSE 8080

# Command to run the FastAPI application with Uvicorn
CMD ["uvicorn", "challenge.api:app", "--host", "0.0.0.0", "--port", "8080"]
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ install: ## Install dependencies
pip install -r requirements-test.txt
pip install -r requirements.txt

STRESS_URL = http://127.0.0.1:8000
STRESS_URL = https://paz-challenge-tryolabs-latam-6fru3wsz3q-uc.a.run.app
.PHONY: stress-test
stress-test:
# change stress url to your deployed app
Expand Down
72 changes: 64 additions & 8 deletions challenge/api.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,69 @@
import fastapi
from fastapi import FastAPI, HTTPException
import pandas as pd

from challenge.model import DelayModel

from pydantic import BaseModel, validator
from typing import List

app = FastAPI()
delay_model = DelayModel()


class Flight(BaseModel):
OPERA: str
MES: int
TIPOVUELO: str

@validator('MES')
def validate_month(cls, v):
if v < 1 or v > 12:
raise HTTPException(
status_code=400,
detail='MES must be between 1 and 12'
)
return v

@validator('TIPOVUELO')
def validate_flight_type(cls, v):
if v not in ['I', 'N']:
raise HTTPException(
status_code=400,
detail='TIPOVUELO must be either "I" or "N"'
)
return v


class PredictionInfo(BaseModel):
flights: List[Flight]

app = fastapi.FastAPI()

@app.get("/health", status_code=200)
async def get_health() -> dict:
return {
"status": "OK"
}
return {"status": "OK"}


@app.post("/predict", response_model=dict, status_code=200)
async def post_predict(input: PredictionInfo) -> dict:
try:
data = [
{
"OPERA": flight.OPERA,
"MES": flight.MES,
"TIPOVUELO": flight.TIPOVUELO
} for flight in input.flights
]
df = pd.DataFrame(data)

preprocessed_data = delay_model.preprocess(df)
predictions = delay_model.predict(preprocessed_data)

return {"predict": predictions}

@app.post("/predict", status_code=200)
async def post_predict() -> dict:
return
except ValueError as ve:
raise HTTPException(status_code=400, detail=str(ve))
except Exception as e:
raise HTTPException(
status_code=500,
detail=f"An error occurred while processing the prediction: {str(e)}"
)
Loading
Loading