armansakif commited on
Commit
f361235
·
verified ·
1 Parent(s): aca8431

Upload app.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. app.py +98 -0
app.py ADDED
@@ -0,0 +1,98 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI, HTTPException, Depends, status
2
+ from fastapi.security import HTTPBasic, HTTPBasicCredentials
3
+ from pydantic import BaseModel, Field
4
+ import joblib
5
+ import os
6
+ import numpy as np
7
+
8
+ # Initialize FastAPI app
9
+ app = FastAPI(
10
+ title="Iris Classification API",
11
+ description="A REST API for predicting Iris species using a pre-trained scikit-learn model.",
12
+ version="1.0.0"
13
+ )
14
+
15
+ # --- Authentication Setup ---
16
+ security = HTTPBasic()
17
+
18
+ def get_current_username(credentials: HTTPBasicCredentials = Depends(security)):
19
+ correct_username = os.getenv("API_USERNAME")
20
+ correct_password = os.getenv("API_PASSWORD")
21
+
22
+ if not correct_username or not correct_password:
23
+ # This handles cases where secrets aren't set in HF Spaces (shouldn't happen if done correctly)
24
+ raise HTTPException(
25
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
26
+ detail="API credentials not configured on the server."
27
+ )
28
+
29
+ if not (credentials.username == correct_username and credentials.password == correct_password):
30
+ raise HTTPException(
31
+ status_code=status.HTTP_401_UNAUTHORIZED,
32
+ detail="Incorrect username or password",
33
+ headers={"WWW-Authenticate": "Basic"},
34
+ )
35
+ return credentials.username
36
+
37
+ # --- Model Loading ---
38
+ model = None
39
+ class_names = None
40
+
41
+ @app.on_event("startup")
42
+ async def load_artifacts():
43
+ global model, class_names
44
+ model_path = os.path.join("model", "iris_model.joblib")
45
+ class_names_path = os.path.join("model", "iris_class_names.joblib")
46
+
47
+ if not os.path.exists(model_path) or not os.path.exists(class_names_path):
48
+ raise RuntimeError(f"Model or class names file not found. Ensure '{model_path}' and '{class_names_path}' exist.")
49
+
50
+ model = joblib.load(model_path)
51
+ class_names = joblib.load(class_names_path)
52
+ print("Model and class names loaded successfully.")
53
+
54
+ # --- Request Body Model ---
55
+ class IrisFeatures(BaseModel):
56
+ sepal_length: float = Field(..., example=5.1, description="Sepal length in cm")
57
+ sepal_width: float = Field(..., example=3.5, description="Sepal width in cm")
58
+ petal_length: float = Field(..., example=1.4, description="Petal length in cm")
59
+ petal_width: float = Field(..., example=0.2, description="Petal width in cm")
60
+
61
+ # --- API Endpoint ---
62
+ @app.post("/predict", summary="Predict Iris Species", response_description="The predicted Iris species and probabilities.")
63
+ async def predict_iris(
64
+ features: IrisFeatures,
65
+ current_user: str = Depends(get_current_username)
66
+ ):
67
+ if model is None or class_names is None:
68
+ raise HTTPException(
69
+ status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
70
+ detail="Model is not loaded yet. Please try again in a moment."
71
+ )
72
+
73
+ input_data = np.array([[
74
+ features.sepal_length,
75
+ features.sepal_width,
76
+ features.petal_length,
77
+ features.petal_width
78
+ ]])
79
+
80
+ prediction_index = model.predict(input_data)[0]
81
+ predicted_species = class_names[prediction_index]
82
+
83
+ probabilities = model.predict_proba(input_data)[0]
84
+ probabilities_dict = {name: float(prob) for name, prob in zip(class_names, probabilities)}
85
+
86
+ return {
87
+ "predicted_species": predicted_species,
88
+ "prediction_probabilities": probabilities_dict
89
+ }
90
+
91
+ # --- Health Check Endpoint ---
92
+ @app.get("/health", summary="Health Check", response_description="Indicates if the API is running.")
93
+ async def health_check():
94
+ return {"status": "ok", "model_loaded": model is not None}
95
+
96
+ # Note: The uvicorn.run part is for local execution.
97
+ # Hugging Face Spaces will use the CMD in the Dockerfile.
98
+ # For local testing in Colab, you'd use ngrok or colabcode (see below).