12 jours (jour 10) – Enregistrement des pièces jointes aux espaces
Bienvenue au Jour 10 des 12 Jours de DigitalOcean ! Hier, nous avons appris à votre application à extraire des informations du contenu des e-mails à l'aide de l'agent GenAI de DigitalOcean. Ce fut une étape énorme, mais soyons réalistes : les reçus et les factures ne figurent pas toujours dans le corps de l’e-mail. Le plus souvent, ce sont des attachements.
Aujourd’hui, nous allons gérer cela. Nous apprendrons à votre application comment extraire ces pièces jointes, les enregistrer en toute sécurité sur DigitalOcean Spaces et générer des URL publiques pour chaque fichier. Ces URL seront éventuellement stockées dans notre base de données, vous permettant de prévisualiser les fichiers joints lors de l'examen des dépenses.
Allons-y.
🚀 Ce que vous apprendrez
À la fin de la session d’aujourd’hui, vous saurez comment :
- Créez un espace DigitalOcean pour stocker les pièces jointes.
- Extrayez et décodez les pièces jointes codées en Base64 des e-mails Postmark.
- Téléchargez des pièces jointes sur DigitalOcean Spaces à l’aide de boto3.
- Générez des noms de fichiers uniques avec uuid pour éviter les écrasements.
- Orchestrez l’ensemble du flux de travail pour gérer plusieurs pièces jointes de manière transparente.
🛠 Ce dont vous aurez besoin
Pour tirer le meilleur parti de ce didacticiel, nous supposons ce qui suit :
- Une application Flask déjà déployée sur DigitalOcean : Si vous n'avez pas encore déployé d'application Flask, vous pouvez suivre les instructions du Jour 7 - Création et déploiement du processeur de reçus par e-mail.
- Postmark configuré pour les tests d'e-mails : pour tester le pipeline de traitement des e-mails au reçu, vous devez configurer Postmark pour transférer les e-mails vers votre application Flask. Voir Jour 8 - Connexion du cachet de la poste à votre application Flask pour un guide étape par étape.
- Configuration des espaces DigitalOcean : nous stockerons les pièces jointes traitées dans un espace DigitalOcean. Si vous n'avez pas encore d'espace, nous vous guiderons dans la création d'un espace dans ce didacticiel.
<$> [info] Remarque : Même si vous n'avez pas tout configuré, vous apprendrez quand même à :
Créez un espace DigitalOcean pour stocker les pièces jointes.
Décodez par programmation les pièces jointes codées en Base64.
Téléchargez des fichiers sur DigitalOcean Spaces en utilisant
boto3
.Intégrez de manière transparente la gestion des pièces jointes dans votre application Flask.
<$>
Étape 1 : Créer un espace DigitalOcean
Tout d’abord, nous avons besoin d’un endroit pour stocker nos pièces jointes. DigitalOcean Spaces est un service de stockage d'objets, parfait pour gérer en toute sécurité des fichiers tels que des reçus et des factures. Il est évolutif, sécurisé et s’intègre parfaitement à notre application.
Créer l'espace
Connectez-vous au tableau de bord DigitalOcean et cliquez sur Spaces Object Storage.
Cliquez ensuite sur Créer un compartiment.
Choisissez une Région proche de vos utilisateurs (par exemple,
nyc3
pour New York).Nommez votre espace (par exemple,
e-mail-reçus
)
Cela créera votre bucket nommé email-receipts
disponible à une URL telle que https://email-receipts.nyc3.digitaloceanspaces.com
Générer des clés d'accès
Pour interagir avec votre espace par programmation (par exemple, via boto3
), vous aurez besoin d'une clé d'accès et d'une clé secrète.
Ouvrez votre espace, cliquez sur Paramètres et faites défiler jusqu'à Clés d'accès.
Cliquez sur Créer une clé d'accès.
Définissez Autorisations sur Toutes les autorisations afin que notre application puisse lire, écrire et supprimer des fichiers.
-
Nommez la clé (ou utilisez la clé par défaut) et cliquez sur Créer une clé d'accès.
Enregistrez la clé d'accès et la clé secrète : c'est la seule fois où vous verrez la clé secrète !
Mettre à jour les variables d'environnement
Dans le tableau de bord DigitalOcean App Platform :
Accédez à Paramètres > Variables d'environnement.
Ajoutez ce qui suit :
SPACES_ACCESS_KEY
: votre identifiant de clé d'accès aux espaces.
SPACES_SECRET_KEY
: votre clé secrète Spaces.SPACES_BUCKET_NAME
: le nom de votre espace (par exemple,reçus par e-mail
).SPACES_REGION
: la région de votre espace (par exemple,nyc3
).
Étape 2 : Traitement et téléchargement des pièces jointes vers les espaces DigitalOcean
Pour gérer les pièces jointes dans votre application, nous mettrons à jour notre app.py
et écrirons quelques nouvelles fonctions. Chaque fonction répond à un objectif spécifique, du décodage des pièces jointes à leur téléchargement sur DigitalOcean Spaces. Passons en revue ces éléments un par un.
Remarque : Si vous n'avez pas encore configuré l'application, suivez les instructions du Jour 7 - Création et déploiement du processeur de reçus par e-mail pour le créer et le déployer sur la plate-forme d'applications de DigitalOcean.
Décoder et enregistrer les pièces jointes
Postmark envoie les pièces jointes sous forme de données codées en Base64 dans la charge utile JSON. La première étape consiste à décoder ces données et à les enregistrer localement à l'aide de la bibliothèque base64
de Python. Cette fonction garantit que chaque fichier obtient un nom unique à l'aide de la bibliothèque uuid
.
Qu'est-ce que Base64 ? C'est comme un traducteur de fichiers binaires (comme les PDF). Il les convertit dans un format de texte brut pouvant être envoyé en toute sécurité sur le Web. Une fois que nous l’avons décodé en binaire, nous pouvons le gérer comme n’importe quel fichier ordinaire.
Où les fichiers sont-ils enregistrés ? : nous enregistrerons temporairement les fichiers décodés dans /tmp
. Il s'agit d'un répertoire de stockage à court terme disponible sur la plupart des systèmes. Considérez-le comme un bloc-notes : il est parfait pour une utilisation à court terme et tout est effacé une fois que l'application cesse de fonctionner.
Voici la fonction pour décoder la pièce jointe, s'assurer que le nom du fichier est unique (grâce à uuid
) et l'enregistrer dans /tmp
.
import os
import base64
import uuid
def decode_and_save_attachment(attachment):
"""Decode base64-encoded attachment and save it locally with a unique name."""
file_name = attachment.get("Name")
encoded_content = attachment.get("Content")
if not file_name or not encoded_content:
logging.warning("Invalid attachment, skipping.")
return None
unique_file_name = f"{uuid.uuid4()}_{file_name}"
file_path = os.path.join("/tmp", unique_file_name)
try:
with open(file_path, "wb") as file:
file.write(base64.b64decode(encoded_content))
logging.info(f"Attachment saved locally: {file_path}")
return file_path
except Exception as e:
logging.error(f"Failed to decode and save attachment {file_name}: {e}")
return None
Télécharger des pièces jointes sur les espaces DigitalOcean
Maintenant que nous avons décodé et enregistré les fichiers, l'étape suivante consiste à les télécharger sur DigitalOcean Spaces. Nous utiliserons boto3
, un puissant SDK Python permettant de travailler avec des API compatibles AWS, pour gérer le téléchargement. Spaces fonctionne comme un compartiment S3, c'est donc un ajustement parfait.
Cette fonction télécharge le fichier sur votre espace et renvoie une URL publique.
import boto3
def upload_attachment_to_spaces(file_path):
"""Upload a file to DigitalOcean Spaces and return its public URL."""
file_name = os.path.basename(file_path)
object_name = f"email-receipt-processor/{file_name}"
try:
s3_client.upload_file(file_path, SPACES_BUCKET, object_name, ExtraArgs={"ACL": "public-read"})
file_url = f"https://{SPACES_BUCKET}.{SPACES_REGION}.cdn.digitaloceanspaces.com/{object_name}"
logging.info(f"Attachment uploaded to Spaces: {file_url}")
return file_url
except Exception as e:
logging.error(f"Failed to upload attachment {file_name} to Spaces: {e}")
return None
Traiter plusieurs pièces jointes
Rassemblons tout cela. Cette fonction orchestre tout :
- Décode chaque pièce jointe.
- Le télécharge sur Spaces.
- Collecte les URL des fichiers téléchargés.
def process_attachments(attachments):
"""Process all attachments and return their URLs."""
attachment_urls = []
for attachment in attachments:
file_path = decode_and_save_attachment(attachment)
if file_path:
file_url = upload_attachment_to_spaces(file_path)
if file_url:
attachment_urls.append({"file_name": os.path.basename(file_path), "url": file_url})
os.remove(file_path) # Clean up local file
return attachment_urls
Mettre à jour la route /inbound
Enfin, mettez à jour la route /inbound
pour inclure la gestion des pièces jointes. Cette route gérera désormais le traitement du contenu des e-mails, le décodage et le téléchargement des pièces jointes, ainsi que le renvoi de la réponse finale.
@app.route('/inbound', methods=['POST'])
def handle_inbound_email():
"""Process inbound emails and return extracted JSON."""
logging.info("Received inbound email request.")
data = request.json
email_content = data.get("TextBody", "")
attachments = data.get("Attachments", [])
if not email_content:
logging.error("No email content provided.")
return jsonify({"error": "No email content provided"}), 400
extracted_data = extract_text_from_email(email_content)
attachment_urls = process_attachments(attachments)
response_data = {
"extracted_data": extracted_data,
"attachments": attachment_urls
}
# Log the final combined data
logging.info("Final Response Data: %s", response_data)
return jsonify(response_data)
Code complet final
Voici le fichier app.py
complet avec toutes les mises à jour :
from flask import Flask, request, jsonify
import os
import base64
import uuid
import boto3
from dotenv import load_dotenv
from openai import OpenAI
import logging
Load environment variables
load_dotenv()
Initialize Flask app
app = Flask(__name__)
Configure logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
Initialize DigitalOcean GenAI client
SECURE_AGENT_KEY = os.getenv("SECURE_AGENT_KEY")
AGENT_BASE_URL = os.getenv("AGENT_BASE_URL")
AGENT_ENDPOINT = f"{AGENT_BASE_URL}/api/v1/"
client = OpenAI(base_url=AGENT_ENDPOINT, api_key=SECURE_AGENT_KEY)
DigitalOcean Spaces credentials
SPACES_ACCESS_KEY = os.getenv("SPACES_ACCESS_KEY")
SPACES_SECRET_KEY = os.getenv("SPACES_SECRET_KEY")
SPACES_BUCKET = os.getenv("SPACES_BUCKET_NAME")
SPACES_REGION = os.getenv("SPACES_REGION")
SPACES_ENDPOINT = f"https://{SPACES_BUCKET}.{SPACES_REGION}.digitaloceanspaces.com"
Initialize DigitalOcean Spaces client
session = boto3.session.Session()
s3_client = session.client(
's3',
region_name=SPACES_REGION,
endpoint_url=SPACES_ENDPOINT,
aws_access_key_id=SPACES_ACCESS_KEY,
aws_secret_access_key=SPACES_SECRET_KEY
)
def extract_text_from_email(email_content):
"""Extract relevant details from the email content using DigitalOcean GenAI."""
logging.debug("Extracting details from email content.")
prompt = (
"Extract the following details from the email:\n"
"- Date of transaction\n"
"- Amount\n"
"- Currency\n"
"- Vendor name\n\n"
f"Email content:\n{email_content}\n\n"
"Ensure the output is in JSON format with keys: date, amount, currency, vendor."
)
response = client.chat.completions.create(
model="your-model-id", # Replace with your GenAI model ID
messages=[{"role": "user", "content": prompt}]
)
logging.debug("GenAI processing completed.")
return response.choices[0].message.content
def decode_and_save_attachment(attachment):
"""Decode base64-encoded attachment and save it locally with a unique name."""
file_name = attachment.get("Name")
encoded_content = attachment.get("Content")
if not file_name or not encoded_content:
logging.warning("Invalid attachment, skipping.")
return None
unique_file_name = f"{uuid.uuid4()}_{file_name}"
file_path = os.path.join("/tmp", unique_file_name)
try:
with open(file_path, "wb") as file:
file.write(base64.b64decode(encoded_content))
logging.info(f"Attachment saved locally: {file_path}")
return file_path
except Exception as e:
logging.error(f"Failed to decode and save attachment {file_name}: {e}")
return None
def upload_attachment_to_spaces(file_path):
"""Upload a file to DigitalOcean Spaces and return its public URL."""
file_name = os.path.basename(file_path)
object_name = f"email-receipt-processor/{file_name}"
try:
s3_client.upload_file(file_path, SPACES_BUCKET, object_name, ExtraArgs={"ACL": "public-read"})
file_url = f"https://{SPACES_BUCKET}.{SPACES_REGION}.cdn.digitaloceanspaces.com/{object_name}"
logging.info(f"Attachment uploaded to Spaces: {file_url}")
return file_url
except Exception as e:
logging.error(f"Failed to upload attachment {file_name} to Spaces: {e}")
return None
def process_attachments(attachments):
"""Process all attachments and return their URLs."""
attachment_urls = []
for attachment in attachments:
file_path = decode_and_save_attachment(attachment)
if file_path:
file_url = upload_attachment_to_spaces(file_path)
if file_url:
attachment_urls.append({"file_name": os.path.basename(file_path), "url": file_url})
os.remove(file_path) # Clean up local file
return attachment_urls
@app.route('/inbound', methods=['POST'])
def handle_inbound_email():
"""Process inbound emails and return extracted JSON."""
logging.info("Received inbound email request.")
data = request.json
email_content = data.get("TextBody", "")
attachments = data.get("Attachments", [])
if not email_content:
logging.error("No email content provided.")
return jsonify({"error": "No email content provided"}), 400
extracted_data = extract_text_from_email(email_content)
attachment_urls = process_attachments(attachments)
response_data = {
"extracted_data": extracted_data,
"attachments": attachment_urls
}
# Log the final combined data
logging.info("Final Response Data: %s", response_data)
return jsonify(response_data)
if __name__ == "__main__":
logging.info("Starting Flask application.")
app.run(port=5000)
Étape 3 : Déployer sur DigitalOcean
Pour déployer l'application Flask mise à jour, suivez les étapes du jour 7. Voici un bref résumé :
Poussez votre code mis à jour vers GitHub : après avoir apporté les modifications nécessaires à votre application Flask, validez et transférez le code mis à jour vers GitHub. Cela déclenchera un déploiement automatique dans la plateforme d’applications de DigitalOcean.
git add . git commit -m "Add attachment processing with DigitalOcean Spaces" git push origin main
Surveiller le déploiement : vous pouvez suivre la progression dans la section Déploiements du tableau de bord de votre application.
Vérifiez votre déploiement : une fois le déploiement terminé, accédez à l'URL publique de votre application et testez ses fonctionnalités. Vous pouvez également consulter les journaux d'exécution dans le tableau de bord pour confirmer que l'application a démarré avec succès.
Étape 4 : tester l'intégralité du flux de travail
Maintenant que votre application est entièrement configurée et prête, il est temps de tester l’ensemble du flux de travail. Nous veillerons à ce que le corps de l'e-mail soit traité, que les pièces jointes soient décodées et téléchargées sur DigitalOcean
Espaces, et le résultat final comprend tout ce dont nous avons besoin.
Voici comment tester étape par étape :
Envoyer un e-mail test : envoyez un e-mail à Postmark avec un corps de texte et une pièce jointe. Si vous ne savez pas comment configurer Postmark, consultez Jour 8 : Connexion de Postmark à votre application Flask où nous avons expliqué la configuration de Postmark pour transférer les e-mails vers votre application.
Vérifier l'activité Postmark JSON : dans le tableau de bord Postmark, accédez à l'onglet Activité. Localisez l'e-mail que vous avez envoyé et assurez-vous que la charge utile JSON inclut le corps du texte et les données de pièce jointe codées en Base64. Cela confirme que Postmark transfère correctement les données de courrier électronique à votre application.
Surveillez les journaux : vérifiez les journaux d'exécution dans votre tableau de bord DigitalOcean App Platform pour vous assurer que l'application traite la charge utile JSON. Nous avons expliqué comment accéder aux journaux d'exécution au jour 9.
Vérifier le téléchargement des espaces : visitez votre espace DigitalOcean pour confirmer que les fichiers ont été téléchargés avec succès. Vous devriez voir les pièces jointes dans votre compartiment.
Vérifiez le résultat final : l'application doit enregistrer les données extraites et les URL des pièces jointes. Ces journaux comprendront :
- Détails extraits du corps de l'e-mail.
- URL publiques des pièces jointes importées.
Reportez-vous au Jour 9 pour obtenir des conseils sur l'inspection des journaux d'exécution.
À la fin de ces étapes, votre flux de travail sera prêt à enregistrer les données dans une base de données, ce que nous aborderons ensuite.
🎁 Conclusion
Aujourd'hui, nous avons appris à votre application à gérer les pièces jointes comme un pro. Voici ce que nous avons fait :
- Création d'un espace DigitalOcean pour un stockage sécurisé et évolutif.
- Pièces jointes décodées en base64 à partir de Postmark JSON.
- Noms de fichiers uniques garantis avec
uuid
. - Pièces jointes téléchargées sur DigitalOcean Spaces à l'aide de
boto3
. - URL publiques générées pour chaque fichier, prêtes à être utilisées dans votre processeur de reçus.
Dans la prochaine étape, nous intégrerons ces données dans une base de données. Cela vous permettra de stocker les détails extraits des e-mails et les URL des pièces jointes pour une utilisation à long terme, rendant ainsi votre processeur de reçus encore plus puissant. Restez à l'écoute!