diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml new file mode 100644 index 0000000..af458b6 --- /dev/null +++ b/.github/workflows/workflow.yml @@ -0,0 +1,32 @@ +name: Build and Deploy API + +on: + push: + branches: [main] + +jobs: + build-and-run: + runs-on: ubuntu-latest + + steps: + - name: Checkout repo + uses: actions/checkout@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + + - name: Login to DockerHub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + + - name: Build and push Docker image + uses: docker/build-push-action@v4 + with: + context: ./deployment/api + push: false + tags: sentiment_api:latest + + - name: Run docker-compose + run: docker-compose up -d diff --git a/deployment/api/.dockerignore b/deployment/api/.dockerignore new file mode 100644 index 0000000..da47ce8 --- /dev/null +++ b/deployment/api/.dockerignore @@ -0,0 +1,14 @@ +# Ignorer les caches Python +__pycache__/ +*.pyc + +# Ignorer les artefacts temporaires +downloaded_artifacts/ +mlruns/ +logs/ + +# Ignorer les tests unitaires +tests/ + +# Ignorer les variables d'environnement. Elles seront plutôt chargées dans la commande docker run `docker run --env-file ./deployment/api/.env -p 8000:8000 sentiment-api` en développement local, et via des variables définies dans le yaml +*.env diff --git a/deployment/api/Dockerfile b/deployment/api/Dockerfile new file mode 100644 index 0000000..596420f --- /dev/null +++ b/deployment/api/Dockerfile @@ -0,0 +1,21 @@ +# Utiliser une image légère Python 3.12 +FROM python:3.12-slim + +# Définir le dossier de travail +WORKDIR /app + +# Copier uniquement requirements.txt d'abord (pour optimiser le cache Docker) +COPY requirements.txt . + +# Installer les dépendances +RUN pip install --no-cache-dir -r requirements.txt + +# Copier le reste du code source +COPY . . + +# Exposer les ports 8000 (fastAPI) et 8501 (streamlit) +EXPOSE 8000 8501 + +RUN chmod +x /app/start.sh + +CMD ["/app/start.sh"] diff --git a/deployment/api/__pycache__/main.cpython-312.pyc b/deployment/api/__pycache__/main.cpython-312.pyc index 08c455a..18201e0 100644 Binary files a/deployment/api/__pycache__/main.cpython-312.pyc and b/deployment/api/__pycache__/main.cpython-312.pyc differ diff --git a/deployment/api/docker-compose.yml b/deployment/api/docker-compose.yml new file mode 100644 index 0000000..76fec07 --- /dev/null +++ b/deployment/api/docker-compose.yml @@ -0,0 +1,31 @@ +version: "3.9" +services: + api: + build: + context: ./deployment/api + dockerfile: Dockerfile + container_name: sentiment_api + ports: + - "8000:8000" # API FastAPI + - "8501:8501" # Streamlit + environment: + MLFLOW_TRACKING_URI: "${MLFLOW_TRACKING_URI}" # ex: http://mlflow:5000 + MLFLOW_S3_ENDPOINT_URL: "${MLFLOW_S3_ENDPOINT_URL}" # ex: url MinIO ou S3 + AWS_ACCESS_KEY_ID: "${AWS_ACCESS_KEY_ID}" # clé ID S3 + AWS_SECRET_ACCESS_KEY: "${AWS_SECRET_ACCESS_KEY}" # clé secrète S3 + + SMTP_SERVER: "${SMTP_SERVER}" # serveur SMTP + SMTP_PORT: "${SMTP_PORT}" # port SMTP (ex: 587) + SMTP_EMAIL: "${SMTP_EMAIL}" # email expéditeur + SMTP_PASSWORD: "${SMTP_PASSWORD}" # mot de passe SMTP + SMTP_FROM_ALIAS: "${SMTP_FROM_ALIAS}" # alias (optionnel) + ADMIN_EMAIL: "${ADMIN_EMAIL}" # email admin destinataire + +# only if you have a reverse proxy server +# networks: +# - swag + restart: always +# only if you have a reverse proxy server +# networks: +# swag: +# external: true diff --git a/deployment/api/email_service.py b/deployment/api/email_service.py index 58dfe5c..a1942db 100644 --- a/deployment/api/email_service.py +++ b/deployment/api/email_service.py @@ -11,10 +11,10 @@ def send_error_report_email(error_reports): """ smtp_server = os.getenv("SMTP_SERVER") smtp_port = int(os.getenv("SMTP_PORT", 587)) - smtp_email = os.getenv("SMTP_EMAIL") + smtp_email = os.getenv("SMTP_EMAIL") # main email address. If smt_from_alias is not, it will also be the mail sender smtp_password = os.getenv("SMTP_PASSWORD") - smtp_from_alias = os.getenv("SMTP_FROM_ALIAS", smtp_email) - admin_email = os.getenv("ADMIN_EMAIL") + smtp_from_alias = os.getenv("SMTP_FROM_ALIAS", smtp_email) # mail sender if not none (alias) + admin_email = os.getenv("ADMIN_EMAIL") # this one will receive the mail if not all([smtp_server, smtp_email, smtp_password, admin_email]): print("❌ Configuration email manquante dans .env") @@ -23,7 +23,11 @@ def send_error_report_email(error_reports): try: # Créer le message msg = MIMEMultipart() - msg['From'] = f"Air Paradis Monitor <{smtp_from_alias}>" + if smtp_from_alias is not None: + smtp_from = smtp_from_alias + else: + smtp_from = smtp_email + msg['From'] = f"Air Paradis Monitor <{smtp_from}>" msg['To'] = admin_email msg['Subject'] = f"🚨 Rapport d'erreurs - Prédictions sentiments ({datetime.now().strftime('%d/%m/%Y %H:%M')})" @@ -86,7 +90,7 @@ Un nouveau rapport d'erreurs a été généré pour l'API de prédiction de sent --- Cordialement, 🤖 API Air Paradis - Système de monitoring automatique -📧 Envoyé depuis : """ + smtp_from_alias +📧 Envoyé depuis : """ + smtp_from # Ajouter le corps du message msg.attach(MIMEText(body, 'plain', 'utf-8')) @@ -97,7 +101,7 @@ Cordialement, server.login(smtp_email, smtp_password) server.send_message(msg) - print(f"✅ Email de rapport envoyé avec succès depuis {smtp_from_alias}") + print(f"✅ Email de rapport envoyé avec succès depuis {smtp_from}") print(f"📊 JSON intégré dans le corps du message") return True diff --git a/deployment/api/main.py b/deployment/api/main.py index a123e43..9220457 100644 --- a/deployment/api/main.py +++ b/deployment/api/main.py @@ -14,7 +14,7 @@ from tensorflow.keras.preprocessing.sequence import pad_sequences PARENT_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../")) sys.path.append(PARENT_DIR) # 🔐 Charger les variables d'environnement (.env à deux niveaux au-dessus) -load_dotenv(dotenv_path=os.path.join(PARENT_DIR, ".env")) +load_dotenv() # 🔁 Import de la fonction de prétraitement (LOCAL) @@ -69,6 +69,7 @@ def load_model_and_artifacts(model_name=MODEL_NAME, stage=STAGE): # Déterminer les chemins des artefacts local_dir = "./downloaded_artifacts" os.makedirs(local_dir, exist_ok=True) + print("Contenu de downloaded_artifacts :", os.listdir(local_dir)) base_path = "local_artifacts" tokenizer_file = "tokenizer.pickle" diff --git a/deployment/api/requirements.txt b/deployment/api/requirements.txt index f7d0971..fba6645 100644 --- a/deployment/api/requirements.txt +++ b/deployment/api/requirements.txt @@ -9,6 +9,7 @@ requests==2.32.4 # === Machine Learning === tensorflow==2.19.0 mlflow==3.1.1 +boto3==1.39.2 numpy==2.1.3 pandas==2.3.0 # === Traitement de texte === diff --git a/deployment/api/start.sh b/deployment/api/start.sh new file mode 100644 index 0000000..ee38fb0 --- /dev/null +++ b/deployment/api/start.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +# Lancer Streamlit en arrière-plan +streamlit run app.py --server.port 8501 --server.address 0.0.0.0 & + +# Lancer Uvicorn en premier plan (Docker "suit" ce processus) +uvicorn main:app --host 0.0.0.0 --port 8000 diff --git a/deployment/api/tests/test_email_service.py b/deployment/api/tests/test_email_service.py index 4a607a7..fe8a156 100644 --- a/deployment/api/tests/test_email_service.py +++ b/deployment/api/tests/test_email_service.py @@ -6,30 +6,6 @@ Created on Sat Jul 5 15:03:24 2025 @author: madfuckinman """ -# # test_alias_email.py -# import os -# from dotenv import load_dotenv -# from email_service import send_error_report_email - -# load_dotenv() - -# print("🔧 Configuration Email:") -# print(f"Serveur SMTP: {os.getenv('SMTP_SERVER')}") -# print(f"Authentification: {os.getenv('SMTP_EMAIL')}") -# print(f"Expéditeur (alias): {os.getenv('SMTP_FROM_ALIAS')}") -# print(f"Destinataire: {os.getenv('ADMIN_EMAIL')}") - -# # Test -# test_reports = { -# "Service client décevant": "positif", -# "J'adore cette compagnie": "negatif", -# "Vol annulé sans prévenir": "positif" -# } - -# print("\n📧 Test d'envoi depuis l'alias...") -# result = send_error_report_email(test_reports) -# print(f"Résultat : {'✅ Succès' if result else '❌ Échec'}") - import os import sys from dotenv import load_dotenv