first version able to send

This commit is contained in:
2026-06-11 00:06:44 +02:00
parent ce43f2658f
commit 3b06f3670e
12 changed files with 740 additions and 67 deletions

View File

@@ -8,7 +8,7 @@ from typing import Iterable
from pydantic import BaseModel, ConfigDict, Field
from .field_values import ignored_entry_field_overrides
from .models import CampaignConfig, EntryConfig, SourceType
from .models import AttachmentConfig, CampaignConfig, EntryConfig, SourceType
class Severity(StrEnum):
@@ -136,6 +136,59 @@ def _iter_template_source_paths(config: CampaignConfig) -> Iterable[tuple[str, s
return paths
def _attachment_base_path_report_value(config: CampaignConfig) -> str:
if config.attachments.base_paths:
return ", ".join(f"{base_path.name}: {base_path.path}" for base_path in config.attachments.base_paths)
return config.attachments.base_path
def _iter_attachment_rules(config: CampaignConfig) -> Iterable[tuple[str, AttachmentConfig, bool]]:
for index, attachment_config in enumerate(config.attachments.global_):
yield f"/attachments/global/{index}", attachment_config, False
inline_entries = config.entries.inline or [] if config.entries.is_inline else []
for entry_index, entry in enumerate(inline_entries):
for attachment_index, attachment_config in enumerate(entry.attachments):
yield f"/entries/inline/{entry_index}/attachments/{attachment_index}", attachment_config, True
if config.entries.defaults:
for attachment_index, attachment_config in enumerate(config.entries.defaults.attachments):
yield f"/entries/defaults/attachments/{attachment_index}", attachment_config, True
def _attachment_path_issues(config: CampaignConfig) -> list[SemanticIssue]:
issues: list[SemanticIssue] = []
configured_paths = {base_path.path for base_path in config.attachments.base_paths}
individual_paths = config.attachments.individual_base_path_values
if config.attachments.base_paths:
for index, base_path in enumerate(config.attachments.base_paths):
if not base_path.name.strip():
issues.append(_issue(Severity.WARNING, "attachment_base_path_missing_name", "attachment base path has no display name", f"/attachments/base_paths/{index}/name"))
if not base_path.path.strip():
issues.append(_issue(Severity.ERROR, "attachment_base_path_missing_path", "attachment base path has no path", f"/attachments/base_paths/{index}/path"))
elif not config.attachments.base_path:
issues.append(_issue(Severity.INFO, "missing_attachment_base_path", "Attachment base path is not configured yet.", "/attachments/base_path"))
if configured_paths:
for path, attachment_config, is_individual in _iter_attachment_rules(config):
if attachment_config.base_dir and attachment_config.base_dir not in configured_paths:
issues.append(_issue(
Severity.WARNING,
"unknown_attachment_base_path",
f"attachment rule refers to base path {attachment_config.base_dir!r}, but it is not listed in attachments.base_paths",
f"{path}/base_dir",
))
if is_individual and individual_paths and attachment_config.base_dir not in individual_paths:
issues.append(_issue(
Severity.WARNING,
"individual_attachment_base_path_not_allowed",
f"individual attachment rule uses base path {attachment_config.base_dir!r}, but that base path does not allow individual attachments",
f"{path}/base_dir",
))
return issues
def _ignored_override_issues(config: CampaignConfig, entry: EntryConfig, path_prefix: str) -> list[SemanticIssue]:
return [
_issue(
@@ -170,6 +223,8 @@ def validate_campaign_config(
f"/global_values/{key}",
))
issues.extend(_attachment_path_issues(config))
if config.server.imap and config.server.imap.enabled:
missing = [name for name in ["host", "port", "username", "password"] if getattr(config.server.imap, name) in (None, "")]
if missing:
@@ -263,14 +318,25 @@ def validate_campaign_config(
issues.append(_issue(Severity.ERROR, "entries_source_read_error", str(exc), "/entries/source/path"))
if check_files:
attachments_base_path = _resolve(campaign_path, config.attachments.base_path)
if not attachments_base_path.exists():
issues.append(_issue(
Severity.WARNING,
"attachments_base_path_not_found",
f"attachments.base_path does not exist: {attachments_base_path}",
"/attachments/base_path",
))
if config.attachments.base_paths:
for index, base_path_config in enumerate(config.attachments.base_paths):
attachments_base_path = _resolve(campaign_path, base_path_config.path)
if not attachments_base_path.exists():
issues.append(_issue(
Severity.WARNING,
"attachments_base_path_not_found",
f"attachment base path {base_path_config.name!r} does not exist: {attachments_base_path}",
f"/attachments/base_paths/{index}/path",
))
else:
attachments_base_path = _resolve(campaign_path, config.attachments.base_path)
if not attachments_base_path.exists():
issues.append(_issue(
Severity.WARNING,
"attachments_base_path_not_found",
f"attachments.base_path does not exist: {attachments_base_path}",
"/attachments/base_path",
))
for schema_path, raw_path in _iter_template_source_paths(config):
path = _resolve(campaign_path, raw_path)
if not path.exists():
@@ -287,7 +353,7 @@ def validate_campaign_config(
issues=issues,
entries_mode=entries_mode,
entries_count=entries_count,
attachments_base_path=config.attachments.base_path,
attachments_base_path=_attachment_base_path_report_value(config),
rate_limit=f"{config.delivery.rate_limit.messages_per_minute}/min, concurrency {config.delivery.rate_limit.concurrency}",
imap_append_enabled=config.delivery.imap_append_sent.enabled,
)