Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -327,7 +327,7 @@ def _resolve_channels(
) -> tuple[str, ...]:
raw_channels = channels
if raw_channels is None:
raw_channels = _get_value(notification_settings, "crisis_alert_channels", None)
raw_channels = _get_value(notification_settings, "strategy_plugin_alert_channels", None)
if raw_channels in (None, "", (), []):
raw_channels = (_CHANNEL_EMAIL, _CHANNEL_SMS, _CHANNEL_PUSH, _CHANNEL_TELEGRAM)
return _normalize_channels(raw_channels)
Expand Down
18 changes: 9 additions & 9 deletions src/quant_platform_kit/notifications/strategy_plugin_email.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,31 +47,31 @@ def from_object(cls, value: object) -> "StrategyPluginEmailSettings":
return value
return cls(
recipients=tuple(
parse_email_recipients(_get_value(value, "crisis_alert_email_recipients", ()))
parse_email_recipients(_get_value(value, "strategy_plugin_alert_email_recipients", ()))
),
sender_email=_first_non_empty(_get_value(value, "crisis_alert_email_sender_email")),
sender_password=_get_value(value, "crisis_alert_email_sender_password"),
sender_email=_first_non_empty(_get_value(value, "strategy_plugin_alert_email_sender_email")),
sender_password=_get_value(value, "strategy_plugin_alert_email_sender_password"),
smtp_host=_first_non_empty(
_get_value(value, "crisis_alert_email_smtp_host")
_get_value(value, "strategy_plugin_alert_email_smtp_host")
)
or _DEFAULT_EMAIL_SMTP_HOST,
smtp_port=_coerce_int(
_get_value(value, "crisis_alert_email_smtp_port"),
_get_value(value, "strategy_plugin_alert_email_smtp_port"),
_DEFAULT_EMAIL_SMTP_PORT,
),
smtp_security=_coerce_smtp_security(
_get_value(value, "crisis_alert_email_smtp_security")
_get_value(value, "strategy_plugin_alert_email_smtp_security")
),
)

def missing_fields(self) -> tuple[str, ...]:
missing: list[str] = []
if not parse_email_recipients(self.recipients):
missing.append("CRISIS_ALERT_EMAIL_RECIPIENTS")
missing.append("STRATEGY_PLUGIN_ALERT_EMAIL_RECIPIENTS")
if not str(self.sender_email or "").strip():
missing.append("CRISIS_ALERT_EMAIL_SENDER_EMAIL")
missing.append("STRATEGY_PLUGIN_ALERT_EMAIL_SENDER_EMAIL")
if not str(self.sender_password or "").strip():
missing.append("CRISIS_ALERT_EMAIL_SENDER_PASSWORD")
missing.append("STRATEGY_PLUGIN_ALERT_EMAIL_SENDER_PASSWORD")
return tuple(missing)

@property
Expand Down
24 changes: 12 additions & 12 deletions src/quant_platform_kit/notifications/strategy_plugin_push.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,37 +47,37 @@ def from_object(cls, value: object) -> "StrategyPluginPushSettings":
if isinstance(value, cls):
return value
provider = (
_first_non_empty(_get_value(value, "crisis_alert_push_provider"))
_first_non_empty(_get_value(value, "strategy_plugin_alert_push_provider"))
or _DEFAULT_PUSH_PROVIDER
).lower()
return cls(
recipients=tuple(
parse_push_recipients(_get_value(value, "crisis_alert_push_recipients", ()))
parse_push_recipients(_get_value(value, "strategy_plugin_alert_push_recipients", ()))
),
provider=provider,
app_token=_first_non_empty(_get_value(value, "crisis_alert_push_app_token")),
access_token=_first_non_empty(_get_value(value, "crisis_alert_push_access_token")),
app_token=_first_non_empty(_get_value(value, "strategy_plugin_alert_push_app_token")),
access_token=_first_non_empty(_get_value(value, "strategy_plugin_alert_push_access_token")),
api_base_url=(
_first_non_empty(_get_value(value, "crisis_alert_push_api_base_url"))
_first_non_empty(_get_value(value, "strategy_plugin_alert_push_api_base_url"))
or _default_api_base_url(provider)
),
device=_first_non_empty(_get_value(value, "crisis_alert_push_device")),
priority=_first_non_empty(_get_value(value, "crisis_alert_push_priority")),
tags=_first_non_empty(_get_value(value, "crisis_alert_push_tags")),
device=_first_non_empty(_get_value(value, "strategy_plugin_alert_push_device")),
priority=_first_non_empty(_get_value(value, "strategy_plugin_alert_push_priority")),
tags=_first_non_empty(_get_value(value, "strategy_plugin_alert_push_tags")),
body_max_chars=_coerce_int(
_get_value(value, "crisis_alert_push_body_max_chars"),
_get_value(value, "strategy_plugin_alert_push_body_max_chars"),
_DEFAULT_PUSH_BODY_MAX_CHARS,
),
)

