bots/triage-slackbot/triage_slackbot/config.py (55 lines of code) (raw):
import os
import typing as t
import toml
from dotenv import load_dotenv
from pydantic import BaseModel, ValidationError, field_validator, model_validator
from pydantic.functional_validators import AfterValidator, BeforeValidator
from triage_slackbot.category import OTHER_KEY, RequestCategory
_CONFIG = None
def convert_categories(v: t.List[t.Dict]):
categories = {}
for category in v:
categories[category["key"]] = category
return categories
def validate_channel(channel_id: str) -> str:
if not channel_id.startswith("C"):
raise ValueError("channel ID must start with 'C'")
return channel_id
class Config(BaseModel):
# OpenAI organization ID associated with OpenAI API key.
openai_organization_id: str
# OpenAI prompt to categorize the request.
openai_prompt: str
# Slack channel where inbound requests are received.
inbound_request_channel_id: t.Annotated[str, AfterValidator(validate_channel)]
# Slack channel where triage updates are posted.
feed_channel_id: t.Annotated[str, AfterValidator(validate_channel)]
# Valid categories for inbound requests to be triaged into.
categories: t.Annotated[t.Dict[str, RequestCategory], BeforeValidator(convert_categories)]
# Enables "Other" category, which will allow triager to
# route the request to a specific conversation.
other_category_enabled: bool
@model_validator(mode="after")
def check_category_keys(config: "Config") -> "Config":
if config.other_category_enabled:
if OTHER_KEY in config.categories:
raise ValidationError("other category is reserved and cannot be used")
category_keys = set(config.categories.keys())
if len(category_keys) != len(config.categories):
raise ValidationError("category keys must be unique")
return config
def load_config(path: str):
load_dotenv()
with open(path) as f:
cfg = toml.loads(f.read())
config = Config(**cfg)
if config.other_category_enabled:
other_category = RequestCategory(
key=OTHER_KEY,
display_name=OTHER_KEY.capitalize(),
oncall_slack_id=None,
autorespond=True,
autorespond_message="Our team looked at your request, and this is actually something that we don't own. We recommend reaching out to {} instead.",
)
config.categories[other_category.key] = other_category
global _CONFIG
_CONFIG = config
return _CONFIG
def get_config() -> Config:
global _CONFIG
if _CONFIG is None:
raise Exception("config not initialized, call load_config() first")
return _CONFIG