Skip to content

Conversation

@JacobCoffee
Copy link
Owner

@JacobCoffee JacobCoffee commented Jan 22, 2026

Summary

Changes

Class Fields Purpose
BaseGuildSetting - Abstract base class for common validation
GuildModelSetting 7 Core guild settings (prefix, channels, linking)
GitHubConfigSetting 3 GitHub integration settings
SOTagsSetting 1 StackOverflow tags configuration
AllowedUserSetting 1 User permissions configuration
ForumSetting 12 Help and showcase forum configuration

UpdateableGuildSetting now uses multiple inheritance from all focused classes.

Test plan

  • All 1125 tests pass
  • Added comprehensive tests for each new focused setting class
  • Verified backwards compatibility with existing API endpoints
  • Manual test: PATCH /api/guilds/{id}/update endpoint still works

🤖 Generated with Claude Code

Summary by Sourcery

Split the monolithic guild update schema into focused setting classes while preserving the existing composite schema for backwards compatibility.

New Features:

  • Introduce BaseGuildSetting as a shared abstract base for guild-related settings.
  • Add focused setting schemas for guild model, GitHub config, StackOverflow tags, allowed users, and forum configuration.

Enhancements:

  • Refactor UpdateableGuildSetting to be a composite of the new focused setting classes via multiple inheritance.
  • Extend schema documentation and inheritance tests to cover the new setting classes and ensure consistent base-model behavior.

Tests:

  • Add dedicated unit tests for each new focused setting class covering validation, field counts, and serialization.
  • Update shared schema tests to assert docstrings, base-model inheritance, and the inheritance hierarchy for UpdateableGuildSetting and its components.

Resolves GH #89 - Split monolithic UpdateableGuildSetting schema into
single-responsibility classes for better maintainability:

- BaseGuildSetting: Abstract base class for common validation
- GuildModelSetting: Core guild settings (prefix, channels, linking)
- GitHubConfigSetting: GitHub integration settings
- SOTagsSetting: StackOverflow tags configuration
- AllowedUserSetting: User permissions configuration
- ForumSetting: Help and showcase forum configuration

UpdateableGuildSetting now uses multiple inheritance from all focused
classes, maintaining full backwards compatibility with the existing API.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Jan 22, 2026

Reviewer's Guide

Refactors the monolithic UpdateableGuildSetting schema into a small hierarchy of focused setting classes that share a common BaseGuildSetting, then composes them back into UpdateableGuildSetting via multiple inheritance while updating tests to validate the new structure and maintain backwards compatibility.

Class diagram for refactored guild setting schemas

classDiagram
    class CamelizedBaseModel

    class BaseGuildSetting {
    }

    class GuildModelSetting {
        +str prefix
        +int help_channel_id
        +int showcase_channel_id
        +int thread_channel_id
        +bool discussion_linking
        +bool comment_linking
        +bool pep_linking
    }

    class GitHubConfigSetting {
        +bool discussion_sync
        +str github_organization
        +str github_repository
    }

    class SOTagsSetting {
        +list~str~ tag_name
    }

    class AllowedUserSetting {
        +int allowed_user_id
    }

    class ForumSetting {
        +bool help_forum
        +str help_forum_category
        +bool help_thread_auto_close
        +int help_thread_auto_close_days
        +bool help_thread_close_on_solution
        +bool help_thread_sync
        +bool showcase_forum
        +str showcase_forum_category
        +bool showcase_thread_auto_close
        +int showcase_thread_auto_close_days
        +bool showcase_thread_close_on_solution
        +bool showcase_thread_sync
    }

    class UpdateableGuildSetting {
    }

    CamelizedBaseModel <|-- BaseGuildSetting
    BaseGuildSetting <|-- GuildModelSetting
    BaseGuildSetting <|-- GitHubConfigSetting
    BaseGuildSetting <|-- SOTagsSetting
    BaseGuildSetting <|-- AllowedUserSetting
    BaseGuildSetting <|-- ForumSetting

    GuildModelSetting <|-- UpdateableGuildSetting
    GitHubConfigSetting <|-- UpdateableGuildSetting
    SOTagsSetting <|-- UpdateableGuildSetting
    AllowedUserSetting <|-- UpdateableGuildSetting
    ForumSetting <|-- UpdateableGuildSetting