def missing_fields(self) -> tuple[str, ...]:
missing: list[str] = []
if self.provider not in _SUPPORTED_PUSH_PROVIDERS:
missing.append("CRISIS_ALERT_PUSH_PROVIDER=pushover or ntfy")
missing.append("STRATEGY_PLUGIN_ALERT_PUSH_PROVIDER=pushover or ntfy")
if not parse_push_recipients(self.recipients):
missing.append("CRISIS_ALERT_PUSH_RECIPIENTS")
missing.append("STRATEGY_PLUGIN_ALERT_PUSH_RECIPIENTS")
if self.provider == PUSH_PROVIDER_PUSHOVER and not str(self.app_token or "").strip():
missing.append("CRISIS_ALERT_PUSH_APP_TOKEN")
missing.append("STRATEGY_PLUGIN_ALERT_PUSH_APP_TOKEN")
return tuple(missing)

@property
Expand Down
26 changes: 13 additions & 13 deletions src/quant_platform_kit/notifications/strategy_plugin_sms.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,34 +39,34 @@ def from_object(cls, value: object) -> "StrategyPluginSmsSettings":
if isinstance(value, cls):
return value
return cls(
recipients=tuple(parse_sms_recipients(_get_value(value, "crisis_alert_sms_recipients", ()))),
provider=(_first_non_empty(_get_value(value, "crisis_alert_sms_provider")) or _DEFAULT_SMS_PROVIDER).lower(),
account_id=_first_non_empty(_get_value(value, "crisis_alert_sms_account_id")),
auth_token=_first_non_empty(_get_value(value, "crisis_alert_sms_auth_token")),
sender=_first_non_empty(_get_value(value, "crisis_alert_sms_sender")),
recipients=tuple(parse_sms_recipients(_get_value(value, "strategy_plugin_alert_sms_recipients", ()))),
provider=(_first_non_empty(_get_value(value, "strategy_plugin_alert_sms_provider")) or _DEFAULT_SMS_PROVIDER).lower(),
account_id=_first_non_empty(_get_value(value, "strategy_plugin_alert_sms_account_id")),
auth_token=_first_non_empty(_get_value(value, "strategy_plugin_alert_sms_auth_token")),
sender=_first_non_empty(_get_value(value, "strategy_plugin_alert_sms_sender")),
messaging_service_id=_first_non_empty(
_get_value(value, "crisis_alert_sms_messaging_service_id")
_get_value(value, "strategy_plugin_alert_sms_messaging_service_id")
),
api_base_url=_first_non_empty(_get_value(value, "crisis_alert_sms_api_base_url"))
api_base_url=_first_non_empty(_get_value(value, "strategy_plugin_alert_sms_api_base_url"))
or _DEFAULT_SMS_API_BASE_URL,
body_max_chars=_coerce_int(
_get_value(value, "crisis_alert_sms_body_max_chars"),
_get_value(value, "strategy_plugin_alert_sms_body_max_chars"),
_DEFAULT_SMS_BODY_MAX_CHARS,
),
)

