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
93 changes: 82 additions & 11 deletions services/api/src/byte_api/domain/guilds/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,19 @@
from byte_api.lib.schema import CamelizedBaseModel

__all__ = (
"AllowedUserSetting",
"AllowedUsersConfigSchema",
"BaseGuildSetting",
"ForumConfigSchema",
"ForumSetting",
"GitHubConfigSchema",
"GitHubConfigSetting",
"GuildCreate",
"GuildModelSetting",
"GuildSchema",
"GuildUpdate",
"SOTagsConfigSchema",
"SOTagsSetting",
"UpdateableGuildSetting",
"UpdateableGuildSettingEnum",
)
Expand Down Expand Up @@ -115,10 +121,13 @@ class GuildUpdate(CamelizedBaseModel):
pep_linking: bool | None = Field(title="PEP Linking", description="Is PEP linking enabled.")


class UpdateableGuildSetting(CamelizedBaseModel):
"""Allowed settings that admins can update for their guild."""
class BaseGuildSetting(CamelizedBaseModel):
"""Base class for guild settings providing common serialization behavior."""


class GuildModelSetting(BaseGuildSetting):
"""Core guild model settings."""

"""Guild Model Settings"""
prefix: str = Field(title="Prefix", description="The prefix for the guild.")
help_channel_id: int = Field(title="Help Channel ID", description="The channel ID for the help forum.")
showcase_channel_id: int = Field(title="Showcase Channel ID", description="The channel ID for the showcase forum.")
Expand All @@ -127,23 +136,34 @@ class UpdateableGuildSetting(CamelizedBaseModel):
comment_linking: bool = Field(title="Comment Linking", description="Is comment linking enabled.")
pep_linking: bool = Field(title="PEP Linking", description="Is PEP linking enabled.")

"""GitHub Config Settings"""

class GitHubConfigSetting(BaseGuildSetting):
"""GitHub-related settings for a guild."""

discussion_sync: bool = Field(title="Discussion Sync", description="Is GitHub discussion sync enabled.")
github_organization: str = Field(title="GitHub Organization", description="The GitHub organization to sync.")
github_repository: str = Field(title="GitHub Repository", description="The GitHub repository to sync.")

"""StackOverflow Tags Config Settings"""

class SOTagsSetting(BaseGuildSetting):
"""StackOverflow tags configuration."""

tag_name: list[str] = Field(
title="StackOverflow Tag(s)",
description="The StackOverflow tag(s) to sync.",
examples=["litestar", "byte", "python"],
)

"""Allowed Users Config Settings"""

class AllowedUserSetting(BaseGuildSetting):
"""User permissions configuration."""

allowed_user_id: int = Field(title="User ID", description="The user or role ID to allow.")

"""Forum Config Settings"""
"""Help Forum"""

class ForumSetting(BaseGuildSetting):
"""Forum configuration settings."""

help_forum: bool = Field(title="Help Forum", description="Is the help forum enabled.")
help_forum_category: str = Field(title="Help Forum Category", description="The help forum category.")
help_thread_auto_close: bool = Field(
Expand All @@ -164,8 +184,6 @@ class UpdateableGuildSetting(CamelizedBaseModel):
help_thread_sync: bool = Field(
title="Help Thread Sync", description="Is the help thread GitHub discussions sync enabled."
)

"""Showcase forum"""
showcase_forum: bool = Field(title="Showcase Forum", description="Is the showcase forum enabled.")
showcase_forum_category: str = Field(title="Showcase Forum Category", description="The showcase forum category.")
showcase_thread_auto_close: bool = Field(
Expand All @@ -176,7 +194,60 @@ class UpdateableGuildSetting(CamelizedBaseModel):
)


# idk
class UpdateableGuildSetting(
GuildModelSetting,
GitHubConfigSetting,
SOTagsSetting,
AllowedUserSetting,
ForumSetting,
):
"""Allowed settings that admins can update for their guild.

This is a composite class that combines all focused setting classes
for backwards compatibility with the existing API.

Important:
All parent setting models must keep distinct field names and
compatible model_config. Introducing overlapping field names
may lead to subtle Pydantic MRO issues.
"""


def _assert_distinct_updateable_guild_setting_fields() -> None:
"""Validate that composed setting models have no overlapping fields.

Multiple inheritance of Pydantic models relies on a stable MRO; if
two parents introduce the same field name, behavior becomes
order-dependent and fragile. This guard raises at import time
if overlapping fields are detected.
"""
parents = (
GuildModelSetting,
GitHubConfigSetting,
SOTagsSetting,
AllowedUserSetting,
ForumSetting,
)

seen: dict[str, str] = {}
overlaps: dict[str, list[str]] = {}

for parent in parents:
for field_name in parent.model_fields:
if field_name in seen:
overlaps.setdefault(field_name, [seen[field_name]]).append(parent.__name__)
else:
seen[field_name] = parent.__name__

if overlaps:
details = ", ".join(f"{name}: {', '.join(sorted(owners))}" for name, owners in sorted(overlaps.items()))
msg = f"UpdateableGuildSetting parent models must not define overlapping fields. Overlapping: {details}"
raise RuntimeError(msg)


_assert_distinct_updateable_guild_setting_fields()


UpdateableGuildSettingEnum = Enum( # type: ignore[misc]
"UpdateableGuildSettingEnum",
{field_name.upper(): field_name for field_name in UpdateableGuildSetting.model_fields},
Expand Down
Loading
Loading