Skip to content

Commit a229e9c

Browse files
committed
Add FastAPI app with Docker, CI/CD, and linter
1 parent 9a7c11b commit a229e9c

File tree

10 files changed

+378
-48
lines changed

10 files changed

+378
-48
lines changed

.github/workflows/ci.yml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,10 @@ jobs:
2323
run: |
2424
python -m pip install --upgrade pip
2525
pip install -r requirements.txt
26+
pip install flake8 # make sure flake8 is installed
2627
27-
- name: Run tests
28-
run: pytest || exit 0
28+
- name: Run flake8 linter
29+
run: flake8 .
2930

31+
- name: Run tests
32+
run: pytest

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ ENV/
2323

2424
# Jupyter Notebook checkpoints
2525
.ipynb_checkpoints/
26-
26+
mlruns
2727
# Data files (don’t track raw or output data)
2828
*.csv
2929
*.tsv

docker-compose.yml

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
1-
version: "3.9"
2-
1+
# docker-compose.yml (no version)
32
services:
43
api:
54
build: .
65
ports:
76
- "8000:8000"
8-
restart: always
7+
volumes:
8+
- ./mlruns:/app/mlruns
9+
- ./mlruns.db:/app/mlruns.db
10+
environment:
11+
- MLFLOW_TRACKING_URI=http://host.docker.internal:5000
12+
13+
working_dir: /app

dockerfile

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,18 @@
1-
# Use a slim Python base image
2-
FROM python:3.9-slim
1+
FROM python:3.10-slim
32

4-
# Set working directory
53
WORKDIR /app
64

