Source code for chatom.slack.config
"""Slack backend configuration.
This module provides configuration classes for the Slack backend.
"""
from pathlib import Path
from ssl import SSLContext
from pydantic import Field, SecretStr, field_validator
from ..backend import BackendConfig
__all__ = ("SlackConfig",)
[docs]
class SlackConfig(BackendConfig):
"""Configuration for Slack backend.
Attributes:
bot_token: Slack Bot User OAuth Token (xoxb-...) or path to file.
app_token: Slack App-Level Token (xapp-...) or path to file.
signing_secret: Slack signing secret for request verification.
team_id: The Slack workspace/team ID.
default_channel: Default channel ID for sending messages.
socket_mode: Whether to use Socket Mode for events.
ssl: Optional SSL context for connections.
Example:
>>> config = SlackConfig(
... bot_token="xoxb-your-token",
... app_token="xapp-your-app-token", # For Socket Mode
... signing_secret="your-signing-secret",
... )
>>> backend = SlackBackend(config=config)
"""
model_config = {"arbitrary_types_allowed": True} # noqa: RUF012
bot_token: str | SecretStr = Field(
default=SecretStr(""),
description="Slack Bot User OAuth Token (xoxb-...) or path to file.",
)
app_token: str | SecretStr = Field(
default=SecretStr(""),
description="Slack App-Level Token (xapp-...) or path to file.",
)
signing_secret: str | SecretStr = Field(
default=SecretStr(""),
description="Slack signing secret for request verification.",
)
team_id: str = Field(
default="",
description="The Slack workspace/team ID.",
)
default_channel: str = Field(
default="",
description="Default channel ID for sending messages.",
)
socket_mode: bool = Field(
default=False,
description="Whether to use Socket Mode for events.",
)
ssl: SSLContext | None = Field(
default=None,
description="Optional SSL context for connections.",
)
@field_validator("app_token", mode="before")
@classmethod
def validate_app_token(cls, v: str | SecretStr | None) -> SecretStr:
"""Validate app token - can be a token string or path to file."""
if v is None or v == "":
return SecretStr("")
# Extract string value from SecretStr
if isinstance(v, SecretStr):
v = v.get_secret_value()
if isinstance(v, str):
if not v: # Empty string
return SecretStr("")
if v.startswith("xapp-"):
return SecretStr(v)
elif Path(v).exists():
return SecretStr(Path(v).read_text().strip())
raise ValueError("App token must start with 'xapp-' or be a file path")
@field_validator("bot_token", mode="before")
@classmethod
def validate_bot_token(cls, v: str | SecretStr | None) -> SecretStr:
"""Validate bot token - can be a token string or path to file."""
if v is None or v == "":
return SecretStr("")
# Extract string value from SecretStr
if isinstance(v, SecretStr):
v = v.get_secret_value()
if isinstance(v, str):
if not v: # Empty string
return SecretStr("")
if v.startswith("xoxb-"):
return SecretStr(v)
elif Path(v).exists():
return SecretStr(Path(v).read_text().strip())
raise ValueError("Bot token must start with 'xoxb-' or be a file path")
@property
def bot_token_str(self) -> str:
"""Get the bot token as a plain string.
Returns:
The bot token string.
"""
return self.bot_token.get_secret_value() if isinstance(self.bot_token, SecretStr) else self.bot_token
@property
def app_token_str(self) -> str:
"""Get the app token as a plain string.
Returns:
The app token string.
"""
return self.app_token.get_secret_value() if isinstance(self.app_token, SecretStr) else self.app_token
@property
def signing_secret_str(self) -> str:
"""Get the signing secret as a plain string.
Returns:
The signing secret string.
"""
return self.signing_secret.get_secret_value() if isinstance(self.signing_secret, SecretStr) else self.signing_secret
@property
def has_socket_mode(self) -> bool:
"""Check if Socket Mode is configured.
Returns True if both socket_mode is enabled and app_token is set.
Returns:
True if Socket Mode is configured, False otherwise.
"""
return self.socket_mode or bool(self.app_token.get_secret_value() if isinstance(self.app_token, SecretStr) else self.app_token)