def missing_fields(self) -> tuple[str, ...]:
missing: list[str] = []
if self.provider != _DEFAULT_SMS_PROVIDER:
missing.append("CRISIS_ALERT_SMS_PROVIDER=twilio")
missing.append("STRATEGY_PLUGIN_ALERT_SMS_PROVIDER=twilio")
if not parse_sms_recipients(self.recipients):
missing.append("CRISIS_ALERT_SMS_RECIPIENTS")
missing.append("STRATEGY_PLUGIN_ALERT_SMS_RECIPIENTS")
if not str(self.account_id or "").strip():
missing.append("CRISIS_ALERT_SMS_ACCOUNT_ID")
missing.append("STRATEGY_PLUGIN_ALERT_SMS_ACCOUNT_ID")
if not str(self.auth_token or "").strip():
missing.append("CRISIS_ALERT_SMS_AUTH_TOKEN")
missing.append("STRATEGY_PLUGIN_ALERT_SMS_AUTH_TOKEN")
if not str(self.sender or "").strip() and not str(self.messaging_service_id or "").strip():
missing.append("CRISIS_ALERT_SMS_SENDER or CRISIS_ALERT_SMS_MESSAGING_SERVICE_ID")
missing.append("STRATEGY_PLUGIN_ALERT_SMS_SENDER or STRATEGY_PLUGIN_ALERT_SMS_MESSAGING_SERVICE_ID")
return tuple(missing)

@property
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,30 +40,30 @@ def from_object(cls, value: object) -> "StrategyPluginTelegramSettings":
return value
return cls(
chat_ids=tuple(
parse_telegram_chat_ids(_get_value(value, "crisis_alert_telegram_chat_ids", ()))
parse_telegram_chat_ids(_get_value(value, "strategy_plugin_alert_telegram_chat_ids", ()))
),
bot_token=_first_non_empty(_get_value(value, "crisis_alert_telegram_bot_token")),
bot_token=_first_non_empty(_get_value(value, "strategy_plugin_alert_telegram_bot_token")),
api_base_url=(
_first_non_empty(_get_value(value, "crisis_alert_telegram_api_base_url"))
_first_non_empty(_get_value(value, "strategy_plugin_alert_telegram_api_base_url"))
or DEFAULT_TELEGRAM_BOT_API_BASE_URL
),
parse_mode=_first_non_empty(_get_value(value, "crisis_alert_telegram_parse_mode")),
parse_mode=_first_non_empty(_get_value(value, "strategy_plugin_alert_telegram_parse_mode")),
disable_web_page_preview=_coerce_bool(
_get_value(value, "crisis_alert_telegram_disable_web_page_preview"),
_get_value(value, "strategy_plugin_alert_telegram_disable_web_page_preview"),
default=True,
),
body_max_chars=_coerce_int(
_get_value(value, "crisis_alert_telegram_body_max_chars"),
_get_value(value, "strategy_plugin_alert_telegram_body_max_chars"),
_DEFAULT_TELEGRAM_BODY_MAX_CHARS,
),
)

def missing_fields(self) -> tuple[str, ...]:
missing: list[str] = []
if not parse_telegram_chat_ids(self.chat_ids):
missing.append("CRISIS_ALERT_TELEGRAM_CHAT_IDS")
missing.append("STRATEGY_PLUGIN_ALERT_TELEGRAM_CHAT_IDS")
if not str(self.bot_token or "").strip():
missing.append("CRISIS_ALERT_TELEGRAM_BOT_TOKEN")
missing.append("STRATEGY_PLUGIN_ALERT_TELEGRAM_BOT_TOKEN")
return tuple(missing)

@property
Expand Down
26 changes: 13 additions & 13 deletions tests/test_strategy_plugin_alert_dispatcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,18 @@ def _alert_signal():


class _NotificationSettings:
crisis_alert_email_recipients = "risk@example.com"
crisis_alert_email_sender_email = "bot@example.com"
crisis_alert_email_sender_password = "app-password"
crisis_alert_sms_recipients = "+15165480265"
crisis_alert_sms_account_id = "AC123"
crisis_alert_sms_auth_token = "secret"
crisis_alert_sms_sender = "+15551234567"
crisis_alert_push_provider = "ntfy"
crisis_alert_push_recipients = "risk-topic"
crisis_alert_push_priority = "5"
crisis_alert_telegram_chat_ids = "123456"
crisis_alert_telegram_bot_token = "bot-token"
strategy_plugin_alert_email_recipients = "risk@example.com"
strategy_plugin_alert_email_sender_email = "bot@example.com"
strategy_plugin_alert_email_sender_password = "app-password"
strategy_plugin_alert_sms_recipients = "+15165480265"
strategy_plugin_alert_sms_account_id = "AC123"
strategy_plugin_alert_sms_auth_token = "secret"
strategy_plugin_alert_sms_sender = "+15551234567"
strategy_plugin_alert_push_provider = "ntfy"
strategy_plugin_alert_push_recipients = "risk-topic"
strategy_plugin_alert_push_priority = "5"
strategy_plugin_alert_telegram_chat_ids = "123456"
strategy_plugin_alert_telegram_bot_token = "bot-token"