Loading

File-Level Changes

Change Details Files
Introduce a guild setting schema hierarchy with a shared base class and focused setting classes, then compose them into UpdateableGuildSetting via multiple inheritance.
  • Add BaseGuildSetting as an abstract base schema for guild settings that inherits from CamelizedBaseModel.
  • Extract core guild fields into GuildModelSetting, GitHub-related fields into GitHubConfigSetting, StackOverflow tag configuration into SOTagsSetting, allowed user configuration into AllowedUserSetting, and forum configuration into ForumSetting.
  • Make UpdateableGuildSetting inherit from the focused setting classes to maintain the same field surface for the API while improving internal separation of concerns.
  • Export the new setting classes in the module’s public interface and keep UpdateableGuildSettingEnum generation based on the composite model’s fields.
services/api/src/byte_api/domain/guilds/schemas.py
Extend unit tests to cover the new guild setting hierarchy and ensure backward-compatible behavior and invariants.
  • Import the new setting classes into the schema test module and add them to the public test symbol list.
  • Add dedicated test classes for BaseGuildSetting and each focused setting to verify inheritance from CamelizedBaseModel and BaseGuildSetting, field counts, construction, and serialization.
  • Extend schema documentation and inheritance tests to include the new classes and to confirm that UpdateableGuildSetting inherits from all focused classes and that all of them inherit from BaseGuildSetting.
tests/unit/api/test_schemas.py

Assessment against linked issues

Issue Objective Addressed Explanation
#89 Refactor the monolithic UpdateableGuildSetting schema into smaller, focused setting classes (e.g., BaseGuildSetting, GuildModelSetting, GitHubConfigSetting, StackOverflow, AllowedUsers, and Forum configs) and make UpdateableGuildSetting a composite of these.
#89 Reduce complexity further by eliminating the dynamic enum creation for UpdateableGuildSettingEnum and replacing it with an explicit, static enum of possible settings (e.g., a StrEnum listing individual fields). The PR keeps UpdateableGuildSettingEnum as a dynamically created Enum using a dict comprehension over UpdateableGuildSetting.model_fields and does not introduce a static StrEnum or equivalent explicit enum definition.

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@railway-app
Copy link

railway-app bot commented Jan 22, 2026

🚅 Deployed to the byte-pr-138 environment in byte

Service Status Web Updated (UTC)
byte ✅ Success (View Logs) Web Jan 22, 2026 at 2:04 pm

@railway-app railway-app bot temporarily deployed to byte / byte-pr-138 January 22, 2026 13:53 Destroyed
Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 2 issues, and left some high level feedback:

  • The BaseGuildSetting docstring and tests describe it as an abstract base with common validation, but it’s currently a concrete, empty wrapper around CamelizedBaseModel; consider either adding actual shared logic/constraints or updating the naming/docs/tests to better reflect its current role.
  • The new tests that assert exact field counts (e.g. len(GuildModelSetting.model_fields) == 7) are quite brittle and will fail on any future additive change; consider asserting on the presence/behavior of required fields instead of the total count.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The `BaseGuildSetting` docstring and tests describe it as an abstract base with common validation, but it’s currently a concrete, empty wrapper around `CamelizedBaseModel`; consider either adding actual shared logic/constraints or updating the naming/docs/tests to better reflect its current role.
- The new tests that assert exact field counts (e.g. `len(GuildModelSetting.model_fields) == 7`) are quite brittle and will fail on any future additive change; consider asserting on the presence/behavior of required fields instead of the total count.

## Individual Comments

### Comment 1
<location> `services/api/src/byte_api/domain/guilds/schemas.py:197-208` </location>
<code_context>


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

<issue_to_address>
**suggestion (bug_risk):** Multiple inheritance of several Pydantic models could become fragile if overlapping fields/config are introduced later.

This works while these setting models keep distinct field names and compatible `model_config`. If any later introduce overlapping fields or conflicting configs, Pydantic’s MRO can cause subtle issues (e.g., validators/config being unexpectedly overridden). Consider documenting/enforcing non-overlap (via tests or conventions), or using composition (sub-models as fields) if this model grows more complex.

