Bot commit: Добавлен файл email_task.ipynb

This commit is contained in:
Vladislav 2026-06-30 19:13:50 -04:00
parent 0a88cbf03e
commit dc9d3d48ce

266
email_task.ipynb Normal file
View File

@ -0,0 +1,266 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"id": "da5a2e63",
"metadata": {},
"outputs": [],
"source": [
"# ignore\n",
"import os\n",
"\n",
"def load_env(file_path):\n",
" \"\"\"Загружает переменные окружения из файла .env с поддержкой булевых значений.\"\"\"\n",
" with open(file_path) as f:\n",
" for line in f:\n",
" line = line.strip()\n",
" if not line or line.startswith('#'):\n",
" continue\n",
" \n",
" try:\n",
" key, value = line.split('=', 1)\n",
" key = key.strip()\n",
" value = value.strip('\"\\' ')\n",
" # Преобразование строковых значений в булевые\n",
" if value.lower() in ['true', '1', 'yes']:\n",
" value = True\n",
" elif value.lower() in ['false', '0', 'no']:\n",
" value = False\n",
" \n",
" # Проверка, что ключ не пустой\n",
" if key:\n",
" os.environ[key] = str(value) # Храним как строку\n",
" except ValueError:\n",
" print(f\"Ошибка: неверный формат в строке '{line}'\")\n",
"\n",
"try:\n",
" load_env('env')\n",
"except:\n",
" load_env('env_example')"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "afd6bc25",
"metadata": {},
"outputs": [],
"source": [
"import smtplib\n",
"from email.mime.multipart import MIMEMultipart\n",
"from email.mime.text import MIMEText\n",
"from celery import Celery\n",
"from config import *\n",
"from templates import templates\n",
"import logging\n",
"from typing import Optional\n",
"import asyncio \n",
"from email.utils import formatdate, make_msgid\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "05fadc2d",
"metadata": {},
"outputs": [],
"source": [
"def build_unsubscribe_url(email: str, token: str, base_url: str) -> str:\n",
" return f\"{base_url}/api/v1/email/unsubscribe?token={token}&email={email}\"\n",
"\n",
"\n",
"async def create_email_message(\n",
" to_email: str,\n",
" subject: str,\n",
" html_content: str,\n",
" user_id: int = None,\n",
"):\n",
" msg = MIMEMultipart(\"alternative\")\n",
" \n",
" msg['From'] = f\"{FROM_NAME} <{FROM_EMAIL}>\"\n",
" msg['To'] = to_email\n",
" msg['Subject'] = subject\n",
" msg['Message-ID'] = make_msgid(domain=FROM_EMAIL.split('@')[1])\n",
" msg['Date'] = formatdate(localtime=True)\n",
" msg['X-Priority'] = \"3\"\n",
" msg['Importance'] = \"normal\"\n",
" msg['X-Auto-Response-Suppress'] = \"All\"\n",
" \n",
" # token = await create_unsubscribe_token(to_email, user_id)\n",
" unsubscribe_url = build_unsubscribe_url(to_email, None, FULL_DOMAIN)\n",
" if to_email != FROM_EMAIL:\n",
" msg['List-Unsubscribe'] = f\"<{unsubscribe_url}>\"\n",
" msg['List-Unsubscribe-Post'] = \"List-Unsubscribe=One-Click\"\n",
" \n",
" msg.attach(MIMEText(html_content, 'html'))\n",
" \n",
" return msg"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "6687f2ab",
"metadata": {},
"outputs": [],
"source": [
"def connect(email_server, email_port):\n",
" server = smtplib.SMTP(email_server, email_port)\n",
" server.starttls(context=ctx)\n",
" return server\n",
"\n",
"\n",
"\n",
"def send_email(\n",
" to_email: str,\n",
" html_content: str,\n",
" subject: str,\n",
" user_id: Optional[str] = None,\n",
") -> bool:\n",
" msg =asyncio.run(create_email_message(\n",
" to_email=to_email,\n",
" subject=subject,\n",
" html_content=html_content,\n",
" user_id=user_id,\n",
" ))\n",
"\n",
"\n",
" try:\n",
" with connect(EMAIL_SMTP_SERVER, EMAIL_SMTP_PORT) as server:\n",
" server.login(FROM_EMAIL, EMAIL_PASS)\n",
" server.send_message(msg)\n",
" # server.close()\n",
"\n",
" logging.info(f\"Email sent successfully to: {to_email} | Subject: {subject}\")\n",
" return True\n",
"\n",
" except Exception as e:\n",
" logging.error(\n",
" f\"Error sending email to: {to_email}, \"\n",
" f\"Subject: {subject}, Error: {str(e)}\"\n",
" )\n",
" return False\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "81e35a88",
"metadata": {},
"outputs": [],
"source": [
"celery_app_email = Celery('emails', broker=f'amqps://admin:{RABBITMQ_PASS}@{RABBITMQ_HOST}/email', backend=f'redis://{REDIS_HOST}:{6379 if PROD else 30003}/0', task_track_started=True )\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "30ca10ab",
"metadata": {},
"outputs": [],
"source": [
"@celery_app_email.task(\n",
" bind=True,\n",
" rate_limit='30/m',\n",
" name='email_task.send_email',\n",
" retry_backoff=True,\n",
" retry_backoff_max=600,\n",
" retry_jitter=True,\n",
" max_retries=3,\n",
" soft_time_limit=30,\n",
" time_limit=45,\n",
")\n",
"def notify(self, to_email: str, data: dict, subject: str = EMAIL_SUBJECT, template: str = 'main.html'):\n",
" logging.info(f\"Отправка email на {to_email} (попытка {self.request.retries + 1})\")\n",
" try:\n",
" html_content = templates.get_template(f\"email/{template}\").render({\n",
" \"subject\": subject, \n",
" \"data\": data\n",
" })\n",
" \n",
" ok = send_email(to_email, html_content, subject=subject)\n",
" if ok:\n",
" return ok\n",
" else:\n",
" raise self.retry(exc=exc)\n",
" \n",
" except Exception as exc:\n",
" logging.warning(\n",
" f\"Ошибка при отправке email на {to_email} (попытка {self.request.retries + 1}): {exc}\"\n",
" )\n",
" raise self.retry(exc=exc)"
]
},
{
"cell_type": "markdown",
"id": "1837d7c2",
"metadata": {},
"source": [
"## Run"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "daf4fe38",
"metadata": {},
"outputs": [],
"source": [
"import json\n",
"import os\n",
"\n",
"IGNORE = ['build.ipynb', 'data.ipynb']\n",
"\n",
"for _filename in [i for i in os.listdir() if 'ipynb' in i and i not in IGNORE]:\n",
" filename_without_extension = os.path.splitext(os.path.basename(_filename))[0]\n",
"\n",
" with open(f'{filename_without_extension}.ipynb', 'r') as f:\n",
" data = f.read()\n",
"\n",
" main = json.loads(data)\n",
" app = []\n",
" for cell in main['cells']:\n",
" if '## Run' in cell['source']:\n",
" break\n",
" if '# ignore\\n' in cell['source']:\n",
" continue\n",
" app.append(''.join(cell['source']))\n",
"\n",
" with open(f'{filename_without_extension}.py', 'w') as f:\n",
" f.write('\\n\\n'.join(app))\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f5194611",
"metadata": {},
"outputs": [],
"source": [
"!celery -A email_task.celery_app_email worker --loglevel=info"
]
}
],
"metadata": {
"kernelspec": {
"display_name": ".venv",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.14.6"
}
},
"nbformat": 4,
"nbformat_minor": 5
}