Sendgrid integration + bump enterprise package
This commit is contained in:
parent
b5763d27eb
commit
0769a290da
BIN
enterprise/dist/litellm_enterprise-0.1.24-py3-none-any.whl
vendored
Normal file
BIN
enterprise/dist/litellm_enterprise-0.1.24-py3-none-any.whl
vendored
Normal file
Binary file not shown.
BIN
enterprise/dist/litellm_enterprise-0.1.24.tar.gz
vendored
Normal file
BIN
enterprise/dist/litellm_enterprise-0.1.24.tar.gz
vendored
Normal file
Binary file not shown.
@ -0,0 +1,79 @@
|
||||
"""
|
||||
LiteLLM x SendGrid email integration.
|
||||
|
||||
Docs: https://docs.sendgrid.com/api-reference/mail-send/mail-send
|
||||
"""
|
||||
|
||||
import os
|
||||
from typing import List
|
||||
|
||||
from litellm._logging import verbose_logger
|
||||
from litellm.llms.custom_httpx.http_handler import (
|
||||
get_async_httpx_client,
|
||||
httpxSpecialProvider,
|
||||
)
|
||||
|
||||
from .base_email import BaseEmailLogger
|
||||
|
||||
|
||||
SENDGRID_API_ENDPOINT = "https://api.sendgrid.com/v3/mail/send"
|
||||
|
||||
|
||||
class SendGridEmailLogger(BaseEmailLogger):
|
||||
"""
|
||||
Send emails using SendGrid's Mail Send API.
|
||||
|
||||
Required env vars:
|
||||
- SENDGRID_API_KEY
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.async_httpx_client = get_async_httpx_client(
|
||||
llm_provider=httpxSpecialProvider.LoggingCallback
|
||||
)
|
||||
self.sendgrid_api_key = os.getenv("SENDGRID_API_KEY")
|
||||
verbose_logger.debug("SendGrid Email Logger initialized.")
|
||||
|
||||
async def send_email(
|
||||
self,
|
||||
from_email: str,
|
||||
to_email: List[str],
|
||||
subject: str,
|
||||
html_body: str,
|
||||
):
|
||||
"""
|
||||
Send an email via SendGrid.
|
||||
"""
|
||||
if not self.sendgrid_api_key:
|
||||
raise ValueError("SENDGRID_API_KEY is not set")
|
||||
|
||||
verbose_logger.debug(
|
||||
f"Sending email via SendGrid from {from_email} to {to_email} with subject {subject}"
|
||||
)
|
||||
|
||||
payload = {
|
||||
"from": {"email": from_email},
|
||||
"personalizations": [
|
||||
{
|
||||
"to": [{"email": email} for email in to_email],
|
||||
"subject": subject,
|
||||
}
|
||||
],
|
||||
"content": [
|
||||
{
|
||||
"type": "text/html",
|
||||
"value": html_body,
|
||||
}
|
||||
],
|
||||
}
|
||||
|
||||
response = await self.async_httpx_client.post(
|
||||
url=SENDGRID_API_ENDPOINT,
|
||||
json=payload,
|
||||
headers={"Authorization": f"Bearer {self.sendgrid_api_key}"},
|
||||
)
|
||||
|
||||
verbose_logger.debug(
|
||||
f"SendGrid response status={response.status_code}, body={response.text}"
|
||||
)
|
||||
return
|
||||
@ -0,0 +1,99 @@
|
||||
import os
|
||||
import sys
|
||||
import unittest.mock as mock
|
||||
|
||||
import pytest
|
||||
from httpx import Response
|
||||
|
||||
sys.path.insert(0, os.path.abspath("../../.."))
|
||||
|
||||
from litellm_enterprise.enterprise_callbacks.send_emails.sendgrid_email import (
|
||||
SendGridEmailLogger,
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_env_vars():
|
||||
with mock.patch.dict(os.environ, {"SENDGRID_API_KEY": "test_api_key"}):
|
||||
yield
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_httpx_client():
|
||||
with mock.patch(
|
||||
"litellm_enterprise.enterprise_callbacks.send_emails.sendgrid_email.get_async_httpx_client"
|
||||
) as mock_client:
|
||||
mock_response = mock.AsyncMock(spec=Response)
|
||||
mock_response.status_code = 202
|
||||
mock_response.text = "accepted"
|
||||
|
||||
mock_async_client = mock.AsyncMock()
|
||||
mock_async_client.post.return_value = mock_response
|
||||
mock_client.return_value = mock_async_client
|
||||
|
||||
yield mock_async_client
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_send_email_success(mock_env_vars, mock_httpx_client):
|
||||
logger = SendGridEmailLogger()
|
||||
|
||||
from_email = "test@example.com"
|
||||
to_email = ["recipient@example.com"]
|
||||
subject = "Test Subject"
|
||||
html_body = "<p>Test email body</p>"
|
||||
|
||||
await logger.send_email(
|
||||
from_email=from_email, to_email=to_email, subject=subject, html_body=html_body
|
||||
)
|
||||
|
||||
mock_httpx_client.post.assert_called_once()
|
||||
call_args = mock_httpx_client.post.call_args
|
||||
assert call_args[1]["url"] == "https://api.sendgrid.com/v3/mail/send"
|
||||
|
||||
payload = call_args[1]["json"]
|
||||
assert payload["from"] == {"email": from_email}
|
||||
assert payload["personalizations"][0]["to"] == [{"email": to_email[0]}]
|
||||
assert payload["personalizations"][0]["subject"] == subject
|
||||
assert payload["content"][0]["type"] == "text/html"
|
||||
assert payload["content"][0]["value"] == html_body
|
||||
|
||||
assert call_args[1]["headers"] == {"Authorization": "Bearer test_api_key"}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_send_email_missing_api_key(mock_httpx_client):
|
||||
with mock.patch.dict(os.environ, {}, clear=True):
|
||||
logger = SendGridEmailLogger()
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
await logger.send_email(
|
||||
from_email="test@example.com",
|
||||
to_email=["recipient@example.com"],
|
||||
subject="Test Subject",
|
||||
html_body="<p>Test email body</p>",
|
||||
)
|
||||
|
||||
mock_httpx_client.post.assert_not_called()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_send_email_multiple_recipients(mock_env_vars, mock_httpx_client):
|
||||
logger = SendGridEmailLogger()
|
||||
|
||||
from_email = "test@example.com"
|
||||
to_email = ["recipient1@example.com", "recipient2@example.com"]
|
||||
subject = "Test Subject"
|
||||
html_body = "<p>Test email body</p>"
|
||||
|
||||
await logger.send_email(
|
||||
from_email=from_email, to_email=to_email, subject=subject, html_body=html_body
|
||||
)
|
||||
|
||||
mock_httpx_client.post.assert_called_once()
|
||||
payload = mock_httpx_client.post.call_args[1]["json"]
|
||||
|
||||
assert payload["personalizations"][0]["to"] == [
|
||||
{"email": "recipient1@example.com"},
|
||||
{"email": "recipient2@example.com"},
|
||||
]
|
||||
Loading…
Reference in New Issue
Block a user