class StrategyPluginAlertDispatcherTests(unittest.TestCase):
Expand Down Expand Up @@ -202,7 +202,7 @@ def test_publish_strategy_plugin_alerts_can_target_telegram_channel(self):

def test_publish_strategy_plugin_alerts_reads_channels_from_settings(self):
settings = _NotificationSettings()
settings.crisis_alert_channels = "email,telegram"
settings.strategy_plugin_alert_channels = "email,telegram"
emails = []
telegram_messages = []

Expand Down
24 changes: 12 additions & 12 deletions tests/test_strategy_plugin_email_notifications.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,9 @@ def test_publish_strategy_plugin_email_alerts_skips_missing_config():
assert result.sent_count == 0
assert result.skipped_count == 1
assert result.deliveries[0].reason == "missing_email_config"
assert "CRISIS_ALERT_EMAIL_RECIPIENTS" in result.deliveries[0].error
assert "CRISIS_ALERT_EMAIL_SENDER_EMAIL" in result.deliveries[0].error
assert "CRISIS_ALERT_EMAIL_SENDER_PASSWORD" in result.deliveries[0].error
assert "STRATEGY_PLUGIN_ALERT_EMAIL_RECIPIENTS" in result.deliveries[0].error
assert "STRATEGY_PLUGIN_ALERT_EMAIL_SENDER_EMAIL" in result.deliveries[0].error
assert "STRATEGY_PLUGIN_ALERT_EMAIL_SENDER_PASSWORD" in result.deliveries[0].error
assert observed == []


Expand Down Expand Up @@ -187,9 +187,9 @@ def test_publish_strategy_plugin_email_alerts_uses_transport_overrides():
def test_email_settings_reads_sender_and_default_transport_names_only():
settings = StrategyPluginEmailSettings.from_object(
SimpleNamespace(
crisis_alert_email_recipients="alerts@example.com; voice@example.com",
crisis_alert_email_sender_email="sender@example.com",
crisis_alert_email_sender_password="app-password",
strategy_plugin_alert_email_recipients="alerts@example.com; voice@example.com",
strategy_plugin_alert_email_sender_email="sender@example.com",
strategy_plugin_alert_email_sender_password="app-password",
)
)

Expand All @@ -205,12 +205,12 @@ def test_email_settings_reads_sender_and_default_transport_names_only():
def test_email_settings_reads_optional_smtp_transport_overrides():
settings = StrategyPluginEmailSettings.from_object(
SimpleNamespace(
crisis_alert_email_recipients="voice@example.com",
crisis_alert_email_sender_email="sender@example.com",
crisis_alert_email_sender_password="secret",
crisis_alert_email_smtp_host="smtp.example.com",
crisis_alert_email_smtp_port="587",
crisis_alert_email_smtp_security="starttls",
strategy_plugin_alert_email_recipients="voice@example.com",
strategy_plugin_alert_email_sender_email="sender@example.com",
strategy_plugin_alert_email_sender_password="secret",
strategy_plugin_alert_email_smtp_host="smtp.example.com",
strategy_plugin_alert_email_smtp_port="587",
strategy_plugin_alert_email_smtp_security="starttls",
)
)

Expand Down
28 changes: 14 additions & 14 deletions tests/test_strategy_plugin_push_notifications.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,8 +135,8 @@ def test_publish_strategy_plugin_push_alerts_skips_missing_config():
assert result.sent_count == 0
assert result.skipped_count == 1
assert result.deliveries[0].reason == "missing_push_config"
assert "CRISIS_ALERT_PUSH_RECIPIENTS" in result.deliveries[0].error
assert "CRISIS_ALERT_PUSH_APP_TOKEN" in result.deliveries[0].error
assert "STRATEGY_PLUGIN_ALERT_PUSH_RECIPIENTS" in result.deliveries[0].error
assert "STRATEGY_PLUGIN_ALERT_PUSH_APP_TOKEN" in result.deliveries[0].error
assert observed == []