```suggestion
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
        or incompatible config may lead to subtle Pydantic MRO issues
        (e.g. validators/config being overridden in unexpected ways).
        The `_assert_distinct_updateable_guild_setting_fields` guard
        below enforces this at import time.
    """


def _assert_distinct_updateable_guild_setting_fields() -> None:
    """Fail fast if any of the composed setting models overlap on fields.

    Multiple inheritance of Pydantic models relies on a stable MRO; if
    two parents introduce the same field name, behaviour becomes
    order-dependent and fragile. This guard makes such changes explicit
    by raising at import time.
    """
    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())
        )
        raise RuntimeError(
            "UpdateableGuildSetting parent models must not define overlapping fields. "
            f"Overlapping fields detected -> {details}"
        )


_assert_distinct_updateable_guild_setting_fields()
```
</issue_to_address>

### Comment 2
<location> `tests/unit/api/test_schemas.py:485-494` </location>
<code_context>
         assert data["help_thread_notify_roles"] == []


+class TestBaseGuildSetting:
+    """Tests for BaseGuildSetting schema."""
+
+    def test_base_guild_setting_is_abstract_base(self) -> None:
+        """Test BaseGuildSetting serves as an abstract base class."""
+        from byte_api.lib.schema import CamelizedBaseModel
+
+        assert issubclass(BaseGuildSetting, CamelizedBaseModel)
+        schema = BaseGuildSetting()
+        assert schema.model_dump() == {}
+
+    def test_base_guild_setting_has_docstring(self) -> None:
+        """Test BaseGuildSetting has a docstring."""
+        assert BaseGuildSetting.__doc__ is not None
+
+
</code_context>

<issue_to_address>
**question (testing):** Clarify whether `BaseGuildSetting` is meant to be instantiable and adjust the test accordingly

The docstring calls `BaseGuildSetting` an abstract base class, but this test instantiates it and checks `model_dump() == {}`. If `BaseGuildSetting` should truly be abstract, the test should instead assert that it cannot be instantiated (e.g., via `abc.ABC` or another enforcement). If it is meant to be instantiable, consider renaming the test or updating the docstring so they don’t imply abstract behavior that isn’t enforced, to avoid misleading future readers about its intended use.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@JacobCoffee
Copy link
Owner Author

We should prob deprecate SO tags since stackoverflow is effectively dead

@JacobCoffee JacobCoffee requested a review from Copilot January 22, 2026 13:56
@github-actions
Copy link

Documentation preview will be available shortly at https://jacobcoffee.github.io/byte-docs-preview/138

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR successfully refactors the monolithic UpdateableGuildSetting schema into focused, single-responsibility classes as requested in issue #89. The refactoring maintains full backwards compatibility with the existing API by using multiple inheritance.

Changes:

  • Created BaseGuildSetting as an abstract base class for common validation
  • Split settings into 5 focused classes: GuildModelSetting, GitHubConfigSetting, SOTagsSetting, AllowedUserSetting, and ForumSetting
  • Maintained UpdateableGuildSetting as a composite class using multiple inheritance for backwards compatibility
  • Added comprehensive test coverage for all new classes with inheritance and serialization tests

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
services/api/src/byte_api/domain/guilds/schemas.py Refactored UpdateableGuildSetting into 6 focused classes (1 base + 5 specialized) with proper inheritance hierarchy and maintained composite class for backwards compatibility
tests/unit/api/test_schemas.py Added comprehensive test classes for all 6 new schema classes, including validation, inheritance, field count, and serialization tests

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

- Update BaseGuildSetting docstring to clarify it's a base class, not abstract
- Add import-time field overlap guard to prevent MRO issues with multiple inheritance
- Replace brittle field count tests with required fields presence tests

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@railway-app railway-app bot temporarily deployed to byte / byte-pr-138 January 22, 2026 14:03 Destroyed
@JacobCoffee JacobCoffee merged commit 3a14d45 into main Jan 22, 2026
5 checks passed
@JacobCoffee JacobCoffee deleted the feature/refactor-guild-settings branch January 22, 2026 14:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Enhancement: Refactor the UpdateableGuildSetting class into smaller, more focused classes.

2 participants