238 lines
10 KiB
Python
238 lines
10 KiB
Python
from __future__ import annotations
|
|
|
|
import json
|
|
import shutil
|
|
import tempfile
|
|
from contextlib import contextmanager
|
|
from pathlib import Path
|
|
from typing import Any, Iterator
|
|
|
|
from govoplan_campaign.backend.runtime import capability
|
|
|
|
|
|
FILES_CAPABILITY = "files.campaign_attachments"
|
|
MAIL_CAPABILITY = "mail.campaign_delivery"
|
|
|
|
|
|
class OptionalModuleUnavailable(RuntimeError):
|
|
pass
|
|
|
|
|
|
class SmtpConfigurationError(RuntimeError):
|
|
pass
|
|
|
|
|
|
class SmtpSendError(RuntimeError):
|
|
def __init__(self, message: str, *, temporary: bool = False, outcome_unknown: bool = False) -> None:
|
|
super().__init__(message)
|
|
self.temporary = temporary
|
|
self.outcome_unknown = outcome_unknown
|
|
|
|
|
|
class ImapConfigurationError(RuntimeError):
|
|
pass
|
|
|
|
|
|
class ImapAppendError(RuntimeError):
|
|
def __init__(self, message: str, *, temporary: bool | None = None) -> None:
|
|
super().__init__(message)
|
|
self.temporary = temporary
|
|
|
|
|
|
class MailProfileError(OptionalModuleUnavailable):
|
|
pass
|
|
|
|
|
|
class _PreparedCampaignSnapshot:
|
|
def __init__(self, directory: Path, path: Path, raw_json: dict[str, Any]) -> None:
|
|
self._directory = directory
|
|
self.path = path
|
|
self.raw_json = raw_json
|
|
self.managed_files_by_local_path: dict[str, Any] = {}
|
|
self.shared_assets: list[Any] = []
|
|
|
|
def cleanup(self) -> None:
|
|
shutil.rmtree(self._directory, ignore_errors=True)
|
|
|
|
|
|
class FilesCampaignIntegration:
|
|
def __init__(self, delegate: Any | None = None) -> None:
|
|
self._delegate = delegate
|
|
|
|
@property
|
|
def available(self) -> bool:
|
|
return self._delegate is not None
|
|
|
|
@contextmanager
|
|
def prepared_campaign_snapshot(self, *args: Any, **kwargs: Any) -> Iterator[Any]:
|
|
if self._delegate is not None:
|
|
with self._delegate.prepared_campaign_snapshot(*args, **kwargs) as prepared:
|
|
yield prepared
|
|
return
|
|
|
|
raw_json = kwargs.get("raw_json") if isinstance(kwargs.get("raw_json"), dict) else {}
|
|
prefix = str(kwargs.get("prefix") or "govoplan-campaign-")
|
|
directory = Path(tempfile.mkdtemp(prefix=prefix))
|
|
snapshot = _PreparedCampaignSnapshot(directory, directory / "campaign.json", raw_json)
|
|
snapshot.path.write_text(json.dumps(raw_json, ensure_ascii=False, indent=2), encoding="utf-8")
|
|
try:
|
|
yield snapshot
|
|
finally:
|
|
snapshot.cleanup()
|
|
|
|
def managed_match_payloads(self, matches: Any, managed_files_by_local_path: dict[str, Any]) -> list[dict[str, Any]]:
|
|
if self._delegate is None:
|
|
return []
|
|
return self._delegate.managed_match_payloads(matches, managed_files_by_local_path)
|
|
|
|
def public_attachment_summary_payload(self, attachment: Any) -> dict[str, Any]:
|
|
if self._delegate is not None:
|
|
return self._delegate.public_attachment_summary_payload(attachment)
|
|
if hasattr(attachment, "model_dump"):
|
|
return attachment.model_dump(mode="json")
|
|
if isinstance(attachment, dict):
|
|
return dict(attachment)
|
|
return {"path": str(attachment)}
|
|
|
|
def annotate_built_messages_with_managed_files(self, built_messages: Any, managed_files_by_local_path: dict[str, Any]) -> None:
|
|
if self._delegate is not None:
|
|
self._delegate.annotate_built_messages_with_managed_files(built_messages, managed_files_by_local_path)
|
|
|
|
def record_campaign_attachment_uses_for_jobs(self, session: Any, jobs: Any, *, stage: str) -> None:
|
|
if self._delegate is not None:
|
|
self._delegate.record_campaign_attachment_uses_for_jobs(session, jobs, stage=stage)
|
|
|
|
def current_version_and_blob(self, session: Any, asset: Any) -> tuple[Any, Any]:
|
|
if self._delegate is None:
|
|
raise OptionalModuleUnavailable("Files module is not available")
|
|
return self._delegate.current_version_and_blob(session, asset)
|
|
|
|
def mark_job_attachment_uses_sent(self, session: Any, job: Any) -> None:
|
|
if self._delegate is not None:
|
|
self._delegate.mark_job_attachment_uses_sent(session, job)
|
|
|
|
|
|
class MailCampaignIntegration:
|
|
def __init__(self, delegate: Any | None = None) -> None:
|
|
self._delegate = delegate
|
|
if delegate is not None:
|
|
self.MailProfileError = getattr(delegate, "MailProfileError", MailProfileError)
|
|
|
|
MailProfileError = MailProfileError
|
|
SmtpConfigurationError = SmtpConfigurationError
|
|
SmtpSendError = SmtpSendError
|
|
ImapConfigurationError = ImapConfigurationError
|
|
ImapAppendError = ImapAppendError
|
|
|
|
@property
|
|
def available(self) -> bool:
|
|
return self._delegate is not None
|
|
|
|
def _require(self) -> Any:
|
|
if self._delegate is None:
|
|
raise MailProfileError("Mail module is not available")
|
|
return self._delegate
|
|
|
|
def materialize_campaign_mail_profile_config(self, session: Any, **kwargs: Any) -> dict[str, Any]:
|
|
if self._delegate is None:
|
|
raw_json = kwargs.get("raw_json")
|
|
if self.mail_profile_id_from_campaign_json(raw_json if isinstance(raw_json, dict) else {}):
|
|
raise MailProfileError("Campaign mail-server profiles require the mail module")
|
|
return dict(raw_json) if isinstance(raw_json, dict) else {}
|
|
try:
|
|
return self._delegate.materialize_campaign_mail_profile_config(session, **kwargs)
|
|
except getattr(self._delegate, "MailProfileError", MailProfileError) as exc:
|
|
raise MailProfileError(str(exc)) from exc
|
|
|
|
def assert_campaign_mail_policy_allows_json(self, session: Any, **kwargs: Any) -> None:
|
|
if self._delegate is None:
|
|
raw_json = kwargs.get("raw_json")
|
|
profile_id = self.mail_profile_id_from_campaign_json(raw_json if isinstance(raw_json, dict) else {})
|
|
if profile_id:
|
|
raise MailProfileError("Campaign mail-server profiles require the mail module")
|
|
return None
|
|
try:
|
|
return self._delegate.assert_campaign_mail_policy_allows_json(session, **kwargs)
|
|
except getattr(self._delegate, "MailProfileError", MailProfileError) as exc:
|
|
raise MailProfileError(str(exc)) from exc
|
|
|
|
def assert_mail_policy_allows_send(self, session: Any, **kwargs: Any) -> None:
|
|
delegate = self._require()
|
|
try:
|
|
return delegate.assert_mail_policy_allows_send(session, **kwargs)
|
|
except getattr(delegate, "MailProfileError", MailProfileError) as exc:
|
|
raise MailProfileError(str(exc)) from exc
|
|
|
|
def mail_profile_id_from_campaign_json(self, raw_json: dict[str, Any]) -> str | None:
|
|
if self._delegate is not None:
|
|
return self._delegate.mail_profile_id_from_campaign_json(raw_json)
|
|
server = raw_json.get("server") if isinstance(raw_json, dict) else None
|
|
profile_id = server.get("mail_profile_id") if isinstance(server, dict) else None
|
|
if profile_id is None and isinstance(server, dict):
|
|
profile_id = server.get("profile_id")
|
|
return str(profile_id).strip() if profile_id else None
|
|
|
|
def ensure_mail_profile_allowed_for_campaign(self, session: Any, **kwargs: Any) -> Any:
|
|
delegate = self._require()
|
|
try:
|
|
return delegate.ensure_mail_profile_allowed_for_campaign(session, **kwargs)
|
|
except getattr(delegate, "MailProfileError", MailProfileError) as exc:
|
|
raise MailProfileError(str(exc)) from exc
|
|
|
|
def smtp_config_from_profile(self, profile: Any) -> Any:
|
|
return self._require().smtp_config_from_profile(profile)
|
|
|
|
def imap_config_from_profile(self, profile: Any) -> Any:
|
|
return self._require().imap_config_from_profile(profile)
|
|
|
|
def effective_profile_credentials_inherited(self, session: Any, **kwargs: Any) -> bool:
|
|
return self._require().effective_profile_credentials_inherited(session, **kwargs)
|
|
|
|
def apply_campaign_credentials(self, profile_payload: dict[str, Any], server: dict[str, Any], protocol: str) -> dict[str, Any]:
|
|
return self._require().apply_campaign_credentials(profile_payload, server, protocol)
|
|
|
|
def wait_for_rate_limit(self, **kwargs: Any) -> None:
|
|
if self._delegate is None:
|
|
return None
|
|
return self._delegate.wait_for_rate_limit(**kwargs)
|
|
|
|
def send_email_bytes(self, *args: Any, **kwargs: Any) -> Any:
|
|
delegate = self._require()
|
|
try:
|
|
return delegate.send_email_bytes(*args, **kwargs)
|
|
except getattr(delegate, "SmtpSendError", SmtpSendError) as exc:
|
|
raise SmtpSendError(str(exc), temporary=bool(getattr(exc, "temporary", False)), outcome_unknown=bool(getattr(exc, "outcome_unknown", False))) from exc
|
|
except getattr(delegate, "SmtpConfigurationError", SmtpConfigurationError) as exc:
|
|
raise SmtpConfigurationError(str(exc)) from exc
|
|
|
|
def append_message_to_sent(self, *args: Any, **kwargs: Any) -> Any:
|
|
delegate = self._require()
|
|
try:
|
|
return delegate.append_message_to_sent(*args, **kwargs)
|
|
except getattr(delegate, "ImapAppendError", ImapAppendError) as exc:
|
|
raise ImapAppendError(str(exc), temporary=getattr(exc, "temporary", None)) from exc
|
|
except getattr(delegate, "ImapConfigurationError", ImapConfigurationError) as exc:
|
|
raise ImapConfigurationError(str(exc)) from exc
|
|
|
|
def send_email_message(self, *args: Any, **kwargs: Any) -> Any:
|
|
delegate = self._require()
|
|
try:
|
|
return delegate.send_email_message(*args, **kwargs)
|
|
except getattr(delegate, "SmtpSendError", SmtpSendError) as exc:
|
|
raise SmtpSendError(str(exc), temporary=bool(getattr(exc, "temporary", False)), outcome_unknown=bool(getattr(exc, "outcome_unknown", False))) from exc
|
|
except getattr(delegate, "SmtpConfigurationError", SmtpConfigurationError) as exc:
|
|
raise SmtpConfigurationError(str(exc)) from exc
|
|
|
|
def mock_mailbox(self) -> Any | None:
|
|
if self._delegate is None or not hasattr(self._delegate, "mock_mailbox"):
|
|
return None
|
|
return self._delegate.mock_mailbox()
|
|
|
|
|
|
def files_integration() -> FilesCampaignIntegration:
|
|
return FilesCampaignIntegration(capability(FILES_CAPABILITY))
|
|
|
|
|
|
def mail_integration() -> MailCampaignIntegration:
|
|
return MailCampaignIntegration(capability(MAIL_CAPABILITY))
|