Expand Down Expand Up @@ -200,22 +200,22 @@ def test_publish_strategy_plugin_push_alerts_skips_duplicate_marker(tmp_path):
def test_push_settings_reads_pushover_and_ntfy_config_from_object():
pushover = StrategyPluginPushSettings.from_object(
SimpleNamespace(
crisis_alert_push_recipients="user-key",
crisis_alert_push_provider="pushover",
crisis_alert_push_app_token="app-token",
crisis_alert_push_device="iphone",
crisis_alert_push_priority="1",
strategy_plugin_alert_push_recipients="user-key",
strategy_plugin_alert_push_provider="pushover",
strategy_plugin_alert_push_app_token="app-token",
strategy_plugin_alert_push_device="iphone",
strategy_plugin_alert_push_priority="1",
)
)
ntfy = StrategyPluginPushSettings.from_object(
SimpleNamespace(
crisis_alert_push_recipients="risk-topic",
crisis_alert_push_provider="ntfy",
crisis_alert_push_access_token="access-token",
crisis_alert_push_api_base_url="https://ntfy.example.test",
crisis_alert_push_priority="5",
crisis_alert_push_tags="warning",
crisis_alert_push_body_max_chars="300",
strategy_plugin_alert_push_recipients="risk-topic",
strategy_plugin_alert_push_provider="ntfy",
strategy_plugin_alert_push_access_token="access-token",
strategy_plugin_alert_push_api_base_url="https://ntfy.example.test",
strategy_plugin_alert_push_priority="5",
strategy_plugin_alert_push_tags="warning",
strategy_plugin_alert_push_body_max_chars="300",
)
)

Expand Down
22 changes: 11 additions & 11 deletions tests/test_strategy_plugin_sms_notifications.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,10 +88,10 @@ def test_publish_strategy_plugin_sms_alerts_skips_missing_config():
assert result.sent_count == 0
assert result.skipped_count == 1
assert result.deliveries[0].reason == "missing_sms_config"
assert "CRISIS_ALERT_SMS_RECIPIENTS" in result.deliveries[0].error
assert "CRISIS_ALERT_SMS_ACCOUNT_ID" in result.deliveries[0].error
assert "CRISIS_ALERT_SMS_AUTH_TOKEN" in result.deliveries[0].error
assert "CRISIS_ALERT_SMS_SENDER or CRISIS_ALERT_SMS_MESSAGING_SERVICE_ID" in result.deliveries[0].error
assert "STRATEGY_PLUGIN_ALERT_SMS_RECIPIENTS" in result.deliveries[0].error
assert "STRATEGY_PLUGIN_ALERT_SMS_ACCOUNT_ID" in result.deliveries[0].error
assert "STRATEGY_PLUGIN_ALERT_SMS_AUTH_TOKEN" in result.deliveries[0].error
assert "STRATEGY_PLUGIN_ALERT_SMS_SENDER or STRATEGY_PLUGIN_ALERT_SMS_MESSAGING_SERVICE_ID" in result.deliveries[0].error
assert observed == []


Expand Down Expand Up @@ -165,13 +165,13 @@ def test_publish_strategy_plugin_sms_alerts_skips_duplicate_marker(tmp_path):
def test_sms_settings_reads_twilio_config_from_object():
settings = StrategyPluginSmsSettings.from_object(
SimpleNamespace(
crisis_alert_sms_recipients="(516) 548-0265",
crisis_alert_sms_provider="twilio",
crisis_alert_sms_account_id="AC123",
crisis_alert_sms_auth_token="secret",
crisis_alert_sms_messaging_service_id="MG123",
crisis_alert_sms_api_base_url="https://twilio.example.test",
crisis_alert_sms_body_max_chars="160",
strategy_plugin_alert_sms_recipients="(516) 548-0265",
strategy_plugin_alert_sms_provider="twilio",
strategy_plugin_alert_sms_account_id="AC123",
strategy_plugin_alert_sms_auth_token="secret",
strategy_plugin_alert_sms_messaging_service_id="MG123",
strategy_plugin_alert_sms_api_base_url="https://twilio.example.test",
strategy_plugin_alert_sms_body_max_chars="160",
)
)

Expand Down
Loading