Sadeep Sachintha commited on
Commit
586f9cf
·
1 Parent(s): bf2421d

successfully created the complete codebase for the Sinhala NLP Deployment project

Browse files
Files changed (8) hide show
  1. .dockerignore +12 -0
  2. .github/workflows/deploy.yml +44 -0
  3. Dockerfile +32 -0
  4. README.md +0 -0
  5. app/main.py +36 -0
  6. app/model.py +25 -0
  7. requirements.txt +7 -0
  8. tests/test_api.py +20 -0
.dockerignore ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ __pycache__/
2
+ .pytest_cache/
3
+ .git/
4
+ .github/
5
+ tests/
6
+ README.md
7
+ *.pyc
8
+ *.pyo
9
+ *.pyd
10
+ .Python
11
+ env/
12
+ venv/
.github/workflows/deploy.yml ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: Deploy to Hugging Face Spaces
2
+
3
+ on:
4
+ push:
5
+ branches: [ "main" ]
6
+
7
+ jobs:
8
+ build-and-test:
9
+ runs-on: ubuntu-latest
10
+ steps:
11
+ - uses: actions/checkout@v3
12
+
13
+ - name: Set up Python
14
+ uses: actions/setup-python@v4
15
+ with:
16
+ python-version: '3.10'
17
+
18
+ - name: Install dependencies
19
+ run: |
20
+ python -m pip install --upgrade pip
21
+ pip install -r requirements.txt
22
+
23
+ - name: Run Tests
24
+ run: |
25
+ pytest tests/
26
+
27
+ deploy-to-hf:
28
+ needs: build-and-test
29
+ runs-on: ubuntu-latest
30
+ steps:
31
+ - uses: actions/checkout@v3
32
+ with:
33
+ fetch-depth: 0 # Fetch all history for push
34
+
35
+ - name: Push to Hugging Face Spaces
36
+ env:
37
+ HF_TOKEN: ${{ secrets.HF_TOKEN }}
38
+ HF_USERNAME: ${{ secrets.HF_USERNAME }}
39
+ HF_SPACE_NAME: ${{ secrets.HF_SPACE_NAME }}
40
+ run: |
41
+ git config --global user.email "actions@github.com"
42
+ git config --global user.name "GitHub Actions"
43
+ git remote add hf https://$HF_USERNAME:$HF_TOKEN@huggingface.co/spaces/$HF_USERNAME/$HF_SPACE_NAME
44
+ git push -f hf main
Dockerfile ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Use an official Python runtime as a parent image
2
+ FROM python:3.10-slim
3
+
4
+ # Set the working directory in the container
5
+ WORKDIR /code
6
+
7
+ # Copy the requirements file into the container
8
+ COPY ./requirements.txt /code/requirements.txt
9
+
10
+ # Install any needed packages specified in requirements.txt
11
+ RUN pip install --no-cache-dir -r /code/requirements.txt
12
+
13
+ # Create a non-root user for security (Hugging Face Spaces requirement)
14
+ RUN useradd -m -u 1000 user
15
+ USER user
16
+ ENV HOME=/home/user \
17
+ PATH=/home/user/.local/bin:$PATH
18
+
19
+ WORKDIR $HOME/app
20
+
21
+ # Copy the current directory contents into the container
22
+ COPY --chown=user . $HOME/app
23
+
24
+ # Pre-download the Hugging Face model during the build stage
25
+ # This saves time and bandwidth during container startup
26
+ RUN python -c "from transformers import pipeline; pipeline('sentiment-analysis', model='keshan/sinhala-sentiment-analysis')"
27
+
28
+ # Make port 7860 available to the world outside this container
29
+ EXPOSE 7860
30
+
31
+ # Run main.py when the container launches
32
+ CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "7860"]
README.md CHANGED
Binary files a/README.md and b/README.md differ
 
app/main.py ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI, HTTPException
2
+ from pydantic import BaseModel
3
+ from app.model import predict_sentiment
4
+ import logging
5
+
6
+ logging.basicConfig(level=logging.INFO)
7
+ logger = logging.getLogger(__name__)
8
+
9
+ app = FastAPI(
10
+ title="Sinhala Sentiment Analysis API",
11
+ description="A robust REST API for predicting sentiment of Sinhala text.",
12
+ version="1.0.0"
13
+ )
14
+
15
+ class SentimentRequest(BaseModel):
16
+ text: str
17
+
18
+ class SentimentResponse(BaseModel):
19
+ label: str
20
+ score: float
21
+
22
+ @app.get("/")
23
+ def read_root():
24
+ return {"message": "Welcome to the Sinhala Sentiment Analysis API. Use POST /predict to analyze text."}
25
+
26
+ @app.post("/predict", response_model=SentimentResponse)
27
+ def predict(request: SentimentRequest):
28
+ if not request.text or len(request.text.strip()) == 0:
29
+ raise HTTPException(status_code=400, detail="Text cannot be empty.")
30
+
31
+ try:
32
+ result = predict_sentiment(request.text)
33
+ return result
34
+ except Exception as e:
35
+ logger.error(f"Prediction error: {e}")
36
+ raise HTTPException(status_code=500, detail="Internal server error during prediction.")
app/model.py ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from transformers import pipeline
2
+ import logging
3
+
4
+ logger = logging.getLogger(__name__)
5
+
6
+ # Using a robust Sinhala sentiment analysis model from Hugging Face
7
+ MODEL_NAME = "keshan/sinhala-sentiment-analysis"
8
+
9
+ try:
10
+ logger.info(f"Loading model {MODEL_NAME}...")
11
+ sentiment_pipeline = pipeline("sentiment-analysis", model=MODEL_NAME)
12
+ logger.info("Model loaded successfully.")
13
+ except Exception as e:
14
+ logger.error(f"Error loading model: {e}")
15
+ sentiment_pipeline = None
16
+
17
+ def predict_sentiment(text: str):
18
+ if not sentiment_pipeline:
19
+ raise RuntimeError("Model pipeline is not initialized.")
20
+
21
+ result = sentiment_pipeline(text)[0]
22
+ return {
23
+ "label": result["label"],
24
+ "score": float(result["score"])
25
+ }
requirements.txt ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ fastapi>=0.110.0
2
+ uvicorn>=0.28.0
3
+ transformers>=4.38.2
4
+ torch>=2.2.1
5
+ pydantic>=2.6.4
6
+ pytest>=8.1.1
7
+ httpx>=0.27.0
tests/test_api.py ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi.testclient import TestClient
2
+ from app.main import app
3
+
4
+ client = TestClient(app)
5
+
6
+ def test_read_root():
7
+ response = client.get("/")
8
+ assert response.status_code == 200
9
+ assert "Welcome" in response.json()["message"]
10
+
11
+ def test_predict_sentiment_positive():
12
+ # "This is a very good creation." in Sinhala
13
+ response = client.post("/predict", json={"text": "මෙය ඉතා හොඳ නිර්මාණයක්."})
14
+ assert response.status_code == 200
15
+ assert "label" in response.json()
16
+ assert "score" in response.json()
17
+
18
+ def test_predict_sentiment_empty():
19
+ response = client.post("/predict", json={"text": ""})
20
+ assert response.status_code == 400