7-
# Install system dependencies (optional: helps with ML libraries)
8-
RUN apt-get update && apt-get install -y \
9-
build-essential \
10-
&& rm -rf /var/lib/apt/lists/*
11-
12-
# Copy and install Python dependencies
5+
# Copy requirements and install deps first for caching
136
COPY requirements.txt .
14-
RUN pip install --no-cache-dir --upgrade pip && \
15-
pip install --no-cache-dir -r requirements.txt
7+
RUN pip install --no-cache-dir -r requirements.txt
8+
9+
# Copy all code and mlruns folder
10+
COPY . /app
1611

17-
# Copy source code
18-
COPY src ./src
12+
# You can also explicitly copy mlruns if it's outside project root
13+
# COPY mlruns /app/mlruns
1914

20-
# Expose FastAPI port
15+
# Expose port 8000 (optional)
2116
EXPOSE 8000
2217

23-
# Start the API
2418
CMD ["uvicorn", "src.api.main:app", "--host", "0.0.0.0", "--port", "8000"]

notebooks/task 1 and 2/load_EDA.ipynb

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -596,6 +596,31 @@
596596
"output_path = r\"C:\\Users\\ABC\\Desktop\\10Acadamy\\Week 5\\Credit-Risk-Probability-Model\\data\\processed\\nan_null.xlsx\"\n",
597597
"df.to_excel(output_path, index=False)\n"
598598
]
599+
},
600+
{
601+
"cell_type": "code",
602+
"execution_count": 2,
603+
"id": "c5366e18",
604+
"metadata": {},
605+
"outputs": [
606+
{
607+
"name": "stdout",
608+
"output_type": "stream",
609+
"text": [
610+
"Tracking URI: file:///C:/Users/ABC/Desktop/10Acadamy/Week 5/Credit-Risk-Probability-Model/mlruns\n"
611+
]
612+
}
613+
],
614+
"source": [
615+
"from pathlib import Path\n",
616+
"import mlflow\n",
617+
"\n",
618+
"mlruns_path = Path(\"C:/Users/ABC/Desktop/10Acadamy/Week 5/Credit-Risk-Probability-Model/mlruns\").absolute()\n",
619+
"mlflow.set_tracking_uri(f\"file:///{mlruns_path.as_posix()}\")\n",
620+
"\n",
621+
"print(\"Tracking URI:\", mlflow.get_tracking_uri())\n",
622+
"\n"
623+
]
599624
}
600625
],
601626
"metadata": {

notebooks/task-6/assigning.ipynb

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "code",
5+
"execution_count": 1,
6+
"id": "ae6b5e80",
7+
"metadata": {},
8+
"outputs": [
9+
{
10+
"name": "stderr",
11+
"output_type": "stream",
12+
"text": [
13+
"C:\\Users\\ABC\\AppData\\Local\\Temp\\ipykernel_16292\\506102746.py:12: FutureWarning: ``mlflow.tracking.client.MlflowClient.transition_model_version_stage`` is deprecated since 2.9.0. Model registry stages will be removed in a future major release. To learn more about the deprecation of model registry stages, see our migration guide here: https://mlflow.org/docs/latest/model-registry.html#migrating-from-stages\n",
14+
" client.transition_model_version_stage(\n"
15+
]
16+
},
17+
{
18+
"data": {
19+
"text/plain": [
20+
"<ModelVersion: aliases=[], creation_timestamp=1751283921891, current_stage='Staging', deployment_job_state=None, description=None, last_updated_timestamp=1751283921936, metrics=None, model_id=None, name='best_model', params=None, run_id='<your_run_id>', run_link=None, source='runs:/<your_run_id>/model', status='READY', status_message=None, tags={}, user_id=None, version=1>"
21+
]
22+
},
23+
"execution_count": 1,
24+
"metadata": {},
25+
"output_type": "execute_result"
26+
}
27+
],
28+
"source": [
29+
"from mlflow.tracking import MlflowClient\n",
30+
"\n",
31+
"client = MlflowClient()\n",
32+
"\n",
33+
"run_id = \"<your_run_id>\" # Run where model was logged\n",
34+
"model_uri = f\"runs:/{run_id}/model\"\n",
35+
"\n",
36+
"model_details = client.create_registered_model(\"best_model\") # Create model in registry if not exists\n",
37+
"mv = client.create_model_version(\"best_model\", model_uri, run_id)\n",
38+
"\n",
39+
"# Transition to staging\n",
40+
"client.transition_model_version_stage(\n",
41+
" name=\"best_model\",\n",
42+
" version=mv.version,\n",
43+
" stage=\"Staging\"\n",
44+
")\n"
45+
]
46+
},
47+
{
48+
"cell_type": "code",
49+
"execution_count": 5,
50+
"id": "5a4642cb",
51+
"metadata": {},
52+
"outputs": [
53+
{
54+
"name": "stdout",
55+
"output_type": "stream",
56+
"text": [
57+
"[<ModelVersion: aliases=[], creation_timestamp=1751336471270, current_stage='Staging', deployment_job_state=None, description=None, last_updated_timestamp=1751336471924, metrics=[], model_id='m-4eb2843bda5d4e60b07f3a7e2dbc78cf', name='credit-risk-model', params={}, run_id='4d40cba2b1ef4491813380e7a40eb4f9', run_link=None, source='models:/m-4eb2843bda5d4e60b07f3a7e2dbc78cf', status='READY', status_message=None, tags={}, user_id=None, version=1>]\n"
58+
]
59+
},
60+
{
61+
"name": "stderr",
62+
"output_type": "stream",
63+
"text": [
64+
"C:\\Users\\ABC\\AppData\\Local\\Temp\\ipykernel_12312\\1572118651.py:9: FutureWarning: ``mlflow.tracking.client.MlflowClient.get_latest_versions`` is deprecated since 2.9.0. Model registry stages will be removed in a future major release. To learn more about the deprecation of model registry stages, see our migration guide here: https://mlflow.org/docs/latest/model-registry.html#migrating-from-stages\n",
65+
" versions = client.get_latest_versions(\"credit-risk-model\")\n"
66+
]
67+
}
68+
],
69+
"source": [
70+
"import mlflow\n",
71+
"from mlflow.tracking import MlflowClient\n",
72+
"\n",
73+
"# Set tracking URI to point to your local mlruns folder\n",
74+
"mlflow.set_tracking_uri(\"file:///C:/Users/ABC/Desktop/10Acadamy/Week 5/Credit-Risk-Probability-Model/src/model/mlruns\")\n",
75+
"\n",
76+
"client = MlflowClient()\n",
77+
"try:\n",
78+
" versions = client.get_latest_versions(\"credit-risk-model\")\n",
79+
" print(versions)\n",
80+
"except Exception as e:\n",
81+
" print(\"Model not found or no versions:\", e)\n",
82+
"\n"
83+
]
84+
},
85+
{
86+
"cell_type": "code",
87+
"execution_count": 6,
88+
"id": "e43e31b8",
89+
"metadata": {},
90+
"outputs": [
91+
{
92+
"name": "stderr",
93+
"output_type": "stream",
94+
"text": [
95+
"C:\\Users\\ABC\\AppData\\Local\\Temp\\ipykernel_12312\\77176695.py:3: FutureWarning: ``mlflow.tracking.client.MlflowClient.get_latest_versions`` is deprecated since 2.9.0. Model registry stages will be removed in a future major release. To learn more about the deprecation of model registry stages, see our migration guide here: https://mlflow.org/docs/latest/model-registry.html#migrating-from-stages\n",
96+
" versions = client.get_latest_versions(\"credit-risk-model\")\n"
97+
]
98+
},
99+
{
100+
"name": "stdout",
101+
"output_type": "stream",
102+
"text": [
103+
"Model versions found: [<ModelVersion: aliases=[], creation_timestamp=1751336471270, current_stage='Staging', deployment_job_state=None, description=None, last_updated_timestamp=1751336471924, metrics=[], model_id='m-4eb2843bda5d4e60b07f3a7e2dbc78cf', name='credit-risk-model', params={}, run_id='4d40cba2b1ef4491813380e7a40eb4f9', run_link=None, source='models:/m-4eb2843bda5d4e60b07f3a7e2dbc78cf', status='READY', status_message=None, tags={}, user_id=None, version=1>]\n",
104+
"Loading model from: C:\\Users\\ABC\\Desktop\\10Acadamy\\Week 5\\Credit-Risk-Probability-Model\\src\\model\\mlruns\\0\\models\\m-4eb2843bda5d4e60b07f3a7e2dbc78cf\\artifacts\\artifacts\\model.pkl\n"
105+
]
106+
},
107+
{
108+
"name": "stderr",
109+
"output_type": "stream",
110+
"text": [
111+
"c:\\Users\\ABC\\Desktop\\10Acadamy\\Week 5\\Credit-Risk-Probability-Model\\env\\Lib\\site-packages\\sklearn\\base.py:440: InconsistentVersionWarning: Trying to unpickle estimator DecisionTreeClassifier from version 1.6.1 when using version 1.7.0. This might lead to breaking code or invalid results. Use at your own risk. For more info please refer to:\n",
112+
"https://scikit-learn.org/stable/model_persistence.html#security-maintainability-limitations\n",
113+
" warnings.warn(\n",
114+
"c:\\Users\\ABC\\Desktop\\10Acadamy\\Week 5\\Credit-Risk-Probability-Model\\env\\Lib\\site-packages\\sklearn\\base.py:440: InconsistentVersionWarning: Trying to unpickle estimator RandomForestClassifier from version 1.6.1 when using version 1.7.0. This might lead to breaking code or invalid results. Use at your own risk. For more info please refer to:\n",
115+
"https://scikit-learn.org/stable/model_persistence.html#security-maintainability-limitations\n",
116+
" warnings.warn(\n"
117+
]
118+
},
119+
{
120+
"name": "stdout",
121+
"output_type": "stream",
122+
"text": [
123+
"Model loaded successfully.\n"
124+
]
125+
}
126+
],
127+
"source": [
128+
"# Check if the model exists\n",
129+
"try:\n",
130+
" versions = client.get_latest_versions(\"credit-risk-model\")\n",
131+
" print(\"Model versions found:\", versions)\n",
132+
"except Exception as e:\n",
133+
" print(\"Error fetching model versions:\", e)\n",
134+
"\n",
135+
"# Load model from MLflow Model Registry (Staging version)\n",
136+
"try:\n",
137+
" model = mlflow.pyfunc.load_model(\"models:/credit-risk-model/Staging\")\n",
138+
" print(\"Model loaded successfully.\")\n",
139+
"except Exception as e:\n",
140+
" print(\"Error loading model:\", e)"
141+
]
142+
},
143+
{
144+
"cell_type": "code",
145+
"execution_count": 10,
146+
"id": "06a72fc9",
147+
"metadata": {},
148+
"outputs": [
149+
{
150+
"name": "stdout",
151+
"output_type": "stream",
152+
"text": [
153+
"Version: 1, Stage: Staging\n"
154+
]
155+
}
156+
],
157+
"source": [
158+
"from mlflow.tracking import MlflowClient\n",
159+
"import mlflow\n",
160+
"from mlflow.tracking import MlflowClient\n",
161+
"# Set tracking URI to point to your local mlruns folder\n",
162+
"mlflow.set_tracking_uri(\"file:///C:/Users/ABC/Desktop/10Acadamy/Week 5/Credit-Risk-Probability-Model/src/model/mlruns\")\n",
163+
"\n",
164+
"client = MlflowClient()\n",
165+
"model_name = \"credit-risk-model\"\n",
166+
"model_versions = client.get_registered_model(model_name)\n",
167+
"\n",
168+
"# Print details about the model versions\n",
169+
"for version in model_versions.latest_versions:\n",
170+
" print(f\"Version: {version.version}, Stage: {version.current_stage}\")"
171+
]
172+
}
173+
],
174+
"metadata": {
175+
"kernelspec": {
176+
"display_name": "env",
177+
"language": "python",
178+
"name": "python3"
179+
},
180+
"language_info": {
181+
"codemirror_mode": {
182+
"name": "ipython",
183+
"version": 3
184+
},
185+
"file_extension": ".py",
186+
"mimetype": "text/x-python",
187+
"name": "python",
188+
"nbconvert_exporter": "python",
189+
"pygments_lexer": "ipython3",
190+
"version": "3.13.2"
191+
}
192+
},
193+
"nbformat": 4,
194+
"nbformat_minor": 5
195+
}

register.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import mlflow
2+
from mlflow.tracking import MlflowClient
3+
from pathlib import Path
4+
5+
# Set tracking URI
6+
mlruns_path = Path(r"C:\Users\ABC\Desktop\10Acadamy\Week 5\Credit-Risk-Probability-Model\mlruns").absolute()
7+
mlflow.set_tracking_uri(f"file:///{mlruns_path.as_posix()}")
8+
# Define model variables
9+
model_name = "best_model"
10+
run_id = "1bed56713a694528a9571bb00576059c"
11+
artifact_path = "models"
12+
model_uri = f"runs:/{run_id}/{artifact_path}"
13+
14+
client = MlflowClient()
15+
16+
# Register the model (will raise exception if already exists)
17+
try:
18+
client.create_registered_model(model_name)
19+
except:
20+
pass # model already exists
21+
22+
# Create new version
23+
mv = client.create_model_version(
24+
name=model_name,
25+
source=model_uri,
26+
run_id=run_id
27+
)
28+
29+
# Transition to Staging
30+
client.transition_model_version_stage(
31+
name=model_name,
32+
version=mv.version,
33+
stage="Staging"
34+
)
35+
36+
print(f"✅ Re-registered as models:/{model_name}/Staging")
37+

src/api/main.py

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,33 @@
1+
import mlflow
12
from fastapi import FastAPI
2-
import mlflow.pyfunc
33
import pandas as pd
4-
from src.api.pydantic_models import CustomerData, PredictionResponse
4+
from .pydantic_models import CustomerData, PredictionResponse
55

66
app = FastAPI()
77

8-
# Load model from MLflow Model Registry
9-
model_name = "best_model"
10-
model_version = 1 # or 'latest'
11-
model_uri = f"models:/{model_name}/{model_version}"
12-
model = mlflow.pyfunc.load_model(model_uri)
8+
# Set MLflow tracking URI to the container's path
9+
mlflow.set_tracking_uri("file:///app/mlruns")
10+
11+
# Load the model from MLflow artifacts inside Docker
12+
model_uri = "file:///app/mlruns/1/models/m-b56f931bfa444e04b71e0ac2ecbe00fb/artifacts"
13+
model = mlflow.sklearn.load_model(model_uri)
1314

1415
@app.post("/predict", response_model=PredictionResponse)
1516
def predict(data: CustomerData):
16-
input_df = pd.DataFrame([data.dict()])
17-
18-
# Add any required preprocessing here if needed
19-
20-
risk_prob = model.predict_proba(input_df)[:, 1][0]
21-
return PredictionResponse(risk_probability=risk_prob)
17+
try:
18+
input_df = pd.DataFrame([data.dict()])
19+
prob_array = model.predict_proba(input_df)
20+
print("🔥 Prediction probabilities:", prob_array)
21+
prob = prob_array[0][1]
22+
return {"risk_probability": prob}
23+
except Exception as e:
24+
return {"detail": f"Prediction failed: {e}"}
25+
26+
#@app.post("/predict", response_model=PredictionResponse)
27+
#def predict(data: CustomerData):
28+
#try:
29+
#input_df = pd.DataFrame([data.dict()])
30+
#prob = model.predict_proba(input_df)[0][1]
31+
#return {"risk_probability": prob}
32+
#except Exception as e:
33+
#return {"detail": f"Prediction failed: {e}"}

0 commit comments

Comments
 (0)