github-actions commited on
Commit
83851f4
·
1 Parent(s): 5504462

Auto-deploy from GitHub Actions

Browse files
.gitattributes DELETED
@@ -1,35 +0,0 @@
1
- *.7z filter=lfs diff=lfs merge=lfs -text
2
- *.arrow filter=lfs diff=lfs merge=lfs -text
3
- *.bin filter=lfs diff=lfs merge=lfs -text
4
- *.bz2 filter=lfs diff=lfs merge=lfs -text
5
- *.ckpt filter=lfs diff=lfs merge=lfs -text
6
- *.ftz filter=lfs diff=lfs merge=lfs -text
7
- *.gz filter=lfs diff=lfs merge=lfs -text
8
- *.h5 filter=lfs diff=lfs merge=lfs -text
9
- *.joblib filter=lfs diff=lfs merge=lfs -text
10
- *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
- *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
- *.model filter=lfs diff=lfs merge=lfs -text
13
- *.msgpack filter=lfs diff=lfs merge=lfs -text
14
- *.npy filter=lfs diff=lfs merge=lfs -text
15
- *.npz filter=lfs diff=lfs merge=lfs -text
16
- *.onnx filter=lfs diff=lfs merge=lfs -text
17
- *.ot filter=lfs diff=lfs merge=lfs -text
18
- *.parquet filter=lfs diff=lfs merge=lfs -text
19
- *.pb filter=lfs diff=lfs merge=lfs -text
20
- *.pickle filter=lfs diff=lfs merge=lfs -text
21
- *.pkl filter=lfs diff=lfs merge=lfs -text
22
- *.pt filter=lfs diff=lfs merge=lfs -text
23
- *.pth filter=lfs diff=lfs merge=lfs -text
24
- *.rar filter=lfs diff=lfs merge=lfs -text
25
- *.safetensors filter=lfs diff=lfs merge=lfs -text
26
- saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
- *.tar.* filter=lfs diff=lfs merge=lfs -text
28
- *.tar filter=lfs diff=lfs merge=lfs -text
29
- *.tflite filter=lfs diff=lfs merge=lfs -text
30
- *.tgz filter=lfs diff=lfs merge=lfs -text
31
- *.wasm filter=lfs diff=lfs merge=lfs -text
32
- *.xz filter=lfs diff=lfs merge=lfs -text
33
- *.zip filter=lfs diff=lfs merge=lfs -text
34
- *.zst filter=lfs diff=lfs merge=lfs -text
35
- *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
.github/workflows/ci_deploy_hf.yml ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: CI + Deploy to Hugging Face Space
2
+
3
+ on:
4
+ push:
5
+ branches: ["main"]
6
+ workflow_dispatch:
7
+
8
+ jobs:
9
+ deploy:
10
+ runs-on: ubuntu-latest
11
+
12
+ steps:
13
+ - name: Checkout
14
+ uses: actions/checkout@v4
15
+
16
+ - name: Set up Git
17
+ run: |
18
+ git config --global user.email "actions@github.com"
19
+ git config --global user.name "github-actions"
20
+
21
+ - name: Deploy to Hugging Face Space
22
+ env:
23
+ HF_TOKEN: ${{ secrets.HF_TOKEN }}
24
+ HF_SPACE: ${{ secrets.HF_SPACE }} # like: username/space_name
25
+ run: |
26
+ rm -rf hf_space
27
+ git clone https://user:$HF_TOKEN@huggingface.co/spaces/$HF_SPACE hf_space
28
+
29
+ rsync -av --delete \
30
+ --exclude ".git" \
31
+ --exclude "hf_space" \
32
+ ./ hf_space/
33
+
34
+ cd hf_space
35
+ git add .
36
+ git commit -m "Auto-deploy from GitHub Actions" || echo "No changes to commit"
37
+ git push
Dockerfile CHANGED
@@ -1,18 +1,14 @@
1
- FROM python:3.11-slim
2
-
3
- ENV PYTHONDONTWRITEBYTECODE=1
4
- ENV PYTHONUNBUFFERED=1
5
-
6
- WORKDIR /app
7
-
8
- RUN apt-get update && apt-get install -y --no-install-recommends \
9
- build-essential gcc \
10
- && rm -rf /var/lib/apt/lists/*
11
-
12
- COPY requirements.txt .
13
- RUN pip install --no-cache-dir -r requirements.txt
14
-
15
- COPY . .
16
-
17
- EXPOSE 7860
18
- CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
 
1
+ FROM python:3.11-slim
2
+
3
+ ENV PYTHONDONTWRITEBYTECODE=1
4
+ ENV PYTHONUNBUFFERED=1
5
+
6
+ WORKDIR /app
7
+
8
+ COPY requirements.txt .
9
+ RUN pip install --no-cache-dir -r requirements.txt
10
+
11
+ COPY . .
12
+
13
+ EXPOSE 7860
14
+ CMD ["uvicorn", "app_ml:app", "--host", "0.0.0.0", "--port", "7860"]
 
 
 
 
README.md DELETED
@@ -1,11 +0,0 @@
1
- ---
2
- title: Class10 Ml Fastapi
3
- emoji: 🚀
4
- colorFrom: gray
5
- colorTo: green
6
- sdk: docker
7
- pinned: false
8
- license: mit
9
- ---
10
-
11
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
app.py → app_ml.py RENAMED
@@ -1,112 +1,113 @@
1
- # ---------- Demo Data Example ----------
2
- DEMO_PREDICT_BODY = {
3
- "sepal_length": 5.1,
4
- "sepal_width": 3.5,
5
- "petal_length": 1.4,
6
- "petal_width": 0.2
7
- }
8
-
9
- # app_ml.py
10
- from fastapi import FastAPI, HTTPException
11
- from pydantic import BaseModel, Field
12
- from typing import List, Dict
13
- import os
14
-
15
- import numpy as np
16
- import joblib
17
-
18
- from sklearn.datasets import load_iris
19
- from sklearn.ensemble import RandomForestClassifier
20
- from sklearn.model_selection import train_test_split
21
-
22
- APP_VERSION = "1.0.0"
23
- MODEL_DIR = "/tmp/models"
24
- MODEL_PATH = os.path.join(MODEL_DIR, "iris_rf.joblib")
25
-
26
- app = FastAPI(
27
- title="Class 8 - ML Model Serving (Iris)",
28
- version=APP_VERSION,
29
- description="Serve a scikit-learn model via FastAPI with input validation."
30
- )
31
-
32
- # ---------- Schemas ----------
33
- class IrisFeatures(BaseModel):
34
- sepal_length: float = Field(..., ge=0.0, le=10.0)
35
- sepal_width: float = Field(..., ge=0.0, le=10.0)
36
- petal_length: float = Field(..., ge=0.0, le=10.0)
37
- petal_width: float = Field(..., ge=0.0, le=10.0)
38
-
39
- class PredictResponse(BaseModel):
40
- ok: bool
41
- model_version: str
42
- predicted_label: str
43
- predicted_class_index: int
44
- probabilities: Dict[str, float]
45
-
46
- # ---------- Model utilities ----------
47
- def train_and_save_model(path: str):
48
- os.makedirs(os.path.dirname(path), exist_ok=True)
49
-
50
- iris = load_iris()
51
- X = iris.data
52
- y = iris.target
53
- class_names = iris.target_names
54
-
55
- X_train, X_test, y_train, y_test = train_test_split(
56
- X, y, test_size=0.2, random_state=42, stratify=y
57
- )
58
-
59
- model = RandomForestClassifier(
60
- n_estimators=200,
61
- random_state=42
62
- )
63
- model.fit(X_train, y_train)
64
-
65
- payload = {
66
- "model": model,
67
- "class_names": class_names.tolist(),
68
- "feature_names": iris.feature_names,
69
- "version": APP_VERSION
70
- }
71
- joblib.dump(payload, path)
72
-
73
- def load_model(path: str):
74
- if not os.path.exists(path):
75
- train_and_save_model(path)
76
- return joblib.load(path)
77
-
78
- MODEL_BUNDLE = load_model(MODEL_PATH)
79
- MODEL = MODEL_BUNDLE["model"]
80
- CLASS_NAMES = MODEL_BUNDLE["class_names"]
81
- MODEL_VERSION = MODEL_BUNDLE.get("version", "unknown")
82
-
83
- # ---------- Endpoints ----------
84
- @app.get("/health")
85
- def health():
86
- return {"status": "ok", "model_loaded": True, "model_version": MODEL_VERSION}
87
-
88
- @app.post("/v1/predict", response_model=PredictResponse)
89
- def predict(features: IrisFeatures):
90
- try:
91
- x = np.array([[
92
- features.sepal_length,
93
- features.sepal_width,
94
- features.petal_length,
95
- features.petal_width
96
- ]], dtype=float)
97
-
98
- proba = MODEL.predict_proba(x)[0]
99
- idx = int(np.argmax(proba))
100
- label = CLASS_NAMES[idx]
101
-
102
- prob_map = {CLASS_NAMES[i]: float(proba[i]) for i in range(len(CLASS_NAMES))}
103
-
104
- return PredictResponse(
105
- ok=True,
106
- model_version=MODEL_VERSION,
107
- predicted_label=label,
108
- predicted_class_index=idx,
109
- probabilities=prob_map
110
- )
111
- except Exception:
112
- raise HTTPException(status_code=500, detail="Prediction failed")
 
 
1
+ # ---------- Demo Data Example ----------
2
+ DEMO_PREDICT_BODY = {
3
+ "sepal_length": 5.1,
4
+ "sepal_width": 3.5,
5
+ "petal_length": 1.4,
6
+ "petal_width": 0.5
7
+ }
8
+
9
+ # app_ml.py
10
+ from fastapi import FastAPI, HTTPException
11
+ from pydantic import BaseModel, Field
12
+ from typing import List, Dict
13
+ import os
14
+
15
+ import numpy as np
16
+ import joblib
17
+
18
+ from sklearn.datasets import load_iris
19
+ from sklearn.ensemble import RandomForestClassifier
20
+ from sklearn.model_selection import train_test_split
21
+
22
+ APP_VERSION = "1.0.0"
23
+ MODEL_DIR = "/tmp/models"
24
+ MODEL_PATH = os.path.join(MODEL_DIR, "iris_rf.joblib")
25
+
26
+ app = FastAPI(
27
+ title="Class 8 - ML Model Serving (Iris)",
28
+ version=APP_VERSION,
29
+ description="Serve a scikit-learn model via FastAPI with input validation."
30
+ )
31
+
32
+ # ---------- Schemas ----------
33
+ class IrisFeatures(BaseModel):
34
+ sepal_length: float = Field(..., ge=0.0, le=10.0)
35
+ sepal_width: float = Field(..., ge=0.0, le=10.0)
36
+ petal_length: float = Field(..., ge=0.0, le=10.0)
37
+ petal_width: float = Field(..., ge=0.0, le=10.0)
38
+
39
+ class PredictResponse(BaseModel):
40
+ ok: bool
41
+ model_version: str
42
+ predicted_label: str
43
+ predicted_class_index: int
44
+ probabilities: Dict[str, float]
45
+
46
+ # ---------- Model utilities ----------
47
+ def train_and_save_model(path: str):
48
+ os.makedirs(os.path.dirname(path), exist_ok=True)
49
+
50
+ iris = load_iris()
51
+ X = iris.data
52
+ y = iris.target
53
+ class_names = iris.target_names
54
+
55
+ X_train, X_test, y_train, y_test = train_test_split(
56
+ X, y, test_size=0.2, random_state=42, stratify=y
57
+ )
58
+
59
+ model = RandomForestClassifier(
60
+ n_estimators=200,
61
+ random_state=42
62
+ )
63
+ model.fit(X_train, y_train)
64
+
65
+ payload = {
66
+ "model": model,
67
+ "class_names": class_names.tolist(),
68
+ "feature_names": iris.feature_names,
69
+ "version": APP_VERSION
70
+ }
71
+ joblib.dump(payload, path)
72
+
73
+ def load_model(path: str):
74
+ if not os.path.exists(path):
75
+ train_and_save_model(path)
76
+ return joblib.load(path)
77
+
78
+ MODEL_BUNDLE = load_model(MODEL_PATH)
79
+ MODEL = MODEL_BUNDLE["model"]
80
+ CLASS_NAMES = MODEL_BUNDLE["class_names"]
81
+ MODEL_VERSION = MODEL_BUNDLE.get("version", "unknown")
82
+
83
+ # ---------- Endpoints ----------
84
+ @app.get("/health")
85
+ def health():
86
+ return {"status": "ok", "model_loaded": True, "model_version": MODEL_VERSION}
87
+
88
+ @app.post("/v1/predict", response_model=PredictResponse)
89
+ def predict(features: IrisFeatures):
90
+ try:
91
+ x = np.array([[
92
+ features.sepal_length,
93
+ features.sepal_width,
94
+ features.petal_length,
95
+ features.petal_width
96
+ ]], dtype=float)
97
+
98
+ proba = MODEL.predict_proba(x)[0]
99
+ idx = int(np.argmax(proba))
100
+ label = CLASS_NAMES[idx]
101
+
102
+ prob_map = {CLASS_NAMES[i]: float(proba[i]) for i in range(len(CLASS_NAMES))}
103
+
104
+ return PredictResponse(
105
+ ok=True,
106
+ model_version=MODEL_VERSION,
107
+ predicted_label=label,
108
+ predicted_class_index=idx,
109
+ probabilities=prob_map
110
+ )
111
+ except Exception:
112
+ raise HTTPException(status_code=500, detail="Prediction failed")
113
+
render.yaml ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ services:
2
+ - type: web
3
+ name: class10-ml-api
4
+ runtime: docker
5
+ plan: free
6
+ region: oregon
7
+ healthCheckPath: /health
8
+ envVars:
9
+ - key: PYTHONUNBUFFERED
10
+ value: "1"
11
+ - key: PYTHONDONTWRITEBYTECODE
12
+ value: "1"
13
+ dockerCommand: >
14
+ uvicorn app_ml:app --host 0.0.0.0 --port $PORT
requirements.txt CHANGED
@@ -2,4 +2,4 @@ fastapi
2
  uvicorn[standard]
3
  numpy
4
  scikit-learn
5
- joblib
 
2
  uvicorn[standard]
3
  numpy
4
  scikit-learn
5
+ joblib